串口设备文件

在Linux系统中,串口设备通常以文件形式存在于/dev/目录下:

  • /dev/ttyS0 - COM1 (传统串口)
  • /dev/ttyUSB0 - USB转串口设备
  • /dev/ttyAMA0 - Raspberry Pi等平台的串口

tcgetattr 和 tcsetattr 函数详解

这两个函数是Linux系统编程中用于配置终端(包括串口)属性的核心函数,它们操作termios结构体来控制终端的各种行为。

函数原型

1
2
3
4
5
#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);

tcgetattr 函数

功能

获取与文件描述符fd关联的终端的当前参数,并将其存储在termios_p指向的结构体中。

参数

  • fd: 打开终端设备的文件描述符
  • termios_p: 指向struct termios的指针,用于存储获取的参数

返回值

  • 成功:返回0
  • 失败:返回-1,并设置errno

示例

1
2
3
4
5
struct termios tty;
if (tcgetattr(fd, &tty) != 0) {
perror("tcgetattr failed");
return -1;
}

tcsetattr 函数

功能

使用termios_p指向的结构体中的参数来设置与文件描述符fd关联的终端的参数。

参数

  • fd: 打开终端设备的文件描述符
  • optional_actions: 指定何时应用更改
  • termios_p: 指向包含新参数的struct termios结构体

optional_actions 参数

  • TCSANOW: 立即应用更改
  • TCSADRAIN: 等待所有输出完成后应用更改
  • TCSAFLUSH: 等待所有输出完成,并丢弃所有未读输入后应用更改

返回值

  • 成功:返回0
  • 失败:返回-1,并设置errno

示例

1
2
3
4
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
perror("tcsetattr failed");
return -1;
}

struct termios 结构体详解

termios结构体包含多个字段来控制终端的行为:

1
2
3
4
5
6
7
struct termios {
tcflag_t c_iflag; // 输入模式标志
tcflag_t c_oflag; // 输出模式标志
tcflag_t c_cflag; // 控制模式标志
tcflag_t c_lflag; // 本地模式标志
cc_t c_cc[NCCS]; // 特殊控制字符
};

[!TIP]

重点关注 c_cflag 即可。

c_cflag - 控制模式标志

标志 说明 常用值
CSIZE 数据位掩码 CS5, CS6, CS7, CS8
CSTOPB 停止位 0=1位, 1=2位
PARENB 奇偶校验使能 0=禁用, 1=启用
PARODD 奇偶校验类型 0=偶校验, 1=奇校验
CREAD 接收使能 必须设置才能接收数据
CLOCAL 本地模式 忽略调制解调器控制线
CRTSCTS 硬件流控 启用RTS/CTS流控
CBAUD 波特率掩码 已废弃,用cfsetispeed/cfsetospeed

c_iflag - 输入模式标志

标志 说明
IGNBRK 忽略BREAK条件
BRKINT BREAK产生中断
IGNPAR 忽略奇偶错误
PARMRK 标记奇偶错误
INPCK 启用奇偶检查
ISTRIP 剥离第8位
INLCR 将NL映射为CR
IGNCR 忽略CR
ICRNL 将CR映射为NL
IXON 启用输出XON/XOFF流控
IXOFF 启用输入XON/XOFF流控
IXANY 允许任何字符重新开始输出

c_oflag - 输出模式标志

标志 说明
OPOST 启用输出处理
ONLCR 将NL映射为CR-NL
OCRNL 将CR映射为NL
ONOCR 在第0列不输出CR
ONLRET NL执行CR功能
OFILL 使用填充字符
OFDEL 填充字符是DEL
NLDLY NL延迟选择
CRDLY CR延迟选择
TABDLY TAB延迟选择
BSDLY BS延迟选择
VTDLY VT延迟选择
FFDLY FF延迟选择

c_lflag - 本地模式标志

标志 说明
ISIG 使能信号
ICANON 规范模式
ECHO 回显输入字符
ECHOE 规范模式下ERASE视觉反馈
ECHOK 规范模式下KILL视觉反馈
ECHONL 回显NL
NOFLSH 禁止信号后的清空
TOSTOP 后台写产生信号
IEXTEN 启用实现定义功能

c_cc[] - 特殊控制字符数组

索引 符号常量 说明 默认值
0 VINTR 中断字符 Ctrl-C (0x03)
1 VQUIT 退出字符 Ctrl-\ (0x1C)
2 VERASE 擦除字符 Backspace (0x7F)
3 VKILL 终止行字符 Ctrl-U (0x15)
4 VEOF 文件结束字符 Ctrl-D (0x04)
5 VTIME 非规范模式读取超时 -
6 VMIN 非规范模式最小字符数 -
7 VSWTC 开关字符 -
8 VSTART 开始字符 Ctrl-Q (0x11)
9 VSTOP 停止字符 Ctrl-S (0x13)

完整配置示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
int configure_serial(int fd, int baudrate) {
struct termios tty;

// 获取当前设置
if (tcgetattr(fd, &tty) != 0) {
perror("tcgetattr");
return -1;
}

// 设置输入输出波特率
cfsetispeed(&tty, baudrate);
cfsetospeed(&tty, baudrate);

// 控制模式配置
tty.c_cflag &= ~PARENB; // 无奇偶校验
tty.c_cflag &= ~CSTOPB; // 1位停止位
tty.c_cflag &= ~CSIZE; // 清除数据位掩码
tty.c_cflag |= CS8; // 8位数据位
tty.c_cflag &= ~CRTSCTS; // 无硬件流控
tty.c_cflag |= CREAD; // 启用接收
tty.c_cflag |= CLOCAL; // 忽略调制解调器控制线

// 输入模式配置
tty.c_iflag &= ~(IXON | IXOFF | IXANY); // 无软件流控
tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP |
INLCR | IGNCR | ICRNL | INPCK);

// 输出模式配置
tty.c_oflag &= ~OPOST; // 原始输出
tty.c_oflag &= ~ONLCR; // 禁止将NL转换为CR-NL

// 本地模式配置
tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL | ISIG);

// 超时配置:等待最多100ms,至少读取1个字符
tty.c_cc[VTIME] = 1; // 0.1秒超时 (单位是0.1秒)
tty.c_cc[VMIN] = 0; // 最小读取0个字符

// 应用设置
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
perror("tcsetattr");
return -1;
}

return 0;
}

C 程序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h> // open()
#include <termios.h> // tcgetattr(), tcsetattr()
#include <unistd.h> // read(), write(), close()
#include <errno.h> // errno

#define SERIAL_PORT "/dev/ttyS1"
#define BAUDRATE B115200
#define BUFFER_SIZE 128

// 打开串口
int open_serial(const char *device) {
int fd = open(device, O_RDWR | O_NOCTTY | O_SYNC);
if (fd < 0) {
perror("Error opening serial port");
return -1;
}
return fd;
}

// 配置串口参数
int configure_serial(int fd, speed_t baudrate) {
struct termios tty;

if (tcgetattr(fd, &tty) != 0) {
perror("Error getting tty attributes");
return -1;
}

cfsetospeed(&tty, baudrate); // 设置输出波特率
cfsetispeed(&tty, baudrate); // 设置输入波特率

tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; // 8位数据位
tty.c_cflag |= (CLOCAL | CREAD); // 启用接收器,忽略调制解调器线状态
tty.c_cflag &= ~(PARENB | PARODD); // 无奇偶校验
tty.c_cflag &= ~CSTOPB; // 1个停止位
tty.c_cflag &= ~CRTSCTS; // 不使用RTS/CTS硬件流控

tty.c_iflag &= ~(IXON | IXOFF | IXANY); // 关闭软件流控
tty.c_iflag &= ~(ICRNL | INLCR); // 关闭CR-LF转换

tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 关闭规范模式,关闭回显
tty.c_oflag &= ~OPOST; // 关闭输出处理

tty.c_cc[VMIN] = 0; // 非阻塞模式
tty.c_cc[VTIME] = 10; // 1秒超时

if (tcsetattr(fd, TCSANOW, &tty) != 0) {
perror("Error setting tty attributes");
return -1;
}

return 0;
}

// 发送数据
int send_data(int fd, const char *data) {
int len = strlen(data);
int bytes_written = write(fd, data, len);

if (bytes_written < 0) {
perror("Error writing to serial port");
return -1;
}
return bytes_written;
}

// 接收数据
int receive_data(int fd, char *buffer, int buffer_size) {
int bytes_read = read(fd, buffer, buffer_size);

if (bytes_read < 0) {
perror("Error reading from serial port");
return -1;
}
return bytes_read;
}

// 关闭串口
void close_serial(int fd) {
if (close(fd) != 0) {
perror("Error closing serial port");
}
}

int main() {
int serial_fd;
char recv_buffer[BUFFER_SIZE];

// 打开串口
serial_fd = open_serial(SERIAL_PORT);
if (serial_fd < 0) return 1;

// 配置串口
if (configure_serial(serial_fd, BAUDRATE) != 0) {
close_serial(serial_fd);
return 1;
}

// 发送数据
const char *message = "Hello World!\n";
if (send_data(serial_fd, message) < 0) {
close_serial(serial_fd);
return 1;
}
printf("Sent: %s", message);

// 接收数据
int bytes_received = receive_data(serial_fd, recv_buffer, BUFFER_SIZE - 1);
if (bytes_received > 0) {
recv_buffer[bytes_received] = '\0'; // 添加字符串终止符
printf("Received: %s\n", recv_buffer);
} else if (bytes_received == 0) {
printf("No data received.\n");
}

// 关闭串口
close_serial(serial_fd);

return 0;
}