
1. 项目概述当RK35XX遇上工业485最近在搞一个工业边缘计算的项目主控用的是瑞芯微的RK35XX系列芯片具体型号是RK3568。项目里需要接好几路传感器和执行器通信方式清一色都是RS-485。这本来是个挺常见的需求但真动起手来才发现事情没那么简单。市面上很多基于RK35XX的开发板默认的Linux系统镜像往往只开启了常用的UART比如调试串口UART2对于需要用作485通信的串口内核驱动层面要么没配要么配置得不完整直接拿来用要么识别不到设备要么无法稳定进行半双工收发。所以这个“在内核层适配485驱动”的活儿就成了项目推进的关键一步。它不仅仅是打开一个内核配置选项那么简单而是涉及到了从芯片引脚复用、设备树Device Tree配置、内核驱动选项到最终用户空间测试的一整套流程。说白了就是要告诉内核“嘿咱们板子上这个串口控制器比如UART3它现在不是普通的全双工串口了它连接了一个485收发芯片需要按照485的半双工规则来玩收发切换的GPIO是哪一个你得管起来。”这个过程对于嵌入式Linux开发者来说算是基本功但里面细节不少一不留神就会踩坑。比如设备树里引脚配置错了导致无法收发驱动编译选项没选对导致缺少485控制接口或者用户空间的串口配置没设对都会让通信失败。接下来我就结合这次在RK3568上的实操把内核层适配485驱动的完整思路、步骤和避坑点详细拆解一遍。2. 核心需求与方案选型解析2.1 为什么一定要在内核层适配首先得搞清楚为什么我们不直接在应用程序里用GPIO控制收发切换而非要折腾内核驱动这里主要有两个核心考量实时性和可靠性。485通信是半双工的同一时刻总线只能处于发送TX或接收RX一种状态。这需要一个控制信号通常是一个GPIO来切换外部485收发芯片的方向。发送数据前要将总线切换到发送模式发送完毕后必须立即切换回接收模式以监听总线上的数据。如果这个切换动作由用户空间的应用程序来控制问题就来了。Linux是分时操作系统应用程序的调度存在不确定性。即便你写完数据后立刻调用GPIO设置函数从系统调用陷入内核、调度、上下文切换再到真正操作硬件寄存器这中间可能有毫秒级的延迟。在高速率比如115200甚至更高波特率通信下这个延迟可能导致数据帧的最后几个字节还没完全发出方向就被切回了接收模式造成数据损坏。更糟糕的是如果切换慢了总线处于发送状态时间过长还会影响其他节点的接收。因此最可靠的做法是将收发方向的控制权交给内核串口驱动。驱动在硬件层面操作当串口发送FIFO先进先出缓冲区为空、最后一个字节的停止位发出后由硬件产生一个中断或通过DMA直接内存访问完成回调驱动在这个时刻立刻切换GPIO延迟是微秒级的保证了时序的精确性。2.2 RK35XX的串口与GPIO资源分析RK35XX系列如RK3568的串口控制器通常是16550兼容的功能强大。以RK3568为例它有多达9个UART控制器UART0-UART8。UART2通常预留给调试终端Serial Console所以我们一般会选择其他UART比如UART3、UART4等作为功能串口。适配485的关键在于为选定的UART找到一个可用的、且硬件连接正确的GPIO引脚作为方向控制脚通常命名为RTS或DE/RE。在原理图上这个GPIO会连接到485收发芯片如SP3485、MAX3485的DE驱动器使能和/或RE接收器使能引脚。有些收发芯片DE和RE接在一起用一个GPIO控制有些则分开控制。在适配前必须做两件事核对原理图确认用于485通信的UART引脚TX、RX以及控制GPIO的具体网络标号。查阅芯片手册找到该GPIO对应的内部IO控制器和引脚编号。例如RK3568的GPIO分为多个BankGPIO0-GPIO4需要记录类似GPIO3_B1这样的信息它对应的是Bank3的第9个引脚B组第1个。2.3 内核驱动方案选择Linux内核中RS-485的支持主要通过串口驱动的rs485属性来实现。这需要内核配置满足以下条件串口驱动支持RS485RK35XX的串口驱动drivers/tty/serial/8250/8250_dw.c或 Rockchip定制的drivers/tty/serial/8250/8250_rockchip.c必须编译了RS485支持。这通常由内核配置项CONFIG_SERIAL_8250_RS485控制。GPIO控制支持方向控制需要通过GPIO子系统实现确保内核支持GPIOCONFIG_GPIOLIB是必须的。我们的适配工作就是确保这些条件在自家板子的内核中都已满足并通过设备树正确描述硬件连接。3. 设备树DTS配置详解设备树是告诉内核硬件布局的“地图”。适配485主要修改在对应UART的节点上。3.1 定位与修改设备树源文件首先找到你的内核源码中对应板子的设备树源文件.dts或.dtsi。路径通常在arch/arm64/boot/dts/rockchip/下。例如对于RK3568可能是rk3568-evb.dtsi或你自己板级的.dts文件。在文件中找到你想要配置为485的UART节点。例如配置UART3uart3 { status okay; pinctrl-names default; pinctrl-0 uart3m0_xfer uart3m0_ctsn uart3m0_rtsn; rs485-rts-active-low; rs485-rts-delay 1 100; // 单位毫秒 linux,rs485-enabled-at-boot-time; rts-gpios gpio3 RK_PB1 GPIO_ACTIVE_LOW; // 关键指定控制GPIO };3.2 关键属性逐行解析status “okay”;启用该UART控制器。pinctrl-0 uart3m0_xfer uart3m0_ctsn uart3m0_rtsn;引脚控制配置。这里不仅包含了TX/RXuart3m0_xfer还包含了CTS和RTS引脚。注意即使我们不使用硬件流控也需要将RTS引脚对应的pinctrl包含进来因为内核的485功能会复用RTS引脚作为方向控制。m0表示引脚复用选项0具体定义需要查看pinctrl节点。rs485-rts-active-low;这是一个布尔属性表示RTS信号低电平有效。对于大多数485收发芯片当DE/RE引脚为低电平时处于接收模式高电平时为发送模式。如果此属性存在则逻辑反转。务必根据你的收发芯片手册确定。如果芯片是高电平发送则不需要设置此属性即默认高电平有效。rs485-rts-delay 1 100;这是两个时间值单位毫秒。第一个值1是发送前延迟即从切换RTS到开始发送第一个字节的间隔第二个值100是发送后延迟即发送完最后一个字节后保持发送状态的时长之后再切换回接收。这是极易出错的地方对于标准的485通信通常不需要前延迟设为0或1后延迟需要根据收发芯片的切换时间和总线物理长度微调一般1-2个字节时间足够。100ms显然太大了会导致总线长时间占用。推荐值0 2或1 2。linux,rs485-enabled-at-boot-time;让内核在启动时就初始化该串口为485模式。如果不设置启动时可能是普通串口模式。rts-gpios gpio3 RK_PB1 GPIO_ACTIVE_LOW;最核心的属性。指定用于485方向控制的GPIO。gpio3指向GPIO控制器节点。RK_PB1Rockchip定义的宏对应GPIO3的B1引脚即GPIO3_PB1。你需要根据原理图修改为正确的引脚。GPIO_ACTIVE_LOW指定有效电平为低。此处的“有效”指的是“激活发送状态”。如果设置了前面的rs485-rts-active-low那么这里GPIO_ACTIVE_LOW意味着“当GPIO输出低电平时激活发送模式”。这需要和硬件逻辑一致。这是一个常见的混淆点后面在避坑部分会详细说。3.3 引脚复用Pinctrl配置检查确保在pinctrl节点中uart3m0_rtsn这个配置项正确地将对应的引脚复用为GPIO功能并且是输出模式。通常它在rk3568-pinctrl.dtsi这类文件中定义一般不需要改动但必须确认其存在且指向的引脚号正确。4. 内核配置与编译设备树改好后需要确保内核支持相关功能。4.1 内核菜单配置进入内核源码目录使用make menuconfig或你喜欢的图形配置工具确保CONFIG_SERIAL_8250_RS485y或m。路径通常在Device Drivers - Character devices - Serial drivers - 8250/16550 and compatible serial support - Support for RS485。确保CONFIG_GPIOLIBy这个基本默认就是开启的。检查Rockchip串口驱动是否编译。通常是CONFIG_SERIAL_8250_DWy和CONFIG_SERIAL_8250_ROCKCHIPy。4.2 编译与更新编译内核make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- -j$(nproc)。编译设备树make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- dtbs。会生成对应的.dtb文件。更新到板子将编译好的内核镜像如Image和设备树二进制文件如rk3568-evb.dtb拷贝到板子的启动分区如通过TF卡、网络TFTP或直接覆盖eMMC并重启板子。5. 用户空间测试与验证内核启动后可以通过sysfs和串口工具来验证配置是否生效。5.1 检查sysfs属性登录到板子的Linux终端查看对应串口设备的sysfs节点# 假设UART3在系统里是ttyS2具体名称可能因内核版本而异可用 dmesg | grep tty 查看 ls -l /sys/class/tty/ttyS2/device/of_node/ cat /sys/class/tty/ttyS2/device/of_node/rs485-rts-active-low cat /sys/class/tty/ttyS2/device/of_node/rs485-rts-delay cat /sys/class/tty/ttyS2/device/of_node/rts-gpios如果这些文件存在且内容与你设备树中配置的一致说明内核已经正确识别了485属性。5.2 使用stty和测试程序查看串口当前设置stty -F /dev/ttyS2 -a | grep -i 485如果驱动支持这里应该会显示与485相关的标志位。配置串口参数并测试 我们不能用简单的echo或cat测试485因为方向控制是内核驱动的。需要编写或使用支持485的小程序。这里提供一个简单的C语言测试思路#include stdio.h #include fcntl.h #include termios.h #include unistd.h #include string.h int main() { int fd open(“/dev/ttyS2”, O_RDWR | O_NOCTTY); if (fd 0) { perror(“open”); return -1; } struct termios options; tcgetattr(fd, options); cfsetispeed(options, B115200); // 设置波特率 cfsetospeed(options, B115200); options.c_cflag ~PARENB; // 无校验 options.c_cflag ~CSTOPB; // 1位停止位 options.c_cflag ~CSIZE; options.c_cflag | CS8; // 8数据位 options.c_cflag ~CRTSCTS; // 禁用硬件流控重要 options.c_cflag | CREAD | CLOCAL; // 启用接收忽略调制解调器状态 options.c_iflag 0; options.c_oflag 0; options.c_lflag 0; // 非规范模式 tcsetattr(fd, TCSANOW, options); // 测试发送 char *msg “Hello RS485!\n”; int n write(fd, msg, strlen(msg)); printf(“Sent %d bytes\n”, n); // 注意由于是半双工发送后驱动会自动切换回接收。 // 这里可以加入一小段延时然后尝试读取如果有回环或另一节点回应 usleep(100000); // 100ms延时 char buf[256]; n read(fd, buf, sizeof(buf)); if (n 0) { buf[n] ‘\0’; printf(“Received: %s”, buf); } close(fd); return 0; }编译后运行。同时你可以用逻辑分析仪或示波器连接到485总线的A/B线上观察发送数据时方向控制GPIO的电平变化是否与数据同步以及发送结束后是否及时切换。6. 常见问题与深度排查指南6.1 问题发送数据时方向控制GPIO没有变化可能原因1设备树配置错误。检查确认rts-gpios属性指向的GPIO号绝对正确。使用cat /sys/kernel/debug/gpio命令查看所有GPIO状态在发送数据时观察对应GPIO的value是否变化。检查确认pinctrl-0包含了RTS引脚的控制项如uart3m0_rtsn。可能原因2有效电平逻辑混乱。情景分析假设你的485芯片是DE高电平发送。情况A设备树设置了rs485-rts-active-low和rts-gpios … GPIO_ACTIVE_LOW。这意味着驱动认为“低电平有效”。当发送时驱动会将该GPIO置为低电平。但你的硬件是高电平发送因此矛盾GPIO可能被驱动拉低导致无法发送。情况B设备树未设置rs485-rts-active-low但rts-gpios设置了GPIO_ACTIVE_LOW。这意味着默认高电平有效但指定了GPIO低电平有效。逻辑冲突。解决方案根据硬件确定唯一配置。对于“高电平发送”的芯片删除rs485-rts-active-low属性。设置rts-gpios … GPIO_ACTIVE_HIGH。这样发送时GPIO输出高电平符合硬件要求。6.2 问题能发送但接收不到数据或数据错乱可能原因1收发切换时序问题。检查rs485-rts-delay设置是否合理。发送后延迟过大会导致本节点发送结束后迟迟不释放总线GPIO仍处于发送状态阻塞其他节点发送。建议先设置为0 1或0 2进行测试。检查发送前延迟如果设置过大可能导致数据帧开头丢失。通常设为0。可能原因2总线终端电阻和偏置电阻。这不是驱动问题但直接影响通信。确保在485总线的两端最远两个节点各接一个120欧姆的终端电阻以消除信号反射。在总线空闲时通过偏置电阻通常一个上拉一个下拉让A-B线之间有一个稳定的差分电压例如200mV确保处于确定的空闲逻辑1状态避免噪声误触发。可能原因3用户空间串口配置错误。检查是否用stty或代码禁用了硬件流控-crtscts。485模式下必须禁用硬件流控。检查波特率、数据位、停止位、校验位是否与通信对端严格一致。6.3 问题内核启动时找不到串口或提示配置错误检查dmesg日志dmesg | grep -E “uart|485|ttyS”。重点关注是否有引脚复用冲突的错误pinctrl或GPIO申请失败的错误。确认驱动加载lsmod | grep 8250查看串口驱动模块是否加载。如果是内置驱动检查内核编译配置。6.4 高级调试使用逻辑分析仪当软件排查困难时硬件工具是最直接的。用逻辑分析仪同时抓取串口TXD引脚CPU端。485方向控制GPIO。485总线A、B线差分信号或其中一线对地。 对比波形你可以清晰看到GPIO是否在TXD数据开始前变高或变低取决于有效电平。GPIO在TXD数据停止位结束后多久切换。总线上的差分信号是否完整与TXD数据是否一致。 这能最直观地验证内核驱动的工作时序是否正确。7. 性能优化与生产环境建议7.1 调整内核驱动参数以降低延迟对于高波特率或实时性要求极高的场景可以尝试调整内核参数减小串口FIFO触发阈值通过修改驱动代码或设备树属性如果支持降低发送FIFO的触发中断阈值让驱动更早地开始处理发送完成中断从而可能减少发送后切换的延迟。但这需要深入研究具体驱动实现一般不建议改动。使用DMA模式如果串口支持DMA启用DMA传输可以减少CPU中断负载但DMA完成回调的时机也需要关注确保方向切换在DMA传输完成后立即进行。RK35XX的UART通常支持DMA需要在设备树中配置dmas和dma-names属性。7.2 用户空间编程最佳实践打开设备时使用O_RDWR485设备必须同时以读写模式打开。彻底禁用流控在termios设置中明确清除CRTSCTS、IXON、IXOFF等所有流控标志。处理读写竞争在复杂的多线程应用中要避免在驱动自动切换方向的瞬间进行读写操作。虽然驱动内部有锁但应用层设计清晰的“发送-等待-接收”状态机仍是好习惯。错误处理检查write()和read()的返回值并处理EIO、EAGAIN等错误这些可能和总线冲突或物理层故障有关。7.3 设备树配置的版本管理将485配置固化在板级设备树文件.dts中并纳入版本控制系统。对于不同批次的硬件如果GPIO引脚有调整可以通过设备树覆盖Device Tree Overlay或在启动脚本中根据硬件版本加载不同的DTB文件来实现灵活配置避免为每一个小改动都重新编译内核。这次为RK3568适配485驱动的过程让我再次体会到嵌入式Linux开发中“细节决定成败”的道理。一个简单的功能背后是芯片手册、设备树、内核驱动和硬件原理的紧密耦合。最深的体会是逻辑分析仪是调试通信问题无可替代的利器它能把软件指令和硬件电平的变化在时间轴上精确对齐任何时序问题都无处遁形。另外关于rs485-rts-active-low和GPIO_ACTIVE_LOW/HIGH的组合一定要画一个真值表结合硬件原理图反复核对这是避免方向控制逻辑错误最有效的方法。