Files
secs2-SJ/usr/protocol/proto_modbus_tcp_master.c
2026-09-09 14:54:40 +08:00

421 lines
19 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "proto_modbus_tcp_master.h"
#include "bsp_W5500_1.h"
#include "string.h"
/* 响应超时时间(单位:毫秒) */
#define RESP_TIMEOUT_MS 500
/* 静态函数声明 */
static void modbus_tcp_send_read(u16 start, u16 num);
static void modbus_tcp_parse_response(bsp_W5500_Class_t *pClass, u8 *data, u16 len);
static void modbus_tcp_init_local(void);
static void modbus_tcp_task(void);
static void modbus_tcp_write_control(u16 bit_mask);
char *proto_modbus_master_alarm[24] = {
"Alarm", /* 40002.00 一级报警 */
"HH Inlet1 Pressure", /* 40002.01 进口压力1高高报警 */
"HH Inlet2 Pressure", /* 40002.02 进口压力2高高报警 */
"HH Inlet3 Pressure", /* 40002.03 进口压力3高高报警 */
"HH Inlet4 Pressure", /* 40002.04 备口压力1高高报警 */
"HH Inlet5 Pressure", /* 40002.05 备口压力2高高报警 */
"HH Inlet6 Pressure", /* 40002.06 备口压力3高高报警 */
"HH Reaction Chamber Temp.", /* 40002.07 反应腔温度高高报警 */
"Drain Pump Alarm", /* 40003.00 排水泵异常报警 */
"LL PCW Flow", /* 40003.01 PCW流量低低报警 */
"LL Liquid Level", /* 40003.02 低低液位报警 */
"HH Liquid Level", /* 40003.03 高高液位报警 */
"EMO", /* 40003.04 EMO */
"HH Exhuast Temp.", /* 40003.05 排气温度高高报警 */
"L General Exhaust Pressure", /* 40003.06 一般排压力低报警 */
"O3 HH", /* 40003.07 O3浓度高高报警 */
"H Reaction Chamber Temp. Delay", /* 40003.08 反应腔温度高延时报警 */
"Reaction Chamber Temp.Out", /* 40003.09 反应腔顶盖超温报警 */
"HH Water Tank Temp.", /* 40003.10 水箱温度高高报警 */
"Water Leakage", /* 40003.11 水箱漏液报警 */
"Containment Leakage", /* 40003.12 围堰漏液报警 */
"Smoke", /* 40003.13 烟雾报警 */
"Heat Source Failure", /* 40003.14 热源失效报警 */
"Circulation Pump Alarm" /* 40003.15 循环水泵异常报警 */
};
char *proto_modbus_master_warning[64] = {
"Inlet1 Pressure Broken", /* 40004.00 进口压力1断线警告 */
"Inlet2 Pressure Broken", /* 40004.01 进口压力2断线警告 */
"Inlet3 Pressure Broken", /* 40004.02 进口压力3断线警告 */
"Inlet4 Pressure Broken", /* 40004.03 备口压力1断线警告 */
"Inlet5 Pressure Broken", /* 40004.04 备口压力2断线警告 */
"Inlet6 Pressure Broken", /* 40004.05 备口压力3断线警告 */
"Outlet Pressure Broken", /* 40004.06 出口压力断线警告 */
"Inlet Valve_1 Open Limit Warning", /* 40004.07 特气进口阀1开限位警告 */
"Warning", /* 40004.08 二级警告 */
"H Inlet1 Pressure", /* 40004.09 进口压力1高警告 */
"H Inlet2 Pressure", /* 40004.10 进口压力2高警告 */
"H Inlet3 Pressure", /* 40004.11 进口压力3高警告 */
"H Inlet4 Pressure", /* 40004.12 备口压力1高警告 */
"H Inlet5 Pressure", /* 40004.13 备口压力2高警告 */
"H Inlet6 Pressure", /* 40004.14 备口压力3高警告 */
"H Outlet Pressure", /* 40004.15 出口压力高警告 */
"Inlet Valve_5 Close Limit Warning", /* 40005.00 特气进口阀4关限位警告 */
"Inlet Valve_6 Open Limit Warning", /* 40005.01 特气进口阀5开限位警告 */
"Inlet Valve_6 Close Limit Warning", /* 40005.02 特气进口阀6关限位警告 */
"H water tank temp.", /* 40005.03 水箱温度高警告 */
"H exhuast temp.", /* 40005.04 排气温度高警告 */
"H reaction chamber temp.", /* 40005.05 反应腔温度高警告 */
"L reaction chamber temp.", /* 40005.06 反应腔温度低警告 */
"Water tank temp. Broken", /* 40005.07 水箱温度断线警告 */
"Inlet Valve_1 Close Limit Warning", /* 40005.08 特气进口阀2开限位警告 */
"Inlet Valve_2 Open Limit Warning", /* 40005.09 特气进口阀3开限位警告 */
"Inlet Valve_2 Close Limit Warning", /* 40005.10 特气进口阀4开限位警告 */
"Inlet Valve_3 Open Limit Warning", /* 40005.11 特气进口阀5开限位警告 */
"Inlet Valve_3 Close Limit Warning", /* 40005.12 特气进口阀6开限位警告 */
"Inlet Valve_4 Open Limit Warning", /* 40005.13 特气进口阀1关限位警告 */
"Inlet Valve_4 Close Limit Warning", /* 40005.14 特气进口阀2关限位警告 */
"Inlet Valve_5 Open Limit Warning", /* 40005.15 特气进口阀3关限位警告 */
"PCW valve closed with flow", /* 40006.00 PCW阀关有流量警告 */
"LSW valve closed with flow", /* 40006.01 CW阀关有流量警告 */
"L torch Current", /* 40006.02 等离子电流低警告 */
"H torch Current", /* 40006.03 等离子电流高警告 */
"L torch Voltage", /* 40006.04 等离子电压低警告 */
"H torch Voltage", /* 40006.05 等离子电压高警告 */
"Torch current sensor Broken", /* 40006.06 等离子电流断线警告 */
"Torch voltage sensor Broken", /* 40006.07 等离子电压断线警告 */
"Exhuast temp. Broken", /* 40006.08 排气温度断线警告 */
"Reaction chamber temp. Broken", /* 40006.09 反应腔温度断线警告 */
"L CDA pressure", /* 40006.10 CDA压力低警告 */
"L N2 pressure", /* 40006.11 N2压力低警告 */
"L LSW flow", /* 40006.12 CW流量低警告 */
"L PCW flow", /* 40006.13 PCW流量低警告 */
"L cathode nitrogen flow", /* 40006.14 等离子N2流量低警告 */
"H cathode nitrogen flow", /* 40006.15 等离子N2流量高警告 */
"Inlet_5 H belt jacket temp.", /* 40007.00 备口2加热带温度高警告 */
"Inlet_6 H belt jacket temp.", /* 40007.01 备口3加热带温度高警告 */
"",
"",
"",
"",
"",
"",
"H Cabinet exhuast Temp.", /* 40007.08 一般排温度高警告 */
"Cabinet exhuast Temp. Broken", /* 40007.09 一般排温度断线警告 */
"Cabinet exhuast pressure Broken", /* 40007.10 一般排压力断线警告 */
"Cabinet door open", /* 40007.11 机箱门打开警告 */
"Inlet_1 H belt jacket temp.", /* 40007.12 入口1加热带温度高警告 */
"Inlet_2 H belt jacket temp.", /* 40007.13 入口2加热带温度高警告 */
"Inlet_3 H belt jacket temp.", /* 40007.14 入口3加热带温度高警告 */
"Inlet_4 H belt jacket temp." /* 40007.15 备口1加热带温度高警告 */
};
/* 全局实例对象 */
proto_modbus_t modbus_tcp = {
.req_state = 0,
.last_req_time = 0,
.retry_cnt = 0,
.transaction_id = 0,
.no_response_count = 0,
.data = {0},
.init = modbus_tcp_init_local,
.task = modbus_tcp_task,
.write = modbus_tcp_write_control,
};
/* 内部指针简化访问 */
static proto_modbus_t *pModbusTcp = &modbus_tcp;
/*******************************************************************************
* 函数名 : modbus_tcp_init_local
* 描述 : 初始化 Modbus TCP 主机:绑定接收回调,重置状态变量
* 输入 : 无
* 输出 : 无
* 返回值 : 无
* 说明 : 该函数通过 modbus_tcp.init 指针调用
*******************************************************************************/
static void modbus_tcp_init_local(void) {
/* 将 W5500 第0个Socket的接收回调绑定到本模块的解析函数 */
W5500_1.W5500_Class[0].Rx_DataAnalysis = modbus_tcp_parse_response;
pModbusTcp->transaction_id = 0;
pModbusTcp->req_state = 0;
pModbusTcp->last_req_time = 0;
pModbusTcp->retry_cnt = 0;
pModbusTcp->write_pending = 0;
memset(&pModbusTcp->data, 0, sizeof(pModbusTcp->data));/*清除设备数据*/
memset(&pModbusTcp->alarm_ctx, 0, sizeof(pModbusTcp->alarm_ctx));/*清除时间*/
memset(&pModbusTcp->status_ctx,0,sizeof(pModbusTcp->status_ctx));
}
/*******************************************************************************
* 函数名 : modbus_tcp_send_read
* 描述 : 发送 Modbus TCP 读保持寄存器请求(功能码0x03)
* 输入 : start : 起始寄存器地址(16位)
* num : 要读取的寄存器数量
* 输出 : 无
* 返回值 : 无
* 说明 : 自动递增事务ID,通过 W5500 Socket 发送
*******************************************************************************/
static void modbus_tcp_send_read(u16 start, u16 num) {
u8 req[12];
pModbusTcp->transaction_id++;
req[0] = (pModbusTcp->transaction_id >> 8) & 0xFF; /* 事务ID高字节 */
req[1] = pModbusTcp->transaction_id & 0xFF; /* 事务ID低字节 */
req[2] = 0x00; req[3] = 0x00; /* 协议标识符,固定0x0000 */
req[4] = 0x00; req[5] = 0x06; /* 后续字节数(固定6) */
req[6] = MODBUS_TCP_DEFAULT_UNIT_ID; /* 单元标识符 */
req[7] = 0x03; /* 功能码:读保持寄存器 */
req[8] = (start >> 8) & 0xFF; /* 起始地址高字节 */
req[9] = start & 0xFF; /* 起始地址低字节 */
req[10] = (num >> 8) & 0xFF; /* 寄存器数量高字节 */
req[11] = num & 0xFF; /* 寄存器数量低字节 */
/* 通过 W5500 的第0个Socket发送 */
W5500_1.Socket_Send(&W5500_1.W5500_Class[0], req, 12);
}
/*******************************************************************************
* 函数名 : modbus_tcp_send_write
* 描述 : 发送 Modbus TCP 写寄存器请求
* 输入 : start : 寄存器地址(16位)
* n
* 输出 : 无
* 返回值 : 无
* 说明 : 自动递增事务ID,通过 W5500 Socket 发送
*******************************************************************************/
static void modbus_tcp_send_write(u16 reg_addr, u16 value) {
u8 req[12];
pModbusTcp->transaction_id++;
pModbusTcp->write_tid = pModbusTcp->transaction_id;
pModbusTcp->write_pending = 2;
pModbusTcp->write_result = 0;
req[0] = (pModbusTcp->transaction_id >> 8) & 0xFF;
req[1] = pModbusTcp->transaction_id & 0xFF;
req[2] = 0x00; req[3] = 0x00; /* 协议标识符 */
req[4] = 0x00; req[5] = 0x06; /* 后续字节数(固定6) */
req[6] = MODBUS_TCP_DEFAULT_UNIT_ID;
req[7] = 0x06; /* 功能码:写单个寄存器 */
req[8] = (reg_addr >> 8) & 0xFF;
req[9] = reg_addr & 0xFF;
req[10] = (value >> 8) & 0xFF;
req[11] = value & 0xFF;
W5500_1.Socket_Send(&W5500_1.W5500_Class[0], req, 12);
}
/*******************************************************************************
* 函数名 : modbus_tcp_parse_response
* 描述 : 解析 W5500 收到的 Modbus TCP 响应,根据当前请求状态存储数据
* 输入 : pClass - W5500 端口类对象指针(未使用,匹配回调原型)
* data - 接收到的数据缓冲区指针
* len - 数据长度
* 输出 : 无
* 返回值 : 无
* 说明 : 该函数由 W5500 的 Rx_DataAnalysis 回调调用
*******************************************************************************/
static void modbus_tcp_parse_response(bsp_W5500_Class_t *pClass, u8 *data, u16 len) {
(void)pClass; /* 避免未使用参数警告 */
/* 最小长度检查:MBAP头(7) + 功能码(1) + 字节数(1) = 9 */
if (len < 9) return;
u16 tid = (data[0] << 8) | data[1];
if (tid != pModbusTcp->transaction_id) return; /* 事务ID不匹配则丢弃 */
if (data[2] != 0 || data[3] != 0) return; /* 协议标识符必须为0 */
if (data[6] != MODBUS_TCP_DEFAULT_UNIT_ID) return;
if (data[7] == 0x83) { /* 异常响应(功能码+0x80) */
pModbusTcp->req_state = 0; /* 放弃当前轮次 */
return;
}
/* ---- 处理写单个寄存器响应 (0x06) ---- */
if (data[7] == 0x06) {
if (tid == pModbusTcp->write_tid) {
pModbusTcp->write_pending = 1;
pModbusTcp->write_result = 1; /* 成功 */
}
return;
}
/* 异常响应(写操作异常) */
if (data[7] == 0x86) { /* 0x06 + 0x80 */
if (tid == pModbusTcp->write_tid) {
pModbusTcp->write_pending = 3;
pModbusTcp->write_result = 0; /* 失败 */
}
return;
}
if (data[7] != 0x03) return; /* 非预期功能码 */
u8 byte_count = data[8];
if (len < (9 + byte_count)) return;
/* 根据当前请求状态解析数据 */
switch (pModbusTcp->req_state) {
case 1: { /* 设备状态寄存器(40000~40007) */
if (byte_count == 16) { /* 8个寄存器 × 2字节 */
u8 *p = &data[9];
for (int i = 0; i < 8; i++) {
pModbusTcp->data.reg_status[i] = (p[0] << 8) | p[1];
p += 2;
}
pModbusTcp->req_state = 2; /* 进入下一分组 */
pModbusTcp->last_req_time = 0; /* 立即发送下一请求 */
pModbusTcp->retry_cnt = 0;
pModbusTcp->no_response_count = 0;
pModbusTcp->alarm_ctx.offline = 0;
}
} break;
case 2: { /* 浮点仪表寄存器(40050~40100) */
if (byte_count == 104) { /* 26个浮点 × 4字节 = 104字节 */
u8 *p = &data[9];
for (int i = 0; i < 26; i++) {
u32 tmp = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
pModbusTcp->data.float_values[i] = *((float*)&tmp);
p += 4;
}
pModbusTcp->req_state = 3;
pModbusTcp->last_req_time = 0;
pModbusTcp->retry_cnt = 0;
pModbusTcp->no_response_count = 0;
pModbusTcp->alarm_ctx.offline = 0;
}
} break;
case 3: { /* 整数仪表寄存器(40250~40252) */
if (byte_count == 6) { /* 3个寄存器 × 2字节 */
u8 *p = &data[9];
for (int i = 0; i < 3; i++) {
pModbusTcp->data.int_values[i] = (p[0] << 8) | p[1];
p += 2;
}
pModbusTcp->req_state = 4;
pModbusTcp->last_req_time = HAL_GetTick(); /* 记录完成时间*/
pModbusTcp->retry_cnt = 0;
pModbusTcp->no_response_count = 0;
pModbusTcp->alarm_ctx.offline = 0;
}
} break;
case 4: /*20253~20260代表sn*/
{
if(byte_count == 20) /*10个寄存器,20个字节*/
{
u8 *p = &data[9];
u8 len = p[0];
if (len > 20) len = 20;
if (len > 0) {
memcpy(pModbusTcp->data.sn_str, &p[1], len);
pModbusTcp->data.sn_str[len] = '\0';
} else {
pModbusTcp->data.sn_str[0] = '\0';
}
// 存储原始寄存器值
for (int i = 0; i < 10; i++) {
pModbusTcp->data.sn[i] = (p[1 + i*2] << 8) | p[2 + i*2];
}
pModbusTcp->req_state = 0; /* 全部完成,回到空闲 */
pModbusTcp->last_req_time = HAL_GetTick();
pModbusTcp->retry_cnt = 0;
pModbusTcp->no_response_count = 0;
pModbusTcp->alarm_ctx.offline = 0;
}
}
default:
break;
}
}
/*******************************************************************************
* 函数名 : modbus_tcp_task
* 描述 : Modbus TCP 主机周期任务:由外部调度器每500ms调用一次。
* 任务内部检查连接状态,空闲时立即发起第一个请求;
* 等待响应时检查超时并重试或跳转至下一分组。
* 输入 : 无
* 输出 : 无
* 返回值 : 无
* 说明 : 该函数由 modbus_tcp.task 指针调用,外部调度周期决定轮询间隔
*******************************************************************************/
static void modbus_tcp_task(void) {
/* 检查 W5500 第0个Socket的连接状态 */
if (!(W5500_1.W5500_Class[0].Run_State & BSP_W5500_PORT_RUN_STATE_CONN)) {
pModbusTcp->req_state = 0; /* 未连接则复位状态 */
pModbusTcp->alarm_ctx.offline = 1;
return;
}
u32 now = HAL_GetTick();
/* 空闲状态:直接发起第一个请求(设备状态) */
if (pModbusTcp->req_state == 0) {
modbus_tcp_send_read(MODBUS_REG_DEVICE_STATUS_START,
MODBUS_REG_DEVICE_STATUS_NUM);
pModbusTcp->req_state = 1;
pModbusTcp->last_req_time = now;
pModbusTcp->retry_cnt = 0;
return;
}
/* 等待响应状态:若超时则重试或跳过当前分组 */
if (now - pModbusTcp->last_req_time >= RESP_TIMEOUT_MS) {
pModbusTcp->retry_cnt++;
if (pModbusTcp->retry_cnt < 4) {
/* 重发当前分组的请求 */
u16 start, num;
switch (pModbusTcp->req_state) {
case 1: start = MODBUS_REG_DEVICE_STATUS_START; num = MODBUS_REG_DEVICE_STATUS_NUM; break;
case 2: start = MODBUS_REG_INSTRUMENT_FLOAT_START; num = MODBUS_REG_INSTRUMENT_FLOAT_NUM; break;
case 3: start = MODBUS_REG_INSTRUMENT_INT_START; num = MODBUS_REG_INSTRUMENT_INT_NUM; break;
case 4: start = MODBUS_REG_INSTRUMENT_SN_START; num = MODBUS_REG_INSTRUMENT_SN_NUM; break;
default: return;
}
modbus_tcp_send_read(start, num);
pModbusTcp->last_req_time = now;
} else {
/* 重试次数耗尽,强制进入下一分组或回到空闲 */
pModbusTcp->retry_cnt = 0;
pModbusTcp->no_response_count++;
if (pModbusTcp->no_response_count >= 3) {
pModbusTcp->alarm_ctx.offline = 1;
}else
{
pModbusTcp->alarm_ctx.offline = 0;
// pModbusTcp->no_response_count = 0;
}
if (pModbusTcp->req_state < 4) {
pModbusTcp->req_state++; /* 跳至下一分组 */
} else {
pModbusTcp->req_state = 0; /* 所有分组完成,回到空闲 */
}
/* 将 last_req_time 置0,使得下一次调用时会立即进入超时分支,发送新分组 */
pModbusTcp->last_req_time = 0;
}
}
}
/*写入单个寄存器*/
static void modbus_tcp_write_control(u16 bit_mask) {
if (!(W5500_1.W5500_Class[0].Run_State & BSP_W5500_PORT_RUN_STATE_CONN)) {
return; /* 未连接不操作 */
}
// 读取当前寄存器值
u16 current_val = pModbusTcp->data.reg_status[7];
// 清除所有控制位 (bit3~bit6)
u16 clear_mask = CONTROL_BIT_START | CONTROL_BIT_STOP |
CONTROL_BIT_IDLE_OPEN | CONTROL_BIT_IDLE_CLOSE;
u16 new_val = current_val & ~clear_mask;
// 设置目标位
new_val |= bit_mask;
/* 发送写请求 */
modbus_tcp_send_write(MODBUS_REG_CONTROL_ADDR, new_val);
}