串口应用编程-I.MX6U嵌入式Linux C应用编程学习笔记基于正点原子阿尔法开发板
串口应用编程

串口应用编程介绍
介绍
-
串口定义:串行接口,数据按顺序传输
-
串口特点:通信线路简单,距离远,速度较低
-
应用领域:常用工业接口
-
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: //默认配置为115200speed = 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: //默认数据位大小为8new_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}; // 串口配置结构体,初始化为0char *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/Ofor ( ; ; )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),关闭串口设备,并正常退出
相关文章:
串口应用编程-I.MX6U嵌入式Linux C应用编程学习笔记基于正点原子阿尔法开发板
串口应用编程 串口应用编程介绍 介绍 串口定义:串行接口,数据按顺序传输 串口特点:通信线路简单,距离远,速度较低 应用领域:常用工业接口 Linux系统中的作用 作为标准输入输出设备 系统打印信息输出 用户与系统交互 串口与终端:在Linux系统中,串口被视为一种终端&#…...
Canvas实现截图
<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>canvas实现截图功能</title><style>.ca…...
Python高性能计算:进程、线程、协程、并发、并行、同步、异步
这里写目录标题 进程、线程、协程并发、并行同步、异步I/O密集型任务、CPU密集型任务 进程、线程、协程 进程、线程和协程是计算机程序执行的三种不同方式,它们在资源管理、执行模型和调度机制上有显著的区别。以下是对它们的详细解释和比较: 进程&…...
kafka基本操作
Kafka详解 一、Kafka概述 Kafka是一个开源的分布式事件流平台,它主要用于高性能数据管道、流分析、数据集成和关键任务应用。Kafka最初被设计为一个分布式的基于发布/订阅模式的消息队列,但随着时间的推移,它已发展成为一个功能强大的流处理…...
JavaFX布局-Accordion
JavaFX布局-Accordion 一个可扩展的面板,包括标题、内容与TitledPane配合一起使用 public static Accordion demo1() {// 创建AccordionAccordion accordion new Accordion();// 内边距accordion.setPadding(new Insets(10, 10, 10, 10));for (int i 1; i < 1…...
【轨物方案】评估光伏组件发电性能一致性方案
光伏电站建设后运行周期长达二十多年,对于电站运营者来说,基础设施的稳定、安全、高效的运行是至关重要的。然而从近些年光伏的发展过程中看到,在电站规划到后期运维整个过程中可能存在着诸多问题,如设备选型不当、施工建设质量差…...
安全基础学习-keil调试汇编代码
初始目的是为了通过汇编编写CRC功能。 但是基础为0,所以目前从搭建工程开始记录。 大佬绕路。 (一)创建项目 1. 新建项目 打开 Keil uVision。选择 Project -> New uVision Project 创建一个新项目。选择你的目标设备(如 ARM Cortex-M 系列处理器),我这里一开始选择…...
Unity复制对象时让私有变量也被复制的简单方法
Unity复制对象时,如果一个变量为公共变量(public),那么这个变量的值会被复制到新的对象中去,但是如果一个变量是私有变量(private),默认是不会被复制的,如果希望被复制&a…...
Flink 实时数仓(二)【DIM 层搭建】
1、DIM 层搭建 1.1、设计要点 DIM层设计要点: DIM层存的是维度表(环境信息,比如人、场、货等)DIM层的数据存储在 HBase 表中DIM层表名的命名规范为dim_表名 DIM 层表是用于维度关联的,要通过主键(维度外…...
知识图谱开启了一个可以理解的人工智能未来
概述 本文是对利用知识图谱(KG)的综合人工智能(CAI)的全面调查研究,其中 CAI 被定义为可解释人工智能(XAI)和可解释机器学习(IML)的超集。 首先,本文澄清了…...
借助Aspose.html控件, 将SVG 转PNG 的 C# 图像处理库
Aspose.HTML for .NET 不仅提供超文本标记语言 ( HTML ) 文件处理,还提供流行图像文件格式之间的转换。您可以利用丰富的渲染和转换功能将SVG文件渲染为PNG、JPG或其他广泛使用的文件格式。但是,我们将使用此C# 图像处理库以编程方式在 C# 中将 SVG 转换…...
vs-2015安装教程
双击安装包 2-如图先选自定义,然后选安装路径(英文路径) 3-安装选项一个就够了,如图 4-点击下一步,之后如下图 5-点击安装 启动,如图则恭喜你成功安装...
Stable Diffusion绘画 | 文生图设置详解—随机种子数(Seed)
随机种子数(Seed) Midjourney 也有同样的概念,通过 --seed 种子数值 来使用。 每次操作「生成」所得到的图片,都会随机分配一个 seed值,数值不同,生成的画面就会不同。 默认值为 -1:每次随机分…...
56、php实现N的阶乘末尾有多个0
题目: php实现N的阶乘末尾有多个0 描述: 阶乘 N! 123*…N; 比如 5! 12345 120 末端有1个0 解题思路: N! K*(10^M) N的阶乘为K和10的M次方的乘积,那么N!末尾就有M个0。如果将N的阶乘分解后,那么N的阶乘可以分解为&…...
混合域注意力机制(空间+通道)
在计算机视觉任务中,空间域注意力通常关注图像中不同位置的重要性,例如突出图像中的关键对象或区域。而通道域注意力则侧重于不同通道(特征图)的重要性,决定哪些特征对于任务更具判别力。混合域注意力机制结合了空间域…...
springboot长春旅游安全地图平台-计算机毕业设计源码90075
摘 要 本文详细阐述了基于微信小程序前端和Spring Boot后端框架的长春旅游安全地图平台的设计思路与实现过程。该平台旨在为长春游客提供安全、便捷的旅游服务,同时为旅游管理部门提供高效的信息管理和应急响应机制。 在平台设计上,我们充分考虑了用户体…...
apex正则表达式匹配富文本字段内容,如何只匹配文本而忽略富文本符号
在Apex中处理富文本字段时,如果你只想匹配其中的纯文本而忽略富文本符号,可以使用正则表达式来去除HTML标签,然后再进行文本匹配。以下是一个示例代码,展示了如何实现这一点: public class RichTextHandler {// Funct…...
空气净化器对去除宠物毛有效吗?小型猫毛空气净化器使用感受
作为一个养猫多年的猫奴,家里有两只可爱的小猫咪:小白和小花。虽然相处起来很开心,但也给生活带来了一些小麻烦。谁懂啊,我真的受够了,每天都在粘毛。猫窝的猫毛一周不清理就要堆成山,空气中也全是浮毛&…...
vue的nextTick是下一次事件循环吗
如题,nextTick的回调是在下一次事件循环被执行的吗? 是不是下一次事件循环取决于nextTick的实现,如果是用的微任务,那么就是本次事件循环;否则如果用的是宏任务,那么就是下一次事件循环。 我们看下Vue3中…...
5.4.软件工程-系统设计
考试占比不高 概述 系统设计的主要目的就是为系统制定蓝图,在各种技术和实施方法中权衡利弊,精心设计,合理地使用各种资源,最终勾画出新系统的详细设计方案。系统设计的主要内容包括新系统总体结构设计、代码设计、输出设计、输…...
大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...
【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...
【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...
GitFlow 工作模式(详解)
今天再学项目的过程中遇到使用gitflow模式管理代码,因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存,无论是github还是gittee,都是一种基于git去保存代码的形式,这样保存代码…...
【C++特殊工具与技术】优化内存分配(一):C++中的内存分配
目录 一、C 内存的基本概念 1.1 内存的物理与逻辑结构 1.2 C 程序的内存区域划分 二、栈内存分配 2.1 栈内存的特点 2.2 栈内存分配示例 三、堆内存分配 3.1 new和delete操作符 4.2 内存泄漏与悬空指针问题 4.3 new和delete的重载 四、智能指针…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...
[ACTF2020 新生赛]Include 1(php://filter伪协议)
题目 做法 启动靶机,点进去 点进去 查看URL,有 ?fileflag.php说明存在文件包含,原理是php://filter 协议 当它与包含函数结合时,php://filter流会被当作php文件执行。 用php://filter加编码,能让PHP把文件内容…...
