如何实现UART串口在Linux环境下的编程应用?

2026-05-06 05:361阅读0评论SEO教程
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1653个文字,预计阅读时间需要7分钟。

如何实现UART串口在Linux环境下的编程应用?

UART,即通用异步收发传输器,是一种常用的串行通信接口。它广泛应用于从8位单片机到64位SoC的各种电子设备中,几乎每个设备都会提供UART接口。

UART,全称Universal Asynchronous Receiver Transmitter,通用异步收发器,俗称串口。作为最常用的通信接口之一,从8位单片机到64位SoC,一般都会提供UART接口。

UART,全称UniversalAsynchronousReceiverTransmitter,通用异步收发器,俗称串口。作为最常用的通信接口之一,从8位单片机到64位SoC,一般都会提供UART接口。

UART的常规构成及特性

芯片内部的UART模块,一般由波特率发生器、发送和接收FIFO、硬件流控、中断源等组件构成。常见特性如下:

  • 全双工通信

  • 硬件流控

  • 可编程的字长(5/6/7/8比特)

  • 可编程的停止位(1/1.5/2比特)

  • 奇偶校验

  • 可编程的FIFO中断触发水位

  • 可编程的波特率

UART硬件信号及应用

信号脚
方向
用途
TXD
输出 串行数据输出
RXD 输入
串行数据输入
CTSN 输入
流控脚,允许发送,由对端设备控制,控制己方UART是否可以发送数据。低电平时,UART可以发送数据出去,高电平时,UART停止发送数据。
RTSN 输出
流控脚,请求发送,连接到对端设备的CTSN脚上,通知对端设备是否可以发送数据。低电平时,通知对端设备可以发送,高电平时,通知对端设备停止发送。

图1带硬件流控的UART连接

图2不带硬件流控的UART连接

UART通信协议

UART通信时序图如下

图3UART时序

数据线的空闲电平为逻辑“1”,要传输数据时:

起始位:先发出一个逻辑”0”的信号,表示传输数据的开始。

数据位:实际要传输的数据,数据位数可以是5、6、7、8,数据是从最低有效位(LSB)开始。

校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。上图中没有特意标明。

停止位:数据的结束标志。可以是1/1.5/2位的空闲电平。Linux串口设备编程接口并不支持设置1.5位。

UART通信是异步的,并没有单独的时钟来做同步,通信双方需要约定好相同的波特率。UART中的波特率可以认为是比特率,即每秒传输的位数。一般波特率有9600,115200,460800等选项。

Linux中的UART驱动

LinuxUART驱动框架如下图所示,UART在用户空间会生成名为/dev/ttyS*的设备(ttyS名称是驱动给出的,可能因驱动而异),应用程序通过读写设备就可以进行UART通信。Linux内核实现了tty层和serialcore,serialcore会调用tty层的接口,注册ttydriver,同时提供了底层uart的抽象:

  • 定义structuart_driver、structuart_port、structuart_ops等结构来描述底层uart驱动;

  • 提供相应接口uart_register_driver、uart_add_one_port等。

图4UART驱动框架

重点看一下structuart_ops定义,如下图所示,定义了UART硬件能完成的操作。

UART控制器驱动会定义uart_ops结构并实现对应的函数功能,当然并不是所有函数都需要实现,最为关键的几个函数如下表所示。

函数
意义 tx_empty
查询TXFIFO是否为空,为空则返回1,否则返回0
stop_tx
停止发送
start_tx
启动发送
stop_rx
停止接收
startup
开启UART
shutdown
关闭UART
set_termios
设置UART属性,比如波特率、数据位数、停止位数、奇偶校验、流控等。

structuart_driver指代一个UART驱动,structuart_port指代一个具体UART端口,它们与structuart_ops的关系大致如下图。

如何实现UART串口在Linux环境下的编程应用?

下面以某厂商的UART控制器驱动为例,看看驱动实现的整体流程。

UART控制器驱动,以platform_driver的形式呈现。

关键的uart_ops结构定义如下,uart_ops会关联到一个或多个uart_port上。

UART控制器驱动的probe函数,完成初始化uart_port,并添加到serialcore中。

Linux上串口的常规操作工具

Linux上,除了一些串口工具比如minicom,cutecom可以操作串口外,也可以用如下命令行工具进行基本的操作。

目的 操作方法(以/dev/ttyS0为例)
查询串口
stty-F/dev/ttyS0
设置串口

stty-F/dev/ttyS0speed115200cs8-parenb-cstopb

115200波特率8数据位1停止位无校验

读取数据
cat/dev/ttyS0
发送数据
echo"testdata">/dev/ttyS0

用户空间的串口编程

打开/读/写串口,与普通字符设备一样,open/read/write系统调用,不再赘述。如何设置串口属性,Linux提供了专门的API。

struct termios options;//获取参数tcgetattr(fd, &options);//波特率cfsetispeed(&options, B115200);cfsetospeed(&options, B115200);//8位数据位options.c_cflag &= ~CSIZE;options.c_cflag |= CS8;//无校验options.c_cflag &= ~PARENB;//1位停止位options.c_cflag &= ~CSTOPB;//设置参数tcsetattr(fd,TCANOW,&options);

内核空间的UART外设编程?

从以上介绍可以看到,UART最终是以用户空间的tty设备来呈现,应用程序可以操作tty设备完成UART通信。但是如果需要在内核中调用UART驱动呢,有没有相关接口?

假设有一个UART接口的按键扩展芯片,既要通过UART获取设备数据,又要将这些数据上报给input子系统。由于input子系统位于内核空间,那么通过UART获取数据的操作也应该在内核空间完成。那这个时候如何操作UART?如果最底层实现的UART操作函数有与tty和serialcore解耦,那倒是可以考虑直接调用这些接口,但现实情况是一般都没有完全解耦。如果是专门针对该应用场景重构UART底层驱动,那也是一种方法,也确实有人这样做。

其它总线驱动,比如I2C驱动,针对挂在I2C总线上的设备,有i2c_driver框架实现设备驱动,有i2c_transfer函数接口来实现I2C通信。SPI驱动也是类似,有spi_driver框架和相应的SPI传输函数。所以在内核空间编写I2C/SPI设备驱动很方便。

针对UART,如果是4.14之前的内核,那么可以通过serio驱动来实现某设备与tty设备的挂钩,进而可以调用tty层的读写函数,tty层的读写函数最后会调用到UART底层驱动。

serio是个抽象的总线,并不专指UART,而是SerialIO的统称。serio驱动代码用structserio_bus表示serio总线,用structserio表示serio控制器,用structserio_drvier表示serio设备驱动。详情可参考代码:

drivers/input/serio/*drivers/input/touchscreen/touchit213.c

如果是4.14版本以后的内核,已经新增了seraildevbus,并提供了相应的设备驱动注册函数:

serdev_device_driver_register(structserdev_device_driver*,structmodule*);

使用方法可参考蓝牙驱动:

drivers/bluetooth/hci_bcm.c

------END------

本文共计1653个文字,预计阅读时间需要7分钟。

如何实现UART串口在Linux环境下的编程应用?

UART,即通用异步收发传输器,是一种常用的串行通信接口。它广泛应用于从8位单片机到64位SoC的各种电子设备中,几乎每个设备都会提供UART接口。

UART,全称Universal Asynchronous Receiver Transmitter,通用异步收发器,俗称串口。作为最常用的通信接口之一,从8位单片机到64位SoC,一般都会提供UART接口。

UART,全称UniversalAsynchronousReceiverTransmitter,通用异步收发器,俗称串口。作为最常用的通信接口之一,从8位单片机到64位SoC,一般都会提供UART接口。

UART的常规构成及特性

芯片内部的UART模块,一般由波特率发生器、发送和接收FIFO、硬件流控、中断源等组件构成。常见特性如下:

  • 全双工通信

  • 硬件流控

  • 可编程的字长(5/6/7/8比特)

  • 可编程的停止位(1/1.5/2比特)

  • 奇偶校验

  • 可编程的FIFO中断触发水位

  • 可编程的波特率

UART硬件信号及应用

信号脚
方向
用途
TXD
输出 串行数据输出
RXD 输入
串行数据输入
CTSN 输入
流控脚,允许发送,由对端设备控制,控制己方UART是否可以发送数据。低电平时,UART可以发送数据出去,高电平时,UART停止发送数据。
RTSN 输出
流控脚,请求发送,连接到对端设备的CTSN脚上,通知对端设备是否可以发送数据。低电平时,通知对端设备可以发送,高电平时,通知对端设备停止发送。

图1带硬件流控的UART连接

图2不带硬件流控的UART连接

UART通信协议

UART通信时序图如下

图3UART时序

数据线的空闲电平为逻辑“1”,要传输数据时:

起始位:先发出一个逻辑”0”的信号,表示传输数据的开始。

数据位:实际要传输的数据,数据位数可以是5、6、7、8,数据是从最低有效位(LSB)开始。

校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。上图中没有特意标明。

停止位:数据的结束标志。可以是1/1.5/2位的空闲电平。Linux串口设备编程接口并不支持设置1.5位。

UART通信是异步的,并没有单独的时钟来做同步,通信双方需要约定好相同的波特率。UART中的波特率可以认为是比特率,即每秒传输的位数。一般波特率有9600,115200,460800等选项。

Linux中的UART驱动

LinuxUART驱动框架如下图所示,UART在用户空间会生成名为/dev/ttyS*的设备(ttyS名称是驱动给出的,可能因驱动而异),应用程序通过读写设备就可以进行UART通信。Linux内核实现了tty层和serialcore,serialcore会调用tty层的接口,注册ttydriver,同时提供了底层uart的抽象:

  • 定义structuart_driver、structuart_port、structuart_ops等结构来描述底层uart驱动;

  • 提供相应接口uart_register_driver、uart_add_one_port等。

图4UART驱动框架

重点看一下structuart_ops定义,如下图所示,定义了UART硬件能完成的操作。

UART控制器驱动会定义uart_ops结构并实现对应的函数功能,当然并不是所有函数都需要实现,最为关键的几个函数如下表所示。

函数
意义 tx_empty
查询TXFIFO是否为空,为空则返回1,否则返回0
stop_tx
停止发送
start_tx
启动发送
stop_rx
停止接收
startup
开启UART
shutdown
关闭UART
set_termios
设置UART属性,比如波特率、数据位数、停止位数、奇偶校验、流控等。

structuart_driver指代一个UART驱动,structuart_port指代一个具体UART端口,它们与structuart_ops的关系大致如下图。

如何实现UART串口在Linux环境下的编程应用?

下面以某厂商的UART控制器驱动为例,看看驱动实现的整体流程。

UART控制器驱动,以platform_driver的形式呈现。

关键的uart_ops结构定义如下,uart_ops会关联到一个或多个uart_port上。

UART控制器驱动的probe函数,完成初始化uart_port,并添加到serialcore中。

Linux上串口的常规操作工具

Linux上,除了一些串口工具比如minicom,cutecom可以操作串口外,也可以用如下命令行工具进行基本的操作。

目的 操作方法(以/dev/ttyS0为例)
查询串口
stty-F/dev/ttyS0
设置串口

stty-F/dev/ttyS0speed115200cs8-parenb-cstopb

115200波特率8数据位1停止位无校验

读取数据
cat/dev/ttyS0
发送数据
echo"testdata">/dev/ttyS0

用户空间的串口编程

打开/读/写串口,与普通字符设备一样,open/read/write系统调用,不再赘述。如何设置串口属性,Linux提供了专门的API。

struct termios options;//获取参数tcgetattr(fd, &options);//波特率cfsetispeed(&options, B115200);cfsetospeed(&options, B115200);//8位数据位options.c_cflag &= ~CSIZE;options.c_cflag |= CS8;//无校验options.c_cflag &= ~PARENB;//1位停止位options.c_cflag &= ~CSTOPB;//设置参数tcsetattr(fd,TCANOW,&options);

内核空间的UART外设编程?

从以上介绍可以看到,UART最终是以用户空间的tty设备来呈现,应用程序可以操作tty设备完成UART通信。但是如果需要在内核中调用UART驱动呢,有没有相关接口?

假设有一个UART接口的按键扩展芯片,既要通过UART获取设备数据,又要将这些数据上报给input子系统。由于input子系统位于内核空间,那么通过UART获取数据的操作也应该在内核空间完成。那这个时候如何操作UART?如果最底层实现的UART操作函数有与tty和serialcore解耦,那倒是可以考虑直接调用这些接口,但现实情况是一般都没有完全解耦。如果是专门针对该应用场景重构UART底层驱动,那也是一种方法,也确实有人这样做。

其它总线驱动,比如I2C驱动,针对挂在I2C总线上的设备,有i2c_driver框架实现设备驱动,有i2c_transfer函数接口来实现I2C通信。SPI驱动也是类似,有spi_driver框架和相应的SPI传输函数。所以在内核空间编写I2C/SPI设备驱动很方便。

针对UART,如果是4.14之前的内核,那么可以通过serio驱动来实现某设备与tty设备的挂钩,进而可以调用tty层的读写函数,tty层的读写函数最后会调用到UART底层驱动。

serio是个抽象的总线,并不专指UART,而是SerialIO的统称。serio驱动代码用structserio_bus表示serio总线,用structserio表示serio控制器,用structserio_drvier表示serio设备驱动。详情可参考代码:

drivers/input/serio/*drivers/input/touchscreen/touchit213.c

如果是4.14版本以后的内核,已经新增了seraildevbus,并提供了相应的设备驱动注册函数:

serdev_device_driver_register(structserdev_device_driver*,structmodule*);

使用方法可参考蓝牙驱动:

drivers/bluetooth/hci_bcm.c

------END------