ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

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

全志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指向别的串口或者直接改成consolettyS0把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、MAX485DE高电平发送、RE低电平接收。如果RTS直接接到DE/RE那么RTS高电平应该对应发送低电平对应接收这种回路下RTS是“高有效”设备树里不需要加rs485-rts-active-low。但如果电路中间加了三极管反相或者用了低电平有效的控制逻辑那就要标记rs485-rts-active-low让驱动在发送时把RTS拉低。判断方法很简单看你的电路里RTS输出高电平时收发器是进入发送还是接收状态。rs485-rts-delay后面的两个数字分别是delay_rts_before_send和delay_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_UARTS和CONFIG_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还可能被解释成CtrlC这种问题排查起来非常隐蔽。CLOCAL忽略调制解调器载波检测CREAD使能接收。CRTSCTS一定要清掉如果不清掉RTS会被硬件流控逻辑接管和RS485方向控制冲突通信行为会变得非常诡异。VMIN和VTIME的配置我建议先设成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完就当成一个完整报文。帧结构起码本文还有配套的精品资源点击获取
返回列表