JTAG通信
入门
以下为 B站up主——“蛋饼的爹地” 制作上传的JTAG科普视频。
以下为 B站up主——“蛋饼的爹地” 制作上传的JTAG科普视频。
Vcc:来源于集电极电源电压, Collector Voltage, 一般用于双极型晶体管, PNP 管时为负电源电压, 有时也标成 -Vcc, NPN 管时为正电压
Vdd:来源于漏极电源电压, Drain Voltage, 用于 MOS 晶体管电路, 一般指正电源。 因为很少单独用 PMOS 晶体管, 所以在 CMOS 电路中 Vdd 经常接在 PMOS 管的源极上。
Vss:源极电源电压, 在 CMOS 电路中指负电源, 在单电源时指零伏或接地。
Vee:发射极电源电压, Emitter Voltage, 一般用于 ECL 电路的负电源电压。
Vbb:基极电源电压, 用于双极晶体管的共基电路。
VDD:电源电压(单极器件);芯片电源电压。(Device)
VCC:电源电压(双极器件);电路电源电压。(Circuit)
VSS:接地端、负极。(Series)
VEE:负电压供电;
VPP:编程/擦除电压。
[TOC]
TMS320F28004x 下称 本系列芯片,本系列设备型号命名方法:
开发本系列芯片可能需要使用到的文件如下:
注意:下文中《TRM》 与《SPRUI33D》为同一份文件。
C28004x 共有2种芯片封装形式: LQFP 和 VQFN 。VQFN 仅有56针,LQFP 有 100针和 64针的区别。分别用 PZ 、PM 和 RSH 三种型号尾缀与 100-pin 、64-pin 和 56-pin 相关联。根据section4.3,可以知道还有个64PMQ的尾缀。
如型号TMS320F280041PM,仅有64针脚,引脚分布如下图所示:
下图应该是F280049的系统功能框图,与F280041PM仍有些出入。
注意:F280041PM不支持 CLA(控制律加速器,Control Law Accelerator),Flash为128KB(64KW),GPIO数量为26,AIO数量为14,ADC通道为14,同时280041支持5个PGA
更正: F28004x系列均支持 CLA(控制律加速器,Control Law Accelerator),如《spru566n》中的下表示,支持type2型CLA。
TMS320F28004x 所支持外设如下:
Q: 为什么要区分type?
A: 就同一类功能而言,在C2000系列上,都会有许多不同的增强/阉割版本,就出现了支持全部基础功能的、出现支持部分扩展功能的 和 支持全部扩展功能的等各种版本,那么就需要从功能范围的本质上去描述这些区别,再用相应的 “功能+type x” 的方式来表示其支持的类别。如下图所示,为《spru556n》page 24 所描述的关于CLA的功能类别。
CLA type 0
,表示的是支持原始CLA功能的模块类型。
CLA type 2
,表示在原始功能的基础上,增加了后台代码模式,可以在后台运行通信和清理程序等任务;后台任务持续运行,直到禁用或设备/软复位;后台任务可以由外设或软件触发;其他前台任务可以按照定义的优先顺序中断后台任务;增加了使后台代码部分不被中断的规定;增加了调试功能,具有真正的软件断点支持,在调试停止期间,CLA从同一地址重新获取数据会被停止。
贴一张 系统控制基础地址 的总表:
Page17 4.2 Pin Attributes 下可以查看各封装芯片引脚的名称、编号 及 功能。(共有10页左右,不粘贴了)
Page30 4.3 Signal Routing 下可以查看引脚的路由信息。包括模拟引脚和数字引脚的复用信息。
Page41 4.4 Pin Multiplexing
下可以查看引脚的复用信息,GPIO口的默认功能就是GPIO,除了GPIO35 和
GPIO37(默认情况下是TDI 和 TDO)。GPIO口的次级功能可以通过设置
GPyGMUXn.GPIOz
和 GPyMUXn.GPIOz
寄存器位来进行。
注意:
GPyGMUXn
寄存器应在 GPyMUXn
之前配置,以避免交替复用选择对GPIO产生瞬时脉冲。表6 为GPIO的针脚复用说明,表中未列明项为GPIO复用设置保留位。
部分引脚可以被置高/低,下表展示了各引脚的设置类型。默认情况下,GPIO不可置高,但是可以通过软件进行使能。
X-BAR 即 Crossbar。
X-BAR 包含四种,分别是 输入X-BAR 、 输出X-BAR 、 CLB X-BAR 和 ePWM X-BAR。每一种 X-BAR 都以其携带的信号命名,例如输入X-BAR携带外部信号“进入”芯片内部;输出X-BAR携带内部芯片信号“输出”至GPIO上;CLB和ePWM X-BAR 则对应其外设。
输入X-BAR被用于从GPIO口引导信号至许多不同的IP块,如ADC、eCAP、ePWM 和 外部中断。
图5 展示了X-BAR的架构,表8 展示了各X-BAR输入可能的路由目的地。
输出X-BAR有8个可以路由至各GPIO模块的输出口。ePWM X-BAR 有8个可以路由至各ePWM模块的输出口。
图6 展示了 输出 X-BAR 和 ePWM X-BAR 的 信号源,这些信号源都是 内部外设 或者 输入X-BAR 的输出。
暂略
ePWM X-BAR将是信号带到ePWM模块。特殊注意,ePWM X-BAR与每一个ePWM模块处理TZ和同步的数字比较器(Digital Compare)子模块相连。
注意:ePWM X-BAR的架构 与 GPIO输出X-BAR的架构 相同(除了 输出闩 / 输出锁存器(output latch))
ePWM X-BAR 拥有8个能够路由至每个ePWM模块的输出。图9-2仅展示了单条输出(Single Output)的架构,其他输出与上图一致。
配置流程
TRIPxMUX0TO15CFT
和 TRIPxMUX16TO31CFG
寄存器选择每个mux的输入。TRIPxMUXENABLE
寄存器中使能mux。TRIPOUTINV
寄存器地对信号进行取反操作(optionally invert)。GPIO 输出 X-BAR 将信号从设备内部带到GPIO引脚。图9-4展示了GPIO 输出
X-BAR的架构。X-BAR包含8个输出,且每个输出都包含至少1个 GPIO mux位置(at
least one position on the GPIO mux),表示为 OUTPUTXBARx
。X-BAR允许 单个信号的选择 或者
最多32个信号的逻辑or操作。
GPIO输出X-BAR也有8个能够路由至GPIO模块的输出。图9-4展示了单输出的架构,与其他剩余输出的架构一致。
配置流程:
OUTPUTXBARx
输出配置一个信号(on signal per mux)(最多32
mux)。OUTPUTxMUX0TO15CFG
和
OUTPUTxMUX16TO31CFG
寄存器位每个mux选择输入。为了能够传递任何信号至GPIO,必须在 OUTPUTxMUXENABLE
寄存器中使能mux。所有已启用的mux将会在被传递到对应的
OUTPUTx
信号之前进行逻辑or操作。
可以选择性的使用 OUTPUTINV
取反信号,
本系列芯片支持以下三种之一的电源供应(在要求核心电压
VDD = 1.2V
的情况下):
注意:必须使用同一个系统电压调节器来驱动VDDIO和VDDIO_SW。
下方是有关两个电压调节器的详细描述:
暂略
设备时钟域为设备上的不同模块提供时钟输入支持,这些设备与派生时钟(derived clock)直接相连, 或通过额外的分频器连接。
F28004x 支持 内源 和 外源 两种类型时钟。内源 即芯片自带的,上电即起振的内源性时钟。外源 即需要通过引脚外接配置的。
下图展示了 本系列芯片支持的时钟系统框图,可以看到时钟源 有 INTOSC1 / INTOSC2 / X1(XTAL) 三个,由时钟源配置产生的时钟最终会产生 看门狗时钟、PLL系统时钟、芯片时钟 主要是这三种。其中 芯片时钟 又会 进一步 配置到给各种外设。
外设的时钟使用也是有区别的,其中 SYSCLK 被 ePIE、RAMs、GPIOs 和 DCSM 所使用;PERx.LSPCLK 被 SCIs 和 SPIs 所使用;CAN-Bit-Clock 被 CAN总线使用(注意,此时必须使用外源时钟进行配置);PERx.SYSCLK 被 剩余其他外设所使用。
下表 3-40 所示为《SPRUI33D》第173页 关于时钟配置相关的寄存器。
At power-up, the device is clocked from an on-chip 10 MHz oscillator (INTOSC2). INTOSC2 is the primary internal clock source, and is the default system clock at reset. It is used to run the boot ROM and can be used as the system clock source for the application.
下图展示了本系列芯片的时钟系统,本系列芯片支持两个独立的 内源时钟,直接映射为 INTOSC1 和 INTOSC2 。默认情况下,都会在芯片启动时开启,且 INTOSC2 才是 内源时钟的主时钟源,INTOSC1 是备用时钟源。这一点可以手动修改。
芯片启动时,INTOSC2 以10MHz速率起振,为 ROM的启动做准备,且可以被配置为 系统时钟。
即系统内部自带的内源时钟,INTOSCx ,可以在不外接晶振的情况下,仅用内源时钟即可驱动工作,且《sprui33》3.7.11中就以内源时钟 INTOSC2 为主时钟(仅有10MHz),讲述了系统运行时钟 SYSCLK (100MHz)的配置方法。
注意:
如果需要使用 CAN 外设,那么仅使用 INTOSCx 的频率是不足以达到CAN的频率要求的,必须使用外部时钟 XTAL 。
在使用内源时钟时,用于接外源时钟的X1引脚必须通过一个 1KΩ的电阻接地。
与其他GPIO相比,GPIO18拥有不一样的电气特性,因为是可以被当做X2来使用的。
如图5-10所示,如果要启用看门狗,其速率最高与 INTOSC1 一致。
系统还可以使用 X1 或 X2 两个接口其中之一来连接 至 外部时钟 。
外源时钟有两个接口,分别是 第41(GPIO18_X2) 和 第42(X1)。按照连接方式分类,可以分为 外源时钟模式 和 单端外源时钟模式:
图 5-12 至 5-14 展示了三种不同的外部时钟源电路:
注意:当 X1 接口被当作外源时钟接口使用时,X2接口不能被当做GPIO使用。仅当使用内源时钟时, X2 可以配置成 GPIO。
如下方所示,为 LaunchPad 硬件的时钟电路,其中 Boosterpack 是指 ???
需要使用到Boosterpack时,需要移除R35,在R31和R38上焊上0Ω的跨电阻连接器,下图为LaunchPad背面的镜像示意。
从C2000WARE套件里的《MCU025A(001)_BOM.xls》可以找到launchpads板上的板载时钟一共有2个,分别是Y1和Y2。其中,与F280049只接相连的是Y2,为Crystal。
表13 展示了(外部)输入时钟频率的范围要求
表14 展示了(外部)晶体振荡器的电气特性
表15 展示了X1的计时要求
表16 展示了PLL锁定的时间
表17 展示了内部时钟频率
表18 展示了输出时钟XCLKOUT的开断性能
表19 展示了(外部)晶体振荡器参数
表20 展示了(外部)晶体振荡器的等效电阻要求
表21 展示了(外部)晶体振荡器的电气特性
表22 展示了(内部)晶体振荡器的电气特性
下图展示了系统PLL的设置框图。
系统PLL(PLLSYSCLK)的计算方法:PLLSYSCLK = OSCCLK * IMULT * FMULT / PLLSYSCLKDIV
PLLSYSCLK : 输出时钟频率
OSCCLK : 输入时钟频率
IMULT : 整数倍频,取值范围为 [0, 127]
FMULT : 分数倍频,取值范围为 [0 , 3]
PLLSYSCLKDIV : 分频, 取值范围为 {0, 1, 2, 4, 6, 8, ..., 124, 126}
需要注意的是,内源时钟配置时,X1需要通过一个1KΩ的电阻接地。
Once the application requirements are understood, a specific clock configuration can be determined. The default configuration is for INTOSC2 to be used as the system clock (PLLSYSCLK) with a divider of 1.
The following procedure can be used to set up the desired application configuration:
Select the reference clock source (OSCCLK) by writing to CLKSRCCTL1.OSCCLKSRCSEL. To enable XTAL, follow the instructions in the previous sections.
Select the reference clock source (OSCCLK) by writing to CLKSRCCTL1.OSCCLKSRCSEL. Allow at least 300 NOP instructions for this to take effect.
Set up the system PLL if desired. TI recommends using the
C2000Ware SysCtl:setClock()
function for proper
configuration of the PLL clock.
Select the LSPCLK divider by writing to LOSPCP.
If an alternate CAN bit clock is needed, select it by writing to CLKSRCCTL2.CANABCLKSEL and CLKSRCCTL2.CANBBCLKSEL.
Enable the desired peripheral clocks by writing to the PCLKCRx registers.
The system clock configuration can be changed at run time. Changing the OSCCLK source will automatically bypass the PLL and set the multiplier to zero. Changing the multiplier from one non-zero value to another will temporarily bypass the PLL until it re-locks.
注意: At least a 300 CPU clock cycles delay is needed after OSSCLK source is changed.
以下为工作频率设置的样例,利用内源时钟 10MHz
,产生芯片满负工作频率 100MHz
:
f28004x_examples.h
文件中,包含了对 IMULT
、FMULT
、 PLLSYSCLKDIV
的变量宏定义。
官方提供的PLL配置函数原型为:void InitSysPll(Uint16 clock_source, Uint16 imult, Uint16 fmult, Uint16 divsel)
。根据配置实例中的参数,函数调用时代码如下:
1 | InitSysPll(INT_OSC2, IMULT_19, FMULT_0pt25, PLLCLK_BY_1); |
或
1 | InitSysPll(0x00, 0x13, 0x01, 0x01); |
即
10MHz * ( 19 + 1/4 ) / (2^1) = 96.25MHz
,最后的CPU工作频率为
96.25MHz,但由于内源时钟振荡器可能存在变化,CPU的工作时钟也存在 ±3%
的公差。
下面配置步骤由《TRM》第104页提供,按照连接方式,将连接模式分为:外源时钟模式 和 单端外源时钟模式
两个模式仅在第2和第7个步骤有所区别,其余均相同。
外源时钟模式:CRYSTAL or RESONATOR | 单端外源时钟模式:OSCILLATOR |
---|---|
1. Clear the XTALCR.OSCOFF bit. |
1. Clear the XTALCR.OSCOFF bit. |
2. Wait for the crystal to power up. 1ms is the typical wait time but this depends on the crystal that is being used. | 2. Set the XTALCR.SE bit to enable single-ended
mode. |
3. Clear the X1 counter by writing a 1 to X1CNT.CLR and
keep clearing until the X1 counter value in the X1CNT
register is no longer saturated 1023 (0x3ff) . |
3. Clear the X1 counter by writing a 1 to X1CNT.CLR and
keep clearing until the X1 counter value in the X1CNT
register is no longer saturated 1023 (0x3ff) . |
4. Wait for the X1 counter value in the X1CNT register
to reach 1023 (0x3ff) . |
4. Wait for the X1 counter value in the X1CNT register
to reach 1023 (0x3ff) . |
5. Repeat steps 3-4 three additional times. | 5. Repeat steps 3-4 three additional times. |
6. Select XTAL as the OSCCLK source by writing a 1 to
CLKSRCCTL1.OSCCLKSRCSEL . |
6. Select XTAL as the OSCCLK source by writing a 1 to
CLKSRCCTL1.OSCCLKSRCSEL . |
7. Check the MCLKSTS bit in the MCDCR
register. If it's set, the oscillator has not finished powering up, and
more time is required:a. Clear the missing clock status by writing a 1 to
MCDCR.MCLKCLR .b. Repeat steps 2-7. Do not reset the device. Doing so will power down the oscillator, which requires the procedure to be restarted from step 1. c. If the oscillator has not finished powering up in 10 milliseconds, there is a real clock failure. |
7. Check the MCLKSTS bit in the MCDCR
register. If it's set, either the
external oscillator or the device has failed. |
8. If MCDCR.MCLKSTS is clear, the oscillator startup is
a success. The system clock is now derived from XTAL. |
8. If MCLKSTS is clear, the switch to the external
clock is a success. The system clock is now derived from XTAL. |
CPU 为 CLA / DMA 和 绝大多数(片上)外设直接提供时钟信号,该时钟就是 PLLSYSCLK , 但会在CPU进入 HALT模式时被栅断(gate off)。
每一个外设都拥有使用 PCLKCRx 寄存器进行独立控制的时钟。
注意:当使用 PCLKCRx 时,应用需要在时钟接入外设后,等待5个 SYSCLK 周期。
如《SPRUI33D》中,表3-55所示,从第195页起可查看 PCLKCRx 相关寄存器的所有说明。
表23 展示了不同时钟源和频率下所需的最小闪存等待状态
表24 展示了闪存的性能参数
在复位时,GPIO会被配置为输入。
对于特定的输入,用户还可以选择 输入鉴定周期(input qualification cycles)的数量来过滤不需要的 噪声突变(unwanted noise glitches)。
表 29 展示了GPIO的特性参数,需要注意的是,GPIO的频率只有25MHz最高。
表 30 展示了GPIO的输入时间要求
除了CPU控制的I/O功能外,最多可有12个独立的外设信号在一个GPIO功能口上进行多路复用。每个功能口的输出都可以被外设或者CPU主机(CPU1或者CPU1.CLA)进行控制。
有两个可输入输出接口:
接口A包含GPIO1 ~ GPIO31
接口B包含GPIO32 ~ GPIO59
本设备上的模拟信号被复用在数字输入上,这些模拟输入输出口(AIO)引脚不具备数字输出的能力。接口范围为:
虽然上述GPIO看起来很多,但实际上本系列芯片只有GPIOA和GPIOB的部分引脚,引脚总数为26个。
在 GPyMUXx
寄存器中,每个区域(field)都决定了每个引脚(IO
PIN)的GPIO复用配置。设置为 0x0
, 0x4
,
0x8
, 0xC
时可以将该引脚配置为
GPIO,配置为其他值时,可以选择一个外设以控制该引脚。查看设备数据手册以获得《外设复用选项表格》。
引脚必须在改变 GPyGMUXx
对应域之前,通过寄存器设置为GPIO模式。
下图8-1 展示了单个GPIO引脚上的功能框图:
从上图可以看出来,GPIO可以设置为输出或输入
输入时可以通过 GPyPUD
寄存器设置为是否
拉高,然后通过 GPyINV
寄存器设置是否信号
反转,GPyCTRL
寄存器控制着4个字段(每个字段独立控制8个GPIO)的
确认采样周期(qualification sampling
period),通过设置该寄存器可以设置其 采样速率范围 为
{0, 2, 4, ...508, 510} 个 SYSCLK 周期;GPyQSEL1/2
寄存器控制着 输入确认类型(可选为
同步/三采样/六采样/异步),最后可以输出至
四个方向(外设、X-BAR、CPU1 和 CPU1CLA)。
输出暂略。
The input and output paths are entirely separate, connecting only at the pin.
Peripheral muxing takes place far from the pin.
As a result, it is always possible for both CPUs and CLAs to read the physical state of the pin independent of CPU mastering and peripheral muxing.
Likewise, external interrupts can be generated from peripheral activity. All pin options such as input qualification and open-drain output are valid for all masters and peripherals.
However, the peripheral muxing, CPU muxing, and pin options can only be configured by CPU1.
《SPRUI33D》第857页起讲述GPIO特性,第870页起讲述相关寄存器信息。
如 表 8-8 所示,GPIO拥有两类寄存器,分别是 GPIO控制寄存器 和 GPIO数据寄存器 。另,控制/数据寄存器 下都有 GPIO A / GPIO B / GPIO H 三种区别。表 8-9 是GPIO控制寄存器的详细介绍,表 8-54 是GPIO数据寄存器的详细介绍。
FUNCTIONS | GPIO A | GPIO B | GPIO H |
---|---|---|---|
PINS | GPIO0 ~ GPIO31 |
GPIO32 ~ GPIO63 |
GPIO224 ~ GPIO255 |
QUALIFICATION SAMPLING PERIOD | Y | Y | Y |
QUALIFICATION TYPE | Y | Y | Y |
PERIPHERAL MUX | Y | Y | Y |
DIRECTION | Y | Y | |
PULL-UP DISABLE | Y | Y | Y |
INPUT INVERSION | Y | Y | Y |
OPEN DRAIN OUTPUT MODE | Y | Y | |
ANALOG MODE SELECT | Y | Y | |
PERIPHERAL GROUP MAX | Y | Y | |
MASTER CORE SELECT | Y | Y | |
LOCK | Y | Y | Y |
LOCK COMMIT | Y | Y | Y |
配置GPIO的通用流程如下:
GPyMUX1/2
和 GPyGMUX1/2
寄存器写入适当的值来实现。当改变一个引脚的GPyGMUX值时,一定要先将相应的GPyMUX位设置为0,以避免在复用中出现突发。默认情况下,所有引脚都是通用的I/O,而不是外围信号。GPyPUD
中的相应位。所有的上拉电阻在默认情况下是禁用的。当没有外部信号驱动时,上拉可以用来保持输入引脚处于已知状态。GPyCTRL
寄存器中选择输入鉴定的采样周期,而在
GPyQSEL1
和 GPyQSEL2
寄存器中选择鉴定的类型。默认情况下,所有鉴定都是同步的,采样周期等于
PLLSYSCLK
。关于输入鉴定的解释,请参见第8.4节。GPyDIR
寄存器指定该引脚的方向为输入或输出。默认情况下,所有的GPIO引脚都是输入。在改变引脚为输出之前,通过向
GPySET
、GPyCLEAR
或 GPyDAT
寄存器写入要驱动的值来加载输出闩锁。一旦锁存器被加载,写入
GPyDIR
来改变引脚的方向。默认情况下,所有输出锁存器为零。GPIO 0-63
可以用来将系统从低功耗模式唤醒。要选择一个或多个GPIO进行唤醒,请写到
GPIOLPMSEL0
和 GPIOLPMSEL1
寄存器的相应位。这些寄存器是CPU系统寄存器空间的一部分。关于低功耗模式和GPIO唤醒的更多信息,请参见系统控制和中断章节中的低功耗模式部分。XINTnCR
寄存器进行配置。其次,必须通过选择输入X-BAR信号4、5、6、13和 14
的来源来设置XINT1-5
GPIO引脚。关于输入X-BAR结构的更多信息,请参见本手册的Crossbar(XBAR)章节。如 GPIO配置手册
所示,GPIO_SetupPinMux();
和
GPIO_SetupPinOption();
并不存在,这两个是存在于
f28004x_gpio.h
中的自定义。
1 | // |
本函数用于将指定引脚与函数进行绑定,绑定前会自动检查对应的CPU和外设是否可用。
然后,创建指向相应寄存器的指针,这是对GPIO寄存器定义方式的一种变通。
头文件中的标准定义使得对一个寄存器或位进行命名访问非常容易,但很难进行任意的数字访问。有一个具有相同寄存器的GPIO模块阵列,包括像
GPyCSEL1-4
这样的多寄存器组的阵列,会更容易。但是头文件没有定义任何我们可以变成数组的东西,所以就用手动指针运算来代替。
要改变多路复用,首先将外设多路复用设置为0/GPIO,以避免出现故障,然后改变组复用,再将外设多路复用设置为目标值。最后,设置CPU选择。这个过程在《TRM》中有所描述。不幸的是,由于我们事先不知道引脚,我们不能硬编码一个位域参考,所以这里有一些棘手的位操作。
警告:该代码不涉及模拟模式选择寄存器。
1 | // |
为指定的引脚设置输入/出,可以通过已定义的标志(flags)来设置,这些标志都是16位的掩码。
输入引脚可用的标志如:
GPIO_PULLUP
使能拉高
GPIO_INVERT
输入极性翻转
GPIO_SYNC
与PLLSYSCLK同步输入阀(这条是默认的,可以不单独设置)
GPIO_QUAL3
使用3采样质量
GPIO_QUAL6
使用6采样质量
GPIO_ASYNC
不使用同步或质量
输出引脚可用标志如下:
GPIO_OPENDRAIN
工作在输出开漏模式
GPIO_PULLUP
输出置高,
注意:如果输出开漏模式打开,也就自动打开了置高。输入配置里的 SYNC / QUAL3 / QUAL6 / ASYNC 只允许启用一个。
默认输入状态:未同步(ASYNC)且无 置高(PULL-UP)和 极性翻转(INVERT)。
默认输出状态:标准数字输出。
1 | void GPIO_SetupPinOptions(Uint16 gpioNumber, Uint16 output, Uint16 flags) |
此处的中断更多的是讲 外设中断,当然后面也会涉及到定时器中断部分,因此统称为中断了,阅读是需要加以区分。
C28x系列芯片有14条外设中断可用,其中13和14中断被直接关联至CPU定时器1和定时器2。 从中断向量表里,也只能看到第1组到第12组中断向量,要绑定定时器1和定时器2的中断程序的话,需要绑定到(不存在与中断向量表)上的第13和第14中断。
剩余12条中断(就是第1~第12条中断)可以通过 增强型外围中断扩展(enhanced Peripheral Interrupt Expansion,ePIE)模块进行信号引导。本模块将最多16个外设中断复用到每个CPU中断线中,并扩展了向量表,允许每个中断有自己的ISR。允许芯片支持数量众多的外围设备。
中断路径一共有三个阶段—— 外设中断阶段、PIE模块阶段 和 CPU阶段。每个阶段都有其使能和标志寄存器。本系统允许CPU在其他中断等待时处理一个中断,在软件中实现并优先处理嵌套中断,并在某些关键任务中禁用中断。
外部中断(external interrupt, XINT)电气特性数据和时序特性:
表31 列出了外部中断的时间要求
表32 列出了外部中断开断特性
图25 展示了外部中断的时序
下图展示了本系列芯片的中断架构。
每个外设都有其独立的中断配置,在每个设备的章节中都有描述。某些外设允许多种事件来触发相同的中断信号。例如,通信外设就可能使用相同的中断来检查数据是否被接收或者是否存在传输错误。中断的触发状态可以通过查看外设状态寄存器来确定。通常,在下一次中断生成前,需要手动清除前一次在状态寄存器上的数据位。
PIE控制器是指用于外围设备中断控制(Peripheral Interrupt Control)的寄存器。
PIE为每一个外设中断信号都提供了独立的标志和使能寄存器位,这些外设中断信号通常也被称为 PIE通道。这些通道根据关联的CPU中断(associated CPU interrupt)被进行绑定成 组(group)。每一个PIE组都拥有一个16bit的 使能寄存器 PIEIERx 和 一个16bit的 标志寄存器 PIEIFRx ,并且在PIE确认寄存器 PIEACK 中还有一个bit。PIEACK register bit acts as a common interrupt mask for the entire PIE group.
当CPU接收一个中断时,从PIE中查找到中断服务程序的地址(fetches the address of the ISR)。【因此写完中断服务程序时需要将程序的地址绑定到PIE上,如下面代码。】PIE返回组中 同时被使能且被标志了的(both flagged and enabled) 最低数字的通道(lowest-numbered channel)的向量地址。在多数中断都被 阻塞(pending)时,这相当于给了低优先级中断一个较高的权限。
如果没有中断被同时标志和使能,PIE就会返回通道1的向量地址。这种情况通常不会发生,只有在 PIE改变状态时 中断也同时 生成才会。(software changes the state of the PIE while an interrupt is propagating)
1 | EALLOW; |
和PIE相似,CPU
也为每一个中断提供了对应的标志和使能寄存器位。在CPU内部寄存器中,有一个
中断使能寄存器(IER)和一个
中断标志寄存器(IFR)。CPU内部还有一个由
ST1 寄存器下的 INTM
位 控制的 全局中断屏蔽(global interrupt
mask),该屏蔽可以用CPU的 SETC
和 CLRC
指令进行进行设置和清除操作。对应到C语言的代码中,就是C2000套件的宏定义
DINT
和 EINT
。
对 IER
和 INTM
的写入都是原子操作。原则上,一旦设置了 INTM
位,队列(pipeline)里的下个指令将会在所有中断都被关闭的情况下运行,不需要软件延迟。
当外设生成中断(在第x组PIE,第y通道)时,以下事件顺序将会触发:
PIEIFRx.y
PIEIERx.y
被设置 ,中断生成PIEACK.x
被清除,中断生成且 PIEACK
被设置IFR.x
IER.x
被设置,中断生成INTM
被清除,CPU接收中断IFR.x
和 IER.x
被清除。INTM
被设置。EALLOW
被清除。PIEIFR.y
被清除所谓中断延迟是指介于 PIEIFRx.y
锁存中断
和 第一个ISR指令进入CPU队列的执行阶段 之间的时间。
最小的中断延迟时间是14个系统周期,在ISR或堆内存中的等待时间也会增加延迟。
外部中断为了 GPIO同步 和 输入限制(input qualification),增加了最少2个系统周期。
使用C28x RPT
指令创建的循环无法被中断。
在 cputimer.h
头文件中,可以看到定时器配置的结构体如下:
1 | struct CPUTIMER_REGS { |
在 cputimervars.h
头文件中,直接配置了如下结构体:
1 | // |
通过在结构体内声明寄存器指针,并在结构体外定义对应的结构体变量:
1 | // |
在对CPU定时器初始化时,则不是操作 struct CPUTIMER_VARS
,而是直接操作对应的三个定时器(timer0 ~ timer2)的寄存器。
在后续对定时器的配置中,也只是将 系统频率
CPUFreqInMHz
和 定时器周期
PeriodInUSec
的配置进行了较为方便的配置而已,书写成嵌套结构体的形式也节省了在函数内声明变量,方便了多个定时器不同配置的相似处理,在代码实际执行过程中,仍然是对寄存器进行直接操作。
定时器清空:
1 | CpuTimer0.RegsAddr = &CpuTimer0Regs; // 初始化地址指针指向对应的定时器寄存器 |
定时器配置:
1 | Uint32 temp; |
将定时器的清空和配置两个操作步骤类似,可以简单归纳成流程图形式:
graph LR 初始化周期 --> 设定定时器速率 --> 设置定时器工作方式
上电时,默认情况下任何中断都不被使能。PIEIER 寄存器和 IER寄存器被清除, INTM寄存器被设置。应用代码负责配置和使能所有外设中断。
以下为中断使能步骤:
DINT
或者
SETC INTM
)PIECTRL
寄存器的 ENPIE
位来使能PIE。PIEIERx
位。(不要使用直接连接至CPU的定时器1和定时器2)EINT
或 CLRC INTM
)上表中,横向为组,如 [x,y],x为组。
上方使能中断的流程,即中断初始化和配置的流程,重新用流程图的方式总结如下:
graph LR 初始化PIE --> 关闭所有中断 --> 清空PIE向量表 --> 将ISR绑定中断向量表 -->初始化CPU定时器 -->调整CPU定时器周期和工作方式 --> 打开对应的组中断 --> 打开对应的中断向量 --> 使能中断
以下代码为上方流程的示例,部分代码也仅为函数封装,具体内容需要参考详细文档描述。
1 | InitPieCtrl(); //初始化PIE(外设中断)模块 |
ISR中断处理程序基本与普通程序一直,以下几点需要遵守:
PIEACK
位IRET
指令来返回如果函数定义时使用了 __interrupt
关键字,第一条和第三条会由C编译器自动处理。
与中断组相关的PIEACK 位必须在用户代码中手动清除,通常是在ISR的末尾执行该操作。如果PIEACK没有被清除,那么CPU将不会继续接收改组的中断。此处与不经过PIE的定时器中断有所不同。
在使用了 __interrupt
关键字后,中断处理时,唯一需要注意的就是对该中断的组进行中断确认,如果没有确认,该中断只会被进行一次,确认组中断的代码如下:
1 | PieCtrlRegs.PIEACK.all = PIEACK_GROUP1; //确认组1的中断 |
为关闭所有中断,需通过 DINT
和 SETC INTM
来设置CPU的全局中断屏蔽。不需要在设置 INTM
或 修改IER后添加
NOP
,在中断关闭后会有下一条指令将被执行。
通过操作 PIEIERx
寄存器能够关闭独立中断,但需要避免竞争发生。当 PIEIER
写入完成时,如果中断信号准备好要生成,该中断信号将会直接到达CPU并且造成假中断的触发。为避免假中断的触发,应循以下步骤:
DINT
或 SETC INTM
)PIEIER
位PIEACK
位EINT
或 CLRC INTM
)使用CPU的 IER 寄存器可以禁用中断组。此时不会产生竞争,因此无需特殊的程序进行处理。
PIEIER
位不可以被软件清除(must never be cleared in
software),因为 读/写/改 操作可能会对进入CPU的中断造成影响(may cause
incoming interrupts to be lost)。唯一安全的清除 PIEIER
位的方法是让CPU处理该中断。以下程序步骤可以用于绕过普通ISR:
DINT
或 SETC INTM
)PIEIFR
位的中断向量映射至空的ISR上,该ISR将只包含一个来自中断指令
IRET
的返回(contain a return from interrupt
instruction)EINT
或 CLRC INTM
)DINT
或 SETC INTM
)PIEACK
位EINT
或 CLRC INTM
)默认情况下,中断不会嵌套。通过软件操控 IER
和
PIEIERx
寄存器可以实现对中断的嵌套和优先级排序。详见 TI
Wiki
ePIE向量表有两个备份,初级向量表地址在 0xD00
到
0xEFF
之间,冗余向量表的地址在 0x01000D00
到
0x01000EFF
之间。对初级向量表的写入时也将会对冗余向量表进行写入,对冗余向量表的写入仅对冗余向量表有效。读取时,两个向量表互相独立。
在向量地址获取时,ePIE将会对两个向量表的输出进行比较。如果没有差别,CPU会生成一个地址并存放到
PIEVERRADDR 寄存器中(the CPU branches to the
address in the PIEVERRADDR
register)并发送错误信号(trip signals)给PWM外设。如果
PIEVERRADDR 寄存器值没有被设置,地址
0x003FFBE
上用于默认启动的ROM句柄将会被使用。
《TRM》P89至P95.
下表来自《TRM》P506。下表中,寄存器分为4类:总控制、确认、使能 和 标志。
本系列芯片拥有两种 时钟门(clock-gating) 低电量模式,分别是 HALT 和 IDLE,STANDARD 模式不支持进入低电量。
低电量模式的进入和退出代码,以及更多关于低电量模式的描述信息,需要参考《TRM》手册。
表33 描述了系统进入任何 时钟门 低电量模式后的影响
表34 列明了IDLE模式下的时序要求
表35 列明了IDLE模式下的开断特性
表36 列明了HALT模式下的时序要求
表37 列明了HALT模式下的开断特性
图26 展示了IDLE模式的时序框图
图27 展示了HALT模式的时序框图
在涉及到一些需要使用浮点数计算的场景,就需要打开CLA。如果不打开,会发现32bit的系统上居然出现了int数据类型只有16bit的情况。
控制律加速器(CLA)2型是一个独立的、完全可编程的、32位浮点数学处理器,它为C28x系列带来了并发的控制回路执行。CLA的低中断延迟允许它“及时”读取ADC示例。这显著减少了ADC样本的输出延迟,以实现更快的系统响应和更高的MHz控制回路。通过使用CLA服务于时间关键的控制回路,主CPU可以自由地执行其他系统任务,如通信和诊断。
CLA有以下特性:
C28x的主芯片可以接入至CLA和vice versa。
CLA能够接入三种内存,包括:程序内存、数据内存和信息内存。每一种内存的行为和仲裁后续都会进行描述,CLA内存由DCSM模块(Dual Code Security Module,双代码安全模块)进行保护,可以参考系统控制和中断章节以获取更多安全计划相关的细节。
暂略
暂略
暂略
暂略
暂略
暂略
本系列芯片的模拟外设包含 ADC 、 PGA 、 DAC 和 CMPSS。
所有的模拟引脚一共有16个,包括ADC三个模块(module,A、B、C)、PGA、DAC等都是复用的。
《TRM》P1420, 下表为模拟子系统寄存器详细:
模拟子系统寄存器特征:(《TRM》P1410)
VREFHIx
和 VSSA
引脚提供电压参考VREFHIx
和 VSSA
引脚提供参考VDDA
和 VSSA
引脚提供参考以下为64针封装芯片的模拟系统框图:
本系列芯片的ADC外设是 第5类(TYPE 5)ADC,只支持12位单端模式。以下讨论中可能会看到 ADCA / ADCB / ADCC 三种不同的描述,其实是ADC三个大的模块,模块下面又有很多个通道。
单个ADC模块的简化示意图如下:
ADC模拟外设的内部架构如下:
SOC和EOC的内部架构如下:
ADCSOC0
~ ADCSOC15
)INT1
~
INT4
)每个通道的寄生电容
ADC模块的 配置项 分为 可配置(Configurable)、不可配置(Unconfigurable) 两种。可配置项又分为 可按模块独立配置 和 全局配置(Globally configurable) 两种。
Options | Configurability |
---|---|
时钟 Clock | 每个模块(A/B/C) |
分辨率 Resolution | 不可配置(仅12bit) |
信号模式 Signal mode | 不可配置(仅单端(Single-ended)) |
参考电压源 Reference voltage source | 每个模块(外部或内部) |
触发源 Trigger source | 每个SOC |
转换通道 Converted Channel | 每个SOC |
获取窗口期 Acquisition window duration | 每个SOC |
EOC定位 EOC location | 每个模块 |
爆发模式 Burst mode | 每个模块 |
注意:这里的 SOC 是指 ADCSOC,start of conversion。
ADC基础时钟由系统时钟 SYSCLK 提供,用以生成ADC 获取窗口(acquisition window)。寄存器 ADCCTL2 有一个决定ADC时钟 ADCCLK 的预分频(prescale)区域。ADCCLK 直接为转换器(converter)提供时钟信号。
转换核心需要约10.5个 ADCCLK 时钟周期来将 输入电压转换成 数字信号,因此需要自行决定和配置获取窗口所需要的周期大小。
ADC分辨率决定着最终模拟信号能够转换成的数值大小。本系列芯片支持12bit分辨率。
本系列芯片的第16、17引脚是ADC的参考电压:
根据《TRM》P1442可知,每个ADC模块都可以配置一个单独的参考电压(包括 VREFHI和 VREFLO),参考电压源可以是内部或外部。需要注意的是,引脚数较少的封装可能在多个ADC之间共享一个VREFHI引脚。在这种情况下,共享一个参考引脚的ADC必须将它们的参考模式配置得完全一样。
在外部参考电压模式下,参考电压源的针脚被当做比率测量参考,以测定ADC转换的输入范围。
以下几点需要注意:
选择内部参考电源时,将由芯片设备来为 VREFHI
提供参考电源。 此电源电压可以被配置为 2.5V
或
1.65V
,当配置 1.65V
时,模拟输入的范围最大为
3.3V
。
注意:内部参考模式同样要求给 VREFHI针脚外接一个电容(具体电容值视具体情况而定)。
某些封装中,多个ADC电压参考针脚可能被捆绑在了一起。这种情况下,当选择外部或内部参考模式,以及选择
3.3V
或 2.5V
内部参考电压范围时,有必要对ganged参考进行相同的配置。
例如,如果 ADC B 和 ADC C参考电源针脚被捆绑在一起,并且需要一个2.5V的内部参考电源时,以下配置代码可供参考:
1 | //ADCB VREFHI and ADCC VREFHI share a pin |
内部硬件设备将确保相同针脚上的多个参考电源不会冲突。(ensure multiple references don't drive conflicting voltages onto the same pin)正因如此,参考电源可以被配置于任意顺序及任意时间。
电压参考模式可以用C2000套件提供的 SetVREF()
或
ADC_setVREF()
函数进行配置。使用这些函数时需确保
修正值(correct trim)已被载入 ADC trim
寄存器,需要确保设备复位后这些函数至少被调用一次,同时不要通过直接写入
ANAREFCTL 寄存器来配置电压参考模式。
本系列芯片支持单端模式,通过单根针脚 ADCINx
采样转换器的输入电压,参考点为 VREFLO
。
基于给定的模拟输入电压,预期情况下的数字转换结果如下表所示。小数部分省略。
模拟输入 | 数字输出 |
---|---|
当输入电压低于参考低电压 ADCINy ≤ VREFLO |
最小量程 0x00 |
当输入电压在高低参考电压之间 VREFLO < ADCINy < VREFHI |
最大量程 0xFFF 和
输入电压差 / 参考电压差 之积即 0xFFF * (ADCINy - VREFLO) / (VREFHI - VREFLO) |
当输入电压超过参考高电压 ADCINy ≥ VREFHI |
最大量程 0xFFF (即
2^(12)-1 ) |
基于给定的ADC数字转换结果,来反推理想的对应模拟输入的结果。公式就用上面的表格来反推就好了。
寄存器这边除了描述最基本的 基地址 (Base Address)以外,还有两类寄存器:结果寄存器(Result Register)和 控制寄存器(Control Register)。
BSY
即 busy ,繁忙状态。
ADCCTL1
包括只读位和读写位。
ADCBSY
为
ADC是否正在工作,ADCBSYCHN
为 哪个通道在工作。ADCPWDNZ
为
ADC的打开和关闭、INTPULSEPOS
为 ADC脉冲位置ADCCTL2
目前仅有 一个可读写位,即 PRESCALE
,ADC模块的时钟预分频。设备开启或系统级复位时,ADC会被断电禁用。为ADC上电启用时,请遵循以下顺序:
PCLKCR13
寄存器中的指定位来启用所需 ADC 时钟ADCCTL2
寄存器中的 预分频 PRESCALE
位来设置 ADC的时钟分频ADCCTL1
寄存器中的 ADCPWDNZ
位来启用
ADC注意:如果多路ADC需要同步开启,第一步和第三步可以通过 同一条写入指令(in one write instruction)来进行配置。因此,也就可以用同一条延迟指令来等待ADC上电启动。
Each SOC is a configuration set defining the single conversion of a single channel.
注意上面这句话,每一个SOC都是一个定义着单通道转换器的配置集(Configuration Set)。
如果有需要,多个SOC可以配置于使用相同的触发器、通道 或 采样周期。使用相同的触发器来配置多个SOC的话,触发器可以生成一个转换序列(generate a sequence of conversions),同时相同的触发器和通道可以过采样(oversampling)。
在SOC配置集中有三个可配置项:
ADCSOCFLG1
启动转换的触发源(the trigger source that
starts the
conversion),操作某个bit可以启动某个SOC,详见《TRM》P1519CHSEL
转换通道(the channel to convert),可取值范围为
0h
~ Fh
,代表 ADC0 ~ ADC15ACQPS
捕获窗口/采样周期(the acquisition window
duration),可取值范围为 000h
~ 1FFh
Upon receiving the trigger configured for a soc, the wrapper will ensure that the specified channel is captured using the specified acquisition window duration.
注意:Acquisition Window 和 ACQPS 不是同一个概念,Acquisition Window 就是下方所述的 S 和 H 的持续时间。
参考ADC输入模型简图:
采样周期和 ACQPS的关系如下:
以下案例在求解采样周期的基础上,倒推 ACQPS 的设置:
为了能够正确读取ADC数据,ADC输入信号必须有足够的时间为
采样和保持电容 Ch
(the sample and hold
capacitor)充电。通常来说,S 和 H
的持续时间的选择是为了使采样电容被充电到最终值的 ½ LSB 或者 ¼ LSB
,具体取决于可容忍的 settling 误差(tolerable settling error)。
可以用 RC settling 模块来确定所需的 settling 时间近似值。具体时间常数模型如下方公式:
所需时间常数的数量也由下面等式给出:
最终S 和 H 的持续时间如下方公式所示:
ADC输入模块需要提供以下参数:
n
= ADC 分辨率 (in bits)RON
= ADC 采样开关电阻 (in Ohms)CH
= ADC 采样电容 (in pF)Cp
= ADC 通道寄生输入电容 (in pF)以下参数则取决于应用(硬件/软件)设计: - settling error = tolerable settling error (in LSBs) - Rs = ADC 驱动电流源电阻值 (in Ohms) - CS = ADC 输入引脚上的 寄生电容值 (in pF)
通过以下参数来说明计算方式:
时间常数计算如下:
所需时间常数的数量如下:
最终结果:
如果系统时间 SYSCLK 为 100 MHz
,周期为 10ns
,则 S和H的持续时间应该是
270 ns/10 ns = 27.0 SYSCLKs
,即27个系统时钟周期,为ADC输入信号所要提供的充足的ACQPS应该至少是
CEILING(27.0) – 1 = 26
While this gives a rough estimate of the required acquisition window, a better method would be to setup a circuit with the ADC input model, a model of the source impedance/capacitance, and any board parasitics in SPICE (or similar software) and simulate to verify that the sampling capacitor settles to the desired accuracy.
参数计算代码如下:
1 | /*------------------------------------------------------------------------- |
官方SOC配置样例提供了四种:ePWM触发单次转换,ePWM触发超采样转换,CPU定时器触发多重转换 和 软件触发转换
当ePWM计时器到达周期时,为了配置好 ADCA模块,以生成通道 ADCIN1 上的单次转换,有以下步骤需要操作:
100ns / 10ns = 10 cycles
,ACQPS
需要设置为
10 - 1 = 9
。1 | AdcaRegs.ADCSOC5CTL.bit.CHSEL = 1; //SOC5 will convert ADCINA1 |
如上配置后,当ePWM3
到达其周期并生成一个SOCB信号,如果ADC此时处于空闲状态,ADC将会对通道
ADCINA1(SOC5)
立即进行采样;如果此时ADC繁忙,ADCINA1将会在SOC5获得权限后开始采样。ADC控制逻辑将会使用指定100ns的捕获窗口宽度对ADCINA1进行采样。在捕获完成后,ADC会立即开始转换采样电压为数字信号。当ADC转换完成时,结果将存放在
ADCRESULT5
寄存器。
ADC可以配置超采样,即在相同的采样周期下,对同一个通道采样超过1次的采样方法。因为 SOC / 通道 / 触发 等配置参数可重复的特殊性,因此可以用于配置超采样。
为了配置ADC对通道 ADCINA1 超采样到达4次,此处使用和之前相同的配置,分别应用到 SOC5 / SOC6 / SOC7 和 SOC8 上。
1 | AdcaRegs.ADCSOC5CTL.bit.CHSEL = 1; //SOC5 will convert ADCINA1 |
如上配置后,当ePWM3
到达其周期并生成一个SOCB信号,如果ADC此时处于空闲状态,ADC将会对通道
ADCINA1(SOC5)
立即进行采样;如果此时ADC繁忙,ADCINA1将会在SOC5获得权限后开始采样。一旦SOC5的转换完成,SOC5
的结果将会存放于 ADCRESULT5
寄存器,同时 SOC6
将会开始转换。该组转换(4个单次转换)将会依次完成(completed
sequentially),转换结果也是依次存放于对应序号的寄存器中。
注意:ADC转换启动顺序可以设置为 SOC6 / SOC7 / SOC8 / SOC5,取决于接收ePWM触发时 round-robin 指针的位置,以上情况虽然可以实现,但没有必要这样搞。详见 13.5 部分。
基于不同的采样周期对多个信号进行采样。
CPU1 定时器2 被用以生成采样触发器(具体配置方法看《系统控制和中断(System Control and Interrupt)》章节的《CPU定时器(CPU Timer)》部分)
步骤如下:
1 | AdcaRegs.ADCSOC0CTL.bit.CHSEL = 5; //SOC0 will convert ADCINA5 |
如上配置后,当CPU1 定时器2生成事件,SOC0 / SOC1 / SOC2 / SOC3
将会按顺序启动采样和转换。ADCIN5 的结果将会存放在
ADCRESULT0
中,其他采样结果也是依次存储。
不论SOC是否配置具体的触发器,软件触发都可以让SOC实现转换。可以通过对
ADCSOCFRC1
寄存器写入来实现。
前述案例使用定时器2来触发转换,使用软件触发可以在不等待定时器的情况下实现立即采样和转换:
1 | AdcaRegs.ADCSOCFRC1.all = 0x000F; //set SOC flags for SOC0 to SOC3 |
当同一时间配置多重SOC标志时,多个SOC的优先级顺序有两种决定模式:Round-Robin 模式 和 High-Priority 模式。
默认优先级模式是
Round-Robin(后简称RR),这种模式下所有SOC优先级都一致。优先级由RR指针决定,RR指针
RRPOINTER
映射在 ADCSOCPRIORITYCTL
寄存器中,
The
RRPOINTER
reflected in theADCSOCPRIORITYCTL
register points to the last SOC converted. ?????
每一个SOC都有一个对应的EOC(End of Conversion)信号。EOC信号可以从来触发ADC中断。
ADC能够配置于在 捕获窗口期结束时(end of the
acquisition window) 或 电压转换结束时(end of the
voltage conversion) 生成EOC脉冲,使用 ADCCTL1
寄存器的
INTPULSEPOS
位来进行配置。
每个ADC模块都拥有4个可配置的ADC中断,这些中断能够被16个EOC的任意一个触发。每一个
ADCINT
的标志位都能够被直接读取,用以判断绑定的SOC是否完成或中断能否被传递至PIE。
注意:ADCCTL1.ADCBSY
位被清除不代表一个SOC里的所有转换都完成了,只有ADC准备好处理下一次转换时才能说明上一次已经完成。为了确定一组SOC是否已经完成,将
ADCINT
标志连接到 队列中的上一个SOC里,并观察它。
如果EOC信号在 ADCINTFLG
寄存器中设置一个标志,但该位置上早已设置过标志了,就会出现中断溢出(interrupt
overflow)。默认情况下,溢出中断不会被传递给PIE模块。当
ADCINTFLG
寄存器发生溢出时,对应的 ADCINOVF
寄存器就会被设置。ADCINOVF
寄存器上的标志位仅用于检测是否溢出,并不会阻止中断溢出到PIE模块。
当ADC中断溢出可能发生时,软件应该在ISR 或 后台循环 中检查
ADCINOVF
对应的标志位,并当检测到溢出时采取相应的措施。以下代码演示了如何在尝试清除
ADCINT
标志后,在ISR中检查 ADCINOVF
标志:
1 | AdcaRegs.ADCINTFLGCLR.bit.ADCINT1 = 1; //clear INT1 flag for ADC-A |
查询《TRM》第1496页可以看到有关ADC中断控制,主要有以下几个寄存器,涉及 中断标志设置、中断溢出设置、中断信号选择设置、中断SOC选择设置 和 SOC优先级控制。
ADCINTSELxNx
是负责控制ADC中断输出的
模式(Mode)、使能(Enable) 和
触发源(Trigger Source)。
ADCINTFLG
寄存器的
ADCINTx
位时生成ADC 中断脉冲 或
EOC脉冲生成时ADC中断脉冲EOCx
触发 本中断ADCINTFLG
是 只读
寄存器,每个ADC模块都有该寄存器,0
则代表 没有脉冲生成,
1
则代表脉冲已生成ADCINTFLGCLR
是 只能写
1
的寄存器,读取时会默认写0,在对应中断位写
1
时清除该中断 和 对应的中断结果标志ADCINTOVF
也是个 只读
的标志寄存器,检测是否有中断溢出。ADCINTOVFCLR
为中断溢出清除寄存器,同样也是
只能写 1
的寄存器。ADCINTSOCSELx
为 ADC中断生成后是否会触发其他
SOC的设置寄存器,只有 第一中断(INT1)和第二中断(INT2)可以触发
SOC。ADCSOCPRICTL
为中断优先级寄存器,需要联系SOC的转换优先级和《TRM》P1513来查看。在配置ADC时,需要思考好项目应用的具体场景和功能,设置哪个通道(Channel,如ADCA1)来检测信号,设置哪个模块的哪个SOC来绑定该通道(如ADCA的SOC1),SOC和EOC是一一对应的,接着需要设置EOC和哪个模块中断(INTx
)进行绑定。
graph TD 将通道中断/中断程序与中断向量表绑定 --> 设置参考电压VREF --> 配置Module --> 配置SOC --> 配置EOC/中断
如果不涉及超采样、多重采样等模式,可以不考虑SOC的优先级配置。
外设ADC配置过程中,最容易将ADC模块(A/B/C)、SOC序号、EOC序号、通道序号 和 模块中断序号之间互相搞混,配置时需要先画草图理清思路再行配置。
启用多个ADC通道时,需要注意模拟外设的引脚有限,某些SOC、ADC模块会复用,画图工作量会大些。
本系列芯片的控制外设包含 eCAP、HRCAP、ePWM、HRPWM、eQEP 和 SDFM。
根据《SPRU566N》表11 可知,ePWM 和 HRPWM 都是4型增强外设,具体功能可以参见《SPRUI33D》。
ePWM是商业和工业控制电力系统的关键组成部分。这些系统包括 数字电机控制、开关电源控制、不间断电源供应 或 其他形式的电源转换。
ePWM这个大的外设模块(Module)组成,是由8个子模块(Submodule)构成的。所有的ePWM模块用数字尾缀来表示第几个ePWM模块,如
ePWM1
、ePWM3
。每个ePWM模块又有两个输出,分别是 A 和 B,例如 ePWM1A
和
ePWM1B
。
ePWM模块通过一条时钟同步表(clock synchronization scheme)同步和串联在一起,形成可以统一操作的整体。此外,这个时钟同步表能够被扩展至 eCAP 外设使用。子模块的数量是由设备(设计)和实际使用需求决定的,每个子模块都能够支持单独操作。
ePWM 模块通过两个PWM输出(EPWMxA
和
EPWMxB
)来组成一条完整的PWM通道。多路
ePWM被在一个设备内实现,几乎每一个ePWM通道实例都是相同的,只有一个例外。有些实例包括一个硬件扩展,可以更精确地控制PWM输出。这种扩展是高分辨率脉宽调制器(HRPWM),在第18.15节中有描述。每个ePWM模块都用一个以1开头的数值表示,例如,ePWM1
是系统中的第一个实例,ePWM3
是第三个实例,ePWMx
表示任何实例。
每一个ePWM模块都被连接至输入和输出信号。每一个ePWM模块都包含着8个子系统,并通过图18-2的方式连接至系统内部。
表 5-57 展示了PWM的时间要求,主要是 同步(Sync)/非同步(Async)/带输入验证(With input qualifier)的 同步输入脉冲宽度(Sync Input Pulse Width)的三种时间
表 5-58 展示了PWM的开关特性,包括最小脉冲周期,同步输出脉冲宽度 和 td(TZ-PWM)
表 5-59
表 18-21 展示了ePWM外设下的各寄存器(自《SPRUI33D》Page 1885),约有81个寄存器。
图 18-1 描述的是外围总线上的多个PWM模块,及输入信号和输出信号通路示例
图 18-2 描述的是PWM模块下各子模块和信号连接方式(简图)
图 5-58 描述的是ePWM内部的子模块和信号连接方式(结构图)
图 5-59
图 5-60 为TB计数器同步链。
图 5-61 PWM高阻态时序特性
《TRM》P1841,简化过的ePWM模型:
PWM事件的周期被 TBPRD寄存器 和 TB计数器的模式 控制着。TB计数器的三种工作模式由 TB控制寄存器 (TBCTL)控制,分别为 增计数(Up-Count)、减计数(Down-Count)和 增减计数(Up-Down-Count)。
影子周期寄存器(shadow period register)的内存地址(memory address)与活跃寄存器相同。TB控制寄存器 (TBCTL)下的 PRDLD位 控制着要读写哪个寄存器,同时控制着是否使能影子寄存器,此时可分为 TB周期影子模式(Time-Base Period Shadow Mode) 或 TB周期即刻载入模式(Time-Base Period Immediate Load Mode)。
在TB计数器同步链中,EXTSYNC1 由 INPUTXBAR5 输入而得,EXTSYNC2 由 INPUTXBAR6 输入而得。而这些输入信号可以通过配置选择任意GPIO作为同步输入源。在使用 SYNCSEL 寄存器配置同步链传播路径时,应确保最长的路径不超过四个 ePWM/eCAP 模块。
每个ePWM模块配置时都可以选择使用或者无视同步输入。如果 TBCTL[PHSEN] 位被设置了,且发生 同步输入脉冲(Synchronization Input Pulse)、 软件强制同步脉冲(Software Forced Synchronization Pulse) 或 数字比较事件同步脉冲(Digital Compare Event Synchronization Pulse) 三者之一的情况时,TB计数器(TBCTR)会自动加载 相位寄存器(TBPHS) 的内容。
Up-Down-Count Mode: 增减计数,TB计数器从0开始自增计数直到等于周期值(TBPRD值)。当与该值相等时,TB计数器会 逐步自减至0。接着周而复始,重新开始自增。
Up-Count Mode: 增计数,TB计数器从0开始自增计数直到等于周期值(TBPRD值)。当与该值相等时,TB计数器 直接归零,接着周而复始,重新开始自增。
Down-Count Mode: 减计数,TB计数器从周期值(TBPRD值)开始自减至0。当到达0值时,TB计数器直接重置TBPR值,接着周而复始,重新开始自减。
Active Register: 活跃寄存器,控制着硬件,并对硬件触发或调用( causes or invokes)的事件进行响应。
Shadow Register: 影子寄存器,为活跃寄存器提供缓存,即临时存储空间( temporary holding location)。不对任何控制硬件造成直接影响。 在关键时刻(at a strategic point in time ),影子寄存器的内容将会传递给活跃寄存器。可以避免因软件异步修改造成的崩溃或假操作(corruption or spurious operation)。
Time-Base Period Shadow Mode:
TB周期影子模式,当 TBCTL[PRDLD]=0
时,影子寄存器打开。对TBPRD内存地址的读写操作将会影响到影子寄存器。当TB计数器为零(TBCTR=0x00
)时,影子寄存器的内容会被传输到活跃寄存器。TBCTL2[PRDLDSYNC]
位决定着同步时间。当且仅当 TBCTL[PRDLD]
值为0时,PRDLDSYNC位的值有效。默认情况下,影子寄存器开启。 通过配置
全局负载配置寄存器(GLDCFG)中对应的位,全局负载控制机制就能够和TB周期寄存器被一起使用。当全局复杂模式选通时,从影子寄存器到活跃寄存器的内容传输
Time-Base Period Immediate Load Mode:
TB周期即刻载入模式,当 TBCTL[PRDLD]=1
时,即刻载入模式开启。对TBPRD内存地址的读写操作将会直接载入到活跃寄存器。
Time-Base Clock Synchronization: TB时钟同步,外设时钟使能寄存器(peripheral clock enable registers)里的 TBCLKSYNC 位 允许所有用户将所有已开启的ePWM模块同步至TB时钟(TBCLK)。开启时,所有已开启的模块时钟会与TBCLK的第一个上升沿对齐。为了完美地同步TBCLK,所有ePWM模块的预分频都要明确设置。
Time-Base Counter Synchronization: TB计数同步,每个ePWM模块都有一个 同步输入(SYNCI)、一个 同步输出(SYNCO) 和 一个 外设同步输出(SYNCPER)。
Synchronization Input Pulse: 同步输入脉冲,当检测到输入同步脉冲时,相位寄存器的值被载入至计数器中。这个载入操作会在下一个有效的TB时钟(TBCLK)边沿发生。
Software Forced Synchronization Pulse: 软件强制同步脉冲,
Digital Compare Event Synchronization Pulse: 数字比较事件同步脉冲,
开启ePWM时钟的步骤如下:
TBCLKSYNC = 0
TBCLKSYNC = 1
CTR,Counter的缩写。
图 18-6 展示了当TB计数器周期设置为4时,增计数、减计数 和 增减计数 三种模式下的PWM周期和频率的关系。时间自增的步长由从ePWM时钟分频而来的时基时钟(TBCLK)定义。
表 18-2 是对关键TB信号的详细描述。
CC子模块, 以TB计数值为输入源。这个值被不断地拿来与CMPA/CMPB/CMPC/CMPD进行比较,当TB计数器的值与其中一个寄存器的值相等时CC子模块就会生成一个适当的事件。
CC子模块的作用:
CTR=CMPA
时,
TB计数器的值等于计数比较器A的值(TBCTR=CMPA)CTR=CMPB
时,
TB计数器的值等于计数比较器B的值(TBCTR=CMPB)CTR=CMPC
时,
TB计数器的值等于计数比较器C的值(TBCTR=CMPC)CTR=CMPD
时,
TB计数器的值等于计数比较器D的值(TBCTR=CMPD)CC子模块的内部信号通路结构示意如下图18-15。可以看出,CC子模块前一级是TB子模块,接收其时间信号。CC子模块不间断地对比 CMPx 和 TBCTR 的值,一旦符合要求,则输出结果给AQ子模块。所有CMPx的输出结果均会导向ET(事件触发器和中断),但是导向AQ子模块的只有 CMPA / CMPB。
AQ子模块在 波形结构 和 PWM生成 上扮演着重要的角色。它决定了哪个事件可以被转换为不同的动作类型(converted into various action types),进而产生 EPWMxA 和 EPWMxB 输出需要的开关波形(producing the required switched waveforms)。
AQ子模块控制着当特定事件发生时,ePWM外设的两条输出线(EPWMxA / EPWMxB)该如何进行输出。输入至AQ子模块的事件 会由 计数器方向(自增或自减)进一步限定。这允许在计数上升和计数下降阶段对输出进行独立操作。
AQ子模块的作用:
CTR = PRD
,TB计数器等于周期(TBCTR = TBPRD)CTR = ZERO
,TB计数器等于0(TBCTR = 0x00)CTR = CMPA
,TB计数器等于 计数比较器A (TBCTR =
CMPA)CTR = CMPB
,TB计数器等于 计数比较器B(TBCTR =
CMPB)从下图18-21 或 表 18-3 中可以看出,AQ子模块的输入信号源除了TB时钟意外,剩下的触发事件有7种,分别是 PRD / ZERO / CMPA / CMPB / DIR / T1 / T2。只有前四种需要使用到TB计数器。软件强制动作(software forced action)是个非常有用的异步事件,由 AQSFRC 和 AQCSFRC 寄存器控制。
注意:如果在影子模式下 CSFA 未被使用,必须配置 RLDCSF 位以关闭影子模式。
对输出(EPWMxA / EPWMxB)可能施加(imposed)的动作如下:
输出(EPWMxA / EPWMxB)的动作需要分别单独指定(specified independently)。在特定输出上,所有或任一事件都可以被配置以生成动作。
比如说,CTR=CMPA
和 CTR=CMPB
都可以被配置到
EPWMxA
上。所有限定动作都可以通过控制寄存器被配置。
每一个符号代表着一个动作,就像是时间上的标记。某些动作在时间上是固定的(如,0和周期),而CMPA和CMPB动作是非固定的(moveable)且其时间位置可以通过CMPA/B寄存器来编程。
使用 无视 操作可以关闭或无效某个动作,无视动作是复位后的默认值。
AQ触发事件源选择寄存器(AQTSRCSEL, Action Qualifier Trigger Event Source Selection register)被用于为T1/T2事件选择源。在AQ子模块中,一个trip/数字比较事件的T1/T2的选择和配置 与 Trip-zone子模块事件的配置 是相互独立的。特定的trip事件是不确定能否在TZ子模块里通过配置来触发trip动作的,但是相同事件是能够确定可以被配置于AQ子模块中,以生成T1/T2来控制PWM生成的。
PWM动作限定器(AQ)是可以在相同时间内接受一个以上事件的,此时,硬件会为事件排好优先级。优先级规则是,事件生成越晚,优先级越高,而且 软件强制事件(software forced event)拥有最高优先级。
增减计数模式下的动作限定事件优先级如下表18-4所示,优先级按数字从小到大依次递减,共分10级。
优先级会因TB计数器的计数模式改变而改变。 但是从下表18-5和 18-6中可以看到,自增模式和自减模式下,只有7个优先级,在任一模式下,其相反动作的触发都不会被考虑,如在自减模式下,自增事件不会被考虑,自增模式下,自减事件不会被考虑。
注意:CMPA或者CMPB是可以设置得比PRD还大的,此时需要考虑依据所处模式进行考虑,如表18-7所示。
图18-25 展示了如何使用TBCTR的增减计数模式来生成一个symmetric PWM波形。
下方为DB死区时间生成子系统的功能框图,主要是设置两个寄存器值
DBFED
和 DBRED
,需要注意的是,他们都是 14bit
的,传递数值的时候需要进行限定。
暂略
TZ子模块,每个ePWM模块都连接了6个 TZn(TZ1 ~ TZ6) 信号 , 其中:
这些信号指示了外部的故障或跳闸情况,并且可以对ePWM的输出进行编程,以便在故障发生时作出相应的反应。
TZ子模块的特点:
如果需要使用ePWM的事件触发,则需要配置ET模块。
例如,如果需要使用ePWM作为ADC_SOC的触发源,则需要使用以下配置:
1 | EPwm6Regs.ETSEL.bit.INTEN = ON; // 关闭EPWM外设的中断 |
如果不需要,则使用以下配置:
1 | EPwm6Regs.ETSEL.bit.INTEN = OFF; // 关闭EPWM外设的中断 |
暂略
本系列芯片使用的是第1类eCAP,特性如下所示:
eCAP模块有如下特性:
后续会描述到的eCAP特性如:
第一类eCAP 较 第0类eCAP增加了以下特性:
为了将设备输入引脚连接至模块,输入X-BAR必须要用上。设备上的任何GPIO都能够配置为输入。通过设置
GPxQSELn
寄存器位可以将GPIO输入验证(input
qualification)设置为同步模式或异步模式。
使用同步输入能够有效提高抗噪性,但是让eCAP丢失±2个周期的精度。在
GPyPUD
寄存器中能够配置内部拉高。一旦GPIO模式开启,GPyINV
寄存器就能够倒置信号了(invert the signals)。
必须配置128:1的输入多路器也是一类eCAP模块的新特性。从下表19-1可以看出,该多路器能够配置不同的输入细节。
下图为《TRM》P966图9-1,可以看到eCAP的输入可以被配置为 INPUT[16:1] 或 Other Sources 。而 INPUT[16:1] 则是INPUTXBAR的16个可配置通道,
输出X-BAR能够用于连接输出信号至 OUTPUTXBARx 输出定位(output location)。
从下表4-8可以看出,INPUT XBAR所有16个输入都可以被配置至其目的地为eCAP。
在配置引脚时有以下步骤:
InputXbarRegs.INPUT10SELECT
进行配置。ECCTL0.INPUTSEL
寄存器为进行配置。1 | InputXbarRegs.INPUT10SELECT = 0xA; // 将GPIO10(0xA)与输入XBAR 10进行连接 |
下图19-3展示了实现捕获功能的不同元件(various components)
输入捕获信号(脉冲串)能够被N预分频(N的取值范围为[2,62],所有可取值均为2的倍数),也可以绕过该预分频,在输入信号频率非常高时有用。
下图19-4 展示了事件预分频器的功能框图。
下图19-5 展示了分频功能的操作。
通过设置 ECCTL2.CTRFILTRSET
寄存器位即可让
事件分频器(Event Prescalar)复位。
功能和特性如下:
CAPx
寄存器。CAPx
寄存器在下降沿时载入(loaded on the falling
edge)。在连续/一次性控制模式(Continuous / One-shot mode)下的eCAP操作:
CAP1
~ CAP4
寄存器。在本模式下,如果 TSCCTR
计数器通过配置 ECCTL1.CTRRSTx
位来
用于在捕获事件(CEVTx)上的复位,它将会在
STOP_WRAP
值满(reached)且
重新装载(re-arm,REARM)尚未发生时 在捕获事件是上对
TSCCTR 计数器一直复位(keep resetting the TSCCTR counter on capture
event)。连续/一次性块 通过软件控制的能够触发 单次操作(mono-shot type of action)的 停值比较器(stop-value comparator)和 重新装载(re-armed) 控制着Mod4计数器的启动、停止和复位。
一旦装载,eCAP模块会 在冻结Mod4计数器和 CAP1
~
CAP4
寄存器(时间戳)之前 等待 1 ~ 4 个(由
停值 决定)捕获事件。
重新装载(re-arming)会将eCAP模块作为另一个捕获顺序的准备,同时清除(至0)Mod4计数器
并 允许再次装载(permits loading again) CAP1
~
CAP4
寄存器,前提是 CAPLDEN
位已经设置了。
在连续模式下,Mod4计数器连续运行(0 -> 1 -> 2 -> 3 ->
0),一次性操作将会被忽略且捕获到的值会被以循环缓存顺序(circular buffer
sequence)持续写入至CAP1
~ CAP4
寄存器。
本32位计数器为时间捕获提供TB,并通过系统时钟锁定(is locked via the system clock)。
相位寄存器的作用是通过硬件和软件强制同步(forced sync),完成与其他计数器的同步。在APWM模式下,当模块间需要相位偏移时比较有用。
对四个任一事件的装载而言,都有一个选项可以重置32bit计数器,对时差捕获有用。先是32位计数器值被捕获,接着被LD1
~ LD4的任一信号复位为 0
。
CAP1
~CAP4
为32位的寄存器,由32位计数定时器总线和 CTR[0-31]
驱动(fed
by the 32-bit counter timer bus,
CTR[0-31]),并在对应的LD输入被触发(strobed)时被加载以捕获其时间戳。
CAPLDEN
控制位能够抑制捕获寄存器的加载。在一次性捕获操作器件,当停止情况发生时(停值
= Mod4),该位会被自动清空(禁止载入)。
在APWM模式下,CAP1
CAP2
寄存器对应地称为(活跃的)周期 和 比较
寄存器(the active period and compare register),CAP3
CAP4
成为了对应活跃寄存器的影子寄存器。
eCAP模块能够通过选择相同的同步源(a common source)
SYNCIN
来与其他模块同步。eCAP的同步源可以是软件同步源或者外部同步源,外部同步源信号能够来自
ePWM、eCAP或者X-BAR。
图19-7所示,eCAP模块的 SWSYNC
与 SYNC
信号进行逻辑 或(OR)。
图19-8所示,SYNC
信号是由
SYNCSELECT[ECAPxSYNCIN]
的选择来定义的。
按一下步骤进行操作可以实现对 ECAP1 和 ECAP3 的软件同步:
ECAP[1..3].ECCTL2.SYNCO_SEL = 0x0
,以使能
同步输入(sync-in)事件成为同步输出(sync-out)信号的通道。ECAP[2..3].ECCTL2.SWSYNC = 0x0
,以关断 ECAP2 和
ECAP3 的软件同步。TBCTL[SYNCOSEL]
信号没有被正确配置,则可能对时间戳寄存器 TSCTR
造成意料之外的结果。在 InputXbarRegs
中选择一个没有使用过的GPIO并配置为输出模式,并往GPIO DAT
寄存器中写入 0
。默认情况下会配置为
GPIO0,但该针脚上的任何活动都会对 SWSYNC
造成影响。SYNCSEL[ECAP1SYNCIN] = 0x5
,将
ECAPx.EXTSYNCIN
外部同步输入关断。ECAP1.ECCTL2.SWSYNC=0x1
,强制开启 时间戳计数器
(TSCTR
counter)的软件同步。为了将 SWSYNC
应用至其他
eCAP模块,需要确保前面的eCAP链条没有生成可能干扰软件同步的
SYNCOUT
信号。
eCAP中断控制的操作和特征如下:
CTR = PRD
,
CTR=CMP
)都能够生成中断。FFFFFFFF -> 00000000
)也提供了中断源(CTROVF)。ECEINT
被用于使能/禁止独立中断事件源。中断标志寄存器 ECFLG
鉴定是否有任何中断事件被锁定 并 维持着全局中断标志位 INT
。只有在任何中断事件被使能,其标志位为 1
且
INT
标志位为 0
时,中断脉冲会被生成并送至
PIE。中断服务程序必须清楚全局中断标志位 并
在任何其他中断脉冲生成前通过中断清除寄存器 ECCLR
服务事件。往 ECCTL2[CLRFILTRESET]
位 写 1
来对事件滤波器进行复位时,所有中断标志都会被清除。可以通过中断强制寄存器
ECFRC
来强制中断事件,对测试目的非常有效。注意:CEVT1 / CEVT2 / CEVT3 / CEVT4
标志仅在捕获模式下可用(ECCTL2[CAP/APWM==0]
)。CTR=PRD /
CTR=CMP
标志仅在APWM模式下可用ECCTL2[CAP/APWM==1]
。CNTOVF在两个模式下都可用。
在0类eCAP模块下,CPU被要求使用DAM来启动数据传输。
在1类eCAP模块下,分立式(separate)DMA触发器(ECAP_DMA_INT
)通过使用DMA,控制着从eCAP寄存器
到 片上内存(on-chip memory)的捕获数据的不间断传输。
通过对 ECCTL2[DMAEVTSEL]
进行操控,可实现四个任一可用中断事件(CEVT1 / CEVT2 / CEVT3 / CEVT4
)都可以被当做 ECAP_DMA_INT
的触发源。
在捕获模式下,该逻辑禁止(锁定)任何从APRD
和
ACMP
到 CAP1
和 CAP2
的影子载入。
在APWM模式下,影子载入被激活,且有以下两个选择可用:
APRD
或 ACMP
将会立即传输至 CAP1
或 CAP2
。CTR[31:0] = PRD[31:0]
APWM,即 Asymmetrical Pulse Width Modulation,非对称脉冲宽度调制。
当eCAP模块没有被用来输入和捕捉信号时,可以改成单通道PWM生成器(32位分辨率)。计数器将工作在
增模式
下,为非对称脉冲宽度调制(APWM)提供TB(Time-base)。其中
CAP1
和 CAP2
寄存器 将被当做对应PWM中存放
周期(period) 和 比较(compare)
的寄存器,对应的 CAP3
和 CAP4
寄存器 将被当做
周期和比较的影子寄存器。
下图19-1捕获和辅助脉冲宽度调制器模式操作的高级视图
eCAP模块的应用通过边沿可以分为两类:上升沿(rising edge) 和 上升下降沿(rising and falling edge)。通过操作方式也可以分为两类:时间戳(time stamp) 和 时间差(time difference)。
边沿和操作方式可以互相组合成共4中应用:时间戳上升沿操作、时间戳上升下降沿操作、时间差上升沿操作 和 时间差上升下降沿操作。
下图19-12中,为连续捕获操作(Mod4计数器周期翻转(wraps around))的例子
图表中, TSCTR 计数器在没有复位的情况下持续上升计数,捕获事件仅被限定在上升沿(注意观察图中所有CAPx旁边都有一个上升沿的标志,即所有CAPx都被设置为在MOD4的上升沿时才能出发CAPx PIN的输出)。从图中也可以看出周期和频率信息。
在一个事件中,TSCTR内容(时间戳)被首先捕获,然后Mod4计数器进入下一阶段。当
TSCTR到达最大值 FFFFFFFF
时,翻转回
00000000
,此时 计数器溢出标志 CTROVF
为
1
,中断(使能时)会生成。
被捕获的时间戳在图中所示的时间点上是有效的(在第4个事件之后),因此事件CEVT4可以方便地用来触发一个中断,CPU可以从 CAPx 寄存器中读取数据。
在19-13图中,eCAP操作模式几乎与 时间戳上升沿操作 一致,除了捕获事件限定在了上升下降沿。给出相同的周期和任务占空比信息:
周期1 = t3 - t1,周期2 = t5 - t3 ......
任务占空比(高电平占比)1 = (t2-t1)/周期1 * 100% ,......
任务占空比(低电平占比)1 = (t3-t2)/周期1 * 100% ,......
通过APWMx 输出引脚生成简易单通道PWM波形。
The PWM polarity is active high, which means that the compare value (CAP2 reg is now a compare register) represents the on-time (high level) of the period. Alternatively, if the APWMPOL bit is configured for active low, then the compare value represents the off time.
《TRM》P2041
以下流程总结自C2000WareV3.4的driverlib样例代码:
与之前的0类HRCAP模块不同,1类eCAP具有HRCAP的功能但不要求二次PLL。然而本HRCAP模块仍然要求 SYSCLK 和 HRCLK 异步时钟源(Asynchronous clock source)。HRCLK对温度和电压的变化较为敏感。因此,当使用时间转换测量(time-converted measurements)时,要求定期进行持续性的校准(periodic continuous calibrations)。
HRCAP_enableCalibrationInterrupt()
使能中断HRCAP_setCalibrationMode()
使能校准HRCAP_setCalibrationPeriod()
配置周期性校准HRCAP_enableHighResolution()
使能HR模式HRCAP_enableHighResolutioniClock()
使能 HRCLKHRCAP_startCalibration()
启动校准步骤 2、3、4 和 8 仅应用在时间转换的测量。当使用HRCAP来进行相关事件测量时仅需 1、5、6 和 7 即可。
除了HRCALINT之外,HRCAP的增强功能还利用了现有的eCAP中断,该中断是由硬件校准块专门使用的。HRCALINT能够被以下情况触发:
SYSCLKCTR = HRCALIBPERIOD
SYSCLKCTR
/ HRCLKCTR
处于溢出态(experience an overflow condition)本系列芯片的SPI具有如下特性:
下表为SPI模块的信号总结,其中有关中断信号和DMA触发的信号可能需要注意。
在SPI处于从模式时,使能/片选 信号 SPISTE 为提供了阻挡假时钟信号和数据脉冲的作用。拉高使能信号时将不会允许从设备接收数据,同时也阻止了从设备从主设备那儿断开同步。这也是TI不推荐将使能信号一直绑定到拉高(active state)状态的原因。
如果从设备确实已经跟主设备断开同步,切换(toggling)寄存器
SPISWRESET
会像复位模块里的不同状态标志(various status
flags)一样,将内部的 位计数器(internal bit
counter)进行复位。将该位计数器复位后,SPI会将下一个时钟信号当做一个新传输的开始。
GPIO多路寄存器必须将外设和对应的针脚连接起来,为了避免连接时的电压尖峰出现,必须首先配置
GPyGMUX
位(当保持对应位为0时),向其写入对应的值。
某些IO口的功能由GPIO寄存器独立定义。对于输入信号,通过将对应的
GPxQSELn
寄存器位设置为 11b
可以将GPIO输入验证(input qualification)设置为异步模式。内部拉高可以在
GPyPUD
寄存器中进行设置。
The four-bit SPICHAR register field specifies the number of bits in the data character (1 to 16). This information directs the state control logic to count the number of bits received or transmitted to determine when a complete character has been processed.
The following statements apply to characters with fewer than 16 bits:
Data must be left-justified when written to SPIDAT and SPITXBUF.
Data read back from SPIRXBUF is right-justified.
SPIRXBUF contains the most recently received character, right-justified, plus any bits that remain from
previous transmission(s) that have been shifted to the left (shown in Example 22-1).
SPI支持125种不同的波特率和4种不同的时钟模式,取决于SPI的工作模式(主/从),SPICLK引脚是向外提供工作时钟还是接受外部时钟。
以上所有模式下,SPICLK 的速率不能够大于 LSPCLK
的频率除以4。即
注意:所配置的波特率不应超过GPIO的最大切换频率(the maximum rated GPIO toggle frequency)。
在 SPIBRR 的可设置范围为3
~ 127
时,其计算公式如下:
在 SPIBRR 的可设置范围为 0
、1
或
2
时,其计算公式如下:
其中:
为了确定SPIBRR应该载入何值,必须确定设备系统时钟(LSPCLK)频率 和 目标SPI时钟。
在标准SPI模式下(HS_MODE = 0)SPI模块的波特率计算,首先需要知道LSPCLK的控制寄存器,查找下图可知,对应LSPCLK由 LOSPCP 寄存器控制,该寄存器的信息在《TRM》P173 及 P185 可查询,如下表 3-40 和 表 3-51。
如:
在 SYSCLK = 100MHz
时,设置 LOSPCP 寄存器下的
LSPCLKDIV
设置为 001b
或 0x01
,则对应的 LSPCLK = SYSCLK / LSPCLKDIV = 100MHz / 2 =
50MHz,此时,据前面SPI计算公式可知,SPI的波特率最高可为
LSPCLK / (SPIBRR +1) = 50000000 /
(3+1) = 12500000Hz = 12.5MHz
。
在 LSPCLKDIV
为 000b
或 0x00
时,SPI的最高波特率为 25MHz
。
时钟极性选择位 CLKPROLARITY
和 时钟相位选择位
CLK_PHASE
控制 SPICLK 引脚上的
4个不同时钟模式,其中极性选择控制时钟的上升沿或者下降沿,相位延迟控制是否延迟半个时钟周期。
只有当 SPIBRR +1
的值为偶数时, SPICLK
才能保持器对称性。当 SPIBRR +1
为奇数 且
SPIBRR
大于 3
,SPICLK
则为非对称性。
当 CLKPOLARITY
为 0
时,SPICLK
的 低电平(low pulse) 会比
高电平(high pulse)长一个 LSPCLK
的宽度。
当 CLKPOLARITY
为 1
时,SPICLK
的 高电平(high pulse) 会比
低电平(low pulse)长一个 LSPCLK
的宽度。
SPI模块包含两条中断线: SPIINT
/ SPIRXINT
或 SPITXINT
。
当工作在非FIFO模式时,所有可用中断都被路由到一起,并生成中断信号
SPIINT
。
当工作在FIFO模式下时,SPIRXINT
和 SPITXINT
都能够被生成。
SPIINT会在非FIFO模式下出现。当FIFO增强开启时,会产生 SPIRXINT 中断,这两种中断会在PIE中共享同一个中断向量。
在非FIFO模式下,以下两种情况能够触发同一个中断,使用的同一个中断向量为
SPIINT
。
INT_FLAG
)OVERRUN_FLAG
)传输完成标志 INT_FLAG
说明SPI已经完成发送或接收最后一个比特,并且准备好继续服务下一次传输了。在该标志为1时,说明接收到的数据已经被放在接收缓存
SPIRXBUF
中。如果 SPIINTENA
位设置过了,此时该标志位还会在中断向量表 的 SPIINT
上生成中断。
接收超限标志 OVERRUN_FLAG
说明在当前字符数据被从缓冲区读取出来之前,传输或者接收操作已经完成。如果OVERRUNINTENA
位为 1
且 OVERRUN_FLAG
已经被清除了,则该标志会在 SPIINT
向量上生成一个中断。
在FIFO模式下,SPI能够在 当前接收FIFO状态(RXFFST)和
接收FIFO中断水平(RXFFIL)之间的某个条件下中断CPU。如果
RXFFST ≥ RXFFIL
,则 接收FIFO中断标志
RXFFINT
将置 1
,如果RXFFINT
被置
1
且 接收FIFO中断 (receive FIFO
interrupt)被使能(RXFFIENA
为 1
),则
SPIRXINT
将会被触发。
在非FIFO模式下,SPITXINT中断不可用。在FIFO模式下,SPITXINT 和 SPIRXINT 相近。
在FIFO模式下,SPI能够在 当前传输FIFO状态(TXFFST) 和
传输FIFO中断水平(TXFFIL)之间的某个条件下中断CPU。如果
TXFFST ≤ TXFFIL
,则 发送FIFO中断标志 TXFFINT
将置 1
,如果 TXFFINT
被置 1
且
传输FIFO中断 被使能(TXFFIENA
为 1
),则
SPITXINT
将会被触发。
下图 22-2 展示了上述这些控制位如何影响SPI的中断生成:
CPU和DMA都可以通过内部外设总线来访问SPI数据的寄存器,最高可以读/写16bit的寄存器。每个SPI模块能够生成两个DMA事件
SPITXDMA
和 SPIRXDMA
。通过配置对应的
SPIFFTX.TXFFIL
和 SPIFFRX.RXFFIL
可以控制
DMA事件。当 TXFFST < TXFFIL
时,SPITXDMA
激活。当 RXFFST ≥ RXFFIL
时,SPIRXDMA
激活。
必须先开启FIFO增强,才能让DMA触发生成。(The SPI must have FIFO enhancements enabled in order for the DMA triggers to be generated.)
SPI的高速模式为所有GPIO多路器选项可用。为了开启
高速增强(High-Speed Enhancements),需要设置
SPICCR.HS_MODE
为
1
。仍需确保针脚上的容性负载(capacitive loading
)不会超过(exceed)数据手册上的规定值(the value stated)。
当关闭高速模式,或当针脚上的容性负载超过规定值时,SPICCR.HS_MODE
需要被设置为 0
。
根据前面波特率计算案例可知,SPI最高能在 LSPCLK == SYSCLK
时达到其全双工通信最高速率 25MHz
。
三线模式在设备为主机或从机模式下时均可设置,都需要设置
TRIWIRE
位,此时SPI的全时双工将变为半时双工模式,以下为主/从机模式下使用三线半双工模式时引脚的功能转变情况:
主机模式:SPISIMOx 则变成了 双向半双工(bi-directional, half-duplex) 的 SPIMOMIx 引脚,SPISOMIx则不再使用。
从机模式: SPISOMIx 变成了 双向半双工(bi-directional, half-duplex) 的 SPISISOx 引脚,对应的 SPISIMOx 则不再使用。
由于需要在同一根信号线上完成读取和写入操作,TALK
位起了决定作用。该数据位写入时将数据传送出去,擦除时则读取数据。
In master mode, in order to initiate a read, the application software must write dummy data to the SPI data register (SPIDAT or SPIRXBUF) while the TALK bit is cleared (no data is transmitted out the SPIMOMI pin) before reading from the data register.
在主机模式下,为了初始化读取操作,软件必须配置为 在
TALK 位 被清除的情况下
向SPI数据寄存器(SPIDAT
或者 SPIRXBUF
)写入假数据(实际上没有数据被传送到SPIMOMI引脚),然后再从数据寄存器中读取。
暂略
SCI外设负责UART协议的编码部分,可以使用CH340芯片的串口助手进行读写测试。
SCI外设支持 单线模式(Idle-line Mode)或 网络模式(Address-bit Mode)。
此处 Idle-line Mode 由本人意译为 UART/RS中仅有两个CPU通信的 单线模式,Address-bit Mode 由本人意译为 UART/RS中有多个CPU通信的 网络模式。
不管是单线模式还是网络模式,都有统一的通信数据格式。
SCI数据,包括接收和传输,都是 NRZ (non-return-to-zero)格式,该格式具有以下特点:
数据的基础格式被称为字符(8 bits)或1~8个数据位的长度。每个字符型数据有 1个起始位、1~2个结束位、可选的校验位 和 地址位。字符型数据的格式称为 帧(frame),如下图23-3所示。
使用 SCICCR
寄存器即可编辑数据格式,编辑数据格式的寄存器位如下所示:
GPIO复用寄存器必须配置为从外设连接至设备引脚。为了避免引脚上的电流尖峰,GPyGMUX
寄存器位必须先配置(当对应的GPyMUX
寄存器位默认保持为
0
时),然后在对应 GPyMUX
位上载入数值。
某些IO口的功能由外设的GPIO寄存器独立设定。对于输入信号,GPIO输入限定器应该通过设置对应的
GPxQSELn
寄存器位为 11b
来设置为异步模式。内部拉高可以通过 GPyPUD
寄存器来设定。
SCI或者UART即使再不一样,都跟I2C一样,一次传送一个
unsigned char
字符。
SCI异步通信格式使用单线(单向)或者双线(双向)通信。在该模式下,帧内存在一个开始位,1~8位数据,一个可选的奇偶校验位 和 1~2个停止位,如下图23-7所示。
线路一直都是置高状态,每次都在收到开始位(拉低)时开始进行数据接收操作。一个有效的起始位由4个0bit的连续的
SCICLK
周期组成,一旦该低电平无法保持4个
SCICLK
周期,则将重头开始等待新的起始位。
为了能够成功采集到开始位之后的数据,处理器会对数据进行三次采样。这些采样发生在第四、第五和第六个SCICLK周期,比特值的确定是以 多数(Majority Vote)(三个中的两个)为基础,即如果该数据不能保持平稳(例如发送方波特率过高 或 噪声干扰等),可能会导致数据采样不稳。
多核通信格式允许单个处理器在同一条串行链路(on the same serial link)上高效传输一组数据(blocks of data)给其他处理器。在单条 串行线(serial line)上,任一时刻只能有一种传输,换句话说就是任一时刻只有一个发送者和一个接受者,即 分时双工 / 半双工。
发送者(talker)发送的第一组数据(the first byte of a block of information)包含一个给所有接收者的地址字(address byte)。只有地址一致的接收者才会接收该地址后面传输的数据(can be interrupted by the data bytes that follow the address byte)。其他地址不一致的接收者在下一次收到地址一致的数据前都会保持原来的工作,不会被中断。
注意,是地址字,不是地址位。
在多个连接上的所有处理器都应该设置 SCI 睡眠位(SCICTL1
的第2位)为 1
,只有在检测到对应的地址位时才会被中断睡眠。当处理器读取到通过软件设置的对应CPU设备地址的地址块(a
block address)时,程序必须清除睡眠位以使能SCI生成中断来接收数据位。
虽然睡眠位为 1
时,接收端仍然独立操作,但是不会设置
RXRDY
、RXINT
或其他接收错误状态位 为
1
,除非地址位被检测到且接收到的帧的地址位为
1
(地址位模式下可用)。
注意:SCI外设不会自动切换 SLEEP
位的数据,必须手动切换。(The SCI does not alter the SLEEP bit;
your software must alter the SLEEP bit.)
每个处理器确定地址字节都不同,取决于多个模式的选择。
在IL多核协议中(ADDR
/ IDLE
模式位为
0
)
多个独立的帧块之间会有 10bit以上的空闲周期。
一次地址多帧数据
SCI 的接受端和发送端都可以被中断控制。
SCI的接收端和发送端都拥有独立外设中断向量。外设中断请求可以设置为高优先级或低优先级,通过从外设输出到PIE控制器上的优先级位进行鉴定。当RX和TX优先级一致时,接收端优先,以减少 接收端过载(receiver overrun)的可能性。
外设中断的操作在 《系统控制和中断》章节下的《外设中断》部分已经说明。
内部生成的串行时钟由 低速设备时钟 LSPCLK 和 波特选择寄存器(baud-select registers)共同决定。SCI使用16 bit 的 波特选择寄存器在给定的 LSPCLK 值上 以选择一个可用的 64K差分串行时钟速率(one of the 64K different serial clock rates)。
查看寄存器信息和SCI异步波特率计算公式。下表展示了
通用SCI比特率(common SCI bit
rates)的波特率选择值。LSPCLK/16
是最大的波特率,例如,LSPCLK=100MHz
时,波特率最大为
6.25Mbps
。
BRR等于高位寄存器 SCIHBAUD
左移8位和低位寄存器
SCILBAUD
的合成值。 \[
BRR = (SCIHBAUD<<8)+(SCILBAUD) \tag{BRR计算公式}
\] 异步波特率的计算公式为: \[
F_{Asynchronous} = \frac{LSPCLK}{(BRR+1)·8} \tag{异步波特率计算公式}
\] 同步波特率的计算公式为: \[
F_{Synchronous}=\frac{LSPCLK}{16} \tag{同步波特率计算公式}
\]
下表23-3是 LSPCLK =100MHz时, 常见的BRR配置值 和 误差情况。
暂略,在ADI的21479开发笔记中有类似,可以参考。
28004x Piccolo
,在选择具体芯片。C2000系列编译器中,关于处理器选项,需要设置 CLA
、
FPU
、 TMU
、 IDIV
和
VCU
这几个参数,其中《spru566n》全文关于 IDIV
的描述仅有 F28002x 和 2838x 支持,此处就默认 28004x
不支持(暂时先留空),其余支持项均可以在《spru566n》表11中查找到。
IDIV
是指 增强型整数除法
,enhanced integer division,也是快速整数除法,fast integer
division。FPU
有 FPU32 / FPU64 / SOFTLIB 三种选项,其中如果设置了
TMU
和 VCU
,则默认设置为 FPU32。各选项设置如下:
向头文件包含设置中,添加几条 c2000Ware 套件安装包提供的支持库信息:
D:\ti\c2000Ware\C2000Ware_2_01_00_00\driverlib\f28004x\driverlib
D:\ti\c2000Ware\C2000Ware_2_01_00_00\device_support\f28004x\common\include
D:\ti\c2000Ware\C2000Ware_2_01_00_00\device_support\f28004x\headers\include
注意:以上路径信息仅供参考,具体路径仍需依据套件安装地址来修改。
最后一共有4条信息:
先在 Add
<dir> to library search path (--search_path, -i)
中添加可以处理链接器的(.cmd)文件路径
D:\ti\c2000Ware\C2000Ware_2_01_00_00\device_support\f28004x\common\cmd
,然后将
Include
library file or command files as input (--input, -I) 中的
libc.a
删除,添加具体的链接器全名
28004x_generic_ram_lnk.cmd
和
rts2800_fpu32.lib
。
此步骤为 可选 步骤,在 Advance
Options 下的 Symbol
Management 中设置 程序入口点 code_start
。
注意:本步骤是用于设置编译参数的,可以在完全新建项目的时候不操作本步骤,此时进行编译
Build Project
的话也是可以编译成功的。
code_start is the first code that is executed after exiting the boot ROM code for the example f28004x projects. The projects are setup such that the codegen entry point is also set to the code start label using linker options. The code start code will automatically re-direct the execution to
_c_init00
.
此步骤为 可选 步骤,在 Resource 下可以设置 字体编码 和 回行 格式。
添加文件有两种形式,一种是以链接形式添加,编译时不会修改到源文件。另一种是直接添加至项目中(复制副本 或 新建文件)。
Add Files
,以 链接形式
添加文件:D:\ti\c2000Ware\C2000Ware_2_01_00_00\driverlib\f28004x\driverlib\ccs\Debug\driverlib.lib
D:\ti\c2000Ware\C2000Ware_2_01_00_00\device_support\f28004x\common\source\device.c
D:\ti\c2000Ware\C2000Ware_2_01_00_00\device_support\f28004x\common\source\f28004x_codestartbranch.asm
Build Project
,会报错
error #10234-D: unresolved symbols remain c28
,需要执行下一步,将
main.c
以 新文件形式
添加进去。1 | #include "driverlib.h" |
修改完之后进行编译 Build Project ,会报错 内存不足,信息如下:
1 | "D:/ti/c2000Ware/C2000Ware_2_01_00_00/device_support/f28004x/common/cmd/28004x_generic_ram_lnk.cmd", line 81: error #10099-D: program will not fit into available memory. run placement with alignment/blocking fails for section ".stack" size 0x400 page 1. Available memory ranges: |
以上错误信息的大意是,软件需要使用到的 堆
.stack
的大小是 0x400
而 RAMM1
的大小只有 0x3f8
。
从错误提示中看到报错的文件名,打开在
设置文件搜索路径 中添加的
28004x_generic_ram_lnk.cmd
文件,已知TI公司利用
.cmd
文件来描述芯片内部地址,打开该文件并搜索可以看到
RAMM1
的确大小不足 0x40
,且此时
RAMM1_RSVD
并未被使用,适当修改 RAMM1
的地址长度,注意不要和其他地址长度重叠即可。
重新编译即可成功。
TI官方的评估板型号为 LAUNCHXL-F280049C (如下示),支持使用JTAG连接与PC进行通信,关于评估板的手册文档如《SPRUII7》所示。
连接完毕之后,设备管理器 会出现如下图所示硬件:
本评估板自带了XDS110接口,只需要使用USB mini
线和PC进行,并在项目文件路径
targetConfigs\TMS320F280049C_LaunchPad.ccxml
文件下确保连接器和设备正确即可。
在.ccxml
文件中进行修改,点击下方的
Advanced,然后选择
cJTAG(1149.7)2-pin advance mode
,然后 save
保存即可。具体操作如下图:
截止2020年7月21日,最新版 F28004x API指南 3.04版 已上传。可以直接在联网电脑上打开,使用Google chrome浏览器可以右键翻译查阅。
以下是外围驱动库源代码的组织概览。
外设驱动程序库支持两种编程模型:直接寄存器访问模型 和 软件驱动程序模型。根据应用程序的需要或开发人员所需的编程环境,每个模型都可以单独使用或组合使用。
每种编程模型都有优点和缺点。与使用软件驱动程序模型相比,使用直接寄存器访问模型通常会产生更小、更高效的代码。然而,直接寄存器访问模型需要详细了解每个寄存器和位域的操作,以及它们的相互作用和外围设备正常操作所需的任何顺序;软件驱动程序模型使开发人员与这些细节更加隔绝,通常需要更少的时间来开发应用程序。软件驱动程序模型还产生了更具可读性的代码。
在 F28004x_device.h
文件中包含一部分伪操作,参考《SPRU430F》做出解释
1 | #define EINT __asm(" clrc INTM") //INTM置0,打开(enable)中断 |
注意: 以上伪操作中,最常使用的 EALLOW
和 EDIS
会在操作某些
写保护的寄存器时使用,利用这两句伪操作来暂时关闭写保护,写完寄存器后再打开保护。
以下为《SPRU430F》第107 ~ 113页提供的伪操作指令合集。
C2000Ware是C28系列芯片的IDE组成部分,开发过程一共安装了两个版本,2.01版与3.04版,两者的文件结构基本一致,包括 样例应用、头文件存放路径 等基本一致,不一致的地方是文件内容,包括 例程的增删 和 说明文档的更新。始终不变的是C2000Ware的例程类别,总共有两种,分别在两个路径下:
D:\ti\c2000Ware\C2000Ware_2_01_00_00\driverlib\f28004x\examples
,存放的是
驱动库样例应用(Driver Library Example
Applications),是以驱动及其案例来进行代码编写的,每个project都可能会涉及许多外设,因此使用前需要先阅读相关文档去了解该案例的开发目标,再结合实际进行修改。D:\ti\c2000Ware\C2000Ware_2_01_00_00\device_support\f28004x\examples
,
存放的是 位域样例应用(Bit-Field Example
Applications),是以最小外设功能为目标的代码编写过程,并不是以project作为分类单元的,而是以外设为分类方法,每个外设的某个细节功能作为代码间的区别。例如:同样是了解PWM外设的使用案例,从
驱动库样例应用(3.04版)
中能找到的案例只有3个:boostxl_afe031_f28004x_pwmmode
、hrpwm
和
adc_ex1_soc_epwm
,具体如何使用,使用到PWM里的何种功能并不能从项目案例中直接看出来。但是从
位域样例应用(3.04版)
中,可以找到13个PWM下的应用:trip_zone
/
updown_aq
/ synchronization
/
digital_compare
/ digital_compare_event_filter
/ valley_switching
/ edge_filter
/
deadband
/ dma
/ chopper
/
configure_signal
/ monoshot_mode
/
up_aq
。
PS:以上信息因C2000Ware版本不同而不同。
以上信息,从《F28004x_DEV_USER_GUIDE》中也可以查看到,理清套件下的文件结构需要阅读的文件有两个,但在3.04版本的C2000Ware中未提供:
2.01与3.04版本都提供了网页版的手册,可以在对应 \docs\
文件夹下查阅。推荐使用网页版 API指南
进行函数原型速查。
逆变器,即将直流DC变换为交流AC的过程。利用 DSP输出波形,控制接入高压直流电的(高位 和 地位)两个IGBT输出正向和反向两种电压,形成交流AC。
逆变电路根据直流侧电源的性质不同(电压源/电流源)分为两种类别: 电压型逆变电路 和 电流型逆变电路。
电压源逆变电路的特点:
- 直流侧为电压源,或并联有大电容,相当于电压源。直流侧电压基本无脉动,直流回路呈现低阻抗。
- 由于直流电压源的钳位作用,交流侧输出电压波形为矩形波,且与负载阻抗角无关。而交流侧输出电流波形与相位因负载阻抗情况的不同而不同。
- 当交流侧为阻感负载时需要提供无功功率,直流侧电容起缓冲无功能能量的作用。为了给交流侧向直流侧反馈的无功能量提供通道,逆变桥各臂都并联了 反馈二极管。
单相电压型逆变电路 可以根据变换出来的波形分为 半桥 或 全桥,即 单相半桥变压型逆变电路 和 单相全桥变压型逆变电路。
具体为:利用型号为 TMS320F280041
芯片的GPIO(最高输出频率为25MHz)输出一个30KHz的PWM波形,需要使用到
ePWM外设。使用官方C2000套件示例中提供的 ePWM6
案例进行开发,输出高位和低位两个幅频合适的波形后,接到型号为
2EDL05I06PF
的驱动上,由驱动控制型号为
IKW50N65WR5
的IGBT 高频开断。
graph TB 系统初始化 --> 使能PWM模块时钟 --> 初始化PWM模块 --> PWM工作
graph TB 配置TB子系统 --> 配置CMPA和CMPB --> 设置CMPC寄存器 --> 配置TZ子系统 --> 配置AQ子系统 --> 配置DB子系统 --> 配置ET子系统
TMS320F280041
的外部晶振输入频率 fXTAL 在 10 ~ 20 MHz
之间,自身工作频率fSYSCLK 在 2 ~
100MHz之间。以下为工作频率设置的样例,利用内源时钟 10MHz
,产生芯片满负工作频率 100MHz
:
本系统采用10MHz的内源时钟 INTOSC2 作为 OSCCLK,其配置方法(包括设置公式和函数原型)都已在前文详细描述和记录,请参考该代码。
以下为两个版本下案例所给的 main()
函数的流程对比。
左侧用的是Bitfield模式,右边用的是driver-lib模式。
同样是将中断重新映射至ISR函数里,下图左侧是2.01版本下的driver-lib模式,右侧是3.04bit-field模式。
在Driver-Lib的 <f28004x_globalVariabledefs.c>
中,提供了ePWM下各模块的定义,如下面代码所示,定义了 外设
ePWM1所有的寄存器(结构体)EPwm1Regs
,是EPWM_REGS的结构体实例。
使用方法:使用该外设下的寄存器时直接按照结构体变量的方法使用即可,如
EPwm1Regs.TBPRD = EPWM1_TIMER_TBPRD
。
1 | #ifdef __cplusplus |
1 | int EPWM1_TIMER_TBPRD = 800; // 设置PWM的周期PRD为1600 |
1 | /*----------------------------------------------------------------------- |
从上面代码里总结出下面配置步骤:
graph LR 配置开始 --> 配置TBCLK --> 配置影子寄存器ShadowRegister --> 配置CMP --> 配置TripZone --> 配置AactionQualifier --> 配置ePWM中断 --> 配置结束
通过配置下方代码中 main()
函数的参数,来对CPU
、ePWM的相关寄存器参数进行调节,可以实现目标PWM周期为30.8Khz的设置。
参数计算代码就不贴了,可以直接下载:main.cpp
从系统时钟设置到外设时钟设置过程中,需要设置的寄存器、位及其数值具体如下(仅供参考):
CLKSRCCTL1
寄存器下的 OSCCLKSRCSEL
值为
0x00
。SYSPLLMULT
寄存器下的 IMULT
/ FMULT
/ ODIV
值为 0x13
/
0x3
/ 0x1
(或使用十进制赋值 19
/
3
/ 1
)。PCLKCRx
寄存器下的对应位,参考《SPRUI33D》第195页相关寄存器说明。TBCTL
寄存器下的
HSPCLKDIV
及 CLKDIV
为
0x00
(1
) 及 0x01
(2
)(默认情况下也是这个值),以获得
CPU时钟速率一半值的外设时钟。TBCTL
寄存器下的
CTRMODE
为 0x2
,打开 TB counter
的增减计数模式。TBPRD
寄存器的值为十进制的
1600
。\[ F_{TBCLK} = {F_{EPWMCLK} \over HSPCLKDIV · CLKDIV} \tag{PWM工作频率公式} \]
\[ T_{EPWM} = (TBPRD +1) · T_{TBCLK} = {(TBPRD +1) · HSPCLKDIV · CLKDIV \over F_{EPWMCLK}} \tag{单个ePWM波周期公式} \] 根据《TRM》P1888,HSPCLKDIV 的可取值范围是 \(\{1,2,4,6,8,10,12,14\}\) ,而 CLKDIV 的可取值范围是 \(\{1,2,4,8,16,32,64,128\}\) 。
如果不修改 TBCTL 寄存器下的
HSPCLKDIV 和
CLKDIV,默认情况下这两个的乘积就是 2
,而
\(F_{EPWMCLK} = 100MHz\) ,也就是最终
\(T_{EPWM}\) 的值完全由 \(T_{PRD}\) 来决定。已知
TBPRD 寄存器是16位寄存器, 取值范围是 \([0,65535]\)
,在本项目情况下,可以写成如下等式:
此处需要注意,因为设定的ePWM使用 增减计数模式,TBPRD 值实际上是设定值的两倍。
死区时间需要联系实际电路中的 IGBT 及其 驱动器 进行综合分析。
下表为温度范围在 [25℃, 175℃] 时,IGBT开关特性和二极管特性表。
已知死区时间最高需要达到 \(145ns\),而系统频率在100MHz时,\(T_{SYSCLK} = 10^{-8}s = 10ns\),而外设 ePWM1 的工作频率为50MHz,\(T_{ePWM} = 20ns\),也就是说,至少需要等待15个系统周期或 \([5.5,7.25]\) 个PWM工作周期。
根据公式 $CMP = T_{HighLevel} / (T_{TBCLK} *2) $,已知所需
IGBT 驱动器内部有个 内部死区时间(Internal Deadtime) \(t_{DT(internal)} = 100ns\)
根据 死区时间计算公式 \(t_{DT} = [ (t_{d(off),max} + t_{f,max}-t_{d(on),min}) + (t_{PHL,max} - t_{PLH, min})] · S\) ,代入计算:
首先需要弄明白以下几个问题:
数模转换开发不同于ePWM配置开发,不仅信号输入和子模块配置方式上有许多不同点,最明显的不同是高频逆变器不需要使用到触发中断,但ADC模块不论是转换触发还是读取数值都需要中断来进行操作,即由外部中断脉冲输入ADC模块,促使ADC模块将电容电压依据参考电压转换为数值并存写到寄存器中,然后生成中断脉冲,触发中断程序来读取其中的数值。
graph TB 进入ADC中断 --> 读取ADC寄存器数值并存储至全局变量 --> 交由主循环计算电压/电流/温度值并判断是否安全 --> id1[定时器中断检查开关/安全] --是--> 保持PWM开启 id1 --否--> 关闭PWM
graph LR 系统初始化 --> 使能ADC模块时钟 --> 配置模块参考电压 --> 配置模块时钟 --> 配置SOC通道 --> 配置采样周期 --> 配置采样触发源 --> 配置EOC中断触发源
ADC模块在功能描述上也有别于ePWM,ePWM采用许多子模块的描述方法,ADC则使用 通道(CHNL,或 channel)、SOC、EOC、INT 等描述词汇,需要区分理解,且ADC模块从顶自下参数都有许多配置限制,具体看前面的描述吧。
1 | /*----------------------------------------------------------------------- |
ADC为12bit分辨率的电压转换外设,下面为读取数值和特定量程之间的电压转换代码,可以通过宏定义来修改最高量程、参考电压最高值 和 单个引脚的偏差值
1 | #define ADCDATA_OFFSET 40 // ADC2通道的量程偏差 |
通过热敏电阻和普通10KΩ电阻的串联,施以3.3V电压,并在两个电阻之间引出导线接入ADC,ADC通过检测电压数值来输出对应 电压值。
由下方的热敏电阻 阻值特性表 可知,热敏电阻的温度升高和阻值之间并非线性关系。阻值偏差也在各温度间情况不同。
中间部分省略......
中间部分省略......
已知热敏电阻的阻值和温度关系公式—— B常数求解公式
,如下式1: \[
B = {ln(R_{T1}) - ln(R_{T2})\over {1 \over {T_1}} -{ 1 \over T_2}}
\] 将阻值特性表中的目标温度 110℃
安全限制温度 作为 $ T2 $,其
热敏电阻值(均值) 2508Ω
作为 $ R_{T2}
\(,\) T_1 \(
则为 `25℃` ,\) R_{T1} $ (均值)为
50000Ω
,代入进行计算,求得B常数为 4021.84
。
将B常数作为已知参数,将式1进行变换获得可求解实时温度的公式2: \[
T_2 = {B · T_1 \over {T_1· (ln(R_{T2}/R_{T1}) + B} }
\] 注意,以上的 $ T_1 $ 和 $ T_2 $ 指的是在绝对零度的基础上,即
25℃ + 273.15
才是 $ T_1 $
,目标温度在公式计算出来之后也需要减去 273.15
。
已知ADC寄存器的度数结果为 $ ADC $,其读数最大值为 $ ADC_{MAX}
$,对应的电压分别为 $ V_{ADC} $ 和 $ V_{REF} $ 。ADC模块的参考电压 \(V_{REF}\)
并不会收到外接ADC电路的影响,该参数是由参考电压和内部定义进行设置的,此处可以看做恒常
3.3V
。 \[
{ADC \over ADC_{MAX} } ={V_{ADC} \over V_{REF}}
\] 由 式3 可以变形成
式4,ADC是寄存器中容易读取的数值,公式变形如下: \[
V_{ADC} = {ADC · V_{REF} \over {ADC_{MAX}}}
\]
已知上方电路图中的\(R_{76}\) 和
\(R_{78}\) 的阻值均为 10KΩ
,用 \(R\) 来表示,向ADC电路施加的
3.3V
电压用 \(V_{thermal}\)
来表示,此时,ADC电路内的电压公式如下: \[
V_{ADC} = {V_{thermal} \over {R_{thermal} + R}} · R
\] 联立 式4 和 式5
,可得热敏电阻的实时电阻计算公式如下: \[
R_{thermal} = {ADC_{MAX} · V_{thermal} · R \over ADC · V_{REF}} -R
\] 若电路设计时,确保此处 \(V_{thermal}\) 和 \(V_{REF}\) 一致,进行公式简化得: \[
R_{thermal} = {ADC_{MAX} · 3.3 · R \over ADC · 3.3} - R = {ADC_{MAX}·R
\over ADC} - R = {(ADC_{MAX}-ADC)·R \over ADC} ={ (4095-ADC)·10000 \over
ADC}
\]
热敏电阻的阻值实时求算公式与B常数、初始温度、初始阻值等无关,仅与外围电路的电阻
\(R\) 有关。
有两种方法可以设定安全关断:
宏定义:
1 | // 下方为ADC读值配置相关宏定义 |
电压转换:
1 | /*----------------------------------------------------------------------- |
阻值转换:
1 | /*----------------------------------------------------------------------- |
温度转换:
1 | /*----------------------------------------------------------------------- |
开发目标:实现对 PWM边沿 和 IGBT分压电流过零点 的相位时间差 δt 的检测,已达到是否达到谐振。
graph TB 系统初始化 --> 使能ECAP模块时钟
项目Flash烧录即将代码从Debug模式变成Release模式,将代码的存放区域从RAM变成FLASH。需要在项目属性中修改原有的Release配置,或者复制原有的调试模式进行部分修改。
在前方已经调试好项目的情况下,FLASH模式较调试时使用DEBUG模式需要增加以下内容或步骤:
.cmd
内存文件</
向 预定义标志(Predefined
Symbols)添加两个标志名称: CPU1
和 _FLASH
。
添加成功时可以看到对应标志下的预编译被打开了,如下示:
选择 编译器(C2000
Compiler),并在下方添加${output_flags} ${output}
,修改过后则变成了
${command} ${flags} ${output_flags} ${output} ${inputs}
。如果只需要使用到调试模式(debug mode),就只需要原来的
${command} ${flags} ${inputs}
即可。
在
D:\ti\CodeComposerStudio0930\ccs\ccs_base\c2000\include
路径下查找到对应的 FLASH 内存文件,并在
通用页面(General)的
链接器命令文件(Linker command file)栏进行替换。
硬件工作流程要求如下:
graph LR 开机 --> id1[待机模式] --检测到工作设备--> id2[工作模式] --检测到设备离开--> id1
开机后立即进入待机模式,对待机模式下的要求:
开机启动流程:
graph id1([开机启动]) --> id2[系统初始化/开启各外设时钟] --> 各外设寄存器清空并初始化 --> id3 --是--> id4([进入待机模式])
待机工作流程:
flowchart id[定时器1启动]
为了达到 \(4\mu s\)
的脉冲电路,需要设置CMPA = 100
,在
TBCTR = 100
且 TBCTR DIRECTION = UP
时
待机模式下IGBT并不工作,但仍需要对周围的设备进行检波和识别,检波方法:
注意:过零检测时两个波形的过零允差需要在一定数值范围内,这个数值范围待测定。
若在工作设备检测下,检测到设备存在,则进入工作模式,否则维持待机模式,持续进行工作设备的检测。
已知PWM工作频率公式 和 单个ePWM波周期公式,可求得单个ePWM波的周期最大为: \[ T_{ePWM\ Max} = (TBPRD_{MAX} +1) · T_{TBCLK} = (65535*2+1) · 0.00000002 = 2.6214 ms \tag{单ePWM最大周期计算} \] 已知: \[ T_{TBCLK}={1s \over 50MHz} = {1 \over 50,000,000} = 2^{-8}s = 20ns\tag{系统时钟周期} \] 时间单位: \[ 1s = 10^{3}ms = 10^{6}\mu s=10^9ns \tag{时间单位} \] 反过来,如果已知高电平工作时长,需要求其CMP值,也可以代入 \(T_{TBCLK}\) ,求得上下边的 CMP 值应为100 \[ CMP = T_{HighLevel} / (T_{TBCLK} *2) = 4*10^{-6}s/(2*2*10^{-8}s) = 100 \tag{已知高电平时长求CMP值} \]
进入工作模式后,按照预设好的多个工作条件(PWM工作频率、占空比)进行(手动/自动)切换,以调节设备工作功率。
注意:不论如何,占空比一定不能超过 50%。
编码工作由F280041芯片的SCI外设负责,需要在对应的代码中设置好一些参数(数据长度、校验位、停止位等),可以使用CH340芯片的串口助手进行读写测试。
全部的设置工作如下:
graph TB 系统初始化 --> 使能SCI通信模块时钟 --> 配置LSP时钟分频 --> 配置SCI模块 --> 使用中断或主程序接收处理上位机指令
graph LR 配置SCIFIFO模块 --> 初始化SCI相关寄存器 --> 配置SCI相关寄存器
配置SCI相关寄存器需要设置到以下几个参数位:
SCICHAR
ADDRIDLE_MODE
PARITYENA
、校验方式
PARITY
)STOPBITS
TXENA
、接收端使能
RXENA
)SLEEP
RXERRINTENA
、接收brk中断使能 RXBKINTENA
、传输中断使能
TXINTENA
)SCIHBAUD
和 SCILBAUD
PC端使用CH340芯片的USB-TTL 转换器与芯片主板的 RX
口 和
TX
口直接通信,通信期间,PC端需要和发送端保持相同的设置,包括波特率、停止位大小、数据位、奇偶校验是否开启(开启的话为奇校验还是偶校验)等均需要保持一致。如下所示,左侧为上位机设置,右侧为SCI通信的设置。
注意:测试设备端口上的
RX
TX
端口要和 TX
RX
相反连接,且需要共地。
下图为SCI_ECHOBACK功能效果演示:
如下所示,校验位设置错误时,数据乱码。
商用台式电脑主机主板背部自带DB9接口(RS232),使用RS232转RS485的转换器进行连接,然后使用双绞线连接被控设备对应的接口。需要注意的是,转换器上的RS485电路不具备供电接口,
VCC
/ GND
同样需要进行连接,且需要由电气的隔离电路提供。
转换器与被控设备的引脚连接方式为:T/R-
连接
IC_COM_MAX487EEPPA
芯片上的 B
口;T/R+
连接 IC_COM_MAX487EEPPA
芯片上的
A
口;其中 A
/ B
口更多地被称为
A+
/ B-
口。
连接成功后,使用串口通信助手进行测试,并查看寄存器结果,看出数据传输成功。
使用RS485的优势是,RS232/RS485转换器间存在电气隔离,RS485和TTL之间也存在电气隔离,一旦被控设备发生浪涌,不会损坏PC机。
通信控制相关流程如下:
graph id1[等待上位机数据] --> 收到通信数据 --> id2{判断FLAG对应的位是否使能} --是--> 修改对应的数据 --> 结束 id2 --否--> id1
传输时,需要注意对应的数据是否为 16bit
或
8bit
,如PWM周期值为 16bit
寄存器,而传输过程需要使用 8bit
的
unsigned char
字符进行传输,则需要下位机对收到的字符进行位移和按位或运算。
通信格式约定如下:
char0
是 标志位之用
flag
,将字符转换成二进制即为对应数据的修改使能控制,所有对数据的修改首先要求标志位(flag)对应位为1时才激活,如下表所示;unit16_t
数据;8位数据合成为16位数据函数如下:
1 | /*----------------------------------------------------------------------- |
从《TRM》P1814 中可知,(上升沿或下降沿的)死区时间大小都其自身的 寄存器设定值,以及 \(T_{TBCLK}\) 有关,关系公式如下: \[ \notag FED = DBFED · T_{TBCLK}\\ RED = DBRED · T_{TBCLK} \]
如果要改变 \(f_{EPWM}\) 的值,则需要改变 TBPRD 的值,因为 \(T_{TBCLK}\) 的值暂时不支持改变(因为涉及到两三个参数,太麻烦了,不想写代码),因此 TBPRD 和 \(f_{EPWM}\) 的关系为: \[ {align*} TBPRD&=&\frac{1}{2}(\frac{T_{EPWM}}{T_{TBCLK}}-1)\\ &=&\frac{1}{2}(\frac{1}{T_{TBCLK}·F_{EPWM}}-1) \nonumber \]
所有计算单位都是
Hz
和s
z。因为
HSPCLKDIV=0x00, CLKDIV=0x01
,,\(F_{TBCLK} = F_{SYSCLK} /2 = 50MHz\) ,得 \(T_{TBCLK} = 2*10^{-8}s\) 。\(T_{EPWM} = 3.33*10^{-5}s\) 时,其 \(TBPRD = 1/2 · (3.33*10^{-5} / 2*10^{-8} -1) = 832\)
当已知需要设置 epwm的目标频率为
30Khz
时,只需要代入公式即可: \[ \notag TBPRD = 1/2 * (\frac{1}{2e-8*3e4} -1) \]
占空比,实际上就是EPWM 在整个周期内部,高电平时长与周期长度的比值。
占空比如果通过寄存器值来进行表达和设置,会出现误差,因为死区时间的寄存器值工作逻辑和原先设置的ePWM工作逻辑存在差异,存在错误的占空比关系公式: \[ Duty \ Cycle &=& \frac{(TBPRD-CMP)·2-DB·2}{TBPRD·2}\\ &=& \frac{(TBPRD-CMP)-DB}{TBPRD} \nonumber \]
正确的方法就是去计算高电平的时长,然后除以整个任务周期时长: \[ Duty \ Cycle = \frac {T_{High \ Level}}{T_{EPWM}} \notag \] 在C++编写的RS485指令的代码中,则通过人为设定占空比来反向计算高电平时长。
在已知 死区时间 DBxED 寄存器数值、 TBPRD 周期寄存器数值 和 任务占空比 三个参数的情况下,目前使用的计算 CMP数值的算法如下: \[ CMP = (1- Duty)·(TBPRD-DB) \tag{CMP-A1} \]
在使用上方 CMP-A1 算法公式的情况下,输入目标频率、死区时间 和 所要求的占空比大小,以获得RS485通信指令,如下:
通信数据收到无误后会返回确认和执行字样:
测试结果:
30 50 2.4
AB1F034001640164007700770050
30 50 1.0
AB1F034001870187003200320050
30 45 1.0
AB1F034001AE01AE003200320050
30 45 2.4
AB1F034001880188007700770050
开机默认波形如下:
30 30 5
AB1F03400198019800F900F90050
经过了三天的努力,终于在10月11日完成了基于C++的 terminal串口通信。哈哈哈哈哈,可以成功实现通信啦~~
只要先在程序内选择指定接口,然后输入 目标工作频率、(下桥臂)任务占空比 和 (上桥臂)死区时间 就可以计算出指令并进行发送。
可以从逻辑分析仪获得较为粗糙的波形信息,如下:
之前输入任务占空比的时候,都是控制的EPWMB的任务占空比,每次输入 40%的任务占空比(死区时间为0)时,EPWMA的任务占空比都为 60%。再加上死区时间实际上是在已知EPWMB任务占空比的情况下去减小EPWMA的占空比的时间。经过简单的修改,可以实现输入目标任务占空比,可以实时控制EPWMA的实际占空比了,死区时间只对EPWMB有影响。
错误提示:存储区域范围(Memory Range)重复声明。
解释:在两个 .cmd
文件中,对 memory 的
page0 下的存储范围重复声明。
1 | "../28004x_RAM_afe031_lnk.cmd", line 7: error #10263: BEGIN memory range has already been specified |
解决办法:删除、移动
28004x_RAM_afe031_lnk.cmd
,或更改其后缀名;确保在
项目属性(Properties)中的
连接器命令文件(Linker Command File)为正确的
280041_RAM_lnk.cmd
。
无法找到指定头文件,报错如下:
1 | "D:/ti/c2000Ware/C2000Ware_2_01_00_00/device_support/f28004x/common/include/F28x_Project.h", line 47: fatal error #1965: cannot open source file "f28004x_device.h" |
可以借助本地搜索工具everything进行文件搜索,将缺失的头文件的绝对路径复制,并粘贴至
头文件设置 下的搜索路径中。上方的错误缺失 f28004x_device.h
,添加
D:\ti\c2000Ware\C2000Ware_2_01_00_00\device_support\f28004x\headers\include
绝对路径以供搜索,即可解决。
在将函数需要的头文件添加到项目后,进行 build project ,出现 “未能解决的标志存留” ,有2种可能原因:
.c
文件。1 | undefined first referenced |
解决方法:
排除第一种情况后,找到 未定义符号(undefined symbol)对应的头文件
f28004x_device.h
和 f28004x_sysctrl.h
所在的文件路径
D:\ti\c2000Ware\C2000Ware_2_01_00_00\device_support\f28004x\headers\include
,注意到该头文件上一级有个 source
文件夹,存储着头文件对应的 .c
文件:
f28004x_globalvariabledefs.c
,将该头文件以链接或复制的形式添加到项目文件中即可。
注意:大部分情况下,header files 都会有其对应的
.c
或 .asm
文件,需要去相同的目录下方寻找即可。
在将评估板接到PC过程中,进行连接测试(Test Connection),报错如下:
1 | -----[Print the reset-command software log-file]----------------------------- |
因为本开发板设计上使用两线制,为 cJTAG,而默认情况下是标准的 JTAG,只需要 TCK 和 TMS 两个信号(官方文档的常见问答部分有说明)。
在.ccxml
文件中进行修改,点击下方的
Advanced,然后选择
cJTAG(1149.7)2-pin advance mode
,然后 save
保存即可。具体操作如下图:
再次运行结果如下:
1 | -----[Print the reset-command software log-file]----------------------------- |
时钟初始化错误是可能新建文件后可能会出现的隐形错误,因为程序编译的时候不报错,只报 WARNING ,因此很难察觉。
但是,能通过 Expression
界面输入寄存器名称,用以观察其是否被初始化,如下图示,为自建项目后初始化的寄存器数值,只运行第一步PLL初始化,可以观察到
ClkCfgRegs
并未被初始化。
同样使用官方样例代码进行仅一步的PLL初始化操作,可以发现初始化成功。
仔细对比和检查后发现,自建项目中缺少了几个,通过link方式或copy方式添加进来,再到项目属性下的
Symbol Management中,为输出模块添加代码入口点 code_start
即可。
使用JTAG调试器XDS100 V2连接至目标设备上时,如果出现下图所示的 “Disconnected: Unknown”,说明目标MCU/DSP没有正确识别。
首先应检查JTAG是否链接正常,打开“设备管理器”进行查看,如果设备连接器中出现了该JTAG设备,则进一步查看项目文件下的.cxxm
l文件,该文件用于配置设备连接信息,包括芯片信号、JTAG调试器信号、通信方式、通信速率等。确保所有设置都正确以后进行全面断电、断开连接、重启CCS并重新尝试Debug,如果仍旧出现问题,请按以下步骤进行操作。
在Debug界面,点击目标CPU,点击 Connect Target
进行手动连接。
连接正确后,手动导入 Build
完之后的 .out
文件,将该程序手动烧写至CPU的RAM/Flash中
选择对应项目下生成的 .out
文件:
可以在Console台(界面)观察到已经写入和初始化成功:
出现以上错误时,我正打算新建函数,但是写到一半忘了,去忙别的,回头编译就出错了。找到最近的修改痕迹,然后果然发现自己只写了一个函数数据类型的声明,其他啥都没写,将其删除即可重新编译。
上述提示标志为 i
,也就是有关于性能优化的提示,可以在项目属性的编译器中,打开对应的优化开关,如下图。
LDO: Low dropout, A DC linear voltage regulator which can operate with a very small input/output differential voltage.
VREG: Voltage Regulator. 稳压器。
VREF: Voltage Reference. 参考电压
ESD: Electrostatic Discharge, 静电放电。
标志:flag,也可以被称作 操作信号 或其他。
LSPCLK: Low-speed peripheral clock frequency of the device, 低速外设时钟频率
IP: Internal Peripheral,内部外设。
1 | #include <iostream> |
这个小例子直接用make编译是无法通过的。报错如下: 1
2
3
4
5/tmp/ccYB66pt.o:在函数‘std::thread::thread<void (&)()>(void (&)())’中:
1-1.cpp:(.text._ZNSt6threadC2IRFvvEJEEEOT_DpOT0_[_ZNSt6threadC5IRFvvEJEEEOT_DpOT0_]+0x21):对‘pthread_create’未定义的引用
collect2: 错误:ld 返回 1
<builtin>: recipe for target '1-1' failed
make: *** [1-1] Error 1
解决方法是在编译的时候加上 -lpthread
参数。这个类用到posix实现的线程了。 1
2g++ -o test test.cpp -lpthread
./test
结果输出: 1
hello concurent world!
通过在Linux上编写代码,计算求解ADAU1772在 输入时钟(MCLK Input)为16.6MHz时,如何设置内部的4个参数,以达到符合输出时钟要求的目的。
据《ADAU1772》和SigmaStudio,1772 Codec 内部可修改的参数有 输入时钟分频(Input Clock Divider)、整数设置(Integer Setting)、分子(Numerator) 和 分母(Denominator)四个。
目标输出时钟(VCO Output) 是 24.576MHz。
以下为《ADAU1772》第30页中,关于PLL计算的公式描述:
以下为SigmaStudio中的PLL设定界面参数设定:
注意:本代码在Linux上,以C++17的版本进行编译运行。
编译代码如下:
1 | g++ main.cpp -std=c++17 -lpthread |
在编写代码时,考虑到SigmaStudio和数据手册之间可能存在表述差异,因此计算PLL的源代码中,也包含了开启
1/2
系数的宏定义 #define HALFCOFF 1
,具体代码如下:
1 | /*--------------------------------------------------------- |
通过自行编写的代码,遍历所有可更改的系数进行计算求解,取得小数点后9位精度,并且符合要求的系数如下(含1/2系数):
以下为不含1/2系数的结果:
经过手工计算,结果(小数点后9位)确实满足要求。
PCM(Pulse Code Modulation)也被称为脉码编码调制,PCM中的声音数据没有被压缩,它是由模拟信号经过采样、量化、编码转换成的标准的数字音频数据。采样转换方式参考下图进行了解:
音频采样包含以下几大要素:
采样率表示音频信号每秒的数字快照数。该速率决定了音频文件的频率范围。采样率越高,数字波形的形状越接近原始模拟波形。低采样率会限制可录制的频率范围,这可导致录音表现原始声音的效果不佳。根据奈奎斯特采样定理,为了重现给定频率,采样率必须至少是该频率的两倍。例如,一般CD唱片的采样率为每秒 44,100 个采样,因此可重现最高为 22,050 Hz 的频率,此频率刚好超过人类的听力极限 20,000 Hz。
图中A是低采样率的音频信号,其效果已经将原始声波进行了扭曲,B则是完全重现原始声波的高采样率的音频信号。
数字音频常用的采样率如下:
位深度决定动态范围。采样声波时,为每个采样指定最接近原始声波振幅的振幅值。较高的位深度可提供更多可能的振幅值,产生更大的动态范围、更低的噪声基准和更高的保真度。
位深度越高,提供的动态范围越大。
在上面的名词解析中我们应该对PCM有了一定的理解和认识,下面我们将对PCM做更多的讲解。
如果是单声道的文件,采样数据按时间的先后顺序依次存入。如果是单声道的音频文件,采样数据按时间的先后顺序依次存入(也可能采用 LRLRLR 方式存储,只是另一个声道的数据为 0)。
如果是双声道的话通常按照 LRLRLR 的方式存储,存储的时候还和机器的大小端有关。(关于字节序大小端的相关内容可参考《字节序问题之大小端模式讲解》进行了解)
PCM的存储方式为小端模式,存储Data数据排列如下图所示:
描述 PCM 音频数据的参数的时候有如下描述方式:
1 | 44100HZ 16bit stereo: 每秒钟有 44100 次采样, 采样数据用 16 位(2 字节)记录, 双声道(立体声) |
44100Hz 指的是采样率,它的意思是每秒取样 44100 次。采样率越大,存储数字音频所占的空间就越大。
16bit 指的是采样精度,意思是原始模拟信号被采样后,每一个采样点在计算机中用 16 位(两个字节)来表示。采样精度越高越能精细地表示模拟信号的差异。
Stereo 指的是声道数,也即采样时用到的麦克风的数量,麦克风越多就越能还原真实的采样环境(当然麦克风的放置位置也是有规定的)。
WAV 是 Microsoft 和 IBM 为 PC 开发的一种声音文件格式,它符合 RIFF(Resource Interchange File Format)文件规范,用于保存 Windows 平台的音频信息资源,被 Windows 平台及其应用程序所广泛支持。WAVE 文件通常只是一个具有单个 “WAVE” 块的 RIFF 文件,该块由两个子块(”fmt” 子数据块和 ”data” 子数据块),它的格式如下图所示:
WAV 格式定义
该格式的实质就是在 PCM 文件的前面加了一个文件头,每个字段的的含义如下:
1 | typedef struct { |
WAV 文件头解析
这里是一个 WAVE 文件的开头 72 字节,字节显示为十六进制数字:
1 | 52 49 46 46 | 24 08 00 00 | 57 41 56 45 |
字段解析如下图:
1 | int simplest_pcm16le_to_wave(const char *pcmpath,int channels,int sample_rate,const char *wavepath) |
注意:函数里声明的数据类型unsigned long在有些C编译器上是64位的,这时候要改成unsigned int才可以,否则wav头有88bytes,标准的是44bytes,改完就正常了,对C还不熟悉的人小小的心得,另外,声道数和采样率也要注意,一般采样率有44100/16000/8000,要确认是哪个,声道是1还是2,这两个参数要设置好才会有正确的转换结果。
一般来说 PCM 数据中的波形幅值越大,代表音量越大,对于 PCM 音频数据而言,它的幅值(即该采样点采样值的大小)代表音量的大小。
如果我们需要降低某个声道的音量,可以通过减小某个声道的数据的值来实现降低某个声道的音量。
1 | int pcm16le_half_volume_left( char *url ) { |
上述代码做的事情是:在读出左声道的 2 Byte 的取样值之后,将其转成了 C 语言中的一个 short 类型的变量。将该数值除以 2 之后写回到了 PCM 文件中。
因为PCM音频数据是按照LRLRLR的方式来存储左右声道的音频数据的,所以我们可以通过将它们交叉的读出来的方式来分离左右声道的数据:
1 | int simplest_pcm16le_split(char *url) { |
本程序中的函数可以从PCM16LE单声道数据中截取一段数据,并输出截取数据的样值。函数的代码如下所示:
1 | /** |
本程序中的函数可以通过计算的方式将PCM16LE双声道数据16bit的采样位数转换为8bit。函数的代码如下所示:
1 | /** |
PCM16LE格式的采样数据的取值范围是-32768到32767,而PCM8格式的采样数据的取值范围是0到255。所以PCM16LE转换到PCM8需要经过两个步骤:第一步是将-32768到32767的16bit有符号数值转换为-128到127的8bit有符号数值,第二步是将-128到127的8bit有符号数值转换为0到255的8bit无符号数值。在本程序中,16bit采样数据是通过short类型变量存储的,而8bit采样数据是通过unsigned char类型存储的。
本程序中的函数可以通过抽象的方式将PCM16LE双声道数据的速度提高一倍,采用采样每个声道奇(偶)数点的样值的方式,函数的代码如下所示:
1 | /** |
视音频数据处理入门:PCM音频采样数据处理 --> 致敬雷神!
Directives,即 伪操作,是汇编语言中的特殊指令助记符。主要作用是为完成汇编程序做各种准备。伪操作仅是在源程序进行汇编时由汇编程序处理,而不是在计算机运行期间由机器执行的指令。即,伪操作只在汇编时起作用,一旦汇编结束,其使命也就结束了。
Pseudo-Instruction,即 伪指令,是汇编语言程序里的特殊指令助记符,不是 真指令。伪指令在汇编时被替换成合适的机器指令(根据芯片架构而定,不同芯片架构之间指令可能不同),故其也只在汇编时其作用,不在机器运行期间由机器执行。
.asm 文件具有以下样式,包含 预处理指令(主要是C/C++宏定义)、汇编伪操作类别(assembler direcitives)、数据块、代码块、条件编译预处理 和 汇编标签。
1 | int img288; //定义C语言变量 |
以上C语言与汇编代码经过编译后:
1 | ax0 = 0x5C; |
此处可否把
dm()
当成一个类似于指针的东西
使用汇编子程序是C语言程序与汇编语言接口的另一种方法。用户定义的子程序放在单独的汇编文件中,或是做成二进制的库文件,并将子程序的定义用GLOBEL输出,汇编后就可以供C语言程序调用。下面是一个不需要参数的子程序的例子:
1 | .MODULE/RAM_delay_; |
注意:以上代码源自参考链接4,实际上代码中的关键字相差可能较大,需要根据实际情况进行改动。
比如,21479系列芯片与ADAU1939芯片一同工作,其ADC采样代码如下:
1 | .section/pm seg_pmco; //程序代码块,procedure memory section |
This section describes cc21k language extension keywords to C and C++ that support the dual-memory space, modified Harvard architecture of the ADSP-21xxx processors. There are two keywords used to designate memory space: dm and pm. They can be used to specify the location of a static or global variable or to qualify a pointer declaration.
以下规则适用于两种内存关键字(dm/pm):
数字音频信号的传输标准,如:
I2S的标准文件源自 飞利浦半导体(Philips Semiconductors)发表于1986年的《I2S bus specification(1996年修订版)》。
I2S(Inter-IC Sound)采用了独立的导线传输时钟与数据信号的设计,通过将数据和时钟信号分离,避免了因时差诱发的失真,为用户节省了购买抵抗音频抖动的专业设备的费用。
需要注意:I2S是一种音频编码协议,同时也是一种音频接口,因此采用I2S接口方式的不一定采用I2S编码,还可以采用 左对齐(Left Justifying)编码 或 右对齐(Right Justifying)编码。
支持全双工/半双工
支持主/从模式
和PCM相比,I2S更适合立体声系统。当然,I2S的变体也支持多通道的时分复用,因此可以支持多声道。
To minimize the number of pins required and to keep wiring simple, a 3-line serial bus is used consisting of a line for two time-multiplexed data channels, a word select line(WS) and a clock line (SCK).
从上图得知,I2S总线共需要三个引脚:
另,有时为了使系统间能够更好地同步,还需要另外传输一个信号MCLK,称为主时钟,也叫系统时钟(Sys Clock),是采样频率的256倍或384倍。
注意:能够生成SCK和WS的就是主设备(Master)。
VL | VH | |
---|---|---|
输出电平 | <0.4V | >2.4V |
输入电平 | 0.8V | 2.0V |
这是使用的TTL电平标准,随着其他IC(LSI)的流行,其他电平也会支持。
I2S格式的信号无论有多少位有效数据,数据的最高位总是出现在LRCK变化(也就是一帧开始)后的第2个SCLK脉冲处。这就使得接收端与发送端的有效位数可以不同。如果接收端能处理的有效位数少于发送端,可以放弃数据帧中多余的低位数据;如果接收端能处理的有效位数多于发送端,可以自行补足剩余的位。这种同步机制使得数字音频设备的互连更加方便,而且不会造成数据错位。
随着技术的发展,在统一的 I2S接口 下,出现了多种不同的数据格式。根据SDATA数据相对于LRCK和SCLK的位置不同,分为左对齐(较少使用)、I2S格式(即飞利浦规定的格式)和右对齐(也叫日本格式、普通格式)。
为了保证数字音频信号的正确传输,发送端和接收端应该采用相同的数据格式和长度。当然,对I2S格式来说数据长度可以不同。
字选择(word select),也可以叫命令选择线,表明了正在被传输的声道。
WS=0
,表示正在传输的是 左声道
的数据。WS=1
,表示正在传输的是 右声道
的数据。WS可以在串行时钟的上升沿或者下降沿发生改变,并且WS信号不需要一定是对称的。在从属装置端,WS在时钟信号的上升沿发生改变。WS总是在最高位传输前的一个时钟周期发生改变,这样可以使从属装置得到与被传输的串行数据同步的时间,并且使接收端存储当前的命令以及为下次的命令清除空间。
以下文章/教程仅针对官方提供的例程《AD1939_I2S_Sample_Based_Talkthru》进行修改。
<ad1939.h>
定义了ADAU1939的宏定义,包括锁相环、时钟、DAC、ADC等的寄存器,与直接从SigmaStudio导出的文件并没有太大差别,应该经过精简。
首先,需要清晰认识1939和1772的硬件差异。ADAU1939和ADAU1772硬件上的异同,都有4个差分输入,但是1939有8个差分输出,1772有2个双声道输出(4个差分输出)。
SPORTS ,即Serial Ports的简称,串行接口。
在<ADDS_21479_EzKit.h>
头文件里,以下声明适用于21479和1939的音频通信通道,定义了
2个双声道输入 和
4个双声道输出 ,正好符合1939的
4个差分输入 和
8个差分输出
的硬件描述,对应修改到1772时需要减少输出的声音通道。本文件仅对一些全局变量、声音通道、函数等做出声明,具体应用在其他.c文件或.asm文件中。
通过 Sport1 A 从Codec接收数据,并通过
Sport0 A/B 和 Sport2
A/B 将音频数据传输至Codec的四个立体声DAC接口。Sport1
接收中断服务程序以在 DMA接收缓存 rx1a_buf
上完成算术运算(arithmetic computations),并将计算结果存放至
DMA传输缓存 tx0a_buf
。
1 | /****************************************************************************************************** |
下方代码是官方示例给出,并经过唐昊整合修改过,其寄存器设置的功能解读和验证如下:
1 | *pSPCTL0 = (SPTRAN | OPMODE | SLEN32 | SPEN_A | SCHEN_A | SDEN_A | SPEN_B | SCHEN_B | SDEN_B); |
SPTRAN: Serial port Data Transfer Direction.
Transfer / Receive from both Channel A and Channel B.
需要注意的是,传输和接收功能只有一个会在 SOPRTx_CLK
和
SPORTx_FS
控制
传输/接收位移寄存器(Transmit/Receive shift
register) 时被激活。
OPMODE: SPORT Operation Mode, I2S / packed, left-justified mode
SLEN32: Serial port word length, in I2S / left-justified mode , we used SLEN8 ~ SLEN32. 但实际上,1772输出的I2S声道字长度是24bits。
SPEN_A: Serial port Channel A DMA enabled.
SCHEN_A: Serial port Channel A DMA Chainning enabled.
SDEN_A: Serial port Channel A DMA enabled.
SPEN_B: Serial port Channel B DMA enabled.
SCHEN_B: Serial port Channel B DMA Chainning enabled.
SDEN_B: Serial port Channel B DMA enabled.
通过查询表《HWR》中的表A-84可知,SPORT 0 ~ 2 的设置符合 0用于传输,1和2用于接收的要求。
需要尽可能拉高逻辑分析仪的采样频率,如24MHz以上。
1772下I2S的数据深度为16 ~ 24bit,后面的长度都会由0补齐,采集显示设置时可以设置32bit或24bit皆可。
ADAU1772 master clock input, but the LRCLK and BCLK must be synchronous to each other. The LRCLK and BCLK pins are used to clock both the serial input and output ports.
ADAU1772 can be set to be either the master or the slave in a system. Because there is only one set of serial data clocks, the input and output ports must always both be either master or slave.
It is recommended to use the high drive settings on the serial port pins.
串行音频接口的通信时序说明图如下,
其中, tSOD 是指:ADC_SDATAx delay; time from BCLK falling (master and slave modes)。从下图中看出PDM( LJ / RJ / I2S )都有 tSOD 。
虽然同样是I2S,且LJ的标准和飞利浦I2S的标准都是左端对齐,但是飞利浦的数据会比LRCLK延时一个完整的时钟周期。而标准LJ则是LRCLK、BCLK 和 SDATA 完全对齐。
量产版采用统一的 16.6MHz
为2个codecs和1个DSP主芯片提供时钟。
异步设备通信开发顺利的前提要求是两者的通信时钟能否保持一致,即最关键的通信时钟信号(BCLK / LRCLK)是否符合设定要求(3.072MHz / 48KHz)。
使用逻辑分析仪去测量音频设备的输出 帧时钟(LRCLK)
频率,如果其时钟输出结果符合设置的 48KHz
要求,则说明正确。其 位时钟频率要求 应等于
帧时钟长度 * 数据深度 * 2
,如 帧时钟频率为48KHz的
位时钟频率为 48KHz * 32bits * 2
,大致等于
3.072 MHz
。
注意:逻辑分析仪能否清晰测量和指示波形周期,仍需要该设备自身支持较高频率的采样速率,最好采样速率能与需要采样的目标速率成倍数关系。如果速率过低,则会出现采样变形。如下图中的位时钟采样和测量为
3MHz
,中途会偶然出现 3.429MHz
。
在设置错误时,会出现如下情况:
在21479上设置好I2S的 slave端 代码,被动接收master端音频并转发回给master端,而ADAU1772在使用USBi单独通信设置好通信频率48KHz后,成功与21479实现全双工音频转发。
在评估版用户手册中,提示需要16.625MHz的时钟提供给DSP21479。通过修改量产板上codec的PLL寄存器设置,发现
16.625MHz
无效,设置时不起作用,因为与所提供的时钟频率(16.6MHz)不符。
设置成 16.6MHz
则可以工作,能够顺利采集到数据(暂停时刻查看缓存数据)。
仔细阅读以下寄存器信息。
注意:SigmaStudio中,设置
输入时钟信号(MCLK input)和
输出时钟信号(VCO Output)后,仅有
输入信号分频(input clock divider,下简称
ICD)是可以设置的,其他参数由
load parameters
自动计算。在
输入频率 16.6MHz
和
输出频率 24.576MHz
不变的情况下,改变 输入信号分频 以生成下表:
ICD = 1 | ICD = 2 | ICD = 3 | ICD = 4 | |
---|---|---|---|---|
PLL_CTRL0 | 0x08 | 0x08 | 0x08 | 0x08 |
PLL_CTRL1 | 0x1B | 0x1B | 0x1B | 0x1B |
PLL_CTRL2 | 0x07 | 0x07 | 0x17 | 0x1F |
PLL_CTRL3 | 0xCA | 0x79 | 0x5E | 0x28 |
PLL_CTRL4 | 0x11 | 0x2B | 0x35 | 0x47 |
PLL_CTRL5 | 0x01 | 0x01 | 0x01 | 0x01 |
PLL_CTRL0
和 PLL_CTRL1
分别构成
PLL分母(denominator)的高位和低位。PLL_CTRL2
和 PLL_CTRL3
分别构成
PLL分子(numerator)的高位和低位。PLL_CTRL4
控制着
PLL类型(整数型/分数型)、输入时钟分频(1
- 4)和 PLL设置的 整数部分。PLL_CTRL5
目前只能是 0x00
或
0x01
。以下为PLL_CTRL4寄存器的位描述:n
至于 CLK_CONTROL
寄存器,主要是数值 0x89
和
0x8F
的差别。见下表可知,是关于PLL分频的问题。
通过自行编写的代码,遍历所有可更改的系数进行计算求解,取得小数点后9位精度,并且符合要求的系数如下(含1/2系数):
根据PLL_CTRL4寄存器,ICD=3、IS=8时,该寄存器的值为
01000101
,即 0x45
。(下图IDC更正为ICD)
以下为不含1/2系数的结果:
根据PLL_CTRL4寄存器,ICD=3、IS=4时,该寄存器的值为
00100101
,即 0x25
。
以下为ADAU1772的特征:
Master clock = core clock = 12.288 MHz
serial input sample rate = 48 kHz
measurement bandwidth = 20 Hz to 20 kHz
word width = 24 bits
ambient temperature = 25°C
outputs line loaded with 10 kΩ
Codec A是接了DAC输出的,Codec B没有,其地址分别为 0x3D
与 0x3E
。
Codec A | Codec B | |
---|---|---|
SCK | DPI_P08 | DPI_P08 |
SDA | DPI_P07 | DPI_P07 |
ADDR0 | +3.3VA | AGND |
ADDR1 | AGND | +3.3VA |
Codec A | Codec B | |
---|---|---|
ADC_SDATA_0 | DAI_P13 | DAI_P15 |
ADC_SDATA_1 (clock) | DAI_P03 | DAI_P16 |
LRCLK | DAI_P02 | DAI_P11 (useless) |
BCLK | DAI_P01 | DAI_P12 (useless) |
DAC_SDATA | DAI_P07 | / |
FLASH | |
---|---|
SPI_SOMI | DPI_P01 |
SPI_SIMO | DPI_P02 |
SPI_CLK | DPI_P03 |
SPI_SCS | DPI_P05 |
即 stroage class,C语言提供了多种不同的模型和存储类别在内存中存储数据。
从硬件方面看, 被存储的每个值都占有一定的物理内存,C语言称之为对象(object)。
与面向对象编程中的对象不同,面向对象编程的对象指的是“类对象”。
1 | int entity = 3; |
该声明创造了一个名为 entity 的 标识符(identifier)。标识符可以用来 指定(desingate)特定对象的内容,且须遵循变量的命名规则。
标识符即 软件(C语言)指定硬件内存中的对象(内存区块)的方式。
同时,那些指定对象的表达式被称为 左值。
左值可以被理解为是 “有具体存储位置的 对象 ”,区别于 “仅有值而没有具体存储位置” 的数据。
另,左值中也有可修改的 普通左值, 和不能支持修改的 常量左值。
“左”(left)的原意是指可以放在赋值符号“=”的左边,但其实也表示能作为&和++等操作符的操作数(B语言中已经如此)。而且,现代C/C++中的含义已经不局限于此。lvalue的
l
被重新解释为location。这也对应于ISO C11/ISO C++11的 内存位置(memory location)。——百度百科
从一个左值中必定可以解析出对应对象的地址,除非该对象是位字段(bit-field)或者被声明为寄存器存储类。生成左值的运算符包括下标运算符(subscript operator)[]和间接运算符(indirection operator)*,如下表所示(如果 array 已被声明为数组,而 ptr 被声明为指针变量)。
表达式 | 是左值吗 |
---|---|
array[1] | 是;一个数组元素是一个具有位置的对象 |
&array[1] | 否;此对象的位置,并非一个具有位置的对象 |
ptr | 是;此指针变量是一个具有位詈的对象 |
*ptr | 是;指针所指的地方是一个具有位置的对象 |
ptr+1 | 否;此加法产生一个新的地址值,但不是一个对象 |
*ptr+l | 否;此加法产生一个新的算术值,但不是一个对象 |
程序运行时常会碰到一些异常情况,例如:
这些异常情况,如果不能发现并加以处理,很可能会导致程序崩溃。
所谓“处理”,可以是给出错误提示信息,然后让程序沿一条不会出错的路径继续执行;
也可能是不得不结束程序,但在结束前做一些必要的工作,如将内存中的数据写入文件、关闭打开的文件、释放动态分配的内存空间等。
一发现异常情况就立即处理未必妥当,因为在一个函数执行过程中发生的异常,在有的情况下由该函数的调用者决定如何处理更加合适。 尤其像库函数这类提供给程序员调用,用以完成与具体应用无关的通用功能的函数,执行过程中贸然对异常进行处理,未必符合调用它的程序的需要。
此外,将异常分散在各处进行处理不利于代码的维护,尤其是对于在不同地方发生的同一种异常,都要编写相同的处理代码也是一种不必要的重复和冗余。如果能在发生各种异常时让程序都执行到同一个地方,这个地方能够对异常进行集中处理,则程序就会更容易编写、维护。
鉴于上述原因,C++ 引入了异常处理机制。其基本思想是:
函数 A 在执行过程中发现异常时可以不加处理,而只是“拋出一个异常”给 A 的调用者,假定为函数 B。
拋出异常而不加处理会导致函数 A 立即中止,在这种情况下,函数 B 可以选择捕获 A 拋出的异常进行处理,也可以选择置之不理。如果置之不理,这个异常就会被拋给 B 的调用者,以此类推。
【也就是说,在C++中,异常发生时,是否处理可以由函数的上一级调用函数去决定,如果选择不处理则会将该异常交给更上一级函数去决断。】
如果一层层的函数都不处理异常,异常最终会被拋给最外层的 main 函数。main 函数应该处理异常。如果main函数也不处理异常,那么程序就会立即异常地中止。
C++ 通过 throw
语句和 try-catch
语句实现对异常的处理。throw
语句的语法如下:
1 | throw /*表达式*/; |
该语句拋出一个异常。异常是一个表达式,其值的类型可以是 基本类型,也可以是 类。
try-catch
语句的语法如下:
1 | try { |
catch
可以有多个,但至少要有一个。
不妨把 try 和其后{}
中的内容称作“try块”,把 catch
和其后{}
中的内容称作“catch块”。
try-catch
语句的执行过程是:
例如下面的程序:
1 | #include <iostream> |
程序的运行结果如下:
1 | 9 6 |
说明当 n 不为 0 时,try 块中不会拋出异常。因此程序在 try 块正常执行完后,越过所有的 catch 块继续执行,catch 块一个也不会执行。
程序的运行结果也可能如下:
1 | 9 0 |
当 n 为 0 时,try 块中会拋出一个整型异常。拋出异常后,try
块立即停止执行。该整型异常会被类型匹配的第一个 catch
块捕获,即进入catch(int e)
块执行,该 catch
块执行完毕后,程序继续往后执行,直到正常结束。
如果拋出的异常没有被 catch
块捕获,例如,将catch(int e)
,改为catch(char e)
,当输入的
n 为 0 时,拋出的整型异常就没有 catch
块能捕获,这个异常也就得不到处理,那么程序就会立即中止,try...catch
后面的内容都不会被执行。
如果希望不论拋出哪种类型的异常都能捕获,可以编写如下 catch 块:
1 | catch(...) { |
这样的 catch 块能够捕获任何还没有被捕获的异常。例如下面的程序:
1 | #include <iostream> |
程序的运行结果如下:
1 | 9 0 |
当 n 为 0 时,拋出的整型异常被catchy(...)
捕获。
程序的运行结果也可能如下:
1 | 0 6 |
当 m 为 0 时,拋出一个 double
类型的异常。虽然catch (double)
和catch(...)
都能匹配该异常,但是catch(double)
是第一个能匹配的
catch 块,因此会执行它,而不会执行catch(...)
块。
由于catch(...)
能匹配任何类型的异常,它后面的 catch
块实际上就不起作用,因此不要将它写在其他 catch 块前面。
如果一个函数在执行过程中拋出的异常在本函数内就被 catch 块捕获并处理,那么该异常就不会拋给这个函数的调用者(也称为“上一层的函数”);如果异常在本函数中没有被处理,则它就会被拋给上一层的函数。例如下面的程序:
1 | #include <iostream> |
程序的输出结果如下:
1 | salary < 0 |
CountTa 函数拋出异常后自行处理,这个异常就不会继续被拋给调用者,即
main 函数。因此在 main 函数的 try 块中,CountTax
之后的语句还能正常执行,即会执行f = Devide(3, 0);
。
第 33 行,Devide 函数拋出了异常却不处理,该异常就会被拋给 Devide 函数的调用者,即 main 函数。拋出此异常后,Devide 函数立即结束,第 13 行不会被执行,函数也不会返回一个值,这从第 33 行 f 的值不会被修改可以看出。
Devide()
中第 12 行 拋出的异常被 main 函数中类型匹配的
catch 块捕获。第 36 行中的 e
对象是用复制构造函数初始化的。
如果拋出的异常是派生类的对象,而 catch 块的异常类型是基类,那么这两者也能够匹配,因为派生类对象也是基类对象。
虽然函数也可以通过返回值或者传引用的参数通知调用者发生了异常,但采用这种方式的话,每次调用函数时都要判断是否发生了异常,这在函数被多处调用时比较麻烦。有了异常处理机制,可以将多处函数调用都写在一个 try 块中,任何一处调用发生异常都会被匹配的 catch 块捕获并处理,也就不需要每次调用后都判断是否发生了异常。
有时,虽然在函数中对异常进行了处理,但是还是希望能够通知调用者,以便让调用者知道发生了异常,从而可以作进一步的处理。在 catch 块中拋出异常可以满足这种需要。例如:
1 | #include <iostream> |
程序的输出结果如下:
1 | CountTax error:zero salary |
第
12行的throw;
没有指明拋出什么样的异常,因此拋出的就是
catch 块捕获到的异常,即 string("zero salary”)
。这个异常会被 main
函数中的 catch
块捕获。
为了增强程序的可读性和可维护性,使程序员在使用一个函数时就能看出这个函数可能会拋出哪些异常,C++ 允许在函数声明和定义时,加上它所能拋出的异常的列表,称为 异常声明列表,具体写法如下:
1 | void func() throw (int, double, A, B, C); |
或
1 | void func() throw (int, double, A, B, C){…} |
上面的写法表明 func 可能拋出 int 型、double 型以及 A、B、C 三种类型的异常。异常声明列表可以在函数声明时写,也可以在函数定义时写。如果两处都写,则两处应一致。
如果异常声明列表如下编写:
1 | void func() throw (); |
则说明 func 函数不会拋出任何异常。
一个函数如果不交待能拋出哪些类型的异常,就可以拋出任何类型的异常。
函数如果拋出了其异常声明列表中没有的异常,在编译时不会引发错误,但在运行时, Dev C++ 编译出来的程序会出错;用 Visual Studio 2010 编译出来的程序则不会出错,异常声明列表不起实际作用。
C++ 标准库中有一些类代表异常,这些类都是从 exception 类派生而来的。常用的几个异常类如图 1 所示。
图1:常用的异常类
bad_typeid、bad_cast、bad_alloc、ios_base::failure、out_of_range 都是
exception 类的派生类。C++ 程序在碰到某些异常时,即使程序中没有写
throw 语句,也会自动拋出上述异常类的对象。这些异常类还都有名为
what()
的成员函数,返回字符串形式的异常描述信息。使用这些异常类需要包含头文件
<stdexcept>
。
下面分别介绍以上几个异常类。本节程序的输出以 Visual Studio 2010为准,Dev C++ 编译的程序输出有所不同。
使用 typeid 运算符时,如果其操作数是一个多态类的指针,而该指针的值为 NULL,则会拋出此异常。
在用 dynamic_cast 进行从多态基类对象(或引用)到派生类的引用的强制类型转换时,如果转换是不安全的,则会拋出此异常。程序示例如下:
1 | #include <iostream> |
程序的输出结果如下:
1 | Bad dynamic_cast! |
在 PrintObj 函数中,通过 dynamic_cast 检测 b 是否引用的是一个 Derived
对象,如果是,就调用其 Print()
成员函数;如果不是,就拋出异常,不会调用
Derived::Print
。
在用 new 运算符进行动态内存分配时,如果没有足够的内存,则会引发此异常。程序示例如下:
1 | #include <iostream> |
程序的输出结果如下:
1 | bad allocation |
在默认状态下,输入输出流对象不会拋出此异常。如果用流对象的 exceptions 成员函数设置了一些标志位,则在出现打开文件出错、读到输入流的文件尾等情况时会拋出此异常。此处不再赘述。
用 vector 或 string 的 at 成员函数根据下标访问元素时,如果下标越界,则会拋出此异常。例如:
1 | #include <iostream> |
程序的输出结果如下:
1 | invalid vector subscript |
如果将v.at(100)
换成v[100]
,将s.at(100)
换成s[100]
,程序就不会引发异常(但可能导致程序崩溃)。因为
at 成员函数会检测下标越界并拋出异常,而 operator[]
则不会。operator[]
相比 at
的好处就是不用判断下标是否越界,因此执行速度更快。