☰
TMS320F28027外设例程详解:SCI、AD、EPwm、TIMER、SPI快速上手
2026/10/20 4:52:33 网站建设 项目流程

简介:这份资源是面向TI C2000系列初学者的TMS320F28027微控制器编程例程合集,围绕SCI串行通信、ADC模数转换、EPwm增强型脉宽调制、TIMER定时器与SPI串行外设接口五大核心外设展开,帮助开发者快速理解各硬件模块的配置流程与软件编程技巧。压缩包共收录1102个文件,约34.57MB,其中C源码与头文件构成例程主体,PDF与PPT文档提供原理讲解与实验指导,工程配置、链接脚本及编译产物则完整保留了可直接导入CCS的工程结构,便于对照调试。目前已有1552人学习下载,适合电机控制、电源管理及实时信号采集等方向的入门与进阶练习。通过动手实践,读者可掌握波特率与数据位设置、ADC采样通道选择、PWM频率与死区时间调节、定时器周期触发以及SPI主从模式通信等关键技能,并借助配套教程与实验指导构建完整的口袋实验室实践环境。

1. 从一份 DSP 例程说起:TMS320F28027 到底能跑哪些外设

如果你手头有一块 TMS320F28027 的板子,或者正在评估用 C2000 系列做电机控制、数字电源、逆变器这类实时性要求高的场景,那这份例程包大概率能帮你省掉不少翻数据手册的时间。它把 SCI、AD、EPwm、TIMER、SPI 这几个最常用的外设各写了一个独立的例程,每个例程都能单独编译、烧录、跑通,不是那种把所有功能塞进一个 main 里的大杂烩。我见过太多人拿到 DSP 之后卡在时钟配置和寄存器初始化上,这份代码的价值就在于它把底层配置的骨架搭好了,你只需要在它的基础上改参数、加逻辑。适合谁?刚接触 C2000 的嵌入式新手,以及需要快速验证外设功能的熟手,都能直接拿来用。

2. 工程结构与外设初始化:从 main 到寄存器配置的完整链路

2.1 目录组织与文件职责

这份例程的目录结构比较典型,按外设分文件夹,每个文件夹里是一套完整的工程文件。常见做法是每个外设一个独立工程,共享一份底层驱动和头文件。我一般会这样组织:

TMS320F28027_Examples/ ├── SCI/ │ ├── main.c │ ├── SCI_Init.c │ ├── SCI_Init.h │ └── project.pjt ├── AD/ │ ├── main.c │ ├── AD_Init.c │ └── AD_Init.h ├── EPwm/ │ ├── main.c │ ├── EPwm_Init.c │ └── EPwm_Init.h ├── TIMER/ │ ├── main.c │ ├── Timer_Init.c │ └── Timer_Init.h ├── SPI/ │ ├── main.c │ ├── SPI_Init.c │ └── SPI_Init.h └── common/ ├── DSP2802x_Headers/ ├── DSP2802x_GlobalVariableDefs.c └── DSP2802x_SysCtrl.c

common文件夹放的是芯片支持库,包括寄存器结构体定义、系统控制初始化、PLL 和看门狗配置。每个外设文件夹里的main.c只做三件事:调用系统初始化、调用外设初始化、进入主循环或等待中断。这种分层的好处是,你调 SCI 的时候不用关心 EPwm 的寄存器,改一个外设不会影响另一个。

2.2 系统时钟与 GPIO 复用配置

C2000 的 GPIO 是复用的,同一个引脚既能做普通 IO,也能做 SCI 的 TXD、EPwm 的输出。所以每个外设例程的第一步都是配置 GPIO 复用寄存器。以 SCI 为例:

// SCI 初始化中的 GPIO 配置 void SCI_GPIO_Setup(void) { EALLOW; // 允许写保护寄存器 // GPIO28 配置为 SCI 接收引脚 SCIRXDA GpioCtrlRegs.GPAMUX2.bit.GPIO28 = 1; // GPIO29 配置为 SCI 发送引脚 SCITXDA GpioCtrlRegs.GPAMUX2.bit.GPIO29 = 1; EDIS; // 禁止写保护寄存器 }

EALLOW和EDIS是 C2000 特有的宏,用来解锁被保护的寄存器。很多新手第一次写 DSP 代码时发现寄存器写不进去,八成就是忘了加这对宏。GPAMUX2是 GPIO 复用选择寄存器,写 1 表示选择外设功能,写 0 表示普通 IO。具体哪个引脚对应哪个值,要查芯片的数据手册,不同封装的引脚映射不一样。

2.3 中断向量表与 PIE 初始化

C2000 的中断系统分三级:外设中断、PIE 中断、CPU 中断。每个外设中断都要经过 PIE 才能到达 CPU。例程里通常会有一个InitPieCtrl()和InitPieVectTable()的调用,把中断服务函数的地址填到向量表里。以 TIMER0 为例:

// 中断向量表初始化 void InitPieVectTable(void) { Uint16 i; Uint32 *Source = (void *) &PieVectTableInit; Uint32 *Dest = (void *) &PieVectTable; EALLOW; for(i = 0; i < 128; i++) *Dest++ = *Source++; EDIS; // 使能 TIMER0 中断,并映射到 PIE 第 1 组第 7 个中断 PieVectTable.TINT0 = &cpu_timer0_isr; PieCtrlRegs.PIEIER1.bit.INTx7 = 1; IER |= M_INT1; // 使能 CPU 中断 1 EINT; // 使能全局中断 ERTM; // 使能实时调试中断 }

这段代码里PieVectTable.TINT0是函数指针,把中断服务函数的地址赋给它,硬件触发中断时就会跳转到你的函数。PIEIER1.bit.INTx7使能 PIE 第 1 组的第 7 个中断,对应 TIMER0。IER |= M_INT1使能 CPU 级的第 1 组中断。三层都使能了,中断才能正常响应。漏掉任何一层,中断都不会触发,这是调试时最常见的坑之一。

3. 五个外设例程逐个拆:SCI、AD、EPwm、TIMER、SPI 怎么跑

3.1 SCI 串口通信:配置波特率与收发数据

SCI 例程通常做两件事:初始化串口参数,然后回环测试或者定时发送数据。波特率计算是重点:

// SCI 初始化,假设 LSPCLK = 60MHz,目标波特率 9600 void SCI_Init(void) { SCI_GPIO_Setup(); // 复位 SCI SciaRegs.SCICCR.all = 0x0007; // 1 停止位,无校验,8 位数据 SciaRegs.SCICTL1.all = 0x0003; // 使能 TXD 和 RXD // 波特率 = LSPCLK / (BRR + 1) / 8 // BRR = 60e6 / (9600 * 8) - 1 = 780 SciaRegs.SCIHBAUD = 0x0003; SciaRegs.SCILBAUD = 0x000C; SciaRegs.SCICTL1.all = 0x0023; // 退出复位,使能发送和接收 }

SCICCR配置数据格式,SCICTL1控制收发使能。波特率寄存器是 16 位的,高 8 位和低 8 位分开写。SCIFFTX、SCIFFRX这些 FIFO 寄存器如果不用可以先不配,但建议使能 FIFO 减少中断频率。发送数据时直接写SciaRegs.SCITXBUF = data,接收则读SciaRegs.SCIRXBUF。注意发送前要检查SCICTL2.bit.TXRDY是否置位,否则数据会丢。

3.2 AD 采样:通道选择与结果读取

AD 例程一般配置成连续采样或者由 EPwm 触发采样。初始化步骤包括使能 ADC 时钟、配置采样窗口、选择通道:

// AD 初始化,采样通道 ADCINA0 void AD_Init(void) { EALLOW; AdcRegs.ADCCTL1.bit.ADCBGPWD = 1; // 使能 ADC 带隙电路 AdcRegs.ADCCTL1.bit.ADCPWD = 1; // 使能 ADC 电源 AdcRegs.ADCCTL1.bit.ADCENABLE = 1; // 使能 ADC DELAY_US(1000); // 等待 ADC 稳定 AdcRegs.ADCSOC0CTL.bit.CHSEL = 0; // 选择 ADCINA0 AdcRegs.ADCSOC0CTL.bit.ACQPS = 6; // 采样窗口 7 个周期 AdcRegs.ADCSOC0CTL.bit.TRIGSEL = 0; // 软件触发 EDIS; }

ADCSOC0CTL是采样启动控制寄存器,CHSEL选通道,ACQPS设采样保持时间。采样时间太短会导致采样不准,太长会降低采样率。读取结果时等ADCINTFLG.bit.ADCINT1置位,然后读AdcResult.ADCRESULT0。注意 AD 结果寄存器是 16 位的,实际有效位看芯片型号,F28027 是 12 位,所以结果范围是 0 到 4095。

3.3 EPwm 配置:频率、占空比与死区

EPwm 是电机控制的核心,例程通常配置成互补输出带死区。关键参数是时基周期和比较值:

// EPwm1 初始化,频率 10kHz,占空比 50% void EPwm1_Init(void) { EPwm1Regs.TBPRD = 3000; // 周期 = 3000 个时钟周期 EPwm1Regs.TBPHS.bit.TBPHS = 0; // 相位清零 EPwm1Regs.TBCTR = 0; // 计数器清零 EPwm1Regs.TBCTL.bit.CTRMODE = 2; // 增减计数模式 EPwm1Regs.TBCTL.bit.HSPCLKDIV = 0; EPwm1Regs.TBCTL.bit.CLKDIV = 0; // 时钟不分频 EPwm1Regs.CMPA.bit.CMPA = 1500; // 比较值,50% 占空比 EPwm1Regs.AQCTLA.bit.CAU = 2; // 计数等于 CMPA 时置高 EPwm1Regs.AQCTLA.bit.CAD = 1; // 计数等于 CMPA 时置低 EPwm1Regs.DBCTL.bit.OUT_MODE = 3; // 使能死区 EPwm1Regs.DBRED = 100; // 上升沿死区 EPwm1Regs.DBFED = 100; // 下降沿死区 }

TBPRD决定 PWM 频率,公式是频率 = 时钟 / (2 * TBPRD)(增减计数模式)。CMPA决定占空比,占空比 = CMPA / TBPRD。死区时间由DBRED和DBFED控制,单位是时钟周期。死区是为了防止上下桥臂直通,电机驱动里必须加。调试时如果发现 PWM 没输出,先查TBCLKSYNC有没有使能,再查 GPIO 复用对不对。

3.4 TIMER 定时中断:周期计算与中断服务

TIMER 例程一般配置成固定周期中断,用来做任务调度或计时:

// TIMER0 初始化,中断周期 1ms void Timer0_Init(void) { InitPieVectTable(); // 初始化中断向量表 EALLOW; PieVectTable.TINT0 = &cpu_timer0_isr; EDIS; CpuTimer0Regs.PRD.all = 15000; // 周期 = 15000 个时钟周期 CpuTimer0Regs.TPR.all = 0; CpuTimer0Regs.TPRH.all = 0; CpuTimer0Regs.TCR.bit.TSS = 1; // 先停止 CpuTimer0Regs.TCR.bit.TRB = 1; // 重载周期 CpuTimer0Regs.TCR.bit.TSS = 0; // 启动 PieCtrlRegs.PIEIER1.bit.INTx7 = 1; IER |= M_INT1; EINT; ERTM; } // 中断服务函数 __interrupt void cpu_timer0_isr(void) { CpuTimer0.InterruptCount++; PieCtrlRegs.PIEACK.all = PIEACK_GROUP1; // 应答中断 }

PRD是周期寄存器,假设系统时钟 150MHz,PRD = 150000对应 1ms。中断服务函数里必须写PIEACK应答,否则同组中断不会再触发。这是新手最容易漏的一步,现象是中断只进一次就再也不进了。

3.5 SPI 通信:主从模式与数据交换

SPI 例程通常配置成主机模式,发送数据同时接收:

// SPI 初始化,主机模式,波特率 1MHz void SPI_Init(void) { SpiaRegs.SPICCR.bit.SPISWRESET = 0; // 进入复位 SpiaRegs.SPICCR.bit.CLKPOLARITY = 0; // 上升沿采样 SpiaRegs.SPICCR.bit.SPICHAR = 7; // 8 位数据 SpiaRegs.SPICTL.bit.MASTER_SLAVE = 1;// 主机模式 SpiaRegs.SPICTL.bit.TALK = 1; // 使能发送 SpiaRegs.SPICTL.bit.SPIINTENA = 0; // 不用中断 SpiaRegs.SPIBRR = 29; // 波特率 = LSPCLK / (29 + 1) SpiaRegs.SPICCR.bit.SPISWRESET = 1; // 退出复位 } // 发送并接收一个字节 Uint16 SPI_Transfer(Uint16 data) { SpiaRegs.SPITXBUF = data; while(SpiaRegs.SPISTS.bit.INT_FLAG == 0); return SpiaRegs.SPIRXBUF; }

SPIBRR决定波特率,SPICCR配置数据长度和时钟极性。发送时写SPITXBUF,然后等SPISTS.bit.INT_FLAG置位,读SPIRXBUF得到接收数据。SPI 是全双工的,发一个字节的同时会收一个字节。如果只发不收,接收到的数据直接丢弃就行。

4. 避坑与常见问题:调试 DSP 例程时容易翻车的几个点

4.1 程序烧录后不运行,CCS 报错 “target not responding”

现象是连接仿真器后点运行没反应,或者提示目标无响应。原因通常是看门狗没关,程序一跑就复位。解决方法是检查common里的InitSysCtrl()有没有调用DisableDog(),或者在 main 开头手动加EALLOW; WdRegs.WDCR = 0x0068; EDIS;。另外电源不稳也会导致这个问题,用示波器看一下 3.3V 和 1.8V 是否正常。

4.2 中断进不去,或者只进一次

现象是中断服务函数里的计数器只加了一次就不再变化。原因有两个:一是忘了写PIEACK应答,二是中断向量表没映射对。解决方法是检查PieCtrlRegs.PIEACK.all = PIEACK_GROUPx有没有写,以及PieVectTable.xxx有没有赋成你的函数地址。还有一个隐蔽的坑是EINT和ERTM的顺序,必须先EINT再ERTM,反了会出问题。

4.3 AD 采样值跳动大,或者一直是 0

现象是读出来的 AD 值不稳定,或者始终为 0。原因可能是采样窗口太短,或者参考电压没配置。解决方法是把ACQPS加大到 15 以上试试,另外检查ADCCTL1里的ADCREFPWD和ADCBGPWD有没有使能。如果用的是外部参考,还要确认ADCREFSEL寄存器配置正确。硬件上,模拟输入引脚要加滤波电容,不然噪声会直接进 ADC。

4.4 EPwm 没有输出,或者输出频率不对

现象是示波器上量不到 PWM 波形,或者频率和计算值差很多。原因通常是 GPIO 复用没配,或者TBCLKSYNC没使能。解决方法是先查GPAMUX寄存器对应的位有没有设成 EPwm 功能,再查SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC有没有置 1。频率不对的话,检查系统时钟和TBPRD的计算公式,注意增减计数模式下频率要除以 2。

4.5 SCI 收发乱码,或者收不到数据

现象是串口助手收到乱码,或者完全没数据。原因一般是波特率不匹配,或者 GPIO 复用错了。解决方法是先用示波器量一下 TXD 引脚有没有波形,有波形但乱码就检查波特率寄存器的计算,注意LSPCLK的频率是不是你以为的那个值。另外SCICTL1的SWRESET位要先写 0 再写 1,顺序反了 SCI 不工作。

5. 二次开发与改造:把这套例程变成你自己的项目

拿到这份例程之后,最直接的改造方式是把多个外设组合起来用。比如做数字电源,你需要 EPwm 输出驱动信号,AD 采样输出电压电流,TIMER 做控制周期中断,SCI 跟上位机通信。这时候就不能一个外设一个工程了,要把它们的初始化函数都放到一个 main 里,注意初始化顺序:先系统时钟,再 GPIO,再外设,最后中断。

我一般会建一个App文件夹,把业务逻辑单独放,底层驱动不动。这样以后换芯片型号,只需要改common里的头文件和驱动,应用层代码不用大改。另外中断服务函数里尽量只做标志位翻转和数据搬运,复杂计算放到主循环里,避免中断执行时间过长影响实时性。

验证改造是否成功,我习惯用三步法:第一步,单独跑通每个外设的例程,确认硬件没问题;第二步,把外设初始化合并到一个工程里,逐个使能,看有没有冲突;第三步,加业务逻辑,用示波器和串口助手同时观测。这样出问题容易定位,不会一上来就一团乱麻。

有个细节值得注意:C2000 的寄存器都是内存映射的,直接操作寄存器效率最高,但可读性差。如果项目周期紧,可以用芯片支持库里的宏和结构体,虽然多一层封装,但开发速度快。我自己的习惯是底层初始化用寄存器,业务逻辑用库函数,兼顾效率和可维护性。

从那以后我每次拿到新的 DSP 例程,都强制自己先把每个外设单独跑一遍,确认硬件和工具链没问题,再开始改代码。这个习惯帮我省了很多调试时间,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询