<think>我们将在Linux下使用C语言实现串口通信,包括以下核心部分:
1. 打开串口设备
2. 配置串口参数(波特率、数据位、停止位、校验位等)
3. 使用环形缓冲区(队列)作为收发数据的缓冲区
4. 创建读取线程,将串口接收到的数据存入接收环形缓冲区
5. 提供发送函数,将数据放入发送环形缓冲区,并实际发送(可以在主循环中发送,也可以使用单独的发送线程)
6. 主程序可以处理接收到的数据并发送数据
参考引用[1][2]中提到的struct termios结构体进行串口配置,引用[3][4]中的环形缓冲区实现。
具体步骤:
步骤1:打开串口设备
步骤2:配置串口参数
步骤3:实现环形缓冲区(接收和发送各一个)
步骤4:创建读取线程,不断读取串口数据并存入接收缓冲区
步骤5:提供发送接口,将数据写入发送缓冲区,并启动发送(可以立即发送或由另一个线程发送)
步骤6:主程序示例
注意:环形缓冲区需要是线程安全的,因为读取线程和主线程可能同时操作接收缓冲区,发送也可能由不同线程操作。
下面我们逐步实现:</think>### Linux下C语言实现带缓冲队列的串口通信
#### 1. **串口设备打开与配置**
使用`termios`结构体配置串口参数[^1][^2]:
```c
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
// 打开串口设备
int serial_open(const char* device) {
int fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd < 0) {
perror("Error opening serial port");
return -1;
}
fcntl(fd, F_SETFL, 0); // 恢复阻塞模式
return fd;
}
// 配置串口参数
int serial_config(int fd, int baud, int databits, char parity, int stopbits) {
struct termios options;
tcgetattr(fd, &options);
// 设置波特率
speed_t speed;
switch (baud) {
case 9600: speed = B9600; break;
case 19200: speed = B19200; break;
case 38400: speed = B38400; break;
case 115200: speed = B115200; break;
default: speed = B9600;
}
cfsetispeed(&options, speed);
cfsetospeed(&options, speed);
// 设置数据位
options.c_cflag &= ~CSIZE;
switch (databits) {
case 5: options.c_cflag |= CS5; break;
case 6: options.c_cflag |= CS6; break;
case 7: options.c_cflag |= CS7; break;
case 8:
default: options.c_cflag |= CS8;
}
// 设置校验位
switch (parity) {
case 'O': // 奇校验
options.c_cflag |= PARENB;
options.c_cflag |= PARODD;
break;
case 'E': // 偶校验
options.c_cflag |= PARENB;
options.c_cflag &= ~PARODD;
break;
case 'N': // 无校验
default:
options.c_cflag &= ~PARENB;
}
// 设置停止位
options.c_cflag &= ~CSTOPB;
if (stopbits == 2)
options.c_cflag |= CSTOPB;
// 基本配置
options.c_cflag |= (CLOCAL | CREAD); // 本地连接,启用接收
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 原始输入模式
options.c_oflag &= ~OPOST; // 原始输出模式
options.c_cc[VMIN] = 0; // 最小读取字符数
options.c_cc[VTIME] = 10; // 读取超时(1秒)
tcsetattr(fd, TCSANOW, &options);
return 0;
}
```
#### 2. **环形缓冲区实现**
参考引用[3][^3]实现线程安全的环形缓冲区:
```c
#include <pthread.h>
#define BUFFER_SIZE 1024
typedef struct {
unsigned char buffer[BUFFER_SIZE];
int head;
int tail;
pthread_mutex_t lock;
} RingBuffer;
// 初始化缓冲区
void ringbuf_init(RingBuffer* rb) {
rb->head = 0;
rb->tail = 0;
pthread_mutex_init(&rb->lock, NULL);
}
// 写入数据
int ringbuf_write(RingBuffer* rb, unsigned char data) {
pthread_mutex_lock(&rb->lock);
int next = (rb->head + 1) % BUFFER_SIZE;
if (next == rb->tail) { // 缓冲区满
pthread_mutex_unlock(&rb->lock);
return -1;
}
rb->buffer[rb->head] = data;
rb->head = next;
pthread_mutex_unlock(&rb->lock);
return 0;
}
// 读取数据
int ringbuf_read(RingBuffer* rb, unsigned char* data) {
pthread_mutex_lock(&rb->lock);
if (rb->tail == rb->head) { // 缓冲区空
pthread_mutex_unlock(&rb->lock);
return -1;
}
*data = rb->buffer[rb->tail];
rb->tail = (rb->tail + 1) % BUFFER_SIZE;
pthread_mutex_unlock(&rb->lock);
return 0;
}
// 获取缓冲区数据量
int ringbuf_count(RingBuffer* rb) {
pthread_mutex_lock(&rb->lock);
int count = (rb->head >= rb->tail) ?
(rb->head - rb->tail) :
(BUFFER_SIZE - rb->tail + rb->head);
pthread_mutex_unlock(&rb->lock);
return count;
}
```
#### 3. **数据收发线程**
```c
#include <pthread.h>
RingBuffer rx_buf, tx_buf;
// 接收线程函数
void* receive_thread(void* arg) {
int fd = *((int*)arg);
unsigned char ch;
while (1) {
int n = read(fd, &ch, 1);
if (n > 0) {
ringbuf_write(&rx_buf, ch);
}
usleep(10000); // 10ms延迟避免CPU过高
}
return NULL;
}
// 发送线程函数
void* transmit_thread(void* arg) {
int fd = *((int*)arg);
unsigned char ch;
while (1) {
if (ringbuf_read(&tx_buf, &ch) == 0) {
write(fd, &ch, 1);
} else {
usleep(5000); // 缓冲区空时短暂休眠
}
}
return NULL;
}
// 发送数据接口
void serial_send(int fd, const unsigned char* data, int len) {
for (int i = 0; i < len; i++) {
while (ringbuf_write(&tx_buf, data[i]) != 0) {
usleep(1000); // 等待缓冲区空间
}
}
}
```
#### 4. **完整使用示例**
```c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int main() {
// 初始化缓冲区
ringbuf_init(&rx_buf);
ringbuf_init(&tx_buf);
// 打开并配置串口
int fd = serial_open("/dev/ttyUSB0");
if (fd < 0) return EXIT_FAILURE;
// 配置: 115200波特率, 8数据位, 无校验, 1停止位
serial_config(fd, 115200, 8, 'N', 1);
// 创建收发线程
pthread_t rx_tid, tx_tid;
pthread_create(&rx_tid, NULL, receive_thread, &fd);
pthread_create(&tx_tid, NULL, transmit_thread, &fd);
// 主循环处理接收数据
unsigned char data;
while (1) {
int count = ringbuf_count(&rx_buf);
if (count > 0) {
printf("Received %d bytes: ", count);
while (ringbuf_read(&rx_buf, &data) == 0) {
printf("%02X ", data); // 十六进制显示
}
printf("\n");
}
// 示例: 每秒发送测试数据
static int counter = 0;
unsigned char test_data[] = {0xAA, 0x55, counter++ % 256};
serial_send(fd, test_data, sizeof(test_data));
sleep(1);
}
close(fd);
return EXIT_SUCCESS;
}
```
#### 5. **关键配置参数对照表**
| 参数 | 函数参数 | 可选值 |
|--------------|----------------------|--------------------------------|
| **波特率** | `baud` | 9600, 19200, 38400, 115200 |
| **数据位** | `databits` | 5, 6, 7, 8 |
| **校验位** | `parity` | 'N'(无), 'O'(奇), 'E'(偶) |
| **停止位** | `stopbits` | 1, 2 |
| **流控** | 需扩展 | 0=无, 1=RTS/CTS, 2=XON/XOFF |
> **编译命令**:`gcc serial.c -o serial -lpthread`
#### 6. **应用场景**
1. **工业设备监控**:实时采集传感器数据
2. **嵌入式开发**:与微控制器通信调试
3. **物联网网关**:连接LoRa/NB-IoT模块
4. **机器人控制**:发送运动指令和接收状态反馈