fw V1.00.27: add IWDG watchdog (4s timeout, idle-hook fed with 3s task heartbeat window, DBG freeze, reset-cause banner) + 'hang' fire-test cmd

This commit is contained in:
evan.liu 2026-09-05 13:53:20 +08:00
parent f7edfa291d
commit e0253d1bbf
21 changed files with 6727 additions and 6387 deletions

View File

@ -5,7 +5,7 @@
本仓库是 **mothercup(母乳杯)** 产品的完整代码仓库,包含三大部分:
1. **设备固件** — 基于国民技术(Nations)**N32WB031 BLE SoC**(Cortex-M0,64MHz HSI,**板载硅片实测 256KB Flash**(0x01000000~0x0103FFFF,开发日志 §37),RAM 48KB+16KB):
- **`mcm-ddc-ble/`** — 唯一活跃的应用固件工程(APP,链接在 APP1 bank `0x01008000`/112KB)。以 SDK rdtss 例程为蓝本,集成 FreeRTOS、UART CLI、BLE 自定义 GATT 服务(CLI 透传 / 设备信息查询 / BLE OTA 双 bank 直写升级)、bootsetting 与 APP_DATA 参数区结构化读写命令。当前版本 **V1.00.26**(`mcm-ddc-ble/inc/app_version.h`)。历史上曾有 `mcm-ddc-04/` 主工程,**已删除**,其功能已全部并入本工程。
- **`mcm-ddc-ble/`** — 唯一活跃的应用固件工程(APP,链接在 APP1 bank `0x01008000`/112KB)。以 SDK rdtss 例程为蓝本,集成 FreeRTOS、UART CLI、BLE 自定义 GATT 服务(CLI 透传 / 设备信息查询 / BLE OTA 双 bank 直写升级)、bootsetting 与 APP_DATA 参数区结构化读写命令。当前版本 **V1.00.27**(`mcm-ddc-ble/inc/app_version.h`)。历史上曾有 `mcm-ddc-04/` 主工程,**已删除**,其功能已全部并入本工程。
- **`Boot/`** — 自写精简 bootloader(链接在 `0x01000000`/16KB):校验 bootsetting 记录自身完整性(magic + 结构体 CRC32)后按 active bank 的 `start_address` 直接跳转;**不校验镜像 CRC(CRC 校验在 OTA 升级过程中完成)**;记录无效或地址越界回退 APP1。
2. **手机 App `SmartAssiter/`** — uni-app **Vue3 + TypeScript** 工程(HBuilderX 项目管理,无 package.json),通过 BLE 连接设备:查看运行参数(温度/电压/转速/bank/堆等)、CLI 终端、参数读写(appget/appset)、OTA 升级页(e0005 锁步,已恢复)。另有登录/注册/用户信息等云端业务页面。
3. **PC 工具 `tools/`** — 整片烧录包制作/烧录脚本 + bleak 蓝牙测试客户端。
@ -55,7 +55,7 @@ mcm-ddc-ble/
├── src/
│ ├── main.c # 入口:VTOR 预置 → USART+banner → LED → app_ble_init → app_params_init
│ │ # → ns_sleep_lock_acquire(锁睡眠保 SWD) → 建 BLE 调度/LED/CLI 任务 → vTaskStartScheduler
│ ├── bsp_usart.c # USART1(PB6/PB7, 460800 8N1, V1.00.26 起):fputc 重定向 + DMA TX(CH1) + RX DMA 循环环形缓冲(CH2, 3KB, IDLE 中断唤醒;擦扇区关中断期间不丢字节) + TX 互斥锁
│ ├── bsp_usart.c # USART1(PB6/PB7, 460800 8N1, V1.00.26 起;IWDG 看门狗见 §50):fputc 重定向 + DMA TX(CH1) + RX DMA 循环环形缓冲(CH2, 3KB, IDLE 中断唤醒;擦扇区关中断期间不丢字节) + TX 互斥锁
│ ├── app_gpio.c # LED1(PB0)/LED2(PA6)
│ ├── app_cli.c # CLI UART 前端:CliTask 行接收/编辑/历史/Tab/Ctrl+Z,提示符 caiic->
│ ├── cli_core.c # CLI 核心(命令表 s_cmds[]/handler/分词执行),输出经可切换 cli_out_fn
@ -95,7 +95,7 @@ mcm-ddc-ble/
"D:\Keil_v5\UV4\UV4.exe" -b caiic_boot.uvprojx -j0 -o build.log # Boot 增量构建
```
要求保持 **0 Error(s), 0 Warning(s)**。当前基线(V1.00.26):`Code=48996 RO-data=4980 RW-data=2088 ZI-data=30144`。
要求保持 **0 Error(s), 0 Warning(s)**。当前基线(V1.00.27):`Code=49408 RO-data=5132 RW-data=2092 ZI-data=30140`。
**固件版本号约定(重要)**:每次修改固件代码必须递增 `mcm-ddc-ble/inc/app_version.h` 中的 `APP_FW_VERSION` 与 `APP_FW_VERSION_NUM`(格式 `0x00MMmmpp`,通常补丁位 +1),用于识别板上实际运行的固件;工程名与出包文件名保持不变。
@ -160,6 +160,7 @@ mcm-ddc-ble/
## 实时架构约定(固件)
- 使用 FreeRTOS V9.0.0 原生 API,**不使用 cmsis_os 封装**。
- **IWDG 看门狗(V1.00.27 起)**:4s 超时,idle hook 仅在 3s 内有任务心跳时喂狗(BLE/LED/CLI 任务循环打点 `app_wdt_heartbeat()`);SWD halt 时冻结(DBG_IWDG_STOP);banner 打印复位原因;`hang` 命令可验证点火。新增任务请在循环里打心跳。
- 任务:BLE 调度任务(栈 512 字,优先级 2,循环 `rwip_schedule(); vTaskDelay(1ms);`——**ke_msg/ke_timer 只允许在该任务上下文运行**);LED 任务(LED1 闪烁半周期读 APP_DATA 参数 `led1_blink_ms`);CliTask(优先级 3);CLI 输出经环形缓冲由 BLE 任务上下文发 notify(`ble_up.c`)。`configTOTAL_HEAP_SIZE = 18KB`(heap_4;SRAM 悬崖约束见 Flash/RAM 布局铁律)。
- SysTick 由 FreeRTOS port 接管:`main` 中不要手动 `SysTick_Config`,`n32wb03x_it.c` 中不要重复定义 SVC/PendSV/SysTick_Handler。
- USART1_IRQn 优先级为 3(最低),因 ISR 内调用 FreeRTOS FromISR API,必须 ≥ `configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY`(=3)。BLE_SW/FIFO IRQ 优先级 0,其 ISR 内禁止调用 FromISR API。

View File

@ -234,6 +234,7 @@ bootsetting 与 APP_DATA 保留区(0x01006000/8KB)的读写以 CLI 命令形
| `factory` | 恢复出厂设置:APP_DATA 参数区恢复缺省值并落盘后复位(**不动 bootsetting**) |
| `uartrst` | 复位串口:USART1 按 460800 8N1 重新初始化并清空 RX 队列 |
| `uartinfo` | 查询串口参数:引脚(PB6/PB7 AF4)、波特率/数据位/校验/停止位、TX DMA/RX 中断形态 |
| `hang` | 挂死 CLI 任务(IWDG 看门狗点火测试,V1.00.27 起;设备 ~4s 后复位) |
参数记录(APP_DATA 区头,52B):magic `0xCA12DA7A` + layout_ver + 参数字段 +
reserved[8] + CRC32;记录无效时固件以缺省值运行,`appset` 时落盘。

View File

@ -1264,3 +1264,24 @@ APP2 链接的镜像发向 APP1 区。设备端 END 校验(向量表 Reset PC
unzip 对 Compress-Archive 的反斜杠分隔符不兼容)。
- 验证:zip 解到仓库外独立目录,ble_ota.bat --uart COM4 全流程 PASS
(2.2s,27 kB/s,0 重传);flash_package.bat 用包内 NSpyocd 烧录 PASS。
## 50. 独立看门狗 IWDG(2026-09-05,V1.00.27)
- 起因:此前 CLI 卡死事故中 `reset` 命令无从执行(任务已死),系统无自救
手段。固件原本没有看门狗。
- 复位路径复查:当前固件 `reset`/`factory`/`appsw`/OTA 延迟复位均实测正常
(NVIC_SystemReset 有效);用户报告的"复位没生效"发生于 CLI 卡死状态,
命令根本无法到达执行路径——这正是看门狗的场景。
- 设计(main.c / app_wdt.h,驱动 n32wb03x_iwdg.c 已加入全部 4 个 target):
- IWDG:LSI 32kHz /128 → 250Hz,reload 1000 = **4s 超时**;
- **喂养策略**:FreeRTOS idle hook 只在"3s 内有任务心跳"时喂狗;BLE 调度/
LED/CLI 三个任务循环里打点 `app_wdt_heartbeat()`。任何任务死锁/死循环
(CLI 独占 CPU 饿死 idle,或全体阻塞 idle 空转无心跳)都会在 ≤4s 复位;
- `DBG_ConfigPeriph(DBG_IWDG_STOP, ENABLE)`:SWD halt 时冻结 IWDG,
调试会话不会被看门狗打断;
- 启动 banner 打印复位原因(RCC IWDGRSTF)并清标志。
- 新增调试命令 `hang`(CLI 任务忙等挂起,验证看门狗点火用)。
- 实测:正常喂狗系统稳定(12s+ 无重启、CLI 正常);`hang` 后 4.4s 复位,
新 banner 打印 "Last reset: IWDG watchdog!"。
- 附带修正:`uartinfo` 输出里 RX 描述更新为 DMA ring(此前还是 RXDNE 队列
的旧文案)。

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -532,6 +532,11 @@
<FileType>1</FileType>
<FilePath>..\..\nations-tec\N32WB03x_SDK_V2.0.0\firmware\n32wb03x_std_periph_driver\src\n32wb03x_qflash.c</FilePath>
</File>
<File>
<FileName>n32wb03x_iwdg.c</FileName>
<FileType>1</FileType>
<FilePath>..\..\nations-tec\N32WB03x_SDK_V2.0.0\firmware\n32wb03x_std_periph_driver\src\n32wb03x_iwdg.c</FilePath>
</File>
</Files>
</Group>
<Group>
@ -1688,6 +1693,11 @@
<FileType>1</FileType>
<FilePath>..\..\nations-tec\N32WB03x_SDK_V2.0.0\firmware\n32wb03x_std_periph_driver\src\n32wb03x_qflash.c</FilePath>
</File>
<File>
<FileName>n32wb03x_iwdg.c</FileName>
<FileType>1</FileType>
<FilePath>..\..\nations-tec\N32WB03x_SDK_V2.0.0\firmware\n32wb03x_std_periph_driver\src\n32wb03x_iwdg.c</FilePath>
</File>
</Files>
</Group>
<Group>
@ -2844,6 +2854,11 @@
<FileType>1</FileType>
<FilePath>..\..\nations-tec\N32WB03x_SDK_V2.0.0\firmware\n32wb03x_std_periph_driver\src\n32wb03x_qflash.c</FilePath>
</File>
<File>
<FileName>n32wb03x_iwdg.c</FileName>
<FileType>1</FileType>
<FilePath>..\..\nations-tec\N32WB03x_SDK_V2.0.0\firmware\n32wb03x_std_periph_driver\src\n32wb03x_iwdg.c</FilePath>
</File>
</Files>
</Group>
<Group>
@ -3709,6 +3724,11 @@
<FileType>1</FileType>
<FilePath>..\..\nations-tec\N32WB03x_SDK_V2.0.0\firmware\n32wb03x_std_periph_driver\src\n32wb03x_qflash.c</FilePath>
</File>
<File>
<FileName>n32wb03x_iwdg.c</FileName>
<FileType>1</FileType>
<FilePath>..\..\nations-tec\N32WB03x_SDK_V2.0.0\firmware\n32wb03x_std_periph_driver\src\n32wb03x_iwdg.c</FilePath>
</File>
</Files>
</Group>
<Group>

View File

@ -27,7 +27,7 @@
#define configUSE_PREEMPTION 1
#define configUSE_IDLE_HOOK 0
#define configUSE_IDLE_HOOK 1
#define configUSE_TICK_HOOK 0
#define configCPU_CLOCK_HZ ( SystemCoreClock )
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )

View File

@ -9,12 +9,12 @@
#define __APP_VERSION_H__
#ifndef APP_FW_VERSION
#define APP_FW_VERSION "V1.00.26"
#define APP_FW_VERSION "V1.00.27"
#endif
/* Numeric form used by the BLE info query / OTA records: 0x00MMmmpp */
#ifndef APP_FW_VERSION_NUM
#define APP_FW_VERSION_NUM 0x0001001Au
#define APP_FW_VERSION_NUM 0x0001001Bu
#endif
#endif /* __APP_VERSION_H__ */

32
mcm-ddc-ble/inc/app_wdt.h Normal file
View File

@ -0,0 +1,32 @@
/**
* @file app_wdt.h
* @brief Independent watchdog (IWDG, LSI 32kHz): 4s timeout, fed from the
* FreeRTOS idle hook only while at least one application task has
* heartbeated within the last 3s. Frozen while the core is halted
* (DBG_IWDG_STOP) so SWD debug sessions do not trigger resets.
*/
#ifndef __APP_WDT_H__
#define __APP_WDT_H__
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Report the reset cause (banner) and start the IWDG.
* Call once from main() after the USART banner.
*/
void app_wdt_init(void);
/**
* @brief Mark a task as alive. Called from the BLE schedule / LED / CLI
* task loops; the idle hook stops feeding the IWDG when no task
* heartbeated for APP_WDT_HB_TIMEOUT_MS.
*/
void app_wdt_heartbeat(void);
#ifdef __cplusplus
}
#endif
#endif /* __APP_WDT_H__ */

View File

@ -13,6 +13,7 @@
#include "cli_core.h"
#include "bsp_usart.h"
#include "app_ota.h"
#include "app_wdt.h"
#include <string.h>
@ -245,12 +246,14 @@ static void CliTask(void* argument)
for (;;)
{
app_wdt_heartbeat();
/* OTA binary frame mode: raw bytes to the OTA channel.
* otaIdleMs is (re)armed on every entry - a leftover count from a
* previous session must not trip an instant idle timeout. */
otaIdleMs = 0;
while (s_otaMode)
{
app_wdt_heartbeat();
if (bsp_usart_read_byte(&ch, OTA_MODE_POLL_MS) != 0)
{
if (app_ota_chan_rx_uart(ch) != 0)

View File

@ -277,6 +277,24 @@ static void CmdFactory(int argc, char* argv[])
NVIC_SystemReset();
}
/**
* @brief "hang": wedge the CLI task in a busy loop so the idle hook stops
* feeding the IWDG - watchdog fire test (device resets in ~4s and
* the next banner shows "Last reset: IWDG watchdog!").
*/
static void CmdHang(int argc, char* argv[])
{
(void)argc;
(void)argv;
cli_write("hanging now - watchdog should reset in ~4s ...\r\n");
vTaskDelay(pdMS_TO_TICKS(CLI_RESET_DELAY_MS));
for (;;)
{
/* busy: idle task starves, IWDG unfed */
}
}
/**
* @brief "uartrst": re-initialize USART1 at the default baud rate and
* flush the RX queue (recovery from a wedged/misconfigured UART).
@ -302,7 +320,7 @@ static void CmdUartInfo(int argc, char* argv[])
cli_write("usart1: TX=PB6 RX=PB7 (AF4)\r\n");
cli_printf("baud: %lu, 8 data bits, no parity, 1 stop bit\r\n",
(unsigned long)BSP_USART_BAUDRATE);
cli_write("flow control: none; tx: DMA CH1 (polled); rx: RXDNE irq queue\r\n");
cli_write("flow control: none; tx: DMA CH1 (polled); rx: DMA CH2 ring (3KB, idle irq)\r\n");
}
/**
@ -342,6 +360,7 @@ static const CliCmd_t s_cmds[] = {
{"factory", "factory: restore params to defaults and reboot", CmdFactory},
{"uartrst", "uartrst: re-init USART1 (460800 8N1), flush rx queue", CmdUartRst},
{"uartinfo", "uartinfo: show USART1 pins/baud/format", CmdUartInfo},
{"hang", "hang: wedge the CLI task (IWDG watchdog fire test)", CmdHang},
};
#define CLI_CMD_COUNT (sizeof(s_cmds) / sizeof(s_cmds[0]))

View File

@ -84,6 +84,8 @@
#include "app_ota.h"
#include "app_version.h"
#include "app_user_config.h"
#include "app_wdt.h"
#include "n32wb03x_iwdg.h"
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
@ -105,8 +107,62 @@
#define LED_TASK_STACK (128) /* stack in words */
#define LED_TASK_PRIORITY (1)
/* Private constants ---------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private constants ----------------------------------------------------------*/
/* Private variables ----------------------------------------------------------*/
/* IWDG: LSI 32kHz, /128 prescaler -> 250Hz counter; 1000 counts = 4s */
#define APP_WDT_RELOAD 1000u
/* the idle hook feeds the IWDG only while some task heartbeated this long */
#define APP_WDT_HB_TIMEOUT_MS 3000u
static volatile TickType_t s_wdt_last_hb;
/**
* @brief Report the reset cause and start the independent watchdog.
* IWDG is frozen while the core is halted (debug-friendly).
*/
void app_wdt_init(void)
{
if (RCC_GetFlagStatus(RCC_CTRLSTS_FLAG_IWDGRSTF) != RESET)
{
printf(" Last reset: IWDG watchdog!\n");
}
RCC_ClrFlag();
/* Freeze IWDG while the core is halted (SWD debug sessions) */
RCC_EnableAPB1PeriphClk(RCC_APB1_PERIPH_PWR, ENABLE);
DBG_ConfigPeriph(DBG_IWDG_STOP, ENABLE);
IWDG_WriteConfig(IWDG_WRITE_ENABLE);
IWDG_SetPrescalerDiv(IWDG_PRESCALER_DIV128);
IWDG_CntReload(APP_WDT_RELOAD);
IWDG_ReloadKey();
IWDG_Enable(); /* LSI is enabled by hardware */
s_wdt_last_hb = 0;
}
void app_wdt_heartbeat(void)
{
if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING)
{
s_wdt_last_hb = xTaskGetTickCount();
}
}
/**
* @brief FreeRTOS idle hook: feed the IWDG only while some application task
* heartbeated recently. A wedged/deadlocked system (idle spins but no
* task advances) resets within the IWDG timeout.
*/
void vApplicationIdleHook(void)
{
if ((xTaskGetTickCount() - s_wdt_last_hb) < pdMS_TO_TICKS(APP_WDT_HB_TIMEOUT_MS))
{
IWDG_ReloadKey();
}
}
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
@ -121,6 +177,7 @@ static void ble_schedule_task(void *pvParameters)
(void)pvParameters;
for (;;)
{
app_wdt_heartbeat();
/*schedule all pending events*/
rwip_schedule();
/*flush pending CLI uplink frames (notify, paced by cfm)*/
@ -147,6 +204,7 @@ static void led_task(void *pvParameters)
{
period = PARAM_LED1_BLINK_MS_DEF;
}
app_wdt_heartbeat();
LedBlink(LED1_PORT, LED1_PIN);
vTaskDelay(pdMS_TO_TICKS(period));
}
@ -177,6 +235,9 @@ int main(void)
printf(" BLE enabled, advertising as \"%s\"\n", CUSTOM_DEVICE_NAME);
printf("========================================\n");
/* reset-cause report + start the independent watchdog (idle-hook fed) */
app_wdt_init();
#if (CFG_APP_NS_IUS)
if(CURRENT_APP_START_ADDRESS == NS_APP1_START_ADDRESS){
NS_LOG_INFO("application 1 start new ...\r\n");

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -1,7 +1,7 @@
{
"file": "mothercup_ble_prod_ota.bin",
"target_bank": 1,
"size": 55532,
"crc32": "0x6AC3C102",
"version": 65562
"size": 56100,
"crc32": "0xC7CF2A19",
"version": 65563
}

View File

@ -1,7 +1,7 @@
{
"file": "mothercup_ble_prod_ota_app2.bin",
"target_bank": 2,
"size": 55832,
"crc32": "0xCA9651B0",
"version": 65562
"size": 56400,
"crc32": "0x0198204E",
"version": 65563
}