第14章 Linux终端设备驱动之URAT设备驱动

本文深入解析了UART设备驱动的设计与实现,重点介绍了Linux内核中串口核心层提供的通用tty驱动层,包括uart_driver、uart_port和uart_ops等关键结构体的作用及其实现方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

14.6 UART(通用异步收发器)设备驱动

    一个特定的 UART 设备驱动完全可以定义tty_driver 并实现 tty_operations 其中的成员函数,但Linux 已经在serial_core.c 中实现UART 设备的通用 tty 驱动层(串口核心层),UART 驱动的主要任务演变成实现 

serial-core.c 中定义的一组 uart_xxx 接口。如图 14.4 所示。


14.4 串口核心层

    serial_core.c 串口核心层完全可以被当作 tty 设备驱动的实例,实现了 UART设备的 tty 驱动。这体现了软件工程中设备驱动的分层思想

    串口核心层为串口设备驱动提供了如下 3 个结构体。

1.uart_driver

    uart_driver 包含串口设备的驱动名、设备名、设备号等信息,uart_driver封装了 tty_driver,使得底层的 UART 驱动无需关心 tty_driver,其定义如代码清单 14.13 所示。

代码清单 14.13 uart_driver 结构体

include/linux/serial_core.h

struct uart_driver {
        struct module           *owner;
        const char              *driver_name;/* 驱动名 */
        const char              *dev_name;/* 设备名 */
        int                      major;/* 主设备号 */
        int                      minor;/* 次设备号 */
        int                      nr;

        struct console          *cons;

        /*
         * these are private; the low level driver should not
         * touch these; they should be initialised to NULL
         */
        struct uart_state       *state;
        struct tty_driver       *tty_driver;
};

注册/注销 uart_driver,使用如下接口:

int uart_register_driver(struct uart_driver *drv);

void uart_unregister_driver(struct uart_driver *drv);

uart_register_driver()和 uart_unregister_driver()中分别包含了 tty_register_driver()和tty_unregister_driver()的操作,如代码清单 14.14 所示。

代码清单 14.14 uart_register_driver()和 uart_unregister_driver()函数

drivers/serial/serial_core.c 

int uart_register_driver(struct uart_driver *drv)
{
struct tty_driver *normal = NULL;
int i, retval;

BUG_ON(drv->state);

/*
* Maybe we should be using a slab cache for this, especially if
* we have a large number of ports to handle.
*/
drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
retval = -ENOMEM;
if (!drv->state)
goto out;

normal  = alloc_tty_driver(drv->nr);
if (!normal)
goto out;

drv->tty_driver = normal;

normal->owner = drv->owner;
normal->driver_name = drv->driver_name;
normal->name = drv->dev_name;
normal->major = drv->major;
normal->minor_start = drv->minor;
normal->type = TTY_DRIVER_TYPE_SERIAL;
normal->subtype = SERIAL_TYPE_NORMAL;
normal->init_termios = tty_std_termios;
normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
normal->driver_state    = drv;
tty_set_operations(normal, &uart_ops);

/*
* Initialise the UART state(s).
*/
for (i = 0; i < drv->nr; i++) {
struct uart_state *state = drv->state + i;
state->close_delay     = 500; /* .5 seconds */
state->closing_wait    = 30000; /* 30 seconds */
mutex_init(&state->mutex);
}

retval = tty_register_driver(normal);
 out:
if (retval < 0) {
put_tty_driver(normal);
kfree(drv->state);
}
return retval;
}

void uart_unregister_driver(struct uart_driver *drv)
{
struct tty_driver *p = drv->tty_driver;
tty_unregister_driver(p);
put_tty_driver(p);
kfree(drv->state);
drv->tty_driver = NULL;
}

2.uart_port

    uart_port 用于描述一个 UART 端口(直接对应于一个串口)的 I/O 端口或 I/O 内存地址、FIFO大小、端口类型等信息,其定义如代码清单 14.15。

代码清单 14.15 uart_port 结构体

include/linux/serial_core.h

struct uart_port {
        spinlock_t              lock;                   /* port lock */
        unsigned int            iobase;                 /* in/out[bwl] */
        unsigned char __iomem   *membase;               /* read/write[bwl] */
        unsigned int            irq;                    /* irq number */
        unsigned int            uartclk;                /* base uart clock */
        unsigned int            fifosize;               /* tx fifo size */
        unsigned char           x_char;                 /* xon/xoff char */
        unsigned char           regshift;               /* reg offset shift */
        unsigned char           iotype;                 /* io access style */
        unsigned char           unused1;

#define UPIO_PORT               (0)
#define UPIO_HUB6               (1)
#define UPIO_MEM                (2)
#define UPIO_MEM32              (3)
#define UPIO_AU                 (4)                     /* Au1x00 type IO */
#define UPIO_TSI                (5)                     /* Tsi108/109 type IO */
#define UPIO_DWAPB              (6)                     /* DesignWare APB UART */
#define UPIO_RM9000             (7)                     /* RM9000 type IO */

        unsigned int            read_status_mask;       /* driver specific */
        unsigned int            ignore_status_mask;     /* driver specific */
        struct uart_info        *info;                  /* pointer to parent info */
        struct uart_icount      icount;                 /* statistics */
        struct console          *cons;                  /* struct console, if any */
#ifdef CONFIG_SERIAL_CORE_CONSOLE
        unsigned long           sysrq;                  /* sysrq timeout */
#endif
        upf_t                   flags;

#define UPF_FOURPORT            ((__force upf_t) (1 << 1))
#define UPF_SAK                 ((__force upf_t) (1 << 2))
#define UPF_SPD_MASK            ((__force upf_t) (0x1030))
#define UPF_SPD_HI              ((__force upf_t) (0x0010))
#define UPF_SPD_VHI             ((__force upf_t) (0x0020))
#define UPF_SPD_CUST            ((__force upf_t) (0x0030))
#define UPF_SPD_SHI             ((__force upf_t) (0x1000))
#define UPF_SPD_WARP            ((__force upf_t) (0x1010))
#define UPF_SKIP_TEST           ((__force upf_t) (1 << 6))
#define UPF_AUTO_IRQ            ((__force upf_t) (1 << 7))
#define UPF_HARDPPS_CD          ((__force upf_t) (1 << 11))
#define UPF_LOW_LATENCY         ((__force upf_t) (1 << 13))
#define UPF_BUGGY_UART          ((__force upf_t) (1 << 14))
#define UPF_MAGIC_MULTIPLIER    ((__force upf_t) (1 << 16))
#define UPF_CONS_FLOW           ((__force upf_t) (1 << 23))
#define UPF_SHARE_IRQ           ((__force upf_t) (1 << 24))
#define UPF_BOOT_AUTOCONF       ((__force upf_t) (1 << 28))
#define UPF_FIXED_PORT          ((__force upf_t) (1 << 29))
#define UPF_DEAD                ((__force upf_t) (1 << 30))
#define UPF_IOREMAP             ((__force upf_t) (1 << 31))

#define UPF_CHANGE_MASK         ((__force upf_t) (0x17fff))
#define UPF_USR_MASK            ((__force upf_t) (UPF_SPD_MASK|UPF_LOW_LATENCY))

        unsigned int            mctrl;                  /* current modem ctrl settings */
        unsigned int            timeout;                /* character-based timeout */
        unsigned int            type;                   /* port type */
        const struct uart_ops   *ops;
        unsigned int            custom_divisor;
        unsigned int            line;                   /* port index */
        unsigned long           mapbase;                /* for ioremap */
        struct device           *dev;                   /* parent device */
        unsigned char           hub6;                   /* this should be in the 8250 driver */
        unsigned char           unused[3];
        void                    *private_data;          /* generic platform data pointer */
};

串口核心层提供如下函数来添加一个端口:

int uart_add_one_port(struct uart_driver *drv, struct uart_port *port);

这个函数的调用应该发生在 uart_register_driver()之后,uart_add_one_port()的一个最重要作用是封装了 tty_register_device()。

int uart_add_one_port(struct uart_driver *drv, struct uart_port *port)
{
struct uart_state *state;
int ret = 0;

BUG_ON(in_interrupt());

if (port->line >= drv->nr)
return -EINVAL;

state = drv->state + port->line;

mutex_lock(&port_mutex);
mutex_lock(&state->mutex);
if (state->port) {
ret = -EINVAL;
goto out;
}

state->port = port;
port->cons = drv->cons;
port->info = state->info;
/*
* If this port is a console, then the spinlock is already
* initialised.
*/
if (!(uart_console(port) && (port->cons->flags & CON_ENABLED))) {
spin_lock_init(&port->lock);
lockdep_set_class(&port->lock, &port_lock_key);
}

uart_configure_port(drv, state, port);

/*
* Register the port whether it's detected or not.  This allows
* setserial to be used to alter this ports parameters.
*/
tty_register_device(drv->tty_driver, port->line, port->dev);

/*
* If this driver supports console, and it hasn't been
* successfully registered yet, try to re-register it.
* It may be that the port was not available.
*/
if (port->type != PORT_UNKNOWN &&
    port->cons && !(port->cons->flags & CON_ENABLED))
register_console(port->cons);

/*
* Ensure UPF_DEAD is not set.
*/
port->flags &= ~UPF_DEAD;

 out:
mutex_unlock(&state->mutex);
mutex_unlock(&port_mutex);

return ret;
}

串口核心层提供如下函数来移除一个端口:

int uart_remove_one_port(struct uart_driver *drv, struct uart_port *port);

其中会调用 tty_unregister_device()。

int uart_remove_one_port(struct uart_driver *drv, struct uart_port *port)
{
struct uart_state *state = drv->state + port->line;
struct uart_info *info;

BUG_ON(in_interrupt());

if (state->port != port)
printk(KERN_ALERT "Removing wrong port: %p != %p\n",
state->port, port);

mutex_lock(&port_mutex);

/*
* Mark the port "dead" - this prevents any opens from
* succeeding while we shut down the port.
*/
mutex_lock(&state->mutex);
port->flags |= UPF_DEAD;
mutex_unlock(&state->mutex);

/*
* Remove the devices from the tty layer
*/
tty_unregister_device(drv->tty_driver, port->line);

info = state->info;
if (info && info->tty)
tty_vhangup(info->tty);

/*
* All users of this port should now be disconnected from
* this driver, and the port shut down.  We should be the
* only thread fiddling with this port from now on.
*/
state->info = NULL;

/*
* Free the port IO and memory resources, if any.
*/
if (port->type != PORT_UNKNOWN)
port->ops->release_port(port);

/*
* Indicate that there isn't a port here anymore.
*/
port->type = PORT_UNKNOWN;

/*
* Kill the tasklet, and free resources.
*/
if (info) {
tasklet_kill(&info->tlet);
kfree(info);
}

state->port = NULL;
mutex_unlock(&port_mutex);

return 0;

}

3.uart_ops

    uart_ops 定义针对 UART 的一系列操作,包括发送、接收及线路设置等,其定义如代码清单 14.16。Linux 驱动中面向对象编程中基类、派生类的关系,派生类(子类)针对特定的事物会更加具体,而基类(父类)则站在更高的抽象层次上。

代码清单 14.16 uart_ops 结构体

include/linux/serial_core.h

/*
 * This structure describes all the operations that can be
 * done on the physical hardware.
 */
struct uart_ops {
        unsigned int    (*tx_empty)(struct uart_port *);
        void            (*set_mctrl)(struct uart_port *, unsigned int mctrl);
        unsigned int    (*get_mctrl)(struct uart_port *);
        void            (*stop_tx)(struct uart_port *);
        void            (*start_tx)(struct uart_port *);
        void            (*send_xchar)(struct uart_port *, char ch);
        void            (*stop_rx)(struct uart_port *);
        void            (*enable_ms)(struct uart_port *);
        void            (*break_ctl)(struct uart_port *, int ctl);
        int             (*startup)(struct uart_port *);
        void            (*shutdown)(struct uart_port *);
        void            (*set_termios)(struct uart_port *, struct ktermios *new, struct ktermios *old);
        void            (*pm)(struct uart_port *, unsigned int state, unsigned int oldstate);
        int             (*set_wake)(struct uart_port *, unsigned int state);

        /*一个描述端口类型的字符串 */
        const char *(*type)(struct uart_port *);

       /* 释放该端口使用的 I/O 和 memory 资源 */
        void            (*release_port)(struct uart_port *);

        /* 申请该端口使用的 I/O 和 memory 资源 */
        int             (*request_port)(struct uart_port *);
        void            (*config_port)(struct uart_port *, int);
        int             (*verify_port)(struct uart_port *, struct serial_struct *);
        int             (*ioctl)(struct uart_port *, unsigned int, unsigned long);
};

分析:

    serial_core.c 中定义 tty_operations 的实例,包含 uart_open()、uart_close()、uart_write()、uart_send_xchar()等成员函数(如代码清单 14.17)。

      static const struct tty_operations uart_ops = {
        .open = uart_open,
        .close = uart_close,
        .write = uart_write,
        .put_char = uart_put_char,
        .flush_chars = uart_flush_chars,
        .write_room = uart_write_room,
        .chars_in_buffer= uart_chars_in_buffer,
        .flush_buffer = uart_flush_buffer,
        .ioctl = uart_ioctl,
        .throttle = uart_throttle,
        .unthrottle = uart_unthrottle,
        .send_xchar = uart_send_xchar,
        .set_termios = uart_set_termios,
        .stop = uart_stop,
        .start = uart_start,
        .hangup = uart_hangup,
        .break_ctl = uart_break_ctl,
        .wait_until_sent= uart_wait_until_sent,
        #ifdef CONFIG_PROC_FS
        .read_proc = uart_read_proc,
        #endif
        .tiocmget = uart_tiocmget,
        .tiocmset = uart_tiocmset,
};

        这些函数借助 uart_ops 结构体中的成员函数来完成具体的操作,代码清单14.18所示为tty_operations的uart_send_xchar()成员函数利用uart_ops中 start_tx()、send_xchar()成员函数的例子。

    代码清单 14.18 串口核心层的 tty_operations 与 uart_ops 关系

static void uart_send_xchar(struct tty_struct *tty, char ch)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port = state->port;
unsigned long flags;

        /* 如果 uart_ops 中实现send_xchar 成员函数 */

if (port->ops->send_xchar)
port->ops->send_xchar(port, ch);
else {    /* uart_ops 中未实现 send_xchar 成员函数 */
port->x_char = ch;
if (ch) {
spin_lock_irqsave(&port->lock, flags);
port->ops->start_tx(port);/* 发送 xchar */
spin_unlock_irqrestore(&port->lock, flags);
}
}
}

总结:

在使用串口核心层这个通用串口 tty 驱动层的接口后,一个串口驱动主要完成的工作如下。

1、定义 uart_driver、uart_ops、uart_port 等结构体的实例,在适当的地方根据具体硬件和驱动的情况初始化,具体设备 xxx 的驱动可以将这些结构套在新定义的 xxx_uart_driver、xxx_uart_ops、xxx_uart_port 之内。

2、在模块初始化时调用 uart_register_driver()和 uart_add_one_port()以注册 UART 驱动并添加端口,在模块卸载时调用 uart_unregister_driver()和 uart_remove_one_port()以注销 UART 驱动并移除端口。

3、根据具体硬件的 datasheet 实现 uart_ops 中的成员函数,这些函数的实现成为 UART 驱动的主体工作。


<think>首先,用户的问题是关于OpenCV项目中使用UART通信的串口设备文件。具体来说,用户想列出与串口通信相关的设备文件,并确认/dev/ttyS0和/dev/ttyAMA*是否可用。 回顾系统级指令: - 所有行内数学表达式用$...$格式。 - 独立公式用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识,例如[^1]。 用户的问题基于之前的引用,但引用似乎与UART通信相关,特别是关于STM32、Raspberry Pi等的串口测试和配置。 关键引用: - 引用[1]:关于使用串口调试助手测试STM32的UART接口。 - 引用[2]:关于串口配置在Python中,使用uart.UART。 - 引用[3]:关于Raspberry Pi上的UART设备,如/dev/ttyS0和/dev/ttyAMA0。 - 引用[4]:关于Qt中的UART初始化代码。 - 引用[5]:关于OpenCV中的人脸检测代码,但提到摄像头,没有直接提到UART。 用户意图:列出UART设备文件,并确认在OpenCV项目中是否可用。 我的回答需要: 1. 结构清晰,逐步解决问题。 2. 真实可靠。 3. 使用中文。 4. 在适当的地方添加引用标识。 5. 生成相关问题。 回答结构: - **引言**:简要介绍UART设备文件。 - **列出常见的UART设备文件**:基于Linux系统,如/dev/ttyS*, /dev/ttyUSB*, /dev/ttyAMA*等。 - **确认/dev/ttyS0和/dev/ttyAMA*是否可用**: - 解释这些文件代表什么。 - 如何检查它们的存在和权限。 - 在OpenCV项目中使用UART的步骤(例如,通过Python库如pyserial)。 - **引用相关部分**:自然地融入引用,例如引用[3]提到Raspberry Pi上的UART。 - **生成相关问题**:在末尾添加相关问题。 生成相关问题: - 基于主题,如:如何配置UART在OpenCV中?常见的UART问题?如何测试UART通信? 确保LaTeX使用:用户问题中没有数学表达式,所以可能不需要。但如果有,要正确格式。 最终回答: - 用中文。 - 清晰步骤。 - 引用:例如,提到Raspberry Pi时引用[3]。</think>### OpenCV项目中使用UART通信的串口设备文件及可用性确认 在OpenCV项目中集成UART通信时,串口设备文件是连接硬件(如摄像头、传感器或智能小车)的关键接口。Linux系统中,UART设备文件通常位于`/dev/`目录下。以下我将逐步解释常见的设备文件列表,并指导您如何确认`/dev/ttyS0`和`/dev/ttyAMA*`的可用性。整个过程基于Linux环境(如Raspberry Pi),因为OpenCV项目常部署在此类平台上。 #### 1. **常见的UART串口设备文件** 在Linux系统中,UART设备文件根据硬件类型和驱动生成。以下是常见的类型: - **`/dev/ttyS*`**:表示主板上的串行端口(如COM端口),例如`/dev/ttyS0`对应第一个硬件串口。 - **`/dev/ttyAMA*`**:特定于ARM架构的设备(如Raspberry Pi),用于主UART或辅助UART。例如,在Raspberry Pi上,`/dev/ttyAMA0`通常用于GPIO引脚上的UART通信[^3]。 - **`/dev/ttyUSB*`**:USB转串口适配器生成的设备文件,如`/dev/ttyUSB0`。 - **`/dev/ttyACM*`**:CDC ACM设备(如某些USB设备),常见于Arduino或STM32开发板。 这些文件是字符设备,允许程序(如OpenCV脚本)通过文件操作读写数据。 在OpenCV项目中,UART常用于传输控制命令或传感器数据(如摄像头反馈)。例如,您可能用Python的`pyserial`库与这些设备文件交互,实现与智能小车的通信。 #### 2. **如何确认`/dev/ttyS0`和`/dev/ttyAMA*`的可用性** 要确认这些设备文件是否可用于OpenCV项目,需检查它们的存在、权限和功能。以下是逐步方法: **步骤1: 检查设备文件是否存在** 使用终端命令列出所有UART设备: ```bash ls /dev/tty* # 列出所有tty设备 ``` - 如果输出中包含`/dev/ttyS0`或`/dev/ttyAMA0`,表示文件存在。例如,在Raspberry Pi上,`/dev/ttyAMA0`通常对应GPIO 14/15引脚的主UART[^3]。 - 如果未找到,可能驱动未加载或硬件不支持。运行`dmesg | grep tty`查看内核日志中的UART初始化信息。 **步骤2: 验证权限和用户访问** UART设备文件默认需要root权限。使用`ls -l`检查权限: ```bash ls -l /dev/ttyS0 /dev/ttyAMA0 # 查看权限信息 ``` - 输出示例:`crw-rw---- 1 root dialout 4, 64 May 10 12:00 /dev/ttyAMA0` - 关键点: - 用户需在`dialout`组才能读写。添加当前用户到组:`sudo usermod -aG dialout $USER`(重启生效)。 - 如果权限不足,OpenCV脚本会抛出`PermissionError`。 **步骤3: 测试通信功能** 在OpenCV项目中,先通过简单测试验证UART是否工作。例如,使用Python脚本发送接收数据: ```python import serial # 尝试打开设备文件 try: ser = serial.Serial('/dev/ttyAMA0', baudrate=115200, timeout=1) # 引用[2]中的配置示例 ser.write(b'Test data') # 发送测试数据 response = ser.readline() print(f"Received: {response}") ser.close() except Exception as e: print(f"Error: {e} # 检查设备是否被占用或配置错误") ``` - **在OpenCV集成中**:将此代码嵌入您的OpenCV循环中。例如,在摄像头帧处理时发送UART命令: ```python import cv2 import serial cap = cv2.VideoCapture(0) # 初始化摄像头,引用[5] ser = serial.Serial('/dev/ttyAMA0', 115200) while True: ret, frame = cap.read() # 人脸检测等处理(引用[5]) if condition: # 例如检测到特定目标 ser.write(b'Move forward') # 通过UART控制智能小车 ``` - 如果测试成功(数据收发正常),则设备文件可用。否则,检查波特率匹配(如引用[1]中强调的波特率设置)或硬件连接。 **步骤4: 处理常见问题** - **设备冲突**:在Raspberry Pi上,`/dev/ttyAMA0`可能被系统控制台占用。编辑`/boot/config.txt`添加`enable_uart=1`并禁用console:`sudo raspi-config` > "Interface Options" > "Serial Port" > 禁用登录shell。 - **OpenCV兼容性**:OpenCV本身不直接处理UART,但可通过多线程或异步库(如`pyserial`)集成。确保Python环境安装`pyserial`:`pip install pyserial`。 - **智能小车项目**:如果用于小车控制,确认电机驱动板(如Arduino)的UART配置匹配,引用[1]中的测试方法可帮助验证。 通过以上步骤,您可以可靠地确认`/dev/ttyS0`和`/dev/ttyAMA*`在OpenCV项目中的可用性。如果测试失败,检查硬件连接或参考引用[3]的Raspberry Pi UART文档。 #### 相关问题 1. 如何在Raspberry Pi上配置UART以禁用系统控制台占用? 2. OpenCV项目中如何实现UART数据与视频流的同步处理? 3. 常见的UART通信错误有哪些及如何调试? 4. 在智能小车项目中,如何通过OpenCV和UART实现障碍物避障? [^1]: 在完成初始化配置后,使用串口调试助手测试STM32的UART接口是常见的第一步,通过观察数据是否按预期被接收和处理,可以验证通信是否成功。 [^2]: 本文只介绍简单的串口通信方式,即传输数据只使用针头帧尾和数据长度,如果你想要适用性更广的方法,即帧头、数据长度、校检和,可以阅读这位博主的文。 [^3]: 在Raspberry Pi上,选择一个UART存在于GPIO14(发送)和15(接收)上这是主 UART,这也将是可能存在 Linux 控制台的 UART。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值