0%

对于晶体管

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 下称 本系列芯片,本系列设备型号命名方法:

image-20210719092842059

开发手册

开发本系列芯片可能需要使用到的文件如下:

image-20210724112707176

注意:下文中《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针脚,引脚分布如下图所示:

image-20210710144933414

Specification

下图应该是F280049的系统功能框图,与F280041PM仍有些出入。

image-20210710141413564

注意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 所支持外设如下:

image-20210710142641475

Q: 为什么要区分type?

A: 就同一类功能而言,在C2000系列上,都会有许多不同的增强/阉割版本,就出现了支持全部基础功能的、出现支持部分扩展功能的 和 支持全部扩展功能的等各种版本,那么就需要从功能范围的本质上去描述这些区别,再用相应的 “功能+type x” 的方式来表示其支持的类别。如下图所示,为《spru556n》page 24 所描述的关于CLA的功能类别。

CLA type 0,表示的是支持原始CLA功能的模块类型。

CLA type 2 ,表示在原始功能的基础上,增加了后台代码模式,可以在后台运行通信和清理程序等任务;后台任务持续运行,直到禁用或设备/软复位;后台任务可以由外设或软件触发;其他前台任务可以按照定义的优先顺序中断后台任务;增加了使后台代码部分不被中断的规定;增加了调试功能,具有真正的软件断点支持,在调试停止期间,CLA从同一地址重新获取数据会被停止。

image-20210712141632913

贴一张 系统控制基础地址 的总表:image-20210802135651683

引脚

引脚分配

Page17 4.2 Pin Attributes 下可以查看各封装芯片引脚的名称、编号 及 功能。(共有10页左右,不粘贴了)

引脚路由

Page30 4.3 Signal Routing 下可以查看引脚的路由信息。包括模拟引脚和数字引脚的复用信息。

引脚复用

Page41 4.4 Pin Multiplexing 下可以查看引脚的复用信息,GPIO口的默认功能就是GPIO,除了GPIO35 和 GPIO37(默认情况下是TDI 和 TDO)。GPIO口的次级功能可以通过设置 GPyGMUXn.GPIOzGPyMUXn.GPIOz 寄存器位来进行。

注意

  • GPyGMUXn 寄存器应在 GPyMUXn 之前配置,以避免交替复用选择对GPIO产生瞬时脉冲。
  • GPIO20 , GPIO21 和 GPIO41 至 GPIO55在任何封装上都不可复用。

表6 为GPIO的针脚复用说明,表中未列明项为GPIO复用设置保留位。

image-20210712094430159
image-20210712094455163

引脚置高/低

部分引脚可以被置高/低,下表展示了各引脚的设置类型。默认情况下,GPIO不可置高,但是可以通过软件进行使能。

image-20210712093251720

X-BAR

X-BAR 即 Crossbar。

X-BAR 包含四种,分别是 输入X-BAR输出X-BARCLB X-BARePWM X-BAR。每一种 X-BAR 都以其携带的信号命名,例如输入X-BAR携带外部信号“进入”芯片内部;输出X-BAR携带内部芯片信号“输出”至GPIO上;CLB和ePWM X-BAR 则对应其外设。

输入X-BAR

输入X-BAR被用于从GPIO口引导信号至许多不同的IP块,如ADC、eCAP、ePWM 和 外部中断。

图5 展示了X-BAR的架构,表8 展示了各X-BAR输入可能的路由目的地。

image-20210712095049189
image-20210712095346292

CLB、ePWM 和 输出X-BAR

输出X-BAR有8个可以路由至各GPIO模块的输出口。ePWM X-BAR 有8个可以路由至各ePWM模块的输出口。

图6 展示了 输出 X-BARePWM X-BAR 的 信号源,这些信号源都是 内部外设 或者 输入X-BAR 的输出。

image-20210712095838640

CLB X-BAR

暂略

ePWM X-BAR

ePWM X-BAR将是信号带到ePWM模块。特殊注意,ePWM X-BAR与每一个ePWM模块处理TZ和同步的数字比较器(Digital Compare)子模块相连。

注意:ePWM X-BAR的架构 与 GPIO输出X-BAR的架构 相同(除了 输出闩 / 输出锁存器(output latch))

image-20210824141830457

ePWM X-BAR 拥有8个能够路由至每个ePWM模块的输出。图9-2仅展示了单条输出(Single Output)的架构,其他输出与上图一致。

image-20210824142639712

配置流程

  1. 从表9-2中挑选出应该传递给PWM的信号,最多为每个mux(最多32个mux)选择一个信号。
  2. 通过 TRIPxMUX0TO15CFTTRIPxMUX16TO31CFG 寄存器选择每个mux的输入。
  3. 为了将任意信号传递至ePWM,必须在 TRIPxMUXENABLE 寄存器中使能mux。
  4. 所有已开启的mux会在被传递到对应ePWM的 TRIPx 信号前进行逻辑or操作。可以适当利用 TRIPOUTINV 寄存器地对信号进行取反操作(optionally invert)。

GPIO 输出X-BAR

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操作

image-20210824143551921

GPIO输出X-BAR也有8个能够路由至GPIO模块的输出。图9-4展示了单输出的架构,与其他剩余输出的架构一致。

image-20210824143615399

配置流程

  1. 根据表9-4确定需要传递到GPIO的信号。最多为每个 OUTPUTXBARx 输出配置一个信号(on signal per mux)(最多32 mux)。
  2. 通过 OUTPUTxMUX0TO15CFGOUTPUTxMUX16TO31CFG 寄存器位每个mux选择输入。

为了能够传递任何信号至GPIO,必须在 OUTPUTxMUXENABLE 寄存器中使能mux。所有已启用的mux将会在被传递到对应的 OUTPUTx 信号之前进行逻辑or操作。

可以选择性的使用 OUTPUTINV 取反信号,

电压

本系列芯片支持以下三种之一的电源供应(在要求核心电压 VDD = 1.2V 的情况下):

  • 外接电源(不支持56针RSH封装)
  • 内部 1.2V LDO电压整流器
  • 内部 1.2V 开关整流器

注意:必须使用同一个系统电压调节器来驱动VDDIO和VDDIO_SW。

下方是有关两个电压调节器的详细描述:

image-20210710154834139

复位

暂略

时钟

设备时钟域为设备上的不同模块提供时钟输入支持,这些设备与派生时钟(derived clock)直接相连, 或通过额外的分频器连接。

F28004x 支持 内源外源 两种类型时钟。内源 即芯片自带的,上电即起振的内源性时钟。外源 即需要通过引脚外接配置的。

下图展示了 本系列芯片支持的时钟系统框图,可以看到时钟源 有 INTOSC1 / INTOSC2 / X1(XTAL) 三个,由时钟源配置产生的时钟最终会产生 看门狗时钟PLL系统时钟芯片时钟 主要是这三种。其中 芯片时钟 又会 进一步 配置到给各种外设。

外设的时钟使用也是有区别的,其中 SYSCLK 被 ePIE、RAMs、GPIOs 和 DCSM 所使用;PERx.LSPCLK 被 SCIs 和 SPIs 所使用;CAN-Bit-Clock 被 CAN总线使用(注意,此时必须使用外源时钟进行配置);PERx.SYSCLK 被 剩余其他外设所使用。

image-20210710155206953

下表 3-40 所示为《SPRUI33D》第173页 关于时钟配置相关的寄存器。

image-20210726121525047

内源时钟

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.

下图展示了本系列芯片的时钟系统,本系列芯片支持两个独立的 内源时钟,直接映射为 INTOSC1INTOSC2 。默认情况下,都会在芯片启动时开启,且 INTOSC2 才是 内源时钟的主时钟源,INTOSC1 是备用时钟源。这一点可以手动修改

芯片启动时,INTOSC2 以10MHz速率起振,为 ROM的启动做准备,且可以被配置为 系统时钟。

即系统内部自带的内源时钟,INTOSCx ,可以在不外接晶振的情况下,仅用内源时钟即可驱动工作,且《sprui33》3.7.11中就以内源时钟 INTOSC2 为主时钟(仅有10MHz),讲述了系统运行时钟 SYSCLK (100MHz)的配置方法。

注意:

  • 如果需要使用 CAN 外设,那么仅使用 INTOSCx 的频率是不足以达到CAN的频率要求的,必须使用外部时钟 XTAL

  • 在使用内源时钟时,用于接外源时钟的X1引脚必须通过一个 1KΩ的电阻接地。

  • 与其他GPIO相比,GPIO18拥有不一样的电气特性,因为是可以被当做X2来使用的。

image-20210730085027137

看门狗

如图5-10所示,如果要启用看门狗,其速率最高与 INTOSC1 一致。

外源时钟

系统还可以使用 X1X2 两个接口其中之一来连接 至 外部时钟

外源时钟有两个接口,分别是 第41GPIO18_X2) 和 第42X1)。按照连接方式分类,可以分为 外源时钟模式单端外源时钟模式

  • 外源时钟模式时,X1X2 都进行连接。进一步区分为 EC(外部晶振器,External Crystal)和 ER(外部谐振器,External Resonator)。
  • 单端外源时钟模式时,仅 X1 连接,X2 留空,VSS需要接地。

图 5-12 至 5-14 展示了三种不同的外部时钟源电路:

image-20210711172426297

注意X1 接口被当作外源时钟接口使用时,X2接口不能被当做GPIO使用。仅当使用内源时钟时, X2 可以配置成 GPIO。

如下方所示,为 LaunchPad 硬件的时钟电路,其中 Boosterpack 是指 ???

image-20210730134835054

需要使用到Boosterpack时,需要移除R35,在R31和R38上焊上0Ω的跨电阻连接器,下图为LaunchPad背面的镜像示意。

image-20210730141929361

从C2000WARE套件里的《MCU025A(001)_BOM.xls》可以找到launchpads板上的板载时钟一共有2个,分别是Y1和Y2。其中,与F280049只接相连的是Y2,为Crystal。

image-20210730145344856

表格

表13 展示了(外部)输入时钟频率的范围要求

表14 展示了(外部)晶体振荡器的电气特性

表15 展示了X1的计时要求

表16 展示了PLL锁定的时间

表17 展示了内部时钟频率

表18 展示了输出时钟XCLKOUT的开断性能

表19 展示了(外部)晶体振荡器参数

表20 展示了(外部)晶体振荡器的等效电阻要求

表21 展示了(外部)晶体振荡器的电气特性

表22 展示了(内部)晶体振荡器的电气特性

image-20210710155540889
image-20210710160134644
image-20210710160150517
image-20210711172100592
image-20210711173148615

PLL计算方法

下图展示了系统PLL的设置框图。

image-20210710155140640

系统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Ω的电阻接地。

image-20210730085027137

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:

  1. Select the reference clock source (OSCCLK) by writing to CLKSRCCTL1.OSCCLKSRCSEL. To enable XTAL, follow the instructions in the previous sections.

  2. Select the reference clock source (OSCCLK) by writing to CLKSRCCTL1.OSCCLKSRCSEL. Allow at least 300 NOP instructions for this to take effect.

  3. Set up the system PLL if desired. TI recommends using the C2000Ware SysCtl:setClock() function for proper configuration of the PLL clock.

  4. Select the LSPCLK divider by writing to LOSPCP.

  5. If an alternate CAN bit clock is needed, select it by writing to CLKSRCCTL2.CANABCLKSEL and CLKSRCCTL2.CANBBCLKSEL.

  6. 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

image-20210715170724177

配置代码

f28004x_examples.h 文件中,包含了对 IMULTFMULTPLLSYSCLKDIV 的变量宏定义。

官方提供的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.
image-20210730164642361

子系统时钟(SYSCLK / PERx.SYSCLK)

CPU 为 CLA / DMA 和 绝大多数(片上)外设直接提供时钟信号,该时钟就是 PLLSYSCLK , 但会在CPU进入 HALT模式时被栅断(gate off)。

每一个外设都拥有使用 PCLKCRx 寄存器进行独立控制的时钟。

注意:当使用 PCLKCRx 时,应用需要在时钟接入外设后,等待5个 SYSCLK 周期。

如《SPRUI33D》中,表3-55所示,从第195页起可查看 PCLKCRx 相关寄存器的所有说明。

image-20210726095356897

闪存

表23 展示了不同时钟源和频率下所需的最小闪存等待状态

表24 展示了闪存的性能参数

image-20210711173614732
image-20210711173721809

JTAG

image-20210730135537700

GPIO

在复位时,GPIO会被配置为输入。

对于特定的输入,用户还可以选择 输入鉴定周期(input qualification cycles)的数量来过滤不需要的 噪声突变(unwanted noise glitches)。

表 29 展示了GPIO的特性参数,需要注意的是,GPIO的频率只有25MHz最高。

表 30 展示了GPIO的输入时间要求

image-20210711174357812
image-20210711174530164

除了CPU控制的I/O功能外,最多可有12个独立的外设信号在一个GPIO功能口上进行多路复用。每个功能口的输出都可以被外设或者CPU主机(CPU1或者CPU1.CLA)进行控制。

有两个可输入输出接口:

  • 接口A包含GPIO1 ~ GPIO31

  • 接口B包含GPIO32 ~ GPIO59

本设备上的模拟信号被复用在数字输入上,这些模拟输入输出口(AIO)引脚不具备数字输出的能力。接口范围为:

  • 接口H包含GPIO224 ~ GPIO247

虽然上述GPIO看起来很多,但实际上本系列芯片只有GPIOA和GPIOB的部分引脚,引脚总数为26个。

引脚复用

GPyMUXx 寄存器中,每个区域(field)都决定了每个引脚(IO PIN)的GPIO复用配置。设置为 0x0 , 0x4 , 0x8 , 0xC 时可以将该引脚配置为 GPIO,配置为其他值时,可以选择一个外设以控制该引脚。查看设备数据手册以获得《外设复用选项表格》。

引脚必须在改变 GPyGMUXx 对应域之前,通过寄存器设置为GPIO模式。

流程图解读

下图8-1 展示了单个GPIO引脚上的功能框图:

image-20210823153758838

从上图可以看出来,GPIO可以设置为输出或输入

  1. 输入时可以通过 GPyPUD 寄存器设置为是否 拉高,然后通过 GPyINV 寄存器设置是否信号 反转GPyCTRL 寄存器控制着4个字段(每个字段独立控制8个GPIO)的 确认采样周期(qualification sampling period),通过设置该寄存器可以设置其 采样速率范围 为 {0, 2, 4, ...508, 510} 个 SYSCLK 周期;GPyQSEL1/2 寄存器控制着 输入确认类型(可选为 同步/三采样/六采样/异步),最后可以输出至 四个方向(外设、X-BAR、CPU1 和 CPU1CLA)。

  2. 输出暂略。

  3. The input and output paths are entirely separate, connecting only at the pin.

  4. 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
image-20210728104527100
image-20210728105035255
image-20210728105057621
image-20210728105118549

配置

配置GPIO的通用流程如下:

  1. 规划设备的引脚布局 列出应用所需的所有外围设备的清单。使用设备数据手册中的外设复用信息,选择哪些GPIO用于外设信号。决定在剩余的GPIO中,哪些作为每个CPU和CLA的输入和输出。一旦选择了外设复用,就应该通过向 GPyMUX1/2GPyGMUX1/2 寄存器写入适当的值来实现。当改变一个引脚的GPyGMUX值时,一定要先将相应的GPyMUX位设置为0,以避免在复用中出现突发。默认情况下,所有引脚都是通用的I/O,而不是外围信号。
  2. (可选)启用内部上拉电阻 要启用或禁用上拉电阻,请写到GPIO上拉禁用寄存器 GPyPUD 中的相应位。所有的上拉电阻在默认情况下是禁用的。当没有外部信号驱动时,上拉可以用来保持输入引脚处于已知状态。
  3. 选择输入资格 如果该引脚将被用作输入,请指定所需的输入鉴定(如果有)。在 GPyCTRL 寄存器中选择输入鉴定的采样周期,而在 GPyQSEL1GPyQSEL2 寄存器中选择鉴定的类型。默认情况下,所有鉴定都是同步的,采样周期等于 PLLSYSCLK。关于输入鉴定的解释,请参见第8.4节。
  4. 选择任何通用I/O引脚的方向 对于每个配置为GPIO的引脚,使用 GPyDIR 寄存器指定该引脚的方向为输入或输出。默认情况下,所有的GPIO引脚都是输入。在改变引脚为输出之前,通过向 GPySETGPyCLEARGPyDAT 寄存器写入要驱动的值来加载输出闩锁。一旦锁存器被加载,写入 GPyDIR 来改变引脚的方向。默认情况下,所有输出锁存器为零。
  5. 选择低功耗模式的唤醒源 GPIO 0-63 可以用来将系统从低功耗模式唤醒。要选择一个或多个GPIO进行唤醒,请写到 GPIOLPMSEL0GPIOLPMSEL1 寄存器的相应位。这些寄存器是CPU系统寄存器空间的一部分。关于低功耗模式和GPIO唤醒的更多信息,请参见系统控制和中断章节中的低功耗模式部分。
  6. 选择外部中断源 配置外部中断是一个两步的过程。首先,中断本身必须被启用,其极性必须通过 XINTnCR 寄存器进行配置。其次,必须通过选择输入X-BAR信号4、5、6、13和 14 的来源来设置XINT1-5 GPIO引脚。关于输入X-BAR结构的更多信息,请参见本手册的Crossbar(XBAR)章节。

案例

GPIO配置手册 所示,GPIO_SetupPinMux();GPIO_SetupPinOption(); 并不存在,这两个是存在于 f28004x_gpio.h 中的自定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//
// AFE_InitGpio - Initialize the GPIOs on launchpad and boosterpack
//
void AFE_InitGpio()
{
EALLOW; // below registers are "protected", allow access.
//GPIO-23 - LaunchPad RED LED
GPIO_SetupPinMux(23, GPIO_MUX_CPU1, 0);
GPIO_SetupPinOptions(23, GPIO_OUTPUT, GPIO_PUSHPULL);

// GPIO-34 - LaunchPad GREEN LED
GPIO_SetupPinMux(34, GPIO_MUX_CPU1, 0);
GPIO_SetupPinOptions(34, GPIO_OUTPUT, GPIO_PUSHPULL);

// GPIO AFE BoosterPack LED
GPIO_SetupPinMux(4, GPIO_MUX_CPU1, 0);
GPIO_SetupPinOptions(4, GPIO_OUTPUT, GPIO_PUSHPULL);

GPIO_SetupPinMux(5, GPIO_MUX_CPU1, 0);
GPIO_SetupPinOptions(5, GPIO_OUTPUT, GPIO_PUSHPULL);


GPIO_WritePin(4, 1);
GPIO_WritePin(5, 1);
EDIS;
// Disable register access
}

函数说明

引脚复用设置函数

本函数用于将指定引脚与函数进行绑定,绑定前会自动检查对应的CPU和外设是否可用。

然后,创建指向相应寄存器的指针,这是对GPIO寄存器定义方式的一种变通。

头文件中的标准定义使得对一个寄存器或位进行命名访问非常容易,但很难进行任意的数字访问。有一个具有相同寄存器的GPIO模块阵列,包括像 GPyCSEL1-4 这样的多寄存器组的阵列,会更容易。但是头文件没有定义任何我们可以变成数组的东西,所以就用手动指针运算来代替。

要改变多路复用,首先将外设多路复用设置为0/GPIO,以避免出现故障,然后改变组复用,再将外设多路复用设置为目标值。最后,设置CPU选择。这个过程在《TRM》中有所描述。不幸的是,由于我们事先不知道引脚,我们不能硬编码一个位域参考,所以这里有一些棘手的位操作。

警告:该代码不涉及模拟模式选择寄存器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
//
// GPIO_SetupPinMux - Set the peripheral muxing for the specified pin.
// The appropriate parameters can be found in the pinout spreadsheet.
//
void GPIO_SetupPinMux(Uint16 gpioNumber, Uint16 cpu, Uint16 muxPosition){
volatile Uint32 *gpioBaseAddr;
volatile Uint32 *mux, *gmux, *csel;
Uint16 pin32, pin16, pin8;

pin32 = gpioNumber % 32;
pin16 = gpioNumber % 16;
pin8 = gpioNumber % 8;
gpioBaseAddr = (Uint32 *)&GpioCtrlRegs + (gpioNumber/32)*GPY_CTRL_OFFSET;

//
// Sanity check for valid cpu and peripheral values
//
if (cpu > GPIO_MUX_CPU1CLA || muxPosition > 0xF){
return;
}

//
// Create pointers to the appropriate registers. This is a workaround
// for the way GPIO registers are defined. The standard definition
// in the header file makes it very easy to do named accesses of one
// register or bit, but hard to do arbitrary numerical accesses. It's
// easier to have an array of GPIO modules with identical registers,
// including arrays for multi-register groups like GPyCSEL1-4. But
// the header file doesn't define anything we can turn into an array,
// so manual pointer arithmetic is used instead.
//
mux = gpioBaseAddr + GPYMUX + pin32/16;
gmux = gpioBaseAddr + GPYGMUX + pin32/16;
csel = gpioBaseAddr + GPYCSEL + pin32/8;

//
// Now for the actual function
//
EALLOW;

//
// To change the muxing, set the peripheral mux to 0/GPIO first to avoid
// glitches, then change the group mux, then set the peripheral mux to
// its target value. Finally, set the CPU select. This procedure is
// described in the TRM. Unfortunately, since we don't know the pin in
// advance we can't hardcode a bitfield reference, so there's some tricky
// bit twiddling here.
//
*mux &= ~(0x3UL << (2*pin16));
*gmux &= ~(0x3UL << (2*pin16));
*gmux |= (Uint32)((muxPosition >> 2) & 0x3UL) << (2*pin16);
*mux |= (Uint32)(muxPosition & 0x3UL) << (2*pin16);

*csel &= ~(0x3L << (4*pin8));
*csel |= (Uint32)(cpu & 0x3L) << (4*pin8);

//
// WARNING: This code does not touch the analog mode select registers.
//

EDIS;
}
引脚属性设置函数

为指定的引脚设置输入/出,可以通过已定义的标志(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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
void GPIO_SetupPinOptions(Uint16 gpioNumber, Uint16 output, Uint16 flags)
{
volatile Uint32 *gpioBaseAddr;
volatile Uint32 *dir, *pud, *inv, *odr, *qsel;
Uint32 pin32, pin16, pinMask, qual;

pin32 = gpioNumber % 32;
pin16 = gpioNumber % 16;
pinMask = 1UL << pin32;
gpioBaseAddr = (Uint32 *)&GpioCtrlRegs + (gpioNumber/32)*GPY_CTRL_OFFSET;

//
// Create pointers to the appropriate registers. This is a workaround
// for the way GPIO registers are defined. The standard definition
// in the header file makes it very easy to do named accesses of one
// register or bit, but hard to do arbitrary numerical accesses. It's
// easier to have an array of GPIO modules with identical registers,
// including arrays for multi-register groups like GPyQSEL1-2. But
// the header file doesn't define anything we can turn into an array,
// so manual pointer arithmetic is used instead.
//
dir = gpioBaseAddr + GPYDIR;
pud = gpioBaseAddr + GPYPUD;
inv = gpioBaseAddr + GPYINV;
odr = gpioBaseAddr + GPYODR;
qsel = gpioBaseAddr + GPYQSEL + pin32/16;

EALLOW;

*dir &= ~pinMask; // Set the data direction
if (output == 1){
*dir |= pinMask; // Output, with optional open drain mode and pull-up
if (flags & GPIO_OPENDRAIN) *odr |= pinMask; // Enable open drain if necessary
else *odr &= ~pinMask;

if (flags & (GPIO_OPENDRAIN | GPIO_PULLUP)) *pud &= ~pinMask; // Enable pull-up if necessary. Open drain mode must be active.
else *pud |= pinMask;
}
else{
*dir &= ~pinMask; // Input, with optional pull-up, qualification, and polarity inversion

if (flags & GPIO_PULLUP) *pud &= ~pinMask; // Enable pull-up if necessary
else *pud |= pinMask;

if (flags & GPIO_INVERT) *inv |= pinMask; // Invert polarity if necessary
else *inv &= ~pinMask;
}

//
// Extract the qualification parameter and load it into the register.
// This is also needed for open drain outputs, so we might as well do it
// all the time.
//
qual = (flags & GPIO_ASYNC) / GPIO_QUAL3;
*qsel &= ~(0x3L << (2 * pin16));
if (qual != 0x0) *qsel |= qual << (2 * pin16);

EDIS;
}

中断

此处的中断更多的是讲 外设中断,当然后面也会涉及到定时器中断部分,因此统称为中断了,阅读是需要加以区分。

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 展示了外部中断的时序

image-20210712083326344

中断架构

下图展示了本系列芯片的中断架构。

image-20210728111202805

STAGE0 - 外设中断阶段

每个外设都有其独立的中断配置,在每个设备的章节中都有描述。某些外设允许多种事件来触发相同的中断信号。例如,通信外设就可能使用相同的中断来检查数据是否被接收或者是否存在传输错误。中断的触发状态可以通过查看外设状态寄存器来确定。通常,在下一次中断生成前,需要手动清除前一次在状态寄存器上的数据位。

STAGE1 - PIE阶段

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
2
3
EALLOW;
PieVectTable.ADCA1_INT = &adcA1ISR; //为通道ADCA1绑定中断应用 adcA1ISR
EDIS;
image-20210728110450590

STAGE2 - CPU阶段

和PIE相似,CPU 也为每一个中断提供了对应的标志和使能寄存器位。在CPU内部寄存器中,有一个 中断使能寄存器IER)和一个 中断标志寄存器IFR)。CPU内部还有一个由 ST1 寄存器下的 INTM 位 控制的 全局中断屏蔽(global interrupt mask),该屏蔽可以用CPU的 SETCCLRC 指令进行进行设置和清除操作。对应到C语言的代码中,就是C2000套件的宏定义 DINTEINT

IERINTM 的写入都是原子操作。原则上,一旦设置了 INTM 位,队列(pipeline)里的下个指令将会在所有中断都被关闭的情况下运行,不需要软件延迟。

中断顺序

image-20210816112840611

当外设生成中断(在第x组PIE,第y通道)时,以下事件顺序将会触发:

  1. 中断被锁定在 PIEIFRx.y
  2. 如果 PIEIERx.y 被设置 ,中断生成
  3. 如果 PIEACK.x 被清除,中断生成且 PIEACK 被设置
  4. 中断被锁定在 IFR.x
  5. 如果 IER.x 被设置,中断生成
  6. 如果 INTM 被清除,CPU接收中断
  7. 任何在管道里的D2或更后面的阶段的指令将会运行到完成。更早阶段的指令将会被清除(flushed)。
  8. CPU在堆(stack)上保存内容
  9. IFR.xIER.x 被清除。INTM 被设置。EALLOW 被清除。
  10. CPU中PIE中索引到ISR的地址。PIEIFR.y 被清除

所谓中断延迟是指介于 PIEIFRx.y 锁存中断第一个ISR指令进入CPU队列的执行阶段 之间的时间。

最小的中断延迟时间是14个系统周期,在ISR或堆内存中的等待时间也会增加延迟。

外部中断为了 GPIO同步输入限制(input qualification),增加了最少2个系统周期。

使用C28x RPT 指令创建的循环无法被中断。

定时器中断

image-20210830140222956

cputimer.h 头文件中,可以看到定时器配置的结构体如下:

1
2
3
4
5
6
7
8
struct CPUTIMER_REGS {
union TIM_REG TIM; // CPU-Timer, Counter Register
union PRD_REG PRD; // CPU-Timer, Period Register
union TCR_REG TCR; // CPU-Timer, Control Register
Uint16 rsvd1; // Reserved
union TPR_REG TPR; // CPU-Timer, Prescale Register
union TPRH_REG TPRH; // CPU-Timer, Prescale Register High
};

cputimervars.h 头文件中,直接配置了如下结构体:

1
2
3
4
5
6
7
8
9
//
// CPU Timer Support Variables:
//
struct CPUTIMER_VARS {
volatile struct CPUTIMER_REGS *RegsAddr;
Uint32 InterruptCount;
float CPUFreqInMHz;
float PeriodInUSec;
};

通过在结构体内声明寄存器指针,并在结构体外定义对应的结构体变量:

1
2
3
4
5
6
//
// Globals
//
struct CPUTIMER_VARS CpuTimer0;
struct CPUTIMER_VARS CpuTimer1;
struct CPUTIMER_VARS CpuTimer2;

在对CPU定时器初始化时,则不是操作 struct CPUTIMER_VARS ,而是直接操作对应的三个定时器(timer0 ~ timer2)的寄存器。

在后续对定时器的配置中,也只是将 系统频率 CPUFreqInMHz定时器周期 PeriodInUSec 的配置进行了较为方便的配置而已,书写成嵌套结构体的形式也节省了在函数内声明变量,方便了多个定时器不同配置的相似处理,在代码实际执行过程中,仍然是对寄存器进行直接操作。

定时器清空:

1
2
3
4
5
6
7
8
9
10
11
CpuTimer0.RegsAddr = &CpuTimer0Regs;    // 初始化地址指针指向对应的定时器寄存器
CpuTimer0Regs.PRD.all = 0xFFFFFFFF; // 初始化定时器周期至最大(PRD寄存器)

// 将定时器的预分频初始化并保持与系统时钟一致的速率
CpuTimer0Regs.TPR.all = 0; // 初始化预分频寄存器为0
CpuTimer0Regs.TPRH.all = 0;

// 初始化定时器控制寄存器
CpuTimer0Regs.TCR.bit.TSS = 1;
CpuTimer0Regs.TCR.bit.TRB = 1; // 使用周期值重新载入所有计数器寄存器
CpuTimer0.InterruptCount = 0; // 复位中断计数器

定时器配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Uint32 temp;

// 初始化定时器周期
Timer->CPUFreqInMHz = Freq;
Timer->PeriodInUSec = Period;
temp = (long) (Freq * Period);
Timer->RegsAddr->PRD.all = temp;

// 将定时器的预分频初始化并保持与系统时钟一致的速率
Timer->RegsAddr->TPR.all = 0;
Timer->RegsAddr->TPRH.all = 0;

// 初始化定时器控制寄存器
Timer->RegsAddr->TCR.bit.TSS = 1; // 1 = Stop timer, 0 = Start/Restart Timer
Timer->RegsAddr->TCR.bit.TRB = 1; // 1 = reload timer
Timer->RegsAddr->TCR.bit.SOFT = 0;

Timer->RegsAddr->TCR.bit.FREE = 0; // Timer Free Run Disabled
Timer->RegsAddr->TCR.bit.TIE = 1; // 0 = Disable 1 = Enable Timer Interrupt
Timer->InterruptCount = 0; // Reset interrupt counter

将定时器的清空和配置两个操作步骤类似,可以简单归纳成流程图形式:

graph LR
初始化周期 --> 设定定时器速率 --> 设置定时器工作方式

配置方法

上电时,默认情况下任何中断都不被使能。PIEIER 寄存器和 IER寄存器被清除, INTM寄存器被设置。应用代码负责配置和使能所有外设中断。

使能中断

以下为中断使能步骤:

  1. 全局性地关闭中断(DINT 或者 SETC INTM)
  2. 通过设置 PIECTRL 寄存器的 ENPIE 位来使能PIE。
  3. 为每个中断在PIE向量表里的对应位置上写ISR向量(中断服务程序地址),如下表3所示,注意下表是 EALLOW 保护的。
image-20210817095119399
  1. 为每个中断设置对应的 PIEIERx 位。(不要使用直接连接至CPU的定时器1和定时器2)
  2. 为所有拥有已启用中断的PIE组(for any PIE group containing enabled interrupts)设置CPU的IER位
  3. 启用外设中断
  4. 全局性启动中断(EINTCLRC INTM

上表中,横向为组,如 [x,y],x为组。

上方使能中断的流程,即中断初始化和配置的流程,重新用流程图的方式总结如下:

graph LR
初始化PIE --> 关闭所有中断 --> 清空PIE向量表 --> 将ISR绑定中断向量表 -->初始化CPU定时器 -->调整CPU定时器周期和工作方式 --> 打开对应的组中断 --> 打开对应的中断向量 --> 使能中断

以下代码为上方流程的示例,部分代码也仅为函数封装,具体内容需要参考详细文档描述。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
InitPieCtrl();            //初始化PIE(外设中断)模块
ClearCPUInterrupt(); //关闭并清除所有CPU中断
InitPieVectTable(); //清空PIE(外设中断)向量表

EALLOW; //关闭中断向量表寄存器写保护
PieVectTable.ADCA1_INT = &adcA1ISR; //为ADCA的中断INT1(即ADCA1)绑定中断应用(的向量地址) &adcA1ISR
PieVectTable.ADCC2_INT = &adcC2ISR; //绑定中断服务程序至中断向量表,C0通道和C2通道同用一个程序
PieVectTable.TIMER0_INT = &cpuTimer0ISR; // 将cpuTimer0ISR定时器0中断服务器程序绑定至对应的中断向量表
EDIS;

InitCpuTimers(); //初始化CPU定时器

ConfigCpuTimer(&CpuTimer0, 100, TIMER0_PRD); //第一个参数填写定时器序号,第二个参数填写CPU主频,第三个参数填写定时器中断周期(微秒)

CpuTimer0Regs.TCR.all = 0x4000;

IER |= M_INT1; // 打开组1中断(group 1 interrupt), ADC A模块 和 CPU定时器0 的 中断处理程序需要使用
IER |= M_INT10; // 打开组10中断(group 10 interrupt),ADC C模块中断处理程序需要使用

PieCtrlRegs.PIEIER1.bit.INTx1 = ON; //打开中断向量表中的1.1中断,其中PIEIER是中断使能寄存器,其后的数字1是组1,INTx1是指第一个中断
PieCtrlRegs.PIEIER10.bit.INTx10 = ON; //打开ADCC2对应的中断向量
PieCtrlRegs.PIEIER1.bit.INTx7 = ON; //打开定时器0对应的中断向量

EINT; // 使能中断
ERTM; // 打开调试模式

处理中断

ISR中断处理程序基本与普通程序一直,以下几点需要遵守:

  1. 保存和恢复特定的CPU寄存器状态(如果需要)
  2. 为中断组清除 PIEACK
  3. 使用 IRET 指令来返回

如果函数定义时使用了 __interrupt 关键字,第一条和第三条会由C编译器自动处理。

与中断组相关的PIEACK 位必须在用户代码中手动清除,通常是在ISR的末尾执行该操作。如果PIEACK没有被清除,那么CPU将不会继续接收改组的中断。此处与不经过PIE的定时器中断有所不同。

在使用了 __interrupt 关键字后,中断处理时,唯一需要注意的就是对该中断的组进行中断确认,如果没有确认,该中断只会被进行一次,确认组中断的代码如下:

1
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;  //确认组1的中断

关闭中断

为关闭所有中断,需通过 DINTSETC INTM 来设置CPU的全局中断屏蔽。不需要在设置 INTM 或 修改IER后添加 NOP,在中断关闭后会有下一条指令将被执行。

通过操作 PIEIERx 寄存器能够关闭独立中断,但需要避免竞争发生。当 PIEIER 写入完成时,如果中断信号准备好要生成,该中断信号将会直接到达CPU并且造成假中断的触发。为避免假中断的触发,应循以下步骤:

  1. 全局性禁止中断(DINTSETC INTM
  2. 为中断清除 PIEIER
  3. 等待5个周期(系统周期?)以确保任何已生成的中断能够到达CPU的IFR寄存器
  4. 为中断的PIE组清除CPU的IFR位 和 PIEACK
  5. 全局性启用中断(EINTCLRC INTM

使用CPU的 IER 寄存器可以禁用中断组。此时不会产生竞争,因此无需特殊的程序进行处理。

PIEIER 位不可以被软件清除(must never be cleared in software),因为 读/写/改 操作可能会对进入CPU的中断造成影响(may cause incoming interrupts to be lost)。唯一安全的清除 PIEIER 位的方法是让CPU处理该中断。以下程序步骤可以用于绕过普通ISR:

  1. 全局性禁止中断(DINTSETC INTM
  2. 修改PIE向量表以将 PIEIFR 位的中断向量映射至空的ISR上,该ISR将只包含一个来自中断指令 IRET 的返回(contain a return from interrupt instruction)
  3. 在外设寄存器上禁用中断
  4. 全局性启用中断(EINTCLRC INTM
  5. 等待空的ISR为阻塞中断服务
  6. 全局性禁止中断(DINTSETC INTM
  7. 修改PIE向量表以将中断向量映射回至其原始的ISR(original ISR)
  8. 为中断的PIE组清除 PIEACK
  9. 全局性启用中断(EINTCLRC INTM

嵌套式中断

默认情况下,中断不会嵌套。通过软件操控 IERPIEIERx 寄存器可以实现对中断的嵌套和优先级排序。详见 TI Wiki

向量地址有效性检查

ePIE向量表有两个备份,初级向量表地址在 0xD000xEFF 之间,冗余向量表的地址在 0x01000D000x01000EFF 之间。对初级向量表的写入时也将会对冗余向量表进行写入,对冗余向量表的写入仅对冗余向量表有效。读取时,两个向量表互相独立。

在向量地址获取时,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类:总控制确认使能标志

image-20210818103607977

低电量模式

本系列芯片拥有两种 时钟门(clock-gating) 低电量模式,分别是 HALT 和 IDLE,STANDARD 模式不支持进入低电量。

低电量模式的进入和退出代码,以及更多关于低电量模式的描述信息,需要参考《TRM》手册。

表33 描述了系统进入任何 时钟门 低电量模式后的影响

表34 列明了IDLE模式下的时序要求

表35 列明了IDLE模式下的开断特性

表36 列明了HALT模式下的时序要求

表37 列明了HALT模式下的开断特性

图26 展示了IDLE模式的时序框图

图27 展示了HALT模式的时序框图

image-20210712100908714
image-20210712101034377
image-20210712101211183
image-20210712101225955

系统控制

CLA

在涉及到一些需要使用浮点数计算的场景,就需要打开CLA。如果不打开,会发现32bit的系统上居然出现了int数据类型只有16bit的情况。

image-20210824202520424

控制律加速器(CLA)2型是一个独立的、完全可编程的、32位浮点数学处理器,它为C28x系列带来了并发的控制回路执行。CLA的低中断延迟允许它“及时”读取ADC示例。这显著减少了ADC样本的输出延迟,以实现更快的系统响应和更高的MHz控制回路。通过使用CLA服务于时间关键的控制回路,主CPU可以自由地执行其他系统任务,如通信和诊断。

CLA有以下特性:

  1. C编译器适用于CLA软件开发
  2. 与主核心(SYSCLKOUT)频率一致
  3. 允许CLA算法独立于主核心执行的独立架构
    • 完整总线架构
      • 程序地址总线 和 程序数据总线
      • 数据读取地址总线
image-20210824203000371

CLA接口

C28x的主芯片可以接入至CLA和vice versa。

CLA能够接入三种内存,包括:程序内存、数据内存和信息内存。每一种内存的行为和仲裁后续都会进行描述,CLA内存由DCSM模块(Dual Code Security Module,双代码安全模块)进行保护,可以参考系统控制和中断章节以获取更多安全计划相关的细节。

CLA / DMA / CPU 仲裁

暂略

CLA 配置和调试

暂略

管道

暂略

指令集

暂略

寄存器

暂略

DMA

暂略

模拟外设

本系列芯片的模拟外设包含 ADCPGADACCMPSS

所有的模拟引脚一共有16个,包括ADC三个模块(module,A、B、C)、PGA、DAC等都是复用的。

image-20210805172817997

《TRM》P1420, 下表为模拟子系统寄存器详细:

image-20210809140243693

模拟子系统寄存器特征:(《TRM》P1410)

  • 灵活的电压基准
    • ADC模块使用 VREFHIxVSSA 引脚提供电压参考
    • 缓存DAC模块使用 VREFHIxVSSA 引脚提供参考
    • 比较器DAC模块使用 VDDAVSSA 引脚提供参考
  • 灵活的针脚使用
    • 缓存DAC输出、比较器子系统输入、PGA功能 和 数字输入 都与 ADC输入复用
    • Internal connection to VREFLO on all ADCs for offset self-calibration.

以下为64针封装芯片的模拟系统框图:

image-20210809142619290

ADC

本系列芯片的ADC外设是 第5类(TYPE 5)ADC,只支持12位单端模式。以下讨论中可能会看到 ADCA / ADCB / ADCC 三种不同的描述,其实是ADC三个大的模块,模块下面又有很多个通道。

image-20210804113737347

单个ADC模块的简化示意图如下:

image-20210730103920701

ADC模拟外设的内部架构如下:

image-20210730092532193

SOC和EOC的内部架构如下:

image-20210730105429591

ADC特点

  • 12bit 分辨率
  • VREFHI VREFLO
  • 可选内部参考电压为 2.5V 或 3.3V
  • 单端信号转换器
  • 最多16个ADC通道
  • 16个配置SOC(ADCSOC0 ~ ADCSOC15
  • 16个独立的可寻址结果寄存器
  • 四个高级的(flexible)外设中断(每个模块都有 INT1 ~ INT4
  • 可配置的中断(interrupt placement)
  • 可以设置爆发模式(Burst Mode)
  • 多触发源
    • S/W ,软件立即启动
    • 所有的ePWMs(只有ADC A 或 ADC B 所配置 SOC能够被触发
    • GPIO XINT2
    • 可以使用 CPU定时器 0/1/2触发
    • 可以使用ADC A/B/C 的 INT1/2来配置触发SOC(可且仅可
  • 四个PPB(post-processing blocks),每一个都有:
    • Saturating offset calibration
    • Error from setpoint calculation
    • High, low and zero-crossing compare, with interrupt and ePWM trip capability
    • Trigger-to-sample delay capture

ADC输入模型

image-20210730091825651

每个通道的寄生电容

image-20210730093537064

ADC配置项

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 时钟周期来将 输入电压转换成 数字信号,因此需要自行决定和配置获取窗口所需要的周期大小。

image-20210805153739063

分辨率

ADC分辨率决定着最终模拟信号能够转换成的数值大小。本系列芯片支持12bit分辨率。

参考电压源

本系列芯片的第16、17引脚是ADC的参考电压:

  • 第16pin 是ADC A/B/C的高参考电压(Voltage Reference High) VREFHIx
  • 第17pin 是ADC A/B/C的低参考电压(Voltage Reference Low) VREFLOx

根据《TRM》P1442可知,每个ADC模块都可以配置一个单独的参考电压(包括 VREFHIVREFLO),参考电压源可以是内部或外部。需要注意的是,引脚数较少的封装可能在多个ADC之间共享一个VREFHI引脚。在这种情况下,共享一个参考引脚的ADC必须将它们的参考模式配置得完全一样。

外部参考电源

在外部参考电压模式下,参考电压源的针脚被当做比率测量参考,以测定ADC转换的输入范围。

以下几点需要注意:

  • 如果没有连接到具体的外部 VREFLO 信号,需将 VREFLO 连接至 设备模拟地 VSSA
  • 外部参考电源(高/低)的输入范围:
  • VREFHI 针脚要求外接电容。
image-20210805090011967 image-20210805090028640
内部参考电源

选择内部参考电源时,将由芯片设备来为 VREFHI 提供参考电源。 此电源电压可以被配置为 2.5V1.65V,当配置 1.65V 时,模拟输入的范围最大为 3.3V

注意:内部参考模式同样要求给 VREFHI针脚外接一个电容(具体电容值视具体情况而定)。

Ganged参考电源

某些封装中,多个ADC电压参考针脚可能被捆绑在了一起。这种情况下,当选择外部或内部参考模式,以及选择 3.3V2.5V 内部参考电压范围时,有必要对ganged参考进行相同的配置。

例如,如果 ADC B 和 ADC C参考电源针脚被捆绑在一起,并且需要一个2.5V的内部参考电源时,以下配置代码可供参考:

1
2
3
4
5
6
//ADCB VREFHI and ADCC VREFHI share a pin
//ADCB VREFLO and ADCC VREFLO share a pin
//Both references must be explicitly configured
//Both references must be configured identically
SetVREF(ADC_ADCB, ADC_INTERNAL, ADC_VREF2P5);
SetVREF(ADC_ADCC, ADC_INTERNAL, ADC_VREF2P5);

内部硬件设备将确保相同针脚上的多个参考电源不会冲突。(ensure multiple references don't drive conflicting voltages onto the same pin)正因如此,参考电源可以被配置于任意顺序及任意时间。

选择参考模式

电压参考模式可以用C2000套件提供的 SetVREF()ADC_setVREF() 函数进行配置。使用这些函数时需确保 修正值(correct trim)已被载入 ADC trim 寄存器,需要确保设备复位后这些函数至少被调用一次,同时不要通过直接写入 ANAREFCTL 寄存器来配置电压参考模式。

信号模式

本系列芯片支持单端模式,通过单根针脚 ADCINx 采样转换器的输入电压,参考点为 VREFLO

预期(Expected)转换结果

基于给定的模拟输入电压,预期情况下的数字转换结果如下表所示。小数部分省略。

模拟输入 数字输出
当输入电压低于参考低电压
ADCINyVREFLO
最小量程 0x00
当输入电压在高低参考电压之间
VREFLO < ADCINy < VREFHI
最大量程 0xFFF输入电压差 / 参考电压差 之积
0xFFF * (ADCINy - VREFLO) / (VREFHI - VREFLO)
当输入电压超过参考高电压
ADCINyVREFHI
最大量程 0xFFF(即 2^(12)-1
解释(Interpreting)转换结果

基于给定的ADC数字转换结果,来反推理想的对应模拟输入的结果。公式就用上面的表格来反推就好了。

ADC寄存器

寄存器这边除了描述最基本的 基地址 (Base Address)以外,还有两类寄存器:结果寄存器(Result Register)和 控制寄存器(Control Register)。

image-20210805085945157

image-20210805090938982
image-20210805090858805

BSYbusy ,繁忙状态。

image-20210809100855861
  • ADCCTL1 包括只读位和读写位。
    • 只读位主要是涉及一些工作状态的读取,例如,ADCBSY 为 ADC是否正在工作,ADCBSYCHN 为 哪个通道在工作。
    • 读写位涉及一些可操控的权限,例如 ADCPWDNZ 为 ADC的打开和关闭、INTPULSEPOS 为 ADC脉冲位置
  • ADCCTL2 目前仅有 一个可读写位,即 PRESCALE ,ADC模块的时钟预分频。

ADC启动顺序

设备开启或系统级复位时,ADC会被断电禁用。为ADC上电启用时,请遵循以下顺序:

  1. 设置 PCLKCR13 寄存器中的指定位来启用所需 ADC 时钟
  2. 设置 ADCCTL2 寄存器中的 预分频 PRESCALE 位来设置 ADC的时钟分频
  3. 设置 ADCCTL1 寄存器中的 ADCPWDNZ 位来启用 ADC
  4. 采样前启用一个延迟(需要查询具体需要多长)

注意:如果多路ADC需要同步开启,第一步和第三步可以通过 同一条写入指令(in one write instruction)来进行配置。因此,也就可以用同一条延迟指令来等待ADC上电启动。

ADC时序

image-20210807142240064

image-20210807142256816

image-20210807142458766

image-20210807142514410

SOC操作原则

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)。

image-20210730094148462

在SOC配置集中有三个可配置项:

  • ADCSOCFLG1 启动转换的触发源(the trigger source that starts the conversion),操作某个bit可以启动某个SOC,详见《TRM》P1519
  • CHSEL 转换通道(the channel to convert),可取值范围为 0h ~ Fh,代表 ADC0 ~ ADC15
  • ACQPS 捕获窗口/采样周期(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.

ACQPS的计算

注意:Acquisition Window 和 ACQPS 不是同一个概念,Acquisition Window 就是下方所述的 SH 的持续时间

参考ADC输入模型简图:

image-20210730091825651

采样周期和 ACQPS的关系如下:

Acquisition window = (ACQPS + 1)∙(System Clock (SYSCLK) cycle time)

以下案例在求解采样周期的基础上,倒推 ACQPS 的设置:

为了能够正确读取ADC数据,ADC输入信号必须有足够的时间为 采样和保持电容 Ch(the sample and hold capacitor)充电。通常来说,SH 的持续时间的选择是为了使采样电容被充电到最终值的 ½ LSB 或者 ¼ LSB ,具体取决于可容忍的 settling 误差(tolerable settling error)。

可以用 RC settling 模块来确定所需的 settling 时间近似值。具体时间常数模型如下方公式:

image-20210805180321634

所需时间常数的数量也由下面等式给出:

image-20210805180508541

最终SH 的持续时间如下方公式所示:

image-20210805180522951

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)

通过以下参数来说明计算方式:

  • n = 12-bits
  • RON = 500Ω
  • CH = 12.5pF
  • Cp = 12.7pF
  • settling error = ¼ LSB
  • Rs = 180Ω
  • Cs = 150pF

时间常数计算如下:

image-20210805180544244

所需时间常数的数量如下:

image-20210805180556088

最终结果:

37.8ns · 7.13 = 270ns

如果系统时间 SYSCLK100 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/*-------------------------------------------------------------------------
* HEADER FILES
-------------------------------------------------------------------------*/
#include <iostream>
#include <math.h>

using namespace std;

/*-------------------------------------------------------------------------
* DATA TYPE DEFINITION
-------------------------------------------------------------------------*/
typedef double ADCPARAM;


/*-------------------------------------------------------------------------
* MACRO DEFINITION
-------------------------------------------------------------------------*/
#define ADC_RESOLUTION 12 //定义分辨率为12位
#define LSB 1

/*-------------------------------------------------------------------------
* GLOBAL PARAMETERS
-------------------------------------------------------------------------*/
// r for Resistance , c for Capacitance
struct adc_parameters{
short resolution; //分辨率
ADCPARAM r_on; // ADC采样开关电阻
ADCPARAM c_hold; // ADC采样和保持电容
ADCPARAM c_parasitic; // ADC通道寄生输入电容

ADCPARAM settling_error; //可容忍的沉降误差
ADCPARAM r_source; //ADC驱动电流源上的电阻值
ADCPARAM c_source; //ADC输入引脚上的寄生电容值
};


/*-------------------------------------------------------------------------
* FUNCTION DECLARATION
-------------------------------------------------------------------------*/
ADCPARAM timeConstanceCalc(ADCPARAM, ADCPARAM, ADCPARAM, ADCPARAM, ADCPARAM);
ADCPARAM numberCalc(ADCPARAM, ADCPARAM, ADCPARAM, ADCPARAM, ADCPARAM);


/*-------------------------------------------------------------------------
* MAIN FUNCTIONS
-------------------------------------------------------------------------*/
int main(){
// 注意: 所有电容值单位为 pF, 所有电阻值单位为 Ω
struct adc_parameters ADC1;
ADC1.resolution = ADC_RESOLUTION; // 不可修改
ADC1.r_on = 500; //不变
ADC1.c_hold = 12.5; //不变
ADC1.c_parasitic = 12.7; //不变

ADC1.settling_error = 1/4.0 *LSB; //不要写1/4 *LSB,会得出无限大的数据(inf)
ADC1.r_source = 1000; //实际使用1kΩ,原180
ADC1.c_source = 10000; //带入0.1uF计算,原150

ADCPARAM result = 0;
result = timeConstanceCalc(ADC1.r_source, ADC1.r_on, ADC1.c_hold, ADC1.c_source, ADC1.c_parasitic) * numberCalc(ADC1.resolution, ADC1.settling_error, ADC1.c_source, ADC1.c_parasitic, ADC1.c_hold);

cout << "ADC采样和保持的时间为" << result << "ns" << endl;

return 0;
}


/*-------------------------------------------------------------------------
* FUNCTION DEFINITIONS
-------------------------------------------------------------------------*/
ADCPARAM timeConstanceCalc(ADCPARAM Rs, ADCPARAM Ron, ADCPARAM Ch, ADCPARAM Cs, ADCPARAM Cp){
return ( ( Rs + Ron ) * Ch + Rs * ( Cs + Cp ) ) /1000;
}

ADCPARAM numberCalc(ADCPARAM n, ADCPARAM se, ADCPARAM Cs, ADCPARAM Cp, ADCPARAM Ch){
return log( exp2(n) / se ) - log( (Cs + Cp) / Ch);
}

触发配置示例

官方SOC配置样例提供了四种:ePWM触发单次转换ePWM触发超采样转换CPU定时器触发多重转换软件触发转换

ePWM触发单次转换

当ePWM计时器到达周期时,为了配置好 ADCA模块,以生成通道 ADCIN1 上的单次转换,有以下步骤需要操作:

  1. ePWM3必须被配置并生成一个SOCA 或 SOCB 信号(这种状态下,SOC的转换启动信号就由ePWM模块提供)。
  2. 以SOCB为例,使用SOC5,且选择任意16通道的其中一个。
  3. 假设在 系统频率为 100MHz时,需要100ns的采样周期,那么就需要AW(Acquisition Window)的持续时间为 100ns / 10ns = 10 cyclesACQPS 需要设置为 10 - 1 = 9
1
2
3
AdcaRegs.ADCSOC5CTL.bit.CHSEL = 1; //SOC5 will convert ADCINA1
AdcaRegs.ADCSOC5CTL.bit.ACQPS = 9; //SOC5 will use sample duration of 10 SYSCLK cycles
AdcaRegs.ADCSOC5CTL.bit.TRIGSEL = 10; //SOC5 will begin conversion on ePWM3 SOCB

如上配置后,当ePWM3 到达其周期并生成一个SOCB信号,如果ADC此时处于空闲状态,ADC将会对通道 ADCINA1(SOC5) 立即进行采样;如果此时ADC繁忙,ADCINA1将会在SOC5获得权限后开始采样。ADC控制逻辑将会使用指定100ns的捕获窗口宽度对ADCINA1进行采样。在捕获完成后,ADC会立即开始转换采样电压为数字信号。当ADC转换完成时,结果将存放在 ADCRESULT5 寄存器。

ePWM触发超采样转换

ADC可以配置超采样,即在相同的采样周期下,对同一个通道采样超过1次的采样方法。因为 SOC / 通道 / 触发 等配置参数可重复的特殊性,因此可以用于配置超采样。

为了配置ADC对通道 ADCINA1 超采样到达4次,此处使用和之前相同的配置,分别应用到 SOC5 / SOC6 / SOC7 和 SOC8 上。

1
2
3
4
5
6
7
8
9
10
11
12
AdcaRegs.ADCSOC5CTL.bit.CHSEL = 1; //SOC5 will convert ADCINA1
AdcaRegs.ADCSOC5CTL.bit.ACQPS = 9; //SOC5 will use sample duration of 10 SYSCLK cycles
AdcaRegs.ADCSOC5CTL.bit.TRIGSEL = 10; //SOC5 will begin conversion on ePWM3 SOCB
AdcaRegs.ADCSOC6CTL.bit.CHSEL = 1; //SOC6 will convert ADCINA1
AdcaRegs.ADCSOC6CTL.bit.ACQPS = 9; //SOC6 will use sample duration of 10 SYSCLK cycles
AdcaRegs.ADCSOC6CTL.bit.TRIGSEL = 10; //SOC6 will begin conversion on ePWM3 SOCB
AdcaRegs.ADCSOC7CTL.bit.CHSEL = 1; //SOC7 will convert ADCINA1
AdcaRegs.ADCSOC7CTL.bit.ACQPS = 9; //SOC7 will use sample duration of 10 SYSCLK cycles
AdcaRegs.ADCSOC7CTL.bit.TRIGSEL = 10; //SOC7 will begin conversion on ePWM3 SOCB
AdcaRegs.ADCSOC8CTL.bit.CHSEL = 1; //SOC8 will convert ADCINA1
AdcaRegs.ADCSOC8CTL.bit.ACQPS = 9; //SOC8 will use sample duration of 10 SYSCLK cycles
AdcaRegs.ADCSOC8CTL.bit.TRIGSEL = 10; //SOC8 will begin conversion on ePWM3 SOCB

如上配置后,当ePWM3 到达其周期并生成一个SOCB信号,如果ADC此时处于空闲状态,ADC将会对通道 ADCINA1(SOC5) 立即进行采样;如果此时ADC繁忙,ADCINA1将会在SOC5获得权限后开始采样。一旦SOC5的转换完成,SOC5 的结果将会存放于 ADCRESULT5 寄存器,同时 SOC6 将会开始转换。该组转换(4个单次转换)将会依次完成(completed sequentially),转换结果也是依次存放于对应序号的寄存器中。

采样转换结果存储的寄存器编号 与其 SOC编号一致,与SOC所选择的通道无关。

注意:ADC转换启动顺序可以设置为 SOC6 / SOC7 / SOC8 / SOC5,取决于接收ePWM触发时 round-robin 指针的位置,以上情况虽然可以实现,但没有必要这样搞。详见 13.5 部分。

CPU定时器触发多重转换

基于不同的采样周期对多个信号进行采样。

CPU1 定时器2 被用以生成采样触发器(具体配置方法看《系统控制和中断(System Control and Interrupt)》章节的《CPU定时器(CPU Timer)》部分)

步骤如下:

  1. 当设置多重采样方法时,先列写出所有信号及其所需要的采样周期。
  2. 计算每个信号所需的系统时间 SYSCLK 周期 及其 ACQPS 寄存器值,如下表 13-5。
  3. 决策哪个ADC针脚连接到每个信号,取决于应用板的布局。一旦针脚选定,通道值就可以直接设定了,如下表13-6。
image-20210807110435097
image-20210807110451836
1
2
3
4
5
6
7
8
9
10
11
12
AdcaRegs.ADCSOC0CTL.bit.CHSEL = 5; //SOC0 will convert ADCINA5
AdcaRegs.ADCSOC0CTL.bit.ACQPS = 23; //SOC0 will use sample duration of 24 SYSCLK cycles
AdcaRegs.ADCSOC0CTL.bit.TRIGSEL = 3; //SOC0 will begin conversion on CPU1 Timer 2
AdcaRegs.ADCSOC1CTL.bit.CHSEL = 0; //SOC1 will convert ADCINA0
AdcaRegs.ADCSOC1CTL.bit.ACQPS = 88; //SOC1 will use sample duration of 89 SYSCLK cycles
AdcaRegs.ADCSOC1CTL.bit.TRIGSEL = 3; //SOC1 will begin conversion on CPU1 Timer 2
AdcaRegs.ADCSOC2CTL.bit.CHSEL = 3; //SOC2 will convert ADCINA3
AdcaRegs.ADCSOC2CTL.bit.ACQPS = 21; //SOC2 will use sample duration of 22 SYSCLK cycles
AdcaRegs.ADCSOC2CTL.bit.TRIGSEL = 3; //SOC2 will begin conversion on CPU1 Timer 2
AdcaRegs.ADCSOC3CTL.bit.CHSEL = 2; //SOC3 will convert ADCINA2
AdcaRegs.ADCSOC3CTL.bit.ACQPS = 58; //SOC3 will use sample duration of 59 SYSCLK cycles
AdcaRegs.ADCSOC3CTL.bit.TRIGSEL = 3; //SOC3 will begin conversion on CPU1 Timer 2

如上配置后,当CPU1 定时器2生成事件,SOC0 / SOC1 / SOC2 / SOC3 将会按顺序启动采样和转换。ADCIN5 的结果将会存放在 ADCRESULT0 中,其他采样结果也是依次存储。

采样转换结果存储的寄存器编号 与其 SOC编号一致,与SOC所选择的通道无关。
软件触发转换

不论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 the ADCSOCPRIORITYCTL register points to the last SOC converted. ?????

EOC操作原则

每一个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里,并观察它。

image-20210730105429591

中断溢出

如果EOC信号在 ADCINTFLG 寄存器中设置一个标志,但该位置上早已设置过标志了,就会出现中断溢出(interrupt overflow)。默认情况下,溢出中断不会被传递给PIE模块。当 ADCINTFLG 寄存器发生溢出时,对应的 ADCINOVF 寄存器就会被设置。ADCINOVF 寄存器上的标志位仅用于检测是否溢出,并不会阻止中断溢出到PIE模块。

当ADC中断溢出可能发生时,软件应该在ISR 或 后台循环 中检查 ADCINOVF 对应的标志位,并当检测到溢出时采取相应的措施。以下代码演示了如何在尝试清除 ADCINT 标志后,在ISR中检查 ADCINOVF 标志:

1
2
3
4
5
AdcaRegs.ADCINTFLGCLR.bit.ADCINT1 = 1; //clear INT1 flag for ADC-A
if(1 == AdcaRegs.ADCINTOVF.bit.ADCINT1){ //ADCINT overflow occurred
AdcaRegs.ADCINTOVFCLR.bit.ADCINT1 = 1 //Clear overflow flag
AdcaRegs.ADCINTFLGCLR.bit.ADCINT1 = 1 //Re-clear ADCINT flag
}

ADC中断

查询《TRM》第1496页可以看到有关ADC中断控制,主要有以下几个寄存器,涉及 中断标志设置中断溢出设置中断信号选择设置中断SOC选择设置SOC优先级控制

image-20210809083644966
  • 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来查看。

配置样例

image-20210809100254146

配置流程

在配置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模块会复用,画图工作量会大些。

ADC模块理解

控制外设

本系列芯片的控制外设包含 eCAPHRCAPePWMHRPWMeQEPSDFM

根据《SPRU566N》表11 可知,ePWM 和 HRPWM 都是4型增强外设,具体功能可以参见《SPRUI33D》。

ePWM

ePWM是商业和工业控制电力系统的关键组成部分。这些系统包括 数字电机控制开关电源控制不间断电源供应 或 其他形式的电源转换。

ePWM这个大的外设模块(Module)组成,是由8个子模块(Submodule)构成的。所有的ePWM模块用数字尾缀来表示第几个ePWM模块,如 ePWM1ePWM3 。每个ePWM模块又有两个输出,分别是 A 和 B,例如 ePWM1AePWM1B

ePWM模块通过一条时钟同步表(clock synchronization scheme)同步和串联在一起,形成可以统一操作的整体。此外,这个时钟同步表能够被扩展至 eCAP 外设使用。子模块的数量是由设备(设计)和实际使用需求决定的,每个子模块都能够支持单独操作。

ePWM 模块通过两个PWM输出(EPWMxAEPWMxB)来组成一条完整的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高阻态时序特性

image-20210715140249937

image-20210715141719524

image-20210712104641579
image-20210712104655718
image-20210712104857729
image-20210712104928659
image-20210723095251220
image-20210723095318306
image-20210723095334783

《TRM》P1841,简化过的ePWM模型:

image-20210802142302371
image-20210802142447715

【#】Time-Base Submodule

TB子模块的信号和寄存器

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计数器同步链中,EXTSYNC1INPUTXBAR5 输入而得,EXTSYNC2INPUTXBAR6 输入而得。而这些输入信号可以通过配置选择任意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时钟的步骤如下:

  1. 在 PCLKCRx 寄存器里,使能ePWM模块时钟
  2. 设置 TBCLKSYNC = 0
  3. 配置模块
  4. 设置 TBCLKSYNC = 1

CTR,Counter的缩写。

图表

图 18-6 展示了当TB计数器周期设置为4时,增计数、减计数 和 增减计数 三种模式下的PWM周期和频率的关系。时间自增的步长由从ePWM时钟分频而来的时基时钟(TBCLK)定义。

表 18-2 是对关键TB信号的详细描述。

image-20210723090439017

image-20210723104144586image-20210723104124781
image-20210723104110747image-20210723104533742

image-20210723083704637

【#】Counter Compare Submodule

CC子模块, 以TB计数值为输入源。这个值被不断地拿来与CMPA/CMPB/CMPC/CMPD进行比较,当TB计数器的值与其中一个寄存器的值相等时CC子模块就会生成一个适当的事件。

image-20210724100317179

CC子模块的作用:

  • 通过CMPA/CMPB/CMPC/CMPD寄存器,基于可编程时间戳生成时间
    • CTR=CMPA 时, TB计数器的值等于计数比较器A的值(TBCTR=CMPA)
    • CTR=CMPB 时, TB计数器的值等于计数比较器B的值(TBCTR=CMPB)
    • CTR=CMPC 时, TB计数器的值等于计数比较器C的值(TBCTR=CMPC)
    • CTR=CMPD 时, TB计数器的值等于计数比较器D的值(TBCTR=CMPD)
  • 如果用CMPA和CMPB对AQ子模块正确地配置,可以控制PWM的占空比
  • 可以在PWM周期活跃时备份(shadows)新的比较值以防崩坏或错误

CC子模块的内部信号通路结构示意如下图18-15。可以看出,CC子模块前一级是TB子模块,接收其时间信号。CC子模块不间断地对比 CMPx 和 TBCTR 的值,一旦符合要求,则输出结果给AQ子模块。所有CMPx的输出结果均会导向ET(事件触发器和中断),但是导向AQ子模块的只有 CMPA / CMPB。

image-20210724102101254

【#】Action Qualifier Submodule

AQ子模块在 波形结构PWM生成 上扮演着重要的角色。它决定了哪个事件可以被转换为不同的动作类型(converted into various action types),进而产生 EPWMxAEPWMxB 输出需要的开关波形(producing the required switched waveforms)。

AQ子模块控制着当特定事件发生时,ePWM外设的两条输出线(EPWMxA / EPWMxB)该如何进行输出。输入至AQ子模块的事件 会由 计数器方向(自增或自减)进一步限定。这允许在计数上升和计数下降阶段对输出进行独立操作。

image-20210724082155433

AQ子模块的作用

  • 根据以下(特定)事件 限定并生成(qualifying and generating)动作(包括 设置清除标记):
    • CTR = PRD ,TB计数器等于周期(TBCTR = TBPRD)
    • CTR = ZERO ,TB计数器等于0(TBCTR = 0x00)
    • CTR = CMPA ,TB计数器等于 计数比较器A (TBCTR = CMPA)
    • CTR = CMPB ,TB计数器等于 计数比较器B(TBCTR = CMPB)
  • T1、T2事件:基于比较器(comparator)、跳闸(trip) 或 同步器(syncin)的触发事件
  • 当这些事件同时发生时管理好它们的优先级
  • 当TB计数器自增或自减时提供对事件的独立控制

从下图18-21 或 表 18-3 中可以看出,AQ子模块的输入信号源除了TB时钟意外,剩下的触发事件有7种,分别是 PRD / ZERO / CMPA / CMPB / DIR / T1 / T2。只有前四种需要使用到TB计数器。软件强制动作(software forced action)是个非常有用的异步事件,由 AQSFRCAQCSFRC 寄存器控制。

注意:如果在影子模式下 CSFA 未被使用,必须配置 RLDCSF 位以关闭影子模式。

image-20210724083434622

image-20210724083824488

对输出(EPWMxA / EPWMxB)可能施加(imposed)的动作如下:

  • 置高(Set High):将输出置高。
  • 置低(Clear Low):将输出置低。
  • 切换(Toggle):如果任一输出(EPWMxA / EPWMxB)被同时置高,则将它们置低。如果任一输出被同时置低,则将它们置高。
  • 无视(Do Nothing):让输出保持与当前相同的电平,不做处理。虽然“无视”选项阻止了事件对输出的可能动作,但是这些事件仍然可以去触发中断和ADC的开启。

输出(EPWMxA / EPWMxB)的动作需要分别单独指定(specified independently)。在特定输出上,所有或任一事件都可以被配置以生成动作。

比如说,CTR=CMPACTR=CMPB 都可以被配置到 EPWMxA 上。所有限定动作都可以通过控制寄存器被配置。

每一个符号代表着一个动作,就像是时间上的标记。某些动作在时间上是固定的(如,0和周期),而CMPA和CMPB动作是非固定的(moveable)且其时间位置可以通过CMPA/B寄存器来编程。

使用 无视 操作可以关闭或无效某个动作,无视动作是复位后的默认值。

image-20210724090622329

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所示。

image-20210724092138218
image-20210724092919402
image-20210724092932350
image-20210724094334396

图18-25 展示了如何使用TBCTR的增减计数模式来生成一个symmetric PWM波形。

image-20210724172530959

【#】Dead-Band Generator Submodule

下方为DB死区时间生成子系统的功能框图,主要是设置两个寄存器值 DBFEDDBRED ,需要注意的是,他们都是 14bit 的,传递数值的时候需要进行限定。

image-20211008160601600

PWM Chopper Submodule

暂略

【#】Trip Zone Submodule

TZ子模块,每个ePWM模块都连接了6个 TZn(TZ1 ~ TZ6) 信号 , 其中:

  • 前三个信号来自于GPIO 复用
  • TZ4 来自于 带有 EQEP 模块设备的 反转(inverted)的 EQEPxERR 信号
  • TZ5 连接至了系统时钟故障(fail)逻辑
  • TZ6 来自于 CPU 的 EMUSTOP 输出

这些信号指示了外部的故障或跳闸情况,并且可以对ePWM的输出进行编程,以便在故障发生时作出相应的反应。

image-20210724104412883

TZ子模块的特点:

  • 错误输入TZ1 ~ TZ6 能够被高级地(flexibly)路由至(mapped to)任何ePWM模块
  • 在出现错误时,输出(EPWMxA / EPWMxB)状态可以被强制指定为其中之一:
    • 置高
    • 置低
    • 高阻抗(High-impedance)
    • 无动作(no action taken)
  • 为主要的 短路过流(short circuits or over-current)提供 一次性错误(one-shot trip, OSHT)
  • 限流操作(current limiting operation)提供 循环错误(cycle-by-cycle tripping)
  • 基于 片上模拟比较模块输出 和/或 TZ1 至 TZ3 提供 数字比较错误(digital compare tripping)
  • 每一个TZ输入和DC子模块 DCAEVT1/2 或 DCBEVT1/2 强制事件能够被定位于 一次性错误 或 循环错误上。
  • 中断生成可用于任何一个TZ输入
  • 软件强制错误也可用
  • 如果不需要使用,TZ子模块也能够被完全屏蔽。
image-20210824152702184

Event Trigger Submodule

如果需要使用ePWM的事件触发,则需要配置ET模块。

image-20210809171747297

例如,如果需要使用ePWM作为ADC_SOC的触发源,则需要使用以下配置:

1
2
3
4
EPwm6Regs.ETSEL.bit.INTEN = ON;                // 关闭EPWM外设的中断
EPwm6Regs.ETSEL.bit.SOCAEN = ON; // 打开ePWM6dSOCA脉冲
EPwm6Regs.ETSEL.bit.SOCASEL = 0x02; // TB计数器等予周期时触发事件
EPwm6Regs.ETPS.bit.SOCAPRD = 0x01; // 在第一次事件时生成脉冲

如果不需要,则使用以下配置:

1
EPwm6Regs.ETSEL.bit.INTEN = OFF;                // 关闭EPWM外设的中断

Digital Compare Submodule

暂略

eCAP

本系列芯片使用的是第1类eCAP,特性如下所示:

image-20210818150848657

eCAP模块有如下特性:

  • 旋转机械的速度测量 (例如,通过霍尔传感器检测齿形链轮)
  • 多个位置传感器脉冲的时间差测量
  • 脉冲串(Pulse Train)信号的周期和占空比测量
  • 将来自任务占空比解码的电流/电压传感器的电流/电压振幅进行解码

后续会描述到的eCAP特性如:

  • 4个事件时间戳寄存器(每个32bit)
  • 最多能够为四个顺序时间戳捕获事件的 边沿极性选择(edge polarity selection)
  • 对四个时间的任何一个进行中断(interrupt on either of the four events)
  • 单次捕获(Single-shot capture)最多可捕获4个事件的时间戳
  • 在一个四层的环形缓冲器中连续捕获时间戳的模式
  • 绝对时间戳捕获
  • 差分(德尔塔)模式的时间戳捕获
  • 当不启用捕获模式时,eCAP模块可以被配置为一个单通道的PWM输出

第一类eCAP 较 第0类eCAP增加了以下特性:

  • 事件过滤器复位位(reset bit)
  • 以计数器状态位为模
  • DMA触发源
  • 输入多路器
  • EALLOW保护

配置引脚

为了将设备输入引脚连接至模块,输入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个可配置通道,

image-20210824090004384 image-20210824090259786

image-20210818171451027

image-20210818171518769

输出X-BAR能够用于连接输出信号至 OUTPUTXBARx 输出定位(output location)。

从下表4-8可以看出,INPUT XBAR所有16个输入都可以被配置至其目的地为eCAP。

image-20210712095346292

在配置引脚时有以下步骤:

  1. 将输入XBAR与对应的GPIO连接,连接时通过 InputXbarRegs.INPUT10SELECT 进行配置。
  2. 将eCAPx与对应的XBAR输入序号连接,连接时通过 ECCTL0.INPUTSEL 寄存器为进行配置。
image-20210824093524933
1
2
InputXbarRegs.INPUT10SELECT = 0xA; // 将GPIO10(0xA)与输入XBAR 10进行连接
ECap1Regs.ECCTL0.bit.INPUTSEL = 0xA; //为eCAP1选择输入源为XBAR的INPUT10,参见《TRM》P2048

Capture模式

下图19-3展示了实现捕获功能的不同元件(various components)

image-20210819200405541

事件分频器

输入捕获信号(脉冲串)能够被N预分频(N的取值范围为[2,62],所有可取值均为2的倍数),也可以绕过该预分频,在输入信号频率非常高时有用。

下图19-4 展示了事件预分频器的功能框图。

下图19-5 展示了分频功能的操作。

通过设置 ECCTL2.CTRFILTRSET 寄存器位即可让 事件分频器(Event Prescalar)复位。

image-20210819202019366

image-20210819202037557

边沿极性选择和限定器

功能和特性如下:

  • 四个独立边沿极性(上升沿/下降沿)选择复用(selection muxes are used),每一个边沿极性都可以对应一个捕捉事件(one for each capture event)。
  • 每个边沿(最多4个)都是由 Modulo 4 序列器进行事件鉴定。(Each edge (up to 4) is event qualified by the Modulo4 sequencer.)
  • 边沿事件通过Mod4计数器导向其各自对应的 CAPx 寄存器。CAPx 寄存器在下降沿时载入(loaded on the falling edge)。

连续/一次性控制

在连续/一次性控制模式(Continuous / One-shot mode)下的eCAP操作:

  • Mod4(2位)计数器通过边沿限定事件(edge qualified events)(CEVT1 ~ CEVT4)进行增加
  • Mod4计数器持续计数(0 -> 1 -> 2 -> 3 -> 0)且往复循环永不停歇,除非被停下(wraps around unless stopped)
  • 在一次操作模式过程中,会使用到一个2位停止寄存器(STOP_WRAP)来与Mod4计数输出进行比较,当数值相等时,停止Mod4计数器并阻止更多数据载入 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 寄存器。

image-20210819205730800

32位计数器和相位控制

本32位计数器为时间捕获提供TB,并通过系统时钟锁定(is locked via the system clock)。

相位寄存器的作用是通过硬件和软件强制同步(forced sync),完成与其他计数器的同步。在APWM模式下,当模块间需要相位偏移时比较有用。

对四个任一事件的装载而言,都有一个选项可以重置32bit计数器,对时差捕获有用。先是32位计数器值被捕获,接着被LD1 ~ LD4的任一信号复位为 0

image-20210820093838065

CAP1-CAP4寄存器

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同步

eCAP模块能够通过选择相同的同步源(a common source) SYNCIN 来与其他模块同步。eCAP的同步源可以是软件同步源或者外部同步源,外部同步源信号能够来自 ePWM、eCAP或者X-BAR。

图19-7所示,eCAP模块的 SWSYNCSYNC 信号进行逻辑 或(OR)。

图19-8所示,SYNC 信号是由 SYNCSELECT[ECAPxSYNCIN] 的选择来定义的。

image-20210822115215346
使用软件同步 SWSYNC

按一下步骤进行操作可以实现对 ECAP1 和 ECAP3 的软件同步:

  1. 配置 ECAP[1..3].ECCTL2.SYNCO_SEL = 0x0 ,以使能 同步输入(sync-in)事件成为同步输出(sync-out)信号的通道。
  2. 配置 ECAP[2..3].ECCTL2.SWSYNC = 0x0,以关断 ECAP2 和 ECAP3 的软件同步。
  3. 从 ePWM1配置默认的同步信号来源,如果 TBCTL[SYNCOSEL] 信号没有被正确配置,则可能对时间戳寄存器 TSCTR 造成意料之外的结果。在 InputXbarRegs 中选择一个没有使用过的GPIO并配置为输出模式,并往GPIO DAT 寄存器中写入 0 。默认情况下会配置为 GPIO0,但该针脚上的任何活动都会对 SWSYNC 造成影响。
  4. 配置 SYNCSEL[ECAP1SYNCIN] = 0x5 ,将 ECAPx.EXTSYNCIN 外部同步输入关断。
  5. 配置 ECAP1.ECCTL2.SWSYNC=0x1 ,强制开启 时间戳计数器 (TSCTR counter)的软件同步。

为了将 SWSYNC 应用至其他 eCAP模块,需要确保前面的eCAP链条没有生成可能干扰软件同步的 SYNCOUT 信号。

中断控制

eCAP中断控制的操作和特征如下:

  • 捕获事件(CEVT1 ~ CEVT4, CTROVF)或者APWM事件(CTR = PRD, CTR=CMP)都能够生成中断。
  • 计数器溢出事件(FFFFFFFF -> 00000000)也提供了中断源(CTROVF)。
  • 这些捕获事件由对应的极性选择和Mod4门控的边缘和序列器进行限定(在时间上排序)。The capture events are edge and sequencer-qualified (ordered in time) by the polarity select and Mod4 gating, respectively.
  • 这些事件任一能够被选择为中断源(从eCAPx 模块)以导向PIE和CLA。
  • 七个中断事件(CEVT1 / CEVT2 / CEVT3 / CEVT4 / CNTOVF / CTR=PRD / CTR=CMP)都能够被生成。中断使能寄存器 ECEINT 被用于使能/禁止独立中断事件源。中断标志寄存器 ECFLG 鉴定是否有任何中断事件被锁定 并 维持着全局中断标志位 INT 。只有在任何中断事件被使能,其标志位为 1INT 标志位为 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在两个模式下都可用。

image-20210822121542134

DMA中断

在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 的触发源。

影子载入和锁定控制(lockout control)

在捕获模式下,该逻辑禁止(锁定)任何从APRDACMPCAP1CAP2 的影子载入。

在APWM模式下,影子载入被激活,且有以下两个选择可用:

  • 立即——在写入新值时,APRDACMP 将会立即传输至 CAP1CAP2
  • 在周期相等时(on period equal),CTR[31:0] = PRD[31:0]

APWM操作模式

APWM,即 Asymmetrical Pulse Width Modulation,非对称脉冲宽度调制。

当eCAP模块没有被用来输入和捕捉信号时,可以改成单通道PWM生成器(32位分辨率)。计数器将工作在 增模式 下,为非对称脉冲宽度调制(APWM)提供TB(Time-base)。其中 CAP1CAP2 寄存器 将被当做对应PWM中存放 周期(period) 和 比较(compare) 的寄存器,对应的 CAP3CAP4 寄存器 将被当做 周期和比较的影子寄存器。

下图19-1捕获和辅助脉冲宽度调制器模式操作的高级视图

image-20210819195912993

image-20210819200302044

eCAP模块应用

eCAP模块的应用通过边沿可以分为两类:上升沿(rising edge) 和 上升下降沿(rising and falling edge)。通过操作方式也可以分为两类:时间戳(time stamp) 和 时间差(time difference)。

边沿和操作方式可以互相组合成共4中应用:时间戳上升沿操作时间戳上升下降沿操作时间差上升沿操作时间差上升下降沿操作

时间戳上升沿操作

下图19-12中,为连续捕获操作(Mod4计数器周期翻转(wraps around))的例子

图表中, TSCTR 计数器在没有复位的情况下持续上升计数,捕获事件仅被限定在上升沿(注意观察图中所有CAPx旁边都有一个上升沿的标志,即所有CAPx都被设置为在MOD4的上升沿时才能出发CAPx PIN的输出)。从图中也可以看出周期和频率信息。

image-20210822144050400

在一个事件中,TSCTR内容(时间戳)被首先捕获,然后Mod4计数器进入下一阶段。当 TSCTR到达最大值 FFFFFFFF 时,翻转回 00000000,此时 计数器溢出标志 CTROVF1 ,中断(使能时)会生成。

被捕获的时间戳在图中所示的时间点上是有效的(在第4个事件之后),因此事件CEVT4可以方便地用来触发一个中断,CPU可以从 CAPx 寄存器中读取数据。

时间戳上升下降沿操作

在19-13图中,eCAP操作模式几乎与 时间戳上升沿操作 一致,除了捕获事件限定在了上升下降沿。给出相同的周期和任务占空比信息:

  • 周期1 = t3 - t1,周期2 = t5 - t3 ......

  • 任务占空比(高电平占比)1 = (t2-t1)/周期1 * 100% ,......

  • 任务占空比(低电平占比)1 = (t3-t2)/周期1 * 100% ,......

image-20210822144108477

时间差上升沿操作

image-20210822144128846

时间差上升下降沿操作

image-20210822144142670

APWM模式应用

通过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.

image-20210822143359948

寄存器

《TRM》P2041

image-20210819194706453
image-20210819195110220

eCAP配置流程

以下流程总结自C2000WareV3.4的driverlib样例代码:

  1. 禁用/复位所有捕获标志和中断
  2. 禁用CAP1~CAP4寄存器载入
  3. 配置eCAP
    • 使能捕获模式
    • 一次捕获模式,在事件4停止捕获
    • 设置事件极性为上升沿/下降沿/上升沿/下降沿
    • 设置为不同时间模式下的捕获(time difference mode)
    • 从XBAR7选择输入
    • 使能eCAP模块
    • 使能中断

eCAP中断程序流程

  1. 获取捕获计数(捕获速度应为ePWM计数的2倍)
  2. 将获取到的捕获计数与周期值进行比较
  3. 追踪ePWM的方向,并相应地调整周期,以产生一个可变频率的PWM。
  4. 为正确的捕获进行计数
  5. 为更多中断清除中断标志
  6. 使能eCAP
  7. 为更多中断确认组中断

HRCAP

HRCAP时钟

与之前的0类HRCAP模块不同,1类eCAP具有HRCAP的功能但不要求二次PLL。然而本HRCAP模块仍然要求 SYSCLK 和 HRCLK 异步时钟源(Asynchronous clock source)。HRCLK对温度和电压的变化较为敏感。因此,当使用时间转换测量(time-converted measurements)时,要求定期进行持续性的校准(periodic continuous calibrations)。

HRCAP初始化顺序

  1. 按需配置eCAP模块,包括中断
  2. 使用 HRCAP_enableCalibrationInterrupt() 使能中断
  3. 使用 HRCAP_setCalibrationMode() 使能校准
  4. 使用 HRCAP_setCalibrationPeriod() 配置周期性校准
  5. 使用 HRCAP_enableHighResolution() 使能HR模式
  6. 使用 HRCAP_enableHighResolutioniClock() 使能 HRCLK
  7. 延迟 1us
  8. 使用 HRCAP_startCalibration() 启动校准

步骤 2、3、4 和 8 仅应用在时间转换的测量。当使用HRCAP来进行相关事件测量时仅需 1、5、6 和 7 即可。

HRCAP中断

除了HRCALINT之外,HRCAP的增强功能还利用了现有的eCAP中断,该中断是由硬件校准块专门使用的。HRCALINT能够被以下情况触发:

  1. SYSCLKCTR = HRCALIBPERIOD
  2. SYSCLKCTR / HRCLKCTR 处于溢出态(experience an overflow condition)
image-20210823142406296

通信外设

SPI

本系列芯片的SPI具有如下特性:

  • 支持主模式或从模式
  • 支持125种可编程速率,最高波特率(baud rate)由IO缓存的最大速度决定。
  • 支持1~16比特数据传输
  • 支持4种时钟模式
  • 支持全双工通信(同时接受和发送),传输模式可以在软件中关闭
  • 传输或接收操作通过 中断驱动(interrupt-driven)算法 或 轮询(polled)算法来完成
  • 16级接收和传输FIFO
  • 支持直接内存访问(DMA)
  • 支持高速模式
  • 延迟传输控制
  • 支持3线模式
  • 在有两个SPI模块的设备上实现数字音频接口接收模式的SPISTE反转(SPISTE inversion for digital audio interface receive mode on devices with two SPI modules)

image-20210819084943717

下表为SPI模块的信号总结,其中有关中断信号和DMA触发的信号可能需要注意。

image-20210819090520768

系统集成部分

特别注意事项

在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 寄存器中进行设置。

主/从模式

image-20210819113506076

主模式

image-20210819113451472

从模式

数据格式

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).

image-20210819114723402

波特率

SPI支持125种不同的波特率和4种不同的时钟模式,取决于SPI的工作模式(主/从),SPICLK引脚是向外提供工作时钟还是接受外部时钟。

  • 从模式下,从SPICLK引脚接收外部时钟作为SPI时钟。
  • 主模式下,SPI生成外部从设备所需时钟,由SPICLK引脚引出。

以上所有模式下,SPICLK 的速率不能够大于 LSPCLK 的频率除以4。即

F (SPICLK) < F (LSPCLK) / 4

注意:所配置的波特率不应超过GPIO的最大切换频率(the maximum rated GPIO toggle frequency)。

波特率公式

在 SPIBRR 的可设置范围为3 ~ 127 时,其计算公式如下:

image-20210819151042265

在 SPIBRR 的可设置范围为 012 时,其计算公式如下:

image-20210819151424040

其中:

  • LSPCLK 为低速外设时钟频率
  • SPIBRR 为 SPI主机SPIBRR的内容

为了确定SPIBRR应该载入何值,必须确定设备系统时钟(LSPCLK)频率 和 目标SPI时钟。

波特率计算案例

在标准SPI模式下(HS_MODE = 0)SPI模块的波特率计算,首先需要知道LSPCLK的控制寄存器,查找下图可知,对应LSPCLK由 LOSPCP 寄存器控制,该寄存器的信息在《TRM》P173 及 P185 可查询,如下表 3-40 和 表 3-51。

image-20210710155206953

image-20210819152113556

image-20210819152141919

SYSCLK = 100MHz 时,设置 LOSPCP 寄存器下的 LSPCLKDIV 设置为 001b0x01 ,则对应的 LSPCLK = SYSCLK / LSPCLKDIV = 100MHz / 2 = 50MHz,此时,据前面SPI计算公式可知,SPI的波特率最高可为 LSPCLK / (SPIBRR +1) = 50000000 / (3+1) = 12500000Hz = 12.5MHz

LSPCLKDIV000b0x00 时,SPI的最高波特率为 25MHz

时钟/时序

时钟极性选择位 CLKPROLARITY 和 时钟相位选择位 CLK_PHASE 控制 SPICLK 引脚上的 4个不同时钟模式,其中极性选择控制时钟的上升沿或者下降沿,相位延迟控制是否延迟半个时钟周期。

  • 下降沿无相位延迟:在时钟 下降沿 传输数据,在时钟 上升沿 接收数据。
  • 下降沿有相位延迟:在时钟 下降沿 半个周期上传输数据,在时钟 下降沿 接收数据。
  • 上升沿无相位延迟:在时钟 上升沿 传输数据,在时钟 下降沿 接收数据。
  • 上升沿有相位延迟:在时钟 上升沿 半个周期上传输数据,在时钟 上升沿 接收数据。

image-20210819084200284

image-20210819115248790

只有当 SPIBRR +1 的值为偶数时, SPICLK 才能保持器对称性。当 SPIBRR +1 为奇数 且 SPIBRR 大于 3SPICLK 则为非对称性。

CLKPOLARITY0 时,SPICLK低电平(low pulse) 会比 高电平(high pulse)长一个 LSPCLK 的宽度。

CLKPOLARITY1 时,SPICLK高电平(high pulse) 会比 低电平(low pulse)长一个 LSPCLK 的宽度。

image-20210819133534654

中断

SPI模块包含两条中断线: SPIINT / SPIRXINTSPITXINT

当工作在非FIFO模式时,所有可用中断都被路由到一起,并生成中断信号 SPIINT

当工作在FIFO模式下时,SPIRXINTSPITXINT 都能够被生成。

SPIINT / SPIRXINT

SPIINT会在非FIFO模式下出现。当FIFO增强开启时,会产生 SPIRXINT 中断,这两种中断会在PIE中共享同一个中断向量。

在非FIFO模式下,以下两种情况能够触发同一个中断,使用的同一个中断向量为 SPIINT

  • 传输完成(INT_FLAG
  • 接收超限(overrun in receiver)(OVERRUN_FLAG

传输完成标志 INT_FLAG 说明SPI已经完成发送或接收最后一个比特,并且准备好继续服务下一次传输了。在该标志为1时,说明接收到的数据已经被放在接收缓存 SPIRXBUF 中。如果 SPIINTENA 位设置过了,此时该标志位还会在中断向量表 的 SPIINT 上生成中断。

接收超限标志 OVERRUN_FLAG 说明在当前字符数据被从缓冲区读取出来之前,传输或者接收操作已经完成。如果OVERRUNINTENA 位为 1OVERRUN_FLAG 已经被清除了,则该标志会在 SPIINT 向量上生成一个中断。

在FIFO模式下,SPI能够在 当前接收FIFO状态(RXFFST)和 接收FIFO中断水平(RXFFIL)之间的某个条件下中断CPU。如果 RXFFST ≥ RXFFIL,则 接收FIFO中断标志 RXFFINT 将置 1 ,如果RXFFINT 被置 1接收FIFO中断 (receive FIFO interrupt)被使能(RXFFIENA1),则 SPIRXINT 将会被触发。

SPITXINT

在非FIFO模式下,SPITXINT中断不可用。在FIFO模式下,SPITXINT 和 SPIRXINT 相近。

在FIFO模式下,SPI能够在 当前传输FIFO状态(TXFFST) 和 传输FIFO中断水平(TXFFIL)之间的某个条件下中断CPU。如果 TXFFST ≤ TXFFIL ,则 发送FIFO中断标志 TXFFINT 将置 1,如果 TXFFINT 被置 1 且 传输FIFO中断 被使能(TXFFIENA1),则 SPITXINT 将会被触发。

下图 22-2 展示了上述这些控制位如何影响SPI的中断生成:

image-20210819110332529

image-20210819110454056

DMA支持

CPU和DMA都可以通过内部外设总线来访问SPI数据的寄存器,最高可以读/写16bit的寄存器。每个SPI模块能够生成两个DMA事件 SPITXDMASPIRXDMA 。通过配置对应的 SPIFFTX.TXFFILSPIFFRX.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.)

image-20210819112140163

高速模式

SPI的高速模式为所有GPIO多路器选项可用。为了开启 高速增强(High-Speed Enhancements),需要设置 SPICCR.HS_MODE1。仍需确保针脚上的容性负载(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 则不再使用。

image-20210819153841825

由于需要在同一根信号线上完成读取和写入操作,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引脚),然后再从数据寄存器中读取。

image-20210819154636295 image-20210819154652213

程序配置

暂略

SCI

SCI外设负责UART协议的编码部分,可以使用CH340芯片的串口助手进行读写测试。

SCI外设支持 单线模式(Idle-line Mode)或 网络模式(Address-bit Mode)。

此处 Idle-line Mode 由本人意译为 UART/RS中仅有两个CPU通信的 单线模式,Address-bit Mode 由本人意译为 UART/RS中有多个CPU通信的 网络模式

外设架构

image-20210910111251456

通信数据格式

不管是单线模式还是网络模式,都有统一的通信数据格式。

SCI数据,包括接收和传输,都是 NRZ (non-return-to-zero)格式,该格式具有以下特点:

  1. 1个起始位,必不可少。
  2. 1~8个数据长度位,但一般传输都使用8bit,即一个字符大小,某些串口助手甚至不让发送低于5个bit的数据。
  3. 1个奇偶校验位(可选),即可选NONE / ODD / EVEN。
  4. 1个/2个停止位,必不可少。
  5. 1个额外的位来区分地址和数据(仅在地址位模式下),即不设置地址位模式则可以不用改数据位。

数据的基础格式被称为字符(8 bits)或1~8个数据位的长度。每个字符型数据有 1个起始位1~2个结束位可选的校验位地址位。字符型数据的格式称为 (frame),如下图23-3所示。

image-20210910152755579

使用 SCICCR 寄存器即可编辑数据格式,编辑数据格式的寄存器位如下所示:

image-20210913095605940

引脚配置

GPIO复用寄存器必须配置为从外设连接至设备引脚。为了避免引脚上的电流尖峰,GPyGMUX 寄存器位必须先配置(当对应的GPyMUX 寄存器位默认保持为 0 时),然后在对应 GPyMUX 位上载入数值。

某些IO口的功能由外设的GPIO寄存器独立设定。对于输入信号,GPIO输入限定器应该通过设置对应的 GPxQSELn 寄存器位为 11b 来设置为异步模式。内部拉高可以通过 GPyPUD 寄存器来设定。

image-20210913094310841

通信格式

SCI或者UART即使再不一样,都跟I2C一样,一次传送一个 unsigned char 字符。

SCI异步通信格式使用单线(单向)或者双线(双向)通信。在该模式下,帧内存在一个开始位,1~8位数据,一个可选的奇偶校验位 和 1~2个停止位,如下图23-7所示。

线路一直都是置高状态,每次都在收到开始位(拉低)时开始进行数据接收操作。一个有效的起始位由4个0bit的连续的 SCICLK 周期组成,一旦该低电平无法保持4个 SCICLK 周期,则将重头开始等待新的起始位。

为了能够成功采集到开始位之后的数据,处理器会对数据进行三次采样。这些采样发生在第四、第五和第六个SCICLK周期,比特值的确定是以 多数(Majority Vote)(三个中的两个)为基础,即如果该数据不能保持平稳(例如发送方波特率过高 或 噪声干扰等),可能会导致数据采样不稳。

image-20210915203147266

多核工作模式

多核通信格式允许单个处理器在同一条串行链路(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 时,接收端仍然独立操作,但是不会设置 RXRDYRXINT 或其他接收错误状态位 为 1,除非地址位被检测到且接收到的帧的地址位为 1(地址位模式下可用)。

注意SCI外设不会自动切换 SLEEP 位的数据,必须手动切换。(The SCI does not alter the SLEEP bit; your software must alter the SLEEP bit.)

地址字节确定

每个处理器确定地址字节都不同,取决于多个模式的选择。

  • 在IL模式下
  • 在AB模式下

Idle-Line 多核模式

在IL多核协议中(ADDR / IDLE 模式位为 0

多个独立的帧块之间会有 10bit以上的空闲周期

一次地址多帧数据

image-20210913095703284

地址位多核模式

  • 多个帧块之间的空闲周期没有明显特征
  • 地址帧上存在地址位。
image-20210913100054258

SCI中断

SCI 的接受端和发送端都可以被中断控制。

  • SCICTL2 寄存器拥有 标志位控制位
    • 控制位:使能接收缓存断点中断,RXBKINTENATXINTENA
    • 标志位:鉴定活跃中断情况 TXRDY ,该寄存器跟数据发送准备相关。
  • SCIRXST 寄存器仅拥有 标志位,且都是与接收状态有关的,其中2个可以生成中断。
    • 拥有两个中断标志位 RXRDYBRKDT ,加上一个能够对 FEOEBRKDTPE 情况进行逻辑或并记录的 RX ERROR 中断标志。发送端和接收端都拥有独立的中断使能位,当中断未使能时,中断活动不会被启用(the interrupts are not asserted),但是情况标志仍然活跃(the condition flags remain active),时刻反映着传输和接收状况。

SCI的接收端和发送端都拥有独立外设中断向量。外设中断请求可以设置为高优先级或低优先级,通过从外设输出到PIE控制器上的优先级位进行鉴定。当RX和TX优先级一致时,接收端优先,以减少 接收端过载(receiver overrun)的可能性。

外设中断的操作在 《系统控制和中断》章节下的《外设中断》部分已经说明。

  • 如果RX/BK INT ENA 位(SCICTL2,位1)已经设置,当以下任意事件发生时,接收端外设中断请求活动会被启用:
    • SCI接收到完整的帧(complete frame)并将 RXSHF 寄存器中数据转移到 SCIRXBUF 寄存器。以上动作就会设置 RXRDY 标志(SCIRXST寄存器第6bit)并初始化生成一个中断。
    • 断点检测情况(break detect condition)发生(SCIRXD 在丢失了结束位之后,置低了9.625个bit 周期),该动作会设置BRKDT标志位(SCIRXST寄存器第5bit)并初始化生成一个中断。
  • 如果TX INT ENA 位(SCICTL2 的第0位)被设置了,每当 SCITXBUF 中的数据被传输至 TXSHF 寄存器时,发送端外设中断请求会被断言(asserted),表明CPU能够写入 SCITXBUF 了。该动作会设置 TXRDY 标志位(SCICTL2的第7位)并初始化一个中断。

波特率计算

内部生成的串行时钟由 低速设备时钟 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

image-20210913171945217

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配置值误差情况

image-20210910161523738

寄存器

image-20210910111834789
image-20210910111903444

I2C

暂略,在ADI的21479开发笔记中有类似,可以参考。

新建项目(Step by step)

新建C28xx项目

  1. 选择新建 CCS 项目
image-20210719094604082
  1. 可暂时不选择具体芯片,选择 F28xx 系列即可,也可以选择 28004x Piccolo,在选择具体芯片。
image-20210719094942412
  1. 设置项目名称并保存。

image-20210719094813670

设置处理器选项

C2000系列编译器中,关于处理器选项,需要设置 CLAFPUTMUIDIVVCU 这几个参数,其中《spru566n》全文关于 IDIV 的描述仅有 F28002x 和 2838x 支持,此处就默认 28004x 不支持(暂时先留空),其余支持项均可以在《spru566n》表11中查找到。

  • IDIV 是指 增强型整数除法 ,enhanced integer division,也是快速整数除法,fast integer division。
  • FPU 有 FPU32 / FPU64 / SOFTLIB 三种选项,其中如果设置了 TMUVCU,则默认设置为 FPU32。
image-20210719091534852
image-20210719090718448

各选项设置如下:

image-20210719094501999

设置头文件选项

向头文件包含设置中,添加几条 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

注意:以上路径信息仅供参考,具体路径仍需依据套件安装地址来修改。

image-20210719095520123

image-20210719095713314

最后一共有4条信息:

image-20210719095934052

设置文件搜索路径

先在 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.cmdrts2800_fpu32.lib

image-20210719100817504

设置代码启动点

此步骤为 可选 步骤,在 Advance Options 下的 Symbol Management 中设置 程序入口点 code_start

注意:本步骤是用于设置编译参数的,可以在完全新建项目的时候不操作本步骤,此时进行编译 Build Project 的话也是可以编译成功的。

image-20210719101229117

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 下可以设置 字体编码回行 格式。

image-20210719101527907

添加文件

添加文件有两种形式,一种是以链接形式添加,编译时不会修改到源文件。另一种是直接添加至项目中(复制副本 或 新建文件)。

  1. 先用官方提供的文件进行预试添加和调试。在项目上右击选择 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
  1. 在项目名上右键点击 Add Files。
image-20210719102326890
  1. 选择对应路径文件。

image-20210719102359274

  1. 选择添加方式 Link to files ,可以看到项目中已经添加了一个文件。
image-20210719102420155 image-20210719102438745
  1. 到此处后仍不能进行 Build Project ,会报错 error #10234-D: unresolved symbols remain c28,需要执行下一步,将 main.c新文件形式 添加进去。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include "driverlib.h"
#include "device.h"
void main(void){
// Initialize device clock and peripherals
Device_init();

// Initialize GPIO and configure the GPIO pin as a push-pull output
Device_initGPIO();
GPIO_setPadConfig(DEVICE_GPIO_PIN_LED1, GPIO_PIN_TYPE_STD);
GPIO_setDirectionMode(DEVICE_GPIO_PIN_LED1, GPIO_DIR_MODE_OUT);

// Initialize PIE and clear PIE registers. Disables CPU interrupts.
Interrupt_initModule();

// Initialize the PIE vector table with pointers to the shell Interrupt
// Service Routines (ISR).
Interrupt_initVectorTable();

// Enable Global Interrupt (INTM) and realtime interrupt (DBGM)
EINT;
ERTM;

// Loop Forever
for(;;){
// Turn on LED
GPIO_writePin(DEVICE_GPIO_PIN_LED1, 0);

// Delay for a bit.
DEVICE_DELAY_US(500000);

// Turn off LED
GPIO_writePin(DEVICE_GPIO_PIN_LED1, 1);

// Delay for a bit.
DEVICE_DELAY_US(500000);
}
}

项目编译

修改完之后进行编译 Build Project ,会报错 内存不足,信息如下:

1
2
"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:
RAMM1 size: 0x3f8 unused: 0x3f8 max hole: 0x3f8

以上错误信息的大意是,软件需要使用到的 .stack 的大小是 0x400RAMM1 的大小只有 0x3f8

从错误提示中看到报错的文件名,打开在 设置文件搜索路径 中添加的 28004x_generic_ram_lnk.cmd 文件,已知TI公司利用 .cmd 文件来描述芯片内部地址,打开该文件并搜索可以看到 RAMM1 的确大小不足 0x40,且此时 RAMM1_RSVD 并未被使用,适当修改 RAMM1 的地址长度,注意不要和其他地址长度重叠即可。

image-20210719110002751

重新编译即可成功。

image-20210719111024816

评估板硬件连接

TI官方的评估板型号为 LAUNCHXL-F280049C (如下示),支持使用JTAG连接与PC进行通信,关于评估板的手册文档如《SPRUII7》所示。

image-20210726152918902

连接完毕之后,设备管理器 会出现如下图所示硬件:

image-20210726154218244
图 设备管理器界面

本评估板自带了XDS110接口,只需要使用USB mini 线和PC进行,并在项目文件路径 targetConfigs\TMS320F280049C_LaunchPad.ccxml 文件下确保连接器和设备正确即可。

image-20210726153612834
图 Project Explorer窗口下的文件路径
image-20210726153524140
图 配置文件下的连接器配置

.ccxml 文件中进行修改,点击下方的 Advanced,然后选择 cJTAG(1149.7)2-pin advance mode,然后 save 保存即可。具体操作如下图:

image-20210726155434636
图 选择cJTAG连接方式
image-20210726160136365
图 点击测试按钮进行连接测试

F28004x API指南

截止2020年7月21日,最新版 F28004x API指南 3.04版 已上传。可以直接在联网电脑上打开,使用Google chrome浏览器可以右键翻译查阅。

文件组织概述

以下是外围驱动库源代码的组织概览。

  • {driverlib} - 此目录包含驱动程序的源代码。
  • {driverlib/inc/} - 此目录包含用于直接寄存器访问编程模型的外设、中断和寄存器访问头文件。
  • {hw_*.h} - 头文件,每个外设一个,描述每个外设的所有寄存器和这些寄存器中的位域。驱动程序使用这些头文件直接访问外设,应用程序代码可以使用这些头文件绕过外设驱动程序库 API。

编程模型

外设驱动程序库支持两种编程模型:直接寄存器访问模型软件驱动程序模型。根据应用程序的需要或开发人员所需的编程环境,每个模型都可以单独使用或组合使用。

每种编程模型都有优点和缺点。与使用软件驱动程序模型相比,使用直接寄存器访问模型通常会产生更小、更高效的代码。然而,直接寄存器访问模型需要详细了解每个寄存器和位域的操作,以及它们的相互作用和外围设备正常操作所需的任何顺序软件驱动程序模型使开发人员与这些细节更加隔绝,通常需要更少的时间来开发应用程序。软件驱动程序模型还产生了更具可读性的代码

F28004x 伪操作

F28004x_device.h 文件中包含一部分伪操作,参考《SPRU430F》做出解释

1
2
3
4
5
6
7
#define  EINT   __asm(" clrc INTM")   //INTM置0,打开(enable)中断
#define DINT __asm(" setc INTM") //INTM置1,关闭(disable)中断
#define ERTM __asm(" clrc DBGM") //打开调试模式(Debug Mode)
#define DRTM __asm(" setc DBGM") //关闭调试模式(Debug Mode)
#define EALLOW __asm(" EALLOW") //打开保护区域写入权限(Enable Write Access to Protected Space)
#define EDIS __asm(" EDIS") //关闭保护区域写入权限(Disable Write Access to Protected Space)
#define ESTOP0 __asm(" ESTOP0") //Emulation Stop 0

注意: 以上伪操作中,最常使用的 EALLOWEDIS 会在操作某些 写保护的寄存器时使用,利用这两句伪操作来暂时关闭写保护,写完寄存器后再打开保护。

以下为《SPRU430F》第107 ~ 113页提供的伪操作指令合集。

image-20210721145040598image-20210721145100629
image-20210721145123473image-20210721145138283
image-20210721145157425image-20210721145226082
image-20210721145242594

C2000Ware

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_pwmmodehrpwmadc_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中未提供:

  1. 《F28004x_DriverLib_Users_Guide》
  2. 《F28004x_DEV_USER_GUIDE》

2.01与3.04版本都提供了网页版的手册,可以在对应 \docs\ 文件夹下查阅。推荐使用网页版 API指南 进行函数原型速查。

image-20210721154709745
图 3.04版本函数的搜索查阅

高频逆变器开发

逆变器,即将直流DC变换为交流AC的过程。利用 DSP输出波形,控制接入高压直流电的(高位 和 地位)两个IGBT输出正向和反向两种电压,形成交流AC。

逆变电路根据直流侧电源的性质不同(电压源/电流源)分为两种类别: 电压型逆变电路电流型逆变电路

电压源逆变电路的特点:

  1. 直流侧为电压源,或并联有大电容,相当于电压源。直流侧电压基本无脉动,直流回路呈现低阻抗。
  2. 由于直流电压源的钳位作用,交流侧输出电压波形为矩形波,且与负载阻抗角无关。而交流侧输出电流波形与相位因负载阻抗情况的不同而不同。
  3. 当交流侧为阻感负载时需要提供无功功率,直流侧电容起缓冲无功能能量的作用。为了给交流侧向直流侧反馈的无功能量提供通道,逆变桥各臂都并联了 反馈二极管。

单相电压型逆变电路 可以根据变换出来的波形分为 半桥全桥,即 单相半桥变压型逆变电路单相全桥变压型逆变电路

image-20210719162954288
image-20210719162958021

具体为:利用型号为 TMS320F280041 芯片的GPIO(最高输出频率为25MHz)输出一个30KHz的PWM波形,需要使用到 ePWM外设。使用官方C2000套件示例中提供的 ePWM6 案例进行开发,输出高位和低位两个幅频合适的波形后,接到型号为 2EDL05I06PF 的驱动上,由驱动控制型号为 IKW50N65WR5 的IGBT 高频开断。

工作目标

  1. 使用内源时钟,使芯片工作在100Mhz
  2. 输出占空比为50%的PWM波形,频率在30KHz
  3. 采集输出的波形和IGBT输出的波形(可能需要检测相位和波纹)
  4. 完成过零饱和电压检测
image-20210719160255497
图 占空比为20%的测试波形

工作流程

graph TB
系统初始化 --> 使能PWM模块时钟 --> 初始化PWM模块 --> PWM工作

初始化PWM流程

graph TB
配置TB子系统 --> 配置CMPA和CMPB --> 设置CMPC寄存器 --> 配置TZ子系统 --> 配置AQ子系统 --> 配置DB子系统 --> 配置ET子系统

PLL设置

  • TMS320F280041 的外部晶振输入频率 fXTAL 在 10 ~ 20 MHz 之间,自身工作频率fSYSCLK 在 2 ~ 100MHz之间。

以下为工作频率设置的样例,利用内源时钟 10MHz ,产生芯片满负工作频率 100MHz

image-20210715170724177

本系统采用10MHz的内源时钟 INTOSC2 作为 OSCCLK,其配置方法(包括设置公式和函数原型)都已在前文详细描述和记录,请参考该代码。

ePWM

以下为两个版本下案例所给的 main()函数的流程对比。

左侧用的是Bitfield模式,右边用的是driver-lib模式。

image-20210722084618101
图 新旧版本main()函数流程对比

同样是将中断重新映射至ISR函数里,下图左侧是2.01版本下的driver-lib模式,右侧是3.04bit-field模式。

image-20210721154450124
图 不同的调用配置方法

宏定义

ePWM外设定义

在Driver-Lib的 <f28004x_globalVariabledefs.c> 中,提供了ePWM下各模块的定义,如下面代码所示,定义了 外设 ePWM1所有的寄存器(结构体)EPwm1Regs ,是EPWM_REGS的结构体实例。

使用方法:使用该外设下的寄存器时直接按照结构体变量的方法使用即可,如 EPwm1Regs.TBPRD = EPWM1_TIMER_TBPRD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifdef __cplusplus
#pragma DATA_SECTION("EPwm1RegsFile")
#else
#pragma DATA_SECTION(EPwm1Regs,"EPwm1RegsFile");
#endif
volatile struct EPWM_REGS EPwm1Regs;

//----------------------------------------
#ifdef __cplusplus
#pragma DATA_SECTION("EPwm2RegsFile")
#else
#pragma DATA_SECTION(EPwm2Regs,"EPwm2RegsFile");
#endif
volatile struct EPWM_REGS EPwm2Regs;

设置上下桥

1
2
3
4
5
int EPWM1_TIMER_TBPRD = 800; // 设置PWM的周期PRD为1600
int EPWM1_CMPA = 200; //设置高边CMPA为800
int EPWM1_CMPB = 200; //设置低边CMPB为800
int EPWM1_DBRED = 35;
int EPWM1_DBFED = 35;

初始化外设

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/*-----------------------------------------------------------------------
* 【函数】:void EPWM1_Init(void);
* 【参数】:无
* 【说明】:需要查阅《TRM》或开发文档相关说明
* 本代码文件中仅包含ePWM6模块的使用,其中ET开头的寄存器为事件
* 触发相关寄存器,用以触发ADC转换(SOC)
* 需要配置的寄存器有:
* 【其他】:注意,EPWM设置时必须开着时钟(PCLKCR2)进行配置,否则配置
* 会失败
* 【参考】:
----------------------------------------------------------------------- */

void EPWM1_Init(void){

EALLOW; //打开保护区域写入权限
CpuSysRegs.PCLKCR2.bit.EPWM1 = ON; // 打开CPUSYSREG 地址下PCLKCR2寄存器上的EPWM1位
CpuSysRegs.PCLKCR0.bit.TBCLKSYNC = OFF; //关闭TBCLK时钟同步
EDIS; //关闭保护区域写入权限

EPwm1Regs.TBPRD = EPWM1_TIMER_TBPRD; // 设定定时器周期
EPwm1Regs.TBPHS.bit.TBPHS = 0x0000; // 初始化相位
EPwm1Regs.TBCTR = 0x0000; // 初始化(清空)TB计数器

EPwm1Regs.CMPA.bit.CMPA = EPWM1_CMPA; // 设定高边值
EPwm1Regs.CMPB.bit.CMPB = EPWM1_CMPB; // 设定低边值

EPwm1Regs.TBCTL.bit.CTRMODE = TB_COUNT_UPDOWN; //设置TB计数器为增减计数模式
EPwm1Regs.TBCTL.bit.PHSEN = TB_DISABLE; // 关闭相位载入
EPwm1Regs.TBCTL.bit.SYNCOSEL = TB_SYNC_DISABLE; //关闭各PWM间的同步
EPwm1Regs.TBCTL.bit.HSPCLKDIV = TB_DIV1; // 调节本值可以设置TB速率,详见开发文档
EPwm1Regs.TBCTL.bit.CLKDIV = TB_DIV2; // 调节本值可以设置TB速率,详见开发文档
EPwm1Regs.TBCTL.bit.FREE_SOFT = 0x03; //不停止,free-run 模式

EPwm1Regs.CMPCTL.bit.SHDWAMODE = CC_SHADOW; //打开CC子模块下的影子寄存器A
EPwm1Regs.CMPCTL.bit.SHDWBMODE = CC_SHADOW;
EPwm1Regs.CMPCTL.bit.LOADAMODE = CC_CTR_ZERO_PRD; // Load on Zero
EPwm1Regs.CMPCTL.bit.LOADBMODE = CC_CTR_ZERO_PRD;


EPwm1Regs.TZSEL.bit.OSHT1 = 1; //为PWM1使能单次事件触发TZ
EPwm1Regs.TZCTL.bit.TZA = 2; // EPWMA会被强制置低
EPwm1Regs.TZCTL.bit.DCAEVT1 = TZ_LOW_STATE;
EPwm1Regs.TZCTL.bit.DCAEVT2 = TZ_LOW_STATE;

EPwm1Regs.TZCTL.bit.TZB = 2; // EPWMB会被强制置低
EPwm1Regs.TZCTL.bit.DCBEVT1 = TZ_LOW_STATE;
EPwm1Regs.TZCTL.bit.DCBEVT2 = TZ_LOW_STATE;

// 设定事件触发结果,当CMPx == TBCTR时该如何动作,此处需要高边A和低边B的动作随着TB增减而相反
EPwm1Regs.AQCTLA.bit.CAU = AQ_SET;
EPwm1Regs.AQCTLB.bit.CBU = AQ_CLEAR;
EPwm1Regs.AQCTLA.bit.CAD = AQ_CLEAR;
EPwm1Regs.AQCTLB.bit.CBD = AQ_SET;

//死区控制
EPwm1Regs.DBCTL.bit.OUT_MODE = DB_FULL_ENABLE; //0x3 MODE2 AHC high level delay
EPwm1Regs.DBCTL.bit.POLSEL = DB_ACTV_HIC; //0x2
EPwm1Regs.DBCTL.bit.IN_MODE = DBA_ALL;
EPwm1Regs.DBRED.all = 120; // 上升沿延时 0us 0个(15MHz) 注意:只有10位,最大1023
EPwm1Regs.DBFED.all = 120; // 下降沿延时 0us (15MHz) 注意:只有10位,最大1023

EPwm1Regs.ETSEL.bit.INTEN = ON; // 关闭EPWM外设的中断
EPwm1Regs.ETSEL.bit.SOCAEN = ON; // 打开ePWM1的SOCA脉冲
EPwm1Regs.ETSEL.bit.SOCASEL = 0x02; // TB计数器等予周期时触发事件
EPwm1Regs.ETPS.bit.SOCAPRD = 0x01; // 在第一次事件时生成脉冲

EALLOW;
EPwm1Regs.TBCTL.bit.PHSEN = ON; //打开从相位寄存器载入
EPwm1Regs.TBCTL.bit.SYNCOSEL = OFF; //同步输出源选择
EPwm1Regs.TBCTL.bit.SWFSYNC = OFF; //软件强制同步脉冲
CpuSysRegs.PCLKCR0.bit.TBCLKSYNC = ON; //打开TBCLK时钟同步
EDIS;
}

从上面代码里总结出下面配置步骤:

graph LR
配置开始 --> 配置TBCLK --> 配置影子寄存器ShadowRegister --> 配置CMP --> 配置TripZone --> 配置AactionQualifier --> 配置ePWM中断 --> 配置结束

EPWMCLK

image-20210726110116356
image-20210802172012594
image-20210802171937172

通过配置下方代码中 main() 函数的参数,来对CPU 、ePWM的相关寄存器参数进行调节,可以实现目标PWM周期为30.8Khz的设置。

参数计算代码就不贴了,可以直接下载:main.cpp

从系统时钟设置到外设时钟设置过程中,需要设置的寄存器、位及其数值具体如下(仅供参考):

  1. 设置时钟源。选择时钟源为 内源时钟2(INTOSC2),修改 CLKSRCCTL1 寄存器下的 OSCCLKSRCSEL 值为 0x00
  2. 设置系统PLL频率。需要分别设置整数分频、分数分频 和 系统分频,对应修改 SYSPLLMULT 寄存器下的 IMULT / FMULT / ODIV 值为 0x13 / 0x3 / 0x1 (或使用十进制赋值 19 / 3 / 1)。
  3. 设置需要使用的PWM设备序号。打开对应控制的 PCLKCRx 寄存器下的对应位,参考《SPRUI33D》第195页相关寄存器说明。
  4. 设定PWMCLK频率。修改 TBCTL 寄存器下的 HSPCLKDIVCLKDIV0x001) 及 0x012)(默认情况下也是这个值),以获得 CPU时钟速率一半值的外设时钟
  5. 设定TB计数器模式。修改 TBCTL 寄存器下的 CTRMODE0x2 ,打开 TB counter 的增减计数模式。
  6. 设定TB周期。修改 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 寄存器下的 HSPCLKDIVCLKDIV,默认情况下这两个的乘积就是 2,而 \(F_{EPWMCLK} = 100MHz\) ,也就是最终 \(T_{EPWM}\) 的值完全由 \(T_{PRD}\) 来决定。已知 TBPRD 寄存器是16位寄存器, 取值范围是 \([0,65535]\) ,在本项目情况下,可以写成如下等式:

此处需要注意,因为设定的ePWM使用 增减计数模式TBPRD 值实际上是设定值的两倍。

死区时间设定

死区时间需要联系实际电路中的 IGBT 及其 驱动器 进行综合分析。

IGBT特性

下表为温度范围在 [25℃, 175℃] 时,IGBT开关特性和二极管特性表。

image-20210907200102300
image-20210907200144294

已知死区时间最高需要达到 \(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\)

image-20210908141224008
image-20210908142609386

死区计算

根据 死区时间计算公式 \(t_{DT} = [ (t_{d(off),max} + t_{f,max}-t_{d(on),min}) + (t_{PHL,max} - t_{PLH, min})] · S\) ,代入计算​:

  • \(T_{vj} = 25℃\)​ 时, $t_{DT} = (417ns+16ns-45ns) + (270ns - ns) · 150% = $​​

数模转换开发

首先需要弄明白以下几个问题:

  1. ADC采样的信号有何种特征(ADCIN),此处所述为电气特性(电压、电流、频率等)
  2. ADC采样所需要的最小精度应该是多少,即ADC模块应该多久转换一次采样电容的电压值来转换成数值,ADC模块读取转换电容的最小周期应该是多少最合适
  3. CPU对数值的读取又是多久读取一次,每一次转换都读取还是间隔多次转换再读取(即,ADC采样触发的周期和数据读取的周期是分开配置的),可以配置为单次采样、超采样、多重采样 或 软触发采样。

数模转换开发不同于ePWM配置开发,不仅信号输入和子模块配置方式上有许多不同点,最明显的不同是高频逆变器不需要使用到触发中断,但ADC模块不论是转换触发还是读取数值都需要中断来进行操作,即由外部中断脉冲输入ADC模块,促使ADC模块将电容电压依据参考电压转换为数值并存写到寄存器中,然后生成中断脉冲,触发中断程序来读取其中的数值。

工作流程

graph TB
进入ADC中断 --> 读取ADC寄存器数值并存储至全局变量 --> 交由主循环计算电压/电流/温度值并判断是否安全 --> id1[定时器中断检查开关/安全] --是--> 保持PWM开启
id1 --否--> 关闭PWM

ADC配置流程

graph LR
系统初始化 --> 使能ADC模块时钟 --> 配置模块参考电压 --> 配置模块时钟 --> 配置SOC通道 --> 配置采样周期 --> 配置采样触发源 --> 配置EOC中断触发源

配置示例

ADC模块在功能描述上也有别于ePWM,ePWM采用许多子模块的描述方法,ADC则使用 通道(CHNL,或 channel)、SOCEOCINT 等描述词汇,需要区分理解,且ADC模块从顶自下参数都有许多配置限制,具体看前面的描述吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/*-----------------------------------------------------------------------
* 【函数】:void ADC_Init(void);
* 【参数】:无
* 【说明】:配置ADC模块的电压、时钟、脉冲生成时间和开关
* 【其他】:以下所有配置项(电压、时钟、脉冲生成时间和开关)都是以模块(A
* /B/C)为最小单位进行配置的,需要启动ADCB和ADCC则需要在此处
* 进行相似配置
* 【参考】:
----------------------------------------------------------------------- */
void ADC_Init(void){
SetVREF(ADC_ADCA, ADC_EXTERNAL, ADC_VREF2P5); //为ADC A提供外部参考电压

EALLOW;
AdcaRegs.ADCCTL2.bit.PRESCALE = 0x02; //`0010`,ADCCLK = 输入时钟 / 2.0,即50MHz
AdcaRegs.ADCCTL1.bit.INTPULSEPOS = 0x1; // 写1的话,在ADC转换完成时(ADC捕获窗口周期结束时)生成脉冲中断
// 写0的话,就是在窗口周期结束时加上一组系统时钟周期后再生成中断,具体看《TRM》P1499
AdcaRegs.ADCCTL1.bit.ADCPWDNZ = ON; // 打开ADC A 的所有模拟电路
EDIS;

DELAY_US(1000); //延迟1ms
}


/*-----------------------------------------------------------------------
* 【函数】:void ADC_SOC_Init(void);
* 【参数】:无
* 【说明】:配置ADC模块下的SOC,设置通道和中断相关内容
* 【其他】:ADCINTSEL1N2寄存器是设置中断源相关的,
* 【参考】:
----------------------------------------------------------------------- */
void ADC_SOC_Init(void){
EALLOW;
AdcaRegs.ADCSOC0CTL.bit.CHSEL = 0x1; // channel select,为SOC0选择通道ADCA1,可设置范围为0~F
AdcaRegs.ADCSOC0CTL.bit.ACQPS = 0x1FF; // acquisition window, AW = (ACQPS+1) * SYSCLK,需要结合实际硬件设计进行计算,计算代码在文档中,1~511
AdcaRegs.ADCSOC0CTL.bit.TRIGSEL = 0x0F; // trigger select,触发源选择为ePWM6,每一次ePWM
AdcaRegs.ADCINTSEL1N2.bit.INT1SEL = 0x0; // 由EOC0来触发ADCA模块的第一个中断(INT1)
AdcaRegs.ADCINTSEL1N2.bit.INT1E = ON; // 打开ADCA模块的第一个中断(INT1)
// 此处没有设置ADC A的 中断生成模式(INT1CONT),默认为0x0,即手动清除中断标志时生成中断脉冲
AdcaRegs.ADCINTFLGCLR.bit.ADCINT1 = 0x1; // 清除ADC中断1的标志
EDIS;
}


/*-----------------------------------------------------------------------
* 【函数】:__interrupt void adcA1ISR(void);
* 【参数】:无
* 【说明】:ADC外设中断的处理程序
* 【其他】:注意需要加 __interrupt 关键字,加了的话编译器会自动处理 保持和恢
* 复特定的CPU寄存器状态(如果需要)且在人为清除中断组PIEACK位
* 后会自动使用IRET指令来返回;如果不加,则保持和恢复工作 以及 返
* 回工作需要人为处理。
* 【参考】:详见开发文档的处理中断部分,或《TRM》P85
----------------------------------------------------------------------- */
__interrupt void adcA1ISR(void){
sampleResult = AdcaResultRegs.ADCRESULT0;

AdcaRegs.ADCINTFLGCLR.bit.ADCINT1 = 1; // ADCA模块上的第一个中断

if(1 == AdcaRegs.ADCINTOVF.bit.ADCINT1){
AdcaRegs.ADCINTOVFCLR.bit.ADCINT1 = 1; //清除中断溢出
AdcaRegs.ADCINTFLGCLR.bit.ADCINT1 = 1; // ADCA模块上的第一个中断
}

PieCtrlRegs.PIEACK.all = PIEACK_GROUP1; // 确认中断

}

转换修正

ADC为12bit分辨率的电压转换外设,下面为读取数值和特定量程之间的电压转换代码,可以通过宏定义来修改最高量程、参考电压最高值 和 单个引脚的偏差值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#define    ADCDATA_OFFSET   40   // ADC2通道的量程偏差
#define VOLTAGE_RANGE 3.30 //电压量程3.3V
#define ADC_RANGE 4096 //ADC量程4096

/*-----------------------------------------------------------------------
* 【函数】:float AdcVoltage(ADCDATA regData);
* 【参数】:1. 瞬时读取的ADC寄存器的值 regData
* 【说明】:将ADC寄存器结果的值转换为对应量程电压的处理程序
* 【其他】:
* 【参考】:
----------------------------------------------------------------------- */
float AdcVoltage(ADCDATA regData){
ADCDATA tempData = 0; //初始化中转变量 tempData

tempData = regData + ADCDATA_OFFSET ; //修正某个模拟端口的偏差值,偏差值可以人为修改(假设测试设备输出电压经过计量)
if(tempData <= 0) tempData = 0; //超量程偏差修正
if(tempData >=ADC_RANGE) tempData = ADC_RANGE; //超量程偏差修正

return ((float)tempData / ADC_RANGE * VOLTAGE_RANGE); //转换成电压值,返回浮点数
}

温度传感

通过热敏电阻和普通10KΩ电阻的串联,施以3.3V电压,并在两个电阻之间引出导线接入ADC,ADC通过检测电压数值来输出对应 电压值。

由下方的热敏电阻 阻值特性表 可知,热敏电阻的温度升高和阻值之间并非线性关系。阻值偏差也在各温度间情况不同。

image-20210820212823758

中间部分省略......

image-20210820212605911

中间部分省略......

image-20210820212218560

阻值与温度公式

已知热敏电阻的阻值和温度关系公式—— 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}}} \]

ADC外接电路电压关系

image-20210821150320248

已知上方电路图中的\(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. 不要求计算B系数,但是只需要设置对应的安全温度阻值即可。即 式7 所示,当 \(R_{thermal}\)​​​ 小于安全温度阻值时,即触发中断。
  2. 设置其安全温度,在上方步骤1 的基础联立公式2进一步求解,计算量会增加,而且并不是非常准确的。

数值转换程序

宏定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 下方为ADC读值配置相关宏定义
#define ADCDATA_OFFSET 40 // ADC2通道的量程偏差
#define ADC_RANGE 4096 //ADC量程4096


// 下方为热敏电阻电路相关宏定义
#define TEMPERATURE_RANGE 110 //温度量程180(摄氏度)
#define VOLTAGE_RANGE 3.30 //电压量程3.3V

#define THM_ABSTEMP 273.15
#define THM_BCONST 4021.84 //根据阻值特性表和公式确定的,在T2 = 110摄氏度时的拟合B常数
#define THM_T1 (25.0+THM_ABSTEMP)
#define THM_RT1 50000.0

#define THM_R_SAFE 2550 //确定安全温度对应的拟合公式内的RT2为2.55KΩ
#define THM_R 10000.0 //设置外接ADC电路的电阻值(10KΩ,需转换成Ω)

电压转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*-----------------------------------------------------------------------
* 【函数】:ADC_RESULTDATA AdcVoltage(ADCDATA);
* 【参数】:1. 瞬时读取的ADC寄存器的值 regData
* 【说明】:将ADC寄存器结果的值转换为对应量程电压的处理程序
* 【其他】:对应的12bit量程、参考电压和偏差值可以在本文件前方的宏定义处修改
* 【参考】:
----------------------------------------------------------------------- */
ADC_RESULTDATA AdcVoltage(ADCDATA regData){
ADCDATA tempData = 0; //初始化中转变量 tempData

tempData = regData + ADCDATA_OFFSET ; //修正某个模拟端口的偏差值,偏差值可以人为修改(假设测试设备输出电压经过计量)
if(tempData <= 0) tempData = 0; //超量程偏差修正
if(tempData >=ADC_RANGE) tempData = ADC_RANGE; //超量程偏差修正

return ((float)tempData / ADC_RANGE * VOLTAGE_RANGE); //转换成电压值,返回浮点数
}

阻值转换:

1
2
3
4
5
6
7
8
9
10
11
12
/*-----------------------------------------------------------------------
* 【函数】:ADC_RESULTDATA AdcTemperature(ADCDATA);
* 【参数】:1. 瞬时读取的ADC寄存器的值 regData
* 【说明】:将ADC寄存器结果的值转换为对应量程温度的处理程序
* 【其他】:函数中使用到的T1、RT1、BCONST等参数均可以在本文件前方的宏定
* 义处找到
* 【参考】:《F280041PM开发文档》下的温度传感
----------------------------------------------------------------------- */
ADC_RESULTDATA AdcTemperature(ADCDATA regData){
return ( (THM_BCONST * THM_T1) / (THM_T1 * log( AdcResistance(regData) /THM_RT1 ) + THM_BCONST) ) - THM_ABSTEMP;
}

温度转换:

1
2
3
4
5
6
7
8
9
10
11
/*-----------------------------------------------------------------------
* 【函数】:ADC_RESULTDATA AdcTemperature(ADCDATA);
* 【参数】:1. 瞬时读取的ADC寄存器的值 regData
* 【说明】:将ADC寄存器结果的值转换为对应量程温度的处理程序
* 【其他】:函数中使用到的T1、RT1、BCONST等参数均可以在本文件前方的宏定
* 义处找到
* 【参考】:《F280041PM开发文档》下的温度传感
----------------------------------------------------------------------- */
ADC_RESULTDATA AdcTemperature(ADCDATA regData){
return ( (THM_BCONST * THM_T1) / (THM_T1 * log( AdcResistance(regData) /THM_RT1 ) + THM_BCONST) ) - THM_ABSTEMP;
}

谐振检测开发

开发目标:实现对 PWM边沿IGBT分压电流过零点 的相位时间差 δt 的检测,已达到是否达到谐振。

工作流程

graph TB
系统初始化 --> 使能ECAP模块时钟

项目FLASH烧录

项目Flash烧录即将代码从Debug模式变成Release模式,将代码的存放区域从RAM变成FLASH。需要在项目属性中修改原有的Release配置,或者复制原有的调试模式进行部分修改。

在前方已经调试好项目的情况下,FLASH模式较调试时使用DEBUG模式需要增加以下内容或步骤:

  • FLASH需要添加指定的标志(Symbol)
  • FLASH需要更换指定的 .cmd 内存文件
  • FLASH需要添加指定的命令行模式(Command-Line pattern)

新增配置

  1. 点击 项目属性(Properties),选择 编译器(C2000 Compiler),选择 管理配置(Manage Configurations)
image-20210830113307673
  1. 选择 新建(New),然后在配置页面下填写新建的配置信息,选择需要基于哪个现有的配置项进行改动。在原有的调试模式上进行更改即可,这样头文件包含路径那些的就不用替换。

image-20210830113400752 image-20210830113441976</

  1. 将新建的配置项设置为 活跃(Active)。
image-20210830113559386

增加标志

预定义标志(Predefined Symbols)添加两个标志名称: CPU1_FLASH

image-20210830113650241

添加成功时可以看到对应标志下的预编译被打开了,如下示:

image-20210830113803514

增加命令行模式

选择 编译器(C2000 Compiler),并在下方添加${output_flags} ${output},修改过后则变成了 ${command} ${flags} ${output_flags} ${output} ${inputs} 。如果只需要使用到调试模式(debug mode),就只需要原来的 ${command} ${flags} ${inputs} 即可。

image-20210830133549942

更换内存文件

D:\ti\CodeComposerStudio0930\ccs\ccs_base\c2000\include 路径下查找到对应的 FLASH 内存文件,并在 通用页面(General)的 链接器命令文件(Linker command file)栏进行替换。

image-20210830134720224

测试与优化

硬件工作流程要求如下:

graph LR
开机 --> id1[待机模式] --检测到工作设备--> id2[工作模式] --检测到设备离开--> id1

待机模式

开机后立即进入待机模式,对待机模式下的要求:

  1. IGBT此时并不开启,即PWM外设暂不开启。
  2. ADC外设需要时刻检测外面温度、电压和电流

开机启动流程:

graph
id1([开机启动]) --> id2[系统初始化/开启各外设时钟] --> 各外设寄存器清空并初始化 --> id3 --是--> id4([进入待机模式])

待机工作流程:

flowchart
id[定时器1启动]

为了达到 \(4\mu s\)​​ 的脉冲电路,需要设置CMPA = 100 ,在 TBCTR = 100TBCTR DIRECTION = UP

  1. 能通过直接关闭外设时钟来关闭外设吗?会否清除寄存器相关设置?重新启动时是否有延时?
  2. 读取GPIO 电平高低的状态寄存器,由其电平状态高低来确定外接硬开关是否按下,

工作设备检测

待机模式下IGBT并不工作,但仍需要对周围的设备进行检波和识别,检波方法:

  1. 每间隔几秒,PWM发射高电平时长为 \(4\mu s\) 的脉冲;
  2. 由eCAP外设检测震荡过零的周期,以判断工作设备是否存在。

注意:过零检测时两个波形的过零允差需要在一定数值范围内,这个数值范围待测定。

若在工作设备检测下,检测到设备存在,则进入工作模式,否则维持待机模式,持续进行工作设备的检测。

已知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%。

RS485通信开发

编码工作由F280041芯片的SCI外设负责,需要在对应的代码中设置好一些参数(数据长度、校验位、停止位等),可以使用CH340芯片的串口助手进行读写测试。

全部的设置工作如下:

  1. 对应GPIO的设置
  2. SCI的FIFO设置
  3. SCI传输编码格式的设置
  4. 使用中断或者主程序对数据进行接收和解读

工作流程

graph TB
系统初始化 --> 使能SCI通信模块时钟 --> 配置LSP时钟分频 --> 配置SCI模块 --> 使用中断或主程序接收处理上位机指令

SCI配置流程

graph LR
配置SCIFIFO模块 --> 初始化SCI相关寄存器 --> 配置SCI相关寄存器

配置SCI相关寄存器需要设置到以下几个参数位:

  • 单次通信传递字符数量 SCICHAR
  • 通信模式 ADDRIDLE_MODE
  • 数据校验位信息(校验使能 PARITYENA 、校验方式 PARITY
  • 停止位信息 STOPBITS
  • 通信使能 ( 发送端使能 TXENA、接收端使能 RXENA
  • 睡眠模式 SLEEP
  • 中断使能(接收错误中断使能 RXERRINTENA 、接收brk中断使能 RXBKINTENA 、传输中断使能 TXINTENA
  • 波特率 SCIHBAUDSCILBAUD

PC-USB-TTL-F280041

PC端使用CH340芯片的USB-TTL 转换器与芯片主板的 RX 口 和 TX 口直接通信,通信期间,PC端需要和发送端保持相同的设置,包括波特率、停止位大小、数据位、奇偶校验是否开启(开启的话为奇校验还是偶校验)等均需要保持一致。如下所示,左侧为上位机设置,右侧为SCI通信的设置。

注意:测试设备端口上的 RX TX 端口要和 TX RX 相反连接,且需要共地。

image-20210914111856207image-20210914132833272

下图为SCI_ECHOBACK功能效果演示:

image-20210914132731428

如下所示,校验位设置错误时,数据乱码。

image-20210914133403477

PC-RS232-RS485-TTL-F280041

商用台式电脑主机主板背部自带DB9接口(RS232),使用RS232转RS485的转换器进行连接,然后使用双绞线连接被控设备对应的接口。需要注意的是,转换器上的RS485电路不具备供电接口, VCC / GND 同样需要进行连接,且需要由电气的隔离电路提供。

转换器与被控设备的引脚连接方式为:T/R- 连接 IC_COM_MAX487EEPPA 芯片上的 B 口;T/R+ 连接 IC_COM_MAX487EEPPA 芯片上的 A 口;其中 A / B 口更多地被称为 A+ / B- 口。

连接成功后,使用串口通信助手进行测试,并查看寄存器结果,看出数据传输成功。

image-20210915165949131

使用RS485的优势是,RS232/RS485转换器间存在电气隔离,RS485和TTL之间也存在电气隔离,一旦被控设备发生浪涌,不会损坏PC机。

通信控制

通信控制相关流程如下:

graph 
id1[等待上位机数据] --> 收到通信数据 --> id2{判断FLAG对应的位是否使能} --是--> 修改对应的数据 --> 结束
id2 --否--> id1

传输时,需要注意对应的数据是否为 16bit8bit ,如PWM周期值为 16bit 寄存器,而传输过程需要使用 8bitunsigned char 字符进行传输,则需要下位机对收到的字符进行位移和按位或运算。

通信格式约定如下

  1. 第一个字符,即 char0 是 标志位之用 flag,将字符转换成二进制即为对应数据的修改使能控制,所有对数据的修改首先要求标志位(flag)对应位为1时才激活,如下表所示;
  2. 后续每两个或一个的char数据需要合成为对应的 unit16_t 数据;
  3. 通信传输时,需要使用16进制数据进行发送;
  4. 修改时会暂时关断PWM外设,修改完毕再开启。
表 通信协议中第0个字符的位说明
image-20210923143745959
表 通信协议各字符说明
image-20210923143648999

8位数据合成为16位数据函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
/*-----------------------------------------------------------------------
* 【函数】:uint16_t uchar2ushort(unsigned char, unsigned char);
* 【参数】:1. 高8位数据 high8bit
* 2. 低8位数据 low8bit
* 【说明】:将两个非负整形8bit数拼成非负整形16bit数
* 【其他】:
* 【参考】:
----------------------------------------------------------------------- */

uint16_t uchar2ushort(unsigned char high8bit, unsigned char low8bit){
return ( (high8bit << 8) | low8bit); //先左移8位然后进行按位或运算
}

通信调试参数计算方法

  1. 死区时间计算

从《TRM》P1814 中可知,(上升沿或下降沿的)死区时间大小都其自身的 寄存器设定值,以及 \(T_{TBCLK}\) 有关,关系公式如下: \[ \notag FED = DBFED · T_{TBCLK}\\ RED = DBRED · T_{TBCLK} \]

  1. TBPRD寄存器值计算

如果要改变 \(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 \]

所有计算单位都是 Hzs 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) \]

  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通信指令,如下:

image-20211009103727624

通信数据收到无误后会返回确认和执行字样:

image-20211009103755175

测试结果:

30 50 2.4

AB1F034001640164007700770050

image-20211009102908175

30 50 1.0

AB1F034001870187003200320050

image-20211009103114948

30 45 1.0

AB1F034001AE01AE003200320050

image-20211009103933753

30 45 2.4

AB1F034001880188007700770050

image-20211009104103063

开机默认波形如下:

30 30 5

AB1F03400198019800F900F90050

image-20211009110443081

2021年10月11日更新信息

经过了三天的努力,终于在10月11日完成了基于C++的 terminal串口通信。哈哈哈哈哈,可以成功实现通信啦~~

只要先在程序内选择指定接口,然后输入 目标工作频率、(下桥臂)任务占空比 和 (上桥臂)死区时间 就可以计算出指令并进行发送。

image-20211011172325394

可以从逻辑分析仪获得较为粗糙的波形信息,如下:

image-20211011172548527

2021年10月13日更新信息

之前输入任务占空比的时候,都是控制的EPWMB的任务占空比,每次输入 40%的任务占空比(死区时间为0)时,EPWMA的任务占空比都为 60%。再加上死区时间实际上是在已知EPWMB任务占空比的情况下去减小EPWMA的占空比的时间。经过简单的修改,可以实现输入目标任务占空比,可以实时控制EPWMA的实际占空比了,死区时间只对EPWMB有影响。

image-20211013163229517
image-20211013163247274

常见错误处理

#10263 MEMORY RANGE HAS ALREADY BEEN SPECIFIED

错误提示:存储区域范围(Memory Range)重复声明。

解释:在两个 .cmd 文件中,对 memory 的 page0 下的存储范围重复声明。

image-20210720171745641 image-20210720171759718
1
2
3
4
5
6
"../28004x_RAM_afe031_lnk.cmd", line 7: error #10263: BEGIN memory range has already been specified
"../28004x_RAM_afe031_lnk.cmd", line 7: error #10264: BEGIN memory range overlaps existing memory range BEGIN
"../28004x_RAM_afe031_lnk.cmd", line 8: error #10263: RAMM0 memory range has already been specified
"../28004x_RAM_afe031_lnk.cmd", line 8: error #10264: RAMM0 memory range overlaps existing memory range RAMM0
"../28004x_RAM_afe031_lnk.cmd", line 9: error #10263: RAMLS0 memory range has already been specified
.....

解决办法:删除、移动 28004x_RAM_afe031_lnk.cmd ,或更改其后缀名;确保在 项目属性(Properties)中的 连接器命令文件(Linker Command File)为正确的 280041_RAM_lnk.cmd

image-20210720172450100

image-20210720172242634

#1965 CANNOT OPEN SOURCE FILE

无法找到指定头文件,报错如下:

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 绝对路径以供搜索,即可解决。

image-20210721104533304

#10234-D UNRESOLVED SYMBOLS REMAIN

在将函数需要的头文件添加到项目后,进行 build project ,出现 “未能解决的标志存留” ,有2种可能原因:

  1. 输出格式(Output Format)不正确。
  2. 头文件缺少对应的 .c 文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 undefined        first referenced   
symbol in file
--------- ----------------
_ClkCfgRegs ./SystemControl.obj
_CpuSysRegs ./SystemControl.obj
_Dcc0Regs ./SystemControl.obj
_DcsmBank0Z1Regs ./SystemControl.obj
_DcsmBank0Z2Regs ./SystemControl.obj
_F28x_usDelay ./SystemControl.obj
_Flash0CtrlRegs ./SystemControl.obj
_Flash0EccRegs ./SystemControl.obj
_WdRegs ./SystemControl.obj

error #10234-D: unresolved symbols remain
error #10010: errors encountered during linking; "F28004x_WPT.out" not built

>> Compilation failure
makefile:143: recipe for target 'F28004x_WPT.out' failed
makefile:139: recipe for target 'all' failed
gmake[1]: *** [F28004x_WPT.out] Error 1
gmake: *** [all] Error 2

**** Build Finished ****

解决方法

排除第一种情况后,找到 未定义符号(undefined symbol)对应的头文件 f28004x_device.hf28004x_sysctrl.h 所在的文件路径 D:\ti\c2000Ware\C2000Ware_2_01_00_00\device_support\f28004x\headers\include ,注意到该头文件上一级有个 source 文件夹,存储着头文件对应的 .c 文件: f28004x_globalvariabledefs.c ,将该头文件以链接或复制的形式添加到项目文件中即可。

注意:大部分情况下,header files 都会有其对应的 .c.asm 文件,需要去相同的目录下方寻找即可。

image-20210727092820296image-20210727094440528

SC_ERR_PATH_BROKEN

在将评估板接到PC过程中,进行连接测试(Test Connection),报错如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
-----[Print the reset-command software log-file]-----------------------------

This utility has selected a 100- or 510-class product.
This utility will load the adapter 'jioxds110.dll'.
The library build date was 'Nov 25 2019'.
The library build time was '16:55:29'.
The library package version is '8.4.0.00006'.
The library component version is '35.35.0.0'.
The controller does not use a programmable FPGA.
The controller has a version number of '5' (0x00000005).
The controller has an insertion length of '0' (0x00000000).
This utility will attempt to reset the controller.
This utility has successfully reset the controller.

-----[Print the reset-command hardware log-file]-----------------------------

The scan-path will be reset by toggling the JTAG TRST signal.
The controller is the XDS110 with USB interface.
The link from controller to target is direct (without cable).
The software is configured for XDS110 features.
The controller cannot monitor the value on the EMU[0] pin.
The controller cannot monitor the value on the EMU[1] pin.
The controller cannot control the timing on output pins.
The controller cannot control the timing on input pins.
The scan-path link-delay has been set to exactly '0' (0x0000).

-----[An error has occurred and this utility has aborted]--------------------

This error is generated by TI's USCIF driver or utilities.

The value is '-233' (0xffffff17).
The title is 'SC_ERR_PATH_BROKEN'.

The explanation is:
The JTAG IR and DR scan-paths cannot circulate bits, they may be broken.
An attempt to scan the JTAG scan-path has failed.
The target's JTAG scan-path appears to be broken
with a stuck-at-ones or stuck-at-zero fault.

[End: Texas Instruments XDS110 USB Debug Probe_0]

因为本开发板设计上使用两线制,为 cJTAG,而默认情况下是标准的 JTAG,只需要 TCK 和 TMS 两个信号(官方文档的常见问答部分有说明)。

.ccxml 文件中进行修改,点击下方的 Advanced,然后选择 cJTAG(1149.7)2-pin advance mode,然后 save 保存即可。具体操作如下图:

image-20210726155434636

再次运行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
-----[Print the reset-command software log-file]-----------------------------

This utility has selected a 100- or 510-class product.
This utility will load the adapter 'jioxds110.dll'.
The library build date was 'Nov 25 2019'.
The library build time was '16:55:29'.
The library package version is '8.4.0.00006'.
The library component version is '35.35.0.0'.
The controller does not use a programmable FPGA.
The controller has a version number of '5' (0x00000005).
The controller has an insertion length of '0' (0x00000000).
This utility will attempt to reset the controller.
This utility has successfully reset the controller.

-----[Print the reset-command hardware log-file]-----------------------------

The scan-path will be reset by toggling the JTAG TRST signal.
The controller is the XDS110 with USB interface.
The link from controller to target is direct (without cable).
The software is configured for XDS110 features.
The controller cannot monitor the value on the EMU[0] pin.
The controller cannot monitor the value on the EMU[1] pin.
The controller cannot control the timing on output pins.
The controller cannot control the timing on input pins.
The scan-path link-delay has been set to exactly '0' (0x0000).

-----[Perform the Integrity scan-test on the JTAG IR]------------------------

This test will use blocks of 64 32-bit words.
This test will be applied just once.

Do a test using 0xFFFFFFFF.
Scan tests: 1, skipped: 0, failed: 0
Do a test using 0x00000000.
Scan tests: 2, skipped: 0, failed: 0
Do a test using 0xFE03E0E2.
Scan tests: 3, skipped: 0, failed: 0
Do a test using 0x01FC1F1D.
Scan tests: 4, skipped: 0, failed: 0
Do a test using 0x5533CCAA.
Scan tests: 5, skipped: 0, failed: 0
Do a test using 0xAACC3355.
Scan tests: 6, skipped: 0, failed: 0
All of the values were scanned correctly.

The JTAG IR Integrity scan-test has succeeded.

-----[Perform the Integrity scan-test on the JTAG DR]------------------------

This test will use blocks of 64 32-bit words.
This test will be applied just once.

Do a test using 0xFFFFFFFF.
Scan tests: 1, skipped: 0, failed: 0
Do a test using 0x00000000.
Scan tests: 2, skipped: 0, failed: 0
Do a test using 0xFE03E0E2.
Scan tests: 3, skipped: 0, failed: 0
Do a test using 0x01FC1F1D.
Scan tests: 4, skipped: 0, failed: 0
Do a test using 0x5533CCAA.
Scan tests: 5, skipped: 0, failed: 0
Do a test using 0xAACC3355.
Scan tests: 6, skipped: 0, failed: 0
All of the values were scanned correctly.

The JTAG DR Integrity scan-test has succeeded.

[End: Texas Instruments XDS110 USB Debug Probe_0]

时钟初始化错误

时钟初始化错误是可能新建文件后可能会出现的隐形错误,因为程序编译的时候不报错,只报 WARNING ,因此很难察觉。

但是,能通过 Expression 界面输入寄存器名称,用以观察其是否被初始化,如下图示,为自建项目后初始化的寄存器数值,只运行第一步PLL初始化,可以观察到 ClkCfgRegs 并未被初始化。

image-20210730170342194

同样使用官方样例代码进行仅一步的PLL初始化操作,可以发现初始化成功。

image-20210730170350083

仔细对比和检查后发现,自建项目中缺少了几个,通过link方式或copy方式添加进来,再到项目属性下的 Symbol Management中,为输出模块添加代码入口点 code_start 即可。

image-20210730171218213
image-20210730171409142

TARGET MUST BE CONNECTED

使用JTAG调试器XDS100 V2连接至目标设备上时,如果出现下图所示的 “Disconnected: Unknown”,说明目标MCU/DSP没有正确识别。

image-20210802160213827

首先应检查JTAG是否链接正常,打开“设备管理器”进行查看,如果设备连接器中出现了该JTAG设备,则进一步查看项目文件下的.cxxml文件,该文件用于配置设备连接信息,包括芯片信号、JTAG调试器信号、通信方式、通信速率等。确保所有设置都正确以后进行全面断电、断开连接、重启CCS并重新尝试Debug,如果仍旧出现问题,请按以下步骤进行操作。

在Debug界面,点击目标CPU,点击 Connect Target 进行手动连接。

image-20210802160242203

连接正确后,手动导入 Build 完之后的 .out 文件,将该程序手动烧写至CPU的RAM/Flash中

image-20210802160456548

选择对应项目下生成的 .out 文件:

image-20210802160358841

可以在Console台(界面)观察到已经写入和初始化成功:

image-20210802154136487

#85 INVALID COMBINATION OF TYPE SPECIFIERS

出现以上错误时,我正打算新建函数,但是写到一半忘了,去忙别的,回头编译就出错了。找到最近的修改痕迹,然后果然发现自己只写了一个函数数据类型的声明,其他啥都没写,将其删除即可重新编译。

常见警告处理

Object file specified, but linking not enabled

image-20210830135156509

常见优化提示

#2614-D (Performance) Use --fp_mode=relaxed to enable TMU hardware support for FP division

image-20210817085055902

上述提示标志为 i ,也就是有关于性能优化的提示,可以在项目属性的编译器中,打开对应的优化开关,如下图。

image-20210817085234005

术语

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. 《TMS320F28004x 微控制器》
  2. 《F28004x_DEV_USER_GUIDE.pdf》
  3. 《SPRU566N C2000 Real-Time Control MCU Peripherals Reference Guide》
  4. 《SPRUI33D TMS320F28004x Real-Time Microcontrollers Technical Reference Manual》
  5. 《SPRUHS1C TMS320C28x Extended Instruction Sets Technical Reference Manual》
  6. 基于IGBT的DC-AC变换器设计
  7. IGBT模块单相半桥逆变电路设计与应用
  8. Texas Instruments F28004x Peripheral Driver Library
  9. 280049LaunchPad仿真器连接不上的问题解决办法
  10. DSP 28335 编译过程中的#10234-D、#10010报错
  11. 《MCU025A(001)_BOM.xls》
  12. 《MCU025A(001)_Sch.pdf》
  13. 《MCU025A_PCB_LayerPlots.pdf》

pthread报错

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <thread>
using namespce std;

void hello(){
cout << "hello concurent world!";
}

int main (int argc, char * argv[]){
thread t(hello);
t.join();
return 0;
}

这个小例子直接用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
2
g++ -o test test.cpp -lpthread
./test

结果输出:

1
hello concurent world!

参考

  1. c++使用thread类时编译出错,对‘pthread_create’未定义的引用

通过在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计算的公式描述:

image-20210706002255187

以下为SigmaStudio中的PLL设定界面参数设定:

image-20210706002412136

代码编写

注意:本代码在Linux上,以C++17的版本进行编译运行。

编译代码如下:

1
g++ main.cpp -std=c++17 -lpthread

在编写代码时,考虑到SigmaStudio和数据手册之间可能存在表述差异,因此计算PLL的源代码中,也包含了开启 1/2 系数的宏定义 #define HALFCOFF 1 ,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/*---------------------------------------------------------
* HEADER FILES
*-------------------------------------------------------*/
#include <iostream>
#include <stdio.h>
#include <math.h>
#include <thread>

using namespace std;
/*---------------------------------------------------------
* MACRO DEFINITION
*-------------------------------------------------------*/
#define ACCURACY 1000000000 //输出时钟精度,小数点后9个0
// #define HALFCOFF 1 //打开1/2系数进行计算,SigmaStudio图示中有该系数,但《ADAU1772》数据手册中没有该系数

/*---------------------------------------------------------
* MACRO DEFINITION FOR DEBUG
*-------------------------------------------------------*/
// #define DEBUG_MODE //Debug模式


/*---------------------------------------------------------
* DATA TYPES
*-------------------------------------------------------*/
typedef double CLK; //数据类型_时钟
typedef unsigned short COFF; //数据类型_系数


/*---------------------------------------------------------
* FILE DOMAIN DATA DECLARATION
*-------------------------------------------------------*/
CLK CLK_IN = 16.6; //输入时钟
CLK CLK_OUT = 24.576; //输出时钟

struct TargetCofficient {
COFF Numerator;
COFF Denominator;
};

COFF Maxiator = 65535;
COFF InputClockDivider = 4;
COFF IntegerSetting = 8;

/*---------------------------------------------------------
* FUNCTIONS DECLARATION
*-------------------------------------------------------*/
int isSafe(COFF, COFF); //判断系数的分子和分母相除是否满足要求
int isOK(CLK); //判断输出结果是否符合输出时钟的精度要求
int CofficientCalculate(CLK, COFF, COFF); //计算

void ICD1(void); //子线程
void ICD2(void); //子线程
void ICD3(void); //子线程
void ICD4(void); //子线程
void SysPrintf(void);

/*---------------------------------------------------------
* MAIN FUNCTIONS
*-------------------------------------------------------*/
int main(){

cout << "SYSTEM: Procedure starts." << endl;
thread thread1(ICD1);
thread thread2(ICD2);
thread thread3(ICD3);
thread thread4(ICD4);

thread1.join();
thread2.join();
thread3.join();
thread4.join();

cout << "SYSTEM: Procedure ends up." << endl;
return 0;
}


/*---------------------------------------------------------
* FUNCTIONS DEFINITION
*-------------------------------------------------------*/
int isSafe(COFF N, COFF D){
double result = (double)N / (double)D;
if( (result >= 0.1) && (result <= 0.9) ) return 1; //判断系数是否满足要求,满足则返回1
else return 0;
}

int isOK(CLK clock){
CLK integerClock , fractClock;
fractClock = modf(clock , &integerClock); //分别取出整数和小数部分

if( ((int)(fractClock * ACCURACY) == (int)(576/(double)1000 * ACCURACY)) && ((int)integerClock == (int)CLK_OUT) ) { //如果小数点精度满足要求,且整数部分相同
return 1; //则返回1
}
else return 0;
}

int CofficientCalculate(CLK ClockInput, COFF ICD, COFF IS){
CLK tempCLK;

for(COFF i = 0; i < Maxiator; i++){ //分母
for(COFF j = 0; j < Maxiator; j++){ //分子
if(isSafe(j, i)){
#ifdef HALFCOFF
tempCLK = ClockInput /(double)ICD * ((double)IS + ((double)j/(double)i)) *1/2; //有系数时
#else
tempCLK = ClockInput /(double)ICD * ((double)IS + ((double)j/(double)i)) ; //无系数时
#endif

#ifdef DEBUG_MODE
cout << "SYSTEM:IDC = " << ICD << ", IS = " << IS << hex <<", Numerator = 0x"<< j << ", Denominator = 0x" << i << ", OutputClock = " << tempCLK << endl;
#endif
}
else continue;

if( isOK(tempCLK)) {
cout << "SYSTEM:IDC = " << ICD << ", IS = " << IS << hex <<", Numerator = 0x"<< j << ", Denominator = 0x" << i << ", OutputClock = " << tempCLK << endl;
}
}
}
return 0;
}


/*---------------------------------------------------------
* THREAD _ CALCULATOR
*-------------------------------------------------------*/
void ICD1(void){
for(COFF enumIS = 2; enumIS <= IntegerSetting; enumIS++) CofficientCalculate(CLK_IN , 1, enumIS);
}

void ICD2(void){
for(COFF enumIS = 2; enumIS <= IntegerSetting; enumIS++) CofficientCalculate(CLK_IN , 2, enumIS);
}

void ICD3(void){
for(COFF enumIS = 2; enumIS <= IntegerSetting; enumIS++) CofficientCalculate(CLK_IN , 3, enumIS);
}

void ICD4(void){
for(COFF enumIS = 2; enumIS <= IntegerSetting; enumIS++) CofficientCalculate(CLK_IN , 4, enumIS);
}

void SysPrintf(void){
// while(1) cout << "Calculating...";
while(1){
printf("Calculating...");
printf("\r\003");
}
}


运行结果

通过自行编写的代码,遍历所有可更改的系数进行计算求解,取得小数点后9位精度,并且符合要求的系数如下(含1/2系数):

image-20210706001243698

以下为不含1/2系数的结果:

image-20210706001350802

经过手工计算,结果(小数点后9位)确实满足要求。

cnblogs.com

名词解析

PCM(Pulse Code Modulation)也被称为脉码编码调制,PCM中的声音数据没有被压缩,它是由模拟信号经过采样、量化、编码转换成的标准的数字音频数据。采样转换方式参考下图进行了解:

img

音频采样包含以下几大要素:

采样率

采样率表示音频信号每秒的数字快照数。该速率决定了音频文件的频率范围。采样率越高,数字波形的形状越接近原始模拟波形。低采样率会限制可录制的频率范围,这可导致录音表现原始声音的效果不佳。根据奈奎斯特采样定理,为了重现给定频率,采样率必须至少是该频率的两倍。例如,一般CD唱片的采样率为每秒 44,100 个采样,因此可重现最高为 22,050 Hz 的频率,此频率刚好超过人类的听力极限 20,000 Hz。

img

图中A是低采样率的音频信号,其效果已经将原始声波进行了扭曲,B则是完全重现原始声波的高采样率的音频信号。

数字音频常用的采样率如下:

img

位深度

位深度决定动态范围。采样声波时,为每个采样指定最接近原始声波振幅的振幅值。较高的位深度可提供更多可能的振幅值,产生更大的动态范围、更低的噪声基准和更高的保真度。

img

位深度越高,提供的动态范围越大。

PCM

在上面的名词解析中我们应该对PCM有了一定的理解和认识,下面我们将对PCM做更多的讲解。

PCM音频数据存储方式

如果是单声道的文件,采样数据按时间的先后顺序依次存入。如果是单声道的音频文件,采样数据按时间的先后顺序依次存入(也可能采用 LRLRLR 方式存储,只是另一个声道的数据为 0)。

如果是双声道的话通常按照 LRLRLR 的方式存储,存储的时候还和机器的大小端有关。(关于字节序大小端的相关内容可参考《字节序问题之大小端模式讲解》进行了解)

PCM的存储方式为小端模式,存储Data数据排列如下图所示:

img

PCM 音频数据的参数

描述 PCM 音频数据的参数的时候有如下描述方式:

1
2
3
44100HZ 16bit stereo: 每秒钟有 44100 次采样, 采样数据用 16 位(2 字节)记录, 双声道(立体声)
22050HZ 8bit mono: 每秒钟有 22050 次采样, 采样数据用 8 位(1 字节)记录, 单声道
48000HZ 32bit 51ch: 每秒钟有 48000 次采样, 采样数据用 32 位(4 字节浮点型)记录, 5.1 声道

44100Hz 指的是采样率,它的意思是每秒取样 44100 次。采样率越大,存储数字音频所占的空间就越大。

16bit 指的是采样精度,意思是原始模拟信号被采样后,每一个采样点在计算机中用 16 位(两个字节)来表示。采样精度越高越能精细地表示模拟信号的差异。

Stereo 指的是声道数,也即采样时用到的麦克风的数量,麦克风越多就越能还原真实的采样环境(当然麦克风的放置位置也是有规定的)。

WAV

WAV 是 Microsoft 和 IBM 为 PC 开发的一种声音文件格式,它符合 RIFF(Resource Interchange File Format)文件规范,用于保存 Windows 平台的音频信息资源,被 Windows 平台及其应用程序所广泛支持。WAVE 文件通常只是一个具有单个 “WAVE” 块的 RIFF 文件,该块由两个子块(”fmt” 子数据块和 ”data” 子数据块),它的格式如下图所示:

img

WAV 格式定义

该格式的实质就是在 PCM 文件的前面加了一个文件头,每个字段的的含义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct {
char ChunkID[4]; //内容为"RIFF"
unsigned long ChunkSize; //存储文件的字节数(不包含ChunkID和ChunkSize这8个字节)
char Format[4]; //内容为"WAVE“
} WAVE_HEADER;

typedef struct {
char Subchunk1ID[4]; //内容为"fmt"
unsigned long Subchunk1Size; //存储该子块的字节数(不含前面的Subchunk1ID和Subchunk1Size这8个字节)
unsigned short AudioFormat; //存储音频文件的编码格式,例如若为PCM则其存储值为1。
unsigned short NumChannels; //声道数,单声道(Mono)值为1,双声道(Stereo)值为2,等等
unsigned long SampleRate; //采样率,如8k,44.1k等
unsigned long ByteRate; //每秒存储的bit数,其值 = SampleRate * NumChannels * BitsPerSample / 8
unsigned short BlockAlign; //块对齐大小,其值 = NumChannels * BitsPerSample / 8
unsigned short BitsPerSample; //每个采样点的bit数,一般为8,16,32等。
} WAVE_FMT;

typedef struct {
char Subchunk2ID[4]; //内容为“data”
unsigned long Subchunk2Size; //接下来的正式的数据部分的字节数,其值 = NumSamples * NumChannels * BitsPerSample / 8
} WAVE_DATA;

WAV 文件头解析

这里是一个 WAVE 文件的开头 72 字节,字节显示为十六进制数字:

1
2
3
4
5
6
52 49 46 46 | 24 08 00 00 | 57 41 56 45
66 6d 74 20 | 10 00 00 00 | 01 00 02 00
22 56 00 00 | 88 58 01 00 | 04 00 10 00
64 61 74 61 | 00 08 00 00 | 00 00 00 00
24 17 1E F3 | 3C 13 3C 14 | 16 F9 18 F9
34 E7 23 A6 | 3C F2 24 F2 | 11 CE 1A 0D

字段解析如下图:

img

PCM & WAV 开发实践

PCM格式转为WAV格式(基于C语言)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
int simplest_pcm16le_to_wave(const char *pcmpath,int channels,int sample_rate,const char *wavepath)
{
typedef struct WAVE_HEADER{
char fccID[4];
unsigned long dwSize;
char fccType[4];
}WAVE_HEADER;
typedef struct WAVE_FMT{
char fccID[4];
unsigned long dwSize;
unsigned short wFormatTag;
unsigned short wChannels;
unsigned long dwSamplesPerSec;
unsigned long dwAvgBytesPerSec;
unsigned short wBlockAlign;
unsigned short uiBitsPerSample;
}WAVE_FMT;
typedef struct WAVE_DATA{
char fccID[4];
unsigned long dwSize;
}WAVE_DATA;
if(channels==0||sample_rate==0){
channels = 2;
sample_rate = 44100;
}
int bits = 16;
WAVE_HEADER pcmHEADER;
WAVE_FMT pcmFMT;
WAVE_DATA pcmDATA;

unsigned short m_pcmData;
FILE *fp,*fpout;
fp=fopen(pcmpath, "rb");
if(fp == NULL) {
printf("open pcm file error\n");
return -1;
}
fpout=fopen(wavepath, "wb+");
if(fpout == NULL) {
printf("create wav file error\n");
return -1;
}
//WAVE_HEADER
memcpy(pcmHEADER.fccID,"RIFF",strlen("RIFF"));
memcpy(pcmHEADER.fccType,"WAVE",strlen("WAVE"));
fseek(fpout,sizeof(WAVE_HEADER),1);
//WAVE_FMT
pcmFMT.dwSamplesPerSec=sample_rate;
pcmFMT.dwAvgBytesPerSec=pcmFMT.dwSamplesPerSec*sizeof(m_pcmData);
pcmFMT.uiBitsPerSample=bits;
memcpy(pcmFMT.fccID,"fmt ",strlen("fmt "));
pcmFMT.dwSize=16;
pcmFMT.wBlockAlign=2;
pcmFMT.wChannels=channels;
pcmFMT.wFormatTag=1;

fwrite(&pcmFMT,sizeof(WAVE_FMT),1,fpout);
//WAVE_DATA;
memcpy(pcmDATA.fccID,"data",strlen("data"));
pcmDATA.dwSize=0;
fseek(fpout,sizeof(WAVE_DATA),SEEK_CUR);
fread(&m_pcmData,sizeof(unsigned short),1,fp);
while(!feof(fp)){
pcmDATA.dwSize+=2;
fwrite(&m_pcmData,sizeof(unsigned short),1,fpout);
fread(&m_pcmData,sizeof(unsigned short),1,fp);
}
pcmHEADER.dwSize=44+pcmDATA.dwSize;
rewind(fpout);
fwrite(&pcmHEADER,sizeof(WAVE_HEADER),1,fpout);
fseek(fpout,sizeof(WAVE_FMT),SEEK_CUR);
fwrite(&pcmDATA,sizeof(WAVE_DATA),1,fpout);

fclose(fp);
fclose(fpout);
return 0;
}

注意:函数里声明的数据类型unsigned long在有些C编译器上是64位的,这时候要改成unsigned int才可以,否则wav头有88bytes,标准的是44bytes,改完就正常了,对C还不熟悉的人小小的心得,另外,声道数和采样率也要注意,一般采样率有44100/16000/8000,要确认是哪个,声道是1还是2,这两个参数要设置好才会有正确的转换结果。

PCM降低某个声道的音量(基于C语言)

一般来说 PCM 数据中的波形幅值越大,代表音量越大,对于 PCM 音频数据而言,它的幅值(即该采样点采样值的大小)代表音量的大小。

如果我们需要降低某个声道的音量,可以通过减小某个声道的数据的值来实现降低某个声道的音量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int pcm16le_half_volume_left( char *url ) {
FILE *fp_in = fopen( url, "rb+" );
FILE *fp_out = fopen( "output_half_left.pcm", "wb+" );
unsigned char *sample = ( unsigned char * )malloc(4); // 一次读取一个sample,因为是2声道,所以是4字节
while ( !feof( fp_in ) ){
fread( sample, 1, 4, fp_in );
short* sample_num = ( short* )sample; // 转成左右声道两个short数据
*sample_num = *sample_num / 2; // 左声道数据减半
fwrite( sample, 1, 2, fp_out ); // L
fwrite( sample + 2, 1, 2, fp_out ); // R
}
free( sample );
fclose( fp_in );
fclose( fp_out );
return 0;
}

上述代码做的事情是:在读出左声道的 2 Byte 的取样值之后,将其转成了 C 语言中的一个 short 类型的变量。将该数值除以 2 之后写回到了 PCM 文件中。

分离PCM音频数据左右声道的数据

因为PCM音频数据是按照LRLRLR的方式来存储左右声道的音频数据的,所以我们可以通过将它们交叉的读出来的方式来分离左右声道的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int simplest_pcm16le_split(char *url) {
FILE *fp=fopen(url,"rb+");
FILE *fp1=fopen("output_l.pcm","wb+");
FILE *fp2=fopen("output_r.pcm","wb+");
unsigned char *sample=(unsigned char *)malloc(4);
while(!feof(fp)){
fread(sample,1,4,fp);
//L
fwrite(sample,1,2,fp1);
//R
fwrite(sample+2,1,2,fp2);
}
free(sample);
fclose(fp);
fclose(fp1);
fclose(fp2);
return 0;
}

从PCM16LE单声道音频采样数据中截取一部分数据

本程序中的函数可以从PCM16LE单声道数据中截取一段数据,并输出截取数据的样值。函数的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* Cut a 16LE PCM single channel file.
* @param url Location of PCM file.
* @param start_num start point
* @param dur_num how much point to cut
*/
int simplest_pcm16le_cut_singlechannel(char *url,int start_num,int dur_num){
FILE *fp=fopen(url,"rb+");
FILE *fp1=fopen("output_cut.pcm","wb+");
FILE *fp_stat=fopen("output_cut.txt","wb+");

unsigned char *sample=(unsigned char *)malloc(2);

int cnt=0;
while(!feof(fp)){
fread(sample,1,2,fp);
if(cnt>start_num&&cnt<=(start_num+dur_num)){
fwrite(sample,1,2,fp1);

short samplenum=sample[1];
samplenum=samplenum*256;
samplenum=samplenum+sample[0];

fprintf(fp_stat,"%6d,",samplenum);
if(cnt%10==0)
fprintf(fp_stat,"\n",samplenum);
}
cnt++;
}

free(sample);
fclose(fp);
fclose(fp1);
fclose(fp_stat);
return 0;
}

将PCM16LE双声道音频采样数据转换为PCM8音频采样数据

本程序中的函数可以通过计算的方式将PCM16LE双声道数据16bit的采样位数转换为8bit。函数的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* Convert PCM-16 data to PCM-8 data.
* @param url Location of PCM file.
*/
int simplest_pcm16le_to_pcm8(char *url){
FILE *fp=fopen(url,"rb+");
FILE *fp1=fopen("output_8.pcm","wb+");

int cnt=0;

unsigned char *sample=(unsigned char *)malloc(4);

while(!feof(fp)){

short *samplenum16=NULL;
char samplenum8=0;
unsigned char samplenum8_u=0;
fread(sample,1,4,fp);
//(-32768-32767)
samplenum16=(short *)sample;
samplenum8=(*samplenum16)>>8;
//(0-255)
samplenum8_u=samplenum8+128;
//L
fwrite(&samplenum8_u,1,1,fp1);

samplenum16=(short *)(sample+2);
samplenum8=(*samplenum16)>>8;
samplenum8_u=samplenum8+128;
//R
fwrite(&samplenum8_u,1,1,fp1);
cnt++;
}
printf("Sample Cnt:%d\n",cnt);

free(sample);
fclose(fp);
fclose(fp1);
return 0;
}

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双声道音频采样数据的声音速度提高一倍

本程序中的函数可以通过抽象的方式将PCM16LE双声道数据的速度提高一倍,采用采样每个声道奇(偶)数点的样值的方式,函数的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* Re-sample to double the speed of 16LE PCM file
* @param url Location of PCM file.
*/
int simplest_pcm16le_doublespeed(char *url){
FILE *fp=fopen(url,"rb+");
FILE *fp1=fopen("output_doublespeed.pcm","wb+");

int cnt=0;

unsigned char *sample=(unsigned char *)malloc(4);

while(!feof(fp)){

fread(sample,1,4,fp);

if(cnt%2!=0){
//L
fwrite(sample,1,2,fp1);
//R
fwrite(sample+2,1,2,fp1);
}
cnt++;
}
printf("Sample Cnt:%d\n",cnt);

free(sample);
fclose(fp);
fclose(fp1);
return 0;
}

参考

视音频数据处理入门:PCM音频采样数据处理 --> 致敬雷神!

伪操作和伪指令

Directives,即 伪操作,是汇编语言中的特殊指令助记符。主要作用是为完成汇编程序做各种准备。伪操作仅是在源程序进行汇编时由汇编程序处理,而不是在计算机运行期间由机器执行的指令。即,伪操作只在汇编时起作用,一旦汇编结束,其使命也就结束了。

Pseudo-Instruction,即 伪指令,是汇编语言程序里的特殊指令助记符,不是 真指令。伪指令在汇编时被替换成合适的机器指令(根据芯片架构而定,不同芯片架构之间指令可能不同),故其也只在汇编时其作用,不在机器运行期间由机器执行。

ADI汇编文件样式

.asm 文件具有以下样式,包含 预处理指令(主要是C/C++宏定义)、汇编伪操作类别(assembler direcitives)、数据块、代码块、条件编译预处理 和 汇编标签。

image-20210630170546778

混合编程

使用asm()

1
2
3
4
int img288; //定义C语言变量
asm("ax0 = 0x5C;");
asm("dm(img288_) = ax0;"); //用汇编语言赋值时,变量需要加后置下划线???
img288 = 0x5C; //直接用C语言赋值

以上C语言与汇编代码经过编译后:

1
2
3
4
ax0 = 0x5C;
dm(img288_) = ax0;
my1 = 92;
dm(img288_) = my1;

此处可否把 dm() 当成一个类似于指针的东西

使用汇编子程序

使用汇编子程序是C语言程序与汇编语言接口的另一种方法。用户定义的子程序放在单独的汇编文件中,或是做成二进制的库文件,并将子程序的定义用GLOBEL输出,汇编后就可以供C语言程序调用。下面是一个不需要参数的子程序的例子:

1
2
3
4
5
6
7
8
9
10
11
12
.MODULE/RAM_delay_;
.external del_cycle; //声明del_cycle是外部变量
.global delay; //声明delay是全局变量

delay_:
function_entry; //子程序开始标志,必不可少
ar = dm(del_cycle_);
cntr = ar;
do d_loop until ce;
d_loop:nop;
exit; //子程序结束标志,必不可少
.ENDMOD;

注意:以上代码源自参考链接4,实际上代码中的关键字相差可能较大,需要根据实际情况进行改动。

比如,21479系列芯片与ADAU1939芯片一同工作,其ADC采样代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
.section/pm seg_pmco;  //程序代码块,procedure memory section

_Receive_ADC_Samples: /*应该类似于 { */
.global _Receive_ADC_Samples; //定义自己为全局函数

r1 = -31;
r0 = dm(_rx1a_buf + Internal_ADC_L1);
f0 = float r0 by r1;
dm(_Left_Channel_In1) = r0;

r0 = dm(_rx1a_buf + Internal_ADC_R1);
f0 = float r0 by r1;
dm(_Right_Channel_In1) = r0;

r0 = dm(_rx1b_buf + Internal_ADC_L2);
f0 = float r0 by r1;
dm(_Left_Channel_In2) = r0;

r0 = dm(_rx1b_buf + Internal_ADC_R2);
f0 = float r0 by r1;
dm(_Right_Channel_In2) = r0;

r0 = DM(AD1939_audio_frame_timer);
r0 = r0 + 1;
DM(AD1939_audio_frame_timer) = r0;

leaf_exit; //程序结束标志,应该类似于return;吧
_Receive_ADC_Samples.end: /*应该类似于 }*/

Dual Memory Support Keywords (dm / pm)

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):

  • The memory space keyword (dm or pm) refers to the expression to the right of the keyword.
  • You can specify a memory space for each level of pointer. This corresponds to one memory space for each * in the declaration.
  • The compiler uses Data Memory (DM) as the default memory space for all variables. All undeclared spaces for data are Data Memory spaces.
  • The compiler always uses Program Memory (PM) as the memory space for functions. Function pointers always point to Program Memory.
  • You cannot assign memory spaces to automatic variables. All automatic variables reside on the stack, which is always in Data Memory.
  • Literal character strings always reside in Data Memory.

参考

  1. VisualDSP++ 5.0 C/C++ Compiler Manual for SHARC Processors
  2. ADSP-21160 SHARC DSP Instruction Set Reference
  3. 《嵌入式系统原理与应用设计》王光学,电子工业出版社
  4. 嵌入式C语言开发ADSP21XX系列

数字音频信号的传输标准,如:

  • I2SPCM (Pulse Code Modulation) 和 PDM (Pulse Density Modulation)主要用于同一块电路板上芯片之间音频信号的传输;
  • Intel HDA (Intel High Definition Audio) 用于PC的Audio子系统(声卡)应用;
  • S/PDIFEthernet AVB 主要应用于板间长距离及需要电缆连接的场合。

Philips I2S 标准

I2S的标准文件源自 飞利浦半导体(Philips Semiconductors)发表于1986年的《I2S bus specification(1996年修订版)》。

I2S(Inter-IC Sound)采用了独立的导线传输时钟与数据信号的设计,通过将数据和时钟信号分离,避免了因时差诱发的失真,为用户节省了购买抵抗音频抖动的专业设备的费用。

需要注意:I2S是一种音频编码协议,同时也是一种音频接口,因此采用I2S接口方式的不一定采用I2S编码,还可以采用 左对齐(Left Justifying)编码 或 右对齐(Right Justifying)编码。

image-20210629145800027

特点

  1. 支持全双工/半双工

  2. 支持主/从模式

  3. 和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).

image-20210629112429113

从上图得知,I2S总线共需要三个引脚:

  • 连续串行时钟线(SCK / SCLK / BCLK),也叫位时钟,即对应数字音频的每一位数据,SCLK都有1个脉冲。SCLK的频率=2×采样频率×采样位数。
  • 字选择线(WS / LRCLK),也叫帧时钟,用于切换左右声道的数据。LRCK为“1”表示正在传输的是右声道的数据,为“0”则表示正在传输的是左声道的数据。LRCK的频率等于采样频率。
  • 时分多路复用(简称 TDM)数据线,即串行数据(SDSDATA),就是用二进制补码表示的音频数据。

另,有时为了使系统间能够更好地同步,还需要另外传输一个信号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总是在最高位传输前的一个时钟周期发生改变,这样可以使从属装置得到与被传输的串行数据同步的时间,并且使接收端存储当前的命令以及为下次的命令清除空间。

ADI-Codec I2S移植

以下文章/教程仅针对官方提供的例程《AD1939_I2S_Sample_Based_Talkthru》进行修改。

  • <ad1939.h> 定义了ADAU1939的宏定义,包括锁相环、时钟、DAC、ADC等的寄存器,与直接从SigmaStudio导出的文件并没有太大差别,应该经过精简。

首先,需要清晰认识1939和1772的硬件差异。ADAU1939和ADAU1772硬件上的异同,都有4个差分输入,但是1939有8个差分输出,1772有2个双声道输出(4个差分输出)。

image-20210628095252103

SPORTS ,即Serial Ports的简称,串行接口。

<ADDS_21479_EzKit.h> 头文件里,以下声明适用于21479和1939的音频通信通道,定义了 2个双声道输入4个双声道输出 ,正好符合1939的 4个差分输入8个差分输出 的硬件描述,对应修改到1772时需要减少输出的声音通道。本文件仅对一些全局变量、声音通道、函数等做出声明,具体应用在其他.c文件或.asm文件中。

通过 Sport1 A 从Codec接收数据,并通过 Sport0 A/BSport2 A/B 将音频数据传输至Codec的四个立体声DAC接口。Sport1 接收中断服务程序以在 DMA接收缓存 rx1a_buf 上完成算术运算(arithmetic computations),并将计算结果存放至 DMA传输缓存 tx0a_buf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/******************************************************************************************************
/ /
/ AD1939 - SPORT1 RX INTERRUPT SERVICE ROUTINE /
/ /
/ Receives input data from the AD1939 ADCs via SPORT1 A and transmits processed audio data /
/ back out to the four AD1939 Stereo DACs/Line Outputs through SPORT0 A/B and SPORT2 A/B /
/ /
/ /
/ This Serial Port 1 Recieve Interrupt Service Routine performs arithmetic computations on /
/ the SPORT1 receive DMA buffer (rx1a_buf) and places results to SPORT0 transmit /
/ DMA buffer (tx0a_buf) /
/ /
/ rx1a_buf[2] - DSP SPORT0 A receive buffer - AD1939 ASDATA1 /
/ Slot # Description DSP Data Memory Address /
/ ------ -------------------------------------- -------------------------------------------------- /
/ 0 Internal ADC 0 Left Channel DM(_rx1a_buf + 0) = DM(_rx1a_buf + Internal_ADC_L1) /
/ 1 Internal ADC 0 Right Channel DM(_rx1a_buf + 1) = DM(_rx1a_buf + Internal_ADC_R1) /
/ /
/ rx1b_buf[2] - DSP SPORT0 B receive buffer - AD1939 ASDATA2 /
/ Slot # Description DSP Data Memory Address /
/ ------ -------------------------------------- -------------------------------------------------- /
/ 0 Internal ADC 1 Left Channel DM(_rx1b_buf + 2) = DM(_rx1b_buf + Internal_ADC_L2) /
/ 1 Internal ADC 1 Right Channel DM(_rx1b_buf + 3) = DM(_rx1b_buf + Internal_ADC_R2) /
/ /
/ tx0a_buf[2] - DSP SPORT0 A transmit buffer - AD1939 DSDATA1 /
/ Slot # Description DSP Data Memory Address /
/ ------ -------------------------------------- -------------------------------------------------- /
/ 0 Internal DAC 1 Left Channel DM(_tx0a_buf + 0) = DM(_tx0a_buf + Internal_DAC_L1) /
/ 1 Internal DAC 1 Right Channel DM(_tx0a_buf + 1) = DM(_tx0a_buf + Internal_DAC_R1) /
/ /
/ tx0a_buf[2] - DSP SPORT0 B transmit buffer - AD1939 DSDATA2 /
/ Slot # Description DSP Data Memory Address /
/ ------ -------------------------------------- -------------------------------------------------- /
/ 2 Internal DAC 2 Left Channel DM(_tx0b_buf + 2) = DM(_tx0b_buf + Internal_DAC_L2) /
/ 3 Internal DAC 2 Right Channel DM(_tx0b_buf + 3) = DM(_tx0b_buf + Internal_DAC_R2) /
/ /
/ tx2a_buf[2] - DSP SPORT2 A transmit buffer - AD1939 DSDATA3 /
/ Slot # Description DSP Data Memory Address /
/ ------ -------------------------------------- -------------------------------------------------- /
/ 4 Internal DAC 3 Left Channel DM(_tx2a_buf + 4) = DM(_tx2a_buf + Internal_DAC_L3) /
/ 5 Internal DAC 3 Right Channel DM(_tx2a_buf + 5) = DM(_tx2a_buf + Internal_DAC_R3) /
/ /
/ tx2b_buf[2] - DSP SPORT2 B transmit buffer - AD1939 DSDATA4 /
/ Slot # Description DSP Data Memory Address /
/ ------ -------------------------------------- -------------------------------------------------- /
/ 6 Internal DAC 4 Left Channel DM(_tx2b_buf + 6) = DM(_tx2b_buf + Internal_DAC_L4) /
/ 7 Internal DAC 4 Right Channel DM(_tx2b_buf + 7) = DM(_tx2b_buf + Internal_DAC_R4) /
/ /
******************************************************************************************************/

SPTCL寄存器操作

下方代码是官方示例给出,并经过唐昊整合修改过,其寄存器设置的功能解读和验证如下:

1
2
3
*pSPCTL0 = (SPTRAN | OPMODE | SLEN32 | SPEN_A | SCHEN_A | SDEN_A | SPEN_B | SCHEN_B | SDEN_B);
*pSPCTL1 = (OPMODE | SLEN32 | SPEN_A | SCHEN_A | SDEN_A | SPEN_B | SCHEN_B | SDEN_B);
*pSPCTL2 = (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_CLKSPORTx_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用于接收的要求。

image-20210702110559930

需要尽可能拉高逻辑分析仪的采样频率,如24MHz以上。

image-20210702111535166

1772下I2S的数据深度为16 ~ 24bit,后面的长度都会由0补齐,采集显示设置时可以设置32bit或24bit皆可。

image-20210702113249646
image-20210702113540262

ASRC OF ADAU1772

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

image-20210702111739875

虽然同样是I2S,且LJ的标准和飞利浦I2S的标准都是左端对齐,但是飞利浦的数据会比LRCLK延时一个完整的时钟周期。而标准LJ则是LRCLK、BCLK 和 SDATA 完全对齐。

通信时钟要求

量产版采用统一的 16.6MHz 为2个codecs和1个DSP主芯片提供时钟。

image-20210705151852538

异步设备通信开发顺利的前提要求是两者的通信时钟能否保持一致,即最关键的通信时钟信号(BCLK / LRCLK)是否符合设定要求(3.072MHz / 48KHz)。

image-20210705084133066

使用逻辑分析仪去测量音频设备的输出 帧时钟(LRCLK) 频率,如果其时钟输出结果符合设置的 48KHz 要求,则说明正确。其 位时钟频率要求 应等于 帧时钟长度 * 数据深度 * 2 ,如 帧时钟频率为48KHz的 位时钟频率为 48KHz * 32bits * 2 ,大致等于 3.072 MHz

注意:逻辑分析仪能否清晰测量和指示波形周期,仍需要该设备自身支持较高频率的采样速率,最好采样速率能与需要采样的目标速率成倍数关系。如果速率过低,则会出现采样变形。如下图中的位时钟采样和测量为 3MHz ,中途会偶然出现 3.429MHz

image-20210705095017424
image-20210705095203461

在设置错误时,会出现如下情况:

image-20210705085041206

在21479上设置好I2S的 slave端 代码,被动接收master端音频并转发回给master端,而ADAU1772在使用USBi单独通信设置好通信频率48KHz后,成功与21479实现全双工音频转发。

在评估版用户手册中,提示需要16.625MHz的时钟提供给DSP21479。通过修改量产板上codec的PLL寄存器设置,发现 16.625MHz 无效,设置时不起作用,因为与所提供的时钟频率(16.6MHz)不符。

image-20210705104426587

image-20210705143238320

设置成 16.6MHz 则可以工作,能够顺利采集到数据(暂停时刻查看缓存数据)。

image-20210705143246050

image-20210705143606164

仔细阅读以下寄存器信息。

image-20210705145709904

注意: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_CTRL0PLL_CTRL1 分别构成 PLL分母(denominator)的高位和低位。
  • PLL_CTRL2PLL_CTRL3 分别构成 PLL分子(numerator)的高位和低位。
  • PLL_CTRL4 控制着 PLL类型(整数型/分数型)、输入时钟分频(1 - 4)和 PLL设置的 整数部分
  • PLL_CTRL5 目前只能是 0x000x01

以下为PLL_CTRL4寄存器的位描述:n

image-20210706101429733

至于 CLK_CONTROL 寄存器,主要是数值 0x890x8F 的差别。见下表可知,是关于PLL分频的问题。

image-20210705153843902

通过自行编写的代码,遍历所有可更改的系数进行计算求解,取得小数点后9位精度,并且符合要求的系数如下(含1/2系数):

根据PLL_CTRL4寄存器,ICD=3、IS=8时,该寄存器的值为 01000101 ,即 0x45 。(下图IDC更正为ICD)

image-20210706001243698

以下为不含1/2系数的结果:

根据PLL_CTRL4寄存器,ICD=3、IS=4时,该寄存器的值为 00100101 ,即 0x25

image-20210706001350802
  • CLK_IN mean value is 16.6Mhz
  • LRCLK mean value is 45.92Khz

ANC主板硬件(量产)

以下为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没有,其地址分别为 0x3D0x3E

TWI (I2C)

Codec A Codec B
SCK DPI_P08 DPI_P08
SDA DPI_P07 DPI_P07
ADDR0 +3.3VA AGND
ADDR1 AGND +3.3VA

I2S

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 /

image-20210702100716720

image-20210702100920210image-20210702101004870

SPI

FLASH
SPI_SOMI DPI_P01
SPI_SIMO DPI_P02
SPI_CLK DPI_P03
SPI_SCS DPI_P05

参考

  1. I2S bus specification

  2. I2S

  3. 【音频】I2S协议详解

  4. I2S/PCM

存储类别

即 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 否;此加法产生一个新的算术值,但不是一个对象

参考

  1. 《C Primer Plus》, Stephen Prata
  2. 左值
  3. C语言左值与右值详解

c.biancheng.net

程序运行时常会碰到一些异常情况,例如:

  • 做除法的时候除数为 0;
  • 用户输入年龄时输入了一个负数;
  • 用 new 运算符动态分配空间时,空间不够导致无法分配;
  • 访问数组元素时,下标越界;打开文件读取时,文件不存在。

这些异常情况,如果不能发现并加以处理,很可能会导致程序崩溃。

所谓“处理”,可以是给出错误提示信息,然后让程序沿一条不会出错的路径继续执行;

也可能是不得不结束程序,但在结束前做一些必要的工作,如将内存中的数据写入文件、关闭打开的文件、释放动态分配的内存空间等。

一发现异常情况就立即处理未必妥当,因为在一个函数执行过程中发生的异常,在有的情况下由该函数的调用者决定如何处理更加合适。 尤其像库函数这类提供给程序员调用,用以完成与具体应用无关的通用功能的函数,执行过程中贸然对异常进行处理,未必符合调用它的程序的需要。

此外,将异常分散在各处进行处理不利于代码的维护,尤其是对于在不同地方发生的同一种异常,都要编写相同的处理代码也是一种不必要的重复和冗余。如果能在发生各种异常时让程序都执行到同一个地方,这个地方能够对异常进行集中处理,则程序就会更容易编写、维护。

鉴于上述原因,C++ 引入了异常处理机制。其基本思想是:

函数 A 在执行过程中发现异常时可以不加处理,而只是“拋出一个异常”给 A 的调用者,假定为函数 B。

拋出异常而不加处理会导致函数 A 立即中止,在这种情况下,函数 B 可以选择捕获 A 拋出的异常进行处理,也可以选择置之不理。如果置之不理,这个异常就会被拋给 B 的调用者,以此类推。

【也就是说,在C++中,异常发生时,是否处理可以由函数的上一级调用函数去决定,如果选择不处理则会将该异常交给更上一级函数去决断。】

如果一层层的函数都不处理异常,异常最终会被拋给最外层的 main 函数。main 函数应该处理异常。如果main函数也不处理异常,那么程序就会立即异常地中止。

C++异常处理基本语法

threw语法定义

C++ 通过 throw 语句和 try-catch 语句实现对异常的处理。throw 语句的语法如下:

1
throw  /*表达式*/;

该语句拋出一个异常。异常是一个表达式,其值的类型可以是 基本类型,也可以是

try-catch语法定义

try-catch 语句的语法如下:

1
2
3
4
5
6
7
8
9
10
try {
//语句组
}
catch(/* 异常类型 */) {
//异常处理代码
}
...
catch( /* 异常类型 */) {
//异常处理代码
}

catch 可以有多个,但至少要有一个。

不妨把 try 和其后{}中的内容称作“try块”,把 catch 和其后{}中的内容称作“catch块”。

try-catch执行过程

try-catch 语句的执行过程是:

  • 执行 try 块中的语句,如果执行的过程中没有异常拋出,那么执行完后就执行最后一个 catch 块后面的语句,所有 catch 块中的语句都不会被执行;
  • 如果 try 块执行的过程中拋出了异常,那么拋出异常后立即跳转到第一个“异常类型”和拋出的异常类型匹配的 catch 块中执行(称作异常被该 catch 块“捕获”),执行完后再跳转到最后一个 catch 块后面继续执行。

例程

例如下面的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;

int main(){
double m ,n;
cin >> m >> n;
try {
cout << "before dividing." << endl;
if( n == 0) throw -1;
else cout << m / n << endl;
cout << "after dividing." << endl;
}
catch(double d){
cout << "catch(double) " << d << endl;
}
catch(int e){
cout << "catch(int) " << e << endl;
}
cout << "finished" << endl;
return 0;
}

程序的运行结果如下:

1
2
3
4
5
9 6
before dividing.
1.5
after dividing.
finished

说明当 n 不为 0 时,try 块中不会拋出异常。因此程序在 try 块正常执行完后,越过所有的 catch 块继续执行,catch 块一个也不会执行。

程序的运行结果也可能如下:

1
2
3
4
9 0
before dividing.
catch\(int) -1
finished

当 n 为 0 时,try 块中会拋出一个整型异常。拋出异常后,try 块立即停止执行。该整型异常会被类型匹配的第一个 catch 块捕获,即进入catch(int e)块执行,该 catch 块执行完毕后,程序继续往后执行,直到正常结束。

如果拋出的异常没有被 catch 块捕获,例如,将catch(int e),改为catch(char e),当输入的 n 为 0 时,拋出的整型异常就没有 catch 块能捕获,这个异常也就得不到处理,那么程序就会立即中止,try...catch 后面的内容都不会被执行。

能够捕获任何异常的 catch 语句

如果希望不论拋出哪种类型的异常都能捕获,可以编写如下 catch 块:

1
2
3
catch(...) {
//...
}

这样的 catch 块能够捕获任何还没有被捕获的异常。例如下面的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;

int main(){
double m, n;
cin >> m >> n;
try {
cout << "before dividing." << endl;
if (n == 0) throw - 1;
else if (m == 0) throw - 1.0;
else cout << m / n << endl;
cout << "after dividing." << endl;
}
catch (double d) {
cout << "catch (double)" << d << endl;
}
catch (...) {
cout << "catch (...)" << endl;
}
cout << "finished" << endl;
return 0;
}

程序的运行结果如下:

1
2
3
4
9 0
before dividing.
catch (...)
finished

当 n 为 0 时,拋出的整型异常被catchy(...)捕获。

程序的运行结果也可能如下:

1
2
3
4
0 6
before dividing.
catch (double) -1
finished

当 m 为 0 时,拋出一个 double 类型的异常。虽然catch (double)catch(...)都能匹配该异常,但是catch(double)是第一个能匹配的 catch 块,因此会执行它,而不会执行catch(...)块。

由于catch(...)能匹配任何类型的异常,它后面的 catch 块实际上就不起作用,因此不要将它写在其他 catch 块前面。

异常的再拋出

如果一个函数在执行过程中拋出的异常在本函数内就被 catch 块捕获并处理,那么该异常就不会拋给这个函数的调用者(也称为“上一层的函数”);如果异常在本函数中没有被处理,则它就会被拋给上一层的函数。例如下面的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <string>
using namespace std;

class CException{
public:
string msg;
CException(string s) : msg(s) {}
};

double Devide(double x, double y){
if (y == 0) throw CException("devided by zero");
cout << "in Devide" << endl;
return x / y;
}

int CountTax(int salary){
try {
if (salary < 0) throw -1;
cout << "counting tax" << endl;
}
catch (int){
cout << "salary < 0" << endl;
}
cout << "tax counted" << endl;
return salary * 0.15;
}

int main(){
double f = 1.2;
try {
CountTax(-1); //此处会报错,立即抛出错误被catch(int)捕获,并终止运行
f = Devide(3, 0); //此处也会报错,在”devided by zero“处立即停止,且没有返回值返回赋给f
cout << "end of try block" << endl;
}
catch (CException e){
cout << e.msg << endl;
}
cout << "f = " << f << endl;
cout << "finished" << endl;
return 0;
}

程序的输出结果如下:

1
2
3
4
5
salary < 0
tax counted
devided by zero
f=1.2
finished

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <string>
using namespace std;

int CountTax(int salary){
try {
if( salary < 0 ) throw string("zero salary");
cout << "counting tax" << endl;
}
catch (string s ){
cout << "CountTax error : " << s << endl;
throw; //单写一个throw会把最近的错误传给上一级进行处理
}
cout << "tax counted" << endl;
return salary * 0.15;
}

int main(){
double f = 1.2;
try {
CountTax(-1);
cout << "end of try block" << endl;
}
catch(string s) {
cout << s << endl;
}
cout << "finished" << endl;
return 0;
}

程序的输出结果如下:

1
2
3
CountTax error:zero salary
zero salary
finished

第 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++标准异常类

C++ 标准库中有一些类代表异常,这些类都是从 exception 类派生而来的。常用的几个异常类如图 1 所示。

img

图1:常用的异常类

bad_typeid、bad_cast、bad_alloc、ios_base::failure、out_of_range 都是 exception 类的派生类。C++ 程序在碰到某些异常时,即使程序中没有写 throw 语句,也会自动拋出上述异常类的对象。这些异常类还都有名为 what() 的成员函数,返回字符串形式的异常描述信息。使用这些异常类需要包含头文件 <stdexcept>

下面分别介绍以上几个异常类。本节程序的输出以 Visual Studio 2010为准,Dev C++ 编译的程序输出有所不同。

1) bad_typeid

使用 typeid 运算符时,如果其操作数是一个多态类的指针,而该指针的值为 NULL,则会拋出此异常。

2) bad_cast

在用 dynamic_cast 进行从多态基类对象(或引用)到派生类的引用的强制类型转换时,如果转换是不安全的,则会拋出此异常。程序示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <stdexcept>

using namespace std;

class Base{
virtual void func() {}
};

class Derived : public Base{
public:
void Print() {}
};

void PrintObj(Base & b){
try{
Derived & rd = dynamic_cast <Derived &>(b);
rd.Print();
}
catch (bad_cast & e){
cerr << e.what() << endl;
}
}

int main(){
Base b;
PrintObj(b);
return 0;
}

程序的输出结果如下:

1
Bad dynamic_cast!

在 PrintObj 函数中,通过 dynamic_cast 检测 b 是否引用的是一个 Derived 对象,如果是,就调用其 Print() 成员函数;如果不是,就拋出异常,不会调用 Derived::Print

3) bad_alloc

在用 new 运算符进行动态内存分配时,如果没有足够的内存,则会引发此异常。程序示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <stdexcept>

using namespace std;

int main(){
try {
char * p = new char[0x7fffffff];
}
catch (bad_alloc & e){
cerr << e.what() << endl;
}
return 0;
}

程序的输出结果如下:

1
2
bad allocation
ios_base::failure

在默认状态下,输入输出流对象不会拋出此异常。如果用流对象的 exceptions 成员函数设置了一些标志位,则在出现打开文件出错、读到输入流的文件尾等情况时会拋出此异常。此处不再赘述。

4) out_of_range

用 vector 或 string 的 at 成员函数根据下标访问元素时,如果下标越界,则会拋出此异常。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <stdexcept>
#include <vector>
#include <string>

using namespace std;

int main(){
vector<int> v(10);
try {
v.at(100) = 100;
}
catch (out_of_range & e) {
cerr << e.what() << endl;
}
string s = "hello";
try {
char c = s.at(100);
}
catch (out_of_range & e) {
cerr << e.what() << endl;
}
return 0;
}

程序的输出结果如下:

1
2
invalid vector subscript
invalid string position

如果将v.at(100)换成v[100],将s.at(100)换成s[100],程序就不会引发异常(但可能导致程序崩溃)。因为 at 成员函数会检测下标越界并拋出异常,而 operator[] 则不会。operator[] 相比 at 的好处就是不用判断下标是否越界,因此执行速度更快。