接前一篇文章:启用两个线程,一个线程负责rs485的发送,一个负责rs485的接收(1)
上一回讨论了两个线程进行RS485发送和接收的各个方案及其优劣,最终汇集到两个可行方案:
(1)使用条件变量来协调
当没有数据要发送时,接收线程可以长时间占用锁(即等待数据),当有数据要发送时,发送线程通知接收线程释放锁。但这样更复杂。
(2)超时机制
更为简单的超时机制:接收线程每次占用锁的时间很短(非阻塞读取,然后立即释放),然后休眠一段时间。这样,发送线程可以很快获取锁。
最终,选择后者为实际实现方案。
3. 最终方案详述
(1)步骤
总体步骤如下:
1)打开串口设备,配置串口(波特率、数据位、停止位、校验位等);
2)设置串口为非阻塞模式(使用fcntl);
3)初始化互斥锁;
4)创建发送线程和接收线程。
(2)实际代码
以下是使用C语言实现的双线程(发送线程+接收线程)RS485通信代码,包含详细的注释和互斥锁保护机制。由于代码较长,只给出关键部分。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#define SERIAL_PORT "/dev/ttyUSB0" // RS485设备路径
#define BAUD_RATE B115200 // 波特率
#define BUFFER_SIZE 256 // 数据缓冲区大小
// 全局变量
int serial_fd; // 串口文件描述符
pthread_mutex_t rs485_mutex; // RS485通信互斥锁
volatile int running = 1; // 线程运行控制标志
// 初始化串口
int init_serial(void)
{
struct termios tty;
// 打开串口设备
serial_fd = open(SERIAL_PORT, O_RDWR | O_NOCTTY | O_SYNC);
if (serial_fd < 0)
{
perror("Error opening serial port");
return -1;
}
// 获取当前串口配置
if (tcgetattr(serial_fd, &tty) != 0)
{
perror("tcgetattr error");
return -1;
}
// 设置波特率
cfsetospeed(tty, BAUD_RATE);
cfsetispeed(tty, BAUD_RATE);
// 设置串口参数
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 | CLOCAL; // 启用接收
// 本地模式设置
tty.c_lflag &= ~ICANON; // 非规范模式
tty.c_lflag &= ~ECHO; // 禁用回显
tty.c_lflag &= ~ECHONL; // 禁用换行回显
tty.c_lflag &= ~ISIG; // 禁用信号字符
// 输入模式设置
tty.c_iflag &= ~(IXON | IXOFF | IXANY); // 禁用软件流控
tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL);
// 输出模式设置
tty.c_oflag &= ~OPOST; // 原始输出
// 超时设置:立即返回
tty.c_cc[VMIN] = 0; // 最小读取字符数
tty.c_cc[VTIME] = 5; // 0.5秒超时
// 应用配置
if (tcsetattr(serial_fd, TCSANOW, &tty) != 0)
{
perror("tcsetattr error");
return -1;
}
// 配置RS485方向控制(根据硬件需求)
// 注意:某些硬件需要额外的GPIO控制
// 此处仅作为示例,实际硬件可能需要特殊配置
return 0;
}
// 发送线程函数
void *tx_thread(void *arg)
{
const char *thread_name = (const char*)arg;
char tx_buffer[BUFFER_SIZE];
int len;
printf("[%s] Starting RS485 transmitter\n", thread_name);
while (running)
{
// 模拟生成数据(实际应用中替换为真实数据)
len = snprintf(tx_buffer, BUFFER_SIZE, "TX@%lu", (unsigned long)time(NULL));
// 获取互斥锁(确保独占访问RS485总线)
pthread_mutex_lock(&rs485_mutex);
// 设置RS485为发送模式(硬件相关)
// 例如:gpio_set_direction(TX_ENABLE_PIN, HIGH);
// 发送数据
ssize_t bytes_written = write(serial_fd, tx_buffer, len);
if (bytes_written < 0)
{
perror("Write error");
}
else
{
printf("[%s] Sent %zd bytes: %.*s\n",
thread_name, bytes_written, (int)bytes_written, tx_buffer);
}
// 等待数据完全发送(重要!)
tcdrain(serial_fd);
// 设置RS485为接收模式
// 例如:gpio_set_direction(TX_ENABLE_PIN, LOW);
// 释放互斥锁
pthread_mutex_unlock(&rs485_mutex);
// 发送间隔
sleep(1);
}
printf("[%s] Exiting transmitter\n", thread_name);
return NULL;
}
// 接收线程函数
void *rx_thread(void *arg)
{
const char *thread_name = (const char*)arg;
char rx_buffer[BUFFER_SIZE] = {0};
printf("[%s] Starting RS485 receiver\n", thread_name);
while (running)
{
// 获取互斥锁(确保接收期间不会发送数据)
pthread_mutex_lock(&rs485_mutex);
// 设置RS485为接收模式(通常默认状态)
// 例如:gpio_set_direction(TX_ENABLE_PIN, LOW);
// 读取数据(非阻塞模式)
ssize_t bytes_read = read(serial_fd, rx_buffer, BUFFER_SIZE - 1);
if (bytes_read < 0)
{
if (errno != EAGAIN && errno != EWOULDBLOCK)
{
perror("Read error");
}
}
else if (bytes_read > 0)
{
rx_buffer[bytes_read] = '\0'; // 添加字符串终止符
printf("[%s] Received %zd bytes: %s\n",
thread_name, bytes_read, rx_buffer);
}
// 释放互斥锁
pthread_mutex_unlock(&rs485_mutex);
// 短暂休眠避免CPU占用过高
usleep(100000); // 100ms
}
printf("[%s] Exiting receiver\n", thread_name);
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t tx_tid, rx_tid;
// 初始化串口
if (init_serial() != 0)
{
fprintf(stderr, "Serial initialization failed\n");
exit(EXIT_FAILURE);
}
// 初始化互斥锁
if (pthread_mutex_init(&rs485_mutex, NULL) != 0)
{
perror("Mutex init failed");
close(serial_fd);
exit(EXIT_FAILURE);
}
// 创建发送线程
if (pthread_create(&tx_tid, NULL, tx_thread, "TX-Thread") != 0)
{
perror("Failed to create TX thread");
close(serial_fd);
exit(EXIT_FAILURE);
}
// 创建接收线程
if (pthread_create(&rx_tid, NULL, rx_thread, "RX-Thread") != 0)
{
perror("Failed to create RX thread");
running = 0;
pthread_join(tx_tid, NULL);
close(serial_fd);
exit(EXIT_FAILURE);
}
// 主线程等待
printf("Press Enter to exit...\n");
getchar();
running = 0;
// 清理资源
pthread_join(tx_tid, NULL);
pthread_join(rx_tid, NULL);
pthread_mutex_destroy(&rs485_mutex);
close(serial_fd);
printf("Program terminated\n");
return 0;
}
(3)关键实现原理说明
1)互斥锁保护机制
- 使用
pthread_mutex_t确保发送和接收操作不会同时进行 - 发送前加锁,发送完成后释放锁
- 接收前加锁,接收完成后释放锁
- 防止总线冲突,确保RS485半双工特性
2)方向控制关键点
- 发送前启用发送模式(硬件相关)
- 使用
tcdrain()确保数据完全发送 - 发送完成后立即切换回接收模式
- 实际硬件可能需要额外的GPIO控制
3)线程设计
- 发送线程:周期性生成数据并通过串口发送
- 接收线程:持续监听串口数据
- 主线程负责初始化和资源清理
4)串口配置要点
- 非规范模式(原始数据)
- 禁用回显和信号处理
- 设置精确的超时控制
- 正确配置波特率/数据位/停止位
5)流量控制
- 接收线程使用非阻塞读取
- 添加usleep避免CPU占用过高
- 全局变量
running控制线程退出

被折叠的 条评论
为什么被折叠?



