目录
本文结论
背景分析
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
}
}
}
最后,通过打印喇叭叫的次数和观察三色灯闪烁的情况是是否出现之前的问题,问题解决。