RK3576开发板UART驱动实战教程,基于触觉智能开发板演示

科技时尚 2026-08-09 chy123 3281

触觉智能Purple Pi OH2开发板,板载2x20Pin的GPIO排针(兼容树莓派),可拓展UART串口等设备。本文基于Purple Pi OH2开发板讲解和调试UART概念、原理、代码示例、调试方法等

一、UART基本概念

1.1什么是UART?

UART(Universal Asynchronous Receiver/Transmitter,通用异步收发传输器)是最常用的串行通信接口,用于:

调试输出:打印内核启动信息

设备通信:与GPS、蓝牙、WiFi等模块通信

数据透传:与上位机或其他MCU交换数据

1.2 UART通信原理

图片

参数

说明

常用值

波特率

每秒传输的比特数

115200, 9600

数据位

每个字符的数据位数

8 bits

停止位

字符结束标志

1 bit

校验位

错误检测

None/Even/Odd

1.3 RK3576 UART资源

RK3576提供12路UART,触觉智能Purple Pi OH2配套核心板SOM7609系列全引脚引出:

uart0~uart11

uart0通常用作调试串口(Debug)

从设备树别名可见:

serial0 = &uart0; // 通常 Debug 串口 serial1 = &uart1; serial2 = &uart2; ... serial11 = &uart11;

二、UART硬件连接

2.1 Purple Pi OH2板载UART接口(GPIO排针)

2.2 UART引脚定义

信号

方向

说明

TX

输出

发送数据

RX

输入

接收数据

RTS

输出

请求发送(流控)

CTS

输入

清除发送(流控)

2.3 UART双工模式

UART支持两种通信模式,由硬件设计和软件配置决定。

模式

特点

硬件要求

应用场景

全双工

TX、RX同时工作,独立传输

TX、RX分开连接

调试串口、蓝牙、GPS

半双工

TX、RX分时工作,共享总线

需要方向控制引脚

RS-485、Modbus

全双工配置

设备树配置(全双工)

&uart2 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&uart2m0_xfer>; /* TX/RX 分开 */ };

半双工配置

图片

软件实现(半双工RS-485)

/* 发送数据 */ void rs485_send(struct uart_dev *priv, u8 *data, int len) { /* 拉高 DE 引脚,切换到发送模式 */ gpio_set_value(priv->de_gpio, 1); /* 发送数据 */ uart_write(priv, data, len); /* 等待发送完成 */ uart_wait_tx_complete(priv); /* 拉低 DE 引脚,切换到接收模式 */ gpio_set_value(priv->de_gpio, 0); }

设备树配置(半双工+方向控制)

&uart2 { status = "okay"; pinctrl-names = "default", "rs485"; pinctrl-0 = <&uart2m0_xfer>; pinctrl-1 = <&uart2m0_rs485>; rs485-gpios = <&gpio4 RK_PA6 GPIO_ACTIVE_HIGH>; rs485-rts-active-high; linux,rs485-enabled-at-boot-time; };

流控模式(硬件流控)

图片

设备树配置(硬件流控)

&uart2 { status = "okay"; pinctrl-names = "default"; /* 包含 TX/RX/RTS/CTS */ pinctrl-0 = <&uart2m0_xfer &uart2m0_ctsn &uart2m0_rtsn>; /* 启用 CTS/RTS 流控 */ cts-cts-cts; rts-cts-rts; };

双工模式对比

特性

全双工

半双工

硬件流控

数据线

TX/RX分开

TX/RX共用

TX/RX + RTS/CTS

同时收发

支持

不支持

支持

方向切换

不需要

需要GPIO

自动

速率限制

低(切换开销)

应用场景

调试、蓝牙

RS-485、Modbus

高速可靠传输

三、UART设备树配置

3.1使能UART节点

编辑ido-evb7609-v1a.dtsi

&uart2 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&uart2m0_xfer &uart2m0_ctsn>; };

3.2 UART蓝牙配置

wireless_bluetooth: wireless-bluetooth { compatible = "bluetooth-platdata"; clocks = <&hym8563>; clock-names = "ext_clock"; uart_rts_gpios = <&gpio1 RK_PC4 GPIO_ACTIVE_LOW>; pinctrl-names = "default", "rts_gpio"; pinctrl-0 = <&uart2m0_rtsn>; pinctrl-1 = <&uart2_gpios>; BT,reset_gpio = <&gpio1 RK_PC3 GPIO_ACTIVE_HIGH>; BT,wake_gpio = <&gpio1 RK_PD4 GPIO_ACTIVE_HIGH>; BT,wake_host_irq = <&gpio0 RK_PB1 GPIO_ACTIVE_HIGH>; status = "okay"; };

status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&uart3m0_xfer>; };

3.2 pinctrl引脚配置

&pinctrl { uart2 { uart2m0_xfer: uart2m0-xfer { rockchip,pins = /* uart2_rxm0 */ <4 RK_PA4 8 &pcfg_pull_up>, /* uart2_txm0 */ <4 RK_PA5 8 &pcfg_pull_none>; }; }; uart3 { uart3m0_xfer: uart3m0-xfer { rockchip,pins = /* uart3_rxm0 */ <3 RK_PC6 9 &pcfg_pull_up>, /* uart3_txm0 */ <3 RK_PC7 9 &pcfg_pull_none>; }; }; };

四、UART驱动开发

4.1内核空间UART操作

#include #include #include struct my_uart_dev { struct uart_port port; struct clk *clk; void __iomem *regs; int irq; }; /* UART 寄存器偏移 */ #define UART_TX 0x00 #define UART_RX 0x00 #define UART_IER 0x04 #define UART_IIR 0x08 #define UART_LSR 0x14 /* 发送数据 */ static void my_uart_tx_char(struct my_uart_dev *priv, unsigned char ch) { /* 等待发送 FIFO 空 */ while (!(readl(priv->regs + UART_LSR) & 0x20)) cpu_relax(); /* 写入数据 */ writel(ch, priv->regs + UART_TX); } /* 接收中断处理 */ static irqreturn_t my_uart_irq(int irq, void *dev_id) { struct my_uart_dev *priv = dev_id; unsigned int iir, status; unsigned char ch; iir = readl(priv->regs + UART_IIR); /* 处理接收中断 */ if (iir & 0x04) { status = readl(priv->regs + UART_LSR); while (status & 0x01) { /* 数据就绪 */ ch = readl(priv->regs + UART_RX); /* 将数据推送到 TTY 层 */ tty_insert_flip_char(&priv->port.state->port, ch, TTY_NORMAL); tty_flip_buffer_push(&priv->port.state->port); status = readl(priv->regs + UART_LSR); } } return IRQ_HANDLED; } /* UART 操作接口 */ static const struct uart_ops my_uart_ops = { .tx_empty = my_uart_tx_empty, .set_mctrl = my_uart_set_mctrl, .get_mctrl = my_uart_get_mctrl, .stop_tx = my_uart_stop_tx, .start_tx = my_uart_start_tx, .stop_rx = my_uart_stop_rx, .enable_ms = my_uart_enable_ms, .break_ctl = my_uart_break_ctl, .startup = my_uart_startup, .shutdown = my_uart_shutdown, .set_termios = my_uart_set_termios, .type = my_uart_type, .release_port = my_uart_release_port, .request_port = my_uart_request_port, .config_port = my_uart_config_port, .verify_port = my_uart_verify_port, };

4.2使用Platform UART驱动框架

#include #include static int my_uart_probe(struct platform_device *pdev) { struct my_uart_dev *priv; struct resource *res; int ret; priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; /* 获取寄存器资源 */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); priv->regs = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(priv->regs)) return PTR_ERR(priv->regs); /* 获取中断 */ priv->irq = platform_get_irq(pdev, 0); if (priv->irq < 0) return priv->irq; /* 获取时钟 */ priv->clk = devm_clk_get(&pdev->dev, "baudclk"); if (IS_ERR(priv->clk)) return PTR_ERR(priv->clk); clk_prepare_enable(priv->clk); /* 初始化 uart_port */ priv->port.iotype = UPIO_MEM; priv->port.membase = (void __iomem *)priv->regs; priv->port.mapbase = res->start; priv->port.irq = priv->irq; priv->port.ops = &my_uart_ops; priv->port.dev = &pdev->dev; priv->port.line = pdev->id; /* UART 编号 */ /* 注册 UART */ ret = uart_add_one_port(&my_uart_driver, &priv->port); if (ret) goto err_clk; platform_set_drvdata(pdev, priv); return 0; err_clk: clk_disable_unprepare(priv->clk); return ret; }

4.3用户空间UART操作

#include #include #include #include #include int uart_open(const char *device) { int fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY); if (fd < 0) { perror("Failed to open UART"); return -1; } /* 清除非阻塞标志 */ fcntl(fd, F_SETFL, 0); return fd; } int uart_config(int fd, int baudrate) { struct termios options; /* 获取当前配置 */ tcgetattr(fd, &options); /* 设置波特率 */ switch (baudrate) { case 115200: cfsetispeed(&options, B115200); cfsetospeed(&options, B115200); break; case 9600: cfsetispeed(&options, B9600); cfsetospeed(&options, B9600); break; default: return -1; } /* 8N1 配置 */ options.c_cflag |= (CLOCAL | CREAD); /* 本地连接,使能接收 */ options.c_cflag &= ~CSIZE; /* 清除数据位掩码 */ options.c_cflag |= CS8; /* 8 数据位 */ options.c_cflag &= ~PARENB; /* 无校验 */ options.c_cflag &= ~CSTOPB; /* 1 停止位 */ /* 原始模式 */ options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); options.c_oflag &= ~OPOST; /* 设置新配置 */ tcsetattr(fd, TCSANOW, &options); return 0; } int uart_send(int fd, const char *data, int len) { return write(fd, data, len); } int uart_receive(int fd, char *buf, int len) { return read(fd, buf, len); } int main() { int fd; char buf[256]; int n; /* 打开 UART2 */ fd = uart_open("/dev/ttyS2"); if (fd < 0) return -1; /* 配置 115200 8N1 */ uart_config(fd, 115200); /* 发送数据 */ const char *msg = "Hello UART!\n"; uart_send(fd, msg, strlen(msg)); /* 接收数据 */ while (1) { n = uart_receive(fd, buf, sizeof(buf) - 1); if (n > 0) { buf[n] = '\0'; printf("Received: %s\n", buf); } usleep(10000); } close(fd); return 0; }

五、调试方法

5.1查看UART设备

# 查看注册的 UART cat /proc/tty/drivers # 查看串口设备 ls -la /dev/ttyS* # 查看 console 配置 cat /proc/consoles

5.2测试串口通信

# 配置串口参数 stty -F /dev/ttyS2 115200 cs8 -cstopb -parenb # 发送测试数据 echo "test" > /dev/ttyS2 # 接收数据(阻塞模式) cat /dev/ttyS2 # 使用 minicom 工具 minicom -D /dev/ttyS2 -b 115200

5.3抓取串口波形

六、常见问题

# 使用逻辑分析仪工具 # 或查看内核日志 dmesg | grep tty

6.1串口无法打开

检查设备节点是否存在:ls /dev/ttyS*

检查权限:chmod 666 /dev/ttyS2

6.2数据乱码

波特率配置是否正确

检查数据位/停止位/校验位

6.3无法接收数据

检查RX引脚连接

确认中断是否正常触发

检查流控设置(RTS/CTS)

参考文档

RKDocs/common/UART/

Documentation/serial/driver

本教程适用于Android14/Linux6.1内核