F28069M开发板笔记

所需的相关程序及代码库(头文件、样例):

  1. CCSTUDIO(CCS):代码编写及程序烧录。
  2. C2000WARE:代码与硬件关联所需的各种头文件及样例。
  3. MOTORWARE:用于电机控制的开发板的头文件及样例。

F28069M开发板

ti官网-LAUNCHXL-F28069M 包含如下信息:

这些信息对于代码开发细节完全不够,还需要找到处理器的相关文档,找到ti官网-TMS320F28069中的User guide文档TMS320x2806x Microcontrollers Technical Reference Manual (Rev. I)包含了所有的寄存器的定义的赋值含义,对后续看样程非常重要,如果有不理解的变量全部能在里面搜索到。

样程使用方法

进入到 C2000 的安装目录下,{C2000路径}/device_support/f2806x/ 就是开发板对应的项目位置,我们将其记为 <base>,下面分析其中每个文件的作用:

  • <base>/docs/ :参考文档,USER_GUIDE 为开发者手册,包含对本项目架构的介绍及使用方法。
  • <base>/headers/:底层外设头文件 (Peripheral header files),包含对开发板中各项内存参数位置的定义,要集成到项目中需要使用其中一部分的定义,项目中更多地使用 common/ 下的头文件定义(也是间接调用此头文件)
  • <base>/common/:样例中的常用头文件定义,样例中调用的头文件通常来自于此
    • https://ecnhf41t16x1.feishu.cn/sync/TzS3d9wocsLU6XbTWrKcNqYNnAe

    • source/:常用的 *.c 文件

    • cmd/:包含一些 .cmd 文件,他们都是指定如何将编译后的代码和数据映射到处理及的内存或闪存中,二者的区别在于,*RAM* 不将编译后的代码烧录进闪存,断电后就会清空编译的代码;不带RAM 则是将代码烧录进闪存中,从而可以断电后,启动即使执行之前的代码(XIP, Execute in place)

  • <base>/examples/:样例文件
    • c28/:CCS中的项目文件
    • cla:基于CLA中的C编译器(Control Law Accelerator, CLA 控制律加速器,TI板的功能,一个完全可编程的独立32位浮点硬件加速器),主要用于加速一些数学密集型计算

外设中断PIE处理流程

参考寄存器文档中的1.6节 Peripheral Interrupt Expansion(PIE), 有如下几个重要的流程图:

img1 img2
中断生成流程图1
中断生成流程图2

首先开发板将所有的外设中断信息划分为了12组每个组最多可处理8个中断, 简记为 x.y, x表示从属的组号, y表示每组中的编号, 中断是否被启用看IFR(Interrupt Flag Register)是否被激活, 中断是否可以被接受看IER(Interrupt Enable Register), 前面带有PIE的就是在PIE部分处理的, 没有PIE的就是直接写在CPU内核中的寄存器. 左图展示了中断信号到达CPU的流程, 主要分为两大块:

  1. PIE部分: 中断首先激活PIEIFR, 判断是否启用了对应的PIEIER, 如果启用则通过MUX(multiplexer, 多路复用)转为按照组别转化为INTx (如果组x里面有一个中断被激活, 则对应的INTx就会被激活), 判断当前的信号是否正在被处理中PIEACK=1, 如果没被处理, 则自动置PIEACK=0, 进入CPU处理部分
  2. CPU部分: 获取到PIE处理后的INTx变量后, 类似的逻辑, 首先激活对应的IFR, 判断是否启用了对应的IER, 如果启用则通过MUX转化为统一中断指令, 判断INTM (全局中断激活)是否为0, 如果是0则处理中断, 中断的处理顺序为: 按照(x,y)二元组从小到大的顺序进行, 对于每个中断处理, 从PIEVecTable (这里Vector表示的就是C语言中的函数句柄)找到对应的函数所处内存中的位置, 对其进行执行.

综上, 处理中断的思路就是:

  1. 初始化CPU中断, 包括:
    1. DINT; 关闭全局中断;
    2. IER, IFR 清空CPU中断信息;
  2. 初始化PIE中断, 包括:
    1. InitPieCtrl(); 清空PIE控制中断, 包括PIEIER, PIEIFR;
    2. InitPieVectTable(); 清空PIEVecTable;
  3. 用户在自定义中断, 包括:
    1. 实现中断函数PieVectTable.[中断名称] = &func(中断函数细节: 函数类型为interrupt void; 在函数结束时, 记得将对应的中断处理寄存器进行更新PieCtrlRegs.PIEACK.bit.ACKx = 1;, 否则会一直等待当前处理, 不会触发下一个中断啦), 将中断函数配置到PieVectTable中的对应位置;
    2. 初始化各种所需的外设参数;
  4. 启动中断检测:
    1. CPU: IER |= M_INTx; 启动IER中对应的Group x检测;
    2. PIE: PieCtrlRegs.PIEIERx.bit.INTy = 1; 启动x.y对应的PIE中断检测;
    3. EINT; 启动全局中断检测.
    4. ERTM; 允许对中断进行DEBUG.

外设与中断的关系图如下 (更详细的外设与中断的关系表请见177面的Table 1-119, PIE Vec关系请见178面的Table 1-120):

中断关系

通用输入输出接口GPIO使用方法

GPIO(General-Purpose Input/Output)就是板子上的各种针脚, 它们功能包含

  1. 作为输入/输出(默认全部都是input状态, 输入指的是测量电压变化, 输出指的是能够发出相对低/高电平), 对于用于输入的接口, 当我们加入一个采样窗口(sample window)对信号进行采样就可以对信号进行接收.
  2. 每个GPIO还具有多路复用的功能, 通过MUX可以选择三种不同的外设信号, 不同接口具有不同的外设功能, 功能包含:产生中断(PIE), 做联合测试JTAG(Joint Test Action Group), 接收ePWM, 每个接口的外设功能请见122面的表1-64.

GPIO接口被分为三大块GPIO0~31属于接口A, GPIO32~58属于接口B, 还有一个分析接口AIO0~15, 它们分别简称为GPIOA (GPA), GPIOB (GPB), AIOx, 前两个又能统称为GPX(X=A或B), 下面我们不考虑AIO接口, 用X.y的格式表示从属第X组的接口y.

GPIO作为数字输出接口

由于默认是输入接口, 下面介绍输出接口的配置方法以及操作输出信号的方法:

  1. 配置接口模式, 通过GpioCtrlRegs.GPXDIR.bit.GPIOy = 1将该接口设置为输出.
  2. 输出模式包含如下操作:
    1. GpioDataRegs.GPXDAT.bit.GPIOy直接对当前接口值进行修改, 0表示低电平, 1表示高电平. 当设置为输出时, 默认为低电平状态, 不建议直接对这个寄存器进行修改, 而是使用下面这些代表操作的寄存器 (因为多次直接对其修改, 可能会导致上下文状态变化产生延迟).
    2. 下述寄存器只有赋值为1时才有作用, 在执行完成后会重新置为0, 并且操作不会干扰其他寄存器状态的延迟:
      • GpioDataRegs.GPXSET.bit.GPIOy = 1 将接口设置为高电平;
      • GpioDataRegs.GPXCLEAR.bit.GPIOy = 1将接口设置为低电平;
      • GpioDataRegs.GPXTOGGLE.bit.GPIOy = 1将接口状态进行变化, 做一次异或操作.

闪存Flash使用方法

烧到flash在代码上非常简单(如果不追求速度), 首先要了解烧录板子的逻辑, 编译逻辑:

  1. CCS中的C语言编译器将我们写的代码编译完成后, 得到.obj文件, 该文件包含了逻辑处理, 但还没有分配内存位置;
  2. 通过TI版中的链接文件(*.cmd), 将所有的目标文件分配对应的位置, 得到可执行文件.out, 对板子进行烧录.

Cmd文件简单理解可以参考TI 链接器命令文件入门

DSP开发板执行逻辑:

  1. 在DSP上电或者复位后, 处理器会跳转到启动地址.reset位置(在*.cmd中进行了定义)
  2. 启动main函数, main函数的执行位置取决于.reset定义的位置, 也就是说函数可以在Flash中运行的, 只是相对于RAM读取速度会降低, 后面就是正常执行函数了.

因此我们要将程序烧录到Flash只需修改*.cmd文件, 将内存分配设置为Flash就行, 如果我们还想将某些重复调用的函数(通常是interrupt函数)从Flash转移到RAM中, 就需要加一些额外的内存转移和声明代码了.

Flash与RAM烧录方法

在例程的工程项目中一定可以找到F28069.cmd或者28069_RAM_lnk.cmd, 它们分别是烧录到Flash和RAM中的, RAM代码可以直接通过修改*.cmd文件转为Flash, 反之, 在你的代码中没有Flash转移到RAM的前提下, 也是可以直接修改*.cmd文件达到的.

首先我们找到这两个文件的所处文件夹: <base>\device_support\f2806x\common\cmd, 修改*.cmd方法很简单, 右键项目名称 -> Properties -> [Linker command file] Browse... (这里一定要选Browse, 不要点下三角选择, 因为CCS自带的cmd文件不正确, 要选择C2000中的cmd) -> 进入<base>\device_support\f2806x\common\cmd文件夹 -> 选择你想要的.cmd文件打开, 就可以看到左侧原来的cmd文件被Exclude掉了, 新的cmd文件被加进去.

如果我们想重新启动之前的cmd, 只需把不用的cmd文件右键Exclude from Build, 再将要用的cmd解除exclude就行了.

完成上述cmd文件修改后, 直接烧录程序就可以将文件烧录到RAM或Flash了, 检测是否烧到Flash只需要把数据线拔掉重插, DSP就会自动执行Flash中的程序, 看看是否和你烧录的一致.

注意: 如果使用DELAY_US(delay)对程序加入延迟, 就必须使用下面的memcpy将该延迟函数转移到RAM中去.

将函数从Flash转移到RAM提高读取速度

代码在Flash中运行速度不及RAM的, 但我们可以手动将某些反复调用的函数(main函数不能通过这个方法放)从Flash放到RAM中, 方法如下:

// 假设我们定义的函数名为 myfunc
#pragma CODE_SECTION(myfunc, "ramfuncs");  // 将函数设置到ramfuncs section中 (这个ramfuncs定义在F28069.cmd文件中)
// 从F28069.cmd文件中导入这几个共用变量
extern Uint16 RamfuncsLoadStart;
extern Uint16 RamfuncsRunStart;
extern Uint16 RamfuncsLoadSize;

// 在调用myfunc之前执行如下内存拷贝, 如果是中断函数, 就在EINT之前就行
// 这个拷贝能将定义在ramfuncs section中的函数, 全部从Flash拷贝到RAM中去
memcpy(&RamfuncsRunStart, &RamfuncsLoadStart, (Uint32)&RamfuncsLoadSize);

这样程序在调用定义在ramfuncs中的函数时, 就会直接去RAM中找, 而非Flash了, 因此必须在调用前执行memcpy, 才能避免函数无法执行的问题.

Flash状态变换(降低功率)

通过如下代码可以控制Flash的开关:

注意: 如果是在子函数中进行的状态设置, 该函数必须在RAM中, 否则会报错. 这几种状态没看出来有什么区别, 文档中说是可以降低功率. 如果将Flash设置为待机, 下次从Flash中读取函数时候又会重启Flash, 但看不出什么延迟.

EALLOW;
FlashRegs.FPWR.bit.PWR = FLASH_SLEEP;  // 将Flash设置为休眠模式
FlashRegs.FPWR.bit.PWR = FLASH_STANDBY;  // 将Flash设置为待机模式
EDIS

增强脉冲宽度调制器ePWM使用方法

ePWM(enhanced pulse width modulator)就是01信号脉冲, 可以通过宽度变换将数字波转换为模拟波, 是一种对波形的数模转换(DAC, digital-to-analog).

该开发板总共包含8个ePWM接口, 但是通过针脚引出去的只有13个(ePWM1~6, ePWM7B), 每个ePWM接口分为A,B两个波形分别对应两个接口, ePWM功能很多, 下面对其中的计数器和中断触发机制进行介绍.

PWM流程图

计数器 (Time-base, TB)

参考文档中251页3.2.2节内容, 位于开发板中的每个ePWM都自带一个计数器, 称为PWMCTR (PWM counter), 首先介绍这块重要的英文缩写: SYNC同步, O (Output, 输出), I (Input, 输入), PHS (Phase, 相位), PRD (Period, 周期), CTR (counter, 计数器), CTL (control, 控制器).

每个计数器包含以下重要参数:

下文中的计数器同步只有在启动相位PHSEN时候才有意义, 同步就是当触发同步条件时, 将当前的计数器重置到相位对应的值上.

  1. SYNCI: 同步输入, 从下左图中可以看出, 第n个ePWM的同步输入就是第n-1的SYNCO的同步状态.
  2. SYNCO: 同步输出, 有四种选择:
    1. TB_SYNC_INTB_SYNC_IN: 跳过当前计数器, 也就是直接将同步输入的信息继续往后面的ePWM传递;
    2. TB_CTR_ZERO: 如果当前计数器为0时, 则产生同步信息;
    3. TB_CTR_CMPB: 这个和计数器比较 (coutner-compare) 部分有关, 还不清楚具体是什么;
    4. TB_SYNC_DISABLE: 直接关闭输出同步.
  3. PHSEN: 是否启动相位, 如上文所述, 只有启动相位计数器的同步才有作用.
  4. TBPHS: 相位设置范围是uint16.
  5. TBPRD: 计数器周期, 也就是计数器的上界, 范围也是uint16.
  6. CTRMODE: 计数器模型, 有三种可选 (见下右图):
    1. TB_COUNT_UP: 向上计数模式, 一直增加当前计数器, 当到达到上界(周期)时, 直接重置为0.
    2. TB_COUNT_DOWN: 向下计数模式, 一直减少当前计数器, 当到达到0时, 直接重置为上界(周期).
    3. TB_COUNT_UPDOWN: 向上后向下计数模式, 一直增加当前计数器, 当到达到上界时, 变为向下计数模式, 当到达0时, 再变为向上计数模式, 以此往复.
  7. SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC: ePWM的全局时间同步, 当开启时候, 则将全部的ePWM同步到上边缘, 在设置上文内容前需要先关闭全局时间同步, 配置完成后, 再打开.
img1 img2
ePWM同步链逻辑
计数器三种模式对应的波形图

一个简单例子:

EALLOW;
SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 0;  // 停止全局同步
EDIS;

InitEPwm1Gpio();  // 将Gpio接口初始化为EPWM信号
EPwm1Regs.TBCTL.bit.SYNCOSEL = TB_CTR_ZERO;  // 设置EPWM输出
EPwm1Regs.TBCTL.bit.PHSEN = 1;  // 启用相位
EPwm1Regs.TBPHS.half.TBPHS = 0;  // 设置相位大小
EPwm1Regs.TBPRD = 0x1000; // 设置周期(计数器上界)大小
EPwm1Regs.TBCTL.bit.CTRMODE = TB_COUNT_UP;  // 设置计数器模式

EALLOW;
SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 1;  // 启动全局同步
EDIS;

计数比较器 (Counter-Compare, CC)

通过与计数器当前的数字进行比较, 当数字与当前端口设定相同时, 会产生一次事件, 该事件会向下文中的AQ中传递, 用于控制PWM波生成. 每个EPWM端口可以生成A,B两种不同的波, 将第x个端口输出的波分别记为EPWMxA, EPWMxB, 处理逻辑图如下所示:
红色框出的部分为EPWMxA波处理流程

动作限定符 (Action-Qualifier, AQ)

动作限定符AQ需要和上述的比较器端口相配合, 从而控制PWM波形的构造生成, AQ可以被视为一个可编程的事件开关, 它的输入为各种不同的事件, 输出为PWM波的0,1信号.

AQ输入与输出(从上到下的输入事件分为别: 计数器到达周期值(上界), 计数器归零(下界), 计数器等于CMPA, 计数器等于CMPB, 由软件产生的事件) ePWM占空比控制方法1(271页)
AQ输入与输出(从上到下的输入事件分为别: 计数器到达周期值(上界), 计数器归零(下界), 计数器等于CMPA, 计数器等于CMPB, 由软件产生的事件)
ePWM占空比控制方法1(271页)

中断触发 (Event trigger, ET)

参考文档3.2.8第292页, 这个比较简单, 只需要根据计数器的状态触发中断即可, 包含以下参数:

  1. INTSEL: 触发模式选择, 包含如下这几种选项
    1. ET_CTR_ZERO: 当计数器为0时;
    2. ET_CTR_PRD: 当计数器达到上界(周期)时;
    3. ET_CTR_PRDZERO: 当计时器为0或达到上界(周期)时;
    4. ET_CTR(U/D)_CMP(A/B): 与计数器比较相关的, 暂不清楚.
  2. INTEN: 启用中断.
  3. INTPRD: 中断产生周期, 包含如下这几种选项
    1. ET_DISABLE: 不产生(默认);
    2. ET_1ST, ET_2ND, ET_3RD: 遇到一/二/三个触发就产生一次中断;

一个简单例子:

EPwm1Regs.ETSEL.bit.INTSEL = ET_CTR_PRD;  // 当达到上界时产生中断
EPwm1Regs.ETSEL.bit.INTEN = 1;  // 启动中断
EPwm1Regs.ETPS.bit.INTPRD = ET_1ST;  // 发送周期为1

控制器区域网络CAN使用方法

该开发板包含32个mailbox, 一个mailbox可以定义消息的收或发, 每个消息的长度为8byte也就是64bit, 每个消息均定义在mailbox中, 这里就讨论基础的ecan消息, 这部分划分的内存如下

img1 img2
ECAN参数内存位置分布
中断逻辑

比较重要的参数包含, 以下参数每个mailbox都有其各自的对应位:

  1. Mailbox Enable, CANME: 使能位;
  2. Mailbox Direction, CANMD: 发送1/接收0, 默认为接收;
  3. Transmission Request Set, CANTRS: 发送请求位, 1表示启动一次信息发送, 默认为0;
  4. Transmission Acknowledgement, TA: 发送成功位, 如果是0则表示发送成功, 通过while死循环可以判断消息是否发送成功;
  5. Received Message Pending, CANRMP: 收到消息等待位, 此位需要手动判断赋值为1 (一般在can接收中断中赋值), 表示收到一次消息, 读取改值没有意义;
  6. Master Control, CANMC: CAN模式控制, 可以切换为self-test-mode;
  7. Mailbox Interrupt Mask, CANGIM: CAN中断使能位;
  8. Mailbox Interrupt Mask, CANMIM: 1表示启动该mailbox的中断, 当收发消息会触发中断 (默认为0);
  9. Mailbox Interrupt Level, CANMIL: can相关的中断仅有两个, 默认为INT0CAN, 当设置为1时, 触发INT1CAN;

每个mailbox的信息:

  1. Message Identifier, MSGID: 消息对应的id编号, 对于接收的mailbox, 只会接收对应ID的信息;
  2. Message Control, MSGCTRL: 对于当前mailbox的配置, 修改当前信息长度data length code(DLC)等;
  3. Message Data Low: mailbox信息的前4bytes;
  4. Message Data High: mailbox信息的后4bytes.

这里不再演示样例中的2806xECanBack2Back自我测试模式, 假设我们有一个CAN交换器, 通过两根导线链接到dsp板子的can通讯针脚上, 通过如下代码实现一个简单的信息交换(用到的依赖文件与ECanBack2Back相同):

#include "DSP28x_Project.h"

Uint32 count = 0;  // 记录收到多少次消息, 可以DEBUG中查看

void ecan_transmit(void) {  // 信息发送
    ECanaRegs.CANTRS.bit.TRS0 = 1;  // 启动消息发送
    while (ECanaRegs.CANTA.bit.TA0 != 1);  // 判断消息是否发送
    GpioDataRegs.GPBTOGGLE.bit.GPIO34 = 1;  // 将LED灯切换一次亮灭
}

interrupt void ecan_receive(void) {  // CAN中断信息接收
    count += 1;
    ecan_transmit();
    ECanaRegs.CANRMP.all = 0x00000002;  // 确认信息已接收 (才会启动下一次接收)
    PieCtrlRegs.PIEACK.bit.ACK9 = 1;  // 确认中断已处理 (才会产生下一次中断)
}

void main(void) {
    // Initialization
    InitSysCtrl();
    DINT;
    IER = 0x0000;
    IFR = 0x0000;
    InitPieCtrl();
    InitPieVectTable();
    InitECanGpio();
    InitECana();
    // ECanA, 这里总共就设置了两个mailbox, mailbox0为发送, mailbox1为接收
    // transmit
    ECanaMboxes.MBOX0.MSGID.all = 0x12345678;  // 设置发送ID
    ECanaMboxes.MBOX0.MSGCTRL.bit.DLC = 8;  // data length code
    ECanaMboxes.MBOX0.MDL.all = 0x12345678;  // 设置发送信息(前8bytes)
    ECanaMboxes.MBOX0.MDH.all = 0x87654321;  // 设置发送信息(后8bytes)
    // receive
    ECanaMboxes.MBOX1.MSGID.all = 0x00000000;  // 设置接收ID
    // configure
    ECanaRegs.CANMD.all = 0x00000002;  // 配置发送/接收
    ECanaRegs.CANME.all = 0x00000003;  // 启动使能
    // LED
    EALLOW;
    GpioCtrlRegs.GPBDIR.bit.GPIO34 = 1;  // 启动红色LED灯的GPIO接口(默认为关闭)
    EDIS;
    // Interrupt
    EALLOW;
    ECanaRegs.CANMIM.all = 0x00000002;  // 启动接收CAN的中断
    PieVectTable.ECAN0INTA = &ecan_receive;  // 设置中断函数
    ECanaRegs.CANGIM.bit.I0EN = 1;  // 启动INT0CAN中断
    EDIS;

    IER |= M_INT9;  // 启动CPU中断
    PieCtrlRegs.PIEIER9.bit.INTx5 = 1;  // 启动PIE终端
    EINT;  // 启动全局中断
    ERTM;  // 允许DEBUG中断

    ecan_transmit();  // 先发送一次信息
    while(1);
}

单个DSP板: 中断函数处于RAM中, 收发延迟1000us, 处于Flash中, 收发延迟1600us. 因此还是推荐将函数转移到RAM中.

参考开发手册中p177页的Interrupt Vector Table 和 p106页CPU Timer相关的TCR (Time Counter)配置.

初始化

样例位于 <base>/examples/c28/timed_led_blink,开发板程序一般需要如下的配置:

#include "DSP28x_Project.h"  // 引入相关定义的头文件,此文件位于 <base>/common/include/ 下

main 函数中需要初始化以下信息:

// Step1. 初始化系统控制, in F2806x_SysCtrl.c
InitSysCtrl();
// Step2. 将GPIO进行初始化GPIO(General Purpose Input/Output)为通用输入输出, in F2806x_Gpio.c
InitGpio();  // 好像不是必要的,因为都是默认初始化为0了
// Step3. 初始化PIE向量表,加入所需的中断触发函数
DINT;  // 停止(Disable)中断检测(INT为Interrupt缩写)
InitPieCtrl();  // 初始化PIE(Peripheral interrupt expansion)外设终端扩展,用于管理终端信息的
IER = 0x0000;  // Interrupt Enable Register 禁用所有CPU中断并清空中断标志
IFR = 0x0000;  // 同上,这两个定义在<base>/headers/include/F2806x_Device.h,都是CPU的内参
InitPieVectTable();  // 初始化PIE向量表,向量表指向中断服务程序
// 加入中断程序,修改带保护的寄存器流程如下
EALLOW;  // 对受保护的寄存器设置为可写状态
PieVectTable.TINT0 = &func;  // 将中断服务与自定义的函数地址进行绑定,中断服务的类型可以在F2806x_PieVect.h中PIE_VECT_TABLE查看
EDIS;  // 关闭可写状态
// Step4. 初始化所有需要用到的驱动外设(本例中只用到CPU计时器)
InitCpuTimers();  // 初始化当前的CPU计时器,来自 F2806x_CpuTimers.c
ConfigCpuTimer(&CpuTimer0, 80, 100000);  // 设定CPU计时器中断的触发频率,80MHz的CPU频率,100ms触发周期
CpuTimer0Regs.TCR.all = 0x4000;  // 这里本质上就是仅将TIE设置为1,表示启动中断,并将TSS状态重置

CpuTimer0Regs.TCR.bit.TSS = 0;  // 将TSS状态重置
CpuTimer0Regs.TCR.bit.TIE = 1;  // 这两行和上面写法等价,启动中断功能(CPU-Timer Interrupt Enable)

这里查看InitCpuTimers()的源代码可以发现,其将CpuTimer0Regs.TCR.TSS设置为1,暂停了CPU计时功能,地18行则是将其置为0重新启动了计时功能,并将TIE设置为1。

自定义内容

经过上面4步的初始化设置后,下面就是用户自定义的各种功能,比如我们这想控制LED灯开关,我们从板子的参考文档的第14页可以发现下图:

img1 img2
LED相关的电路图
LED灯的位置

上图中LED灯为D9和D10(红色中间和蓝色上面),如果我们想通过CPU控制其亮灭,那么就要找到对应的GPIO编号,通过调整其电压高低,从而控制亮灭,默认情况下GPIO应该是Input模式,应该相当于一个通路,想要产生电势差就需要改为Output模型:

EALLOW;  // 修改寄存器的固定流程,关闭写保护
GpioCtrlRegs.GPBDIR.bit.GPIO34 = 1;  // GPBDIR表示GPIO中B组的Direction Register,将其打开相当于进入Output模式,进入Output模型默认就输出了低电平(因为debug灯亮了)
GpioDataRegs.GPBSET.bit.GPIO34 = 1;  // high voltage (light off)
GpioDataRegs.GPBCLEAR.bit.GPIO34 = 1;  // low voltage (light on)
GpioDataRegs.GPBTOGGLE.bit.GPIO34 = 1;  // high/low voltage (off/on)
EDIS;  // 打开写保护

控制GPIO电平高低方法是通过修改寄存器GPBSET, GPBCLEAR, GPBTOGGLE这三个均为1bit变量(从属于GpioCtrlRegs),含义分别为启动高电平、启动低电平、切换当前电平,因此如果我们想要灯光闪烁那么就只需要将GPBTOGGLE设置为1即可,因此我们只需要把中断触发函数(上文中step3中的func)写成如下形式即可:

__interrupt void cpu_timer0_isr(void) {  // 有趣的是,这里面的寄存器修改就不需要加EALLOW和EDIS了
    CpuTimer0.InterruptCount++;  // 可有可无的计数器
    GpioDataRegs.GPBTOGGLE.bit.GPIO34 = 1;  // 切换电平
    PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;  // 为了接受更多的中断信号,需要将当前信号进行确认,由于TINT0从属于GROUP1,因此我们需对GROUP1进行确认(这里用或操作应该好些)
    PieCtrlRegs.PIEACK.bit.ACK1 = 1;  // 与上行结果相同
}

上述代码中,我们还需要确认TINT0从属的组别,进入到F2806x_PieVect.h下,找到TINT0,不难发现其在Group1第7个,因此上文信号确认中对Group1进行确认。

最后将全部的中断检测重新打开,并进入死循环:

IER |= M_INT1;  // 这是在CPU中配置相应的中断处理,由于启用的中断属于PIE Group1因此我们需要将CPU中INT1中断启用
PieCtrlRegs.PIEIER1.bit.INTx7 = 1;  // 由于CPU-Timer 0属于外设,所以需要对其进行中断控制启动,上文找到了其所在的位置是Group1第7个
EINT;   // 启动全局中断
ERTM;   // 启动实时中断,允许DEBUG代码
for(;;);  // 进入死循环,通过CPU的100ms触发一次中断处理函数,实现闪灯

代码简化

综上我们可以将跑马灯代码简化如下,效果如下图所示:

创建新项目后,有一个空白的main.c文件把下面代码拷贝进去,然后还需要把相关的.c文件(直接从Example_2806xCpuTimer里面拷贝)和头文件加载进来,头文件加载方式右键项目名->Properties->Build->C2000 Compiler->Include Options->Add dir to #include右边的加号->Browse->将``上文中提到的头文件``都加载一遍一共4个):

下面代码只要按照Flash与RAM烧录方法中将cmd文件替换为F28069.cmd就可以直接烧录到Flash中了, 在断电后也可以继续执行跑马灯代码.

#include <DSP28x_Project.h>

interrupt void led_flash(void) {  // don't forget interrupt keywork
    // Use toggle to change GPIO output level
    GpioDataRegs.GPBTOGGLE.bit.GPIO34 = 1;
    GpioDataRegs.GPBTOGGLE.bit.GPIO39 = 1;
    // reference p178, TINT0 belongs to INT1.7 (group 1, no.7)
    PieCtrlRegs.PIEACK.bit.ACK1 = 1;
}

int main(void) {
    /* Initialization */
    InitSysCtrl();
    DINT;
    IER = 0;
    IFR = 0;
    InitPieCtrl();
    InitPieVectTable();

    /* Vector Configure */
    EALLOW;
    PieVectTable.TINT0 = &led_flash;
    EDIS;

    /* Configure Peripheral */
    InitCpuTimers();
    // CPU freq = 90hz, Period = 1e5us = 0.1s
    ConfigCpuTimer(&CpuTimer0, 90, 1e5);

    /* Start CPU interrupter */
    // Reference p106, Table 1-52 TIMERXTCR Descriptions
    CpuTimer0Regs.TCR.bit.TSS = 0;  // stop status
    CpuTimer0Regs.TCR.bit.TIE = 1;  // interrupt enable

    /* Initialize LED */
    EALLOW;
    GpioCtrlRegs.GPBDIR.bit.GPIO34 = 1;  // output mode
    GpioCtrlRegs.GPBDIR.bit.GPIO39 = 1;  // output mode
    GpioDataRegs.GPBSET.bit.GPIO39 = 1;  // high level
    EDIS;

    /* Start Global Interrupt */
    IER |= M_INT1;  // TINT0 belongs to INT1.7
    PieCtrlRegs.PIEIER1.bit.INTx7 = 1;
    EINT;  // enable global interrupt
    ERTM;  // enable interrupt debug

    /* LOOP forever */
    while (1);

    return 0;
}

ePWM Timer from Flash

样例位于<base>\examples\c28\flash_f28069, 这是一个将代码烧录到Flash中的程序,其还使用了ePWN(enhanced pulse width modulator 增强脉宽调制).

使用方法: 这个程序给出了通过ePWM的计时器产生中断的方法, 但是这个产生的频率非常高, 我们只能通过三个计数器的数值大小来看相对的关系. 使用debug模型运行代码, 启动代码后过一会, 按暂停按钮, 在变量列表中查看EPwm1TimerIntCount,EPwm2TimerIntCount,EPwm3TimerIntCount的数值大小, 可以看到它们是3:2:1的关系, 主要是学习了将程序烧到Flash中的方法.

初始化

InitSysCtrl();  // 系统初始化
DINT;  // 停止CPU中断处理
InitPieCtrl;  // 初始化PIE(peripheral interrupt expansion)控制
IEF = 0;  // 清空CPU中断
IFR = 0;
InitPieVectTable();  // 初始化PIE中断服务表
EALLOW;  // 关闭写保护
PieVectTable.EPWM1_INT = &func  // 设置ePWM脉宽调制1的中断服务
EDIS;  // 启动写保护

ePWM计数器以及中断产生的初始化

他将初始化函数定义为InitEPwmTimer,对ePWM的初始化非常复杂,我们以一个ePWM初始化为例,主要工作就是初始化与外围模块的时钟信息,即TB(time base) 时钟的各种参数:

EALLOW;
SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 0;  // 停止所有TB时钟, PCLKCR (peripheral clock control), TBCLKSYNC (time base clock sync)
EDIS;
// 这里没用到输出的A,B波, 所以也没必要加下面这句话
InitEPwm1Gpio();  // 调用F2806x_EPwm.c文件中的初始化函数,每个EPWM由A和B两个输出端口组成,我们需要通过GPIO来输出ePWM信号,因此首先要将将GPIO的上拉电阻关闭,GpioCtrlRegs.GPAPUD.bit.GPIO0 = 1,然后在将GPIO的端口复用MUX(multiplexing)设置为EPWM1A即可
// ePWM同步设置如下
EPwm1Regs.TBCTL.bit.SYNCOSEL = TB_SYNC_IN;  // 配置TB控制的同步输出SYNCOSEL(sync output select)选择为EPWM同步模式,保持不同的EPWM模块的同步性
// 这里不启用同步都可以 (注释掉下面这两句话)
EPwm1Regs.TBCTL.bit.PHSEN = TB_ENABLE;  // 将ePWM的计数寄存器设置从相位寄存器PHSEN (phase register enable)中读取
EPwm1Regs.TBPHS.half.TBPHS = 100;  // TB Phase 时基相位寄存器, 这里全部改成0都可以
EPwm1Regs.TBPRD = PWM1_TIMER_TBPRD;  // TB Period 时基周期寄存器,也确定了频率
EPwm1Regs.TBCTL.bit.CTRMODE = TB_COUNT_UP;  //  Counter Mode 脉冲计数的模式,设置为向上增加计数器的方式
EPwm1Regs.ETSEL.bit.INTSEL = ET_CTR_ZERO;  // Event-Trigger Selection ePWM中断触发选项, 当计数器归0时候产生一次中断

C/C++源码理解

简单看了下头文件的写法,记录一些有意思的东西

宏定义

在头文件 *.h 中主要就是进行各种内存、变量的位置定义,常见的一种写法

// 例如当前头文件名为 filename.h,通过下述方法能够避免在两次调用该头文件时出现重复定义
#ifndef FILENAME_H  // if not define,和当前文件名相同(易于判断)
#define FILENAME_H  // 对 FILENAME_H 进行一次宏的存在定义,不包含具体值
...  // 进行各种有含义的宏定义
#endif  // 用于结束 #ifndef

Union数据结构

F2806x_Cputimers.h, F2806x_Gpio.h可以看到一种通过structunion结合的方式,这种union数据类型通常包含两个共享内存的变量all, bit它们分别表示整个分配的内存和对内存的划分,通过:位数来对内存按位进行划分的方法,其使用方法如下

#include <stdio.h>
#include <stdint.h>

struct Bit {  // 总共使用了36位,除以16向上取整为3,因此其分配的内存大小为3*2=6 Bytes
  uint16_t a:4;
  uint16_t b:4;
  uint16_t c:4;
  uint16_t d:4;
  uint16_t e:4;
  uint16_t f:16;
};

union Foo {  // union中的变量全部共享一块内存,分配的内存为其中所有变量所需的最大内存
  uint16_t all;  // 分配前16位,从低到高分别对应a,b,c,d
  struct Bit bit;
};

int main(void) {
  union Foo foo;
  foo.all = 0x142A;
  printf("size of Bit %zu bytes\n", sizeof(struct Bit));  // 6 Bytes
  printf("size of Foo %zu bytes\n", sizeof(union Foo));  // 6 Bytes
  printf("%u %u %u %u %u\n", foo.bit.a, foo.bit.b, foo.bit.c, foo.bit.d, foo.bit.e);
  foo.bit.d = 6;
  printf("%hX\n", foo.all);
  return 0;
}

F28069M开发板笔记
https://wty-yy.github.io/posts/10130/
作者
wty
发布于
2024年8月31日
许可协议