struct termios
struct termios
是用于控制终端 I/O(输入输出)行为的一个结构体,用于设置和获取终端设备的参数,例如输入模式、输出模式、控制模式、局部模式、特殊字符等。
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标志常量
标志常量 | 描述 |
---|---|
IGNBRK | 忽略输入上的BREAK条件 |
BRKINT | BREAK引起输入和输出队列刷新,并可能发送SIGINT信号 |
IGNPAR | 忽略帧错误和奇偶校验错误 |
PARMRK | 标记有奇偶校验或帧错误的输入字节 |
INPCK | 启用输入奇偶校验检查 |
ISTRIP | 去掉第8位 |
INLCR | 输入时将NL转换为CR |
IGNCR | 忽略输入的回车符 |
ICRNL | 输入时将回车符转换为换行符 |
IUCLC | 输入时将大写字符映射为小写字符(非POSIX) |
IXON | 启用输出上的XON/XOFF流控制 |
IXANY | 任何字符都能重启停止的输出 |
IXOFF | 启用输入上的XON/XOFF流控制 |
IMAXBEL | 输入队列满时响铃 |
IUTF8 | 输入为UTF8(Linux 2.6.4起) |
c_oflag标志常量
标志常量 | 描述 |
---|---|
OPOST | 启用实现定义的输出处理 |
OLCUC | 输出时将小写字符映射为大写字符(非POSIX) |
ONLCR | 输出时将NL映射为CR-NL |
OCRNL | 输出时将CR映射为NL |
ONOCR | 在第0列不输出CR |
ONLRET | NL字符假定执行回车功能 |
OFILL | 发送填充字符以延迟,而不是使用定时延迟 |
OFDEL | 填充字符为ASCII DEL |
NLDLY | 换行延迟掩码 |
CRDLY | 回车延迟掩码 |
TABDLY | 水平制表符延迟掩码 |
BSDLY | 退格延迟掩码 |
VTDLY | 垂直制表符延迟掩码 |
FFDLY | 换页延迟掩码 |
c_cflag标志常量
标志常量 | 描述 |
---|---|
CBAUD | 波特率掩码(非POSIX) |
CBAUDEX | 额外波特率掩码(非POSIX) |
CSIZE | 字符大小掩码 |
CSTOPB | 设置两个停止位 |
CREAD | 启用接收器 |
PARENB | 启用输出奇偶校验生成和输入奇偶校验检查 |
PARODD | 设置奇校验,否则为偶校验 |
HUPCL | 最后一个进程关闭设备后降低调制解调器控制线 |
CLOCAL | 忽略调制解调器控制线 |
LOBLK | 阻止非当前shell层的输出(非POSIX) |
CIBAUD | 输入速度掩码(非POSIX) |
CMSPAR | 使用”粘性”(标记/空格)奇偶校验 |
CRTSCTS | 启用RTS/CTS(硬件)流控制 |
c_lflag标志常量
标志常量 | 描述 |
---|---|
ISIG | 接收INTR、QUIT、SUSP或DSUSP字符时生成相应信号 |
ICANON | 启用规范模式 |
XCASE | 终端为大写模式(非POSIX,Linux不支持) |
ECHO | 回显输入字符 |
ECHOE | ERASE字符擦除前一个输入字符 |
ECHOK | KILL字符擦除当前行 |
ECHONL | 即使未设置ECHO也回显NL字符 |
ECHOCTL | 回显特殊字符为^X格式 |
ECHOPRT | 擦除字符时打印它们 |
ECHOKE | KILL通过擦除每字符来回显 |
DEFECHO | 仅在进程读取时回显(Linux不实现) |
FLUSHO | 正在刷新输出(Linux不支持) |
NOFLSH | 生成信号时不刷新输入和输出队列 |
TOSTOP | 向后台进程组发送SIGTTOU信号 |
PENDIN | 重新打印输入队列中的所有字符(Linux不支持) |
IEXTEN | 启用实现定义的输入处理 |
特殊字符(c_cc数组)
标志常量 | 描述 |
---|---|
VDISCARD | 切换丢弃待处理输出 |
VDSUSP | 延迟挂起字符 |
VEOF | 文件结束字符 |
VEOL | 额外的行结束字符 |
VEOL2 | 另一个行结束字符 |
VERASE | 擦除字符 |
VINTR | 中断字符 |
VKILL | 杀死字符 |
VLNEXT | 字面下一个字符 |
VMIN | 非规范读取的最小字符数 |
VQUIT | 退出字符 |
VREPRINT | 重新打印未读字符 |
VSTART | 开始字符 |
VSTATUS | 状态字符 |
VSTOP | 停止字符 |
VSUSP | 挂起字符 |
VSWTCH | 切换字符 |
VTIME | 非规范读取的超时时间 |
VWERASE | 单词擦除字符 |
函数
tcgetattr
是一个用于获取终端属性的函数。以下是其相关手册内容的中文翻译:
termios
, tcgetattr
, tcsetattr
, tcsendbreak
, tcdrain
, tcflush
, tcflow
, cfmakeraw
, cfgetospeed
, cfgetispeed
, cfsetispeed
, cfsetospeed
, cfsetspeed
- 获取和设置终端属性、线路控制、获取和设置波特率
#include <termios.h>#include <unistd.h>
int tcgetattr(int fd, struct termios *termios_p);int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);
int tcsendbreak(int fd, int duration);int tcdrain(int fd);int tcflush(int fd, int queue_selector);int tcflow(int fd, int action);
void cfmakeraw(struct termios *termios_p);
speed_t cfgetispeed(const struct termios *termios_p);speed_t cfgetospeed(const struct termios *termios_p);
int cfsetispeed(struct termios *termios_p, speed_t speed);int cfsetospeed(struct termios *termios_p, speed_t speed);int cfsetspeed(struct termios *termios_p, speed_t speed);
tcgetattr
int tcgetattr(int fd, struct termios *termios_p);
作用
tcgetattr()
用于获取与文件描述符 fd
关联的终端参数,并将这些参数存储在 termios_p
指向的 termios
结构体中。
参数
int fd
:指定终端设备的文件描述符。struct termios *termios_p
:指向termios
结构体的指针,用于存储获取到的终端参数。
tcsetattr
int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);
作用
tcsetattr()
用于设置与终端相关的参数,这些参数从 termios_p
指向的 termios
结构体中获取。optional_actions
参数指定更改何时生效。
参数
int fd
:指定终端设备的文件描述符。int optional_actions
:指定更改生效的时间。
选项 | 说明 |
---|---|
TCSANOW | 更改立即生效。 |
TCSADRAIN | 更改在所有写入 fd 的输出传输完成后生效。当更改影响输出的参数时,应使用此选项。 |
TCSAFLUSH | 更改在所有写入 fd 的输出传输完成后生效,并且在更改生效前,所有已接收但未读取的输入将被丢弃。 |
cfsetspeed
int cfsetispeed(struct termios *termios_p, speed_t speed);int cfsetospeed(struct termios *termios_p, speed_t speed);int cfsetspeed(struct termios *termios_p, speed_t speed);
作用
cfsetspeed
函数用于设置终端的输入波特率。它修改 termios
结构中的输入速度字段,但不直接改变终端的设置。要使更改生效,必须使用 tcsetattr
函数将修改后的 termios
结构应用到终端。
参数
struct termios *termios_p
:指向termios
结构的指针,该结构包含终端的参数。speed_t speed
:指定的输入波特率。该值必须是定义在<termios.h>
中的有效波特率常量之一。 常用的波特率选项:
常量 | 波特率 |
---|---|
B4800 | 4800 |
B9600 | 9600 |
B19200 | 19200 |
B38400 | 38400 |
B57600 | 57600 |
B115200 | 115200 |
B230400 | 230400 |
B460800 | 460800 |
B500000 | 500000 |
B576000 | 576000 |
B921600 | 921600 |
B1000000 | 1000000 |
B1152000 | 1152000 |
B1500000 | 1500000 |
B2000000 | 2000000 |
返回值
- 成功时返回 0。
- 失败时返回 -1,并设置
errno
指示错误类型。
cfgetspeed
speed_t cfgetispeed(const struct termios *termios_p);speed_t cfgetospeed(const struct termios *termios_p);
作用
cfgetispeed(cfgetospeed)
函数用于获取存储在 termios
结构体中的输入/输出波特率。
参数
const struct termios *termios_p
:指向termios
结构体的指针,该结构体包含了终端的配置信息,包括波特率。
返回值
- 返回存储在
termios
结构体中的输入波特率。返回值是波特率宏常量
tcflush
int tcflush(int fd, int queue_selector);
作用
tcflush
函数用于丢弃与文件描述符 fd
相关联的终端设备中尚未传输的数据或尚未读取的数据,具体取决于 queue_selector
参数的值。
参数
int fd
:与终端设备相关联的文件描述符。int queue_selector
:指定要刷新的数据队列。
选项 | 说明 |
---|---|
TCIFLUSH | 刷新已接收但尚未读取的数据。 |
TCOFLUSH | 刷新已写入但尚未传输的数据。 |
TCIOFLUSH | 刷新已接收但尚未读取的数据,以及已写入但尚未传输的数据。 |
返回值
- 成功时返回 0。
- 失败时返回 -1,并设置
errno
以指示错误类型。
利用终端IO传输数据
将USB-TLL的RX和TX相连形成一个回环。这样我们发送的数据会原封不动地返回接收端。
步骤
- 打开串口设备文件:使用
open()
函数打开设备节点,通常需要O_RDWR | O_NOCTTY | O_NDELAY
标志: - 配置串口参数:使用
tcgetattr``tcsetattr
设置串口参数,数据位,奇偶校验,波特率等。 - 使用
read``write
函数读写串口。
示例
#include "fcntl.h"#include "termios.h"#include "unistd.h"#include <stddef.h>#include <stdio.h>#include <stdlib.h>#include <string.h>
#define RDBUF 512
int set_opt(int fd, int speed, int databits, int stopbits, char parity, struct termios *oldtio);int reset_opt(int fd, const struct termios *oldtio);
int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Usage://\n"); exit(1); } const char *dev_path = argv[1]; int fd; fd = open(dev_path, O_RDWR | O_NOCTTY); if (fd < 0) { perror("open()"); exit(1); } struct termios oldtio; if (set_opt(fd, 115200, 8, 1, 'n', &oldtio) != 0) { close(fd); exit(1); } char readbuf[RDBUF]; char *linebuf = NULL; size_t linesize = 0; int get_n; int w_n; int r_n;
get_n = getline(&linebuf, &linesize, stdin); tcflush(fd, TCIOFLUSH); w_n = write(fd, linebuf, get_n); if (w_n < 0) { perror("write()"); exit(1); } if (tcdrain(fd) != 0) { perror("tcdrain failed"); } int total_r_n = 0; while (1) { r_n = read(fd, readbuf, RDBUF); if (r_n > 0) { readbuf[r_n] = '\0'; // 确保字符串结尾(可选) write(STDOUT_FILENO, readbuf, r_n); } else if (r_n == 0) { printf("read timeout or no data\n"); } else { perror("read failed"); } total_r_n += r_n; if (total_r_n >= get_n) { break; } } reset_opt(fd, &oldtio); return 0;}
// static struct termios oldtio;
int set_opt(int fd, int speed, int databits, int stopbits, char parity, struct termios *oldtio) {
struct termios newtio;
if (tcgetattr(fd, oldtio) < 0) { perror("tcgetattr()"); exit(1); } memcpy(&newtio, &oldtio, sizeof(struct termios));
newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG | IEXTEN); newtio.c_iflag &= ~(IXON | IXOFF | IXANY | ICRNL | INLCR | IGNCR); newtio.c_oflag &= ~OPOST; newtio.c_cflag |= (CLOCAL | CREAD);
speed_t speed_;
switch (speed) { 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; default: perror("ERROR"); exit(1); }
cfsetispeed(&newtio, speed_); cfsetospeed(&newtio, speed_);
newtio.c_cflag &= ~CSIZE; newtio.c_cflag &= ~PARENB; newtio.c_cflag &= ~CSTOPB;
switch (databits) { case 7: newtio.c_cflag |= CS7; break; case 8: newtio.c_cflag |= CS8; break; default: fprintf(stderr, "Unsupported data size\n"); return -1; // 改进:返回错误码 }
switch (parity) { case 'o': case 'O': newtio.c_cflag |= PARENB; newtio.c_cflag |= PARODD; newtio.c_iflag |= (INPCK | ISTRIP); break; case 'e': case 'E': newtio.c_cflag |= PARENB; newtio.c_cflag &= ~PARODD; newtio.c_iflag |= (INPCK | ISTRIP); break; case 'n': case 'N': newtio.c_cflag &= ~PARENB; break; default: newtio.c_cflag &= ~PARENB; break; }
if (stopbits == 2) { newtio.c_cflag |= CSTOPB; }
newtio.c_cc[VTIME] = 1; newtio.c_cc[VMIN] = 0;
tcflush(fd, TCIFLUSH); if (tcsetattr(fd, TCSANOW, &newtio) < 0) { perror("tcsetattr()"); exit(1); } return 0;}
int reset_opt(int fd, const struct termios *oldtio) {
tcsetattr(fd, TCSANOW, oldtio); return 0;}
上述代码仅仅是用于简单测试,程序的收发。
结构化数据传输
串口的速度非常缓慢,发送端的一次发送,接收端可能需要多次读取才能完全读完。
终端设备是基于字节流(Stream)
的通讯协议,如果需要结构化的数据,需要基于流来规定数据报的获取方式。
- 约定结束符号:我们可以已一个或多个数用作数据的结尾。
- 在数据头规定长度。读到规定长度后截止。
- 制定完整的通讯协议。可以制定完整的通讯协议,规定帧头,长度,数据,校验位,帧尾。