☰
DSP28335 SCI串口回传实验:FIFO配置与中断实现详解
2026/10/15 6:29:10 网站建设 项目流程

简介:本资源是面向嵌入式开发工程师与高校电类专业学生的TMS320F28335 DSP串行通信实战项目,聚焦SCI模块在RS232标准下的16位字符双向传输实现,重点解决实时控制场景中高效、稳定串口通信的中断驱动与FIFO管理难题。压缩包含139个文件,涵盖41个头文件(.h)定义寄存器与接口、38个依赖文件(.d)支撑编译依赖、9个C源码(如main.c、DSP2833x_Sci.c)实现SCI初始化、中断服务例程及FIFO收发逻辑,以及汇编启动代码、链接命令文件(.cmd)、工程配置(.ccsproject)等完整构建要素,总大小315KB。已有1484人学习下载,提供可直接导入CCS的完整工程结构,包含SCI中断使能、16位数据打包/解析、发送/接收FIFO阈值触发机制及回传验证逻辑,代码注释详实,便于理解TI底层外设驱动设计思想与实时系统中断响应流程。 拿到一块新的DSP28335开发板,我通常第一件事不是点灯,而是先把SCI串口打通。干这行的人都清楚,后续不管调试变量、看运行状态,还是跟上位机做联调,串口永远是最快的那条路。而串口调试里最基础也最直观的验证实验,就是“回传”(echo):PC通过RS-232发一个字符,DSP的SCI模块收到后原样发回来。链路通没通、波特率配没配准、电平转换芯片有没有接错,一次回传全都能暴露出来。

这篇文章我会把TMS320F28335的SCIA外设配合RS-232电平转换、利用SCI内置16级FIFO做发送与回传的完整过程讲清楚,包括寄存器怎么配、代码怎么写、实测会遇到哪些坑、以及从echo Demo如何升级成一个可用的通信框架。内容偏向实操,适合刚开始接触28335串口、或者被SCI各种寄存器位搞晕的朋友。

1. “发送与回传”到底要打通什么:先把硬件链路理清楚

回传Demo从代码量上看很简单,但它背后是一条完整的硬件链路。很多朋友代码写完发现收不到数据,问题往往不在软件,而在硬件链路某一环断了。

1.1 从SCI到RS-232:两套电平标准别混淆

DSP28335的SCI模块本质上是UART(通用异步收发器),它输出的是3.3V TTL电平。而PC的串口(DB9接口)遵循的是RS-232标准,逻辑电平是负逻辑,高电平在-3V到-15V,低电平在+3V到+15V。两者不能直接相连,中间必须经过电平转换芯片,常见的有MAX3232(3.3V供电)和MAX232(5V供电)。

28335是3.3V器件,所以优选MAX3232。市面上很多DSP28335开发板已经把MAX3232电路集成在了板子上,引出一个DB9公头或者排针。如果你用的是核心板+底板的组合,需要确认底板上SCIA的TXD/RXD是否已经通过电平转换芯片接到了串口插座。

接线逻辑并不复杂:DSP的SCITXDA引脚连接MAX3232的TTL侧输入端(比如T1IN),MAX3232的RS-232侧输出(比如T1OUT)连接DB9的第2脚;PC发送过来的RS-232信号从DB9第3脚进入MAX3232的R1IN,再从R1OUT输出到DSP的SCIRXDA引脚。DB9的5脚(GND)必须和DSP开发板的GND连在一起。

信号通路可以这样看:

PC串口发送 → DB9第3脚 → MAX3232 R1IN → R1OUT → DSP的SCIRXDA DSP的SCITXDA → MAX3232 T1IN → T1OUT → DB9第2脚 → PC串口接收

如果你用的是USB转RS-232线,或者USB转TTL模块,需要先搞清楚线序。USB转TTL模块输出的已经是3.3V或5V TTL电平,如果直接接DSP的TTL引脚,可以绕过板载MAX3232,但要注意模块是3.3V还是5V电平,5V电平直连3.3V的DSP有风险。稳妥起见,优先使用板载的RS-232通道。

1.2 回传Demo的实际价值:为什么先做echo而不是直接写业务

回传这个实验虽然看起来“玩具”,但它能一次性验证三件事:

  • 电气链路是否连通:TXD/RXD有没有接反,GND是否共地,MAX3232是否正常工作
  • 波特率配置是否正确:波特率错了,回传回来的字符会变成乱码,这是最直接的反馈
  • FIFO和中断配置是否正确:触发级别、标志位清除、PIE中断使能,任何一个环节出错,数据要么丢要么卡死

我自己的习惯是,新板子到手,先用回传Demo把SCI链路验证通过,再在这个基础上叠加帧协议、环形缓冲区、上位机联调。否则一上来就写一堆逻辑,出问题时分不清是硬件故障还是软件bug,排查成本成倍增加。

2. SCI外设与16级FIFO:关键寄存器和工作机制

这一节把28335的SCI模块内部结构讲清楚。很多Datasheet把寄存器表格堆在一起,看着头大,但实际用到的就那么几个寄存器,一个个拆开看,逻辑非常清晰。

2.1 发送通道和接收通道:FIFO帮你缓冲数据

28335的每个SCI模块(SCIA和SCIB)都有独立的发送器和接收器,各自带一个16级深度的FIFO。所谓16级,就是硬件上能缓存16个字符(每个字符按SCICCR配置的位数决定,默认8位)。

在没有FIFO的传统UART模式里,CPU发送一个字节,必须等移位寄存器把这一位一位地发完才能写下一个;接收也一样,每收到一字节CPU就得立刻读走,否则下一个字节来了就会覆盖。数据量一上来,CPU就卡死在等待上。

FIFO模式解决了这个问题。发送路径变成:

CPU写SCITXBUF → 发送FIFO(最多缓存16字节) → 发送移位寄存器 → SCITXD引脚

接收路径变成:

SCIRXD引脚 → 接收移位寄存器 → 接收FIFO(最多缓存16字节) → CPU读SCIRXBUF

这样CPU可以一次性把16个字节塞进发送FIFO,然后去做别的事,硬件慢慢发;接收时,哪怕CPU没来得及读,FIFO也能先存着最多16个字节,不至于立刻丢数据。

2.2 核心寄存器速查:SCICCR、SCICTL1、SCICTL2

先记住三个最基础的配置寄存器。

SCICCR(SCI通信控制寄存器)主要管通信格式:

  • bit 2-0(SCICHAR):字符长度,000=1位,001=2位,一直到111=8位
  • bit 2(STOP):停止位数量,0=1个停止位,1=2个停止位
  • bit 4(POLINT):奇偶校验使能,0=不使能
  • bit 5(LOOPBKENA):环回测试使能,1=内部环回,调试用非常方便

SCICTL1(SCI控制寄存器1)主要管发送器接收器的使能和复位状态:

  • bit 0(RXENA):接收器使能
  • bit 1(TXENA):发送器使能
  • bit 5(SWRESET):软件复位,0=SCI处于复位状态,配置完成后置1启动工作

SCICTL2(SCI控制寄存器2)主要管中断和状态标志:

  • bit 0(TXINTENA):发送FIFO中断使能
  • bit 6(RXBKINTENA):接收缓冲器中断使能
  • bit 1(TXRDY)、bit 3(TXEMPTY):发送器状态标志

2.3 16级FIFO的状态与控制:SCIFFTX和SCIFFRX

FIFO寄存器是配置的重点,SCIFFTX管发送FIFO,SCIFFRX管接收FIFO。

SCIFFTX各位的作用:

  • bit 15(SCIRST):SCI复位控制,1=正常运行
  • bit 14(SCIFFENA):FIFO增强功能使能,1=使用FIFO
  • bit 13(TXFIFORESET):发送FIFO复位,1=正常运行,0=复位
  • bit 11-8(TXFFST):只读,当前发送FIFO中待发送的字符个数
  • bit 7(TXFFINTCLR):写1清除发送FIFO中断标志
  • bit 4-0(TXFFIL):发送FIFO中断触发级别,当TXFFST小于等于这个值时触发中断

SCIFFRX各位的作用:

  • bit 15(RXFIFORESET):接收FIFO复位,1=正常运行
  • bit 14-13(RXFFOVF):只读,接收FIFO溢出标志
  • bit 11-8(RXFFST):只读,当前接收FIFO中已接收的字符个数
  • bit 7(RXFFINTCLR):写1清除接收FIFO中断标志
  • bit 5(RXFFIENA):接收FIFO中断使能
  • bit 4-0(RXFFIL):接收FIFO中断触发级别,当RXFFST大于等于这个值时触发中断

注意一个容易混淆的点:发送FIFO的中断触发条件是“FIFO深度小于等于触发级别”,因为发送FIFO空了才需要CPU继续填数据;接收FIFO的中断触发条件是“FIFO深度大于等于触发级别”,因为接收到足够数据才需要CPU取走。方向相反,别搞混。

2.4 “16位字符”到底是什么意思

标题里的“16位字符”需要澄清一下,避免误解。DSP28335的SCI外设数据位最长只能配到8位,也就是一个标准字节。所谓“16”,其实指的是FIFO深度是16级,外设寄存器按16位组织,SCITXBUF和SCIRXBUF都是16位寄存器。

至于SCIRXBUF的高8位,在某些型号上会携带错误标志位(比如奇偶校验错误、溢出标志等),但在28335上,通常我们只用低8位取数据。如果你确实需要传输一个“16位数据类型”(比如16位ADC采样值、16位温度值),也不能靠SCICHAR配到16,正确做法是拆成两个8位字节发送,接收端再拼接。这个后面扩展部分会提到。

3. 初始化代码逐行解析:GPIO、波特率、FIFO一个都不能错

下面进入正题。我用SCIA作为示例,完整写一遍初始化代码,并逐段解释每一行在做什么、为什么这么写。

3.1 GPIO复用配置:把引脚切换成SCI功能

28335的GPIO口默认是通用数字IO,要切换成SCI功能,需要配置GPAMUX寄存器。SCIA的功能引脚有两个映射位置,我默认用GPIO28和GPIO29,这也是绝大多数开发板的设计。

EALLOW; // GPIO28 -> SCIRXDA,GPIO29 -> SCITXDA GpioCtrlRegs.GPAMUX2.bit.GPIO28 = 1; GpioCtrlRegs.GPAMUX2.bit.GPIO29 = 1; // 使能内部上拉,防止空闲时电平悬空 GpioCtrlRegs.GPAPUD.bit.GPIO28 = 0; GpioCtrlRegs.GPAPUD.bit.GPIO29 = 0; EDIS;

GPAMUX2中每个GPIO占用2位,01代表外设功能1(对GPIO28/29来说就是SCI功能),我直接使用bit域操作,不用去手动算移位。另外,配置为外设功能后,引脚方向由硬件自动管理,不需要再配置GPADIR。

如果开发板把SCIA接到了GPIO35/36上,配置改成:

GpioCtrlRegs.GPBMUX1.bit.GPIO35 = 1; // SCIRXDA GpioCtrlRegs.GPBMUX1.bit.GPIO36 = 1; // SCITXDA

无论用哪组引脚,关键是查原理图确认信号方向:GPIO28/35是接收引脚,连的是MAX3232的R1OUT;GPIO29/36是发送引脚,连的是MAX3232的T1IN。接反了数据就“只进不出”或者“只出不进”。

3.2 波特率计算:BRR公式和实际误差

波特率寄存器是16位的,分高8位SCHIBAUD和低8位SCILBAUD两部分。计算公式如下:

BRR = LSPCLK / (波特率 × 8) - 1

LSPCLK是低速外设时钟。在28335默认配置下,系统时钟SYSCLKOUT为150MHz,LSPCLK = SYSCLKOUT / 4 = 37.5MHz。

以115200波特率为例:

BRR = 37500000 / (115200 × 8) - 1 = 37500000 / 921600 - 1 = 40.69 - 1 = 39.69

取整BRR=40。由于BRR只能取整数,实际波特率为:

实际波特率 = 37500000 / (8 × (40+1)) = 37500000 / 328 = 114329

跟115200的误差约0.756%,在UART通信允许的误差范围(通常±2%)内,可以正常工作。

再看9600波特率:

BRR = 37500000 / (9600 × 8) - 1 = 37500000 / 76800 - 1 = 487.28 - 1 = 486.28,取整整487

实际波特率 = 37500000 / (8 × 488) = 9602,误差约0.025%,更精确。

初始化代码如下:

// 115200波特率,LSPCLK = 37.5MHz,BRR = 40 SciaRegs.SCIHBAUD = 0x0000; SciaRegs.SCILBAUD = 0x0028; // 40 // 如果是9600波特率,BRR = 487 = 0x01E7 // SciaRegs.SCIHBAUD = 0x0001; // SciaRegs.SCILBAUD = 0x00E7;

这里有个经验之谈:如果你发现上位机收到的全是乱码,优先怀疑波特率有没有算对。尤其是有人会拿150MHz直接代入公式,忘记除以4,算出来的BRR差了4倍,必然乱码。

3.3 标准初始化流程:顺序很重要

FIFO和SCI的初始化顺序有讲究,混乱的配置顺序会导致某些寄存器写入不生效。我习惯按下面这个顺序:

void SCI_Init(void) { // 第1步:配置GPIO复用 EALLOW; GpioCtrlRegs.GPAMUX2.bit.GPIO28 = 1; GpioCtrlRegs.GPAMUX2.bit.GPIO29 = 1; GpioCtrlRegs.GPAPUD.bit.GPIO28 = 0; GpioCtrlRegs.GPAPUD.bit.GPIO29 = 0; EDIS; // 第2步:配置通信格式 // 8位字符长度,1个停止位,无校验 SciaRegs.SCICCR.all = 0x0007; // 第3步:使能发送和接收,但保持SWRESET=0(软件复位状态) SciaRegs.SCICTL1.all = 0x0003; // 第4步:配置波特率 SciaRegs.SCIHBAUD = 0x0000; SciaRegs.SCILBAUD = 0x0028; // 115200 // 第5步:配置FIFO // 发送FIFO:SCIRST=1,SCIFFENA=1,TXFIFORESET=1 SciaRegs.SCIFFTX.all = 0xE000; // 接收FIFO:RXFIFORESET=1 SciaRegs.SCIFFRX.all = 0x8000; // 接收FIFO中断触发级别配置为1,即每收到1个字符触发中断 SciaRegs.SCIFFRX.bit.RXFFIL = 1; // 字符间发送延迟为0 SciaRegs.SCIFFCT.all = 0x0000; // 第6步:解除软件复位,SCI开始工作 SciaRegs.SCICTL1.all = 0x0023; // 第7步:使能接收FIFO中断 SciaRegs.SCIFFRX.bit.RXFFIENA = 1; }

为什么要在SWRESET=0的状态下先做配置?因为SCI处于复位状态时,配置寄存器可以被修改;一旦解除复位,某些位就锁定为运行状态,修改无效。最后一步把SWRESET置1(0x0023中bit5就是SWRESET),SCI才真正开始收发。

4. 回传逻辑实现:查询模式和FIFO中断模式

初始化搞定后,回传逻辑本身很简单。但有两条路可以走,各有适用场景,我分别说明。

4.1 查询模式:最直接的验证方式

查询模式不涉及中断,主循环里不断检查接收FIFO的状态位,有数据就读取并转发到发送FIFO:

void SCI_Echo_Loop(void) { Uint16 rxData; while(1) { // 检查接收FIFO是否有数据 if(SciaRegs.SCIFFRX.bit.RXFFST > 0) { // 读取接收数据(低8位有效) rxData = SciaRegs.SCIRXBUF.all & 0x00FF; // 等待发送FIFO有空位(不为16) while(SciaRegs.SCIFFTX.bit.TXFFST == 16); // 写回发送FIFO SciaRegs.SCITXBUF.all = rxData; } } }

查询模式的优点是代码简单、逻辑一目了然,非常适合第一次验证链路是否通畅。缺点是CPU全程占用,主循环不能干别的。对于回传Demo来说完全够用,但放到实际项目中不现实。

4.2 FIFO中断模式:每收到1个字符就自动回传

中断模式让硬件在接收到指定数量的字符后自动触发中断,在中断服务程序里完成读取和回传。先配置PIE中断向量:

// 在main函数中注册中断服务函数 EALLOW; PieVectTable.SCIRXINTA = &sciaRxFifoIsr; EDIS; // 使能PIE第9组第1个中断(SCIA接收中断) PieCtrlRegs.PIEIER9.bit.INTx1 = 1; // 使能CPU中断第9组 IER |= M_INT9; // 开总中断 EINT;

中断服务函数的完整写法:

__interrupt void sciaRxFifoIsr(void) { Uint16 rxData; // 读取接收FIFO数据,低8位是有效字符 rxData = SciaRegs.SCIRXBUF.all & 0x00FF; // 等待发送FIFO有空间 while(SciaRegs.SCIFFTX.bit.TXFFST == 16); // 把收到的字符写入发送FIFO,实现回传 SciaRegs.SCITXBUF.all = rxData; // 清除接收FIFO中断标志,否则会不停进中断 SciaRegs.SCIFFRX.bit.RXFFINTCLR = 1; // 应答PIE控制器,允许同组后续中断 PieCtrlRegs.PIEACK.all = PIEACK_GROUP9; }

这段代码有几个细节必须注意:

  • RXFFINTCLR必须在中断处理完成后写1,清掉中断标志,否则中断标志一直有效,CPU会反复进中断,表现为系统卡死。
  • PIEACK的应答不能漏。28335的PIE控制器同一组内多个中断共享一个CPU中断入口,处理完一个必须向PIEACK写1,才能继续响应同组其他中断。否则即使有新的SCI接收中断,CPU也不会响应。
  • 中断函数里尽量别做耗时操作,回传这种“读一个字节、写一个字节”的简单操作没问题,千万不要在中断里做大数据处理或打印。

4.3 两种发送方式的取舍

上面回传是“收一个回一个”。但实际项目中经常需要主动发送字符串或数据块。发送FIFO的使用有两种思路:

逐字节等待发送:

void SCI_SendByte(Uint16 data) { // 等待发送FIFO有空间(深度小于16) while(SciaRegs.SCIFFTX.bit.TXFFST == 16); SciaRegs.S <p> <a href="https://download.csdn.net/download/qq_38948179/75681588" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>

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

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

立即咨询