485总线多设备收发控制

目录

本文结论

​​​背景分析

        1.1 第一版

        1.2 第二版

        1.3 第三版


本文结论

        对于485总线上有2个及以上设备时,应采用多线程强占式的并发线程进行处理来尽量避免数据冲突的问题

​​​背景分析

        在XXXX项目中开发三色灯与喇叭功能模块时,采用在485线上串联了设备1喇叭和设备2三色灯,通过数据收发间歇500ms向三色灯发送MODBUS_485数据,间歇性5s向喇叭发送控制数据,由于消息的收发容易导致这2个设备不能正常的工作,总会出现灯闪灭不及时或者喇叭播报间隔时间过长、播报不全等问题,分析代码,发现:

        1.1 第一版

void* processLEDWrapper(void* arg) {
    #if LED_INFO
        // 设置日志文件名
        hlog_set_file("led_speaker.log");
        // 设置日志格式
        hlog_set_format("[led_speaker] %y-%m-%d %H:%M:%S.%z %L %s");
        // 启用日志颜色(如果支持)
        logger_enable_color(hlog, 1);
    #endif
    rs485_info_log_init();
    modbus_t* ctx = NULL;
    static int connect_attempt = 0;
    static int conn_succ_flag = 0;
    static int led_count = 0, speak_count = 0;
    static int led_send = 0;
    // 添加定时器功能
    //time_t last_print_time = time(NULL);
    while (1) {
        if (ctx == NULL) {
            // 新建libmodbus context
            ctx = modbus_new_rtu("/dev/ttyS3", 9600, 'N', 8, 1);
            if (ctx == NULL) {
                fprintf(stderr, "Unable to allocate libmodbus context\n");
                #if LED_INFO
                fprintf(stderr, "Unable to allocate libmodbus context\n");
                #endif
                if(rs485_info_log)
                {
                    hlogi("Unable to allocate libmodbus context\n");
                }
                //goto cleanup;
            }

            if(ctx != NULL)
            {
            // 设置485模式
            modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485);
            }
        }
        // 连接设备
        if (ctx != NULL) {
           if ((conn_succ_flag==0)&&(modbus_connect(ctx) == -1))
           {
                conn_succ_flag =0;
                fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
                #if LED_INFO
                fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
                #endif
                if(rs485_info_log)
                {
                    hlogi("Connection failed: %s\n", modbus_strerror(errno));
                }
                connect_attempt++;

                if (connect_attempt >= MAX_CONNECT_ATTEMPTS) {
                    fprintf(stderr, "Exceeded maximum connect attempts. Exiting.\n");
                    #if LED_INFO
                    fprintf(stderr, "Exceeded maximum connect attempts. Exiting.\n");
                    #endif
                    if(rs485_info_log)
                    {
                        hlogi("Exceeded maximum connect attempts. Exiting.\n");
                    }
                    goto cleanup;
                }
                // Retry after a delay
                printf("-------------ERROR------------------\n");
                usleep(500000);
                continue;
            } 
            else
            {
                connect_attempt = 0; // Reset connect attempt count on successful connection
                conn_succ_flag = 1;
            }
            // modbus_set_response_timeout(ctx, 0, 100); // 设置超时时间
            // 设置从机地址
            // 设置从机2

            #if 1
            if (modbus_set_slave(ctx, SLAVE2_ID) == -1) {

                fprintf(stderr, "Unable to set slave2 ID: %s\n", modbus_strerror(errno));
                #if LED_INFO
                fprintf(stderr, "Unable to allocate libmodbus context\n");
                #endif
                if(rs485_info_log)
                {
                    hlogi("Unable to allocate libmodbus context2\n");
                }
                modbus_close(ctx);
                modbus_free(ctx);
                ctx = NULL;
            }else{  //设置从机2成功
                //printf("led_count:%d\n",led_count);
                if(led_count++ > LED_DELAY_TIME) //非堵塞模式
                {
                    led_count = 0;
                    handle_LED(ctx, ledArray);
                }
            }
            #endif               
            #if 1
            //设置从机1
            if (modbus_set_slave(ctx, SLAVE1_ID) == -1) {
            fprintf(stderr, "Unable to set slave1 ID: %s\n", modbus_strerror(errno));
            #if LED_INFO
            fprintf(stderr, "Unable to allocate libmodbus context1\n");
            #endif
            if(rs485_info_log)
            {
                hlogi("Unable to allocate libmodbus context1\n");
            }
            modbus_close(ctx);
            modbus_free(ctx);
            ctx = NULL;
            }else{
                // printf("speak_count:%d\n",speak_count);
                {
                    if(speak_count++ > SPEAK_DELAY_TIME )
                    {
                        // led_send = 0;
                        speak_count = 0;
                        handle_Speaker(ctx,ledArray);
                    }
                }
            }
            #endif
        }
        usleep(1);  //进来是以时间片作为参考系的
    }     
    goto cleanup;
cleanup:
    // 清理资源
    if (ctx) {
        modbus_close(ctx);
        modbus_free(ctx);
    }
    return NULL;
}

缺点:非阻塞的模式时间精度不够高,485总线上的收发包应该要错开来,推荐时间戳的收发包

       代码如下:

        1.2 第二版

//线程1的入口函数,处理数据的线程函数,负责处理LED线程
void* processLEDWrapper(void* arg) {
    #if LED_INFO
        // 设置日志文件名
        hlog_set_file("led_speaker.log");
        // 设置日志格式
        hlog_set_format("[led_speaker] %y-%m-%d %H:%M:%S.%z %L %s");
        // 启用日志颜色(如果支持)
        logger_enable_color(hlog, 1);
    #endif
    rs485_info_log_init();
    modbus_t* ctx = NULL;
    static int connect_attempt = 0;
    static int conn_succ_flag = 0;
    static int led_count = 0, speak_count = 0;
    static int led_send = 0;
    time_t last_LED_time = time(NULL);
    time_t last_Speaker_time = time(NULL);
    // 添加定时器功能
    //time_t last_print_time = time(NULL);
    while (1) {
        if (ctx == NULL) {
            // 新建libmodbus context
            ctx = modbus_new_rtu("/dev/ttyS3", 9600, 'N', 8, 1);
            if (ctx == NULL) {
                fprintf(stderr, "Unable to allocate libmodbus context\n");
                #if LED_INFO
                fprintf(stderr, "Unable to allocate libmodbus context\n");
                #endif
                if(rs485_info_log)
                {
                    hlogi("Unable to allocate libmodbus context\n");
                }
                //goto cleanup;
            }

            if(ctx != NULL)
            {
            // 设置485模式
            modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485);
            }
        }
        // 连接设备
        if (ctx != NULL) {
           if ((conn_succ_flag==0)&&(modbus_connect(ctx) == -1))
           {
                conn_succ_flag =0;
                fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
                #if LED_INFO
                fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
                #endif
                if(rs485_info_log)
                {
                    hlogi("Connection failed: %s\n", modbus_strerror(errno));
                }
                connect_attempt++;

                if (connect_attempt >= MAX_CONNECT_ATTEMPTS) {
                    fprintf(stderr, "Exceeded maximum connect attempts. Exiting.\n");
                    #if LED_INFO
                    fprintf(stderr, "Exceeded maximum connect attempts. Exiting.\n");
                    #endif
                    if(rs485_info_log)
                    {
                        hlogi("Exceeded maximum connect attempts. Exiting.\n");
                    }
                    goto cleanup;
                }
                // Retry after a delay
                printf("-------------ERROR------------------\n");
                usleep(500000);
                continue;
            } 
            else
            {
                connect_attempt = 0; // Reset connect attempt count on successful connection
                conn_succ_flag = 1;
            }
            // modbus_set_response_timeout(ctx, 0, 100); // 设置超时时间
            // 设置从机地址
            // 设置从机2
            #if 1
            
            if (modbus_set_slave(ctx, SLAVE2_ID) == -1) {

                fprintf(stderr, "Unable to set slave2 ID: %s\n", modbus_strerror(errno));
                #if LED_INFO
                fprintf(stderr, "Unable to allocate libmodbus context\n");
                #endif
                if(rs485_info_log)
                {
                    hlogi("Unable to allocate libmodbus context2\n");
                }
                modbus_close(ctx);
                modbus_free(ctx);
                ctx = NULL;
            }else{  //设置从机2成功
                time_t current_time = time(NULL);
                if (difftime(current_time, last_LED_time) >= HEARTBEAT_INTERVAL) {  //间隔1s发1次
                    last_LED_time = current_time;
                    handle_LED(ctx, ledArray);
                }
            }
            #endif               
            #if 1
            //设置从机1
            if (modbus_set_slave(ctx, SLAVE1_ID) == -1) {
            fprintf(stderr, "Unable to set slave1 ID: %s\n", modbus_strerror(errno));
            #if LED_INFO
            fprintf(stderr, "Unable to allocate libmodbus context1\n");
            #endif
            if(rs485_info_log)
            {
                hlogi("Unable to allocate libmodbus context1\n");
            }
            modbus_close(ctx);
            modbus_free(ctx);
            ctx = NULL;
            }else{
                time_t current_time2 = time(NULL);
                if (difftime(current_time2, last_Speaker_time) >= 2) {  //间隔2s发1次
                    last_Speaker_time = current_time2;
                    handle_Speaker(ctx, ledArray);
                }
                // if(speak_count++ > SPEAK_DELAY_TIME )
                //     {
                //         // led_send = 0;
                //         speak_count = 0;
                //         handle_Speaker(ctx,ledArray);
                //     }
            }
            #endif
        }
        usleep(1);
    }     
    goto cleanup;
cleanup:
    // 清理资源
    if (ctx) {
        modbus_close(ctx);
        modbus_free(ctx);
    }
    return NULL;
}

         通过在一条控制线程切换ctx来控制两个不同的设备一方面会随着后续设备的增多导致延时时间过长,另一方面tick的精度会随着长时间运行导致误差过大。

        综上,最终采用抢占式多并发的处理线程来解决上诉问题,采用多线程高并发的模式来解决上诉问题,代码如下:

        1.3 第三版

void* process_Speaker_entry(modbus_t* ctx, void* arg) {
    while (1) {
        //wwb++;
        //printf("wwwwwww ===%d\n",wwb); 
        if (ctx != NULL && conn_succ_flag == 1) {
            //设置从机1
            pthread_mutex_lock(&ctx_mutex);
            if (modbus_set_slave(ctx, SLAVE1_ID) == -1) {
                fprintf(stderr, "Unable to set slave1 ID: %s\n", modbus_strerror(errno));
                #if LED_INFO
                fprintf(stderr, "Unable to allocate libmodbus context1\n");
                #endif
                if(rs485_info_log)
                {
                    hlogi("Unable to allocate libmodbus context1\n");
                }
            }else{  
                handle_Speaker(ctx,ledArray);               
            }
            pthread_mutex_unlock(&ctx_mutex);
        } 
        sleep(5);//延时1s
    }
    return NULL;
}

// Function to handle communication with device2
void* process_Led_entry(modbus_t* ctx, void* arg){
    
    while (1) {
        if (ctx == NULL) {
            conn_succ_flag = 0;
            // 新建libmodbus context
            ctx = modbus_new_rtu("/dev/ttyS3", 9600, 'N', 8, 1);
            if (ctx == NULL) {
                fprintf(stderr, "Unable to allocate libmodbus context\n");
                #if LED_INFO
                fprintf(stderr, "Unable to allocate libmodbus context\n");
                #endif
                if(rs485_info_log)
                {
                    hlogi("Unable to allocate libmodbus context\n");
                }
                //goto cleanup;
            }
            if(ctx != NULL)
            {
                // 设置485模式
                modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485);
            }
        }

        // 连接设备
        if (ctx != NULL) {
           if ((conn_succ_flag==0)&&(modbus_connect(ctx) == -1))
           {
                conn_succ_flag =0;
                fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
                #if LED_INFO
                fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
                #endif
                if(rs485_info_log)
                {
                    hlogi("Connection failed: %s\n", modbus_strerror(errno));
                }
                connect_attempt++;

                if (connect_attempt >= MAX_CONNECT_ATTEMPTS) {
                    fprintf(stderr, "Exceeded maximum connect attempts. Exiting.\n");
                    #if LED_INFO
                    fprintf(stderr, "Exceeded maximum connect attempts. Exiting.\n");
                    #endif
                    if(rs485_info_log)
                    {
                        hlogi("Exceeded maximum connect attempts. Exiting.\n");
                    }
                    modbus_close(ctx);
                    modbus_free(ctx);
                    ctx = NULL;
                }
                // Retry after a delay
                //printf("-------------ERROR------------------\n");
                usleep(500000);
                continue;
            } 
            else
            {
                connect_attempt = 0; // Reset connect attempt count on successful connection
                conn_succ_flag = 1;
            }
        }

        if (ctx != NULL && conn_succ_flag == 1) {
            pthread_mutex_lock(&ctx_mutex);
            if (modbus_set_slave(ctx, SLAVE2_ID) == -1) {
                fprintf(stderr, "Unable to set slave2 ID: %s\n", modbus_strerror(errno));
                #if LED_INFO
                fprintf(stderr, "Unable to allocate libmodbus context\n");
                #endif
                if(rs485_info_log)
                {
                    hlogi("Unable to allocate libmodbus context2\n");
                }
            }else{  //设置从机2成功
                handle_LED(ctx, ledArray);  
            }
            pthread_mutex_unlock(&ctx_mutex);
            usleep(1000000); //延时1s
        }
    }
}

        最后,通过打印喇叭叫的次数和观察三色灯闪烁的情况是是否出现之前的问题,问题解决。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/915367.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Jmeter基础篇(23)TPS和QPS的异同

前言 这是一篇性能测试指标的科普文章哦! TPS和QPS是同一个概念吗? TPS(Transactions Per Second)和QPS(Queries Per Second)虽然都是衡量系统性能的指标,但是它们并不是同一个概念。这两个各…

IEC60870-5-104 协议源码架构详细分析

IEC60870-5-104 协议源码架构 前言一、资源三、目录层级一二、目录层级二config/lib60870_config.hdependencies/READMEexamplesCMakeLists.txtcs101_master_balancedcs104_client_asyncmulti_client_servertls_clienttls_server说明 make这些文件的作用是否需要导入这些文件&a…

机器学习在网络安全中的应用

💓 博客主页:瑕疵的CSDN主页 📝 Gitee主页:瑕疵的gitee主页 ⏩ 文章专栏:《热点资讯》 机器学习在网络安全中的应用 机器学习在网络安全中的应用 机器学习在网络安全中的应用 引言 机器学习概述 定义与原理 发展历程 …

JUC基础类-AbstractQueuedSynchronizer

AbstractQueuedSynchronizer 1、AbstractQueuedSynchronizer概述2、AbstractQueuedSynchronizer源码分析2.1 AQS源码2.2 Node类 如有侵权,请联系~ 如有问题,也欢迎批评指正~ 1、AbstractQueuedSynchronizer概述 AbstractQueuedSy…

文献阅读 | Nature Methods:使用 STAMP 对空间转录组进行可解释的空间感知降维

文献介绍 文献题目: 使用 STAMP 对空间转录组进行可解释的空间感知降维 研究团队: 陈金妙(新加坡科学技术研究局) 发表时间: 2024-10-15 发表期刊: Nature Methods 影响因子: 36.1&#xff0…

Redis系列之底层数据结构ZipList

Redis系列之底层数据结构ZipList 实验环境 Redis 6.0 什么是Ziplist? Ziplist,压缩列表,这种数据结构会根据存入数据的类型和大小,分配大小不同的空间,所以是为了节省内存而采用的。因为这种数据结构是一种完整连续…

界面控件DevExpress WPF中文教程:TreeList视图及创建分配视图

DevExpress WPF拥有120个控件和库,将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序,这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件…

数据结构中数据有序性/ 单调性 ——二分查找

以下记录的都是闭区间写法 例题&#xff1a;34. 在排序数组中查找元素的第一个和最后一个位置 1.关系转换 寻找目标值有四种情况&#xff1a;≥、>、≤、< 比如目标值x&#xff0c; 可以转化为 ≥x、≥ x1、≤x、≤ x1 比如数组大小为6&#xff0c;目标值为…

探索Python的HTTP利器:Requests库的神秘面纱

文章目录 **探索Python的HTTP利器&#xff1a;Requests库的神秘面纱**一、背景&#xff1a;为何选择Requests库&#xff1f;二、Requests库是什么&#xff1f;三、如何安装Requests库&#xff1f;四、Requests库的五个简单函数使用方法1. GET请求2. POST请求3. PUT请求4. DELET…

《Linux从小白到高手》综合应用篇:深入详解Linux swap及其调整优化

1. 引言&#xff1a; Swap是存储设备上的一块空间&#xff08;分区&#xff09;&#xff0c;操作系统可以在这里暂存一些内存里放不下的东西。这从某种程度上相当于增加了服务器的可用内存。虽然从swap读写比内存慢&#xff0c;但总比没有好&#xff0c;算是内存不足时一种比较…

SpringMVC学习笔记(一)

一、SpringMVC的基本概念 &#xff08;一&#xff09;三层架构和MVC 1、三层架构概述 我们的开发架构一般都是基于两种形式&#xff0c;一种是 C/S 架构&#xff0c;也就是客户端/服务器&#xff0c;另一种是 B/S 架构&#xff0c;也就是浏览器服务器。在 JavaEE 开发中&…

小面馆叫号取餐流程 佳易王面馆米线店点餐叫号管理系统操作教程

一、概述 【软件资源文件下载在文章最后】 小面馆叫号取餐流程 佳易王面馆米线店点餐叫号管理系统操作教程 点餐软件以其实用的功能和简便的操作&#xff0c;为小型餐饮店提供了高效的点餐管理解决方案&#xff0c;提高了工作效率和服务质量 ‌点餐管理‌&#xff1a;支持电…

【.NET 8 实战--孢子记账--从单体到微服务】--简易权限--角色可访问接口管理

咱们继续来编写孢子记账的简易权限&#xff0c;这篇文章中我们将编写角色可访问接口的管理API&#xff0c;同样我不会把完整的代码全都列出来&#xff0c;只会列出部分代码&#xff0c;其余代码我希望大家能自己手动编写&#xff0c;然后对比项目代码。废话不多说&#xff0c;开…

Linux上Python使用MySQLdb包连接MySQL5.7和MySQL8的问题

在一台安装有MySQL8的Linux上用MySQLdb包连接MySQL5.7&#xff0c;连接参数中加上ssl_mode‘DISABLED’,能正常连接&#xff1b;不加ssl_mode参数&#xff0c;会报 而在连接MySQL8时加不加ssl_mode都能正常连接&#xff0c;但在使用过程&#xff0c;加了ssl_mode参数&#xff…

列表(list)

一、前言 本次博客主要讲解 list 容器的基本操作、常用接口做一个系统的整理&#xff0c;结合具体案例熟悉自定义内部排序方法的使用。如有任何错误&#xff0c;欢迎在评论区指出&#xff0c;我会积极改正。 二、什么是list list是C的一个序列容器&#xff0c;插入和删除元素…

spring使用xml文件整合事务+druid+mybatis

1.事务 事务&#xff08;Transaction&#xff09;是数据库管理系统中的一个重要概念&#xff0c;它表示一组不可分割的操作序列&#xff0c;这些操作要么全部执行成功&#xff0c;要么全部不执行&#xff0c;以确保数据库从一个一致性状态转换到另一个一致性状态。事务具有以下…

大语言模型LLM综述

一、LM主要发展阶段 1.1、统计语言模型SLM 基于统计学习方法&#xff0c;基本思想是基于马尔可夫假设HMM建立词概率预测模型。如n-gram语言模型 1.2、神经语言模型NLM 基于神经网络来做词的分布式表示。如word2vec模型 1.3、 预训练语言模型PLM 预训练一个网络模型来做词表…

【Jenkins实战】Windows安装服务启动失败

写此篇短文&#xff0c;望告诫后人。 如果你之前装过Jenkins&#xff0c;出于换域账号/本地帐号的原因想重新安装&#xff0c;你大概率会遇上一次Jenkins服务启动失败提示&#xff1a; Jenkins failed to start - Verify that you have sufficient privileges to start system…

Linux kernel 堆溢出利用方法(二)

前言 本文我们通过我们的老朋友heap_bof来讲解Linux kernel中off-by-null的利用手法。在通过讲解另一道相对来说比较困难的kernel off-by-null docker escape来深入了解这种漏洞的利用手法。&#xff08;没了解过docker逃逸的朋友也可以看懂&#xff0c;毕竟有了root权限后&a…

微服务(一)

目录 1.认识微服务 1.1.单体架构 1.2.微服务 1.3.SpringCloud SpringCloud版本 SpringBoot版本 2.服务注册和发现 2.1.注册中心原理 2.2.Nacos注册中心 2.3.服务注册 2.3.1.添加依赖 2.3.2.配置Nacos 2.4.服务发现 2.4.1.引入依赖 2.4.2.配置Nacos地址 2.4.3.发…