STM32从零到量产开发:四路继电器工业控制模块开发-Modbus RTU 从站协议栈设计说明
1. 核心功能概述
该代码实现了一个基于 STM32 的 Modbus RTU 从站协议栈,主要面向四通道继电器控制模块(P02设备)。除了标准的 Modbus 协议支持外,还针对量产需求增加了故障安全、诊断计数和设备信息上报等扩展功能。
1.1 支持的 Modbus 功能码
| 功能码 (Hex) |
名称 |
描述 |
| 0x01 |
Read Coils |
读取继电器线圈状态 |
| 0x02 |
Read Discrete Inputs |
读取数字输入 (DI) 状态 |
| 0x03 |
Read Holding Regs |
读取保持寄存器(设备配置参数) |
| 0x04 |
Read Input Regs |
读取输入寄存器(运行状态与诊断信息) |
| 0x05 |
Write Single Coil |
控制单个继电器吸合/断开 |
| 0x06 |
Write Single Reg |
修改单个保持寄存器参数 |
| 0x0F |
Write Multiple Coils |
批量控制多个继电器 |
| 0x10 |
Write Multiple Regs |
批量修改多个保持寄存器参数 |
| 0x11 |
Report Slave ID |
上报从站标识及运行状态 |
1.2 异常码支持
| 异常码 |
含义 |
触发场景 |
| 0x01 |
非法功能码 |
请求了未支持的功能码 |
| 0x02 |
非法数据地址 |
请求的寄存器/线圈地址越界 |
| 0x03 |
非法数据值 |
写入的数据值不合法(如继电器控制值非 0xFF00/0x0000) |
| 0x06 |
设备忙 |
(预留)设备处于忙碌状态 |
1.3 扩展功能
- 通信故障安全 (#1):当连续
fail_safe_sec 秒未收到有效 Modbus 帧时,自动将所有继电器切换至安全态(断开),防止上位机死机或通信断线导致设备失控。收到新有效帧后自动恢复。
- 诊断计数 (#3):实时统计异常响应次数、各继电器动作次数及寿命告警位图,用于通信质量评估与设备维护。
- 设备信息 (#4):通过 FC17 及输入寄存器暴露设备序列号(SN)、固件版本、设备类型码及累计运行小时数。
2. API 接口说明
2.1 核心控制接口
void Modbus_Init(void):初始化 Modbus 从站。加载 Flash 参数,配置 RS485 波特率,并重置所有统计计数与状态变量。
void Modbus_Poll(void):Modbus 主任务处理。在主循环中高频调用,负责检测 RS485 帧接收、CRC 校验、地址匹配、功能码分发执行、响应发送及参数落盘。
void Modbus_CheckFailSafe(void):通信故障安全检测。在主循环中高频调用,监测通信超时并触发安全保护机制。
2.2 状态查询接口
uint16_t Modbus_GetFrameCount(void):获取累计有效帧计数。
uint16_t Modbus_GetCRCErrorCount(void):获取累计 CRC 校验错误计数。
uint16_t Modbus_GetExceptionCount(void):获取异常响应计数(仅统计非广播帧)。
uint8_t Modbus_IsFailSafeActive(void):查询当前是否处于故障安全态(1=已触发,0=正常)。
3. 寄存器映射表
3.1 线圈 (Coils) - 地址 0x0000 ~ 0x0003
| 地址 |
含义 |
读写属性 |
| 0x0000 |
RELAY1 (继电器1) |
读/写 |
| 0x0001 |
RELAY2 (继电器2) |
读/写 |
| 0x0002 |
RELAY3 (继电器3) |
读/写 |
| 0x0003 |
RELAY4 (继电器4) |
读/写 |
3.2 离散输入 (Discrete Inputs) - 地址 0x0000 ~ 0x0003
| 地址 |
含义 |
读写属性 |
| 0x0000 |
DI1 (数字输入1) |
只读 |
| 0x0001 |
DI2 (数字输入2) |
只读 |
| 0x0002 |
DI3 (数字输入3) |
只读 |
| 0x0003 |
DI4 (数字输入4) |
只读 |
3.3 输入寄存器 (Input Registers) - 地址 0x0000 ~ 0x000F
| 地址 |
宏定义 |
含义 |
备注 |
| 0x0000 |
IR_ADDR_UPTIME |
运行秒数 |
16位回绕 |
| 0x0001 |
IR_ADDR_RESETCOUNT |
累计复位次数 |
- |
| 0x0002 |
IR_ADDR_FRAMECOUNT |
有效帧计数 |
- |
| 0x0003 |
IR_ADDR_CRCERROR |
CRC 错误计数 |
- |
| 0x0004 |
IR_ADDR_EXCEPCOUNT |
异常响应计数 |
#3 诊断 |
| 0x0005 |
IR_ADDR_RUNHOURS |
累计运行小时 |
#4 设备信息 |
| 0x0006 |
IR_ADDR_RELAY1_ACT |
继电器1动作次数 |
#3 诊断 |
| 0x0007 |
IR_ADDR_RELAY2_ACT |
继电器2动作次数 |
#3 诊断 |
| 0x0008 |
IR_ADDR_RELAY3_ACT |
继电器3动作次数 |
#3 诊断 |
| 0x0009 |
IR_ADDR_RELAY4_ACT |
继电器4动作次数 |
#3 诊断 |
| 0x000A |
IR_ADDR_LIFE_ALARM |
寿命告警位图 |
bit0~3 对应继电器1~4 |
| 0x000B |
IR_ADDR_FW_VER |
固件版本 |
格式: 0xMMmm |
| 0x000C |
IR_ADDR_DEV_TYPE |
设备类型码 |
0x0002 = P02 |
| 0x000D |
IR_ADDR_SN_LOW |
序列号低16位 |
取自芯片 UID |
| 0x000E |
IR_ADDR_SN_HIGH |
序列号高16位 |
取自芯片 UID |
| 0x000F |
IR_ADDR_FAILSAFE |
故障安全态位图 |
bit0=使能, bit8=当前触发 |
3.4 保持寄存器 (Holding Registers) - 地址 0x0000 ~ 0x0004
| 地址 |
宏定义 |
含义 |
备注 |
| 0x0000 |
HR_ADDR_SLAVE |
从站地址 |
写入后需重启生效 |
| 0x0001 |
HR_ADDR_BAUD |
波特率索引 |
写入后回复帧结束自动切换 |
| 0x0002 |
HR_ADDR_LINK |
链路使能 |
0=静默模式(不回复), 1=正常 |
| 0x0003 |
HR_ADDR_FAILSAFE_EN |
故障安全使能 |
#1 扩展: 0/1 |
| 0x0004 |
HR_ADDR_FAILSAFE_SEC |
故障安全超时(秒) |
#1 扩展: 0=禁用 |
4. 关键数据结构与逻辑流程
4.1 关键数据结构
param_t (Flash 参数结构):由 FlashParam_Get() 获取,包含 slave_addr、baud_index、link_enable、fail_safe_en、fail_safe_sec 等持久化配置。
- 静态状态变量:
s_frame_count / s_crc_err_count / s_ex_resp_count:协议统计计数器。
s_last_valid_tick / s_fail_safe_active:故障安全机制的时间戳与状态标志。
s_pending_*:参数变更缓存。写入保持寄存器时仅更新 pending 变量,在 Modbus_Poll 末尾统一调用 FlashParam_Save() 落盘,避免频繁擦写 Flash。
- 收发缓冲区:
s_tx_buf (响应组帧缓冲) 与 s_rx_frame (接收帧缓存)。
4.2 核心逻辑流程
- 初始化阶段:调用
Modbus_Init(),从 Flash 读取波特率并配置 RS485 硬件,将当前系统时间 (HAL_GetTick()) 设为 s_last_valid_tick 以防止上电瞬间误触发故障安全。
- 主循环轮询 (
Modbus_Poll):
- 接收与校验:检测 RS485 帧就绪,提取数据并进行 CRC16 校验。校验失败则累加
s_crc_err_count 并丢弃。
- 地址匹配:判断是否为广播地址 (0x00) 或本机地址,不匹配则静默丢弃。
- 保活与指示:有效帧刷新
s_last_valid_tick,触发通信 LED 脉冲 (BSP_CommPulse)。
- 功能码分发:通过
switch(fc) 路由至对应的 MB_Handle* 处理函数。处理函数内部完成数据解析、BSP 层调用及响应帧组装。
- 响应发送:若非广播且链路使能,调用
BSP_RS485_Send 发送响应。
- 安全态恢复:响应发送完成后,若当前处于故障安全态,则清除该状态并熄灭故障 LED,确保本次响应能如实反映触发状态。
- 参数落盘与应用:检查
s_pending_* 变量,若有变更则写入 Flash;若波特率变更,在回复发送完毕后重新配置 RS485 波特率。
- 故障安全检测 (
Modbus_CheckFailSafe):
- 检查故障安全是否使能及超时时间是否大于 0。
- 计算当前时间与
s_last_valid_tick 的差值,若超过设定阈值且尚未处于安全态,则调用 BSP_Relay_SetAll(0) 断开所有继电器,置位 s_fail_safe_active 并点亮故障 LED。
#ifndef __MODBUS_RTU_H
#define __MODBUS_RTU_H
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
#include "bsp_led.h"
#define MB_FC_READ_COILS 0x01u
#define MB_FC_READ_DISCRETE_INPUTS 0x02u
#define MB_FC_READ_HOLDING_REGS 0x03u
#define MB_FC_READ_INPUT_REGS 0x04u
#define MB_FC_WRITE_SINGLE_COIL 0x05u
#define MB_FC_WRITE_SINGLE_REG 0x06u
#define MB_FC_WRITE_MULTIPLE_COILS 0x0Fu
#define MB_FC_WRITE_MULTIPLE_REGS 0x10u
#define MB_FC_REPORT_SLAVE_ID 0x11u
#define MB_EX_ILLEGAL_FUNCTION 0x01u
#define MB_EX_ILLEGAL_DATA_ADDRESS 0x02u
#define MB_EX_ILLEGAL_DATA_VALUE 0x03u
#define MB_EX_SLAVE_DEVICE_BUSY 0x06u
#define MB_MAX_PDU_SIZE 253u
void Modbus_Init(void);
void Modbus_Poll(void);
uint16_t Modbus_GetFrameCount(void);
uint16_t Modbus_GetCRCErrorCount(void);
uint16_t Modbus_GetExceptionCount(void);
void Modbus_CheckFailSafe(void);
uint8_t Modbus_IsFailSafeActive(void);
#ifdef __cplusplus
}
#endif
#endif
#include "mid_mb_slave.h"
#include "bsp_rs485.h"
#include "bsp_relay.h"
#include "bsp_di.h"
#include "bsp_led.h"
#include "bsp_storage.h"
#include "app_main.h"
#include <string.h>
#define COIL_BASE_ADDR 0x0000u
#define COIL_COUNT RELAY_CH_NUM
#define DI_BASE_ADDR 0x0000u
#define DI_COUNT DI_CH_NUM
#define IR_BASE_ADDR 0x0000u
#define IR_COUNT 16u
#define IR_ADDR_UPTIME 0x0000u
#define IR_ADDR_RESETCOUNT 0x0001u
#define IR_ADDR_FRAMECOUNT 0x0002u
#define IR_ADDR_CRCERROR 0x0003u
#define IR_ADDR_EXCEPCOUNT 0x0004u
#define IR_ADDR_RUNHOURS 0x0005u
#define IR_ADDR_RELAY1_ACT 0x0006u
#define IR_ADDR_RELAY2_ACT 0x0007u
#define IR_ADDR_RELAY3_ACT 0x0008u
#define IR_ADDR_RELAY4_ACT 0x0009u
#define IR_ADDR_LIFE_ALARM 0x000Au
#define IR_ADDR_FW_VER 0x000Bu
#define IR_ADDR_DEV_TYPE 0x000Cu
#define IR_ADDR_SN_LOW 0x000Du
#define IR_ADDR_SN_HIGH 0x000Eu
#define IR_ADDR_FAILSAFE 0x000Fu
#define HR_BASE_ADDR 0x0000u
#define HR_COUNT 5u
#define HR_ADDR_SLAVE 0x0000u
#define HR_ADDR_BAUD 0x0001u
#define HR_ADDR_LINK 0x0002u
#define HR_ADDR_FAILSAFE_EN 0x0003u
#define HR_ADDR_FAILSAFE_SEC 0x0004u
#define MB_BROADCAST_ADDR 0x00u
#define FW_VERSION 0x0100u
#define DEVICE_TYPE 0x0002u
#define DEV_UID_BASE 0x1FFFF7E8u
static uint16_t s_frame_count = 0u;
static uint16_t s_crc_err_count = 0u;
static uint16_t s_ex_resp_count = 0u;
static uint32_t s_last_valid_tick = 0u;
static uint8_t s_fail_safe_active = 0u;
static uint8_t s_pending_slave = 0u;
static uint8_t s_pending_baud = 0xFFu;
static uint8_t s_pending_link = 0xFFu;
static uint8_t s_pending_fs_en = 0xFFu;
static uint8_t s_pending_fs_sec = 0xFFu;
static uint8_t s_need_apply_baud = 0u;
static uint8_t s_tx_buf[RS485_TX_BUF_SIZE];
static uint16_t s_tx_len = 0u;
static uint8_t s_rx_frame[RS485_RX_BUF_SIZE];
static uint16_t MB_CRC16(const uint8_t *data, uint16_t len)
{
uint16_t crc = 0xFFFFu;
uint16_t i, j;
for (i = 0u; i < len; i++)
{
crc ^= (uint16_t)data[i];
for (j = 0u; j < 8u; j++)
{
if (crc & 0x0001u)
{
crc = (uint16_t)((crc >> 1) ^ 0xA001u);
}
else
{
crc >>= 1;
}
}
}
return crc;
}
static void MB_AppendByte(uint8_t b)
{
if (s_tx_len < (RS485_TX_BUF_SIZE - 2u))
{
s_tx_buf[s_tx_len++] = b;
}
}
static void MB_AppendWord(uint16_t w)
{
MB_AppendByte((uint8_t)(w >> 8));
MB_AppendByte((uint8_t)(w & 0xFFu));
}
static void MB_FinalizeCRC(void)
{
uint16_t crc = MB_CRC16(s_tx_buf, s_tx_len);
MB_AppendByte((uint8_t)(crc & 0xFFu));
MB_AppendByte((uint8_t)((crc >> 8) & 0xFFu));
}
static void MB_BuildException(uint8_t fc, uint8_t slave_addr, uint8_t ex_code)
{
if (slave_addr != MB_BROADCAST_ADDR)
{
s_ex_resp_count++;
}
s_tx_buf[0] = slave_addr;
s_tx_buf[1] = (uint8_t)(fc | 0x80u);
s_tx_buf[2] = ex_code;
s_tx_len = 3u;
MB_FinalizeCRC();
}
static uint32_t MB_GetDeviceSN(void)
{
const uint32_t *uid = (const uint32_t *)DEV_UID_BASE;
return (uid[0] ^ uid[1] ^ uid[2]);
}
static void MB_HandleReadCoils(const uint8_t *frame, uint16_t len, uint8_t slave_addr)
{
uint16_t start, qty, i, byte_count;
if (len < 8u)
{
MB_BuildException(MB_FC_READ_COILS, slave_addr, MB_EX_ILLEGAL_DATA_VALUE);
return;
}
start = (uint16_t)(((uint16_t)frame[2] << 8) | frame[3]);
qty = (uint16_t)(((uint16_t)frame[4] << 8) | frame[5]);
if (qty < 1u || qty > COIL_COUNT ||
(start - COIL_BASE_ADDR) + qty > COIL_COUNT)
{
MB_BuildException(MB_FC_READ_COILS, slave_addr, MB_EX_ILLEGAL_DATA_ADDRESS);
return;
}
s_tx_buf[0] = slave_addr;
s_tx_buf[1] = MB_FC_READ_COILS;
byte_count = (qty + 7u) / 8u;
s_tx_buf[2] = (uint8_t)byte_count;
s_tx_len = 3u;
for (i = 0u; i < byte_count; i++)
{
s_tx_buf[3u + i] = 0u;
}
for (i = 0u; i < qty; i++)
{
uint8_t coil_idx = (uint8_t)(start - COIL_BASE_ADDR + i);
if (BSP_Relay_Get((uint8_t)(coil_idx + 1u)))
{
s_tx_buf[3u + (i / 8u)] |= (uint8_t)(1u << (i % 8u));
}
}
s_tx_len = 3u + byte_count;
MB_FinalizeCRC();
}
static void MB_HandleReadDiscreteInputs(const uint8_t *frame, uint16_t len, uint8_t slave_addr)
{
uint16_t start, qty, i, byte_count;
if (len < 8u)
{
MB_BuildException(MB_FC_READ_DISCRETE_INPUTS, slave_addr, MB_EX_ILLEGAL_DATA_VALUE);
return;
}
start = (uint16_t)(((uint16_t)frame[2] << 8) | frame[3]);
qty = (uint16_t)(((uint16_t)frame[4] << 8) | frame[5]);
if (qty < 1u || qty > DI_COUNT ||
(start - DI_BASE_ADDR) + qty > DI_COUNT)
{
MB_BuildException(MB_FC_READ_DISCRETE_INPUTS, slave_addr, MB_EX_ILLEGAL_DATA_ADDRESS);
return;
}
s_tx_buf[0] = slave_addr;
s_tx_buf[1] = MB_FC_READ_DISCRETE_INPUTS;
byte_count = (qty + 7u) / 8u;
s_tx_buf[2] = (uint8_t)byte_count;
s_tx_len = 3u;
for (i = 0u; i < byte_count; i++)
{
s_tx_buf[3u + i] = 0u;
}
for (i = 0u; i < qty; i++)
{
uint8_t di_idx = (uint8_t)(start - DI_BASE_ADDR + i);
if (BSP_DI_Get((uint8_t)(di_idx + 1u)))
{
s_tx_buf[3u + (i / 8u)] |= (uint8_t)(1u << (i % 8u));
}
}
s_tx_len = 3u + byte_count;
MB_FinalizeCRC();
}
static void MB_HandleReadHoldingRegs(const uint8_t *frame, uint16_t len, uint8_t slave_addr)
{
uint16_t start, qty, i;
const param_t *p;
if (len < 8u)
{
MB_BuildException(MB_FC_READ_HOLDING_REGS, slave_addr, MB_EX_ILLEGAL_DATA_VALUE);
return;
}
start = (uint16_t)(((uint16_t)frame[2] << 8) | frame[3]);
qty = (uint16_t)(((uint16_t)frame[4] << 8) | frame[5]);
if (qty < 1u || qty > HR_COUNT ||
(start - HR_BASE_ADDR) + qty > HR_COUNT)
{
MB_BuildException(MB_FC_READ_HOLDING_REGS, slave_addr, MB_EX_ILLEGAL_DATA_ADDRESS);
return;
}
p = FlashParam_Get();
s_tx_buf[0] = slave_addr;
s_tx_buf[1] = MB_FC_READ_HOLDING_REGS;
s_tx_buf[2] = (uint8_t)(qty * 2u);
s_tx_len = 3u;
for (i = 0u; i < qty; i++)
{
uint16_t reg_addr = (uint16_t)(start - HR_BASE_ADDR + i);
uint16_t value = 0u;
switch (reg_addr)
{
case HR_ADDR_SLAVE: value = p->slave_addr; break;
case HR_ADDR_BAUD: value = p->baud_index; break;
case HR_ADDR_LINK: value = p->link_enable; break;
case HR_ADDR_FAILSAFE_EN: value = p->fail_safe_en; break;
case HR_ADDR_FAILSAFE_SEC:value = p->fail_safe_sec; break;
default: break;
}
MB_AppendWord(value);
}
MB_FinalizeCRC();
}
static void MB_HandleReadInputRegs(const uint8_t *frame, uint16_t len, uint8_t slave_addr)
{
uint16_t start, qty, i;
if (len < 8u)
{
MB_BuildException(MB_FC_READ_INPUT_REGS, slave_addr, MB_EX_ILLEGAL_DATA_VALUE);
return;
}
start = (uint16_t)(((uint16_t)frame[2] << 8) | frame[3]);
qty = (uint16_t)(((uint16_t)frame[4] << 8) | frame[5]);
if (qty < 1u || qty > IR_COUNT ||
(start - IR_BASE_ADDR) + qty > IR_COUNT)
{
MB_BuildException(MB_FC_READ_INPUT_REGS, slave_addr, MB_EX_ILLEGAL_DATA_ADDRESS);
return;
}
s_tx_buf[0] = slave_addr;
s_tx_buf[1] = MB_FC_READ_INPUT_REGS;
s_tx_buf[2] = (uint8_t)(qty * 2u);
s_tx_len = 3u;
for (i = 0u; i < qty; i++)
{
uint16_t reg_addr = (uint16_t)(start - IR_BASE_ADDR + i);
uint16_t value = 0u;
switch (reg_addr)
{
case IR_ADDR_UPTIME: value = SystemApp_GetUptime(); break;
case IR_ADDR_RESETCOUNT: value = SystemApp_GetResetCount(); break;
case IR_ADDR_FRAMECOUNT: value = s_frame_count; break;
case IR_ADDR_CRCERROR: value = s_crc_err_count; break;
case IR_ADDR_EXCEPCOUNT: value = s_ex_resp_count; break;
case IR_ADDR_RUNHOURS: value = (uint16_t)SystemApp_GetRunHours(); break;
case IR_ADDR_RELAY1_ACT: value = (uint16_t)BSP_Relay_GetActionCount(1u); break;
case IR_ADDR_RELAY2_ACT: value = (uint16_t)BSP_Relay_GetActionCount(2u); break;
case IR_ADDR_RELAY3_ACT: value = (uint16_t)BSP_Relay_GetActionCount(3u); break;
case IR_ADDR_RELAY4_ACT: value = (uint16_t)BSP_Relay_GetActionCount(4u); break;
case IR_ADDR_LIFE_ALARM: value = (uint16_t)BSP_Relay_GetLifeAlarmMask(); break;
case IR_ADDR_FW_VER: value = FW_VERSION; break;
case IR_ADDR_DEV_TYPE: value = DEVICE_TYPE; break;
case IR_ADDR_SN_LOW: value = (uint16_t)(MB_GetDeviceSN() & 0xFFFFu); break;
case IR_ADDR_SN_HIGH: value = (uint16_t)((MB_GetDeviceSN() >> 16) & 0xFFFFu); break;
case IR_ADDR_FAILSAFE:
value = (uint16_t)((FlashParam_Get()->fail_safe_en ? 1u : 0u) |
(s_fail_safe_active ? 0x100u : 0u));
break;
default: break;
}
MB_AppendWord(value);
}
MB_FinalizeCRC();
}
static uint8_t MB_WriteSingleCoil(uint16_t addr, uint16_t value)
{
uint8_t ch;
if ((addr - COIL_BASE_ADDR) >= COIL_COUNT)
{
return MB_EX_ILLEGAL_DATA_ADDRESS;
}
if (value != 0xFF00u && value != 0x0000u)
{
return MB_EX_ILLEGAL_DATA_VALUE;
}
ch = (uint8_t)(addr - COIL_BASE_ADDR + 1u);
BSP_Relay_Set(ch, (value == 0xFF00u) ? RELAY_ON : RELAY_OFF);
return 0u;
}
static void MB_HandleWriteSingleCoil(const uint8_t *frame, uint16_t len, uint8_t slave_addr)
{
uint16_t addr, value;
uint8_t ex;
if (len < 8u)
{
MB_BuildException(MB_FC_WRITE_SINGLE_COIL, slave_addr, MB_EX_ILLEGAL_DATA_VALUE);
return;
}
addr = (uint16_t)(((uint16_t)frame[2] << 8) | frame[3]);
value = (uint16_t)(((uint16_t)frame[4] << 8) | frame[5]);
ex = MB_WriteSingleCoil(addr, value);
if (ex != 0u)
{
MB_BuildException(MB_FC_WRITE_SINGLE_COIL, slave_addr, ex);
return;
}
s_tx_buf[0] = slave_addr;
s_tx_buf[1] = MB_FC_WRITE_SINGLE_COIL;
s_tx_len = 2u;
MB_AppendWord(addr);
MB_AppendWord(value);
MB_FinalizeCRC();
}
static uint8_t MB_WriteSingleHoldingReg(uint16_t addr, uint16_t value)
{
if ((addr - HR_BASE_ADDR) >= HR_COUNT)
{
return MB_EX_ILLEGAL_DATA_ADDRESS;
}
switch (addr)
{
case HR_ADDR_SLAVE:
if (value < SLAVE_ADDR_MIN || value > SLAVE_ADDR_MAX)
{
return MB_EX_ILLEGAL_DATA_VALUE;
}
s_pending_slave = (uint8_t)value;
break;
case HR_ADDR_BAUD:
if (value > BAUD_INDEX_MAX)
{
return MB_EX_ILLEGAL_DATA_VALUE;
}
s_pending_baud = (uint8_t)value;
break;
case HR_ADDR_LINK:
if (value > 1u)
{
return MB_EX_ILLEGAL_DATA_VALUE;
}
s_pending_link = (uint8_t)value;
break;
case HR_ADDR_FAILSAFE_EN:
if (value > FAILSAFE_EN_MAX)
{
return MB_EX_ILLEGAL_DATA_VALUE;
}
s_pending_fs_en = (uint8_t)value;
break;
case HR_ADDR_FAILSAFE_SEC:
if (value > 255u)
{
return MB_EX_ILLEGAL_DATA_VALUE;
}
s_pending_fs_sec = (uint8_t)value;
break;
default:
return MB_EX_ILLEGAL_DATA_ADDRESS;
}
return 0u;
}
static void MB_HandleWriteSingleReg(const uint8_t *frame, uint16_t len, uint8_t slave_addr)
{
uint16_t addr, value;
uint8_t ex;
if (len < 8u)
{
MB_BuildException(MB_FC_WRITE_SINGLE_REG, slave_addr, MB_EX_ILLEGAL_DATA_VALUE);
return;
}
addr = (uint16_t)(((uint16_t)frame[2] << 8) | frame[3]);
value = (uint16_t)(((uint16_t)frame[4] << 8) | frame[5]);
ex = MB_WriteSingleHoldingReg(addr, value);
if (ex != 0u)
{
MB_BuildException(MB_FC_WRITE_SINGLE_REG, slave_addr, ex);
return;
}
s_tx_buf[0] = slave_addr;
s_tx_buf[1] = MB_FC_WRITE_SINGLE_REG;
s_tx_len = 2u;
MB_AppendWord(addr);
MB_AppendWord(value);
MB_FinalizeCRC();
}
static void MB_HandleWriteMultipleCoils(const uint8_t *frame, uint16_t len, uint8_t slave_addr)
{
uint16_t start, qty, byte_count, i;
if (len < 9u)
{
MB_BuildException(MB_FC_WRITE_MULTIPLE_COILS, slave_addr, MB_EX_ILLEGAL_DATA_VALUE);
return;
}
start = (uint16_t)(((uint16_t)frame[2] << 8) | frame[3]);
qty = (uint16_t)(((uint16_t)frame[4] << 8) | frame[5]);
byte_count = frame[6];
if (qty < 1u || qty > COIL_COUNT ||
(start - COIL_BASE_ADDR) + qty > COIL_COUNT)
{
MB_BuildException(MB_FC_WRITE_MULTIPLE_COILS, slave_addr, MB_EX_ILLEGAL_DATA_ADDRESS);
return;
}
if (byte_count != (uint16_t)((qty + 7u) / 8u))
{
MB_BuildException(MB_FC_WRITE_MULTIPLE_COILS, slave_addr, MB_EX_ILLEGAL_DATA_VALUE);
return;
}
if (len < (uint16_t)(7u + byte_count + 2u))
{
MB_BuildException(MB_FC_WRITE_MULTIPLE_COILS, slave_addr, MB_EX_ILLEGAL_DATA_VALUE);
return;
}
for (i = 0u; i < qty; i++)
{
uint8_t coil_idx = (uint8_t)(start - COIL_BASE_ADDR + i);
uint8_t bit_val = (uint8_t)((frame[7u + (i / 8u)] >> (i % 8u)) & 0x01u);
BSP_Relay_Set((uint8_t)(coil_idx + 1u), bit_val ? RELAY_ON : RELAY_OFF);
}
s_tx_buf[0] = slave_addr;
s_tx_buf[1] = MB_FC_WRITE_MULTIPLE_COILS;
s_tx_len = 2u;
MB_AppendWord(start);
MB_AppendWord(qty);
MB_FinalizeCRC();
}
static void MB_HandleWriteMultipleRegs(const uint8_t *frame, uint16_t len, uint8_t slave_addr)
{
uint16_t start, qty, byte_count, i;
uint8_t ex = 0u;
if (len < 9u)
{
MB_BuildException(MB_FC_WRITE_MULTIPLE_REGS, slave_addr, MB_EX_ILLEGAL_DATA_VALUE);
return;
}
start = (uint16_t)(((uint16_t)frame[2] << 8) | frame[3]);
qty = (uint16_t)(((uint16_t)frame[4] << 8) | frame[5]);
byte_count = frame[6];
if (qty < 1u || qty > HR_COUNT ||
(start - HR_BASE_ADDR) + qty > HR_COUNT)
{
MB_BuildException(MB_FC_WRITE_MULTIPLE_REGS, slave_addr, MB_EX_ILLEGAL_DATA_ADDRESS);
return;
}
if (byte_count != (uint16_t)(qty * 2u))
{
MB_BuildException(MB_FC_WRITE_MULTIPLE_REGS, slave_addr, MB_EX_ILLEGAL_DATA_VALUE);
return;
}
if (len < (uint16_t)(7u + byte_count + 2u))
{
MB_BuildException(MB_FC_WRITE_MULTIPLE_REGS, slave_addr, MB_EX_ILLEGAL_DATA_VALUE);
return;
}
for (i = 0u; i < qty; i++)
{
uint16_t reg_addr = (uint16_t)(start - HR_BASE_ADDR + i);
uint16_t value = (uint16_t)(((uint16_t)frame[7u + i * 2u] << 8) | frame[7u + i * 2u + 1u]);
ex = MB_WriteSingleHoldingReg(reg_addr, value);
if (ex != 0u)
{
MB_BuildException(MB_FC_WRITE_MULTIPLE_REGS, slave_addr, ex);
return;
}
}
s_tx_buf[0] = slave_addr;
s_tx_buf[1] = MB_FC_WRITE_MULTIPLE_REGS;
s_tx_len = 2u;
MB_AppendWord(start);
MB_AppendWord(qty);
MB_FinalizeCRC();
}
static void MB_HandleReportSlaveID(const uint8_t *frame, uint16_t len, uint8_t slave_addr)
{
uint8_t slave_id = FlashParam_Get()->slave_addr;
uint8_t run_status = 0xFFu;
uint8_t extra[3];
uint8_t extra_len = 3u;
(void)frame; (void)len;
extra[0] = (uint8_t)(DEVICE_TYPE & 0xFFu);
extra[1] = (uint8_t)((FW_VERSION >> 8) & 0xFFu);
extra[2] = (uint8_t)(FW_VERSION & 0xFFu);
s_tx_buf[0] = slave_addr;
s_tx_buf[1] = MB_FC_REPORT_SLAVE_ID;
s_tx_buf[2] = (uint8_t)(1u + 1u + extra_len);
s_tx_len = 3u;
MB_AppendByte(slave_id);
MB_AppendByte(run_status);
MB_AppendByte(extra[0]);
MB_AppendByte(extra[1]);
MB_AppendByte(extra[2]);
MB_FinalizeCRC();
}
void Modbus_Init(void)
{
s_frame_count = 0u;
s_crc_err_count = 0u;
s_ex_resp_count = 0u;
s_pending_slave = 0u;
s_pending_baud = 0xFFu;
s_pending_link = 0xFFu;
s_pending_fs_en = 0xFFu;
s_pending_fs_sec = 0xFFu;
s_need_apply_baud = 0u;
s_fail_safe_active = 0u;
s_last_valid_tick = HAL_GetTick();
s_tx_len = 0u;
BSP_RS485_SetBaudrate(FlashParam_Get()->baud_index);
}
void Modbus_Poll(void)
{
uint16_t len = 0u;
uint8_t slave_addr;
uint8_t fc;
uint8_t is_broadcast;
uint8_t need_reply;
uint16_t calc_crc, recv_crc;
uint8_t link_enable;
if (!BSP_RS485_IsFrameReady())
{
return;
}
BSP_RS485_GetFrame(s_rx_frame, &len);
if (len < 4u || len > RS485_RX_BUF_SIZE)
{
return;
}
calc_crc = MB_CRC16(s_rx_frame, (uint16_t)(len - 2u));
recv_crc = (uint16_t)(((uint16_t)s_rx_frame[len - 1u] << 8) | s_rx_frame[len - 2u]);
if (calc_crc != recv_crc)
{
s_crc_err_count++;
return;
}
slave_addr = s_rx_frame[0];
is_broadcast = (slave_addr == MB_BROADCAST_ADDR) ? 1u : 0u;
if (!is_broadcast && slave_addr != FlashParam_Get()->slave_addr)
{
return;
}
s_frame_count++;
BSP_CommPulse();
s_last_valid_tick = HAL_GetTick();
link_enable = FlashParam_Get()->link_enable;
need_reply = (!is_broadcast && link_enable != 0u) ? 1u : 0u;
s_pending_slave = 0u;
s_pending_baud = 0xFFu;
s_pending_link = 0xFFu;
s_pending_fs_en = 0xFFu;
s_pending_fs_sec = 0xFFu;
fc = s_rx_frame[1];
s_tx_len = 0u;
switch (fc)
{
case MB_FC_READ_COILS:
MB_HandleReadCoils(s_rx_frame, len, slave_addr);
break;
case MB_FC_READ_DISCRETE_INPUTS:
MB_HandleReadDiscreteInputs(s_rx_frame, len, slave_addr);
break;
case MB_FC_READ_HOLDING_REGS:
MB_HandleReadHoldingRegs(s_rx_frame, len, slave_addr);
break;
case MB_FC_READ_INPUT_REGS:
MB_HandleReadInputRegs(s_rx_frame, len, slave_addr);
break;
case MB_FC_WRITE_SINGLE_COIL:
MB_HandleWriteSingleCoil(s_rx_frame, len, slave_addr);
break;
case MB_FC_WRITE_SINGLE_REG:
MB_HandleWriteSingleReg(s_rx_frame, len, slave_addr);
break;
case MB_FC_WRITE_MULTIPLE_COILS:
MB_HandleWriteMultipleCoils(s_rx_frame, len, slave_addr);
break;
case MB_FC_WRITE_MULTIPLE_REGS:
MB_HandleWriteMultipleRegs(s_rx_frame, len, slave_addr);
break;
case MB_FC_REPORT_SLAVE_ID:
MB_HandleReportSlaveID(s_rx_frame, len, slave_addr);
break;
default:
if (need_reply)
{
MB_BuildException(fc, slave_addr, MB_EX_ILLEGAL_FUNCTION);
}
break;
}
if (need_reply && s_tx_len > 0u)
{
BSP_RS485_Send(s_tx_buf, s_tx_len);
}
if (s_fail_safe_active)
{
s_fail_safe_active = 0u;
BSP_LED_SetFault(0u);
}
if (s_pending_slave != 0u || s_pending_baud != 0xFFu || s_pending_link != 0xFFu ||
s_pending_fs_en != 0xFFu || s_pending_fs_sec != 0xFFu)
{
FlashParam_Save(s_pending_slave, s_pending_baud, s_pending_link,
s_pending_fs_en, s_pending_fs_sec);
if (s_pending_baud != 0xFFu)
{
s_need_apply_baud = 1u;
}
}
if (s_need_apply_baud)
{
BSP_RS485_SetBaudrate(FlashParam_Get()->baud_index);
s_need_apply_baud = 0u;
}
}
uint16_t Modbus_GetFrameCount(void)
{
return s_frame_count;
}
uint16_t Modbus_GetCRCErrorCount(void)
{
return s_crc_err_count;
}
uint16_t Modbus_GetExceptionCount(void)
{
return s_ex_resp_count;
}
uint8_t Modbus_IsFailSafeActive(void)
{
return s_fail_safe_active;
}
void Modbus_CheckFailSafe(void)
{
const param_t *p = FlashParam_Get();
if (p->fail_safe_en == 0u || p->fail_safe_sec == 0u)
{
return;
}
if (s_fail_safe_active)
{
return;
}
uint32_t now = HAL_GetTick();
uint32_t elapsed = now - s_last_valid_tick;
uint32_t timeout_ms = (uint32_t)p->fail_safe_sec * 1000u;
if (elapsed >= timeout_ms)
{
BSP_Relay_SetAll(0u);
s_fail_safe_active = 1u;
BSP_LED_SetFault(1u);
}
}
所有评论(0)