feat: Modbus RTU V1.6 私有协议编码实现

新增:
- rs485_modbus.h/c — 完整 Modbus RTU 从机实现 (516行)
  · FC 0x02 读离散输入 (9-bit 位图: car_state/线圈/光耦/继电器)
  · FC 0x03 读保持寄存器 (含 HR 0x0010/0x0011)
  · FC 0x04 读输入寄存器 (基础 IR + 全数据块 0x0100-0x0112)
  · FC 0x06/0x10 写寄存器 (含值域校验+只读拦截)
  · FC 0x11 Report Slave ID
  · CRC16 查表 (poly=0xA001)
  · 全数据块组装 (19 reg = 38B big-endian)
  · 主动上报 3 级速率调度 (事件立即/活动150ms/空闲800ms)
  · 车间距/通过时间测量 (mstick/2=10ms单位, relay边沿触发)
  · 光耦状态实时读取 (GPIOB_ReadPortPin)
  · Holding Register 写穿透: Fusion Mode 同步 direction_mode,
    Hold Time 同步 g_hold_time, Freq Level 同步 g_loop_cng_unit

改造:
- rs485_700bs.h/c: 暴露 rs485_send() + rx缓冲(_rx_buf/_rx_len/_rx_last_tick) 供Modbus共用
- peripheral_main.c: g_fm_700bs_disable 分派 700BS/Modbus; modbus_init() 随 rs485_init 调用
This commit is contained in:
wangfq
2026-07-30 12:02:24 +08:00
parent df0585162b
commit e32f3892fe
5 changed files with 892 additions and 6 deletions
@@ -0,0 +1,818 @@
/*
* rs485_modbus.c
* DLD154Pro RS485 Modbus RTU 从机协议 — V1.6 私有扩展
*
* 实现功能:
* FC 0x02 — 读离散输入 (车辆状态/故障/光耦/继电器)
* FC 0x03 — 读保持寄存器 (配置参数 + 主动上报控制)
* FC 0x04 — 读输入寄存器 (传感器数据 + 全数据块 0x01000x0112)
* FC 0x06 — 写单寄存器
* FC 0x10 — 批量写寄存器
* FC 0x11 — Report Slave ID
* 主动上报 — 3 级速率调度, 帧格式复用 FC 0x04 响应
*
* 2026-07-30 初始实现
*/
#include "CONFIG.h"
#include "CH59x_common.h"
#include "cmcng.h"
#include "rs485_modbus.h"
#include "rs485_700bs.h" /* rs485_send(), UART ISR 共享缓冲 */
#include "storage.h"
#include "dbn_ble_srv.h"
#include <string.h>
/*===========================================================================
* CRC16 查表 — poly=0xA001, init=0xFFFF
*===========================================================================*/
static const uint16_t _crc16_table[256] = {
0x0000,0xC0C1,0xC181,0x0140,0xC301,0x03C0,0x0280,0xC241,
0xC601,0x06C0,0x0780,0xC741,0x0500,0xC5C1,0xC481,0x0440,
0xCC01,0x0CC0,0x0D80,0xCD41,0x0F00,0xCFC1,0xCE81,0x0E40,
0x0A00,0xCAC1,0xCB81,0x0B40,0xC901,0x09C0,0x0880,0xC841,
0xD801,0x18C0,0x1980,0xD941,0x1B00,0xDBC1,0xDA81,0x1A40,
0x1E00,0xDEC1,0xDF81,0x1F40,0xDD01,0x1DC0,0x1C80,0xDC41,
0x1400,0xD4C1,0xD581,0x1540,0xD701,0x17C0,0x1680,0xD641,
0xD201,0x12C0,0x1380,0xD341,0x1100,0xD1C1,0xD081,0x1040,
0xF001,0x30C0,0x3180,0xF141,0x3300,0xF3C1,0xF281,0x3240,
0x3600,0xF6C1,0xF781,0x3740,0xF501,0x35C0,0x3480,0xF441,
0x3C00,0xFCC1,0xFD81,0x3D40,0xFF01,0x3FC0,0x3E80,0xFE41,
0xFA01,0x3AC0,0x3B80,0xFB41,0x3900,0xF9C1,0xF881,0x3840,
0x2800,0xE8C1,0xE981,0x2940,0xEB01,0x2BC0,0x2A80,0xEA41,
0xEE01,0x2EC0,0x2F80,0xEF41,0x2D00,0xEDC1,0xEC81,0x2C40,
0xE401,0x24C0,0x2580,0xE541,0x2700,0xE7C1,0xE681,0x2640,
0x2200,0xE2C1,0xE381,0x2340,0xE101,0x21C0,0x2080,0xE041,
0xA001,0x60C0,0x6180,0xA141,0x6300,0xA3C1,0xA281,0x6240,
0x6600,0xA6C1,0xA781,0x6740,0xA501,0x65C0,0x6480,0xA441,
0x6C00,0xACC1,0xAD81,0x6D40,0xAF01,0x6FC0,0x6E80,0xAE41,
0xAA01,0x6AC0,0x6B80,0xAB41,0x6900,0xA9C1,0xA881,0x6840,
0x7800,0xB8C1,0xB981,0x7940,0xBB01,0x7BC0,0x7A80,0xBA41,
0xBE01,0x7EC0,0x7F80,0xBF41,0x7D00,0xBDC1,0xBC81,0x7C40,
0xB401,0x74C0,0x7580,0xB541,0x7700,0xB7C1,0xB681,0x7640,
0x7200,0xB2C1,0xB381,0x7340,0xB101,0x71C0,0x7080,0xB041,
0x5000,0x90C1,0x9181,0x5140,0x9301,0x53C0,0x5280,0x9241,
0x9601,0x56C0,0x5780,0x9741,0x5500,0x95C1,0x9481,0x5440,
0x9C01,0x5CC0,0x5D80,0x9D41,0x5F00,0x9FC1,0x9E81,0x5E40,
0x5A00,0x9AC1,0x9B81,0x5B40,0x9901,0x59C0,0x5880,0x9841,
0x8801,0x48C0,0x4980,0x8941,0x4B00,0x8BC1,0x8A81,0x4A40,
0x4E00,0x8EC1,0x8F81,0x4F40,0x8D01,0x4DC0,0x4C80,0x8C41,
0x4400,0x84C1,0x8581,0x4540,0x8701,0x47C0,0x4680,0x8641,
0x8201,0x42C0,0x4380,0x8341,0x4100,0x81C1,0x8081,0x4040,
};
static uint16_t crc16(const uint8_t *data, uint8_t len)
{
uint16_t crc = 0xFFFF;
for (uint8_t i = 0; i < len; i++) {
crc = (crc >> 8) ^ _crc16_table[(crc ^ data[i]) & 0xFF];
}
return crc;
}
/*===========================================================================
* 访问共享的 700BS ISR 接收缓冲
*
* rs485_700bs.c 的 UART0_IRQHandler 负责填充这 3 个变量,
* Modbus 轮询时从同一缓冲读取。
*===========================================================================*/
extern uint8_t _rx_buf[16];
extern uint8_t _rx_len;
extern uint32_t _rx_last_tick;
/*===========================================================================
* Modbus 协议状态
*===========================================================================*/
static uint8_t _auto_rpt_enable = 0; /* HR 0x0010 影子 */
static uint16_t _active_threshold = 10; /* HR 0x0011 影子, 默认 10 */
/* 主动上报调度 */
static uint32_t _rpt_last_ms = 0; /* 上次上报时刻 (mstick) */
static uint8_t _rpt_active = 0; /* 当前上报速率: 0=空闲, 1=活动 */
static uint8_t _rpt_hyst_cnt = 0; /* 降速迟滞计数器 */
/* 车间距 / 通过时间 (10ms 单位) */
static uint32_t _last_relay_off_10ms = 0; /* 上次继电器释放时刻 (mstick/2) */
static uint32_t _last_relay_on_10ms = 0; /* 上次继电器吸合时刻 (mstick/2) */
static uint8_t _last_relay_state = 0; /* 继电器状态快照 (边沿检测) */
static uint16_t _misc_time_type = 0xFFFF; /* 0=间隙, 1=通过, 0xFFFF=无效 */
static uint32_t _misc_time_value = 0; /* 时间值 (10ms 单位) */
/* 计数器 (上电清零) */
static uint32_t _relay_engage_cnt = 0; /* 继电器吸合累计次数 */
/*===========================================================================
* 主动上报 — 全数据块组装 & 发送
*===========================================================================*/
/* 计算线圈频率 (Hz) */
static uint32_t calc_freq(void)
{
extern uint32_t loop1_CAPVD;
extern uint16_t loop1_LPCNT;
if (loop1_CAPVD == 0 || loop1_LPCNT == 0) return 0;
return (uint32_t)((uint64_t)FREQ_SYS * loop1_LPCNT / loop1_CAPVD) + 360;
}
/* 计算 Variation = CAPVD Origin */
static int32_t calc_variation(void)
{
extern uint32_t loop1_CAPVD;
extern uint32_t loop1_Origin;
return (int32_t)(loop1_CAPVD - loop1_Origin);
}
/* 构建全数据块 (19 寄存器 = 38 字节, big-endian) */
static void build_full_data(uint8_t *buf)
{
extern uint32_t loop1_CAPVD;
extern uint32_t loop1_Origin;
extern uint8_t loop1_LOOP_OK;
extern uint32_t g_loop_cut_amount;
int32_t var;
uint32_t freq, capvd, origin, val32;
/* 线圈侧: 只读快照 */
var = calc_variation();
freq = calc_freq();
capvd = loop1_CAPVD;
origin = loop1_Origin;
/* 0x0100 Sensitivity */
buf[0] = 0x00;
buf[1] = g_loop_cng_unit.sensitvity & 0x0F;
/* 0x0101 Freq Level */
buf[2] = 0x00;
buf[3] = g_freq_level & 0x01;
/* 0x0102 Fusion Mode */
buf[4] = 0x00;
buf[5] = g_output_radar_mode & 0x0F;
/* 0x0103 Car State */
buf[6] = 0x00;
buf[7] = g_loop_acs_info.car_state ? 0x01 : 0x00;
/* 0x0104 Loop Status: bit0=断开, bit1=稳定 */
{
uint8_t st = 0;
if (!loop1_LOOP_OK) st |= 0x01;
if (g_loop_stable) st |= 0x02;
buf[8] = 0x00;
buf[9] = st;
}
/* 0x0105 Opto Status (光耦输入低电平有效) */
{
uint8_t opto = (GPIOB_ReadPortPin(GPIO_Pin_10) == 0) ? 1 : 0;
buf[10] = 0x00;
buf[11] = opto;
}
/* 0x01060x0107 Loop Freq (Hz, 32-bit big-endian) */
buf[12] = (uint8_t)(freq >> 24);
buf[13] = (uint8_t)(freq >> 16);
buf[14] = (uint8_t)(freq >> 8);
buf[15] = (uint8_t)(freq);
/* 0x01080x0109 CAPVD (32-bit big-endian) */
buf[16] = (uint8_t)(capvd >> 24);
buf[17] = (uint8_t)(capvd >> 16);
buf[18] = (uint8_t)(capvd >> 8);
buf[19] = (uint8_t)(capvd);
/* 0x010A0x010B Variation (32-bit signed, big-endian) */
val32 = (uint32_t)var;
buf[20] = (uint8_t)(val32 >> 24);
buf[21] = (uint8_t)(val32 >> 16);
buf[22] = (uint8_t)(val32 >> 8);
buf[23] = (uint8_t)(val32);
/* 0x010C0x010D Cut Cnt (线圈断开累计, 32-bit big-endian) */
val32 = g_loop_cut_amount;
buf[24] = (uint8_t)(val32 >> 24);
buf[25] = (uint8_t)(val32 >> 16);
buf[26] = (uint8_t)(val32 >> 8);
buf[27] = (uint8_t)(val32);
/* 0x010E0x010F Relay Cnt (继电器吸合累计, 32-bit big-endian) */
buf[28] = (uint8_t)(_relay_engage_cnt >> 24);
buf[29] = (uint8_t)(_relay_engage_cnt >> 16);
buf[30] = (uint8_t)(_relay_engage_cnt >> 8);
buf[31] = (uint8_t)(_relay_engage_cnt);
/* 0x0110 Time Type */
buf[32] = (uint8_t)(_misc_time_type >> 8);
buf[33] = (uint8_t)(_misc_time_type);
/* 0x01110x0112 Misc Time (10ms 单位, 32-bit big-endian) */
buf[34] = (uint8_t)(_misc_time_value >> 24);
buf[35] = (uint8_t)(_misc_time_value >> 16);
buf[36] = (uint8_t)(_misc_time_value >> 8);
buf[37] = (uint8_t)(_misc_time_value);
}
/* 发送全数据块 (FC 0x04 响应格式) */
static void send_full_data(void)
{
uint8_t buf[MB_FULL_DATA_FRAME_LEN];
uint16_t crc;
buf[0] = g_rs485_id & 0xFF; /* 从机地址 */
buf[1] = 0x04; /* FC 0x04 */
buf[2] = 38; /* 字节计数 = 19 × 2 */
build_full_data(&buf[3]);
crc = crc16(buf, 41);
buf[41] = (uint8_t)(crc);
buf[42] = (uint8_t)(crc >> 8);
rs485_send(buf, MB_FULL_DATA_FRAME_LEN);
}
/* 更新车间距 / 通过时间 (每次 relay state 翻转时调用) */
static void update_misc_time(void)
{
uint32_t now_10ms = mstick() / 2; /* 5ms/tick → 10ms 单位 */
uint8_t relay = g_loop_acs_info.relay1_state;
if (relay == _last_relay_state) return;
if (relay) {
/* 0→1 吸合沿: 车间距 = now − 上次释放时刻 */
_misc_time_value = now_10ms - _last_relay_off_10ms;
_misc_time_type = 0; /* 间隙时间 */
_last_relay_on_10ms = now_10ms;
_relay_engage_cnt++;
} else {
/* 1→0 释放沿: 通过时间 = now − 上次吸合时刻 */
_misc_time_value = now_10ms - _last_relay_on_10ms;
_misc_time_type = 1; /* 通过时间 */
_last_relay_off_10ms = now_10ms;
}
_last_relay_state = relay;
}
/*===========================================================================
* 主动上报速率调度
*===========================================================================*/
/* 判断是否处于活动状态 (|Variation| ≥ 阈值) */
static uint8_t is_active(void)
{
int32_t var = calc_variation();
if (var < 0) var = -var;
return ((uint32_t)var >= _active_threshold) ? 1 : 0;
}
/* 主动上报调度 — 每 tick 调用一次 */
static void modbus_report_schedule(void)
{
if (!_auto_rpt_enable) return;
/* 继电器翻转 → 立即上报 */
{
uint8_t relay = g_loop_acs_info.relay1_state;
if (relay != _last_relay_state) {
update_misc_time(); /* 先更新时间值 */
send_full_data(); /* 立即发送 */
_rpt_last_ms = mstick();
_rpt_hyst_cnt = 0;
_rpt_active = is_active();
return;
}
}
uint32_t now = mstick();
uint32_t elapsed = now - _rpt_last_ms;
if (is_active()) {
/* 活动态: 150ms 间隔 */
if (elapsed >= MB_RPT_ACTIVE_MS) {
update_misc_time();
send_full_data();
_rpt_last_ms = now;
_rpt_active = 1;
_rpt_hyst_cnt = 0;
}
} else {
/* 空闲态: 需要先确认降速 (连续 2 次低于阈值) */
if (_rpt_active) {
_rpt_hyst_cnt++;
if (_rpt_hyst_cnt < MB_RPT_ACTIVE_HYST) {
/* 仍在迟滞期, 保持活动间隔 */
if (elapsed >= MB_RPT_ACTIVE_MS) {
update_misc_time();
send_full_data();
_rpt_last_ms = now;
}
return;
}
/* 迟滞期满 → 降速 */
_rpt_active = 0;
_rpt_hyst_cnt = 0;
_rpt_last_ms = now; /* 重置计时, 不立即发 */
}
/* 空闲态: 800ms 间隔 */
if (elapsed >= MB_RPT_IDLE_MS) {
update_misc_time();
send_full_data();
_rpt_last_ms = now;
}
}
}
/*===========================================================================
* 异常响应
*===========================================================================*/
static void send_exception(uint8_t fc, uint8_t excode)
{
uint8_t buf[5];
uint16_t crc;
buf[0] = g_rs485_id & 0xFF;
buf[1] = fc | 0x80; /* 功能码最高位置 1 */
buf[2] = excode;
crc = crc16(buf, 3);
buf[3] = (uint8_t)(crc);
buf[4] = (uint8_t)(crc >> 8);
rs485_send(buf, 5);
}
/*===========================================================================
* FC 0x02 — 读离散输入
*===========================================================================*/
static void handle_read_discrete_inputs(const uint8_t *rx, uint8_t rx_len)
{
(void)rx_len; /* 固定 8 字节请求 */
extern uint8_t loop1_LOOP_OK;
uint16_t start_addr = ((uint16_t)rx[2] << 8) | rx[3];
uint16_t quantity = ((uint16_t)rx[4] << 8) | rx[5];
if (quantity == 0 || quantity > 16) {
send_exception(0x02, 0x03); /* Illegal Data Value */
return;
}
/* 构建 9-bit 位图 (9 个离散输入: 0x00000x0008) */
uint16_t bits = 0;
if (g_loop_acs_info.car_state) bits |= (1 << 0); /* 0x0000 */
if (!loop1_LOOP_OK) bits |= (1 << 1); /* 0x0001 */
if (g_loop_stable) bits |= (1 << 2); /* 0x0002 */
if (GPIOB_ReadPortPin(GPIO_Pin_10) == 0) bits |= (1 << 3); /* 0x0003 光耦有效=低电平 */
if (!g_loop_power_up_state) bits |= (1 << 4); /* 0x0004 上电初始化 */
if (g_loop_acs_info.relay1_state) bits |= (1 << 5); /* 0x0005 */
/* 0x0006 Freq Low Fault — DLD154Pro 未实现, 恒为 0 */
/* 0x0007 Freq High Fault — DLD154Pro 未实现, 恒为 0 */
/* 0x0008 Init Flag — DLD154Pro 未实现, 恒为 0 */
if (start_addr >= 9) {
send_exception(0x02, 0x02); /* Illegal Data Address */
return;
}
/* 读取指定位段 */
uint16_t mask = ((1 << quantity) - 1) << start_addr;
uint8_t val = (uint8_t)((bits & mask) >> start_addr);
uint8_t buf[6];
uint16_t crc;
buf[0] = g_rs485_id & 0xFF;
buf[1] = 0x02;
buf[2] = 1; /* 1 字节数据 */
buf[3] = val;
crc = crc16(buf, 4);
buf[4] = (uint8_t)(crc);
buf[5] = (uint8_t)(crc >> 8);
rs485_send(buf, 6);
}
/*===========================================================================
* FC 0x04 — 读输入寄存器 (含全数据块 0x01000x0112)
*===========================================================================*/
static uint8_t read_ir_register(uint16_t addr, uint8_t *hi, uint8_t *lo)
{
extern uint32_t loop1_CAPVD;
extern uint32_t loop1_Origin;
extern uint8_t loop1_LOOP_OK;
extern uint32_t g_loop_cut_amount;
int32_t var;
uint32_t freq, capvd, val32;
switch (addr) {
/* 基础 IR 0x00000x000C (保持向后兼容) */
case 0x0000: /* Freq Hi */
freq = calc_freq();
*hi = (uint8_t)(freq >> 24); *lo = (uint8_t)(freq >> 16); return 1;
case 0x0001: /* Freq Lo */
freq = calc_freq();
*hi = (uint8_t)(freq >> 8); *lo = (uint8_t)(freq); return 1;
case 0x0002: /* CAPVD Hi */
*hi = (uint8_t)(loop1_CAPVD >> 24); *lo = (uint8_t)(loop1_CAPVD >> 16); return 1;
case 0x0003: /* CAPVD Lo */
*hi = (uint8_t)(loop1_CAPVD >> 8); *lo = (uint8_t)(loop1_CAPVD); return 1;
case 0x0004: /* Origin Hi */
*hi = (uint8_t)(loop1_Origin >> 24); *lo = (uint8_t)(loop1_Origin >> 16); return 1;
case 0x0005: /* Origin Lo */
*hi = (uint8_t)(loop1_Origin >> 8); *lo = (uint8_t)(loop1_Origin); return 1;
case 0x0006: /* Variation Hi */
var = (int32_t)(loop1_CAPVD - loop1_Origin);
val32 = (uint32_t)var;
*hi = (uint8_t)(val32 >> 24); *lo = (uint8_t)(val32 >> 16); return 1;
case 0x0007: /* Variation Lo */
var = (int32_t)(loop1_CAPVD - loop1_Origin);
val32 = (uint32_t)var;
*hi = (uint8_t)(val32 >> 8); *lo = (uint8_t)(val32); return 1;
case 0x0008: /* FW Version (BCD: v3.01 → 0x0301) */
*hi = FIRMWARE_VER_MAIN; *lo = FIRMWARE_VER_SUB; return 1;
case 0x0009: /* HW Version */
*hi = HARDWARE_VER_MAIN; *lo = HARDWARE_VER_SUB; return 1;
case 0x000A: /* Freq Sens */
*hi = 0; *lo = g_loop_cng_unit.sensitvity & 0x0F; return 1;
case 0x000B: /* Condition */
*hi = 0; *lo = g_loop_acs_info.condition; return 1;
case 0x000C: /* DIP Virtual: bit[7:4]=function_mode, bit[3:0]=fusion_mode */
*hi = 0; *lo = (g_function_mode << 4) | (g_output_radar_mode & 0x0F); return 1;
/* 全数据块 0x01000x0112 — 热路径, 直接计算 */
default:
if (addr >= 0x0100 && addr <= 0x0112) {
/* build_full_data 的偏移量映射 */
uint8_t off = (uint8_t)((addr - 0x0100) * 2);
uint8_t tmp[38];
build_full_data(tmp);
if (off < 38) {
*hi = tmp[off];
*lo = tmp[off + 1];
return 1;
}
}
return 0; /* 非法地址 */
}
}
static void handle_read_input_regs(const uint8_t *rx, uint8_t rx_len)
{
(void)rx_len;
uint16_t start_addr = ((uint16_t)rx[2] << 8) | rx[3];
uint16_t quantity = ((uint16_t)rx[4] << 8) | rx[5];
if (quantity == 0 || quantity > 125) {
send_exception(0x04, 0x03);
return;
}
uint8_t data_buf[250];
uint16_t reg_addr;
for (uint16_t i = 0; i < quantity; i++) {
reg_addr = start_addr + i;
uint8_t hi, lo;
if (!read_ir_register(reg_addr, &hi, &lo)) {
send_exception(0x04, 0x02);
return;
}
data_buf[i * 2] = hi;
data_buf[i * 2 + 1] = lo;
}
uint8_t byte_cnt = (uint8_t)(quantity * 2);
uint8_t buf[5 + 250]; /* addr + fc + bc + data + crc */
uint16_t crc;
buf[0] = g_rs485_id & 0xFF;
buf[1] = 0x04;
buf[2] = byte_cnt;
memcpy(&buf[3], data_buf, byte_cnt);
crc = crc16(buf, 3 + byte_cnt);
buf[3 + byte_cnt] = (uint8_t)(crc);
buf[3 + byte_cnt + 1] = (uint8_t)(crc >> 8);
rs485_send(buf, 3 + byte_cnt + 2);
}
/*===========================================================================
* FC 0x03 — 读保持寄存器
*===========================================================================*/
static uint8_t read_hr_register(uint16_t addr, uint8_t *hi, uint8_t *lo)
{
switch (addr) {
case 0x0000: /* Sensitivity */
*hi = 0; *lo = g_loop_cng_unit.sensitvity & 0x0F; return 1;
case 0x0001: /* Fusion Mode */
*hi = 0; *lo = g_output_radar_mode & 0x0F; return 1;
case 0x0002: /* Hold Time */
*hi = 0; *lo = g_loop_cng_unit.exist_mode; return 1;
case 0x0003: /* Output Delay */
*hi = 0; *lo = g_loop_cng_unit.delay_time; return 1;
case 0x0004: /* Freq Level */
*hi = 0; *lo = g_freq_level & 0x01; return 1;
case 0x0005: /* RS485 Baud Hi (只读) */
*hi = (uint8_t)(g_storage_uart_baud >> 24); *lo = (uint8_t)(g_storage_uart_baud >> 16); return 1;
case 0x0006: /* RS485 Baud Lo (只读) */
*hi = (uint8_t)(g_storage_uart_baud >> 8); *lo = (uint8_t)(g_storage_uart_baud); return 1;
case 0x0007: /* RS485 Addr (只读) */
*hi = 0; *lo = g_rs485_id; return 1;
case 0x0008: /* Protocol (只读) */
*hi = 0; *lo = g_fm_700bs_disable; return 1;
case 0x0009: /* BT Timeout */
*hi = 0; *lo = g_max_counter_bt_min; return 1;
case 0x000A: /* Safe Timeout */
*hi = 0; *lo = g_loop_cng_unit.loopSafe_Timeout; return 1;
case 0x000B: /* Opto Func (只读) */
*hi = 0; *lo = (g_function_mode >> 1) & 0x01; return 1;
case 0x0010: /* Auto Rpt Enable */
*hi = 0; *lo = _auto_rpt_enable; return 1;
case 0x0011: /* Active Threshold */
*hi = (uint8_t)(_active_threshold >> 8); *lo = (uint8_t)(_active_threshold); return 1;
default:
return 0;
}
}
static void handle_read_holding_regs(const uint8_t *rx, uint8_t rx_len)
{
(void)rx_len;
uint16_t start_addr = ((uint16_t)rx[2] << 8) | rx[3];
uint16_t quantity = ((uint16_t)rx[4] << 8) | rx[5];
if (quantity == 0 || quantity > 125) {
send_exception(0x03, 0x03);
return;
}
uint8_t data_buf[250];
for (uint16_t i = 0; i < quantity; i++) {
uint16_t reg_addr = start_addr + i;
uint8_t hi, lo;
if (!read_hr_register(reg_addr, &hi, &lo)) {
send_exception(0x03, 0x02);
return;
}
data_buf[i * 2] = hi;
data_buf[i * 2 + 1] = lo;
}
uint8_t byte_cnt = (uint8_t)(quantity * 2);
uint8_t buf[5 + 250];
uint16_t crc;
buf[0] = g_rs485_id & 0xFF;
buf[1] = 0x03;
buf[2] = byte_cnt;
memcpy(&buf[3], data_buf, byte_cnt);
crc = crc16(buf, 3 + byte_cnt);
buf[3 + byte_cnt] = (uint8_t)(crc);
buf[3 + byte_cnt + 1] = (uint8_t)(crc >> 8);
rs485_send(buf, 3 + byte_cnt + 2);
}
/*===========================================================================
* FC 0x06 — 写单寄存器
*===========================================================================*/
static void handle_write_single_reg(const uint8_t *rx, uint8_t rx_len)
{
(void)rx_len;
uint16_t reg_addr = ((uint16_t)rx[2] << 8) | rx[3];
uint16_t reg_val = ((uint16_t)rx[4] << 8) | rx[5];
/* 只读寄存器拦截 */
switch (reg_addr) {
case 0x0005: case 0x0006: case 0x0007: case 0x0008: case 0x000B:
send_exception(0x06, 0x02); /* Illegal Data Address (只读) */
return;
}
/* 值校验 & 写入 */
switch (reg_addr) {
case 0x0000: /* Sensitivity: 0-3 */
if (reg_val > 3) { send_exception(0x06, 0x03); return; }
g_loop_cng_unit.sensitvity = (uint8_t)reg_val;
break;
case 0x0001: /* Fusion Mode: 0-6 */
if (reg_val > 6) { send_exception(0x06, 0x03); return; }
g_output_radar_mode = (uint8_t)reg_val;
g_loop_cng_unit.direction_mode = (g_function_mode << 4) | (g_output_radar_mode & 0x0F);
break;
case 0x0002: /* Hold Time: 0-255 */
g_loop_cng_unit.exist_mode = (uint8_t)reg_val;
g_hold_time = (uint16_t)reg_val * 20 * 5;
break;
case 0x0003: /* Output Delay: 0-255 */
g_loop_cng_unit.delay_time = (uint8_t)reg_val;
break;
case 0x0004: /* Freq Level: 0-1 */
if (reg_val > 1) { send_exception(0x06, 0x03); return; }
g_freq_level = (uint8_t)reg_val;
g_loop_cng_unit.loopFreq_Level = g_freq_level + 1;
break;
case 0x0009: /* BT Timeout: 0-255 */
g_max_counter_bt_min = (uint8_t)reg_val;
g_max_counter_bt_timeout = (uint32_t)reg_val * 60 * 1000;
break;
case 0x000A: /* Safe Timeout: 0-255 */
g_loop_cng_unit.loopSafe_Timeout = (uint8_t)reg_val;
break;
case 0x0010: /* Auto Rpt Enable: 0-1 */
if (reg_val > 1) { send_exception(0x06, 0x03); return; }
_auto_rpt_enable = (uint8_t)reg_val;
if (_auto_rpt_enable) {
_rpt_last_ms = mstick(); /* 对齐计时起点 */
_rpt_active = 0;
_rpt_hyst_cnt = 0;
}
break;
case 0x0011: /* Active Threshold: 0-65535 */
_active_threshold = reg_val;
break;
default:
send_exception(0x06, 0x02); /* Illegal Data Address */
return;
}
/* 回显 (Modbus 标准: 写成功的响应 = 请求原样返回) */
{
uint8_t buf[8];
uint16_t crc;
buf[0] = g_rs485_id & 0xFF;
buf[1] = 0x06;
buf[2] = rx[2]; buf[3] = rx[3]; /* 地址 */
buf[4] = rx[4]; buf[5] = rx[5]; /* 值 */
crc = crc16(buf, 6);
buf[6] = (uint8_t)(crc);
buf[7] = (uint8_t)(crc >> 8);
rs485_send(buf, 8);
}
}
/*===========================================================================
* FC 0x10 — 批量写寄存器
*
* 简化实现: 逐寄存器调用 FC 0x06 的校验+写入逻辑
*===========================================================================*/
static void handle_write_multiple_regs(const uint8_t *rx, uint8_t rx_len)
{
(void)rx_len;
uint16_t start_addr = ((uint16_t)rx[2] << 8) | rx[3];
uint16_t quantity = ((uint16_t)rx[4] << 8) | rx[5];
uint8_t byte_cnt = rx[6];
if (quantity == 0 || quantity > 123 || byte_cnt != quantity * 2) {
send_exception(0x10, 0x03);
return;
}
for (uint16_t i = 0; i < quantity; i++) {
uint16_t reg_addr = start_addr + i;
uint16_t reg_val = ((uint16_t)rx[7 + i * 2] << 8) | rx[8 + i * 2];
/* 只读拦截 */
if (reg_addr == 0x0005 || reg_addr == 0x0006 || reg_addr == 0x0007 ||
reg_addr == 0x0008 || reg_addr == 0x000B) {
send_exception(0x10, 0x02);
return;
}
/* 值校验 — 同样的逻辑 */
switch (reg_addr) {
case 0x0000: if (reg_val > 3) { send_exception(0x10, 0x03); return; }
g_loop_cng_unit.sensitvity = (uint8_t)reg_val; break;
case 0x0001: if (reg_val > 6) { send_exception(0x10, 0x03); return; }
g_output_radar_mode = (uint8_t)reg_val;
g_loop_cng_unit.direction_mode = (g_function_mode << 4) | (g_output_radar_mode & 0x0F); break;
case 0x0002: g_loop_cng_unit.exist_mode = (uint8_t)reg_val;
g_hold_time = (uint16_t)reg_val * 20 * 5; break;
case 0x0003: g_loop_cng_unit.delay_time = (uint8_t)reg_val; break;
case 0x0004: if (reg_val > 1) { send_exception(0x10, 0x03); return; }
g_freq_level = (uint8_t)reg_val;
g_loop_cng_unit.loopFreq_Level = g_freq_level + 1; break;
case 0x0009: g_max_counter_bt_min = (uint8_t)reg_val;
g_max_counter_bt_timeout = (uint32_t)reg_val * 60 * 1000; break;
case 0x000A: g_loop_cng_unit.loopSafe_Timeout = (uint8_t)reg_val; break;
case 0x0010: if (reg_val > 1) { send_exception(0x10, 0x03); return; }
_auto_rpt_enable = (uint8_t)reg_val;
if (_auto_rpt_enable) { _rpt_last_ms = mstick(); _rpt_active = 0; _rpt_hyst_cnt = 0; }
break;
case 0x0011: _active_threshold = reg_val; break;
default: send_exception(0x10, 0x02); return;
}
}
/* 响应: 回显 addr + fc + start + qty */
{
uint8_t buf[8];
uint16_t crc;
buf[0] = g_rs485_id & 0xFF;
buf[1] = 0x10;
buf[2] = rx[2]; buf[3] = rx[3];
buf[4] = rx[4]; buf[5] = rx[5];
crc = crc16(buf, 6);
buf[6] = (uint8_t)(crc);
buf[7] = (uint8_t)(crc >> 8);
rs485_send(buf, 8);
}
}
/*===========================================================================
* FC 0x11 — Report Slave ID
*===========================================================================*/
static void handle_report_slave_id(const uint8_t *rx, uint8_t rx_len)
{
(void)rx; (void)rx_len;
extern uint8_t loop1_LOOP_OK;
/* 返回: addr + fc + byte_cnt + model(11) + status + crc */
const char *model = PRODUCT_MODEL; /* "DLD154Pro" = 10 chars + '\0' = 11 */
uint8_t model_len = (uint8_t)strlen(model) + 1; /* 含 '\0' */
uint8_t buf[5 + 12 + 2]; /* addr+fc+bc + model(max12) + status + crc */
uint16_t crc;
uint8_t idx = 0;
buf[idx++] = g_rs485_id & 0xFF;
buf[idx++] = 0x11;
buf[idx++] = model_len + 1; /* byte_cnt = model + status */
memcpy(&buf[idx], model, model_len);
idx += model_len;
/* Status: 0x00=RUN, 0xFF=STOP */
buf[idx++] = loop1_LOOP_OK ? 0x00 : 0xFF;
crc = crc16(buf, idx);
buf[idx++] = (uint8_t)(crc);
buf[idx++] = (uint8_t)(crc >> 8);
rs485_send(buf, idx);
}
/*===========================================================================
* 初始化 & 轮询
*===========================================================================*/
void modbus_init(void)
{
/* 协议状态复位 */
_auto_rpt_enable = 0;
_active_threshold = 10;
_rpt_last_ms = mstick();
_rpt_active = 0;
_rpt_hyst_cnt = 0;
_last_relay_state = g_loop_acs_info.relay1_state;
_last_relay_off_10ms = 0;
_last_relay_on_10ms = 0;
_misc_time_type = 0xFFFF;
_misc_time_value = 0;
_relay_engage_cnt = 0;
}
void modbus_poll(void)
{
/*=== 帧接收 ===*/\n if (_rx_len > 0) {
uint32_t now = mstick();
if (now - _rx_last_tick >= MB_FRAME_TIMEOUT_TICK) {
/* 帧超时 → 处理完整帧 */
uint8_t *rx = _rx_buf;
uint8_t len = _rx_len;
/* 最小帧: addr(1) + fc(1) + crc(2) = 4 */
if (len >= 4) {
uint8_t addr = rx[0];
uint8_t fc = rx[1];
uint16_t crc_rx = ((uint16_t)rx[len - 1] << 8) | rx[len - 2]; /* 低字节在前 */
uint16_t crc_calc = crc16(rx, len - 2);
/* 地址匹配 (0=广播, 本从机不响应) */
if (addr != 0 && addr == (g_rs485_id & 0xFF)) {
if (crc_rx == crc_calc) {
switch (fc) {
case 0x02: handle_read_discrete_inputs(rx, len); break;
case 0x03: handle_read_holding_regs(rx, len); break;
case 0x04: handle_read_input_regs(rx, len); break;
case 0x06: handle_write_single_reg(rx, len); break;
case 0x10: handle_write_multiple_regs(rx, len); break;
case 0x11: handle_report_slave_id(rx, len); break;
default: send_exception(fc, 0x01); break;
}
}
/* CRC 错误 → 静默 (Modbus 标准行为) */
}
/* 地址不匹配 → 静默 */
}
_rx_len = 0; /* 清空缓冲 */
}
}
/*=== 主动上报调度 ===*/\n modbus_report_schedule();
}