diff --git a/Docs/RS485扩展继电器协议.md b/Docs/RS485扩展继电器协议.md index f913710..7e93e0e 100644 --- a/Docs/RS485扩展继电器协议.md +++ b/Docs/RS485扩展继电器协议.md @@ -82,15 +82,16 @@ 4. 反馈采样建议 10~50ms 周期,加简单消抖(如连续 3 次一致才更新 REG1);**消抖总时长必须 <150ms**——主控在 FC05 回显后 150ms 就读 FC03 校验反馈位,反馈跟不上会误判"负载未通电" 5. 只响应地址 0x10 的帧;**不要响应广播/其它地址**(总线上还有秤,地址 0x01) 6. 收帧间隔不足 50ms 也要能正常解析(主控保证发送间隔,从机只做鲁棒接收) +7. 主控**平时不发帧**(无周期轮询,只在控制命令和上电探测时通信),从机不要把"长时间收不到帧"当异常处理 ## 5. 主机行为(联调参考) -主控侧已实现(`HardWare/RELAY/ext_relay.c`): +主控侧已实现(`HardWare/RELAY/ext_relay.c`),**平时总线零流量(无周期轮询),只在控制时检测**: -- **命令流程**:平台/蓝牙下发 → FC05 → 等回显(300ms 超时,补发 1 次)→ 延时 150ms 等触点稳定 → FC03 读状态 → 校验"输出位=目标 且 反馈位=目标 且 **反馈位有跳变**(命令前≠目标,与板载继电器反馈同语义;重复命令原本已在目标态的豁免)" → 应答平台(失败:`Load unpowered / relay failure !`)→ 上报全状态 -- **周期轮询**:空闲每 2s 发一次 FC03,兼在线检测;连续 3 次无应答判离线(日志提示) +- **上电探测**:主控上电 3s 后发一次 FC03——有应答则标记在线并触发断电恢复比对;无应答(补发 1 次仍无)记日志"未接扩展板?",不影响其它功能 +- **命令流程**:平台/蓝牙下发 → **FC03 预读**(判定在线 + 快照命令前反馈值;无应答补发 1 次,仍无应答回平台 `ext relay board offline`)→ FC05 → 等回显(300ms 超时,补发 1 次)→ 延时 150ms 等触点稳定 → FC03 读状态 → 校验"输出位=目标 且 反馈位=目标 且 **反馈位有跳变**(命令前≠目标,与板载继电器反馈同语义;重复命令原本已在目标态的豁免)" → 应答平台(失败:`Load unpowered / relay failure !`)→ 上报全状态 +- **离线判定**:命令链路最终无应答即判离线(日志提示),不单独周期监测;下次命令第一步自动重新探测,恢复应答即上线 - **断电记忆恢复**:扩展板状态存在主控 EEPROM(偏移 102 的 Relay_State[5],bit0~3);每次离线→上线时比对实际状态与记忆,不一致的回路逐路补发 FC05 纠正,恢复完成统一上报一次 -- **离线行为**:离线期间下发命令直接回复平台 "ext relay board offline";板子恢复应答后自动上线并补一次状态上报 - 上电 3s 内不发帧(避开上电窗口) ## 6. 平台/蓝牙映射 diff --git a/STM32F103_App1/HardWare/RELAY/ext_relay.c b/STM32F103_App1/HardWare/RELAY/ext_relay.c index 4a7389c..c3b2831 100644 --- a/STM32F103_App1/HardWare/RELAY/ext_relay.c +++ b/STM32F103_App1/HardWare/RELAY/ext_relay.c @@ -4,8 +4,11 @@ * Э: Modbus RTU, ӻַ 0x10, Docs/RS485չ̵Э.md * FC05 дȦ(Ȧ0~3=̵1~4, 0xFF00/0x0000Ͽ), ӻ * FC03 ּĴ 0x0000 4 : ״̬/״̬/ϱ־/̼汾 - * ʱ: ߾Ĭ >=50ms ŷ֡(Ӳѯ rs485_bus ʱ); - * Ӧʱ 300ms 1 ; ÿ 2s ѯһ״̬߼ + * ʱ: ߾Ĭ >=50ms ŷ֡(Ӳѯ rs485_bus ʱ); Ӧʱ 300ms 1 + * : ƽʱ(ѯ), ֻʱշ: + * 1) ϵ 3s һ̽(ȷ + ϵ״ָ̬) + * 2) ƽ̨/: FC03 ǰ̬(ǰ) -> FC05 -> 150ms -> FC03 У + * 3) ϵָ: ->ʱȶ EEPROM , һµĻ·· FC05 */ #include "ext_relay.h" @@ -21,8 +24,11 @@ #define EXT_RESP_TIMEOUT_MS 300u /* Ӧʱ */ #define EXT_FB_SETTLE_MS 150u /* Ժȴ/ȶٶ */ -#define EXT_POLL_INTERVAL_MS 2000u /* ѯ */ -#define EXT_OFFLINE_MISS 3u /* ʧܴ */ + +/* ֡¼(on_frame λ, process ; ͬ app_task ߳) */ +static uint8_t rx_echo_ok = 0; +static uint8_t rx_state_ok = 0; +static uint8_t rx_exc = 0; /* Modbus CRC16(ͬʽ) */ static uint16_t ext_crc16(const uint8_t *data, uint16_t len) @@ -37,15 +43,10 @@ static uint16_t ext_crc16(const uint8_t *data, uint16_t len) return crc; } -/* ֡¼(on_frame λ, process ; ͬ app_task ߳) */ -static uint8_t rx_echo_ok = 0; -static uint8_t rx_state_ok = 0; -static uint8_t rx_exc = 0; - -/* ߷: 50ms Ĭ + ջ(¾֡) + + ˢʱ */ +/* ߷: Ӧ־ + 50ms Ĭ + ջ + + ˢʱ */ static void ext_tx(const uint8_t *frame, uint16_t len) { - rx_echo_ok = 0; /* һӦ־, ֹٵ֡ǰ */ + rx_echo_ok = 0; rx_state_ok = 0; rx_exc = 0; uint32_t dt = HAL_GetTick() - rs485_bus_last_tick(); @@ -89,21 +90,29 @@ static uint8_t s_state = 0; /* REG0 static uint8_t s_fb = 0; /* REG1 ״̬ */ static uint8_t s_fault = 0; /* REG2 ϱ־ */ static uint16_t s_ver = 0; /* REG3 ӻ汾 */ -static uint8_t s_miss = 0; /* Ӧ */ static uint8_t s_fault_logged = 0; /* ѴӡĹλ(仯ش) */ -static uint32_t s_last_poll = 0; -/* ״̬ */ -static uint8_t cmd_pending = 0; /* */ -static uint8_t cmd_stage = 0; /* 0 1FC05 2ȶʱ 3FC03Ӧ */ +/* ׶ */ +#define ST_IDLE 0 /* */ +#define ST_READ 1 /* : ϵ̽ / ǰȡ */ +#define ST_ECHO 2 /* FC05 */ +#define ST_SETTLE 3 /* ȶʱ */ +#define ST_CONFIRM 4 /* У */ + +/* */ +#define TXN_NONE 0 /* ϵ̽ */ +#define TXN_CMD 1 /* ƽ̨/ */ +#define TXN_RESTORE 2 /* ϵָ */ + +static uint8_t cmd_stage = ST_IDLE; +static uint8_t cmd_txn = TXN_NONE; +static uint8_t cmd_pending = 0; /* û */ static uint8_t cmd_ch = 0, cmd_on = 0, cmd_retry = 0; -static uint8_t cmd_is_poll = 0; /* ǰѯ(Ӧƽ̨) */ -static uint8_t cmd_is_restore = 0;/* ǰǶϵָ(Ӧƽ̨) */ static uint32_t cmd_tick = 0; static char cmd_id[20] = {0}; -static uint8_t cmd_state_before = 0; /* ǰλ(ѯֵ) */ -static uint8_t cmd_fb_before = 0; /* ǰλ(ѯֵ) */ - +static uint8_t cmd_state_before = 0; /* ǰλ(FC03 Ԥֵ) */ +static uint8_t cmd_fb_before = 0; /* ǰλ(FC03 Ԥֵ) */ +static uint8_t boot_probe_done = 0; /* ϵһ̽ѷ */ #define EXT_STATE_EEPROM_IDX 5 /* MqttInfoStr.Relay_State[5] ԭֽ: չ״̬λ */ @@ -134,9 +143,9 @@ uint8_t ext_relay_online(void) { return s_online; } uint8_t ext_relay_state_mask(void) { return s_state; } uint8_t ext_relay_fault_mask(void) { return s_fault; } +/* һɹ: fresh_state=1 ʾյ FC03(״̬µ) */ static void ext_note_success(int fresh_state) { - s_miss = 0; if (!s_online) { s_online = 1; log_info("> EXT: չ̵ (ӻ汾 V%u.%02X)", s_ver >> 8, s_ver & 0xFF); @@ -156,10 +165,9 @@ static void ext_note_success(int fresh_state) static void ext_note_fail(void) { - if (s_miss < 255) s_miss++; - if (s_online && s_miss >= EXT_OFFLINE_MISS) { + if (s_online) { s_online = 0; - log_warn("> EXT: չ̵( %u Ӧ)", EXT_OFFLINE_MISS); + log_warn("> EXT: չ̵(Ӧ)"); } } @@ -174,12 +182,8 @@ static void ext_respond(int ok, const char *msg) void ext_relay_cmd(uint8_t ch, uint8_t on, const char *id) { if (ch < 1 || ch > EXT_RELAY_CH) return; - if (!s_online) { - log_warn("> EXT: չ, ܾ SW%d ", ch + 4); - if (id) NET_publish_response(id, 0, "ext relay board offline"); - return; - } - if (cmd_pending || cmd_stage != 0) { + /* Ҳճ: ·һ FC03 ̽, ͨӦ(ƽʱѯ) */ + if (cmd_pending || cmd_stage != ST_IDLE) { log_warn("> EXT: һδ, ܾ SW%d ", ch + 4); if (id) NET_publish_response(id, 0, "ext relay busy"); return; @@ -233,79 +237,130 @@ void ext_relay_on_frame(const uint8_t *b, uint16_t len) log_warn("> EXT: δʶ֡ FC=%02X len=%u", b[1], (unsigned)len); } +/* ǰʧβ */ +static void ext_txn_failed(const char *respond_msg) +{ + if (cmd_txn == TXN_CMD) { + log_warn("> EXT: SW%d ʧ(%s)", cmd_ch + 4, respond_msg); + ext_respond(0, respond_msg); + } else if (cmd_txn == TXN_RESTORE) { + restore_next_tick = HAL_GetTick() + 2000; /* ˱ܺ, λ */ + } else { + log_warn("> EXT: ϵ̽Ӧ(δչ?)"); + } + ext_note_fail(); + cmd_txn = TXN_NONE; + cmd_stage = ST_IDLE; +} + +/* ʱ/쳣ͳһ: 1 , ʧβ */ +static void ext_txn_retry_or_fail(uint8_t resend_fc05, const char *respond_msg) +{ + if (cmd_retry < 1) { + cmd_retry++; + if (resend_fc05) ext_send_fc05(cmd_ch, cmd_on); + else ext_send_fc03(); + cmd_tick = HAL_GetTick(); + return; + } + ext_txn_failed(respond_msg); +} + void ext_relay_process(void) { uint32_t now = HAL_GetTick(); if (now < 3000u) return; /* ϵȶ, ״βѯ */ - /* ȡ(ʱ) */ - if (cmd_pending && cmd_stage == 0) { - cmd_pending = 0; - cmd_is_poll = 0; + /* ϵһ̽: ȷ + ϵָȶ */ + if (!boot_probe_done && cmd_stage == ST_IDLE && !cmd_pending) { + boot_probe_done = 1; + cmd_txn = TXN_NONE; cmd_retry = 0; - /* ǰ״̬(һ: У鷴, ӻгɹ) */ - cmd_state_before = (uint8_t)((s_state >> (cmd_ch - 1)) & 1u); - cmd_fb_before = (uint8_t)((s_fb >> (cmd_ch - 1)) & 1u); - cmd_stage = 1; - ext_send_fc05(cmd_ch, cmd_on); + cmd_stage = ST_READ; + ext_send_fc03(); cmd_tick = now; return; } - if (cmd_stage == 1) { /* FC05 */ - if (rx_echo_ok) { - rx_echo_ok = 0; - ext_note_success(0); - cmd_stage = 2; - cmd_tick = now; - } else if (rx_exc || (now - cmd_tick) > EXT_RESP_TIMEOUT_MS) { - rx_exc = 0; - if (cmd_retry < 1) { - cmd_retry++; + /* û: FC03 Ԥ(ж+ǰ״̬) */ + if (cmd_pending && cmd_stage == ST_IDLE) { + cmd_pending = 0; + cmd_txn = TXN_CMD; + cmd_retry = 0; + cmd_stage = ST_READ; + ext_send_fc03(); + cmd_tick = now; + return; + } + + if (cmd_stage == ST_READ) { + if (rx_state_ok) { + rx_state_ok = 0; + ext_note_success(1); + if (s_fault != s_fault_logged) { /* λ仯Ŵӡ */ + s_fault_logged = s_fault; + if (s_fault) { + log_warn("> EXT: 쳣־=%02X (=%02X =%02X)", s_fault, s_state, s_fb); + } + } + if (cmd_txn == TXN_CMD) { + /* ǰ״̬(һ: У鷴, ӻгɹ) */ + cmd_state_before = (uint8_t)((s_state >> (cmd_ch - 1)) & 1u); + cmd_fb_before = (uint8_t)((s_fb >> (cmd_ch - 1)) & 1u); + cmd_retry = 0; + cmd_stage = ST_ECHO; ext_send_fc05(cmd_ch, cmd_on); cmd_tick = now; } else { - log_warn("> EXT: SW%d Ӧ", cmd_ch + 4); - if (!cmd_is_restore) ext_respond(0, "ext relay no response"); - else { cmd_is_restore = 0; restore_next_tick = now + 2000; } - ext_note_fail(); - cmd_stage = 0; + cmd_txn = TXN_NONE; + cmd_stage = ST_IDLE; /* ϵ̽ */ + } + } else if (rx_exc || (now - cmd_tick) > EXT_RESP_TIMEOUT_MS) { + rx_exc = 0; + if (cmd_txn == TXN_CMD) { + ext_txn_retry_or_fail(0, "ext relay board offline"); + } else { + ext_txn_retry_or_fail(0, ""); } } return; } - if (cmd_stage == 2) { /* /ȶʱ */ + if (cmd_stage == ST_ECHO) { /* FC05 */ + if (rx_echo_ok) { + rx_echo_ok = 0; + ext_note_success(0); + cmd_stage = ST_SETTLE; + cmd_tick = now; + } else if (rx_exc || (now - cmd_tick) > EXT_RESP_TIMEOUT_MS) { + rx_exc = 0; + ext_txn_retry_or_fail(1, "ext relay no response"); + } + return; + } + + if (cmd_stage == ST_SETTLE) { /* /ȶʱ */ if ((now - cmd_tick) >= EXT_FB_SETTLE_MS) { cmd_retry = 0; - cmd_stage = 3; + cmd_stage = ST_CONFIRM; ext_send_fc03(); cmd_tick = now; } return; } - if (cmd_stage == 3) { /* FC03 ״̬Ӧ(βѯ) */ + if (cmd_stage == ST_CONFIRM) { /* У */ if (rx_state_ok) { rx_state_ok = 0; ext_note_success(1); - if (cmd_is_restore) { - /* ϵָһ·: λ, ȫɲͳһ+ϱ */ - cmd_is_restore = 0; + if (cmd_txn == TXN_RESTORE) { + cmd_txn = TXN_NONE; restore_diff &= (uint8_t)~(1u << (cmd_ch - 1)); if (!restore_diff) { log_info("> EXT: ϵ״ָ̬ (=%02X)", s_state); ext_save_state(); relayStatueUpdata(); } - } else if (cmd_is_poll) { - cmd_is_poll = 0; - if (s_fault != s_fault_logged) { /* λ仯Ŵӡ, ֹÿ 2s ˢ */ - s_fault_logged = s_fault; - if (s_fault) { - log_warn("> EXT: 쳣־=%02X (=%02X =%02X)", s_fault, s_state, s_fb); - } - } } else { /* ؼ̵ͬ: λ=Ŀ λ=Ŀ * (ǰ!=Ŀ, =Ŀ); ظԭĿ̬ĻҪ @@ -325,54 +380,31 @@ void ext_relay_process(void) cmd_state_before, cmd_fb_before); ext_respond(0, "Load unpowered / relay failure !"); /* ʧİһ */ } + cmd_txn = TXN_NONE; ext_save_state(); /* ϵ: ʵ״̬ */ relayStatueUpdata(); /* ɺϱȫ״̬ */ } - cmd_stage = 0; + cmd_stage = ST_IDLE; } else if (rx_exc || (now - cmd_tick) > EXT_RESP_TIMEOUT_MS) { rx_exc = 0; - if (cmd_retry < 1) { - cmd_retry++; - ext_send_fc03(); - cmd_tick = now; - } else { - if (!cmd_is_poll && !cmd_is_restore) { - log_warn("> EXT: SW%d ״̬ȷӦ", cmd_ch + 4); - ext_respond(0, "ext relay no response"); - } - cmd_is_poll = 0; - if (cmd_is_restore) { cmd_is_restore = 0; restore_next_tick = now + 2000; } - ext_note_fail(); - cmd_stage = 0; - } + ext_txn_retry_or_fail(0, "ext relay no response"); } return; } - /* ϵָ: · FC05 ʹչص EEPROM ״̬ */ - if (restore_diff && now >= restore_next_tick) { + /* ϵָ: · FC05 ʹչص EEPROM ״̬(ʱ) */ + if (restore_diff && now >= restore_next_tick && cmd_stage == ST_IDLE && !cmd_pending) { uint8_t bit = (uint8_t)(restore_diff & (uint8_t)(-(int)restore_diff)); /* ʹָλ */ uint8_t ch = 1; while (!(bit & 1)) { bit >>= 1; ch++; } - cmd_is_poll = 0; - cmd_is_restore = 1; + cmd_txn = TXN_RESTORE; cmd_ch = ch; cmd_on = (uint8_t)((restore_target >> (ch - 1)) & 1u); cmd_retry = 0; - cmd_stage = 1; + cmd_stage = ST_ECHO; ext_send_fc05(cmd_ch, cmd_on); cmd_tick = now; log_info("> EXT: ָ SW%d -> %s", ch + 4, cmd_on ? "" : ""); return; } - - /* : ѯ(߼) */ - if ((now - s_last_poll) >= EXT_POLL_INTERVAL_MS) { - s_last_poll = now; - cmd_is_poll = 1; - cmd_retry = 0; - cmd_stage = 3; - ext_send_fc03(); - cmd_tick = now; - } } diff --git a/版本说明.md b/版本说明.md index f5f38a8..8f2901e 100644 --- a/版本说明.md +++ b/版本说明.md @@ -16,6 +16,11 @@ - 协议文档:`Docs/RS485扩展继电器协议.md`(从机板按此实现) ### 追加(2026-09-04) +- **扩展板检测改为按需触发,取消 2s 周期轮询**(平时总线零流量): + 上电 3s 一次性 FC03 探测(确认在线 + 触发断电恢复比对); + 控制命令改三段式:FC03 预读(判定在线 + 快照命令前反馈)→ FC05 → 150ms → FC03 校验跳变; + 命令链路最终无应答即判离线并应答平台 `ext relay board offline`,下次命令自动重新探测恢复; + 断电恢复触发点不变(离线→上线比对 EEPROM 逐路补发) - **扩展板状态断电记忆+上电恢复**(与板载继电器一致):状态位掩码存 `MqttInfoStr.Relay_State[5]`(原保留字节, bit0~3=扩展 1~4 路,0xFF 空白视为全断),复用 EEPROM 偏移 102 现有落盘路径; 扩展板每次离线→上线时比对 FC03 实际状态与 EEPROM 记忆,不一致的回路逐路补发 FC05 纠正