刚接触DSP28335那会儿,我最头疼的不是PWM、也不是ADC,反而是最不起眼的串口。明明照着例程配好了SCI-A,能发数据,但换到SCI-B就死活不出数据。后来才知道,引脚复用、FIFO、波特率这些坑一个接一个,网上资料又零零散散。今天就把我调通DSP28335与Vofa+上位机通信的完整过程整理出来,重点讲SCI-B的配置避坑点,附带可以直接抄的代码,希望能帮你少走弯路。
这篇内容适合正在用28335做电机控制、电源调试、信号采集的工程师,也适合刚入门DSP、想把内部变量实时可视化出来的同学。只要你的需求是“把DSP里的数据发到电脑上看波形”,这篇文章就能直接套用。
1. 项目概述与整体思路
1.1 为什么我用Vofa+替代传统串口助手
调试DSP程序的时候,最痛苦的事情就是看不到内部变量。你用仿真器CCS的Watch窗口倒是能看,但变量多了以后刷新慢,而且没法看动态波形。传统串口助手虽然能显示ASCII字符串,但处理浮点数、画波形、多通道对比这些事基本干不了。
Vofa+这个上位机工具我很早就在用了,它支持三种数据协议:JustFloat、RawData、FireWater。其中JustFloat协议是最舒服的,你只需要把float数据按小端字节序发出去,再加一个CRC16校验尾巴,Vofa+就能自动识别通道、画波形。相比用串口助手打印“PWM=1250, Current=3.25”这种字符串,效率高一个量级。
我用28335做主控做过一个无刷电机驱动器,需要同时看速度环、电流环的给定值和反馈值,Vofa+一次拉4个通道,曲线直接叠一起看,PID参数调起来非常直观。这篇文章就是基于这个项目提炼出来的。
1.2 整体方案选型:SCI-B还是SCI-A
28335上有多组SCI模块,SCI-A、SCI-B、SCI-C。很多例程都喜欢用SCI-A,因为官方例程基本都是A口。但我在项目里经常把A口留给其他外设,比如接蓝牙模块或者另一块MCU,所以B口就变成我的调试串口。
选SCI-B有个前提:对应的GPIO引脚必须确认好。28335的SCI-B可以映射到GPIO18/GPIO19,也可以映射到GPIO22/GPIO23。GPIO18/19默认是SCI-B功能,GPIO22/23是第二功能。如果你的板子上GPIO18/19被其他外设占用了,那就用GPIO22/23,这时候多一步MUX寄存器配置,很多人就是在这里栽的跟头。
另外就是硬件连接。DSP的串口是3.3V TTL电平,电脑USB口是5V电平,直接连肯定不行,中间得加一个USB转TTL模块。我建议选CP2102或FT232方案的模块,这两种芯片电平是3.3V兼容的,CH340很多模块是5V电平,接DSP的RX会有烧引脚风险。接线就四根:DSP的SCITXDB接模块的RXD,SCIRXDB接模块的TXD,然后GND一定要共地。
2. SCI-B硬件与寄存器配置深度解析
2.1 引脚复用配置:GPIO18/19和GPIO22/23的坑
先搞清楚GPIO和引脚功能的关系。28335的每个GPIO引脚可以复用好几种外设功能,通过GPxMUX寄存器选择。SCI-B有两个可选映射:
- GPIO18(SCIRXDB)、GPIO19(SCITXDB):这是A组复用,在GPAMUX2寄存器里配置
- GPIO22(SCIRXDB)、GPIO23(SCITXDB):这是B组复用,在GPBMUX1寄存器里配置
最常见的错误是照抄SCI-A的配置代码,把GPAMUX1改一下给SCI-B用,结果发现SCIB收发没反应。为什么?因为GPIO18/19的MUX控制位在GPAMUX2的bit12和bit13,GPIO22/23的控制位在GPBMUX1的bit14和bit15,完全不是同一个寄存器。
我实际用的GPIO18/19配置代码是:
EALLOW; GpioCtrlRegs.GPAMUX2.bit.GPIO18 = 1; // GPIO18 -> SCIRXDB GpioCtrlRegs.GPAMUX2.bit.GPIO19 = 1; // GPIO19 -> SCITXDB GpioCtrlRegs.GPADIR.bit.GPIO18 = 0; // RX输入 GpioCtrlRegs.GPADIR.bit.GPIO19 = 1; // TX输出 EDIS;注意EALLOW和EDIS是必须的,这两个宏是28335外设寄存器写保护的开锁和上锁。忘记写EALLOW,寄存器写不进去,代码根本不报错,但功能就是不生效,这种问题特别隐蔽。
如果要用GPIO22/23,那就改成:
EALLOW; GpioCtrlRegs.GPBMUX1.bit.GPIO22 = 1; // GPIO22 -> SCIRXDB GpioCtrlRegs.GPBMUX1.bit.GPIO23 = 1; // GPIO23 -> SCITXDB GpioCtrlRegs.GPBDIR.bit.GPIO22 = 0; // RX输入 GpioCtrlRegs.GPBDIR.bit.GPIO23 = 1; // TX输出 EDIS;还有GPIO的上拉电阻,为了串口空闲状态稳定,建议把对应的上拉打开:
GpioCtrlRegs.GPAPUD.bit.GPIO18 = 0; // 使能上拉 GpioCtrlRegs.GPAPUD.bit.GPIO19 = 0; // 使能上拉GPxPUD寄存器位写0使能内部上拉,写1禁用。串口RX引脚悬空时如果没有上拉,容易收到一堆0xFF乱码,别问我怎么知道的,说多了都是泪。
2.2 波特率计算:公式很简单,误差要亲测
28335的SCI波特率由LSPCLK和BRR寄存器共同决定。计算公式是:
波特率 = LSPCLK / (8 × (BRR + 1))
所以BRR = LSPCLK / (8 × 波特率) - 1
这里LSPCLK是多少?28335系统时钟SYSCLKOUT默认150MHz,LSPCLK默认是SYSCLKOUT/4,也就是37.5MHz。这个值可以在InitPeripheralClocks()里修改,一般在DSP2833x_Examples.h里配置。
以37.5MHz LSPCLK为例,我算几个常用的波特率:
| 目标波特率 | 理论BRR | 取整BRR | 实际波特率 | 误差 |
|---|---|---|---|---|
| 9600 | 487.5 | 488 | 9581 | 0.2% |
| 19200 | 243.25 | 243 | 19230 | 0.16% |
| 115200 | 39.69 | 40 | 114329 | 0.76% |
| 921600 | 4.08 | 4 | 937500 | 1.7% |
BRR写入SCI-B的方式是16位数据,分成SCIHBAUD(高8位)和SCILBAUD(低8位)。115200波特率的写法:
ScibRegs.SCIHBAUD = 0x0028 >> 8; // 40的高位,实际是0 ScibRegs.SCILBAUD = 0x0028 & 0xFF; // 40的低位,0x28很多人的配置代码里SCIHBAUD和SCILBAUD直接写死,如果换了一个外部晶振,或者修改了LSPCLK分频,波特率就跟着偏了。所以项目里最好做一个带参数的计算宏:
#define LSPCLK_FREQ 37500000L #define SCIB_BAUD 115200 #define SCIB_BRR ((LSPCLK_FREQ / (8 * SCIB_BAUD)) - 1)这样改波特率只需要改宏参数,避免手算出错。
2.3 SCI-B寄存器初始化完整流程
SCI-B的初始化涉及SCICCR、SCICTL1、SCICTL2、SCIFFTX、SCIFFRX、SCIFFCT这些寄存器,顺序不能乱。我最开始照着网上代码复制,结果发送不出去,后来对比手册才发现是初始化顺序的问题。
正确顺序是:
- 先复位SCI模块(SCICTL1的SWRESET=0)
- 配置SCICCR:数据位、停止位、校验位
- 配置SCICTL1:使能TX/RX
- 配置波特率寄存器
- 配置FIFO
- 清除FIFO溢出和中断标志
- 最后置位SWRESET释放复位
SCICCR配置:我平时用8位数据位、1位停止位、无校验。SCICCR的低三位SCICHAR决定数据位,写0b011代表8位;PARITY和PARITYENA置0;STOPB置0代表1位停止位。整体写0x0007。
SCICTL1:第0位RXENA使能接收,第1位TXENA使能发送。注意SCICTL1的第5位SWRESET在配置期间必须为0,所有配置完成后再置1。配置期间的代码:
ScibRegs.SCICTL1.all = 0x0003; // RX和TX使能,SWRESET保持0 ScibRegs.SCICCR.all = 0x0007; // 8位数据,无校验,1位停止位 ScibRegs.SCIHBAUD = (SCIB_BRR >> 8) & 0xFF; ScibRegs.SCILBAUD = SCIB_BRR & 0xFF;FIFO这部分是重灾区。28335的SCI自带16级FIFO,在SCIFFTX和SCIFFRX寄存器里配置。很多教程直接写`ScibRegs.SCIFFTX.all = 0xE040;``这个值什么意思?SCIFFTX的bit15是SCIRST,必须为1,否则整个SCI复位无法工作;bit13是SCIFFENA,置1使能FIFO增强功能;bit5是TXFFRST,置1释放FIFO发送功能。0xE040 = 1110 0000 0100 0000,对应SCIRST=1、SCIFFENA=1、TXFFRST=1、TXFFINTCLR=1,没有使能发送中断。
SCIFFRX的0x2044 = 0010 0000 0100 0100,对应RXFFOVRCLR=1(清除溢出标志)、RXFFINTCLR=1(清除接收中断标志)、RXFFRST=1(释放FIFO接收功能)、RXFFIL=0100(接收FIFO中断阈值4)。这里的RXFFIL就是你希望达到几个字节后触发接收中断,我常用4或者8。
初始化里别忘了SCIFFCT,这个寄存器控制FIFO传输延迟,一般写0即可。
完整的SCIB初始化函数我放在下面这节,和发送函数一起贴出来。
3. 完整代码实现与通信实战
3.1 SCI-B初始化源码:可以直接抄
我直接贴我调试通过的完整初始化函数,里面加了注释,复制到工程里改一下波特率宏就能用:
#define LSPCLK_FREQ 37500000L #define SCIB_BAUD 115200 #define SCIB_BRR ((LSPCLK_FREQ / (8 * SCIB_BAUD)) - 1) void SciB_Init(void) { // 1. GPIO引脚复用 EALLOW; GpioCtrlRegs.GPAMUX2.bit.GPIO18 = 1; // SCIRXDB GpioCtrlRegs.GPAMUX2.bit.GPIO19 = 1; // SCITXDB GpioCtrlRegs.GPADIR.bit.GPIO18 = 0; // RX输入 GpioCtrlRegs.GPADIR.bit.GPIO19 = 1; // TX输出 GpioCtrlRegs.GPAPUD.bit.GPIO18 = 0; // 上拉使能 GpioCtrlRegs.GPAPUD.bit.GPIO19 = 0; // 上拉使能 EDIS; // 2. SCI模块复位 ScibRegs.SCICTL1.all = 0x0003; // TX/RX使能,SWRESET为0 // 3. 帧格式:8位数据,无校验,1位停止位 ScibRegs.SCICCR.all = 0x0007; // 4. 波特率 ScibRegs.SCIHBAUD = (SCIB_BRR >> 8) & 0xFF; ScibRegs.SCILBAUD = SCIB_BRR & 0xFF; // 5. FIFO配置 ScibRegs.SCIFFTX.all = 0xE040; // 使能FIFO,清除TX中断标志 ScibRegs.SCIFFRX.all = 0x2044; // 清除RX标志,FIFO阈值4字节 ScibRegs.SCIFFCT.all = 0x0000; // 无延迟 // 6. 清除错误标志 ScibRegs.SCICTL1.bit.SWRESET = 1; // 释放SCI复位 ScibRegs.SCIFFRX.bit.RXFFOVRCLR = 1; // 清除溢出标志 ScibRegs.SCIFFRX.bit.RXFFINTCLR = 1; // 清除接收中断标志 // 7. 如果要用接收中断,再开PIE中断 // PieCtrlRegs.PIEIER9.bit.INTx4 = 1; // SCIRXINTA对应PIE9.4 // IER |= M_INT9; }这段代码使用查询方式发送,接收中断默认关闭。如果你需要接收功能,把注释部分打开,然后在中断服务函数里处理数据。
3.2 浮点数据发送:联合体与JustFloat协议
Vofa+的JustFloat协议很简洁:一帧数据包包含N个float数据 + 1个float帧尾 + 2字节CRC16校验。帧尾固定是0x00 0x00 0x80 0x7F,这个值在IEEE754 float里是一个特殊值(约1.1755e-38),不太会和其他数据冲突,相当于帧结束标记。CRC16算法用CRC16/MODBUS,多项式0x8005,初始值0xFFFF。
发送一个float最简单的方式是联合体:
typedef union { float fval; uint8_t bytes[4]; } Float32_T; Float32_T tx_data;把float值赋给tx_data.fval,然后tx_data.bytes[0]到[3]就是它在内存里的字节序。28335是小端模式,先发送低字节:
void Vofa_SendFloat(float data) { Float32_T u; u.fval = data; Scib_SendByte(u.bytes[0]); Scib_SendByte(u.bytes[1]); Scib_SendByte(u.bytes[2]); Scib_SendByte(u.bytes[3]); }单个字节发送函数:
void Scib_SendByte(uint8_t data) { // 等待发送FIFO不满 while (ScibRegs.SCIFFTX.bit.TXFFST >= 16) { // 如果FIFO满,等一下,这里可以加超时 } ScibRegs.SCITXBUF.all = data; }但直接逐字节发效率不高,更好的做法是把一帧数据都打包进一个缓冲区,然后一次性往FIFO里填。Vofa+的JustFloat一次发4个通道,代码可以这样写:
#define VOFA_CHANNELS 4 void Vofa_SendChannels(float* data, uint16_t chNum) { Float32_T u; uint16_t i; uint16_t crc = 0xFFFF; for (i = 0; i < chNum; i++) { u.fval = data[i]; Scib_SendByte(u.bytes[0]); Scib_SendByte(u.bytes[1]); Scib_SendByte(u.bytes[2]); Scib_SendByte(u.bytes[3]); } // 帧尾 00 00 80 7F Scib_SendByte(0x00); Scib_SendByte(0x00); Scib_SendByte(0x80); Scib_SendByte(0x7F); // 对前面的数据做CRC16 // 实现见下方 crc16_modbus 函数 // crc需要覆盖:所有有效数据 + 帧尾4字节 // 这里省去实现细节,后面给完整版 }CRC16/MODBUS的实现我单独贴一下,这个代码网上版本很多,我用的这个验证过没问题:
uint16_t crc16_modbus(uint8_t* data, uint16_t len) { uint16_t crc = 0xFFFF; uint16_t i, j; for (i = 0; i < len; i++) { crc ^= data[i]; for (j = 0; j < 8; j++) { if (crc & 0x0001) { crc = (crc >> 1) ^ 0xA001; // MODBUS多项式 } else { crc = crc >> 1; } } } return crc; }注意:生成CRC时要把所有float数据和帧尾4字节都包含进去,然后低字节先发,高字节后发。
3.3 多通道发送Demo:用周期中断跑起来
下面是我在主循环里定时调用的示例,比如用CPU定时器0每1ms触发一次采集,然后在定时器中断里发送数据:
__interrupt void cpu_timer0_isr(void) { float ch[4]; // 采集或计算你的数据 ch[0] = speed_ref; // 速度给定 ch[1] = speed_feedback; // 速度反馈 ch[2] = current_iq; // Q轴电流 ch[3] = voltage_dc; // 母线电压 Vofa_SendChannels(ch, 4); // 清中断标志 CpuTimer0.InterruptCount++; PieCtrlRegs.PIEACK.all = PIEACK_GROUP1; }这个模式下,Vofa+每收到一帧就能画出4条曲线,效果相当于一个四通道示波器。帧率可以自己定,一般控制1ms到10ms一帧,上位机显示太慢或太快都会影响观看体验。我的经验是电机调速用1ms发一次,观察稳态波形非常清楚;如果只是粗略看数据变化趋势,10ms一帧就够了,频率太高达上位机CPU占用率会飙高。
3.4 接收功能:做一个简单的命令解析
不要只发不收。调试中往往需要上位机发命令来切换模式、改参数。Vofa+本身也有串口发送功能,在界面下方有发送框。我在DSP里做了一个简单的命令解析,通过接收中断实现:
__interrupt void scib_rx_isr(void) { uint8_t rx_byte; // 获取接收到的字节 rx_byte = ScibRegs.SCIRXBUF.all & 0xFF; // 简单协议:收到 '1' 启动,'0' 停止 if (rx_byte == '1') { motor_enable = 1; } else if (rx_byte == '0') { motor_enable = 0; } // 清除接收中断标志 ScibRegs.SCIFFRX.bit.RXFFINTCLR = 1; PieCtrlRegs.PIEACK.all = PIEACK_GROUP9; }记得打开PIE中断的第九组使能:
PieCtrlRegs.PIEIER9.bit.INTx3 = 1; // SCITXINTA对应PIE9.3(如果用了发送中断) PieCtrlRegs.PIEIER9.bit.INTx4 = 1; // SCIRXINTA对应PIE9.4 IER |= M_INT9;这里有个绕弯的地方:PIE通道的中断向量表里,SCI-A的接收中断在PIE9.4,SCI-B的接收中断在PIE9.6。很多人把SCIA和SCIB的中断号搞混,SCIB_RXINT对应的PIE9.6,不是PIE9.4。我在代码里用SCIB接收中断时,踩过一次这个坑。确保中断向量表里有对应的入口项,在DSP2833x_PieVect.c里注册:
PieVectTable.SCIRXINTB = &scib_rx_isr;必须放在初始化里,否则中断来了找不到地方跳。
4. Vofa+上位机配置与协议对接详解
4.1 三种协议怎么选
Vofa+支持JustFloat、RawData、FireWater、COM Virtural等模式。我实际用下来,优先级建议:
- JustFloat:适合多通道浮点波形显示,协议自带CRC校验,数据可靠,我主力模式
- RawData:适合快速调试,没有校验,数据按原始字节显示,但看着费劲
- FireWater:适合传输整型数据或自定义文本,灵活性最高但需要自己解析
打开Vofa+后,在左侧“串口设置”里选择你的USB转串口COM口,波特率改成和DSP一致。如果你是FT232或者CP2102方案,设备管理器里能看到USB Serial Port,COM号一般是COM3到COM10不等。
在“数据格式”里选JustFloat,然后“帧结束符”会自动识别,不需要额外设置。多通道数据会在同一个波形窗口里自动区分颜色。
4.2 波形显示与坐标轴调节
Vofa+界面右侧有波形区,每个通道默认有自己的颜色。数据刷新太快波形像一堆毛刺,点一下右下角的“暂停”按钮就能凝固住画面,仔细测量波峰波谷。左侧可以调整Y轴范围,点击“适应”按钮会自动缩放到最佳范围。
我最常用的功能是“游标测量”。暂停波形后,鼠标移动到波形上,会显示该点的数值和时间,方便测量电机转速超调量。调PID的时候,先给一个阶跃给定,看反馈波形的上升时间、超调量、振荡次数,比用仿真器看变量直观多了。
4.3 Vofa+版本和下载注意
Vofa+有两个大版本:Vofa+ 1.3.10和Vofa+ FireWater版本。FireWater版本支持FireWater协议,但界面布局和JustFloat版稍微不同。如果你只想调DSP波形,下JustFloat版就够了。下载时解压后直接运行exe,不需要安装,绿色软件。
上电顺序也有讲究。我遇到过USB转串口模块插电脑以后,DSP还没上电,Vofa+显示一堆乱码。正确操作是先给DSP上电,再打开Vofa+连接串口。原理是USB转TTL模块在未检测到有效信号时会把RX线拉低或产生噪声,DSP的TX引脚在没初始化之前可能是不确定电平,两者叠加就会产生垃圾数据。
还有,Vofa+的波特率选项里如果没有你要的数值,可以手动输入。115200和9600是常驻的,460800也能选,但DSP端如果BRR取整误差超过2%,就会出现偶发乱码,建议先用9600验证通路,再切高速率。
4.4 连上Vofa+但收不到数据的排查顺序
按这个顺序排查,大部分问题都能定位:
- 确认COM口号是否正确:设备管理器里看,拔插模块看哪个COM号变了
- 确认波特率是否匹配:DSP的LSPCLK是不是真的37.5MHz,如果改过主频,BRR要重新算
- 确认DSP侧程序在跑:LED闪烁或者定时器标志位都要有
- 确认接收电压:万用表量TX引脚,空闲状态应该是3.3V,发送时会有波形跳动
- 确认共地:DSP的GND和USB转TTL模块的GND必须相连,没有共地就收不到任何数据
5. 实际调试中踩过的坑与解决方案
5.1 问题速查表
这章内容是我重点想分享的,每一个坑都是真金白银换来的教训,整理成表格方便你快速定位:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 完全收不到数据 | GPIO MUX没配或配错 | 检查GPAMUX2/GPBMUX1对应位 |
| 完全收不到数据 | 忘记EALLOW | 配置外设寄存器必须包EALLOW/EDIS |
| 收不到数据但代码正常 | SCI-B引脚被其他外设占用 | 改用GPIO22/23映射,或确认板子资源 |
| 乱码 | 波特率误差过大 | 用公式重算BRR,优先9600验证 |
| 乱码 | USB转TTL模块电平不匹配 | 换CP2102/FT232模块,禁用CH340 |
| 波形一帧只有第一个通道正常 | 数据字节序不对 | Vofa+ JustFloat是小端发送 |
| 波形通道数量错乱 | 发送通道数和上位机设置不一致 | 检查Vofa+通道数和DSP发送数匹配 |
| 发送卡死 | while等待FIFO满时死循环 | 加超时机制,或检查SCIFFTX配置 |
| 中断不进入 | 忘记注册PieVectTable中断函数 | 初始化中PieVectTable.SCIRXINTB = ... |
| 中断不进入 | 忘记开IER | IER |
| 内部发送数据对,上位机显示NaN | 发送了未初始化的float | 初始化为0,或在发送前检查isnan |
| 每次上电第一次发送乱码 | 初始化顺序问题 | 先配置寄存器,最后SWRESET=1 |
5.2 经典坑一:SCI-B初始化顺序与FIFO复位
我调这块卡了整整一下午。现象是:初始化代码每一步都对,发送函数也执行了,但串口就是没输出。最后发现是我在配置SCIFFTX的时候,把SCI全局复位位(SCIRST)给覆盖了。
调试经过是这样的:我先设了SCICTL1.all = 0x0003,这时SCI没有完全退出复位;然后配置SCIFFTX.all = 0xE040,在这个值里面bit15是SCIRST,0xE040的bit15是1,说明SCIRST被置位了。看着没问题,但我又在代码最后执行了ScibRegs.SCICTL1.bit.SWRESET = 1;,这条语句本意是释放字符发送复位。问题在于,如果SWRESET在发送过程中被触发一次复位,FIFO配置就可能被清零,后续的TXFFST一直显示满,发送永远卡死。
反过来说,SWRESET不能随便在运行中操作。正确做法是:初始化期间保持SWRESET=0,所有寄存器配完以后一次性置1。后面运行阶段不要再动这个位,否则会出各种诡异问题。
5.3 经典坑二:FT232模块的兼容性问题
我有一个USB转TTL模块是FT232RL方案的,最初觉得FT232芯片高大上,不会出问题。结果在笔记本上通信一切正常,换到台式机上就连不上。一查发现,台式机主板背部USB口是USB 3.0,FT232RL芯片对USB 3.0的兼容性反而差,经常枚举失败。后来换了个USB 2.0Hub就好了。
另外提醒一下,很多廉价的FT232模块是盗版芯片,驱动打不上或者掉线,如果手头模块插电脑上在设备管理器显示“FT232R USB UART”但一会儿消失,基本就是盗版芯片。建议买之前先看看模块上的FTDI标志位是否清晰,靠谱的模块在Windows更新驱动后能正常识别。CP2102芯片相对没那么多盗版问题,实际工程中我更推荐CP2102方案。
5.4 经典坑三:Vofa+收不到数据但串口助手能收到
遇到过一次很吊诡的情况:我用SSCOM串口助手能收到DSP发的数据,但Vofa+就是没有波形。排查半天,发现Vofa+的数据格式选成了“FireWater”而不是“JustFloat”。FireWater解析规则和JustFloat完全不一样,它要求的帧格式更复杂,我的普通float数据进来解析不出来就是空白。
Vofa+对不同协议的数据显示是隔离的,切换协议时历史数据不会带入新协议。如果你从RawData切到JustFloat,原来的数据不会自动转换,必须重新刷新接收。遇见这种情况别慌,先把数据格式确认好,然后重新点一下“连接”。
5.5 经典坑四:数据发送频率过高导致波形毛刺
把发送周期调到100us一帧,Vofa+波形反而开始抖动,一卡一卡的。原因是上位机CPU处理不过来,尤其是笔记本开了降频省电模式,USB串口接收会丢包,丢包以后JustFloat的CRC校验不过,Vofa+就会丢弃整帧。
后来我把发送周期改为500us到1ms,波形既流畅又清晰。除非你确实需要超高速采样,否则1ms一帧完全够看动态响应了。如果非要高速上传,建议把多次采样的数据暂存FIFO,然后集中发送,相当于批量上传,效率会高很多。
5.6 给DSP发送数据时的常见误区
Vofa+的发送框也支持给单片机发数据。比如我想在界面上输入目标速度,就在发送框里写个数字字符串,DSP端用接收中断去解析。但这里有个特别容易踩的坑:Vofa+发送字符串时默认会附加换行符(\r\n),你DSP里如果用switch(rx_byte)匹配,就会在匹配到合法字符后紧接着收到0x0D和0x0A,导致逻辑错乱。
处理办法:在中断服务函数里过滤掉换行和回车,只处理可打印字符。
if ((rx_byte == '\r') || (rx_byte == '\n')) { return; }另外,Vofa+发送浮点数时通常会带小数点,比如“3.14\n”,DSP端如果只想接收整数,就要考虑好解析方式。我一般用Vofa+发送整数控制字,简单粗暴,不容易出错。
6. 进阶玩法与项目扩展方向
6.1 多通道PID调试图
这个是最有价值的应用。我之前调一个双闭环电机驱动,把给定转速、实际转速、电流环输出、母线电压四个量同时发到Vofa+。在界面上调整PID参数,看波形就能判断参数是否合适,不用每次改完参数都烧录看仿真器,效率提升非常明显。
调试PID的时候注意先把P加小一点,看波形是否发散,然后增加I消除稳态误差。Vofa+上的波形能很直观地看出超调量和振荡频率,比用CCS的Graph工具方便太多。
6.2 与GRBL上位机和SCADA上位机的区分
做了这个串口通信项目后,可能有人会问到GRBL上位机、SCADA上位机这些概念。简单说下我的理解:
- GRBL上位机是用来控制GRBL固件的CNC或者激光雕刻机,偏向运动控制命令下发
- SCADA上位机是用在工业组态里的,比如WinCC、组态王这类,偏向工艺流程监控和数据采集
- Vofa+这类串口波形工具是工程师调试用的,轻量、快速、所见即所得,适合开发阶段
三者场景完全不同。DSP调试串口这件事,用Vofa+就好,不用杀鸡用牛刀去上SCADA。
6.3 数据记录回放:把波形存下来
Vofa+支持把数据记录保存为CSV文件。在“记录”面板里可以启动记录,数据会被存成文本。我调完一轮波形后会把关键数据存下来,之后用Excel或者Python画图放到报告里,几百毫秒的阶跃响应曲线很清楚。
6.4 无线串口透传
如果DSP和电脑之间有距离限制,加了蓝牙透传模块或者ESP8266 WiFi透传,波特率建议降到9600或19200,射频模块的缓冲区有限,高速率容易丢数据。实测ESP8266透传模块能扛到115200,但延迟略高,对实时性要求高的场合还是用有线。
7. 写在最后的经验体会
串口通信算是DSP开发里最基础也最容易忽略的环节,但只要把寄存器细节理清楚,Vofa+的协议格式对上号,整个调试链路会变得异常顺手。我在这个项目里总结出几个习惯,分享给你参考:
第一,先把波特率和GPIO复用确认好再写代码,这是排查问题的第一道防线。第二,JustFloat协议发送的字节序一定要确认是小端,28335默认小端,Vofa+也是小端,两者对得上才不发疯。第三,所有while等待循环都加个超时计数,避免程序卡死在串口发送上,这个习惯救了我好多次。
后来我又把同样的逻辑扩展到了SCI-A、SCI-C和MCBSP模拟串口上,发现只要理解了寄存器配置的思路,换模块只是改几个地址的事。Vofa+这类的上位机工具也越来越多,但能在我手头留这么久,确实是因为它简单、好用、不折腾。
你在实际调串口的过程中如果遇到什么奇怪的问题,不妨回头看看是不是引脚复用和FIFO复位这两类问题,大多数“玄学”故障最后都能在这两个地方找到答案。