在前文中,我们已经完成了RK3576平台的Linux内核编译与设备树配置,掌握了基本的驱动开发环境。接下来,我们将深入探讨UART(通用异步收发传输器)驱动的实现。UART是嵌入式系统中最为常用的外设之一,用于串行通信。
本章将从三个层面展开: UART驱动开发、调试方法、常见问题和解决办法
对上篇感兴趣 可点此浏览
四、UART驱动开发
4.1 内核空间UART操作
#include <linux/serial_core.h>
#include <linux/tty_flip.h>
#include <linux/of.h>
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 <linux/platform_device.h>
#include <linux/serial.h>
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 <stdio.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <string.h>
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内核