Linux-串口驱动实验

串口是很常用的一个外设,在 Linux 下通常通过串口和其他设备或传感器进行通信,根据电平的不同,串口分为 TTL RS232。不管是什么样的接口电平,其驱动程序都是一样的,通过外接 RS485 这样的芯片就可以将串口转换为 RS485 信号,正点原子的 I.MX6U-ALPHA 开发板就是这么做的。对于正点原子的 I.MX6U-ALPHA 开发板而言, RS232RS485 以及 GPS 模块接口通通连接到了 I.MX6U UART3 接口上,因此这些外设最终都归结为 UART3 的串口驱动。本章我们就来学习一下如何驱动 I.MX6U-ALPHA 开发板上的 UART3 串口


Linux UART 驱动框架

1uart_driver 注册与注销

I2CSPI 一样,Linux 也提供了串口驱动框架,我们只需要按照相应的串口框架编写驱动程序即可。串口驱动没有什么主机端和设备端之分,就只有一个串口驱动,而且这个驱动也已经由 NXP 官方已经编写好了,我们真正要做的就是在设备树中添加所要使用的串口节点信息。当系统启动以后串口驱动和设备匹配成功,相应的串口就会被驱动起来,生成/dev/ttymxcX(X=0….n)文件。

虽然串口驱动不需要我们去写,但是串口驱动框架我们还是需要了解的,uart_driver 结构体表示 UART 驱动,uart_driver 定义在 include/linux/serial_core.h 文件中,内容如下:

每个串口驱动都需要定义一个 uart_driver,加载驱动的时候通过 uart_register_driver 函数向系统注册这个 uart_driver,此函数原型如下:

int uart_register_driver(struct uart_driver *drv)

函数参数和返回值含义如下:

drv要注册的 uart_driver

返回值:0,成功;负值,失败。

注销驱动的时候也需要注销掉前面注册的 uart_driver,需要用到 uart_unregister_driver 函数,

函数原型如下:

void uart_unregister_driver(struct uart_driver *drv)

函数参数和返回值含义如下:

drv要注销的 uart_driver

返回值:无。

2uart_port 的添加与移除

uart_port 表示一个具体的 portuart_port 定义在 include/linux/serial_core.h 文件,内容如下 (有省略)

uart_port 中最主要的就是第 235 行的 opsops 包含了串口的具体驱动函数,这个我们稍后再看。每个 UART 都有一个 uart_port,那么 uart_port 是怎么和 uart_driver 结合起来的呢?这里要用到 uart_add_one_port 函数,函数原型如下:

int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)

函数参数和返回值含义如下:

drv:此 port 对应的 uart_driver

uport要添加到 uart_driver 中的 port

返回值:0,成功;负值,失败。

卸载 UART 驱动的时候也需要将 uart_port 从相应的 uart_driver 中移除,需要用到

uart_remove_one_port 函数,函数原型如下:

int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)

函数参数和返回值含义如下:

drv:要卸载的 port 所对应的 uart_driver

uport要卸载的 uart_port

返回值:0,成功;负值,失败。

3uart_ops 实现

在上面讲解 uart_port 的时候说过,uart_port 中的 ops 成员变量很重要,因为 ops 包含了针对 UART 具体的驱动函数,Linux 系统收发数据最终调用的都是 ops 中的函数。ops uart_ops类型的结构体指针变量,uart_ops 定义在 include/linux/serial_core.h 文件中,内容如下:

UART 驱动编写人员需要实现 uart_ops,因为 uart_ops 是最底层的 UART 驱动接口,是实实在在的和 UART 寄存器打交道的。关于 uart_ops 结构体中的这些函数的具体含义请参考 Documentation/serial/driver 这个文档。

UART 驱动框架大概就是这些,接下来我们理论联系实际,看一下 NXP 官方的 UART 动文件是如何编写的。


I.MX6U UART 驱动分析

1UART platform 驱动框架

打开 imx6ull.dtsi 文件,找到 UART3 对应的子节点,子节点内容如下所示:

重点看一下第 23 行的 compatible 属性,这里一共有三个值:“fsl,imx6ul-uart”、“fsl,imx6q-uart”和“fsl,imx21-uart”。在 linux 源码中搜索这三个值即可找到对应的 UART 驱动文件,此文件为 drivers/tty/serial/imx.c,在此文件中可以找到如下内容:

可以看出 I.MX6U UART 本质上是一个 platform 驱动,第 267~280 行,imx_uart_devtype为传统匹配表。

283~288 行,设备树所使用的匹配表,第 284 行的 compatible 属性值为“fsl,imx6q-uart”。

2071~2082 行,platform 驱动框架结构体 serial_imx_driver

2084~2096 行,驱动入口函数,第 2086 行调用 uart_register_driver 函数向 Linux 内核注册 uart_driver,在这里就是 imx_reg

2098~2102 行,驱动出口函数,第 2101 行调用 uart_unregister_driver 函数注销掉前面注册的 uart_driver,也就是 imx_reg

2uart_driver 初始化

imx_serial_init 函数中向 Linux 内核注册了 imx_regimx_reg 就是 uart_driver 类型的结构体变量,imx_reg 定义如下:

3uart_port 初始化与添加

UART 设备和驱动匹配成功以后 serial_imx_probe 函数就会执行,此函数的重点工作就是初始化 uart_port,然后将其添加到对应的 uart_driver 中。在看 serial_imx_probe 函数之前先来看一下 imx_port 结构体,imx_port NXP I.MX 系列 SOC 定义的一个设备结构体,此结构体内部就包含了 uart_port 成员变量,imx_port 结构体内容如下所示(有缩减)

217 行,uart_port 成员变量 port

接下来看一下 serial_imx_probe 函数,函数内容如下:

1971 行,定义一个 imx_port 类型的结构体指针变量 sport

1977 行,为 sport 申请内存。

1987~1988 行,从设备树中获取 I.MX 系列 SOC UART 外设寄存器首地址,对于I.MX6ULL 的 UART3 来说就是 0X021EC000。得到寄存器首地址以后对其进行内存映射,得到对应的虚拟地址。

1992~1994 行,获取中断信息。

1996~2034 行,初始化 sport,我们重点关注的就是第 2003 行初始化 sport port 成员变量,也就是设置 uart_ops imx_popsimx_pops 就是 I.MX6ULL 最底层的驱动函数集合,稍后再来看。

2040~2055 行,申请中断。

2061 行,使用 uart_add_one_port uart_driver 添加 uart_port,在这里就是向 imx_reg 添加 sport->port

4imx_pops 结构体变量

imx_pops 就是 uart_ops 类型的结构体变量,保存了 I.MX6ULL 串口最底层的操作函数,imx_pops 定义如下:

imx_pops 中的函数基本都是和 I.MX6ULL UART 寄存器打交道的,这里就不去详细的分析了。简单的了解了 I.MX6U UART 驱动以后我们再来学习一下,如何驱动正点原子I.MX6U-ALPHA 开发板上的 UART3 接口。


RS232 驱动编写

前面我们已经说过了,I.MX6U UART 驱动 NXP 已经编写好了,所以不需要我们编写。

我们要做的就是在设备树中添加 UART3 对应的设备节点即可。打开 imx6ull-alientek-emmc.dts文件,在此文件中只有 UART1 对应的 uart1 节点,并没有 UART3 对应的节点,因此我们可以参考 uart1 节点创建 uart3 节点。

1UART3 IO 节点创建

UART3 用到了UART3_TXD UART3_RXD 这两个IO,因此要先在 iomuxc 中创建UART3对应的 pinctrl 子节点,在 iomuxc 中添加如下内容:

最后检查一下 UART3_TX 和 UART3_RX 这两个引脚有没有被用作其他功能,如果有的话要将其屏蔽掉,保证这两个 IO 只用作 UART3,切记!!!

2、添加 uart3 节点

默认情况下 imx6ull-alientek-emmc.dts 中只有 uart1 uart2 这两个节点,如图 63.4.1 所示:

uart1 是 UART1 的,在正点原子的 I.MX6U-ALPHA 开发板上没有用到 UART2,而且 UART2默认用到了 UART3 IO,因此需要将 uart2 这个节点删除掉,然后加上 UART3 对应的 uart3,uart3 节点内容如下:

完成以后重新编译设备树并使用新的设备树启动 Linux,如果设备树修改成功的话,系统启动以后就会生成一个名为“/dev/ttymxc2”的设备文件,ttymxc2 就是 UART3 对应的设备文件,应用程序可以通过访问 ttymxc2 来实现对 UART3 的操作。

补充

为什么串口的操作函数是struct uart_ops 而不是struct file_operations

串口(UART,Universal Asynchronous Receiver/Transmitter)驱动的操作函数使用 struct uart_ops 而不是 struct file_operations,主要是因为串口驱动的设计需要满足以下需求:

1. 串口驱动的分层设计

串口驱动在内核中分为两层:

上层:与用户空间交互的部分,使用 struct file_operations

下层:与硬件交互的部分,使用 struct uart_ops

这种分层设计使得串口驱动可以更好地分离硬件操作和用户接口,提高代码的模块化和可维护性。

2. struct uart_ops 的作用

struct uart_ops 是串口驱动与硬件交互的核心结构体,它定义了串口控制器硬件的操作函数。这些函数包括:

启动和停止串口传输。

配置串口参数(如波特率、数据位、停止位等)。

读取和写入数据。

处理中断。

这些操作是硬件相关的,通常由串口控制器驱动实现。

3. struct file_operations 的作用

struct file_operations 是字符设备驱动的标准接口,用于与用户空间交互。对于串口设备,struct file_operations 的实现通常由 TTY 子系统提供,而不是由具体的串口驱动实现。

TTY 子系统是 Linux 内核中用于管理终端设备的框架,它负责:

提供统一的用户接口(如 /dev/ttyS0)。

处理用户空间的系统调用(如 readwriteioctl 等)。

将用户空间的操作转换为对底层串口驱动的调用。

4. 为什么串口驱动不直接使用 struct file_operations

硬件操作与用户接口分离

  串口驱动需要处理硬件相关的操作(如配置波特率、处理中断等),这些操作与用户空间的接口无关。

  通过 struct uart_ops,串口驱动可以专注于硬件操作,而用户接口由 TTY 子系统统一管理。

复用 TTY 子系统的功能

  TTY 子系统已经实现了与用户空间交互的大部分功能(如行规范、缓冲、信号处理等)。

  如果串口驱动直接使用 struct file_operations,就需要重新实现这些功能,导致代码冗余。

支持多种设备类型

  TTY 子系统不仅支持串口设备,还支持伪终端(PTY)、控制台(Console)等设备。

  通过 struct uart_ops,串口驱动可以无缝集成到 TTY 子系统中,与其他设备共享相同的用户接口。

5. 串口驱动的工作流程

用户空间操作

  1.   用户程序通过 /dev/ttyS0 等设备文件访问串口。

  1.   系统调用(如 openreadwrite)由 TTY 子系统处理。

TTY 子系统

  1.   TTY 子系统调用底层串口驱动的 struct uart_ops 函数来完成硬件操作。

  1.   例如,当用户调用 write 时,TTY 子系统会调用 struct uart_ops 中的 start_tx 函数来启动数据传输。

串口驱动

  1.   串口驱动实现 struct uart_ops 中的函数,直接操作硬件。

  1.   例如,start_tx 函数会将数据写入串口控制器的发送寄存器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值