1. 为什么串口在嵌入式开发里始终绕不开搞嵌入式的人都有一个共识板子拿到手第一件事不是点灯而是把串口调通。灯亮了只能说明供电正常串口通了才意味着你能跟芯片对上话。RK356X 这颗片子——不管是 RK3566 还是 RK3568——在工业控制、边缘计算、商显设备里出镜率极高而 UART 几乎是每个项目都会用到的外设。调试信息输出、跟传感器模组通信、跟 MCU 做协议对接甚至产测工装都离不开它。但实际做下来你会发现RK356X 的 UART 并不像单片机那样“配个波特率就能跑”。它涉及引脚复用、时钟树、DMA、RS485 方向控制、流控等一堆细节稍不注意就会出现“发送正常接收乱码”“高波特率丢数据”“DMA 一开就卡死”这类问题。这篇内容就是把我自己在 RK356X 上折腾 UART 的完整过程拆开讲清楚从设备树配置到应用层读写从普通模式到 DMA 和 RS485把踩过的坑和验证过的参数都摆出来。适合正在用 RK356X 做项目的驱动工程师、应用开发者也适合刚接触 Rockchip 平台、想快速把串口跑起来的同学。2. RK356X UART 硬件资源与整体设计思路2.1 先搞清楚这颗片子到底有几个串口RK356X 的 UART 控制器数量在不同型号上略有差异以常见的 RK3568 为例芯片内部集成了多路 UART通常标号为 UART0 到 UART9 这个范围具体可用数量要以你手上的 datasheet 为准不同封装和型号会有裁剪。这里面有几个关键点必须先理清UART0 和 UART1 比较特殊它们往往和调试口、系统启动相关很多参考设计会把 UART2 作为默认的调试串口输出波特率固定 1500000。你在改设备树的时候千万别随手把调试串口给复用成普通串口否则系统启动日志就没了排查问题会非常痛苦。不是每个 UART 都能随便引到任意引脚。Rockchip 的引脚复用pinctrl是有限制的某个 UART 的 TX/RX 只能映射到特定的几组引脚上。选引脚的时候要对照 pinctrl 的 dtsi 文件看清楚哪几个 pin 支持这路 UART 的 m0、m1 功能。部分 UART 支持硬件流控RTS/CTS和 RS485 自动方向控制但不是全部。如果你的项目要用 RS485得先确认选的这路 UART 是否带 RS485 功能否则就得用普通 GPIO 手动翻转方向实时性和稳定性都会打折扣。我一般的做法是先在原理图上把要用的串口和引脚圈出来然后回到芯片手册确认这路 UART 的复用能力和特性最后再动设备树。顺序反了很容易配到一半发现引脚冲突或者功能不支持返工成本很高。2.2 方案选型普通模式、DMA 还是 RS485RK356X 的 UART 在软件层面大致有三种使用形态选哪种取决于你的实际场景使用形态适用场景优点注意事项普通中断模式波特率 ≤ 115200数据量小配置简单稳定高波特率大数据量易丢包DMA 模式波特率 ≥ 460800或大数据块传输降低 CPU 占用减少丢数据需要正确配置 DMA 通道缓冲区对齐RS485 模式工业总线、多机通信硬件自动方向控制实时性好需要收发器支持设备树要配 RTS选型的核心逻辑是先看波特率和数据量再看总线类型。如果你只是打印个调试信息115200 的普通模式足够了没必要上 DMA徒增复杂度。但如果你要跑 921600 甚至更高或者一次要收发几百字节以上的数据包那 DMA 基本是必选项否则 CPU 会被串口中断拖垮还容易因为中断响应不及时丢数据。RS485 则是另一个维度的事情。它解决的是“一根总线挂多个设备、半双工通信”的问题。RK356X 的 UART 控制器内置了 RS485 模式可以通过 RTS 引脚自动控制收发方向省掉了应用层手动翻转 GPIO 的麻烦。这个功能在工业场景里非常实用后面我会单独展开。3. 设备树配置把 UART 从“死”变“活”的关键一步3.1 打开 UART 节点并配置引脚复用设备树是 RK356X 上一切外设的入口。UART 要在系统里出现/dev/ttyS*设备节点第一步就是在设备树里把对应的 UART 控制器状态改成okay并且配好 pinctrl。以启用 UART3 为例典型配置长这样基于常见 Rockchip 平台实践具体节点名以你的 SDK 为准uart3 { status okay; pinctrl-names default; pinctrl-0 uart3m1_xfer; };这里有几个细节值得说清楚pinctrl-0引用的uart3m1_xfer是 pinctrl 文件里预定义的一组引脚配置m1表示复用功能组 1。同一路 UART 可能有m0、m1甚至m2多组可选选哪组取决于你的硬件走线。一定要对着原理图确认引脚号配错了就是“设备节点在但引脚没信号”。如果要用流控还得加上uart3m1_cts和uart3m1_rts这两组 pinctrl。有些 SDK 里 UART 节点默认是disabled你不改它系统启动后根本不会有对应的 ttyS 设备ls /dev/ttyS*会少那么一个。我踩过的一个坑是pinctrl 名字写对了但引用的那个 pinctrl 组在 dtsi 里被别的外设占用了编译能过启动也不报错就是串口不通。后来用cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pins一看引脚被别的功能抢走了。所以配完一定要查引脚的实际复用状态别只看编译结果。3.2 波特率、DMA 与 RS485 的参数配置设备树里除了使能还能配一些运行时参数。虽然波特率通常在应用层用termios设置但 DMA 和 RS485 这类特性更适合在设备树里声明。DMA 配置大致是这样uart3 { status okay; pinctrl-names default; pinctrl-0 uart3m1_xfer; dmas dmac0 6, dmac0 7; dma-names tx, rx; };dmas里的通道号不是随便填的要查芯片的 DMA 请求映射表确认 UART3 的 TX/RX 对应哪两个 DMA 通道。填错了轻则 DMA 不工作回退到中断模式重则系统直接挂死。这个通道号每个平台都不一样必须查手册不能抄别的项目。RS485 的配置则是这样uart3 { status okay; pinctrl-names default; pinctrl-0 uart3m1_xfer uart3m1_rts; linux,rs485-enabled-at-boot-time; rs485-rts-active-low; rs485-rx-during-tx; };linux,rs485-enabled-at-boot-time让串口一启动就进入 RS485 模式。rs485-rts-active-low表示 RTS 低电平有效这个要跟你的收发器芯片匹配配反了方向控制就是错的。rs485-rx-during-tx允许发送时接收自己发的内容调试阶段有用正式产品里一般关掉。提示RS485 的 RTS 极性一定要对着收发器手册确认。我见过有人配反了结果发送的时候收发器处于接收态数据根本发不出去查了半天以为是驱动问题。4. 应用层读写从 open 到稳定收发4.1 用 termios 正确设置串口参数设备树配好之后应用层看到的就是/dev/ttyS3这样的节点。但直接open之后读写往往会遇到乱码或者收不到数据因为串口参数没设对。Linux 下标准做法是用termios结构体配置。下面是一段我常用的初始化代码把波特率、数据位、校验位、停止位、流控一次性配好#include termios.h #include fcntl.h #include unistd.h int uart_open(const char *dev, int baud) { int fd open(dev, O_RDWR | O_NOCTTY); if (fd 0) { return -1; } struct termios opt; tcgetattr(fd, opt); cfmakeraw(opt); // 原始模式不做任何字符处理 cfsetispeed(opt, baud); cfsetospeed(opt, baud); opt.c_cflag | (CLOCAL | CREAD); // 忽略调制解调器控制线使能接收 opt.c_cflag ~CSIZE; opt.c_cflag | CS8; // 8 位数据 opt.c_cflag ~PARENB; // 无校验 opt.c_cflag ~CSTOPB; // 1 位停止位 opt.c_cflag ~CRTSCTS; // 关闭硬件流控 opt.c_cc[VMIN] 0; // 非阻塞读 opt.c_cc[VTIME] 10; // 超时 1 秒 tcflush(fd, TCIOFLUSH); tcsetattr(fd, TCSANOW, opt); return fd; }几个容易出问题的地方cfmakeraw很关键。不加它终端会把0x0D、0x0A这些字符做转换二进制协议直接乱套。VMIN和VTIME决定read的行为。VMIN0, VTIME10表示最多等 1 秒有数据就返回。如果你要阻塞读固定长度就把VMIN设成你要的字节数。波特率宏B115200、B921600这些是标准值。RK356X 的 UART 时钟支持非标准波特率但需要驱动层配合一般项目用标准值就够了。4.2 高波特率下的 DMA 与缓冲区调优波特率一上到 460800 以上普通中断模式就开始吃力了。这时候 DMA 的价值就体现出来了。但 DMA 不是开了就万事大吉还有几个调优点第一缓冲区大小和对齐。DMA 传输对内存对齐有要求一般要求 4 字节或 8 字节对齐。如果你用read/write系统调用内核会帮你处理但如果你用mmap或者自己管理缓冲区就得注意对齐问题。第二读写块大小。高波特率下频繁的小块读写会让 DMA 频繁启停效率反而低。我一般会把一次读写的块大小设成 256 字节或 512 字节让 DMA 一次搬完减少中断次数。第三超时设置。DMA 模式下如果对端发了一半就不发了read可能会一直等。所以VTIME要设一个合理值配合应用层的协议超时机制避免卡死。实测下来在 RK356X 上跑 921600 波特率开 DMA 之后 CPU 占用能从 15% 左右降到 3% 以下而且连续收发几万字节不丢包。这个提升在数据密集的场景里非常明显。4.3 RS485 模式下的收发时序控制RS485 是半双工发送和接收不能同时进行。RK356X 的硬件 RS485 模式会自动控制 RTS 引脚来切换方向但应用层还是要注意时序。关键点是发送完成之前不能立刻去读。因为硬件需要一点时间把最后几个字节移出去然后才切换方向。如果你write完马上read可能会读到自己的回声或者干脆读不到对端的响应。我的做法是在write之后加一个基于波特率的延时或者用tcdrain等待发送缓冲区真正清空write(fd, buf, len); tcdrain(fd); // 等待所有数据发送完成 // 然后再 readtcdrain会阻塞到发送缓冲区为空比手动算延时靠谱。如果对端响应很快还可以配合select或poll来等数据避免死等。注意RS485 总线上如果挂了多个设备一定要保证同一时刻只有一个设备在发送。这个靠应用层协议来保证硬件层面帮不了你。5. 常见问题排查与实战避坑5.1 串口不通、乱码、丢数据怎么查串口问题排查有一套相对固定的思路我整理成一张速查表遇到问题按顺序过一遍基本能定位到原因现象可能原因排查方法/dev/ttyS*不存在设备树没使能或引脚冲突查 dmesg看 pinctrl 状态能发不能收TX/RX 接反或引脚复用错对调 TX/RX 试查原理图收到乱码波特率不匹配或时钟不对两端波特率对齐查时钟配置高波特率丢数据中断模式扛不住开 DMA加大缓冲区RS485 发不出去RTS 极性配反改rs485-rts-active-lowDMA 开启后卡死DMA 通道号填错查手册确认通道映射排查的时候示波器或者逻辑分析仪是最有效的工具。先量 TX 引脚有没有波形波形对不对波特率、电平再看 RX 有没有回应。软件层面查半天不如量一下波形来得快。5.2 几个我实际踩过的坑坑一调试串口被复用。有一次为了省引脚把默认调试串口那路 UART 复用成了普通串口结果系统启动日志全没了出了问题只能靠猜。后来学乖了调试串口永远单独留着哪怕引脚紧张也不动它。坑二DMA 通道号抄错。从别的项目抄了一段 DMA 配置通道号没改编译能过启动也不报错就是串口一收大数据就卡死。查了两天才发现通道号对不上。DMA 通道号必须查当前芯片的手册没有捷径。坑三RS485 方向切换延时不够。早期用 GPIO 手动控制方向write完立刻翻转 GPIO结果最后一个字节还没发完方向就切了数据被截断。后来改用硬件 RS485 模式或者write后加tcdrain问题才解决。坑四termios 没配 raw 模式。传二进制协议的时候数据里的0x0D被终端转换成0x0A对端解析全乱。加上cfmakeraw之后正常。这个坑很隐蔽因为文本数据看不出问题只有二进制协议才暴露。5.3 性能与稳定性优化建议如果你对串口的稳定性要求比较高除了上面说的 DMA 和缓冲区调优还有几个点可以关注中断亲和性在多核平台上可以把 UART 中断绑定到某个 CPU 核上减少核间切换带来的延迟抖动。电源管理确认串口所在的电源域在系统休眠时不会被关掉否则唤醒后串口可能失效。看门狗配合如果串口通信是系统的关键链路可以在应用层加心跳机制通信断了能及时恢复。日志分级调试串口输出大量日志会影响实时性正式产品里把调试日志级别调高减少不必要的输出。6. 写在最后的一点个人体会RK356X 的 UART 用起来难点从来不在“怎么配”而在“怎么配得稳”。设备树、termios、DMA、RS485 这些知识点单独看都不复杂但组合到一个真实项目里任何一个环节没对齐都会表现为“串口不通”这一个笼统的现象。我的经验是遇到问题先分层硬件层量波形驱动层看 dmesg 和 pinctrl 状态应用层查 termios 和读写逻辑。一层一层往下剥比东改一下西改一下高效得多。另外DMA 和 RS485 这两个特性建议在项目早期就验证别等到后期数据量上来了才发现要改架构。尤其是 RS485 的 RTS 极性一定要在硬件打样阶段就跟硬件工程师确认清楚软件配反了还能改硬件走线错了就得飞线那才是真的麻烦。