news 2026/9/5 23:29:56

全志T113 Linux下RS485通信实战:从设备树配置到Modbus RTU应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
全志T113 Linux下RS485通信实战:从设备树配置到Modbus RTU应用

简介:本资源是一份面向嵌入式Linux开发者与工业通信项目工程师的RS485串口通信实战代码包,聚焦全志T113-S3平台(基于米尔MYD-YT113X开发板),解决Linux环境下RS485收发控制、驱动适配及阻塞/非阻塞模式切换等核心问题。压缩包共8个文件(6KB),含4个C源文件(main.c、uart.c、thread.c、log.c)、3个头文件(uart.h、thread.h、log.h)和1个Makefile,分别承担主控逻辑、串口配置与读写、多线程调度、日志输出及编译构建功能,结构清晰、模块解耦。已有53人学习下载,代码已通过实测验证,支持快速移植至其他Linux嵌入式平台——仅需调整硬件引脚定义即可复用。读者可直接获取完整可运行工程,掌握RS485在Linux中的ioctl控制、termios参数设置、文件描述符阻塞属性切换等关键实践技能,并参考双模式实现范式优化自身通信模块设计。 最近在一个工业数据采集项目上,用全志T113跑Linux,需要和现场的一批电表通过RS485总线通信。这个活儿听着简单,真正调起来才发现,RS485在Linux下要玩明白,涉及的东西远不止打开串口读写那么简单——从内核设备树配置、半双工方向切换时序,到总线组网和故障排查,每一层都有坑等着你。

这篇博文把整个开发过程中最核心的东西完整整理出来:从T113平台RS485的硬件原理、内核侧配置,到应用层C代码实现,再到实测调试和组网经验,全部是能直接拿去用的东西。适合正在做全志T113嵌入式Linux开发,或者需要在Linux下搞定RS485通信的朋友参考。

1. 项目全貌与方案选型:为什么用T113做RS485通信

1.1 全志T113平台特性与RS485的应用场景

全志T113是一颗双核ARM Cortex-A7处理器,主频最高1.2GHz,内置DDR3,还带了一个RISC-V协处理器。这颗芯片在工控领域出镜率很高,常见于工业HMI、数据采集终端、电力集中器、楼宇对讲等设备。它引出了多路UART、SPI、TWI、USB、GMAC、CAN等接口,其中UART就有8路,这对需要同时挂多路串口外设的场景非常友好。

我这次的项目是一个数据采集网关,需要采集Modbus RTU协议的智能电表数据。为什么选RS485而不是RS232或者CAN?核心原因有三个:第一,RS485是差分信号,抗干扰能力强,工业现场电机启停、变频器工作带来的电磁干扰很凶,RS232在这种环境下基本撑不住;第二,RS485支持多点组网,一条总线上可以挂32个节点(用128节点的收发器可以挂更多),电表数量多的时候不用每个设备都拉一根线到主机;第三,传输距离远,9600bps下理论距离可以到1200米,而RS232通常只能到15米左右。所以在工业现场,RS485几乎是仪表通信的事实标准。

T113跑Linux系统做RS485通信,相比用单片机有什么优势?最直接的一点是协议栈开发效率高。单片机裸机或者RTOS下做Modbus协议解析、超时重传、帧校验,全得自己写,调试工具也弱。而Linux下直接用POSIX标准串口API,配合多线程、select/epoll这些机制,写一个健壮的采集程序轻松得多。另外T113的性能摆在那里,跑个Modbus主站程序绰绰有余,还有余力跑MQTT上报、Web配置界面这些功能。

1.2 RS485通信方案的整体设计思路

整个RS485通信方案,我把它分成三层来看:

  • 硬件层:RS485收发器电路设计,负责TTL电平与差分电平的转换,常见芯片有SP3485、MAX3485、ISL83485等。
  • 内核层:设备树配置串口引脚、使能RS485模式,驱动层面处理数据收发和方向切换。
  • 应用层:C程序调用POSIX串口API,实现Modbus RTU协议的数据组装、发送、接收、解析。

为什么要把方向切换放到内核层?这是这次开发中最重要的一个设计决策。RS485是半双工通信,同一时刻只能发送或者接收。方向切换有两种实现方式:一种是应用层用GPIO控制收发器的DE/RE引脚,发数据之前拉高,发完再拉低;另一种是让内核在驱动层面自动处理方向。我强烈建议用第二种,让内核的8250串口驱动通过RTS信号自动控制收发器的DE/RE引脚。原因后面详细说,这里先记住一个结论:应用层用GPIO控制方向,在时序上很难做到精确,实际使用中容易出现数据丢字节、错位一类的问题。

2. RS485底层机制与内核侧配置:先把地基打牢

2.1 RS485电气特性与半双工方向控制原理

RS485标准定义的是A、B两根差分信号线上的电平关系。逻辑1(也就是空闲状态)时,A-B之间的电压差在+2V到+6V之间;逻辑0时,A-B电压差在-2V到-6V之间。接收端的判定阈值是±200mV,也就是说只要A-B压差大于200mV就认为收到逻辑1,小于-200mV就收到逻辑0。这个200mV的滞回区间,就是RS485抗干扰能力的来源之一。

半双工通信的工作方式,可以类比成对讲机:同一时间只能有一个人说话,其他人听。要说的时候按住PTT键(发射按键),说完松开回到接收状态。RS485的收发器也有一个方向控制引脚,通常叫DE(Driver Enable,发送使能)和RE(Receiver Enable,接收使能),很多时候这两个引脚是连在一起的。DE为高电平时,收发器把差分总线上的信号驱动出来;DE为低电平时,收发器处于接收状态,把总线上的差分信号转换成TTL电平送给SoC的RX引脚。

方向切换的实现方式大致有三种:

  • 硬件上把SoC的RTS引脚连接到收发器的DE/RE,由内核驱动在发送前自动置高RTS、发送后自动拉低,这是最推荐的方式。
  • DE/RE接到普通GPIO,由应用层程序翻转电平。
  • 不想占任何控制引脚,用TXD信号经过逻辑电路自动控制DE,这种方式省引脚,但遇到总线竞争时容易出问题,对信号质量要求高。

我的板子硬件设计上就是RTS控制DE/RE,所以走的是第一种方式,配合内核的RS485支持机制,应用层完全不需要关心方向切换。

2.2 内核串口驱动的RS485支持与设备树配置

T113的内核,我用的SDK是Tina Linux,内核版本5.4。这个版本的8250串口驱动已经原生支持RS485模式,会在发送数据时自动控制RTS引脚的电平来切换收发方向。在设备树里打开对应的串口节点时,加上RS485相关属性即可。

设备树配置典型示例如下:

&uart1 { pinctrl-names = "default"; pinctrl-0 = <&uart1_pb_pins>; linux,rs485-enabled-at-boot-time; rs485-rts-active-low; rs485-rts-delay = <1 1>; status = "okay"; };

这里几个属性逐个说明。linux,rs485-enabled-at-boot-time表示内核启动后这个串口就自动处于RS485模式,不需要应用层再通过ioctl去使能。rs485-rts-active-low表示RTS信号低电平有效时是发送状态,这个取决于你的收发器DE/RE脚和RTS之间的接线方式——如果DE接RTS且高电平有效,则不需要这个属性;如果DE接的是RTS反相信号(比如经过了三极管或者逻辑门反相),就得加这个属性。rs485-rts-delay = <1 1>表示发送前延时1毫秒、发送后延时1毫秒。延时是为了给收发器芯片留出切换时间,短了会出现首字节被吞或者末字节被截的怪问题。

T113的引脚复用功能和默认串口配置,可以在板级dts中找到。以我用的板子为例,UART1的引脚被复用到了PB组:

pinctrl-0 = <&uart1_pb_pins>;

这个uart1_pb_pins在pinctrl相关dtsi文件中定义,选择的是PB0和PB1两个引脚,分别对应UART1的TX和RX。

设备树配置完成后,编译并烧写内核和dtb,先在板子上确认一下驱动是否真的以RS485模式初始化了:

# 查看内核启动日志中与该串口相关的内容 dmesg | grep uart1 dmesg | grep ttyS1 # 确认设备树中RS485属性是否生效 ls /proc/device-tree/uart1/ cat /proc/device-tree/uart1/linux,rs485-enabled-at-boot-time

如果设备树里配了但启动日志里没有相关提示,优先检查compatible属性是不是snps,dw-apb-uart,T113的8250驱动走的是DesignWare UART这个路径。如果本身没有打印RS485相关信息,也可以通过应用层ioctl来确认——能成功执行TIOCSRS485 ioctl就是支持。

2.3 GPIO方式控制RS485方向作为后备方案

如果你的板卡硬件设计上,没有把RTS连到收发器的DE/RE,而是单独引了一个GPIO出来控制方向,那就只能走应用层GPIO控制的方案了。这种方式也能用,但在编码和调时序上要多费不少心思。

T113上操作GPIO,新版SDK推荐用libgpiod,比老旧的sysfs方式规范得多。核心逻辑如下:

/* 发送数据前,将DE/RE引脚拉高,进入发送模式 */ gpiod_line_set_value(de_line, 1); write(fd, buf, len); tcdrain(fd); /* 必须等待发送完成 */ /* 恢复为接收模式 */ gpiod_line_set_value(de_line, 0);

用GPIO方案最需要注意的是方向切换的时序。RTS方案之所以推荐,是因为内核驱动在发送数据时会自动精确地在数据发送完成后拉低RTS,而应用层GPIO控制则完全依赖程序员的代码质量。tcdrain这里绝对不能省,它会让程序阻塞直到发送缓冲区全部数据真正发出去。我曾经偷懒没加这一句,结果数据发到一半GPIO就被拉低了,总线上出现半截帧,从机端直接CRC错误。

写完这段再啰嗦一句:如果你有板子硬件改板的机会,尽量让DE/RE走RTS,省掉应用层的麻烦,这是无数实践换来的经验。

3. 应用层RS485通信代码实现:可直接抄作业

3.1 串口打开与参数配置

应用层开发的第一步是把串口设备打开并配置好参数。全志T113对应的串口设备节点:UART0对应/dev/ttyS0,UART1对应/dev/ttyS1,以此类推。如果SDK里启用了别的别名机制(比如/dev/ttyAS1),以实际系统为准,可以用ls /dev/ttyS*确认。

下面是通用的串口打开和配置函数,兼容POSIX标准,全志平台上直接能用:

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <termios.h> #include <sys/ioctl.h> #include <linux/serial.h> #include <errno.h> static int uart_open(const char *path, int baudrate) { int fd; struct termios options; fd = open(path, O_RDWR | O_NOCTTY | O_NDELAY); if (fd < 0) { perror("open serial"); return -1; } /* 恢复为阻塞模式,让read在没有数据时挂起等待 */ if (fcntl(fd, F_SETFL, 0) < 0) { perror("fcntl F_SETFL"); close(fd); return -1; } if (tcgetattr(fd, &options) != 0) { perror("tcgetattr"); close(fd); return -1; } /* 原始模式:不做行处理、不做特殊字符解析 */ cfmakeraw(&options); /* 本地连接,使能接收 */ options.c_cflag |= (CLOCAL | CREAD); /* 1位停止位,无校验,8位数据位 */ options.c_cflag &= ~CSTOPB; options.c_cflag &= ~PARENB; options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; /* 关闭硬件流控,RS485不使用RTS/CTS流控 */ options.c_cflag &= ~CRTSCTS; /* 设置波特率 */ cfsetispeed(&options, baudrate); cfsetospeed(&options, baudrate); if (tcsetattr(fd, TCSANOW, &options) != 0) { perror("tcsetattr"); close(fd); return -1; } /* 清空缓冲区,避免残留数据干扰 */ tcflush(fd, TCIOFLUSH); return fd; }

几个关键参数的说明值得展开。cfmakeraw这个函数会把终端设置成“裸”模式——关闭ECHO、关闭ICANON(行缓冲模式)、关闭ISIG(信号生成)、关闭IEXTEN等。串口通信必须用raw模式,否则你发0x03这样的字节会被系统当成Ctrl+C信号处理掉,数据就丢了。CLOCALCREAD一个表示这是本地连接不考虑调制解调器控制信号,一个表示使能接收器,这两个标志位必须设置,否则可能出现打不开或者读不到数据的情况。

波特率参数直接调cfsetispeedcfsetospeed。注意波特率是整数常量,比如B9600B115200。如果要用非标准波特率,需要走termios2的接口,但RS485应用基本用标准波特率,这里不展开。

3.2 启用内核RS485模式

如果设备树里没有配置linux,rs485-enabled-at-boot-time,或者你想在应用层动态控制RS485模式的开关,可以通过TIOCSRS485这个ioctl命令来设置。TIOCSRS485是Linux内核提供的一套标准RS485配置接口,结构体定义在linux/serial.h中。

static int rs485_enable(int fd) { struct serial_rs485 rs485conf; memset(&rs485conf, 0, sizeof(rs485conf)); rs485conf.flags |= SER_RS485_ENABLED; rs485conf.flags |= SER_RS485_RTS_ON_SEND; rs485conf.delay_rts_before_send = 1; rs485conf.delay_rts_after_send = 1; if (ioctl(fd, TIOCSRS485, &rs485conf) < 0) { perror("TIOCSRS485"); return -1; } return 0; }

这里SER_RS485_RTS_ON_SEND的意思是发送时RTS置高。如果你的硬件是反逻辑,改成SER_RS485_RTS_AFTER_SEND或者在flags里不设置RTS_ON_SEND。delay_rts_before_senddelay_rts_after_send单位是毫秒,分别表示RTS生效后延时多久才开始发数据、数据发送完成后延时多久再释放RTS。收发器芯片的切换时间通常在几百纳秒到几微秒之间,但考虑RS485总线电容负载和信号稳定时间,设成1毫秒比较稳妥。

设备树属性和应用层ioctl是等效的,区别只是使能时机。设备树配好后系统起来RS485模式就是开的,应用层不用管;用ioctl则完全是应用层控制。我建议设备树里就配成linux,rs485-enabled-at-boot-time,并配合延时参数调好,应用层代码更干净。

3.3 发送与接收的核心逻辑

RS485通信中,发送和接收两个函数是基本功。写发送函数时,最关键的一点是发送完成后要调用tcdrain(fd)等待数据真正从串口硬件发出去。因为write只是把数据拷贝到内核发送缓冲区,如果写完立刻切方向或者关闭串口,数据可能还没发完就被废弃了。

static int rs485_send(int fd, const uint8_t *buf, int len) { int ret; if (fd < 0 || buf == NULL || len <= 0) return -1; ret = write(fd, buf, len); if (ret < 0) { perror("write"); return -1; } /* 关键:等待数据全部发送完成 */ if (tcdrain(fd) != 0) { perror("tcdrain"); return -1; } return ret; }

接收函数用select加超时,避免阻塞在read上。

static int rs485_recv(int fd, uint8_t *buf, int len, int timeout_ms) { fd_set rfds; struct timeval tv; int ret; FD_ZERO(&rfds); FD_SET(fd, &rfds); tv.tv_sec = timeout_ms / 1000; tv.tv_usec = (timeout_ms % 1000) * 1000; ret = select(fd + 1, &rfds, NULL, NULL, &tv); if (ret == 0) { errno = ETIMEDOUT; return -1; /* 超时无数据 */ } if (ret < 0) { perror("select"); return -1; } return read(fd, buf, len); }

这里用了select而不是直接read阻塞,好处是有超时控制。Modbus RTU主站在发请求帧后,需要在规定时间内等待从机应答,超时了要重试或者报错。没有超时机制,一旦从机掉线,程序就会一直卡在read上,整个采集逻辑就瘫痪了。

下面是一个完整的Modbus RTU读保持寄存器(功能码0x03)的帧组装和CRC计算实现:

static uint16_t modbus_crc16(const uint8_t *data, uint16_t len) { uint16_t crc = 0xFFFF; uint16_t i; uint8_t j; for (i = 0; i < len; i++) { crc ^= data[i]; for (j = 0; j < 8; j++) { if (crc & 0x0001) crc = (crc >> 1) ^ 0xA001; else crc >>= 1; } } return crc; } static int modbus_read_holding_regs(int fd, uint8_t slave_addr, uint16_t reg_addr, uint16_t reg_count, uint8_t *rx_buf, int rx_len, int timeout_ms) { uint8_t tx_buf[8]; uint16_t crc; int len; int ret; /* 组装请求帧:地址 + 功能码 + 寄存器地址 + 寄存器数量 + CRC */ tx_buf[0] = slave_addr; tx_buf[1] = 0x03; tx_buf[2] = (reg_addr >> 8) & 0xFF; tx_buf[3] = reg_addr & 0xFF; tx_buf[4] = (reg_count >> 8) & 0xFF; tx_buf[5] = reg_count & 0xFF; crc = modbus_crc16(tx_buf, 6); tx_buf[6] = crc & 0xFF; tx_buf[7] = (crc >> 8) & 0xFF; len = rs485_send(fd, tx_buf, sizeof(tx_buf)); if (len != sizeof(tx_buf)) { fprintf(stderr, "send modbus frame failed\n"); return -1; } ret = rs485_recv(fd, rx_buf, rx_len, timeout_ms); if (ret < 0) { fprintf(stderr, "recv timeout or error\n"); return -1; } /* 校验应答帧长度和CRC */ if (ret < 5 || (rx_buf[1] & 0x80)) { fprintf(stderr, "modbus exception or short frame\n"); return -1; } if (rx_buf[0] != slave_addr) { fprintf(stderr, "slave address mismatch\n"); return -1; } crc = modbus_crc16(rx_buf, ret - 2); if ((rx_buf[ret - 2] != (crc & 0xFF)) || (rx_buf[ret - 1] != ((crc >> 8) & 0xFF))) { fprintf(stderr, "crc check failed\n"); return -1; } return ret; }

这段代码在项目里的角色就是主站发送读请求、校验从站应答,实际跑下来很稳。Modbus RTU协议有个需要注意的地方:帧和帧之间的间隔要大于等于3.5个字符传输时间。在9600波特率下,一个字符约1.04ms,3.5个字符约3.6ms。如果连续发两帧的间隔太短,从机可能把两帧合并成一帧解析,直接导致异常响应。在轮询多个从机时,帧间加一点延时是保证稳定的实用小技巧。

4. 实测调试过程与组网经验:从单机到总线

4.1 回环测试与波形验证:先确认硬件通路

拿到板子后先别急着跑业务代码,先做一次串口回环测试。RS485是半双工差分信号,不像RS232那样可以粗暴地把TXD和RXD短接。回环测试的目的是验证数据能从T113的UART发出,经过收发器芯片转换成差分信号,再回来被UART接收——整个过程链路是通的。

我做的第一步是在软件层面做回环:把RS485收发器的DI和RO短接。这样相当于自发自收,数据发出去立刻被自己接收。用一段简单的C程序或者直接命令行验证:

# 打开一个终端,后台接收数据 cat /dev/ttyS1 & # 发送一串数据 echo "hello rs485" > /dev/ttyS1 # 查看是否在cat终端里收到了同样的内容

如果能看到自己发的数据,说明UART口本身的收发链路没问题,RTS方向控制大概率也是正常的。如果收不到,先用万用表量一下A、B线上的电压。总线空闲时,A-B压差应该大于200mV;发送数据时,用示波器能看到差分信号波形——一根线上是反相方波,另一根线是正相方波,两者叠加才是RS485标准的电平。

4.2 修改打印串口:把调试口腾出来给功能串口

之前提到的热词“全志t113修改打印串口”,这个需求在实际开发中非常常见。T113默认UART0是调试串口,内核的启动日志、/dev/console都挂在ttyS0上。如果硬件设计上把RS485收发器接到了UART0,就得把控制台输出挪到别的串口,否则RS485收发的数据会跟内核日志混在一起。

我的做法是修改内核cmdline中的console参数。在Tina Linux SDK中,环境变量一般定义在env.cfgboot_package相关配置里,把console=ttyS0,115200改成console=ttyS1,115200。同时确认设备树里UART0的statusokay,并且没有被串口驱动占用为调试口。

改完之后,内核启动日志从UART1输出,UART0就可以独立做RS485通信了。这个操作建议在开发阶段尽早处理,别等应用代码写完了再改,因为调试串口一换,之前所有依赖console输出定位问题的习惯都得跟着变。

4.3 多机轮询通信与总线组网实战

回环测试通过后,开始接真正的从机设备。一条RS485总线上挂多个从机,采用一主多从的轮询模式:主机依次向每个从机发送请求帧,从机收到与自身地址匹配的请求后应答。Modbus RTU就是典型的这种模式。

轮询逻辑的伪代码如下:

while (1) { for (i = 0; i < slave_count; i++) { ret = modbus_read_holding_regs(fd, slave_addr[i], start_reg, reg_num, rx_buf, sizeof(rx_buf), 500); if (ret > 0) { /* 解析并保存从机数据 */ parse_and_store(i, rx_buf, ret); } else { /* 记录超时或异常,继续下一个从机 */ printf("slave %d read failed\n", slave_addr[i]); } /* 帧间隔,避免连续帧间隔过短 */ usleep(10000); } }

组网时总线敷设方式直接影响通信可靠性。RS485总线要求手拉手接线,也就是菊花链拓扑,绝对不能用星型拓扑。总线两端要各接一个120欧姆终端电阻,匹配电缆特性阻抗,减少信号反射。如果总线上的设备不够密实、有些节点离主线很远,那根很长的分支线也要尽量短,分支过长会变成一根天线,把干扰收进总线。总线长度和波特率成反比关系:9600bps可以跑1200米,19200bps大概800米,115200bps一般不超过300米。项目里如果只是机柜内短距离通信,115200bps没问题;跨车间长距离就老老实实降到9600bps。

4.4 从主机侧观察信号质量

在实测过程中,我习惯用示波器探头接在A-B之间观察波形。正常通信时,能看到明显的差分方波,幅值大约在3.5V~5V之间(取决于收发器供电电压)。如果波形前沿有严重的过冲和振铃,说明终端电阻缺失或者阻抗不匹配,通信距离稍长就莫名奇偶校验错误。如果发送时A-B电压差只有几百毫伏,说明总线负载太重或者偏置电阻设计不合理,需要重新计算。

还有一个经常被忽视的点:RS485收发器的共模电压范围是-7V到+12V。当两个设备用的电源地电位差较大时,虽然差分信号不受共模干扰影响,但一旦共模电压超出芯片允许范围,收发器会烧毁。多个设备之间最好做好等电位连接,或者使用带隔离的RS485模块(比如ADI的ADM2483、国产的MAX14850等)。

5. 常见问题排查与避坑实录:这些坑我都踩过

5.1 RS485总线上电死机问题的根因与对策

“RS485上电死机”这个现象很容易复现:设备一上电,屏幕上花屏或者系统起不来,拔掉RS485线就恢复正常。我第一次遇到也摸不着头脑,后来分析发现根因在收发器的DE/RE引脚上。

很多板卡的硬件设计里,DE/RE引脚由SoC的GPIO或RTS控制,但上电瞬间SoC还没完成GPIO初始化,DE/RE引脚处于高阻态。此时收发器的输出状态不确定,如果恰好处于发送使能状态,它就会把总线上的信号强行驱动出来,形成总线竞争;如果RO输出(接SoC的RX)在无有效信号时随机翻转,SoC的串口控制器会收到一堆垃圾数据,触发中断风暴。在极端情况下,这些垃圾数据甚至会让bootloader误判为启动命令,导致系统启动异常。

解决办法从硬件和软件两个层面同时下手。

  • 硬件上,DE/RE引脚加一个10kΩ下拉电阻,确保上电默认是接收态(RE低电平有效,DE低电平禁用)。
  • 软件上,应用层程序启动后第一件事就是把方向控制引脚初始化到接收态,再打开串口。
  • 总线侧,A线上加一个上拉电阻到VCC,B线加一个下拉电阻到GND,保证总线空闲时A-B有一个明确的电平差,收发器的RO不会输出随机电平。偏置电阻的典型值在1kΩ到10kΩ之间,取值要综合节点数量和收发器输入阻抗计算。

5.2 收发错位与数据丢失:调试时序问题的独家经验

“发一帧数据,收到的内容错位几个字节”或者“第一帧正常,后面的帧经常丢字节”,这类问题几乎都出在方向切换时序上。RS485半双工通信中,从发送切到接收,或者从接收切到发送,都需要时间。

一个典型的错误写法是:

write(fd, buf, len); /* 没有tcdrain,直接切GPIO方向 */ gpio_set_value(de_pin, 0);

这样数据还没发完,收发器就已经被切到接收态了,总线上的数据被截断,接收方自然解不出完整帧。正确写法是write后必须tcdrain(fd)等内核把数据全部送到硬件FIFO并且移位寄存器发送完毕,然后才允许切方向。

如果用的是内核RS485模式(RTS自动切换),也有一个参数要调:delay_rts_after_send。有些收发器在RTS拉低后,还要一小段时间才能真正释放总线进入接收态,如果这个时间设成0,紧接着读数据可能读不到完整的响应或者读到噪声。实测中,这个延时设1~2毫秒比较合适,太快容易出问题。

接收丢数据还有一个常见原因:应用层读数据不够及时。如果read返回后你没有立即把数据从内核缓冲区取走,后续数据会把它覆盖。用select+read的组合,读四次直到select超时,可以确保把一帧完整取出。

5.3 常见问题速查表

我把项目调试过程中遇到的典型问题整理成了速查表,方便现场排查时快速定位:

现象可能原因排查方向
收不到任何数据AB接线接反、方向未切换、波特率错误量A-B电压,确认空闲>200mV;示波器看发送波形
数据乱码波特率误差大、总线反射、收发器损坏示波器观察波形质量;降低波特率测试;换收发器芯片
上电后系统卡死DE/RE引脚悬空、总线空闲时电平不确定DE/RE加下拉电阻;A/B加偏置电阻
第一帧正常后续异常方向切换时序太短、帧间隔不足调大delay_rts_after_send;帧间加延时
CRC校验失败发送被截断、接收缓冲溢出、总线干扰用tcdrain确保发完;提高读速率;检查屏蔽接地
从机全部无响应总线短路、终端电阻错放、主机地址配置错误万用表量AB间电阻,应约为60Ω(两个120Ω并联)
距离远后偶发错误线缆质量差、缺少终端电阻、波特率过高换屏蔽双绞线;加终端电阻;降低波特率

5.4 关于硬件电路设计的几点补充

虽然这篇博文的重点是代码,但RS485通信的成败,硬件电路至少占一半比重。简单说说我这次的硬件设计经验。收发器选型上,我用了SP3485,3.3V供电,和T113的IO电平完全匹配,不需要额外的电平转换。SP3485最大支持32个节点,对这个项目够用。如果节点更多,可以选带1/8负载的芯片,比如ISL83485,能挂256个节点。

收发器的A、B输出端要对地各加一个TVS管,型号选SMBJ6.0CA或者类似的,防止雷击浪涌和静电打坏芯片。这是工业现场的刚需,我见过好几块板子因为省了TVS,在打雷天气或者附近有大功率设备启停时,RS485芯片一批一批地坏。另外,如果总线需要长距离走线,建议用屏蔽双绞线,屏蔽层单端接地(一般在主机端接地),不要两端都接,否则会形成地环路,引入更大的干扰。

6. 调试工具与效率提升

开发嵌入式Linux下的RS485程序,好的调试工具能省一半时间。我常用的工具组合如下。

6.1 命令行工具快速验证

stty是Linux下查看和设置串口参数的利器。调试初期用下面命令快速配置串口:

# 查看当前串口参数 stty -F /dev/ttyS1 -a # 设置9600波特率、8N1、raw模式 stty -F /dev/ttyS1 9600 cs8 -cstopb -parenb raw

catecho配合可以做简单收发测试,但只能发ASCII文本,不能发任意十六进制字节。要发十六进制数据,可以用printfdd,或者直接用Python一行命令:

printf '\x01\x03\x00\x00\x00\x02\xc4\x0b' > /dev/ttyS1

从机响应数据可以用xxd查看十六进制:

timeout 3 cat /dev/ttyS1 | xxd

6.2 总线上抓包分析

如果RS485总线上挂了多个设备,想知道谁在发数据、数据是否冲突,最直接的办法是接一个USB转RS485模块到电脑上,用串口助手工具抓包。我习惯用一个小的USB转485模块并联到总线末端(注意方向是监听模式,模块的DE/RE接低电平固定接收,不参与发送),配上minicom或者moserial,就能实时看到总线上所有设备的数据帧。

这里有个细节:USB转RS485模块的默认方向控制和普通RS485设备一样,需要正确设置。抓包时如果不发送数据,它应该一直处于接收态。如果模块同时挂在总线上且它的DE是使能的,它也会干扰通信,这点要特别注意。

6.3 性能压力测试

项目验收前做了一次疲劳测试:主机以100ms周期轮询总线上64个从机,连续运行72小时,统计丢包率和异常帧数。测试脚本的核心思路是记录每个从机响应的成功率,异常时打点保存现场数据。这个测试很关键,很多偶发问题在短时调试中根本暴露不出来,只有长时间跑才能看出总线质量和驱动稳定性的真实水平。

测试中我发现一个有趣的现象:在高温环境(45℃)下,部分从机响应时间明显变慢,原本500ms超时足够的请求出现了超时重试。排查后确认是从机设备的电源在高负载下纹波偏大,导致其RS485收发器供电异常,信号质量下降。后来从机端电源加了滤波电容,问题就消失了。这类问题在常温调试时几乎不可能发现,但现场真实环境里就是致命的。

7. 踩坑心得与后续扩展方向

做这个项目前后花了三周,踩过的坑比我预想的多。最后分享几个最深刻的体会。

第一,RS485调试一定要先硬件后软件。总线电平不对、终端电阻没装好,软件调得再好也没用。板子到手第一件事是拿示波器看波形,把物理层确认了,后面所有问题都只是代码层面的。

第二,方向切换时序是半双工通信的命门。无论你用RTS自动切换还是GPIO手动切换,都要把延时参数传准,宁可多等1毫秒也不要抢时间。总线响应慢一点可以接受,数据帧损坏在工业现场是不可接受的。

第三,协议层一定要做超时和重试机制。RS485总线环境复杂,电磁干扰、节点掉线、线路老化都可能造成通信失败。没有超时重试的代码,一个节点掉线就能让整个轮询卡死。我的主循环里每个从机都有独立的超时时间和重试计数,连续多次失败会上报告警,而不是无限重试拖垮总线。

第四,如果项目后续要扩展,可以考虑把RS485通信程序做成独立的守护进程,用消息队列或共享内存跟业务程序解耦。通信层只管收发和协议解析,业务层只管逻辑处理。这样一来,后期加协议或者换从机设备型号,只需要改通信层的解析函数,业务层完全不用动。

这套代码后来还移植到了另一个基于T113的项目上,改动量很小。说明只要按照“设备树配置RS485模式 + 应用层标准POSIX串口API + 协议解析独立模块”这个思路来做,可复用性是很高的。如果你正在做类似的T113或者其它全志系列芯片的RS485通信开发,希望这篇分享能帮你少走一些弯路。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/3 1:31:25

音频分帧加窗原理与MATLAB实现:帧长帧移窗函数及重构要点

简介&#xff1a;本资源是面向数字信号处理初学者与音频算法实践者的MATLAB基础工具包&#xff0c;聚焦音频预处理核心环节——信号分帧与加窗操作&#xff0c;解决语音识别、声纹分析、频谱计算等任务中时频域转换的入门实操难题。压缩包共3个文件&#xff0c;含2个关键.m函数…

作者头像 李华
网站建设 2026/9/5 19:38:00

WordPress资源站搭建:Modown主题与erphpdown插件完整配置指南

简介&#xff1a;本资源是模板兔专为WordPress付费内容运营场景开发的Modown v9.4主题开心版&#xff0c;面向中小型知识付费站、资源下载站及虚拟商品卖家&#xff0c;解决收费下载、VIP分级权限管理、推广分佣与多渠道支付集成等核心需求。压缩包共2006个文件&#xff0c;含1…

作者头像 李华
网站建设 2026/9/5 15:17:44

基于PyTorch与CNN的遥感滑坡识别工程:从数据集到部署全解析

简介&#xff1a;本资源是一套面向遥感图像分析初学者与地质灾害监测研究者的深度学习实践方案&#xff0c;聚焦滑坡区域自动识别这一典型地物目标检测任务。项目基于PyTorch框架构建端到端CNN模型&#xff0c;完整覆盖数据预处理、模型训练、推理可视化及评估全流程&#xff0…

作者头像 李华
网站建设 2026/9/4 6:18:41

YOLOv5钢材缺陷检测与Qt界面开发实战:从数据集到部署全记录

简介&#xff1a;本资源是一套开箱即用的YOLOv5钢材表面缺陷检测完整方案&#xff0c;面向工业视觉初学者、自动化质检工程师及高校科研人员&#xff0c;解决钢铁制造中常见缺陷&#xff08;如裂纹、划痕、氧化斑等&#xff09;的快速识别与定位问题。压缩包共180个文件&#x…

作者头像 李华
网站建设 2026/9/5 7:42:42

从零到企业级:ReAct Agent 与 Workflow Agent 的全面解析

从零到企业级&#xff1a;ReAct Agent 与 Workflow Agent 的全面解析 1. 引言&#xff1a;AI 驱动的两种范式 在 2026 年的 AI 应用中&#xff0c;两种主要范式正在塑造智能系统的构建方式&#xff1a; ReAct Agent&#xff1a;一种基于大语言模型&#xff08;LLM&#xff09;自…

作者头像 李华
网站建设 2026/9/5 6:33:16

Deep Agent 全面指南:从零基础到企业级架构

Deep Agent 全面指南&#xff1a;从零基础到企业级架构 1. 引言&#xff1a;什么是 Deep Agent&#xff1f; 在 2026 年的 AI 浪潮中&#xff0c;Deep Agent&#xff08;深度智能体&#xff09; 代表着从“对话式 AI”到“自主执行型 AI”的范式转变。它不再是简单地回答问题&a…

作者头像 李华