4. UART串口驱动
任务目标
在RK3588 Linux环境下,编写UART串口驱动程序,实现数据收发与波特率配置,并通过用户空间程序完成回环通信验证。本案例聚焦于设备树配置、串口驱动框架及用户空间访问路径,不涉及DMA或硬件流控等高级特性。
环境与依赖
- RK3588开发板,内核源码树已就绪(路径:
~/kernel) - 交叉编译工具链:
aarch64-linux-gnu-gcc - 硬件连接:将UART2的TX(引脚GPIO4_B4)与RX(GPIO4_B5)短接,形成物理回环
设备树配置
在设备树文件(如rk3588-evb.dts)中确认或添加UART2节点配置,确保引脚复用正确且未与其他外设冲突:
&uart2 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&uart2m0_xfer>;
};
驱动实现
RK3588的UART控制器由8250_dw驱动覆盖,本案例通过设备树使能现有驱动,并编写一个轻量级平台驱动用于演示数据收发与波特率设置接口。核心代码如下:
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/serial_core.h>
#include <linux/tty.h>
#include <linux/serial_8250.h>
#define DRV_NAME "rk_uart_demo"
static struct uart_port demo_port;
static int demo_uart_probe(struct platform_device *pdev)
{
struct resource *res;
int ret;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENXIO;
memset(&demo_port, 0, sizeof(demo_port));
demo_port.membase = devm_ioremap(&pdev->dev, res->start,
resource_size(res));
if (!demo_port.membase)
return -ENOMEM;
demo_port.mapbase = res->start;
demo_port.irq = platform_get_irq(pdev, 0);
demo_port.uartclk = 24000000; /* 24MHz APB时钟 */
demo_port.fifosize = 64;
demo_port.ops = &serial8250_port_ops;
demo_port.flags = UPF_BOOT_AUTOCONF | UPF_IOREMAP;
demo_port.dev = &pdev->dev;
demo_port.type = PORT_16550A;
demo_port.iotype = UPIO_MEM32;
demo_port.regshift = 2;
ret = serial8250_register_8250_port(&demo_port);
if (ret < 0)
return ret;
platform_set_drvdata(pdev, &demo_port);
dev_info(&pdev->dev, "UART demo port registered at 0x%llx\n",
(u64)res->start);
return 0;
}
static int demo_uart_remove(struct platform_device *pdev)
{
serial8250_unregister_port(demo_port.line);
return 0;
}
static const struct of_device_id demo_uart_of_match[] = {
{ .compatible = "rockchip,rk3588-uart-demo" },
{ }
};
MODULE_DEVICE_TABLE(of, demo_uart_of_match);
static struct platform_driver demo_uart_driver = {
.probe = demo_uart_probe,
.remove = demo_uart_remove,
.driver = {
.name = DRV_NAME,
.of_match_table = demo_uart_of_match,
},
};
module_platform_driver(demo_uart_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("RK3588 Driver Course");
MODULE_DESCRIPTION("RK3588 UART demo driver");
将上述代码保存为rk_uart_demo.c,并添加到内核drivers/tty/serial/Makefile:
obj-m += rk_uart_demo.o
编译与加载
# 编译驱动模块
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- M=drivers/tty/serial modules
# 将生成的 rk_uart_demo.ko 拷贝至开发板,加载模块
insmod rk_uart_demo.ko
# 确认设备节点生成(应显示 /dev/ttyS1 或类似名称)
ls /dev/ttyS*
用户空间回环测试
编写测试程序,打开UART设备,设置波特率为115200,发送数据并读取回环数据:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <string.h>
int main(void)
{
int fd;
struct termios tty;
char tx_buf[] = "RK3588 UART Loopback Test";
char rx_buf[64] = {0};
fd = open("/dev/ttyS1", O_RDWR | O_NOCTTY);
if (fd < 0) {
perror("open");
return -1;
}
memset(&tty, 0, sizeof(tty));
cfsetospeed(&tty, B115200);
cfsetispeed(&tty, B115200);
tty.c_cflag = CS8 | CLOCAL | CREAD;
tty.c_iflag = 0;
tty.c_oflag = 0;
tty.c_lflag = 0;
tcsetattr(fd, TCSANOW, &tty);
write(fd, tx_buf, strlen(tx_buf));
usleep(100000); /* 等待数据回环 */
read(fd, rx_buf, sizeof(rx_buf));
printf("TX: %s\n", tx_buf);
printf("RX: %s\n", rx_buf);
if (strcmp(tx_buf, rx_buf) == 0)
printf("PASS: Loopback data matches\n");
else
printf("FAIL: Data mismatch\n");
close(fd);
return 0;
}
编译并运行:
aarch64-linux-gnu-gcc -o uart_test uart_test.c
./uart_test
验证判定标准
- 驱动加载后,
dmesg输出包含“UART demo port registered”信息 /dev/ttyS1节点存在且可打开- 测试程序输出TX与RX内容一致,并显示
PASS字样 - 使用
stty -F /dev/ttyS1 -a可查看当前波特率确为115200
若测试失败,请检查TX/RX引脚是否短接正确,以及设备树中uart2m0_xfer引脚复用是否与其他节点冲突。