关注+星号公众号,不容错过精彩
作者:HywelStar
本章节主要对Linux 下tty 相关设备,以serial进行一些分析;
Kernel version:kernel 5.10
根据之前的章节对TTY的整个框架有一些简单了解,常说的串口驱动也一并进行简单说明,对tty调用至串口相关的整个流程进行讲解。回顾第一章节:
1. Linux 串口驱动
Serial Core 层:Serial Core 层提供了一个抽象层,用于表示和处理串口设备。它定义了一组通用的接口和数据结构,使得不同类型的串口设备能够在同一接口上进行交互。
Uart driver 层:UART Driver Layer 提供了一个硬件抽象层,允许不同型号和厂商的 UART 控制器以统一的方式与 Linux 内核交互。这种抽象层使得 Linux 内核能够适应各种不同的硬件实现。
1.1 Uart 驱动注册流程
串口驱动的注册主要包含:UART驱动注册,platform 平台注册
UART驱动注册:
uart_register_driver(&imx_uart_uart_driver);
注册流程图
从注册流程来看,最终会注册到一个TTY设备,将会在文件系统产生一个tty 设备,这样可以提供给应用程进行调用控制UART.
主要代码接口uart_register_driver
关于代码调用相关:
这里引用TTY 驱动设备创建过程:
platform 平台注册:
platform_driver_register(&imx_uart_platform_driver);
这一步注册了一个平台驱动。imx_uart_platform_driver
是一个 platform_driver
结构体的实例,它定义了平台驱动的属性和操作函数,包括匹配、探测、移除等操作。平台驱动负责与具体的硬件平台进行交互。
主要用于总线匹配:
通过 platform_driver_register
将 platform_driver
结构体注册到内核,内核因此知道有一个新的平台驱动可以处理特定的平台设备。
1.2 uart 操作函数
串口驱动的 uart_ops
结构体包含了uart
的操作函数。在不同平台SoC需要实现该操作函数。
static const struct uart_ops imx_uart_pops = {
.tx_empty = imx_uart_tx_empty, // 检查发送缓冲区是否为空
.set_mctrl = imx_uart_set_mctrl, // 设置调制解调器控制信号
.get_mctrl = imx_uart_get_mctrl, // 获取调制解调器控制信号
.stop_tx = imx_uart_stop_tx, // 停止发送数据
.start_tx = imx_uart_start_tx, // 开始发送数据
.stop_rx = imx_uart_stop_rx, // 停止接收数据
.enable_ms = imx_uart_enable_ms, // 启用调制解调器状态中断
.break_ctl = imx_uart_break_ctl, // 控制串口的断点信号
.startup = imx_uart_startup, // 启动串口
.shutdown = imx_uart_shutdown, // 关闭串口
.flush_buffer = imx_uart_flush_buffer, // 刷新发送和接收缓冲区
.set_termios = imx_uart_set_termios, // 设置串口的参数(波特率、数据位、停止位等)
.type = imx_uart_type, // 获取串口类型
.config_port = imx_uart_config_port, // 配置串口端口
.verify_port = imx_uart_verify_port, // 验证串口端口
#if defined(CONFIG_CONSOLE_POLL)
.poll_init = imx_uart_poll_init, // 初始化轮询模式
.poll_get_char = imx_uart_poll_get_char, // 在轮询模式下获取字符
.poll_put_char = imx_uart_poll_put_char, // 在轮询模式下发送字符
#endif
};
假如在RK3588
平台,同样需要实现uart_ops
的操作函数。
1.3 line discipline
在架构图中可以发现有一个line discipline
,这个作用是什么?
line discipline
是线路规划意思,是TTY子系统中的一个比较重要概念,它在字符设备和用户空间之间扮演了一个中间层的角色,处理从用户空间传入的数据和从硬件传出的数据。它的主要作用是提供一个可插拔的处理机制,使得不同的协议和数据处理方式可以方便地集成到串口驱动中。
line discipline
代码中结构体 tty_ldisc_ops
代表不同的线路规程(line discipline),每种线路规程实现了一种特定的数据处理方式或协议。
比如:
N_TTY
:默认线路规划,用于标准的字符终端输入输出处理。它处理基本的控制字符(如回车、删除等),并提供行缓冲和行编辑功能。
N_PPP
:用于点对点协议(PPP)处理,适用于通过串口进行的网络连接。它负责处理 PPP 的帧封装和解封装。
N_SLIP
:用于串行线路互联网协议(SLIP)处理。SLIP 是一种简单的封装协议,用于在串行接口上传输 IP 数据包。
N_R3964
:用于西门子的 R3964 协议,通常用于工业控制系统。它实现了西门子硬件设备的通信协议。
最常用的就是n_tty
,源码路径:drivers/tty/n_tty.c
static struct tty_ldisc_ops n_tty_ops = {
.magic = TTY_LDISC_MAGIC,
.name = "n_tty",
.open = n_tty_open,
.close = n_tty_close,
.flush_buffer = n_tty_flush_buffer,
.read = n_tty_read,
.write = n_tty_write,
.ioctl = n_tty_ioctl,
.set_termios = n_tty_set_termios,
.poll = n_tty_poll,
.receive_buf = n_tty_receive_buf,
.write_wakeup = n_tty_write_wakeup,
.receive_buf2 = n_tty_receive_buf2,
};
2. Linux tty应用层使用
IIC
,SPI
具体设备驱动,关于具体设备一般都在应用层实现。对于 应用层中只有简单的一些操作。2.1 UART 操作步骤
打开串口设备
配置串口参数
读写数据
关闭串口设备
这几个步骤 需要注意在配置串口举例子:
#include <termios.h>
struct termios options;
tcgetattr(fd, &options); // 获取当前串口配置
// 设置波特率
cfsetispeed(&options, B9600);
cfsetospeed(&options, B9600);
// 设置控制模式
options.c_cflag |= (CLOCAL | CREAD); // 允许接收数据
options.c_cflag &= ~PARENB; // 无校验
options.c_cflag &= ~CSTOPB; // 一位停止位
options.c_cflag &= ~CSIZE; // 清除数据位掩码
options.c_cflag |= CS8; // 数据位 8
// 设置输入模式
options.c_iflag &= ~(IXON | IXOFF | IXANY); // 关闭软件流控制
// 设置输出模式
options.c_oflag &= ~OPOST; // 原始输出模式
// 设置本地模式
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 原始输入模式
// 设置新的串口配置
tcsetattr(fd, TCSANOW, &options);
关于读写操作普通的write
read
等。
2.2 UART 使用注意要点
使用串口通信需要注意的是:
波特率的问题:需要与通信接口的波特率一直既可。
阻塞和非阻塞模式:在阻塞模式下,读写操作会一直等待直到完成;在非阻塞模式下,读写操作会立即返回,应用程序需要使用轮询或其他方法来检查是否有数据可用或是否已经完成写入。
串口设备文件权限:确保应用程序对串口设备文件有足够的访问权限,否则可能无法打开串口。