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

在前文中,我们已经完成了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内核

相关推荐
LCG元8 小时前
STM32软件模拟I2C驱动多从机实战:AHT20温湿度 + BH1750光照 + 0.96寸OLED实时显示
stm32·单片机·嵌入式硬件
soulermax9 小时前
cuda thread block 和 gpu thread warp 映射关系
架构·硬件架构
天天爱吃肉821810 小时前
【工程师硬件学习笔记:串口‑总线体系与三电试验室异构系统集成深度实战】
大数据·笔记·python·嵌入式硬件·学习·汽车
hsjiasb19 小时前
FreeRTOS学习(二十三)——消息缓冲区(Message Buffer)
stm32·单片机·嵌入式硬件·学习·freertos
沸速存储1 天前
CPU 和 GPU 核心差别在哪?为什么 AI 训练离不开 GPU
服务器·人工智能·科技·嵌入式硬件·电脑
我不会JAVA!1 天前
【无标题】
嵌入式硬件·h.264
芯岭技术郦1 天前
OM6625A芯片 Timer 定时器计数模式使用说明
stm32·单片机·嵌入式硬件
Dr.kangder1 天前
嵌入式面试总结(八)——大小端
嵌入式硬件·面试·职场和发展·架构·嵌入式
星马梦缘1 天前
“零一”智能硬件开发挑战赛 宣传稿
驱动开发·智能硬件·活动·培训·科协
zlinear数据采集卡1 天前
D223的SRAM硬件记录仪:外部触发与瞬态波形捕获方案
arm开发·嵌入式硬件·fpga开发·开源