news 2026/8/31 16:31:21

全志T113 RS485通信调试全攻略:设备树配置与应用层实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
全志T113 RS485通信调试全攻略:设备树配置与应用层实现

简介:本资源是一份面向嵌入式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支持一键编译;结构清晰,模块职责分明,便于理解RS485在Linux中作为特殊字符设备的控制机制。已有53人学习下载,代码已通过实测验证,完整覆盖阻塞/非阻塞双模式切换、串口参数(波特率、校验位等)动态配置、硬件方向控制(DE/RE引脚管理)等关键细节,可快速适配其他Linux平台,是掌握嵌入式串口通信底层编程的典型参考范例。 最近在基于全志T113的板子上调RS485通信,前前后后折腾了好几天。全志T113是一片双核Cortex-A7处理器,跑Linux在工控、物联网网关、数据采集项目里很常见,而RS485又是工业现场绕不开的总线协议,所以这两个东西组合在一起的使用频率相当高。

我一开始以为Linux下写RS485通信代码就是把串口打开、发数据、收数据这么简单,结果实际调试才发现,硬件方案、设备树、内核驱动、应用层ioctl任何一步没有想清楚,通信都跑不起来。这篇文章把整套链路从硬件选型到内核配置再到用户态代码完整过一遍,代码可以直接改来用,也希望帮你在排障的时候少走点弯路。

1. 全志T113的硬件资源与RS485通信方案选型

1.1 T113的串口资源盘点

先说硬件。我手头这块板子用的是全志T113-S3,核心板引出了好几路UART,其中UART0默认是调试串口,用来打印内核日志和接命令行。输出日志用的是/dev/ttyS0,剩下几路UART可以自由分配给业务使用。具体哪一路对应Linux下的哪个/dev/ttySx节点,是由设备树里使能的节点和引脚复用的顺序决定的,不同板子差异很大,拿到板卡第一件事就是确认这个映射关系。

我这边把UART3作为RS485通信口,设备树里使能对应节点之后,系统启动完就会在/dev下面看到ttyS3。这里有个常见误区:总以为串口编号一定和原理图上的UART编号一致,其实不一定,有的BSP会把UART0映射成ttyS0,有的因为内核配置了console占用,导致实际注册的编号错位。所以不要凭猜,先用dmesg | grep tty看一下实际注册了哪些节点。

T113这颗芯片本身支持RTS/CTS硬件流控引脚,这给RS485方向控制提供了很大的便利。因为RS485是半双工总线,收发方向切换必须可靠,如果芯片本身没有RTS引脚引出来,后面所有自动控制方案都无从谈起。选板子的时候优先选把RTS引脚引出来的核心板,这个决定能省掉后面一大半麻烦。

1.2 RS485方向控制:硬件自动还是GPIO切换

RS485和普通的UART TTL电平串口最关键的区别就是半双工。一条总线上所有设备共享两根差分线A和B,任意时刻只允许一个设备往总线上发送数据,其他设备都处于接收监听状态。具体到每个节点上,就是通过收发器芯片的DE/RE引脚来控制:DE拉高进入发送态,RE拉低进入接收态。这个方向切换在裸机上可以用一个GPIO搞定,但在Linux下就没那么简单了。

拿对讲机来类比就很好理解:RS485总线就像一群人用对讲机通话,谁按下PTT按键谁就能说话,松手就切换到收听状态。RTS引脚在RS485应用里扮演的就是PTT按键的角色。方案上有三种常见做法:第一种是纯粹用GPIO控制DE/RE方向,应用层发送前先拉高GPIO,发送完成再拉低;第二种是使用UART控制器自带的RTS信号去接DE/RE,由内核串口驱动在发送时自动翻转RTS;第三种是使用带自动方向切换功能的RS485芯片,例如MAX13487E这类,芯片自己检测TXD状态来决定方向。

从工程可靠性角度,我最推荐第二种,也就是用RTS自动方向控制。原因很简单:GPIO控制方向需要应用程序自己在write前后去操作GPIO,这中间隔着一个用户态到内核态的切换,还有Linux调度器引入的不确定延时。一旦系统负载高或者发生线程抢占,总线方向切换的时机就可能抖动。在只有两个设备的点对点通信里这个问题不明显,但在一条总线上挂了几十个设备时,方向切换不及时就会造成数据冲突。第三种自动方向芯片虽然硬件上简单,但切换过程中存在微小盲区和额外延时,在高速通信场景下表现不如RTS自动控制稳定。

1.3 方案选型建议

我把上面三种方案的实际表现整理成了一张表,方便对照着选型:

方向控制方案实现复杂度时序精度适用场景主要坑点
GPIO控制DE/RE低,应用层直接操作差,受调度影响点对点、低速、不要求强实时发送完成后切回接收不及时,容易漏收
UART RTS自动控制中,设备树加属性好,由内核8250驱动控制多设备组网、波特率较高需要引出RTS引脚,设备树配置要正确
自动方向切换芯片低,硬件自动完成较好,但存在盲区简单场景、不想改软件芯片成本高,高速/长距离有隐患

如果让我直接给出建议,那就是:核心板上要把RTS引脚引出来,软件上走内核8250驱动的RS485模式,应用层只需要在初始化时通过ioctl设置几个标志位,读写就完全不用关心方向切换。这也是下面整篇内容要展开的主线。

2. 内核侧配置:设备树与驱动参数

2.1 设备树中串口节点的基本配置

确定硬件方案之后,第一件事不是写应用代码,而是先把设备树改对。全志T113的BSP用的是标准设备树机制,内核启动时会根据设备树里串口节点的状态来决定注册哪些串口设备、复用哪些引脚。如果设备树里没有使能UART3,那不管应用层怎么open都找不到/dev/ttyS3

一个典型的RS485串口节点配置大概是这样的:

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

这里status = "okay"是必须的,表示使能该串口。pinctrl-0引用的引脚组定义里必须包含TXD、RXD和RTS三个引脚。我之前踩过一个坑:板级设备树里默认只把TXD和RXD引脚配成了UART用途,RTS引脚仍然是普通GPIO状态,导致RTS信号根本拉不出来,无论应用层怎么设置RS485模式,方向切换都不生效。所以要检查你的BSP里对应的pinctrl定义,确认RTS引脚的function是UART而不是GPIO。

还有一个小细节是调试串口的处理。如果UART3默认被配置成了console,日志会从/dev/ttyS3不断往外打,RS485数据会跟调试日志混在一起。遇到这种情况,需要在内核启动参数bootargs里把console指向别的串口,或者直接改成console=ttyS0,把UART3让出来作为纯业务口。这个修改每个BSP的位置不太一样,有的在设备树chosen节点里,有的在uboot环境变量里,搜一下bootargs就能找到。

2.2 让内核接管RS485方向控制

设备树里那几行RS485相关的属性,是内核能自动控制方向的关键。linux,rs485-enabled-at-boot-time意思是说在串口驱动初始化阶段就直接把RS485模式打开,这样应用层即使不调用任何ioctl,驱动也已经处于RS485工作模式了。rs485-rts-active-low表示RTS信号以低电平作为有效发送电平,具体要不要加这个属性,取决于你的RS485收发器电路里DE/RE引脚和RTS是怎么接的。

这里有个容易绕晕的地方。常见RS485收发器比如SP3485、MAX485,DE高电平发送、RE低电平接收。如果RTS直接接到DE/RE,那么RTS高电平应该对应发送,低电平对应接收,这种回路下RTS是“高有效”,设备树里不需要加rs485-rts-active-low。但如果电路中间加了三极管反相,或者用了低电平有效的控制逻辑,那就要标记rs485-rts-active-low,让驱动在发送时把RTS拉低。判断方法很简单:看你的电路里RTS输出高电平时,收发器是进入发送还是接收状态。

rs485-rts-delay后面的两个数字分别是delay_rts_before_senddelay_rts_after_send,单位是毫秒。前者是发送前提前多久把RTS置为发送态,给收发器一个稳定的建立时间;后者是发送完成后等待多久再把RTS切回接收态,这个参数非常关键。如果delay_rts_after_send设成0,发送完最后一个字节的瞬间总线驱动器就关掉了,会造成总线电平的瞬间跳变,在长线或者多设备场景里容易产生毛刺,干扰其他节点。我实测下来,9600波特率下至少留2到3个字节的时间再切方向,也就是2毫秒以上;115200波特率下留0.5到1毫秒基本就够了。

2.3 内核配置项与驱动确认

设备树只是第一步,还得确认内核里把对应的串口驱动编译进去了。全志T113的BSP里串口驱动有两种常见形态:一种是挂在标准8250串口框架下,设备节点就是/dev/ttySx;另一种是全志私有的sunxi-uart驱动。不管是哪种,关键是要确认驱动代码里对RS485模式有支持,因为只有驱动支持了TIOCSRS485这个ioctl命令,应用层才能通过标准接口来配置RS485模式。

在内核menuconfig里可以搜索SERIAL_8250或者SERIAL_SUNXI来确认驱动是否编译。一般情况下需要确保CONFIG_SERIAL_8250是开启的,同时根据实际需要调整CONFIG_SERIAL_8250_NR_UARTSCONFIG_SERIAL_8250_RUNTIME_UARTS,把串口数量配置得比实际用到的多几个,免得后面想多开一路串口时发现数量不够。如果用的是全志私有驱动,就要看SDK自带的补丁里是否包含SER_RS485_ENABLED相关代码。

启动系统之后,用下面几条命令确认串口是否正常工作:

dmesg | grep tty cat /proc/tty/driver/serial ls -l /dev/ttyS*

如果/dev/ttyS3不存在,大概率是设备树里节点没有使能,或者内核驱动没编译。如果节点存在但一读写就报错,看一下dmesg里有没有驱动报错的信息,比如引脚被复用、中断申请失败之类。

3. 应用层RS485通信代码实战

3.1 串口基础初始化:termios配置要点

内核侧配置好了以后,应用层的工作就清晰多了。先做一个标准串口初始化,把波特率、数据位、校验位这些都设好。这里直接贴一段我常用的初始化函数,后面每个模块代码都基于它:

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <termios.h> #include <sys/ioctl.h> #include <sys/select.h> #include <linux/serial.h> static int uart_init(int fd, speed_t baud) { struct termios opt; memset(&opt, 0, sizeof(opt)); tcgetattr(fd, &opt); cfmakeraw(&opt); cfsetispeed(&opt, baud); cfsetospeed(&opt, baud); opt.c_cflag |= CLOCAL | CREAD; opt.c_cflag &= ~CRTSCTS; opt.c_cflag &= ~CSTOPB; opt.c_cflag &= ~PARENB; opt.c_cflag &= ~CSIZE; opt.c_cflag |= CS8; opt.c_cc[VMIN] = 1; opt.c_cc[VTIME] = 0; tcsetattr(fd, TCSANOW, &opt); tcflush(fd, TCIOFLUSH); return 0; }

有几个细节值得啰嗦一句。cfmakeraw会把终端驱动的那套换行处理、特殊字符处理全部关掉,RS485通信场景下必须用raw模式,否则你发0x0A可能被改写成0x0D 0x0A,收数据的时候0x03还可能被解释成Ctrl+C,这种问题排查起来非常隐蔽。CLOCAL忽略调制解调器载波检测,CREAD使能接收。CRTSCTS一定要清掉,如果不清掉,RTS会被硬件流控逻辑接管,和RS485方向控制冲突,通信行为会变得非常诡异。

VMINVTIME的配置我建议先设成1和0,也就是read至少返回一个字节,并且不做超时处理。后面做封装的时候再统一用select来控制超时,这样逻辑更清晰,不会因为termios的超时机制和select的超时机制叠加在一起产生预期之外的行为。

3.2 用ioctl设置RS485模式

初始化完串口,接下来就要让内核驱动把RS485方向控制打开。标准做法是使用TIOCSRS485这个ioctl命令,配合struct serial_rs485结构体。代码如下:

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

SER_RS485_ENABLED告诉驱动开启RS485模式,这一步是总开关。SER_RS485_RTS_ON_SEND表示在发送期间把RTS置为“发送有效电平”,SER_RS485_RTS_AFTER_SEND表示发送结束后把RTS置为“接收有效电平”。这两个标志配合在一起,驱动就会在每次write数据之前自动翻转RTS进入发送态,数据发完再根据设定的延时翻转回接收态。

这两个标志的含义和芯片手册里DE/RE引脚的极性必须一致。如果你发现发送出去的数据对端能收到,但是一直收不到对端的响应,而且RTS波形看起来不太对,多半是这里高低电平的关系反了。排查方式很简单:把设备树里rs485-rts-active-low这个属性去掉或者加上,再观察现象。两个地方的实际效果是叠加的,如果设备树和应用层设置不一致,方向可能会完全反掉。

实际项目中,我习惯把这两段初始化合成一个函数,在每次打开串口后都调用一次,确保驱动状态是确定的。即使内核已经通过linux,rs485-enabled-at-boot-time在启动阶段开了RS485模式,应用层再设置一次也不是坏事,因为某些驱动在串口close之后的uart_close回调里会把rs485状态复位。

3.3 读写流程与半双工时序控制

RS485的读写流程和普通全双工串口有一个核心区别:写完之后不能立刻进入阻塞式read,要等驱动把方向切换回来,同时给对端设备留出响应时间。这里有两个层面的延时,一个是驱动层delay_rts_after_send控制的RTS释放延时,另一个是协议层等待从机响应的超时时间。

协议层超时时间尤其要注意。比如你通过RS485去读一台支持Modbus协议的仪表,主机发出请求帧之后,从机通常需要几十毫秒甚至几百毫秒处理,然后才把应答帧发回来。这时候如果主机在write之后马上read并只等10毫秒,很可能超时。我一般会根据实际设备的响应时间设置超时,常用做法是200毫秒到1秒,具体看设备手册说明。如果是自定义协议,建议在帧格式里设计好状态机和超时重试机制,这样总线上的设备即使偶尔异常也不会导致整个系统卡死。

串口本质上是一个字节流,不是数据包接口。read返回的数据不保证正好是一整帧,可能一个帧分两次读出来,也可能一次read把两个帧的数据都读进来了。所以我写的每个RS485收发模块都会自己维护一个接收缓冲区和帧解析状态机,而不是每次read完就当成一个完整报文。帧结构起码

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

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

2026 Java后端面试突击:核心考点与答题框架

2026 年的金九银十已经进入倒计时&#xff0c;Java 后端岗位的竞争节奏比往年更紧凑。这一轮面试考察的不只是“背没背过八股文”&#xff0c;而是能不能在 30 分钟内把 JVM 调优、并发编程、MySQL 索引、Spring 三级缓存这些知识点讲得清楚&#xff0c;同时还能接住场景题和 A…

作者头像 李华
网站建设 2026/8/31 16:25:18

TRPO信赖域策略优化:从KL约束到PPO前身的核心原理

策略梯度方法里有个绕不开的老问题&#xff1a;一轮更新到底应该走多大。步长太小&#xff0c;训练慢&#xff1b;步长太大&#xff0c;一个 batch 的噪声就可能把策略推到悬崖边&#xff0c;收益曲线瞬间崩掉。TRPO&#xff08;Trust Region Policy Optimization&#xff0c;信…

作者头像 李华
网站建设 2026/8/31 16:25:15

自动泊车控制算法Matlab仿真全流程详解

简介&#xff1a;本资源是一套基于MATLAB实现的自动泊车控制算法参考方案&#xff0c;面向智能驾驶系统开发者、车辆控制方向研究生及自动驾驶算法工程师&#xff0c;聚焦狭小空间下的路径规划与运动控制核心问题。压缩包含3个.m脚本文件&#xff0c;总大小仅4KB&#xff0c;轻…

作者头像 李华
网站建设 2026/8/31 16:21:18

Python环境搭建与JupyterLab调试全流程指南:从虚拟环境到报告导出

在“装好 Python 就算搭好环境”这个误区上&#xff0c;几乎每个初学者都吃过亏。代码逻辑本身不难&#xff0c;真正劝退人的往往是环境&#xff1a;Python 没加入 PATH&#xff0c;命令行里输入 jupyter 提示“不是内部或外部命令”&#xff1b;浏览器打开 Jupyter 后页面空…

作者头像 李华
网站建设 2026/8/31 16:21:11

Hermes Agent实战:桌面浏览器独立窗口与远程MCP接入指南

Hermes Agent 是一个开源 Agent 桌面客户端&#xff0c;核心价值不是多一个聊天框&#xff0c;而是把大模型、工具调用、浏览器操作和外部服务集中到一个可配置的桌面入口里。标题中的 v2026.8.27 属于日期型版本号&#xff0c;这类版本在开源项目里通常用 Release 页面管理&am…

作者头像 李华
网站建设 2026/8/31 16:20:26

Python推荐系统源码实战:从召回、排序到上线部署

简介&#xff1a;这是一份面向推荐系统初学者与进阶开发者的PythonSpark协同实践项目&#xff0c;聚焦个性化推荐全流程实现&#xff0c;涵盖数据清洗、特征工程、模型训练&#xff08;协同过滤/ALS&#xff09;、评估与可视化等核心环节&#xff0c;适用于高校课程设计、企业算…

作者头像 李华