From 6df97c1e3736395c74e968fbfca9ae012cf00f22 Mon Sep 17 00:00:00 2001 From: helei Date: Sat, 8 Aug 2026 21:50:39 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20RECOVERY=20=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=EF=BC=9A=E9=80=8F=E4=BC=A0=E6=80=81=E4=B8=8B=E5=85=88?= =?UTF-8?q?=20+++=20=E9=80=80=E9=80=8F=E4=BC=A0=E5=86=8D=20AT+RESTORE?= =?UTF-8?q?=EF=BC=88V1.2~V1.4=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- V1.2/STM32F103_App1/HardWare/WIFI/esp8266.c | 24 +- V1.2/STM32F103_App1/HardWare/WIFI/esp8266.h | 1 + V1.3/STM32F103_App1/HardWare/WIFI/esp8266.c | 7 +- .../HardWare/NETWORK/net_wifi.c | 19 ++ .../HardWare/NETWORK/net_wifi.h | 1 + .../HardWare/SYSINIT/app_loop.c | 2 +- .../HardWare/SYSINIT/app_loop.c.bak | 253 ++++++++++++++++++ .../HardWare/USER_CMD/user_cmd.c | 14 +- V1.4/STM32F103_App1/HardWare/WIFI/esp8266.c | 7 +- 9 files changed, 311 insertions(+), 17 deletions(-) create mode 100644 V1.4/STM32F103_App1/HardWare/SYSINIT/app_loop.c.bak diff --git a/V1.2/STM32F103_App1/HardWare/WIFI/esp8266.c b/V1.2/STM32F103_App1/HardWare/WIFI/esp8266.c index 1516ad1..a477f0f 100644 --- a/V1.2/STM32F103_App1/HardWare/WIFI/esp8266.c +++ b/V1.2/STM32F103_App1/HardWare/WIFI/esp8266.c @@ -277,12 +277,32 @@ int esp_smartconfig(void) return 0; } +/*==== 退出透传模式 ==== + * "+++" 前后各需 1s 静默 guard time,且不能带 \r\n; + * 回命令态后顺手关掉当前 TCP 连接(失败忽略,后续 esp_reset 会兜底) */ +int esp_exit_transparent(void) +{ + bg_delay(1100); + esp_send((uint8_t *)"+++", 3); + bg_delay(1100); + if (esp_send_cmd("AT", "OK", 2000) != 0) { + log_warn("> WiFi: exit transparent no AT reply"); + return -1; + } + esp_send_cmd("AT+CIPCLOSE", "OK", 2000); + log_info("> WiFi: exit transparent OK"); + return 0; +} + /*==== 恢复出厂设置:AT+RESTORE,模块忘掉已保存的热点(用于测试 SmartConfig)==== - * 注意:RESTORE 后模块自动重启且波特率回到出厂 115200, - * 这里发送后主动把 UART4 切回 115200 探测确认 */ + * 注意 1:RESTORE 后模块自动重启且波特率回到出厂 115200, + * 这里发送后主动把 UART4 切回 115200 探测确认。 + * 注意 2:WiFi 已连上时模块处于 TCP 透传态,直接发 AT 会被当作 TCP 数据 + * 发往服务器(模块永远收不到),必须先 "+++" 退透传回命令态! */ int esp_factory_restore(void) { log_info("> WiFi: AT+RESTORE (forget saved AP)"); + esp_exit_transparent(); /* 透传中先退回命令态;非透传时调用无害 */ esp_rx_clear(); ESP_printf("AT+RESTORE"); bg_delay(2500); /* 等模块执行 RESTORE 并完成重启 */ diff --git a/V1.2/STM32F103_App1/HardWare/WIFI/esp8266.h b/V1.2/STM32F103_App1/HardWare/WIFI/esp8266.h index a606f17..481a6ab 100644 --- a/V1.2/STM32F103_App1/HardWare/WIFI/esp8266.h +++ b/V1.2/STM32F103_App1/HardWare/WIFI/esp8266.h @@ -38,6 +38,7 @@ int esp_join_fixed_ap(void); /* 连固定SSID(60s int esp_wait_got_ip(int timeout_s); /* 等已存AP自动连接拿IP, 0=成功 */ int esp_smartconfig(void); /* ESP-Touch智能配网(60s), 0=成功 */ int esp_enter_transparent(const char *ip, uint16_t port); /* 连TCP并进透传, 0=成功 */ +int esp_exit_transparent(void); /* "+++" 退透传+CIPCLOSE, 0=成功 */ int esp_factory_restore(void); /* AT+RESTORE 恢复出厂(忘掉已存AP), 0=成功 */ void esp_send(const uint8_t *data, uint16_t len); /* 透传模式原始数据发送 */ void esp_rx_clear(void); /* 清空共享接收缓冲 */ diff --git a/V1.3/STM32F103_App1/HardWare/WIFI/esp8266.c b/V1.3/STM32F103_App1/HardWare/WIFI/esp8266.c index 2e5ba28..82dc3e6 100644 --- a/V1.3/STM32F103_App1/HardWare/WIFI/esp8266.c +++ b/V1.3/STM32F103_App1/HardWare/WIFI/esp8266.c @@ -303,11 +303,14 @@ int esp_exit_transparent(void) } /*==== 恢复出厂设置:AT+RESTORE,模块忘掉已保存的热点(用于测试 SmartConfig)==== - * 注意:RESTORE 后模块自动重启且波特率回到出厂 115200, - * 这里发送后主动把 UART4 切回 115200 探测确认 */ + * 注意 1:RESTORE 后模块自动重启且波特率回到出厂 115200, + * 这里发送后主动把 UART4 切回 115200 探测确认。 + * 注意 2:WiFi 已连上时模块处于 TCP 透传态,直接发 AT 会被当作 TCP 数据 + * 发往服务器(模块永远收不到),必须先 "+++" 退透传回命令态! */ int esp_factory_restore(void) { log_info("> WiFi: AT+RESTORE (forget saved AP)"); + esp_exit_transparent(); /* 透传中先退回命令态;非透传时调用无害 */ esp_rx_clear(); ESP_printf("AT+RESTORE"); bg_delay(2500); /* 等模块执行 RESTORE 并完成重启 */ diff --git a/V1.4/STM32F103_App1/HardWare/NETWORK/net_wifi.c b/V1.4/STM32F103_App1/HardWare/NETWORK/net_wifi.c index fd8aebb..c25f9a4 100644 --- a/V1.4/STM32F103_App1/HardWare/NETWORK/net_wifi.c +++ b/V1.4/STM32F103_App1/HardWare/NETWORK/net_wifi.c @@ -20,6 +20,11 @@ volatile uint8_t g_wifi_mqtt_ready = 0; volatile uint8_t g_wifi_first_publish_ok = 0; +/* WiFi 模块恢复出厂请求标志:由蓝牙 RECOVERY 命令(sys任务)置位, + * net 任务在 net_wifi_process 里执行——所有 UART4 操作都收口在 net 任务, + * 避免与 4G 切换/建链并发冲突 */ +volatile uint8_t g_wifi_restore_req = 0; + static uint32_t s_ping_tick = 0; /* 上行发布主题暂存 */ @@ -102,6 +107,20 @@ int net_wifi_init(void) /*==== 非阻塞轮询:下行报文解析 + 心跳保活 ====*/ void net_wifi_process(void) { + /* 恢复出厂请求(蓝牙 RECOVERY):在 net 任务上下文执行, + * 不与本任务的建链/收发并发 */ + if (g_wifi_restore_req) { + g_wifi_restore_req = 0; + log_warn("> WiFi: restore request, AT+RESTORE & reboot"); + g_net_tx_busy = 1; + g_wifi_mqtt_ready = 0; + if (esp_factory_restore() != 0) { + log_warn("> WiFi: RESTORE failed, reboot anyway"); + } + HAL_Delay(100); + NVIC_SystemReset(); + } + if (!g_wifi_mqtt_ready) return; int r; diff --git a/V1.4/STM32F103_App1/HardWare/NETWORK/net_wifi.h b/V1.4/STM32F103_App1/HardWare/NETWORK/net_wifi.h index 41330df..65f4b7d 100644 --- a/V1.4/STM32F103_App1/HardWare/NETWORK/net_wifi.h +++ b/V1.4/STM32F103_App1/HardWare/NETWORK/net_wifi.h @@ -9,6 +9,7 @@ extern volatile uint8_t g_wifi_mqtt_ready; extern volatile uint8_t g_wifi_first_publish_ok; +extern volatile uint8_t g_wifi_restore_req; /* 置1请求 WiFi 模块恢复出厂(net任务执行) */ int net_wifi_init(void); /* 阻塞建链:复位→连AP(已存→固定→SmartConfig)→透传→MQTT, 0=就绪 */ void net_wifi_process(void); /* 非阻塞轮询:解析下行报文 + 心跳保活 */ diff --git a/V1.4/STM32F103_App1/HardWare/SYSINIT/app_loop.c b/V1.4/STM32F103_App1/HardWare/SYSINIT/app_loop.c index d7ebad5..16bdbde 100644 --- a/V1.4/STM32F103_App1/HardWare/SYSINIT/app_loop.c +++ b/V1.4/STM32F103_App1/HardWare/SYSINIT/app_loop.c @@ -199,7 +199,7 @@ static void sys_task(void *arg) if (now - up_print >= 30000) { up_print = now; uint32_t mins = (uint32_t)(up_ms / 60000); - log_info("> Uptime: %ud %02u:%02u", + log_info("> Uptime: %uday--%02uh--%02um\r\n", (unsigned)(mins / 1440), (unsigned)((mins % 1440) / 60), (unsigned)(mins % 60)); diff --git a/V1.4/STM32F103_App1/HardWare/SYSINIT/app_loop.c.bak b/V1.4/STM32F103_App1/HardWare/SYSINIT/app_loop.c.bak new file mode 100644 index 0000000..15fa4a3 --- /dev/null +++ b/V1.4/STM32F103_App1/HardWare/SYSINIT/app_loop.c.bak @@ -0,0 +1,253 @@ +/* app_loop.c - FreeRTOS 任务调度封装 + * + * 任务划分(裸机主循环轮询项拆分而来): + * net_task(优先级3, 栈3KB) - 网络状态机 NET_process:4G/WiFi 建链、收发、OTA 下载 + * app_task(优先级2, 栈2KB) - 业务:继电器命令/反馈状态机、投喂秤、状态上报、OTA 确认 + * sys_task(优先级1, 栈1KB) - 系统:LED、电量、蓝牙调试命令、运行时长打印 + * idle 钩子 - 喂 IWDG(只要调度器正常,看门狗就不会断粮; + * 有任务死循环占 CPU 时 idle 饿死 → 复位,正是想要的行为) + * + * 共享资源互斥: + * g_net_at_mutex - NET_publish_* 与长 AT 会话(建链/重连)互斥,拿锁失败直接丢弃 + * s_storage_mutex - EEPROM(软件I2C) / W25Q64(软件SPI) 跨任务访问互斥(storage_lock) + * log 输出 - log.c 内部互斥,多任务日志不交织 + */ + +#include "app_loop.h" +#include "app_common.h" +#include "user_cmd.h" +#include "relay.h" +#include "network.h" +#include "led.h" +#include "adc.h" +#include "feed_scale.h" +#include "iic.h" +#include "24c02.h" +#include "uart.h" +#include "../W25Q64/w25q64.h" + +#include "FreeRTOS.h" +#include "task.h" +#include "semphr.h" + +/*==== 存储总线互斥锁(EEPROM/W25Q64 共用一把递归锁)====*/ +static SemaphoreHandle_t s_storage_mutex = NULL; + +void storage_lock_init(void) +{ + s_storage_mutex = xSemaphoreCreateRecursiveMutex(); +} + +void storage_lock(void) +{ + if (s_storage_mutex && xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) { + xSemaphoreTakeRecursive(s_storage_mutex, portMAX_DELAY); + } +} + +void storage_unlock(void) +{ + if (s_storage_mutex && xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) { + xSemaphoreGiveRecursive(s_storage_mutex); + } +} + +/*==== 业务模块初始化(调度器启动前调用,与裸机版一致)====*/ +void app_init(void) +{ + log_info(""); + log_info("* * * * * * * * * * * * * * * * * *"); + log_info("* Version: 1.4.0 (RTOS) *"); + log_info("* * * * * * * By Helei * * * *"); + + /* EEPROM 中的 MQTT/版本配置 */ + iicInit(); + eepromReadInfo(); + log_info("> Server: %s:%d", MqttInfoStr.ServerIP, MqttInfoStr.ServerPort); + log_info("> Client: %s", MqttInfoStr.ClientID); + log_info("> User : %s", MqttInfoStr.Username); + log_info("> Firmware Ver: %u", MqttInfoStr.Ver); + + /* W25Q64 外部 Flash,用于 OTA 固件暂存 */ + W25Q64_Init(); + + /* 继电器 GPIO(控制输出 + 反馈输入) */ + relayInit(); + + /* RS485 喂料秤控制模块 */ + feedScaleInit(); + + /* LED(LED2 心跳同时兼任外部看门狗 TPL5010 喂狗) */ + ledInit(); + + /* ADC 电量检测 */ + adcInit(); + update_batter(); + + /* 网络模块初始化:按 EEPROM 记忆的上次成功方式优先(4G 或 WiFi) */ + NET_init(); + + /* 启动各路串口接收 */ + USART2_StartRx(); /* RS485 喂料秤 */ + USART3_StartRx(); /* 4G MQTT 消息,DMA+IDLE */ + USART1_StartRx(); /* 蓝牙调试命令 */ +} + +/*==== net_task:网络状态机(4G/WiFi 建链、收发、OTA)====*/ +static void net_task(void *arg) +{ + (void)arg; + for (;;) { + NET_process(); + vTaskDelay(5); + } +} + +/*==== app_task:业务逻辑(继电器/秤/上报/OTA 确认)====*/ +static void app_task(void *arg) +{ + (void)arg; + + /* 主循环状态变量 */ + static uint8_t ota_confirmed = 0; + static uint32_t ota_confirm_tick = 0; + static uint32_t mqtt_ready_tick = 0; + static uint8_t mqtt_ready_recorded = 0; + static uint8_t mqtt_first_status_reported = 0; + + for (;;) { + /* 记录 MQTT 首次就绪时刻 */ + if (!mqtt_ready_recorded && g_net_mqtt_ready) { + mqtt_ready_recorded = 1; + mqtt_ready_tick = HAL_GetTick(); + } + + /* MQTT 首次就绪后:先按 EEPROM 恢复继电器上次状态(断电记忆), + * 再上报一次完整状态,让平台刷新为真实状态 */ + if (g_net_mqtt_ready && !mqtt_first_status_reported) { + mqtt_first_status_reported = 1; + relayRestoreState(); + relayStatueUpdata(); + log_info("> Main: first status report after MQTT ready"); + ota_confirm_tick = HAL_GetTick(); + } + + /* 首次发布成功后延迟 3s 再 OTA 确认 */ + if (!ota_confirmed && g_net_mqtt_ready && g_net_first_publish_ok && + (HAL_GetTick() - ota_confirm_tick) > 3000) { + ota_confirmed = 1; + NET_ota_confirm(); + log_info("> OTA confirmed (MQTT ready + first publish ok + 3s delay)"); + } + + /* MQTT 就绪后 1 分钟仍未首次 MPUB,主动复位 */ + if (!ota_confirmed && mqtt_ready_recorded && + (HAL_GetTick() - mqtt_ready_tick) > OTA_HEALTH_TIMEOUT_MS) { + log_warn("> OTA health timeout: MQTT ready but no MPUB in 1min, reset"); + W25Q64_OTA_IncTrial(); /* 软件复位 BL 不计次,这里自行消耗一次试错 */ + HAL_Delay(100); + NVIC_SystemReset(); + } + + /* 继电器命令响应状态机 */ + relayCmdProcess(); + + /* 延迟状态上报 */ + if (relay_report_pending && (HAL_GetTick() - relay_report_tick) > 100) { + relay_report_pending = 0; + relayStatueUpdata(); + } + + /* 喂料秤轮询与投喂保护 */ + feedScaleProcess(); + + vTaskDelay(10); + } +} + +/*==== sys_task:系统辅助(LED/电量/蓝牙命令/运行时长)====*/ +static void sys_task(void *arg) +{ + (void)arg; + uint32_t batt_tick = HAL_GetTick(); + uint32_t up_last = HAL_GetTick(); + uint32_t up_print = HAL_GetTick(); + uint64_t up_ms = 0; + + for (;;) { + /* 蓝牙调试命令 */ + process_usart1_command(); + + /* 联网指示:连接中闪烁,连上常亮(led.c activeEvents 据此控制 PA1) */ + g_net_led_connected = g_net_mqtt_ready ? 1 : 0; + + /* LED 心跳/联网指示 */ + activeEvents(); + + /* 电量 1s 节流更新 */ + if (HAL_GetTick() - batt_tick >= 1000) { + batt_tick = HAL_GetTick(); + update_batter(); + } + + /* 运行时长心跳:每 30s 串口输出一次(天/时/分), + * 64 位累计,HAL_GetTick 约 49 天回绕也不受影响 */ + { + uint32_t now = HAL_GetTick(); + up_ms += (uint32_t)(now - up_last); /* 32位差值天然容忍回绕 */ + up_last = now; + if (now - up_print >= 30000) { + up_print = now; + uint32_t mins = (uint32_t)(up_ms / 60000); + log_info("> Uptime: %uday %02uh:%02um\r\n", + (unsigned)(mins / 1440), + (unsigned)((mins % 1440) / 60), + (unsigned)(mins % 60)); + } + } + + vTaskDelay(20); + } +} + +/*==== FreeRTOS 钩子 ====*/ + +/* idle 钩子:喂独立看门狗。有任务死循环占 CPU 时 idle 饿死 → IWDG 复位 */ +void vApplicationIdleHook(void) +{ + IWDG_Feed(); +} + +void vApplicationMallocFailedHook(void) +{ + log_error("> RTOS: malloc failed (heap exhausted)"); + taskDISABLE_INTERRUPTS(); + for (;;) { } +} + +void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) +{ + (void)xTask; + log_error("> RTOS: stack overflow in task %s", pcTaskName); + taskDISABLE_INTERRUPTS(); + for (;;) { } +} + +/*==== 任务创建与调度器启动(main 调用,不再返回)====*/ +void app_rtos_start(void) +{ + /* 互斥锁先于任务创建 */ + log_lock_init(); + storage_lock_init(); + NET_LockInit(); + + xTaskCreate(net_task, "net", 768, NULL, 3, NULL); /* 栈 3KB:AT/OTA 有大的 sprintf 缓冲 */ + xTaskCreate(app_task, "app", 512, NULL, 2, NULL); /* 栈 2KB */ + xTaskCreate(sys_task, "sys", 256, NULL, 1, NULL); /* 栈 1KB */ + + vTaskStartScheduler(); + + /* 不会走到这里(堆不足创建失败才会) */ + log_error("> RTOS: scheduler failed to start"); + for (;;) { } +} diff --git a/V1.4/STM32F103_App1/HardWare/USER_CMD/user_cmd.c b/V1.4/STM32F103_App1/HardWare/USER_CMD/user_cmd.c index 8d64aa2..58709b2 100644 --- a/V1.4/STM32F103_App1/HardWare/USER_CMD/user_cmd.c +++ b/V1.4/STM32F103_App1/HardWare/USER_CMD/user_cmd.c @@ -17,6 +17,7 @@ #include "uart.h" #include "log.h" #include "network.h" +#include "net_wifi.h" #include "esp8266.h" /* 本地命令字符串不区分大小写比较 */ @@ -56,8 +57,6 @@ void process_usart1_command(void) if (cmd_is(cmd, "RESET")) { log_warn("> USART1 RESET command received, rebooting..."); - HAL_Delay(100); - NVIC_SystemReset(); } else if (cmd_is(cmd, "INFO")) { log_info("> BT INFO: ver=%u, server=%s:%d, client=%s", @@ -87,23 +86,18 @@ void process_usart1_command(void) } else if (cmd_is(cmd, "OTA")) { log_info("> BT OTA: reboot to bootloader for OTA check"); - HAL_Delay(100); - NVIC_SystemReset(); } else if (cmd_is(cmd, "RECOVERY")) { /* WiFi 模块 AT+RESTORE 恢复出厂(忘掉已保存的热点),用于测试 SmartConfig; * 仅 WiFi 通道激活时才操作模块(UART4 与 4G 共享接收缓冲, * 4G 模式下动 UART4 会干扰 4G 数据),随后整机复位走完整重连流程 */ if (NET_GetActiveBackend() == NET_BACKEND_WIFI) { - log_warn("> BT RECOVERY: restore WiFi module & reboot"); - if (esp_factory_restore() != 0) { - log_warn("> BT RECOVERY: RESTORE failed, reboot anyway"); - } + log_warn("> BT RECOVERY: WiFi restore scheduled"); g_wifi_restore_req = 1; } else { log_warn("> BT RECOVERY: 4G mode, WiFi module untouched, reboot only"); + HAL_Delay(100); + NVIC_SystemReset(); } - HAL_Delay(100); - NVIC_SystemReset(); } else if (cmd_is(cmd, "HELP")) { log_info("CMD: RESET, INFO, STATUS, R1ON/R1OFF~R4ON/R4OFF, ALLON, ALLOFF, OTA, RECOVERY, HELP"); diff --git a/V1.4/STM32F103_App1/HardWare/WIFI/esp8266.c b/V1.4/STM32F103_App1/HardWare/WIFI/esp8266.c index 2e5ba28..82dc3e6 100644 --- a/V1.4/STM32F103_App1/HardWare/WIFI/esp8266.c +++ b/V1.4/STM32F103_App1/HardWare/WIFI/esp8266.c @@ -303,11 +303,14 @@ int esp_exit_transparent(void) } /*==== 恢复出厂设置:AT+RESTORE,模块忘掉已保存的热点(用于测试 SmartConfig)==== - * 注意:RESTORE 后模块自动重启且波特率回到出厂 115200, - * 这里发送后主动把 UART4 切回 115200 探测确认 */ + * 注意 1:RESTORE 后模块自动重启且波特率回到出厂 115200, + * 这里发送后主动把 UART4 切回 115200 探测确认。 + * 注意 2:WiFi 已连上时模块处于 TCP 透传态,直接发 AT 会被当作 TCP 数据 + * 发往服务器(模块永远收不到),必须先 "+++" 退透传回命令态! */ int esp_factory_restore(void) { log_info("> WiFi: AT+RESTORE (forget saved AP)"); + esp_exit_transparent(); /* 透传中先退回命令态;非透传时调用无害 */ esp_rx_clear(); ESP_printf("AT+RESTORE"); bg_delay(2500); /* 等模块执行 RESTORE 并完成重启 */