启用两个线程,一个线程负责rs485的发送,一个负责rs485的接收(2)

接前一篇文章:启用两个线程,一个线程负责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 &amp;= ~(IXON | IXOFF | IXANY);  // 禁用软件流控
    tty.c_iflag &amp;= ~(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(&amp;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(&amp;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(&amp;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控制线程退出

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝天居士

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值