串口应用编程
串口应用编程介绍
介绍
-
串口定义:串行接口,数据按顺序传输
-
串口特点:通信线路简单,距离远,速度较低
-
应用领域:常用工业接口
-
Linux系统中的作用
-
作为标准输入输出设备
-
系统打印信息输出
-
用户与系统交互
-
-
串口与终端:在Linux系统中,串口被视为一种终端(Terminal)设备
终端 Terminal
-
终端定义
- 处理主机输入输出的设备,实现人机交互
-
终端分类
-
本地终端(如PC机显示器键盘组合)
-
串口连接的远程终端
-
基于网络的远程终端
-
物理终端vs伪终端
-
物理终端直接关联物理设备
-
本地终端(如PC机显示器键盘组合)
-
串口连接的远程终端
-
-
伪终端不直接关联物理设备
- 基于网络的远程终端
-
-
-
Linux中终端对应的设备节点
-
Linux终端原则:一切皆文件,每个终端在/dev目录下有对应设备节点
-
本地终端设备节点
-
格式:/dev/ttyX(X为数字编号)
-
范围:/dev/tty1 ~ /dev/tty63,共63个
- 本地终端设备节点
-
特点:Linux内核初始化时生成
-
-
伪终端设备节点
-
格式:/dev/pts/X(X为数字编号)
- 伪终端设备节点
-
特点:远程登录时生成
-
-
串口终端设备节点
-
示例:/dev/ttymxcX(基于特定开发板)
-
对于 ALPHA/Mini I.MX6U 开发板来说,有两个串口
-
设备节点编号说明
-
编号与硬件支持的串口数量和注册情况有关
-
出厂系统只注册了 2 个串口外设,分别是 UART1 和 UART3,所以对应这个数字就是 0 和 2
-
-
-
-
注意:命名与硬件平台相关,但通常以"tty"开头
-
-
查看系统连接终端
-
使用who命令可查看当前连接的终端
-
可显示本地终端、串口终端和远程登录的伪终端
-
-
串口应用编程
-
串口在Linux系统中的定位
-
属于终端设备
-
在特定开发板上对应/dev/ttymxc0和/dev/ttymxc2
-
-
串口应用编程基本操作
-
使用ioctl()配置串口
-
使用read()读取数据
-
使用write()写入数据
-
就是这么简单!但是我们不这么做,Linux 为上层用户做了一层封装
-
-
Linux提供的串口编程封装
-
封装了底层ioctl()操作
-
提供了一套标准API,称为termios API
-
-
termios API特点
-
是C库函数
-
可通过man手册查看帮助信息
-
适用于所有终端设备,不仅限于串口
-
-
termios API的应用范围
-
串口设备
-
本地连接的鼠标、键盘
-
远程登录的伪终端
-
-
使用termios API的准备
- 在应用程序中包含termios.h头文件
struct termios 结构体
-
终端应用编程的两个主要方面
-
配置
-
读写
-
-
struct termios结构体的重要性
-
描述终端的配置信息
-
控制和影响终端的行为和特性
-
是终端设备应用编程的核心
-
-
struct termios结构体的组成
-
struct termios
{
tcflag_t c_iflag; /* input mode flags /
tcflag_t c_oflag; / output mode flags /
tcflag_t c_cflag; / control mode flags /
tcflag_t c_lflag; / local mode flags /
cc_t c_line; / line discipline /
cc_t c_cc[NCCS]; / control characters /
speed_t c_ispeed; / input speed /
speed_t c_ospeed; / output speed */
};-
输入模式:c_iflag
-
输入模式的作用
-
控制输入数据在传递给应用程序前的处理方式
-
处理来自串口或键盘的字符数据
-
-
输入模式的配置方法
-
通过设置struct termios结构体中的c_iflag成员
-
使用预定义的宏来设置标志
-
获取详细信息的方法
-
可以通过man手册查询各个宏的详细描述
-
使用命令"man 3 termios"查看相关信息
-
-
配置的灵活性
- 通过组合不同的宏,可以实现对输入模式的精细控制
-
-
-
配置方式的通用性
- c_oflag、c_cflag和c_lflag成员也采用类似的宏配置方式
-
-
输出模式:c_oflag
-
输出模式的定义
- 控制输出字符的处理方式
-
输出模式的作用范围
-
处理应用程序发送的字符数据
-
在数据传递到串口或屏幕之前进行处理
-
-
配置方法
-
通过设置struct termios结构体中的c_oflag成员
-
使用预定义的宏来设置标志
-
-
-
控制模式:c_cflag
-
控制终端设备的硬件特性
-
对串口设置尤为重要
-
可配置的硬件特性
-
波特率
-
数据位
-
校验位
-
停止位等
-
-
配置方法
-
通过设置struct termios结构体中的c_cflag成员
-
使用预定义的标志来配置
-
波特率设置
-
Linux系统使用CBAUD位掩码来指定波特率
-
其他系统可能使用c_ispeed和c_ospeed成员变量
-
-
波特率操作函数
-
cfgetispeed()用于获取波特率
-
cfsetispeed()用于设置波特率
-
-
不同系统可能有不同的波特率设置方法
-
-
-
本地模式:c_lflag
-
用于控制终端的本地数据处理和工作模式
-
通过设置 struct termios 结构体中 c_lflag 成员的标志对本地模式进行配置
-
-
特殊控制字符:c_cc
-
特殊控制字符的定义
-
特定的字符组合,如Ctrl+C、Ctrl+Z等
-
触发终端的特殊处理
-
-
实现机制
-
通过struct termios结构体中的c_cc数组实现
-
将特殊字符映射到对应的支持函数
-
-
主要特殊控制字符及其功能
-
a) VEOF (Ctrl+D): 文件结尾符
-
使终端驱动程序将输入行中的全部字符传递给
正在读取输入的应用程序 -
如果文件结尾符是该行的第一个字符,则用户
程序中的 read 返回 0,表示文件结束
-
-
b) VEOL (Carriage return-CR): 附加行结尾符
- 作用类似于行结束符
-
c) VEOL2 (LF): 第二行结尾符
-
d) VERASE (Backspace-BS): 删除操作符
- 使终端驱动程序删除输入行中的最后一个字符
-
e) VINTR (Ctrl+C): 中断控制字符
- 使终端驱动程序向与终端相连的进程发送
SIGINT 信号
- 使终端驱动程序向与终端相连的进程发送
-
f) VKILL (Ctrl+U): 删除行符
-
g) VMIN: 非规范模式下最少读取字符数
-
h) VQUIT (Ctrl+Z): 退出操作符
- 使终端驱动程序向与终端相连的进程发送
SIGQUIT 信号
- 使终端驱动程序向与终端相连的进程发送
-
i) VSTART (Ctrl+Q): 开始字符
- 重新启动被 STOP 暂停的输出
-
j) VSTOP (Ctrl+S): 停止字符
-
字符作用“截流”,即阻止向终端的进一步输出
-
用于支持 XON/XOFF 流控
-
-
k) VSUSP (Ctrl+Z): 挂起字符
- 使终端驱动程序向与终端相连的进程发SIGSUSP 信号,用于挂起当前应用程序
-
l) VTIME: 非规范模式下字符间超时时间
- 定读取的每个字符之间的超时时间(以
分秒为单位)TIME
- 定读取的每个字符之间的超时时间(以
-
-
特殊说明
-
VMIN和VTIME仅用于非规范模式
-
用于控制非规范模式下read()调用的行为
-
-
应用场景
-
这些特殊字符用于控制终端行为和进程通信
-
支持各种终端操作和信号发送
-
-
-
-
小结
-
主要内容
-
struct termios结构体的四个主要成员: c_iflag(输入模式) c_oflag(输出模式) c_cflag(控制模式) c_lflag(本地控制)
-
这些参数控制和影响终端的行为特性
-
-
成员变量赋值方法
-
建议不要直接初始化
- struct termios ter;
-
-
-
ter.c_iflag = IGNBRK | BRKINT | PARMRK;
- 推荐使用"按位与"、"按位或"等操作添加或清除标志
- ter.c_iflag |= (IGNBRK | BRKINT | PARMRK | ISTRIP);
- 标志的适用性
- 并非所有标志对所有终端设备都有效
- 不同终端设备的硬件特性存在差异
- 串口可以配置波特率、数据位、停止位等这些硬件参数,但是其它终端是不一定支持这些配置的,譬如本地终端键盘、显示器,这些设备它是没有这些硬件概念的
- API的通用性和局限性
- 所有终端设备使用同一套API进行编程
- 由于硬件差异,某些配置参数可能对特定设备无效
- 根据具体设备和需求选择合适的配置
终端的三种工作模式
-
三种工作模式
-
a) 规范模式(canonical mode)
-
b) 非规范模式(non-canonical mode)
-
c) 原始模式(raw mode)
-
-
规范模式特点
-
基于行处理输入
-
需要输入行结束符(回车符、EOF 等)才能读取
-
支持行编辑
-
一次read()最多读取一行
- 如果在 read()函数中被请求读取的数据字节数小于当前行可读取的字节数,则 read()函数只会读
取被请求的字节数,剩下的字节下次再被读取
- 如果在 read()函数中被请求读取的数据字节数小于当前行可读取的字节数,则 read()函数只会读
-
-
非规范模式特点
-
输入即时有效
-
不需要行结束符
-
不支持行编辑
-
通过MIN和TIME参数控制read()行为
-
MIN和TIME参数组合
-
MIN=0, TIME=0:立即返回
- 若有可读数据,则读取数据并返回被读取的字节数;否则读取不到任何数据并返回 0
-
MIN>0, TIME=0:阻塞直到满足MIN个字符
- 到有 MIN 个字符可以读取时才返回,返回值是读取的字符数量。到达文件尾时返回 0
-
MIN=0, TIME>0:有数据或超时立即返回
-
只要有数据可读或者经过 TIME 个十分之一秒的时间,read()
函数则立即返回,返回值为被读取的字节数 -
如果超时并且未读到数据,则 read()函数返回 0
-
-
MIN>0, TIME>0:满足MIN个字符或字符间超时才返回
- 在输入第一个字符后系统才会启动定时器,所
以,在这种情况下,read()函数至少读取一个字节后才返回
- 在输入第一个字符后系统才会启动定时器,所
-
-
-
-
原始模式
-
特殊的非规范模式
-
以字节为单位处理输入
-
禁用终端特殊处理
-
通过cfmakeraw()函数设置
- cfmakeraw()函数内部其实就是对 struct termios 结构体进行了如下配置:
ermios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
| INLCR | IGNCR | ICRNL | IXON);
termios_p->c_oflag &= ~OPOST;
termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
termios_p->c_cflag &= ~(CSIZE | PARENB);
termios_p->c_cflag |= CS8;
- cfmakeraw()函数内部其实就是对 struct termios 结构体进行了如下配置:
-
原始模式应用场景
-
串口作为数据传输接口时
-
与其他设备或传感器通信
-
需要直接处理原始数据,不进行ASCII转换
-
-
-
终端模式设置
-
通过struct termios结构体的c_lflag成员设置ICANON标志
-
默认为规范模式
-
打开串口设备
-
串口应用程序编写的第一步:打开串口设备,使用 open()函数打开串口的设备节点文件,得到文件描述符
- int fd;
fd = open(“/dev/ttymxc2”, O_RDWR |O_NOCTTY);
if (0 > fd) {
perror(“open error”);
return -1;
}
-
打开串口设备的方法
-
使用open()函数
-
打开串口的设备节点文件
-
获取文件描述符
-
获取终端当前的配置参数:tcgetattr()函数
-
获取终端当前配置参数的目的
-
便于之后恢复终端到原始状态
-
为安全和调试考虑
-
-
使用tcgetattr()函数获取配置参数
- #include <termios.h>
#include <unistd.h>
- #include <termios.h>
int tcgetattr(int fd, struct termios *termios_p);
- 第一个参数:串口终端设备的文件描述符fd
- 第二个参数:struct termios结构体指针,用于存储配置参数
- 函数返回值:成功返回0
。失败返回-1,并设置errno
-
函数调用前的准备
-
定义struct termios结构体变量
-
将结构体变量的指针作为第二个参数传入
-
-
使用示例
- struct termios old_cfg;
if (0 > tcgetattr(fd, &old_cfg)) {
/* 出错处理 */
do_something();
}
对串口终端进行配置
-
1)配置串口终端为原始模式
-
调用<termios.h>头文件中申明的 cfmakeraw()函数,这个函数没有返回值
-
struct termios new_cfg;
-
//内存清零 初始化结构体
memset(&new_cfg, 0x0, sizeof(struct termios));
//配置为原始模式
cfmakeraw(&new_cfg);
-
2)接收使能
-
在c_cflag成员中添加CREAD标志
-
new_cfg.c_cflag |= CREAD; //接收使能
-
-
3)设置串口的波特率
-
用户不能直接通过位掩码来操作
-
使用cfsetispeed()和cfsetospeed()函数
-
cfsetispeed(&new_cfg, B115200);
cfsetospeed(&new_cfg, B115200); -
B115200 是一个宏
-
-
一般来说,用户需将终端的输入和输出波特率设置成一样
-
或使用cfsetspeed()函数同时设置输入输出波特率
- cfsetspeed(&new_cfg, B115200);
-
这几个函数在成功时返回 0,失败时返回-1。
-
-
4)设置数据位大小
-
清除CSIZE位掩码
-
设置CS8为8位数据位
-
new_cfg.c_cflag &= ~CSIZE;
new_cfg.c_cflag |= CS8; //设置为 8 位数据位
-
-
5)设置奇偶校验位
-
奇校验:设置PARODD和PARENB标志
-
偶校验:设置PARENB标志,清除PARODD标志
-
无校验:清除PARENB标志
-
//奇校验使能
new_cfg.c_cflag |= (PARODD | PARENB);
new_cfg.c_iflag |= INPCK;
-
//偶校验使能
new_cfg.c_cflag |= PARENB;
new_cfg.c_cflag &= ~PARODD; /* 清除 PARODD 标志,配置为偶校验 */
new_cfg.c_iflag |= INPCK;
//无校验
new_cfg.c_cflag &= ~PARENB;
new_cfg.c_iflag &= ~INPCK;
-
6)设置停止位
-
一个停止位:清除CSTOPB标志
-
两个停止位:添加CSTOPB标志
-
/ 将停止位设置为一个比特
new_cfg.c_cflag &= ~CSTOPB;
-
// 将停止位设置为 2 个比特
new_cfg.c_cflag |= CSTOPB;
-
7)设置 MIN 和 TIME 的值
-
影响非规范模式下read()调用的行为
-
设置为0使read()立即返回,实现非阻塞读取
-
new_cfg.c_cc[VTIME] = 0;
new_cfg.c_cc[VMIN] = 0;
-
缓冲区的处理
-
缓冲区处理的必要性
- 使用串口前需处理缓冲区中可能存在的数据
-
相关函数
- #include <termios.h>
#include <unistd.h>
- #include <termios.h>
int tcdrain(int fd);
int tcflush(int fd, int queue_selector);
int tcflow(int fd, int action);
- tcdrain() 函数
- 调用后阻塞应用程序,直到输出缓冲区数据全部发送完毕
- tcflow() 函数
- 用于暂停或重启数据传输/接收
- 参数 action 决定具体操作(TCOOFF, TCOON, TCIOFF, TCION)
- TCOOFF:暂停数据输出(输出传输)
- TCOON:重新启动暂停的输出
- TCIOFF:发送 STOP 字符,停止终端设备向系统发送数据
- TCION:发送一个 START 字符,启动终端设备向系统发送数据
- tcflush() 函数
- 清空输入/输出缓冲区
- 参数 queue_selector 决定清空哪些缓冲区(TCIFLUSH, TCOFLUSH, TCIOFLUSH)
- TCIFLUSH:对接收到而未被读取的数据进行清空处理
- TCOFLUSH:对尚未传输成功的输出数据进行清空处理
- TCIOFLUSH:包括前两种功能,即对尚未处理的输入/输出数据进行清空处理
- 以上这三个函数,调用成功时返回 0;失败将返回-1、并且会设置 errno
-
常用处理方式
-
使用 tcdrain() 阻塞等待数据发送完毕
- tcdrain(fd);
-
使用 tcflush() 清空缓冲区
- tcflush(fd, TCIOFLUSH);
-
写入配置、使配置生效:tcsetattr()函数
-
完成struct termios结构体配置后,需将参数写入终端设备使其生效
-
tcsetattr()函数
-
用于将配置参数写入硬件设备
-
#include <termios.h>
#include <unistd.h>
-
int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);
- fd:文件描述符
- optional_actions:指定配置生效时机
- TCSANOW:配置立即生效
- TCSADRAIN:配置在所有写入 fd 的输出都传输完毕之后生效
- TCSAFLUSH:所有已接收但未读取的输入都将在配置生效之前被丢弃
- termios_p:指向struct termios对象的指针
- 调用成功时返回 0;失败将返回-1,并设置 errno
-
调用 tcsetattr()将配置参数写入设备,使其立即生效
- tcsetattr(fd, TCSANOW, &new_cfg);
写数据:read()、write()
- 所有准备工作完成之后,接着便可以读写数据了,直接调用 read()、write()函数即可
串口应用编程实战
#define _GNU_SOURCE //在源文件开头定义_GNU_SOURCE宏
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <termios.h>
typedef struct uart_hardware_cfg {
unsigned int baudrate; /* 波特率 */
unsigned char dbit; /* 数据位 */
char parity; /* 奇偶校验 */
unsigned char sbit; /* 停止位 */
} uart_cfg_t;
static struct termios old_cfg; //用于保存终端的配置参数
static int fd; //串口终端对应的文件描述符
/**
** 串口初始化操作
** 参数device表示串口终端的设备节点
**/
static int uart_init(const char *device)
{
/* 打开串口终端 */
fd = open(device, O_RDWR | O_NOCTTY);
if (0 > fd) {
fprintf(stderr, "open error: %s: %s\n", device, strerror(errno));
return -1;
}
/* 获取串口当前的配置参数 */
if (0 > tcgetattr(fd, &old_cfg)) {
fprintf(stderr, "tcgetattr error: %s\n", strerror(errno));
close(fd);
return -1;
}
return 0;
}
/**
** 串口配置
** 参数cfg指向一个uart_cfg_t结构体对象
**/
static int uart_cfg(const uart_cfg_t *cfg)
{
struct termios new_cfg = {0}; //将new_cfg对象清零
speed_t speed;
/* 设置为原始模式 */
cfmakeraw(&new_cfg);
/* 使能接收 */
new_cfg.c_cflag |= CREAD;
/* 设置波特率 */
switch (cfg->baudrate) {
case 1200: speed = B1200;
break;
case 1800: speed = B1800;
break;
case 2400: speed = B2400;
break;
case 4800: speed = B4800;
break;
case 9600: speed = B9600;
break;
case 19200: speed = B19200;
break;
case 38400: speed = B38400;
break;
case 57600: speed = B57600;
break;
case 115200: speed = B115200;
break;
case 230400: speed = B230400;
break;
case 460800: speed = B460800;
break;
case 500000: speed = B500000;
break;
default: //默认配置为115200
speed = B115200;
printf("default baud rate: 115200\n");
break;
}
if (0 > cfsetspeed(&new_cfg, speed)) {
fprintf(stderr, "cfsetspeed error: %s\n", strerror(errno));
return -1;
}
/* 设置数据位大小 */
new_cfg.c_cflag &= ~CSIZE; //将数据位相关的比特位清零
switch (cfg->dbit) {
case 5:
new_cfg.c_cflag |= CS5;
break;
case 6:
new_cfg.c_cflag |= CS6;
break;
case 7:
new_cfg.c_cflag |= CS7;
break;
case 8:
new_cfg.c_cflag |= CS8;
break;
default: //默认数据位大小为8
new_cfg.c_cflag |= CS8;
printf("default data bit size: 8\n");
break;
}
/* 设置奇偶校验 */
switch (cfg->parity) {
case 'N': //无校验
new_cfg.c_cflag &= ~PARENB;
new_cfg.c_iflag &= ~INPCK;
break;
case 'O': //奇校验
new_cfg.c_cflag |= (PARODD | PARENB);
new_cfg.c_iflag |= INPCK;
break;
case 'E': //偶校验
new_cfg.c_cflag |= PARENB;
new_cfg.c_cflag &= ~PARODD; /* 清除PARODD标志,配置为偶校验 */
new_cfg.c_iflag |= INPCK;
break;
default: //默认配置为无校验
new_cfg.c_cflag &= ~PARENB;
new_cfg.c_iflag &= ~INPCK;
printf("default parity: N\n");
break;
}
/* 设置停止位 */
switch (cfg->sbit) {
case 1: //1个停止位
new_cfg.c_cflag &= ~CSTOPB;
break;
case 2: //2个停止位
new_cfg.c_cflag |= CSTOPB;
break;
default: //默认配置为1个停止位
new_cfg.c_cflag &= ~CSTOPB;
printf("default stop bit size: 1\n");
break;
}
/* 将MIN和TIME设置为0 */
new_cfg.c_cc[VTIME] = 0;
new_cfg.c_cc[VMIN] = 0;
/* 清空缓冲区 */
if (0 > tcflush(fd, TCIOFLUSH)) {
fprintf(stderr, "tcflush error: %s\n", strerror(errno));
return -1;
}
/* 写入配置、使配置生效 */
if (0 > tcsetattr(fd, TCSANOW, &new_cfg)) {
fprintf(stderr, "tcsetattr error: %s\n", strerror(errno));
return -1;
}
/* 配置OK 退出 */
return 0;
}
/***
--dev=/dev/ttymxc2
--brate=115200
--dbit=8
--parity=N
--sbit=1
--type=read
***/
/**
** 打印帮助信息
**/
static void show_help(const char *app)
{
printf("Usage: %s [选项]\n"
"\n必选选项:\n"
" --dev=DEVICE 指定串口终端设备名称, 譬如--dev=/dev/ttymxc2\n"
" --type=TYPE 指定操作类型, 读串口还是写串口, 譬如--type=read(read表示读、write表示写、其它值无效)\n"
"\n可选选项:\n"
" --brate=SPEED 指定串口波特率, 譬如--brate=115200\n"
" --dbit=SIZE 指定串口数据位个数, 譬如--dbit=8(可取值为: 5/6/7/8)\n"
" --parity=PARITY 指定串口奇偶校验方式, 譬如--parity=N(N表示无校验、O表示奇校验、E表示偶校验)\n"
" --sbit=SIZE 指定串口停止位个数, 譬如--sbit=1(可取值为: 1/2)\n"
" --help 查看本程序使用帮助信息\n\n", app);
}
/**
** 信号处理函数,当串口有数据可读时,会跳转到该函数执行
**/
static void io_handler(int sig, siginfo_t *info, void *context)
{
unsigned char buf[10] = {0}; // 数据缓冲区
int ret;// 读取数据大小
int n; // 循环变量
if(SIGRTMIN != sig)
return;
/* 判断串口是否有数据可读 */
if (POLL_IN == info->si_code) {
ret = read(fd, buf, 8); //一次最多读8个字节数据
printf("[ ");
for (n = 0; n < ret; n++)
printf("0x%hhx ", buf[n]);
printf("]\n");
}
}
/**
** 异步I/O初始化函数
**/
static void async_io_init(void)
{
struct sigaction sigatn;
int flag; // 文件状态标志
/* 使能异步I/O */
flag = fcntl(fd, F_GETFL); //使能串口的异步I/O功能
flag |= O_ASYNC;
fcntl(fd, F_SETFL, flag);
/* 设置异步I/O的所有者 */
fcntl(fd, F_SETOWN, getpid());
/* 指定实时信号SIGRTMIN作为异步I/O通知信号 */
fcntl(fd, F_SETSIG, SIGRTMIN);
/* 为实时信号SIGRTMIN注册信号处理函数 */
sigatn.sa_sigaction = io_handler; //当串口有数据可读时,会跳转到io_handler函数
sigatn.sa_flags = SA_SIGINFO;
sigemptyset(&sigatn.sa_mask);
sigaction(SIGRTMIN, &sigatn, NULL);
}
int main(int argc, char *argv[])
{
uart_cfg_t cfg = {0}; // 串口配置结构体,初始化为0
char *device = NULL; // 串口设备名称
int rw_flag = -1; // 读写标志
unsigned char w_buf[10] = {0x11, 0x22, 0x33, 0x44,
0x55, 0x66, 0x77, 0x88}; //通过串口发送出去的数据
int n;
/* 解析出参数 */
for (n = 1; n < argc; n++) {
if (!strncmp("--dev=", argv[n], 6))
device = &argv[n][6];
else if (!strncmp("--brate=", argv[n], 8))
cfg.baudrate = atoi(&argv[n][8]);
else if (!strncmp("--dbit=", argv[n], 7))
cfg.dbit = atoi(&argv[n][7]);
else if (!strncmp("--parity=", argv[n], 9))
cfg.parity = argv[n][9];
else if (!strncmp("--sbit=", argv[n], 7))
cfg.sbit = atoi(&argv[n][7]);
else if (!strncmp("--type=", argv[n], 7)) {
if (!strcmp("read", &argv[n][7]))
rw_flag = 0; //读
else if (!strcmp("write", &argv[n][7]))
rw_flag = 1; //写
}
else if (!strcmp("--help", argv[n])) {
show_help(argv[0]); //打印帮助信息
exit(EXIT_SUCCESS);
}
}
if (NULL == device || -1 == rw_flag) {
fprintf(stderr, "Error: the device and read|write type must be set!\n");
show_help(argv[0]);
exit(EXIT_FAILURE);
}
/* 串口初始化 */
if (uart_init(device))
exit(EXIT_FAILURE);
/* 串口配置 */
if (uart_cfg(&cfg)) {
tcsetattr(fd, TCSANOW, &old_cfg); //恢复到之前的配置
close(fd);
exit(EXIT_FAILURE);
}
/* 读|写串口 */
switch (rw_flag) {
case 0: //读串口数据
async_io_init(); //我们使用异步I/O方式读取串口的数据,调用该函数去初始化串口的异步I/O
for ( ; ; )
sleep(1); //进入休眠、等待有数据可读,有数据可读之后就会跳转到io_handler()函数
break;
case 1: //向串口写入数据
for ( ; ; ) { //循环向串口写入数据
write(fd, w_buf, 8); //一次向串口写入8个字节
sleep(1); //间隔1秒钟
}
break;
}
/* 退出 */
tcsetattr(fd, TCSANOW, &old_cfg); //恢复到之前的配置
close(fd);
exit(EXIT_SUCCESS);
}
用户参数解析
-
串口设备节点、波特率、数据位、停止位、奇偶校验和读写模式
-
如果用户输入了–help参数,程序会调用show_help()函数打印使用帮助信息,并退出程序
串口初始化
-
调用uart_init()函数
-
打开指定的串口终端设备
-
获取当前的串口配置参数(保存到old_cfg中)
-
串口配置
-
调用uart_cfg()函数
-
设置为原始模式
-
使能接收功能
-
根据输入的波特率、数据位、停止位和奇偶校验进行相应的设置
-
清空串口缓冲区并将新配置应用到串口设备
-
操作模式选择
-
根据输入的–type参数决定进行读取还是写入操作
-
如果是read,程序将进入异步读取模式
-
如果是write,程序将循环写入数据到串口
-
异步I/O初始化(仅用于读取)
-
调用async_io_init()为异步I/O设置信号处理
-
使能异步I/O
-
设置异步I/O的所有者
-
定实时信号SIGRTMIN作为异步I/O通知信号
-
注册io_handler()函数,当有数据可读时会被调用
- 程序会读取串口中的数据并打印。一次最多读取8个字节,超出的数据将在后续的读取中处理
-
写入操作(仅用于写入模式)
- 在写入模式下,程序循环向串口写入固定的数据(w_buf数组内容),并每隔1秒进行一次写入
退出
- 在程序结束前,恢复串口到原来的配置(old_cfg),关闭串口设备,并正常退出