前言
通常,我们开发串口驱动和应用时,就是以Linux的TTY子系统(TTY框架)为基础进行的,而TTY子系统(TTY框架)的结构体termios就与串口通信参数的配置紧密相关,所以这篇博文就对结构体termios进行详解。
struct termios
是 Linux TTY(终端)子系统 用于配置串口终端设备的结构体,位于 <termios.h>
头文件中。
它用于 设置串口通信参数,如波特率、数据位、校验位、控制模式等。
1. termios结构体的定义
struct termios {tcflag_t c_iflag; /* 输入模式标志 */tcflag_t c_oflag; /* 输出模式标志 */tcflag_t c_cflag; /* 控制模式标志 */tcflag_t c_lflag; /* 本地模式标志 */cc_t c_cc[NCCS]; /* 控制字符 */speed_t c_ispeed; /* 输入波特率 */speed_t c_ospeed; /* 输出波特率 */
};
tcflag_t
:无符号整数类型,存储 模式标志cc_t
:char
类型,存储 特殊控制字符speed_t
:存储 波特率
2. termios结构体的字段解析
(1) c_iflag
:输入模式标志
控制串口接收数据时的处理方式。
#define IGNBRK 0x00000001 // 忽略 BREAK 信号
#define BRKINT 0x00000002 // 产生 SIGINT 信号
#define ICRNL 0x00000100 // 将 CR 转换为 NL(回车 -> 换行)
#define IGNPAR 0x00000004 // 忽略奇偶校验错误
#define INLCR 0x00000200 // 将 NL 转换为 CR(换行 -> 回车)
#define INPCK 0x00000010 // 使能奇偶校验
#define ISTRIP 0x00000020 // 剥离第 8 位(只保留低 7 位)
关于选项值IGNBRK、BRKINT的详细介绍见我的另一篇博文:https://blog.csdn.net/wenhao_ir/article/details/146190068
示例
options.c_iflag &= ~(ICRNL | INPCK); // 关闭回车转换和奇偶校验
(2) c_oflag
:输出模式标志
控制数据从串口发送时的处理方式。
#define OPOST 0x00000001 // 使能输出处理
#define ONLCR 0x00000002 // 将换行符 NL 转换为回车换行 CR-NL
示例
options.c_oflag &= ~OPOST; // 关闭输出处理(原始模式)
(3) c_cflag
:控制模式标志
控制 波特率、数据位、校验位、停止位
#define CSIZE 0x00000300 // 选择数据位掩码
#define CS5 0x00000000 // 5 数据位
#define CS6 0x00000100 // 6 数据位
#define CS7 0x00000200 // 7 数据位
#define CS8 0x00000300 // 8 数据位(常用)#define CSTOPB 0x00000400 // 2 停止位(默认 1 停止位)
#define CREAD 0x00000800 // 使能接收器
#define PARENB 0x00001000 // 使能校验
#define PARODD 0x00002000 // 奇校验(默认偶校验)
#define CLOCAL 0x00004000 // 忽略调制解调器控制线
示例
options.c_cflag |= CLOCAL | CREAD; // 使能本地控制和数据接收
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8; // 8 数据位
这里我要说明下字段值CLOCAL
。要搞清楚这个字段值,你要知道并不是所有的串口都只有三根线,串口有两线模式、三线模式、九线模式,如下表所示:
串口类型 | 使用的线 | 描述 |
---|---|---|
两线模式 | TXD 、RXD | 只支持单向数据通信 |
三线模式 | TXD 、RXD 、GND | 标准的 UART 通信方式 |
九线模式 | TXD 、RXD 、GND 、RTS 、CTS 、DTR 、DSR 、DCD 、RI | 完整的 RS-232 Modem 连接 |
上面这个表格中所说的“完整的 RS-232 Modem 连接 ”,为什么里面有Modem,因为我们网络通信中用的调制解调器的信号接口就是RS-232串口,即RS-232串口最常见、最常用于调制解调器中,所以说“RS-232 Modem 连接”。
通常,嵌入式系统(如 IMX6ULL 开发板)使用的串口是三线制:
- TXD(发送数据)
- RXD(接收数据)
- GND(地)
在三线制这种串口模式下,数据直接从一端的 TXD 传输到另一端的 RXD,不涉及 Modem 控制信号,因而 必须设置 CLOCAL
(即忽略 DCD 信号),否则 Linux 系统可能会等待 Modem 信号(DCD)而造成阻塞 open()
。
CLOCAL 状态 | DCD(数据载波检测)作用 | 适用场景 |
---|---|---|
设置(CLOCAL =1) | 忽略 DCD 信号,串口随时可用 | 直接设备通信(嵌入式 UART) |
清除(CLOCAL =0) | open() 可能会阻塞,等待 Modem 响应 | 传统 RS-232 Modem 连接 |
九线制这种串口模式的各引脚信号介绍下:
信号名 | 方向 | 功能 |
---|---|---|
TXD (Transmit Data) | 设备 → Modem | 发送数据 |
RXD (Receive Data) | Modem → 设备 | 接收数据 |
GND (Ground) | - | 参考地 |
RTS (Request to Send) | 设备 → Modem | 设备请求发送数据 |
CTS (Clear to Send) | Modem → 设备 | Modem 允许设备发送数据 |
DTR (Data Terminal Ready) | 设备 → Modem | 设备准备就绪 |
DSR (Data Set Ready) | Modem → 设备 | Modem 设备准备就绪 |
DCD (Data Carrier Detect) | Modem → 设备 | Modem 发现远程连接 |
RI (Ring Indicator) | Modem → 设备 | 有来电时 Modem 拉高此信号 |
此时,如果 CLOCAL
关闭,Linux TTY 子系统会监听 DCD(数据载波检测) 信号,只有当 Modem 端 DCD
变为高电平时,open("/dev/ttyS0")
才会成功,否则可能会阻塞。
(4) c_lflag
:TTY终端的本地输入处理方式
c_lflag
(本地模式标志)字段用于控制 Linux TTY(终端)子系统 的本地输入处理方式。它的主要作用是影响终端如何处理 行编辑、信号、回显 等功能。
#define ECHO 0x00000008 // 使能回显
#define ECHOE 0x00000010 // 擦除时回显
#define ICANON 0x00000002 // 启用规范模式(行缓冲模式)
#define ISIG 0x00000001 // 使能信号处理(如 `Ctrl+C` 触发 SIGINT)
以上配置值的详细解释如下:
字段解析
宏定义 | 值 | 作用 |
---|---|---|
ECHO | 0x08 | 输入字符时回显到终端,即在屏幕上显示用户输入内容 |
ECHOE | 0x10 | 启用 ERASE(退格)字符的回显,即按 Backspace 时,光标回退并删除字符 |
ICANON | 0x02 | 启用规范模式(Canonical Mode),即输入按 行缓冲 处理,需要 Enter 确认输入 |
ISIG | 0x01 | 使能信号处理,如 Ctrl+C 触发 SIGINT ,Ctrl+Z 触发 SIGTSTP |
详细解析
1. ECHO
(回显输入)
- 使能
ECHO
后,终端会显示用户输入的字符,适用于一般交互式终端(如bash
)。 - 关闭
ECHO
时,用户输入不会回显(如输入密码时sudo
命令隐藏输入)。
示例:
struct termios options;
tcgetattr(fd, &options);
options.c_lflag &= ~ECHO; // 禁用回显(输入不可见)
tcsetattr(fd, TCSANOW, &options);
应用场景:
- 关闭回显:
sudo
、passwd
命令的密码输入 - 开启回显:默认的终端输入
2. ECHOE
(回显擦除)
- 仅在
ICANON
开启 时生效(行缓冲模式)。 - 启用
ECHOE
后:- 按
Backspace
(ERASE
控制字符) 时,会删除屏幕上最后一个字符。 - 光标会回退,使用户可见删除效果。
- 按
- 若
ECHOE
关闭,则Backspace
只会在缓冲区中删除字符,但不会在屏幕上反映删除效果。
示例:
struct termios options;
tcgetattr(fd, &options);
options.c_lflag |= ECHOE; // 使能退格回显
tcsetattr(fd, TCSANOW, &options);
应用场景:
- 终端文本编辑,
Backspace
可见删除字符
3. ICANON
(规范模式)
- 开启(默认):
- 行缓冲:用户输入会被缓冲,直到按
Enter
(\n
)才会发送到程序处理。 - 支持行编辑:
Backspace
、Ctrl+U
(清除行)等可用。
- 行缓冲:用户输入会被缓冲,直到按
- 关闭(原始模式):
- 字符缓冲:每输入一个字符就立即发送到程序(常用于
vim
、ssh
等终端应用)。 - 不支持行编辑:退格不会删除字符,输入不可编辑。
- 字符缓冲:每输入一个字符就立即发送到程序(常用于
示例:
struct termios options;
tcgetattr(fd, &options);
options.c_lflag &= ~ICANON; // 关闭规范模式,启用原始模式
tcsetattr(fd, TCSANOW, &options);
应用场景:
- 规范模式:终端交互(
bash
命令行) - 原始模式:
vim
、nano
、游戏程序(即时处理键盘输入)
4. ISIG
(启用信号)
- 使能
ISIG
后,终端会解析特殊控制字符:Ctrl+C
→ 发送SIGINT
终止进程Ctrl+Z
→ 发送SIGTSTP
暂停进程Ctrl+\
→ 发送SIGQUIT
终止进程并生成 core dump
- 关闭
ISIG
后,这些快捷键不再生效,程序不会因为Ctrl+C
退出。
示例:
struct termios options;
tcgetattr(fd, &options);
options.c_lflag &= ~ISIG; // 关闭信号处理
tcsetattr(fd, TCSANOW, &options);
应用场景:
- 启用
ISIG
:正常的bash
终端,可以使用Ctrl+C
终止进程。 - 禁用
ISIG
:- 交互式程序不希望
Ctrl+C
退出(如 Vim)。 - 游戏程序或某些嵌入式系统防止误操作。
- 交互式程序不希望
表格总结
标志位 | 作用 | 关闭后的影响 |
---|---|---|
ECHO | 用户输入字符时会回显 | 输入不可见(如密码输入) |
ECHOE | Backspace 退格时删除并回显 | 退格仍可删除字符,但不会回显 |
ICANON | 行缓冲模式,Enter 提交输入 | 变为字符缓冲模式,每个字符立刻传递 |
ISIG | Ctrl+C 、Ctrl+Z 触发信号 | 这些快捷键失效,不会终止进程 |
常见应用
- 关闭回显(密码输入):
options.c_lflag &= ~ECHO;
- 关闭规范模式(即时输入):
options.c_lflag &= ~ICANON;
- 禁用
Ctrl+C
终止进程:options.c_lflag &= ~ISIG;
- 启用
Backspace
退格回显:options.c_lflag |= ECHOE;
小结
ECHO
:控制输入字符是否回显。ECHOE
:控制Backspace
时是否回显删除。ICANON
:控制是否 逐行缓冲(规范模式)。ISIG
:控制是否解析 特殊终端信号(Ctrl+C
、Ctrl+Z
等)。
(5) c_cc[NCCS]
:特殊控制字符
存储特殊控制字符(如 EOF
、ERASE
、VINTR
)。
options.c_cc[VMIN] = 1; // 最少读取 1 字节才返回
options.c_cc[VTIME] = 0; // 读取超时时间(0 = 不超时)
VMIN
:最小读取字节数VTIME
:超时时间(单位:1/10 秒)
(6) c_ispeed
/ c_ospeed
:输入/输出波特率
设置串口的收发速率。
#define B9600 9600
#define B115200 115200
示例
cfsetispeed(&options, B115200); // 设置输入波特率为 115200
cfsetospeed(&options, B115200); // 设置输出波特率为 115200
3. 代码示例
以下是一个串口初始化的示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>int set_serial(int fd) {struct termios options;// 1. 获取当前配置if (tcgetattr(fd, &options) != 0) {perror("tcgetattr error");return -1;}// 2. 设置波特率cfsetispeed(&options, B115200);cfsetospeed(&options, B115200);// 3. 配置控制模式options.c_cflag |= (CLOCAL | CREAD); // 本地连接, 使能接收options.c_cflag &= ~CSIZE;options.c_cflag |= CS8; // 8 数据位options.c_cflag &= ~PARENB; // 无校验options.c_cflag &= ~CSTOPB; // 1 停止位// 4. 配置输入输出模式options.c_iflag &= ~(IXON | IXOFF | IXANY); // 关闭软件流控options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 关闭规范模式options.c_oflag &= ~OPOST; // 关闭输出处理// 5. 设置超时options.c_cc[VMIN] = 1;options.c_cc[VTIME] = 0;// 6. 刷新缓冲区并应用配置tcflush(fd, TCIFLUSH);if (tcsetattr(fd, TCSANOW, &options) != 0) {perror("tcsetattr error");return -1;}return 0;
}int main() {int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY);if (fd < 0) {perror("open serial port error");return -1;}if (set_serial(fd) == 0) {printf("Serial port configured successfully\n");}close(fd);return 0;
}
4.总结
✅ struct termios
是 TTY 子系统 用于配置串口/终端设备的核心结构体
✅ 主要字段:
c_iflag
:输入模式(如奇偶校验)c_oflag
:输出模式(如换行转换)c_cflag
:控制模式(如数据位、校验位、停止位)c_lflag
:本地模式(如规范模式)c_cc[]
:特殊控制字符(如VMIN
、VTIME
)c_ispeed / c_ospeed
:波特率设置