尧图精选

基于AVP28335的Modbus RTU Slave RS-485通信节点实现

🕒 发布时间:2026/10/2 9:09:44 📁 来源:尧图网络
做嵌入式这几年Modbus RTU 是我接触过最“皮实”的工业总线协议之一。无论是在 PLC、仪表还是变频器上几乎都能看到它的身影。这次要分享的项目是在 AVP28335兼容德州仪器 TMS320-28335 的国产替代型号上完整实现一个 Modbus Slave RTU-485 通信节点带源码和逐行注释。如果你正打算给手头的 28335 项目加上 485 通信或者想弄明白 Modbus 协议栈在 DSP 上到底怎么落地这篇文章应该能给你省下不少时间。先说说这个项目到底做了什么。AVP28335 这颗芯片和 TI 的 TMS320F28335 在引脚、寄存器、指令集上保持兼容所以编译、烧录、调试的流程基本和老款芯片一致。硬件上我用 SCIB 外设加一颗 SP3485 做 RS-485 电平转换软件上实现了 Modbus RTU Slave 的完整功能地址识别、03 功能码读保持寄存器、06 写单个寄存器、16 写多个寄存器、CRC16 校验、异常响应、485 收发方向切换全部源码都加了详细注释。整套代码可以直接移植到其他基于 28335 的控制板上只要串口引脚和 GPIO 方向控制脚按实际板卡调整即可。这个方案适合谁参考如果你是做电力仪表、变频器、运动控制器、传感器采集这类需要和上位机或触摸屏通信的项目需要在 DSP 上开一个标准的 Modbus Slave 口那么这套代码就是现成的模板。如果你只是刚接触 Modbus把它当成一个带注释的“活教材”来读也完全没问题。下面我把整个项目的设计思路、关键代码、踩过的坑一次说清楚。1. 项目整体设计与思路拆解1.1 为什么选 Modbus RTU RS-485在做工业设备通信时摆在面前的选择其实不少。CAN 总线抗干扰强、实时性好但需要专门的控制器和收发器协议栈也相对复杂。EtherCAT、Profinet 这类以太网总线性能强但对 MCU 的资源要求高而且授权和开发工具的门槛摆在那里。反观 Modbus RTU协议栈简单到可以用状态机轻松实现CRC 校验保证了数据完整性RS-485 物理层在 1200 米以内、115200bps 以下非常稳定而且几乎所有的组态软件、触摸屏、PLC 都原生支持 Modbus Slave开发联调几乎不费力气。还有一个很实际的考量28335 本身的 SCI 串口资源足够丰富不需要额外挂载外部协议芯片而 SP3485 这类 485 收发器的成本非常低外围电路也就几个电阻电容。用最少的硬件成本换一个几乎所有工业现场都能识别的通信接口这笔账怎么算都划算。AVP28335 的 SCI 模块也完全继承了 TI 的寄存器结构所以代码写好后在国产片子和老款 TI 芯片上跑的现象是完全一致的。1.2 系统的整体逻辑分层整个通信节点在逻辑上可以分成四层。最底层是串口驱动负责配置 SCIB 的波特率、数据位、校验位以及收发 FIFO 和中断第二层是物理层方向控制因为 485 是半双工总线发送时要把方向脚拉高接收时拉低这一步处理不好会出现自发自收或者丢字节的怪问题第三层是 Modbus 帧解析包括地址匹配、功能码分发、数据合法性检查、CRC16 校验最上层是寄存器映射也就是把外部主站读写的寄存器地址映射到 DSP 内部的实际变量上。这四层对应到代码里就是初始化函数、中断服务函数、协议解析函数、寄存器CB函数。每一层的职责非常单一出问题时很容易定位。有些初学者喜欢把所有逻辑都塞进串口中断里省了几行代码但后续排查问题的时候会很痛苦。中断里只做最基础的字节接收和超时判断协议解析放到主循环里做这样无论是调试还是后期加功能都从容得多。1.3 兼容性考虑的细节既然用了 AVP28335就绕不开兼容性这个话题。这颗芯片在设计上尽量向 TMS320F28335 看齐但一些国产替代型号在 ADC 的精度、PWM 的某些特性上会有细微差别这时候最稳妥的办法是拿到芯片手册后逐个对照你要用到的外设寄存器描述。我这次只用到 SCI 和 GPIO这两个外设在兼容性上做得比较完善代码没有做任何条件编译就正常跑通了。但如果你要用到 FPU 或者 CLA还是建议先看一下具体的勘误手册。另外晶振频率会影响串口波特率误差AVP28335 的片上 PLL 配置和 TI 原版一样都是通过 PLLCR 寄存器倍频到 150MHz SYSCLKOUT。实际测试中只要晶振精度在 20ppm 以内9600bps 到 115200bps 这几个常用档位的波特率误差都很小完全满足 Modbus 的通信要求。如果你手头的板子用了无源晶振最好用示波器量一下实际频率有时候便宜晶振的偏差会让通信变成“时好时坏”的玄学问题。2. 开发环境搭建与 RS-485 硬件准备2.1 AVP28335 的工程模板与 CCS 配置开发环境我用的还是 Code Composer Studio版本是 6.2 以上都行。新建工程时芯片型号直接选 TMS320F28335这样编译器会使用 C28x 的指令集和对应的头文件AVP28335 也能正常识别。工程里需要包含 TI 官方提供的 DSP2833x_common 和 DSP2833x_headers 两个库里面有全局寄存器结构体的定义比如 SchBRegs、GpioDataRegs没有这些文件写寄存器就像在黑暗里摸开关效率极低。需要注意的是28335 的中断向量表初始化、PLL 配置、看门狗初始化这些底层代码直接用 TI 的例程就行不必自己重写。我通常在 Device_init() 里完成所有外设时钟使能然后在主程序里关中断、初始化外设、再开中断。这里有一个容易忽略的坑28335 的 PIE外设中断扩展模块需要手动使能对应的中断组否则串口接收中断永远不会触发我在第一次调试时卡在这里半小时后来才反应过来是 PIE 没配好。2.2 RS-485 的接线与终端电阻硬件接线部分我用的是 SP3485 芯片也可以用 MAX3485引脚兼容。连接方式很简单DSP 的 SCIB TXD 接到收发器的 DIRXD 接到 RO一个 GPIO 引脚接到 RE 和 DE 的并联端用来控制收发方向。A、B 两根差分线接到外部总线A 接 A、B 接 B千万别接反否则通信完全不通。还有一种接反的现象是“偶尔能收到一两个字节”但整体还是不通这种时候优先检查 A/B 是否接反。在网络的两端需要各接一个 120 欧姆的终端电阻。如果只是两台设备短距离点对点测试接收端的 120 欧电阻一定要加上否则波形反射会导致误码。我曾经在一个项目里偷懒没加终端电阻短距离测着没问题线拉长到几十米后数据偶发错误加了电阻后立刻就好了。终端电阻不是可选配置在长线通信时是必须项。2.3 收发方向控制的硬件时序485 方向控制看似简单但细节里藏着不少坑。SP3485 的 DE 拉高时进入发送模式拉低时进入接收模式问题是模式切换需要时间。发送完最后一个字节后如果立刻把 DE 拉低发送移位寄存器里的最后一位可能还没完全发出去导致总线上最后一个字节丢尾主站就会报 CRC 错误。在代码里我发送完一帧后并没有马上操作 GPIO而是先清空 SCI 发送标志再做一个简单的延时延时时间大于一个字节的传输时间。以 9600bps 为例一个字节约 1.04ms我延时 2ms 再拉低 DE这样既保证数据完整发出也不会占用过多 CPU 时间。另外一个细节是接收模式下把 DE 拉低的同时RE 也拉低进入接收使能状态这个状态必须在初始化时就设置好否则一上电就是发送模式自己发出去的帧会立刻被自己的接收端收到产生逻辑混乱。3. Modbus RTU 协议核心与帧解析3.1 帧格式和时间约束Modbus RTU 的帧格式非常紧凑地址 1 字节、功能码 1 字节、数据 N 字节、CRC 低字节、CRC 高字节。地址范围 1 到 2470 是广播地址广播只执行不回复。对于 Slave 来说收到地址后先判断是不是自己的地址不是就直接丢弃。这里有一个容易忽略的点RTU 模式对帧间隔有严格要求两个字节之间不能超过 3.5 个字符时间否则就认为帧结束了两个帧之间至少要有 3.5 个字符时间的静默间隔。3.5 个字符时间怎么算以 9600bps、8 数据位、1 停止位为例一个字符包含 1 个起始位 8 个数据位 1 个停止位共 11 位元传输一个字符约 1.145ms3.5 个字符就是约 4.01ms。这个时间参数决定了接收超时的窗口。我在代码里用一个 1ms 的定时器中断作为时基每次收到一个字节就刷新“最后接收时间”变量主循环里判断当前时间减去最后接收时间是否超过 5ms超过则认为当前帧接收完毕进入帧处理流程。实际使用中5ms 比理论值稍微宽松一点给中断处理留了余量也不会误判正常连续字节流。3.2 常用功能码报文的逐字节拆解这次实现了三个功能码03读保持寄存器、06写单个寄存器、16写多个寄存器覆盖了绝大多数工业应用场景。以 03 功能码为例主站发送设备地址 0x01功能码 0x03寄存器起始地址高字节 0x00低字节 0x00寄存器数量高字节 0x00低字节 0x0ACRC 两字节。这串报文的意思是“地址 1 的设备请把你的保持寄存器 0 到 9 共 10 个寄存器的值发给我”。从站回复的报文是地址、功能码、字节数寄存器数量乘以 2、然后是按高字节在前、低字节在后的寄存器数据最后是 CRC。06 功能码是写单个寄存器主站发送地址、0x06、寄存器地址、写入值、CRC。从站收到后原样返回这一整帧表示写入成功。09 功能码 16 则是写多个寄存器主站发送的报文结构比 03 和 06 多一个字节计数位从站成功处理后的应答是地址 功能码 起始地址 寄存器数量 CRC不返回具体数据。3.3 接收状态机的设计思路我写的接收部分采用状态机思维但实现时其实很轻量。每次 SCIB 接收中断到来时把字节放入缓冲区然后根据状态迁移等待地址、等待功能码、等待数据、等待 CRC。实际代码里我不需要真的维护一个复杂状态机因为 Modbus 帧长度可以通过功能码推断出来所以我只用一个“期望字节数”变量收满为止。数据长度、起始地址、寄存器数量全部合法后再进行 CRC 校验。当然边界情况要小心。比如设备地址不匹配时我直接清空缓冲区但不清除“最后接收时间”而是让它自然超时这样就不会把下一帧的头部误判成当前帧的尾部。另一个做法是收到不匹配地址后立即重置接收状态但要确保帧间隔计时器仍然生效避免把本帧剩余的字节当成下一帧去处理。这两种方式我都试过前者代码上更简洁也不需要额外标志位。4. 核心源码实现带注释的关键部分4.1 SCI 初始化的完整代码void SCIB_Init(void) { // 1. 使能 SCIB 外设时钟 // 28335 上 SCIB 时钟源自 LSPCLK默认是 SYSCLKOUT/4 37.5MHz EALLOW; SysCtrlRegs.PCLKCR0.bit.SCIBENCLK 1; // 开启 SCIB 时钟 EDIS; // 2. 配置 SCIB 引脚为功能模式 // GPIO73 - SCIRXDB (接收) // GPIO72 - SCITXDB (发送) EALLOW; GpioCtrlRegs.GPBMUX2.bit.GPIO73 1; // 复用为 SCIB 接收 GpioCtrlRegs.GPBMUX2.bit.GPIO72 1; // 复用为 SCIB 发送 GpioCtrlRegs.GPBPUD.bit.GPIO72 0; // 使能上拉 GpioCtrlRegs.GPBPUD.bit.GPIO73 0; // 使能上拉 EDIS; // 3. 配置 SCIB 控制寄存器 SchBRegs.SCICCR.all 0x07; // 8 位数据、无校验、1 位停止位 SchBRegs.SCICTL1.all 0x03; // 使能 TX、RX SchBRegs.SCICTL2.bit.TXINTENA 1; // 使能发送中断但这里我主循环轮询发送也可以不开 // 4. 设置波特率 9600 // 公式BRR (LSPCLK / (波特率 * 8)) - 1 // 当 BRR 0 时使用 8 倍分频如果 BRR 0则改用 16 倍分频 // LSPCLK 37.5MHz目标波特率 9600 // BRR (37500000 / (9600 * 8)) - 1 488.28 - 1 487取整 SchBRegs.SCIHBAUD 487 8; // 写高字节 SchBRegs.SCILBAUD 487 0xFF; // 写低字节 // 5. 完成配置后重新使能 SCI SchBRegs.SCICTL1.all 0x23; // 唤醒 SCI同时保持 TX/RX 使能 }这里最重要的就是波特率计算。很多同学直接把 Modbus 例程里的波特率寄存器值抄过来结果换了外部晶振或者改了系统时钟后通信就乱码。波特率寄存器的值必须根据实际的 LSPCLK 频率算公式就写在注释里。LSPCLK 默认是 SYSCLKOUT 的四分之一也就是 150MHz / 4 37.5MHz但如果你修改了 LOSPCP 寄存器这个频率会变一定要同步调整。4.2 CRC16 Modbus 的查表实现CRC 校验是 Modbus 协议里最容易写错的部分。Modbus 的 CRC 多项式是 0x8005初始值为 0xFFFF结果的低字节在帧中先发送。有两种常见实现方式逐位运算和查表法。逐位运算代码短但速度慢查表法更快且代码更清晰。这里我给出查表法的实现。// CRC 高字节表共 256 项生成多项式 0xA001反映后的多边形 const unsigned char crc_hi[] { 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, // 完整表见源码共 256 项这里不全部展开 }; const unsigned char crc_lo[] { 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, // 完整表见源码 }; unsigned int ModbusCRC16(unsigned char *buf, unsigned int len) { unsigned int crc 0xFFFF; // 初始值固定为 0xFFFF while (len--) { crc (crc 8) ^ crc_hi[(crc ^ *buf) 0xFF]; crc (crc 8) ^ crc_lo[(crc ^ (crc 8)) 0xFF]; // 实际多字节表实现 } return crc; }严格来说标准 Modbus CRC 计算只需要一张 256 项的表把当前字节和 CRC 低字节异或后查表再和 CRC 右移 8 位异或即可。上面的两个表实现是正确的但代码稍显复杂。我更推荐单表实现占用的 ROM 少一半逻辑也更直观。关键点是计算出的 CRC 低字节在前高字节在后这点新手经常搞反导致主站一直回复超时。4.3 接收中断与帧超时判断#define MODBUS_RX_BUF_SIZE 256 volatile unsigned char modbus_rx_buf[MODBUS_RX_BUF_SIZE]; volatile unsigned int modbus_rx_len 0; volatile unsigned long modbus_last_rx_time 0; // 最后一次接收字节的时间戳 volatile unsigned char modbus_rx_complete 0; // 一帧接收完成标志 interrupt void SCIB_RX_ISR(void) { // 判断是否真的收到数据 if (SchBRegs.SCIRXST.bit.RXRDY 1) { unsigned char rx_data SchBRegs.SCIRXBUF.all; // 防止缓冲区越界 if (modbus_rx_len MODBUS_RX_BUF_SIZE) { modbus_rx_buf[modbus_rx_len] rx_data; modbus_last_rx_time millis_tick; // millis_tick 由定时器中断维护 } } // 清除中断标志 PieCtrlRegs.PIEACK.all PIEACK_GROUP9; // SCIB 接收中断属于 PIE 第 9 组 }在定时器中断里我会维护一个毫秒计时的变量 millis_tick。主循环中持续判断if (!modbus_rx_complete modbus_rx_len 0) { // 如果当前时间和最后一次收到字节的时间差超过 5ms认为帧结束 if ((millis_tick - modbus_last_rx_time) 5) { modbus_rx_complete 1; } }这个超时判断很关键。因为没有它主循环可能在字节流还没传完时就开始了帧处理导致解析出一个“半截帧”。Modbus 协议要求帧内字节间隔小于 3.5 个字符时间所以我取 5ms 作为超时窗口既符合协议也给主循环处理留出余量。4.4 帧处理与应答拼帧void Modbus_Process_Frame(void) { unsigned char addr, func, *pdata; unsigned int len modbus_rx_len; unsigned char *buf (unsigned char *)modbus_rx_buf; // 先清完成标志防止重入 modbus_rx_complete 0; modbus_rx_len 0; if (len 4) return; // 最短帧地址 功能码 CRC 2 字节 addr buf[0]; func buf[1]; // 地址匹配判断 if (addr ! MODBUS_SLAVE_ADDR addr ! 0x00) { return; // 不是发给自己的帧直接丢弃 } // CRC 校验注意帧尾部两字节是 CRC 低字节和高字节 unsigned int crc_received buf[len-1] 8 | buf[len-2]; unsigned int crc_calc ModbusCRC16(buf, len - 2); if (crc_received ! crc_calc) { return; // CRC 错误丢弃 } // 按功能码分发 switch (func) { case 0x03: Modbus_ReadRegisters(buf, len); break; case 0x06: Modbus_WriteSingleReg(buf, len); break; case 0x10: Modbus_WriteMultipleRegs(buf, len); break; default: Modbus_SendException(buf[0], 0x01); // 非法功能码 break; } }这段代码是整个协议栈的中枢。逐条来看先检查帧长度至少 4 字节再进行地址匹配。CRC 校验放在地址匹配之后可以避免对不相关帧做无意义的运算。对于功能码分发我用 switch 而非 if-else结构更清晰新增功能码时只需要加一个 case。对于非法功能码需要回复异常帧功能码是原功能码加上 0x80异常码 0x01 表示非法功能。4.5 485 方向切换与发送void Modbus_Send(unsigned char *buf, unsigned int len) { // 1. 拉高 DE进入发送模式 GpioDataRegs.GPBSET.bit.GPIO70 1; // 假设 GPIO70 连接 DE/RE // 2. 逐个字节发送 for (unsigned int i 0; i len; i) { // 等待发送缓冲为空 while (SchBRegs.SCICTL2.bit.TXRDY 0); SchBRegs.SCITXBUF.all buf[i]; } // 3. 关键延时等待最后一个字节完全移出 // 9600bps 下一个字节约 1.145ms这里延时 3ms 确保完全发完 DELAY_MS(3); // 4. 拉低 DE返回接收模式 GpioDataRegs.GPBCLEAR.bit.GPIO70 1; }第 3 步的延时是整个收发切换里最容易被忽视的地方。很多人在发送完最后一个字节后立刻拉低 DE结果主站那边总是报超时或 CRC 错误原因就是最后一个字节的末尾几个位元被 485 芯片截断了。延时时间必须大于 1 个字节的传输时间这个经验值在不同波特率下要作调整。5. 调试方法与问题排查实录5.1 用 Modbus Poll 做上位机联调调试 Modbus Slave最趁手的工具是 Modbus Poll它是 Windows 下的 Modbus 主站模拟软件免费版功能足够用。联调时我用 USB 转 485 线把电脑和 AVP28335 板卡连在一起先在软件里设置从站地址、功能码、寄存器地址和数量然后点击连接。如果是读寄存器界面上会实时刷新数值如果是写寄存器可以手动输入目标值。Modbus Poll 最大的价值在于它能显示报错类型超时、CRC 错误、异常响应码。我遇到过一种情况读写单个寄存器都正常但写多个寄存器就会超时。在 Modbus Poll 里能看到主站发出的报文格式和从站返回的报文格式一对比就发现我的应答帧里寄存器数量字节填错了改过来就通了。这种“照妖镜”式的调试方式比自己靠 log 推测快得多。5.2 我踩过的 5 个典型坑做成排查表现象可能原因排查方法完全收不到任何响应485 A/B 接反或 DE 方向脚配置错误先用回环测试把 A、B 短接看自发自收是否成功能收到响应但 CRC 错误波特率误差过大或者 CRC 字节序反了示波器量波形实测波特率检查 CRC 低字节在前偶发性丢帧成功率不到 70%帧间隔超时判断太短中断被其他高优先级任务抢占适当延长超时窗口比如从 5ms 改到 10ms写单个寄存器正常写多个寄存器超时16 功能码的请求长度计算错误或者应答帧字节数不对对照协议文档逐字节检查报文结构上电后立即自发自收DE 上电默认电平为高485 芯片处于发送状态在 GPIO 初始化时先拉低 DE再配置复用功能这里面最让我头疼的是第一个问题A/B 接反。有一次现场调试测了半小时都没通最后发现是一根转接线内部标记反了。从那以后我在所有 485 接线端子上都贴了明确标签并且在接口电路上加了 TVS 管和反接保护二极管避免因为接错线烧坏收发器。5.3 关于中断优先级和实时性的调整28335 的 PIE 中断分组已经定好了SCIB 接收中断在第九组。默认情况下所有中断的抢占优先级由 PIE 控制寄存器决定一般情况下不需要修改。但如果你的项目里同时有 ADC 中断、PWM 中断而且这些中断的服务函数执行时间很长就可能挤占串口接收中断的时间窗口造成帧内字节超过 3.5 字符间隔从而被误判为断帧。解决思路有两个最直接的方式是保证串口中断服务函数足够短我建议中断里只把数据搬到内存缓冲区并更新时间戳不进行任何解析另一个是在任务调度上给串口中断更高的 CPU 占用率比如在进入长时间运算的临界区前暂时关闭无关中断或者把耗时任务拆分成多个小步骤轮询执行。我见过不少工程师在中断里做浮点运算或者大量循环赋值这是非常危险的做法。5.4 逻辑分析仪在验证时序时的作用如果现场没有 Modbus Poll或者需要更底层的时序验证一定要善用逻辑分析仪。把逻辑分析仪的通道夹在 485 收发器的 RO 和 DI 引脚上可以清晰看到数据线上的原始波形和字节间隔。我用的是 24MHz 采样率的逻辑分析仪抓 9600 波特的波形绰绰有余。有一次为了验证 3.5 字符时间的超时窗口设置是否正确我抓了几个帧的数据量出字节与字节之间的间隔只有 1.2ms而帧与帧之间的间隔约 6ms这个 6ms 说明我的 5ms 超时判断没有误触发。但如果帧间隔只有 3ms我就要考虑把超时窗口进一步缩短到 4ms。这种用硬件波形反推软件参数的思路比靠肉眼观察现象去猜原因靠谱得多。6. 这套方案能用在哪些场景怎么扩展6.1 工业现场最常见的三个应用方向第一个方向是电力仪表的数据采集。把 AVP28335 作为某个配电柜里的采集网关读取电压、电流、功率等参数通过 Modbus RTU 上报给能源管理平台。保持寄存器地址可以规划成两字节一段比如 0x0000 到 0x000F 存放电压0x0010 到 0x001F 存放电流主站定时轮询读取。第二个方向是运动控制器的参数配置与状态监控。伺服驱动器或者步进驱动器通常需要一组参数比如目标位置、速度、加速度以及当前实际位置和报警状态。用 06 功能码写参数用 03 功能码读状态调试时用 Modbus Poll 就能实时修改 PID 参数不用改程序重新烧录。第三个方向是变流器和逆变器内部板卡之间的数据交互。在功率电子设备中控制板需要实时获取驱动板的状态但又不想用大量的硬连线通过 485 通信就可以把几十个变量打包传回来。Modbus 虽然没有 CAN 那种复杂的优先级仲裁但在多数变频器品牌里Modbus 依然作为标配的扩展接口存在。6.2 功能扩展增加保持寄存器的数量默认实现里我定义了 100 个保持寄存器地址范围是 0x0000 到 0x0063。如果你的项目需要更多寄存器只需要修改寄存器数组的大小并且在读写回调函数中做好地址偏移计算。注意Modbus 协议规定单个请求最多读取 125 个保持寄存器超过这个数量就要拆分成多个请求所以如果你需要一次读很多数据要对主站做相应的请求拆分。6.3 从单 Slave 到多 Slave 的组网一个 485 总线上可以挂多个从站每个从站地址不同。AVP28335 通常作为其中一个节点地址可以通过拨码开关设定。上电初始化时用 GPIO 读取拨码开关的状态作为 Modbus 从站地址这样同一块板卡在不同设备上可以免烧录直接更换。需要注意的是28335 的 GPIO 有上拉和下拉设置拨码开关的接线要注意电平逻辑避免默认地址和主站预期不一致。6.4 代码移植到其他 28335 板卡的注意事项不同的 28335 板卡串口引脚和 GPIO 编号可能不同。比如有的板子用 SCIA 而不是 SCIB有的将方向控制脚接到 GPIO34 而不是 GPIO70。移植时只需要改三处SCIA/SCIB 寄存器的名字、GPIO 复用配置的引脚编号、DE 方向控制脚的 GPIO 编号。协议解析部分完全不用动。如果你的板子上 SCI 引脚接了两个不同的 485 收发器还可以把 Slave 地址区分开实现一个 DSP 带两个 Modbus 从站端口的场景这在一些需要“桥接”的设备里很实用。7. 最后说几句实在话在做这个项目的过程中最大的体会是Modbus 协议本身并不复杂难的是把时序控制和异常处理做到位。只要把帧间隔计时、CRC 校验、485 方向切换这三个基本功练扎实后续不管是加功能码还是改波特率都只是小改动。另外国产芯片兼容 TMS320-28335 这件事实际跑起来并没有想象中那么多雷至少在这个项目里SCI 和 GPIO 的行为和原版几乎一致编译和烧录流程也没有任何阻碍。如果你也在调这个方案建议先用最短的代码把自发自收跑通确认物理链路没问题之后再逐步加入协议解析每加一层就测试一层。这样做的好处是一旦出现问题你心里很清楚是链路的问题、还是协议栈的问题而不是两眼一抹黑地乱试参数。希望这篇分享能帮你少走一些弯路。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →