
简介一份基于RS485的双串口通信协议转换源码工程面向嵌入式开发者与工业自动化学习者用于解决两种设备因通讯协议不一致而无法直接数据互通的问题。工程中既有协议解析、转换、错误处理与实时性优化的完整C语言实现也提供可烧录的hex/bin固件适合在STC8F等单片机平台上验证与二次开发。源码包含串口驱动、协议帧解析、转换逻辑、看门狗及IAP等模块配套MD说明文档与Keil工程文件可帮助读者理解RS485组网、MODBUS与自定义协议之间的协议栈设计与移植思路。资源共21个文件以8个头文件和6个C源文件为主体配合UVOPT工程配置与二进制文件整体仅61KB结构紧凑便于快速上手。目前已有115人学习/下载适合作为工业现场协议转换器设计的参考范例。1. 双串口协议转换卡住的从来不是电平而是帧的重组很多 RS485 转换项目在实验室能通一挂到现场就跑飞现象千篇一律偶发丢包、上报延迟、设备死锁。问题通常不在 A/B 线接反或波特率不匹配而在「帧边界」怎么认定、两路串口的数据怎么互不阻塞地流转。这个资源里的转换器基于 STC8F 双串口单片机一路接传感器类设备sms310.c 对应的协议另一路接 GPRS 类模块usr_gprs_730.c把两边的数据在 MCU 内部完成接收、解析、重组成对方认识的帧格式。RS485 只是物理层载体真正决定项目成败的是双串口的中断接收策略、环形缓冲区的容量规划和状态机收帧方式。这套代码适合要自己写转换固件的工程师而不是只想买现成模块的集成方。2. 从物理层到引脚分配STC8F 双串口与 RS485 自动收发电路设计2.1 RS485 与 TTL 串口的本质差别RS485 通讯协议详解通常都会强调差分信号。TTL 电平用单端 0/3.3V 表示逻辑而 RS485 用 A、B 两线之间的电压差表示逻辑 1 和 0A 比 B 高 200mV 以上为逻辑 1反之为逻辑 0。这个差别带来两个直接后果一是共模干扰被差分接收抵消线缆上的噪声不会直接转成错误电平二是支持多点组网一主多从的 RS485 组网在线缆上可以拉到 1200 米节点数取决于收发器负载常见 MAX485 支持 32 个节点。很多人直接把 TTL 转 RS485 模块插在单片机上用模块内部完成电平转换理论上没有问题但有两个限制。第一模块的收发方向切换由 DE/RE 引脚控制如果控制时序没有处理好发送时会把数据环回到自己的 RXD形成错误帧第二现成模块把串口引脚占死双串口方案里两个 UART 都要接 RS485 时引脚复用和方向控制就得重新规划。对这个项目来说STC8F 的两组 UART 分别接两种设备其中至少一路要用 RS485 物理层因此收发器的选择和控制方式需要在硬件阶段定下来。2.2 双串口引脚分配与中断分组STC8F 是 51 内核增强型单片机自带两个独立 UART每个都有独立的中断向量和波特率发生器互不干扰。常见做法是用 UART1 接传感器侧sms310UART2 接 GPRS 侧usr_gprs_730两个串口的接收中断都打开各自把数据塞进独立的环形缓冲区主循环再从缓冲区取帧处理。这样即使一侧的协议解析比较耗时另一侧的字节也不会丢。外设引脚方向说明UART1_TXDP3.1输出接 MAX485 的 DI或接 TTL 设备 RXUART1_RXDP3.0输入接 MAX485 的 RO或接 TTL 设备 TXUART1_DEP3.2输出控制 MAX485 收发方向高为发送UART2_TXDP1.1输出接另一路设备UART2_RXDP1.0输入接另一路设备UART2_DEP1.2输出第二路方向控制按需启用引脚分配的原则是两个串口的收发引脚尽量分散在不同端口方便布线方向控制 IO 紧挨着对应串口引脚减少走线长度避免高速切换时电平震荡。STC8F 的端口模式要设置为准双向口方向控制 IO 输出高电平时进入发送态低电平时回到接收态。2.3 发送方向控制的时序要点RS485 方向切换的经典坑是先把 DE 拉高立刻写 SBUF 发送最后一字节发完后马上拉低 DE结果最后一字节的停止位被截断接收方报帧错误。原因是写 SBUF 只是把数据交给移位寄存器移位还需要时间方向引脚的电平翻转必须覆盖完整的停止位。void rs485_send_byte(UART_Type uart, uint8_t dat) { if (uart UART1) { DE1 1; // 拉高方向进入发送态 SBUF1 dat; // 写入发送缓冲触发移位输出 while (!TI1); // 等待发送完成中断标志置位 TI1 0; // 手动清标志否则下一次发送会误判 DE1 0; // 发送完成后拉低回到接收态 } }这段代码的逻辑是方向引脚在写入数据前先拉高保证第一个起始位出现时总线已经被驱动while (!TI1)等待的是移位寄存器输出完毕此时停止位已经完整发出再拉低方向就是安全的。TI1必须手动清零因为中断服务里如果没清下一次进这个函数会跳过等待方向切换提前造成帧尾丢失。波特率越高方向切换窗口越窄9600 波特率下一个位时间是 104 微秒115200 下只有 8.7 微秒所以这个等待逻辑不能省。3. 协议解析与帧重组从传感器协议到 GPRS 帧的字段映射3.1 先分清「协议转换」到底转什么协议转换不是把 A 串口的字节挪到 B 串口就算完。字节级别的直接转发叫作透明传输只有两端协议完全一致时才成立。常见的 Modbus 通讯协议转其他协议的网关设备做的也是帧级别的拆解和重组。Modbus RTU 帧结构包含地址码、功能码、数据区和 CRC16 校验而许多传感器私有协议有自己的帧头、长度域和校验方式两者之间字段意义不同、字节序不同、校验算法不同直接转发必然导致对端解析失败。这个资源里涉及的两端从文件命名看是 sms310 类传感器协议和 usr_gprs_730 类 GPRS 模块协议。GPRS 设备通常接收的是透明数据或带 AT 命令的帧传感器侧则是周期上报的测量数据。转换器要做的事情可以拆成四步从传感器侧收到一帧完整数据校验并提取有效负载把负载按 GPRS 侧的帧要求重新封装通过另一路串口发出去。反向链路同理GPRS 侧下发的控制指令也要转成传感器协议能识别的帧格式。3.2 用状态机收帧而不是等字节串口接收中断里最常见的错误写法是每收到一个字节就处理一个字节把组帧逻辑放在中断里。中断是优先级最高的上下文任何耗时操作都会拖垮另一路串口而且帧是多个字节的组合单字节处理无法判断帧边界。正确做法是中断只做搬运把字节放进环形缓冲主循环用状态机从缓冲里取字节、拼帧、判断完整性。typedef enum { FRAME_WAIT_HEAD, FRAME_COLLECT, FRAME_DONE } FrameState; FrameState state FRAME_WAIT_HEAD; uint8_t frame_buf[64]; uint8_t frame_len 0; void protocol_parse_byte(uint8_t dat) { switch (state) { case FRAME_WAIT_HEAD: if (dat 0xAA) { // 帧头匹配 frame_buf[0] dat; frame_len 1; state FRAME_COLLECT; } break; case FRAME_COLLECT: frame_buf[frame_len] dat; if (frame_len 4 dat 0x55) { // 帧尾匹配 state FRAME_DONE; } break; default: state FRAME_WAIT_HEAD; break; } }这段状态机的逻辑是只有出现帧头字节才进入收集状态否则丢弃所有数据收集过程中持续往缓冲里填充直到出现帧尾或长度超限。帧头帧尾的匹配条件只是示例实际以 sms310 的协议定义为准。状态机的好处是即使线路噪声插入随机字节只要下一帧的帧头正确接收逻辑就能自动重新同步不需要每次失步都重启。3.3 字段映射与校验重算帧完整接收之后下一步是提取字段并映射到目标协议。这里要特别留意字节序问题很多传感器协议的数据域是低字节在前而 GPRS 报文要求高字节在前直接复制过去会把数值读错。常见做法是把源帧解析成结构体再从结构体生成目标帧。源帧字段偏移长度目标帧字段转换规则帧头 0xAA01帧头 0x7E直接替换数据长度11长度域重算目标帧字节数传感器 ID22设备地址小端转大端温度值42数据域乘以 0.1 精度系数校验和N-11CRC16按目标协议重算映射时最容易踩的坑是沿用原协议的校验域。Modbus 用 CRC16很多私有传感器用简单累加和校验算法不同整个帧的合法性判断就完全不同。转换器必须在重组完成后按目标协议重新计算校验字节序规则也要一并调整否则对端会直接丢帧。4. 代码落盘uart1.c、uart2.c、main.c 的分工与数据流4.1 项目文件结构与功能边界这套代码的工程结构划分得很清楚uart1.c 和 uart2.c 只负责串口初始化和收发main.c 负责主流程和协议转换iap.c 负责参数存取wdt.h 封装看门狗。这样分层的目的是把硬件操作和业务逻辑剥离开调试时只需要盯住一个文件。文件职责关键接口uart1.c / uart2.c波特率配置、中断接收、发送uart_init()、rs485_send_byte()main.c主循环、协议转换状态机main()、protocol_convert()iap.c / iap.hEEPROM 读写参数eeprom_read()、eeprom_write()wdt.h看门狗初始化与喂狗wdt_init()、wdt_feed()sms310.c / usr_gprs_730.c两种协议的帧解析与封装sms310_parse()、gprs_pack()main.c 是整个项目的胶水层。上电后先初始化两个串口和看门狗再从 EEPROM 读取波特率和设备地址参数然后进入主循环先从 sensor 缓冲取帧解析组装成 GPRS 帧发送再从 gprs 缓冲取帧解析组装成 sensor 帧发送。两个方向的处理不能串行太久否则另一边缓冲会溢出。4.2 双串口中断接收与环形缓冲环形缓冲是串口代码里的标准组件。中断里只做一件事把收到的字节写入环形缓冲的写指针位置然后移动写指针并更新计数。主循环里读走数据后再移动读指针。这样收发双方不需要互相等待中断也不会因为主循环忙而丢字节。#define RING_SIZE 256 typedef struct { uint8_t buf[RING_SIZE]; volatile uint8_t head; volatile uint8_t tail; } RingBuf; void ring_write(RingBuf *q, uint8_t dat) { uint8_t next (q-head 1) % RING_SIZE; if (next ! q-tail) { // 留一个空位判断满 q-buf[q-head] dat; q-head next; } } void uart1_isr() interrupt 4 { if (RI1) { RI1 0; ring_write(uart1_rx, SBUF1); } }这段代码的逻辑是环形缓冲用 head 表示下一个写入位置tail 表示下一个读取位置二者相等时缓冲为空(head 1) 等于 tail 时缓冲为满所以实际可用空间是 SIZE - 1。中断服务函数只清接收标志、读数据、写缓冲不做协议解析保证中断处理时间在微秒级。256 字节的容量在 9600 波特率下大约能存 230 毫秒的数据对常见的周期上报帧来说已经足够。4.3 主循环里的转换工作流与看门狗喂养主循环是这套代码的调度核心。看门狗在这里喂协议转换也在这里做还有一个容易被忽略的点两路串口的处理必须公平不能因为一路数据多就一直处理这一路导致另一路缓冲溢出。常见的做法是两个方向交替处理或者给每个方向的转换加一个时间片上限。void main(void) { wdt_init(); eeprom_load_config(); uart_init_all(); while (1) { wdt_feed(); if (ring_has_data(uart1_rx)) { process_sensor_frame(); } if (ring_has_data(uart2_rx)) { process_gprs_frame(); } } }主循环的逻辑是每次循环先喂狗防止程序跑飞后设备死锁然后分别检查两个环形缓冲是否有数据有则取帧处理。这里的 process_sensor_frame 和 process_gprs_frame 内部各自完成帧同步、校验、字段映射和发送整个过程不阻塞。喂狗的位置必须在循环的最前面或最后面不能放在某个协议处理的中间——如果某帧数据异常导致处理函数卡住看门狗反而被正常喂养失去保护意义。EEPROM 的作用在这个项目里容易被忽略。eeprom.bin 和 iap.c 的存在说明设备支持断电保存参数比如设备地址、波特率、上报周期。启动时通过 IAP 读取配置并初始化寄存器运行时如果收到参数修改指令写入 EEPROM 并重新生效。这样同一个固件可以适配不同场景不需要重新编译烧录。5. 联调排查用逻辑分析仪验证 RS485 方向切换与帧对齐5.1 波形上看三个关键时间点RS485 自动收发电路图看得再多不如实际抓一次波形。调试时把逻辑分析仪的通道分别接 A 线、B 线和方向控制脚重点观察三个时刻发送起始位前方向引脚是否已经拉高最后一字节停止位结束后方向引脚是否才拉低接收过程中方向引脚是否一直保持低电平。方向引脚早拉或晚拉波形上都会出现明显的毛刺或截断。5.2 常见故障对照表故障现象可能原因排查方式对端收不到数据方向控制时序错误停止位被截断逻辑分析仪看停止位完整性自己收到自己发的内容DE 未及时拉低或自动收发电路翻转慢检查方向引脚是否在发送后立即回到低电平偶发丢字节环形缓冲容量不足或主循环处理不及时加大缓冲或在读指针处查看丢弃计数帧校验总失败字节序未转换或校验算法未切换对比源帧和目标帧的校验域字节距离拉长后误码缺少终端电阻或偏置电阻总线两端并联 120Ω 终端电阻5.3 一个值得保留的习惯调试双串口协议转换时我一般会在环形缓冲的写入侧加一个自增计数器主循环定期读取这个值并打印出来。如果计数值增长但帧解析函数没有输出说明帧头或帧尾匹配条件有问题如果计数值停止增长说明中断没有进来或方向控制把数据吞掉了。这两个判断可以快速缩小问题范围比反复改代码烧录高效得多。最后把两个串口的波特率都降到 9600 重新过一遍测试用例排除高速下的信号完整性问题再逐步升回目标速率确认边界。本文还有配套的精品资源点击获取