From 8f0e137c57e0675c08ca534751961b279a43dabb Mon Sep 17 00:00:00 2001 From: helei Date: Fri, 4 Sep 2026 21:23:05 +0800 Subject: [PATCH] =?UTF-8?q?485=E6=89=A9=E5=B1=95=E7=BB=A7=E7=94=B5?= =?UTF-8?q?=E5=99=A8=E6=9D=BF=E7=8A=B6=E6=80=81=E6=96=AD=E7=94=B5=E8=AE=B0?= =?UTF-8?q?=E5=BF=86+=E4=B8=8A=E7=BA=BF=E6=81=A2=E5=A4=8D(Relay=5FState[5]?= =?UTF-8?q?=E4=BD=8D=E6=8E=A9=E7=A0=81=E5=AD=98EEPROM,=E7=A6=BB=E7=BA=BF->?= =?UTF-8?q?=E4=B8=8A=E7=BA=BF=E8=87=AA=E5=8A=A8=E6=AF=94=E5=AF=B9=E7=BA=A0?= =?UTF-8?q?=E6=AD=A3);=E7=89=88=E6=9C=AC=E5=8F=B7=E4=B8=8D=E5=8A=A8?= =?UTF-8?q?=E4=BF=9D=E6=8C=811.7.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AI项目上下文.md | 4 +- Docs/RS485扩展继电器协议.md | 4 +- STM32F103_App1/HardWare/RELAY/ext_relay.c | 78 +++++++++++++++++++++-- 版本说明.md | 9 ++- 4 files changed, 84 insertions(+), 11 deletions(-) diff --git a/AI项目上下文.md b/AI项目上下文.md index fc5b560..e4bc2b0 100644 --- a/AI项目上下文.md +++ b/AI项目上下文.md @@ -68,7 +68,7 @@ | KEY1~4 | **PC6/PC7/PB14/PB15 上拉输入,功能预留** | | 取消 | PA8 手动模式开关(MANUAL_MODE 已删) | -**485 总线多从机(V1.7.1 起)**:称重秤 0x01(Modbus 读重量)+ 扩展继电器板 0x10(FC05 控制/FC03 读状态,协议见 `Docs/RS485扩展继电器协议.md`);收帧统一在 feedScaleProcess 按站号分发,发送共用 uart.c 的 rs485_bus 时间戳做 50ms 静默保护;平台标识符 sw5~sw8,蓝牙 R5ON~R8OFF +**485 总线多从机(V1.7.1 起)**:称重秤 0x01(Modbus 读重量)+ 扩展继电器板 0x10(FC05 控制/FC03 读状态,协议见 `Docs/RS485扩展继电器协议.md`);收帧统一在 feedScaleProcess 按站号分发,发送共用 uart.c 的 rs485_bus 时间戳做 50ms 静默保护;平台标识符 sw5~sw8,蓝牙 R5ON~R8OFF;扩展板状态断电记忆(Relay_State[5] 位掩码),离线→上线自动比对恢复 ## 4. 云平台协议 @@ -81,7 +81,7 @@ ## 5. EEPROM 布局(AT24C02,两个终端共用) - 0~110:`MqttInfo_Str`(ClientID/Username/Password/ServerIP/Topic/Port/Relay_State[7]/Magic=0xDEADBEEF/Ver) -- 102:继电器状态;120~124:秤去皮/单次投料/初始化标志 +- 102:继电器状态(Relay_State[1~4]=板载 sw1~sw4,**[5]=485 扩展板 sw5~sw8 位掩码,0xFF 空白视为全断**);120~124:秤去皮/单次投料/初始化标志 - 132~157:BLE 名缓存;158:BLE 功率;160:日志等级 - 164~171:电能脉冲(控制终端);172:产品模式;173:GPS 开关;175~194:OTA 任务 id - **200~244:WiFi 热点配置(magic=0xA6@200,SSID 22B@201,PASS 22B@223)** diff --git a/Docs/RS485扩展继电器协议.md b/Docs/RS485扩展继电器协议.md index 4e42929..58dfc90 100644 --- a/Docs/RS485扩展继电器协议.md +++ b/Docs/RS485扩展继电器协议.md @@ -75,7 +75,8 @@ ## 4. 从机行为要求 -1. **上电默认 4 路全部断开**(主控不负责恢复扩展板断电前状态) +1. **上电默认 4 路全部断开**(主控侧有断电记忆:每次离线→上线时,主控会比对 EEPROM 记忆状态与 REG0 实际状态, + 不一致的回路会**逐路补发 FC05 纠正**——所以从机上线后紧跟着收到几条 FC05 属正常恢复流程,务必如实执行并回显) 2. FC05 收到并校验通过(地址/功能码/线圈范围/CRC)后**立即执行**再回显 3. 反馈输入:每路继电器配负载反馈检测(同主控板载继电器:吸合后负载得电=高)。从机自行维护 REG2 故障位:某路输出状态与反馈不一致**持续超过 1s** 置 1,恢复一致后清 0 4. 反馈采样建议 10~50ms 周期,加简单消抖(如连续 3 次一致才更新 REG1) @@ -88,6 +89,7 @@ - **命令流程**:平台/蓝牙下发 → FC05 → 等回显(300ms 超时,补发 1 次)→ 延时 150ms 等触点稳定 → FC03 读状态 → 校验"输出位=目标状态 且 故障位=0" → 应答平台 OK/失败 → 上报全状态 - **周期轮询**:空闲每 2s 发一次 FC03,兼在线检测;连续 3 次无应答判离线(日志提示) +- **断电记忆恢复**:扩展板状态存在主控 EEPROM(偏移 102 的 Relay_State[5],bit0~3);每次离线→上线时比对实际状态与记忆,不一致的回路逐路补发 FC05 纠正,恢复完成统一上报一次 - **离线行为**:离线期间下发命令直接回复平台 "ext relay board offline";板子恢复应答后自动上线并补一次状态上报 - 上电 3s 内不发帧(避开上电窗口) diff --git a/STM32F103_App1/HardWare/RELAY/ext_relay.c b/STM32F103_App1/HardWare/RELAY/ext_relay.c index b98c9d5..5b60d00 100644 --- a/STM32F103_App1/HardWare/RELAY/ext_relay.c +++ b/STM32F103_App1/HardWare/RELAY/ext_relay.c @@ -10,6 +10,7 @@ #include "ext_relay.h" #include "relay.h" +#include "24c02.h" #include "uart.h" #include "log.h" #include "network.h" @@ -88,6 +89,7 @@ static uint8_t cmd_pending = 0; /* static uint8_t cmd_stage = 0; /* 0 1FC05 2ȶʱ 3FC03Ӧ */ 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}; @@ -96,17 +98,52 @@ static uint8_t rx_echo_ok = 0; static uint8_t rx_state_ok = 0; static uint8_t rx_exc = 0; +#define EXT_STATE_EEPROM_IDX 5 /* MqttInfoStr.Relay_State[5] ԭֽ: չ״̬λ */ + +/* ϵָ״̬ */ +static uint8_t restore_diff = 0; /* ָλ(ʵ!=) */ +static uint8_t restore_target = 0; /* ָĿ(EEPROM ) */ +static uint32_t restore_next_tick = 0; /* ָʧܺ˱ */ + +/* EEPROM չ״̬(0xFF=հΪȫ) */ +static uint8_t ext_saved_mask(void) +{ + uint8_t v = MqttInfoStr.Relay_State[EXT_STATE_EEPROM_IDX]; + if (v == 0xFF) return 0; + return (uint8_t)(v & 0x0F); +} + +/* FC03 ʵ״̬ˢ EEPROM(仯д) */ +static void ext_save_state(void) +{ + uint8_t nv = (uint8_t)(s_state & 0x0F); + if (MqttInfoStr.Relay_State[EXT_STATE_EEPROM_IDX] != nv) { + MqttInfoStr.Relay_State[EXT_STATE_EEPROM_IDX] = nv; + eepromWriteData(102, &MqttInfoStr.Relay_State[0], 7); + } +} + 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; } -static void ext_note_success(void) +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); relayStatueUpdata(); /* ߺһȫ״̬, ƽ̨ˢ sw5~sw8 */ + /* ϵָ: ͷ״̬ʱ, ȶ EEPROM , һµĻ·· */ + if (fresh_state) { + uint8_t saved = ext_saved_mask(); + uint8_t diff = (uint8_t)((s_state ^ saved) & 0x0F); + if (diff) { + restore_diff = diff; + restore_target = saved; + log_warn("> EXT: չ״̬ϵ䲻(ʵ=%02X =%02X), ·ָ", s_state, saved); + } + } } } @@ -208,7 +245,7 @@ void ext_relay_process(void) if (cmd_stage == 1) { /* FC05 */ if (rx_echo_ok) { rx_echo_ok = 0; - ext_note_success(); + ext_note_success(0); cmd_stage = 2; cmd_tick = now; } else if (rx_exc || (now - cmd_tick) > EXT_RESP_TIMEOUT_MS) { @@ -219,7 +256,8 @@ void ext_relay_process(void) cmd_tick = now; } else { log_warn("> EXT: SW%d Ӧ", cmd_ch + 4); - ext_respond(0, "ext relay no response"); + 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; } @@ -240,8 +278,17 @@ void ext_relay_process(void) if (cmd_stage == 3) { /* FC03 ״̬Ӧ(βѯ) */ if (rx_state_ok) { rx_state_ok = 0; - ext_note_success(); - if (cmd_is_poll) { + ext_note_success(1); + if (cmd_is_restore) { + /* ϵָһ·: λ, ȫɲͳһ+ϱ */ + cmd_is_restore = 0; + 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) { log_warn("> EXT: 쳣־=%02X (=%02X =%02X)", s_fault, s_state, s_fb); @@ -257,6 +304,7 @@ void ext_relay_process(void) cmd_ch + 4, cmd_on ? "" : "", s_state, s_fb, s_fault); ext_respond(0, "ext relay failed / load unpowered"); } + ext_save_state(); /* ϵ: ʵ״̬ */ relayStatueUpdata(); /* ɺϱȫ״̬ */ } cmd_stage = 0; @@ -267,11 +315,12 @@ void ext_relay_process(void) ext_send_fc03(); cmd_tick = now; } else { - if (!cmd_is_poll) { + 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; } @@ -279,6 +328,23 @@ void ext_relay_process(void) return; } + /* ϵָ: · FC05 ʹչص EEPROM ״̬ */ + if (restore_diff && now >= restore_next_tick) { + 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_ch = ch; + cmd_on = (uint8_t)((restore_target >> (ch - 1)) & 1u); + cmd_retry = 0; + cmd_stage = 1; + 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; diff --git a/版本说明.md b/版本说明.md index f312bbe..931ddb2 100644 --- a/版本说明.md +++ b/版本说明.md @@ -15,9 +15,14 @@ - **蓝牙命令新增 R5ON~R8OFF**;STATUS 显示扩展板在线状态与各路开关/故障位 - 协议文档:`Docs/RS485扩展继电器协议.md`(从机板按此实现) +### 追加(2026-09-04) +- **扩展板状态断电记忆+上电恢复**(与板载继电器一致):状态位掩码存 `MqttInfoStr.Relay_State[5]`(原保留字节, + bit0~3=扩展 1~4 路,0xFF 空白视为全断),复用 EEPROM 偏移 102 现有落盘路径; + 扩展板每次离线→上线时比对 FC03 实际状态与 EEPROM 记忆,不一致的回路逐路补发 FC05 纠正 + (从机上电默认全断,即实现"断电后重连恢复到断电前状态"),恢复完成统一保存+上报一次 + ### 说明 -- 扩展板不参与断电记忆恢复(从机上电默认全断);不挂扩展板时功能无影响(轮询无应答记离线日志) -- 无秤模式同样可用扩展板 +- 不挂扩展板时功能无影响(轮询无应答记离线日志);无秤模式同样可用扩展板 ## V1.7.0 —— 新硬件板型引脚重排(2026-09-04,不兼容老板,仓库迁至 shuichan_new)