瑞芯微RK3288驱动开发:UART串口驱动与调试串口配置实战
在瑞芯微RK3288驱动开发过程中,串口是嵌入式工程师打交道最频繁的外设——无论是板卡 bring-up 阶段用调试串口看内核日志,还是产品里挂接 4G 模组、GPS、传感器,UART 串口驱动几乎贯穿整个开发周期。本文以一个真实的板卡调试案例为线索,拆解 RK3288 UART 串口驱动与调试串口的配置全流程,覆盖设备树、内核驱动框架、应用层收发代码和常见翻车点。

一、问题场景
新拿到一块 RK3288 核心板,Uboot 阶段串口打印正常,但内核起来之后:
- 调试串口只输出了几行就戛然而止,后面一片空白;
- 换一个 UART 口挂外部模组,
cat /dev/ttyS1能收到数据,但echo发出去的数据对方收不到,或者乱码; - 用
stty -F /dev/ttyS1 115200配置波特率后,实测波形波特率偏离标称值,长报文偶发丢字节。
这三个问题正好对应了串口驱动里的三个层次:设备树配置、驱动框架挂载、波特率时钟精度。下面从原理出发逐个击破。
二、原理分析
1. RK3288 的 UART 硬件
RK3288 内部集成了 5 路 UART 控制器(UART0~UART4),IP 内核来自 Synopsys 的 DesignWare APB UART(DW_apb_uart)。这几路串口有一个很有意思的细节:UART2 是官方默认的调试串口,基地址被单独放到了 0xFF690000,其他四路(UART0/1/3/4)则依次排在 0xFF180000~0xFF1C0000 区间。
| 控制器 | 基地址 | 默认用途 |
|---|---|---|
| UART0 | 0xFF180000 | 一般用途 |
| UART1 | 0xFF190000 | 一般用途 |
| UART2 | 0xFF690000 | 调试串口(默认 console) |
| UART3 | 0xFF1B0000 | 一般用途 |
| UART4 | 0xFF1C0000 | 一般用途 |
2. Linux 内核驱动框架
Linux 对这类 8250 兼容串口统一走 serial/8250 子系统。DW_apb_uart 的具体驱动是 drivers/tty/serial/8250/8250_dw.c,它通过设备树 compatible 绑定:
// drivers/tty/serial/8250/8250_dw.c 中的匹配表(关键片段)
static const struct of_device_id dw8250_of_match[] = {
{ .compatible = "snps,dw-apb-uart" },
{ /* rockchip wrapper 继承 snps 兼容,走同一套驱动 */ },
{ /* end of list */ },
};
RK3288 的设备树节点里,compatible 会写成 "rockchip,rk3288-uart",前面挂一个 "snps,dw-apb-uart" 作为兼容回退。驱动加载后,每个 UART 注册一个 ttyS* 设备节点。
3. 波特率是怎么算出来的
这是串口驱动最容易出错的点。DW_apb_uart 的波特率由时钟分频得到:
baud = baudclk / (16 × divisor)
其中 divisor 由 DLL/DLM 两个寄存器拼成。RK3288 的串口 baudclk 默认取 24MHz(来自 CRU 的 SCLK_UART*)。以 115200 为例:
divisor = 24000000 / (16 × 115200) = 13.02
硬件只能取整数 13,于是实际波特率变成 24000000/(16×13) = 115384,偏差约 0.16%。这个误差在短报文里无所谓,但长报文或高波特率(如 921600)下误差被放大,就会丢字节。DW_apb_uart 专门提供了一个 DLF 分数分频寄存器来修正这个小数部分,内核 8250_dw 驱动会自动处理,前提是 clock-frequency 与 CRU 实际输出频率一致——不一致正是乱码的头号元凶。
三、代码实战
1. 设备树配置
在 RK3288 的设备树里打开一个 UART 节点,核心是三件事:status、pinctrl、时钟。
&uart2 {
status = "okay"; /* 使能 UART2 调试串口 */
pinctrl-names = "default";
pinctrl-0 = <&uart2_xfer>; /* 绑定引脚复用 */
};
&pinctrl {
uart2 {
uart2_xfer: uart2-xfer {
rockchip,pins =
/* TX : GPIO4_C0,复用为 UART2_TX */
<4 16 RK_FUNC_1 &pcfg_pull_none>,
/* RX : GPIO4_C1,复用为 UART2_RX */
<4 17 RK_FUNC_1 &pcfg_pull_none>;
};
};
};
如果要把 UART2 作为内核 console,还需要在命令行里指定。Uboot 的 bootargs 里加:
console=ttyS2,115200n8 # ttyS2 对应 UART2,n8 = 无校验 8 数据位
2. 应用层串口收发(termios 配置)
硬件和内核就绪后,应用层通过 termios 配置串口参数。下面是一段标准的串口打开+配置+收发代码:
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
int main(int argc, char *argv[])
{
int fd;
struct termios opt;
if (argc != 2) {
printf("usage: %s /dev/ttyS1\n", argv[0]);
return -1;
}
/* 打开串口:O_NOCTTY 防止变成控制终端,O_NDELAY 非阻塞打开 */
fd = open(argv[1], O_RDWR | O_NOCTTY | O_NDELAY);
if (fd < 0) {
perror("open");
return -1;
}
tcgetattr(fd, &opt);
/* 输入输出波特率统一 115200 */
cfsetispeed(&opt, B115200);
cfsetospeed(&opt, B115200);
/* 8 数据位、无校验、1 停止位 */
opt.c_cflag &= ~PARENB; /* 关闭校验位 */
opt.c_cflag &= ~CSTOPB; /* 1 个停止位 */
opt.c_cflag &= ~CSIZE; /* 清除数据位掩码 */
opt.c_cflag |= CS8; /* 8 数据位 */
opt.c_cflag &= ~CRTSCTS; /* 关闭硬件流控(硬件没接 RTS/CTS 必须关) */
cfmakeraw(&opt); /* 原始模式,关闭行缓冲与回显 */
tcsetattr(fd, TCSANOW, &opt);
/* 发送测试字符串 */
char *msg = "RK3288 UART test\n";
write(fd, msg, strlen(msg));
/* 回读串口数据 */
char buf[64] = {0};
int n = read(fd, buf, sizeof(buf) - 1);
printf("recv %d bytes: %s\n", n, buf);
close(fd);
return 0;
}
3. 命令行快速自测(回环测试)
先把 TX/RX 短接,用 shell 一行命令验证收发:
# 配置波特率与原始模式
stty -F /dev/ttyS1 115200 raw -echo
# 后台持续读,前台写
cat /dev/ttyS1 &
echo "hello rk3288" > /dev/ttyS1
四、运行结果
回环测试下,终端会立刻打印出写入的字符串:
hello rk3288
用交叉编译的 C 程序在板端运行:
# ./uart_test /dev/ttyS1
recv 16 bytes: RK3288 UART test
串口波特率实测(逻辑分析仪抓 TX 波形):
标称 115200 → 实测 115384(偏差 0.16%)
标称 921600 → 实测 923076(偏差 0.16%)
这个偏差在 921600 波特率下仍小于容限(一般要求 <2%),收发正常,说明 24MHz 时钟源与 clock-frequency 一致,分数分频生效。
五、避坑指南
1. 调试串口起来后就没下文了。 九成是 bootargs 里的 console= 写错,或 UART2 的 pinctrl 没配上、TX/RX 引脚被别的外设复用。确认 console=ttyS2,115200n8 里的设备号与设备树 aliases 的映射一致。
2. 能发不能收,或收到乱码。 优先怀疑波特率时钟源。检查 DTS 里 clock-frequency 是否等于 CRU 实际输出的 SCLK_UART*,二者不一致时内核按错误的时钟算分频,必然乱码。
3. 挂了模组却死活收不到数据。 看是否误开了硬件流控。代码里 CRTSCTS 若开启,而硬件没接 RTS/CTS 线,发送会被流控信号卡住。模组场景通常直接关流控。
4. 长报文偶发丢字节。 两个方向排查:一是 FIFO 触发阈值与中断是否正常;二是高波特率下分数分频是否失效(时钟配置错误导致误差放大)。确认 8250_dw 驱动识别到 DLF 寄存器。
5. 应用层 open() 返回 Permission denied。 这是 ttyS 设备节点权限问题。临时可 chmod 666 /dev/ttyS1,正式量产建议写 udev 规则给串口节点授权。
6. 早期内核日志看不到。 bring-up 阶段 console 未初始化前的打印,靠 earlycon 打通。RK3288 UART2 用 8250 的 mmio32 早期控制台:
earlycon=uart8250,mmio32,0xff690000
六、总结
RK3288 的 UART 串口驱动本质上是「设备树正确配置 + 8250_dw 框架 + 时钟精度」三件事的叠加。设备树管使能和引脚复用,8250 子系统负责注册 ttyS* 节点与中断收发,而波特率精度则由 24MHz 时钟源和 DLF 分数分频共同保证。把这三层捋顺,串口这个最基础也最容易踩坑的外设就能一次点亮。调试串口和业务串口遵循同一套逻辑,区别只在 console/earlycon 的启动参数配置上。
你在 RK3288 串口调试中遇到过哪些诡异的乱码或丢字节问题?欢迎在评论区交流你的排查思路。
更多推荐


所有评论(0)