2525c2d948
用户裁定两条:① 剩余裸写端口的地方加「工厂默认」限定词,不改固件行为; ② 生效语义按需求口径「保存即生效」定,现实现(端口下次重 bind 才生效)是 要改的一侧 → 建任务登记,本笔不动代码行为。 限定词(24 处 / 17 文件):AGENTS 数据主线、App/protocol/README、根 README 文档索引、Src/freertos.c 三处任务注释、docs/README、项目启动顺序管理、存储 架构说明、软件架构与运行时规格(2)、RAM 使用分析、产品需求设计文档(5)、 项目架构总览(3)、test/host/README、iec104_master 流程注释、IEC104 上行设 计说明、继电保护功能复现规划。配图两张同批重跑:gen_arch_svg.py 与 gen_memory_svg.py(脚本与产物同一笔)。 两处陈旧计数(都做过双源核证): - 测试开发标准流程 像素层「60 内容页」→ 内容页黄金参考 66 张(64 页); 双源 = simulator/tests/ref/shot_*.bin 实数 66 与 check_content_pages.py 的 PAGES 66 条(含两个 _p2)。 - .pre-commit-config.yaml 的 uinav-selftest 注释「18 张黄金参考」→ 写成 「接线当时 18 张、现 66 张」:git log -S 定到 6a904c1b(2026-10-25),那 天 ref 目录确实 18 张,写时为真,但它描述的是钩子现有行为,属当前态。 口径修正与登记: - 页面扩充详细方案「通讯设置 = 无配置模型(端口与 CA 都是 #define)」→ 标 「当时」,并写明现已在 /config.ini 的 [comm] 段可配、UI 通讯设置页可改。 - 产品需求 §通讯配置:删掉「改后需重启生效」这句把两件事混为一谈的话,改成 需求要求「保存即生效」+ 现实现(公共地址重启、端口下次重 bind)+ 指向登记簿。 - docs/任务规划/登记簿.md §1 新增「104 端口改后立刻重 bind」:含隐性耦合 —— .serialtest/config.json 的必检锚点写死 listening on 2404,端口一改串口 回归必假失败;改行为时要么锚点只查 listening on,要么由脚本注入端口。 复扫(旧形态归零判据)剩 20 处未带限定词,逐条定性为刻意不改:默认值定义处、 出厂模板与断言、usage 与 argparse default、UI 黄金参考显示值、台架主站地址 (同数不同义)、串口锚点、叙事句、引用关系图坐标。逐条理由见一次性台账。 门禁:check_doc_links --sections-strict 对 16 篇改动 .md 逐个 rc=0; check_mermaid --all 31 文件 62 围栏新发 0;check_nested_comment --all 449 文件新发 0;check_bom_drift --all 99 个名单内文件无漂移; check_generated_fresh PASS(25 生成器 / 21 一致 / 4 只减不增基线);字节体检 无新增 BOM / CR / U+FFFD / 行尾空格(三连空行与 HEAD 逐项相同 = 存量)。 /keil-build 0 错 0 警,Program Size Code=246720 RO=29836 RW=2052 ZI=149708 与凭证同码:本笔只碰注释与配图文字,无固件行为变更。
611 lines
29 KiB
C
611 lines
29 KiB
C
/* USER CODE BEGIN Header */
|
||
/**
|
||
******************************************************************************
|
||
* File Name : freertos.c
|
||
* Description : Code for freertos applications
|
||
******************************************************************************
|
||
* @attention
|
||
*
|
||
* Copyright (c) 2026 STMicroelectronics.
|
||
* All rights reserved.
|
||
*
|
||
* This software is licensed under terms that can be found in the LICENSE file
|
||
* in the root directory of this software component.
|
||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||
*
|
||
******************************************************************************
|
||
*/
|
||
/* USER CODE END Header */
|
||
|
||
/* Includes ------------------------------------------------------------------*/
|
||
#include "FreeRTOS.h"
|
||
#include "task.h"
|
||
#include "main.h"
|
||
#include "cmsis_os.h"
|
||
|
||
/* Private includes ----------------------------------------------------------*/
|
||
/* USER CODE BEGIN Includes */
|
||
#include "test_config.h" /* 套件选择:STORAGE 套件下 defaultTask 让出存储所有权 */
|
||
#include <string.h>
|
||
#include "net_task.h"
|
||
#include "ch395f.h"
|
||
#include "dbg_log.h"
|
||
#include "net_socket.h"
|
||
#include "lftpd.h"
|
||
#include "task_watchdog.h"
|
||
#include "../test/board/ch395f_test_task.h"
|
||
#include "../test/board/ch395f_test.h" /* ENABLE_NET_LAYER_TESTS:netTask 条件创建 */
|
||
#include "time_sync.h"
|
||
#include "sys_events.h"
|
||
#include "default_task.h"
|
||
#include "adc_task.h" /* B1(2026-10-10):ADC 采集消费者(手写任务,见 Variables 区) */
|
||
#include "iec104_serv.h" /* B4(2026-10-12):IEC 104 上行服务(手写任务,见 Variables 区) */
|
||
#include "protect_task.h" /* B5(2026-10-12):保护判据节拍任务(手写任务,同上) */
|
||
/* USER CODE END Includes */
|
||
|
||
/* Private typedef -----------------------------------------------------------*/
|
||
/* USER CODE BEGIN PTD */
|
||
|
||
/* USER CODE END PTD */
|
||
|
||
/* Private define ------------------------------------------------------------*/
|
||
/* USER CODE BEGIN PD */
|
||
/*
|
||
* 名称指针合法性校验(栈溢出/越界可能已破坏 TCB,指针不可信):
|
||
* 只接受落在 Flash / 主 SRAM / CCM 映射范围内的指针;不校验就直接交给 UART,
|
||
* 钩子自身也会以同样方式崩(2026-09-24 实测 HardFault:BFAR=0x10010000 恰为
|
||
* CCM 末尾+1,PC 落在 HAL_UART_Transmit 的缓冲读取处)。
|
||
* ⚠ 本辅助函数须定义在钩子**之前**:两个钩子本体已迁入 CubeMX 生成的
|
||
* `USER CODE BEGIN 4 / 5` 区(位置在 `MX_FREERTOS_Init` 原型之后,比
|
||
* `Application` 区靠前),故 helper 一并搬到此标准保护位(全周傅氏相量)。
|
||
*/
|
||
static int hook_ptr_ok(const void *p)
|
||
{
|
||
uint32_t a = (uint32_t)p;
|
||
uint32_t win = 32u; /* 将要读取的最大字节数(任务名 ≤ 32) */
|
||
|
||
if ((a >= 0x08000000u) && (a <= (0x08100000u - win))) {
|
||
return 1; /* Flash(RO 区) */
|
||
}
|
||
if ((a >= 0x20000000u) && (a <= (0x20020000u - win))) {
|
||
return 1; /* SRAM1 + SRAM2 */
|
||
}
|
||
if ((a >= 0x10000000u) && (a <= (0x10010000u - win))) {
|
||
return 1; /* CCM(FreeRTOS 堆:TCB/栈所在) */
|
||
}
|
||
return 0;
|
||
}
|
||
/* USER CODE END PD */
|
||
|
||
/* Private macro -------------------------------------------------------------*/
|
||
/* USER CODE BEGIN PM */
|
||
|
||
/* USER CODE END PM */
|
||
|
||
/* Private variables ---------------------------------------------------------*/
|
||
/* USER CODE BEGIN Variables */
|
||
/*
|
||
* ===== 任务栈核算依据(受保护记录,勿删本块)=====
|
||
* ⚠ 栈**值**的事实源是 `.ioc` 的 `FREERTOS.Tasks01`——改栈改那里,CubeMX 会生成到下面各
|
||
* `.stack_size`。本块只记录"为什么是这个值":原先这些注释贴在各自 `.stack_size` 旁边,
|
||
* 但那里位于生成区之外,2026-09-29 与 2026-10-09 共四次重生成里丢了三次,故集中搬进
|
||
* `USER CODE BEGIN Variables` 这个标准保护位(模板真实生成、名字从未变过 → 重生成不丢)。
|
||
* 数据来源:`scripts/stack_report.py`(Keil 静态调用图最坏链)+ 串口 `[STACK]` 运行时水位;
|
||
* 底线规则:余量 ≥ max(1024 B ISR, 30%)。完整核算见 docs/内存管理/RAM使用分析与优化方案.md,
|
||
* 全表(含 ftpTask 1024 / uiTask 4096 的现值)见 AGENTS.md 任务表。
|
||
*
|
||
* defaultTask 1280 words:扛 mount+mkfs+FAT 探针+cfg 加载+syslog 写+配置引擎
|
||
* (全深层 FatFS I/O)。原 512,2KB 实测峰值溢出——开启
|
||
* configCHECK_FOR_STACK_OVERFLOW=2 后被钩子抓到(2026-09-21)。
|
||
* 2026-10-10 栈整改(**未抬栈**,靠削链达标):静态最坏链 5312 → 2776 B、
|
||
* 实测峰值 3392 → 2152 B(余 1944 B / 47%)。四个杠杆:① 删旧档 NCFG 迁移
|
||
* (net_cfg_legacy,−600 B 链);② 6 处局部 `FIL` 改函数级 static(FF_FS_TINY=0
|
||
* → 单个 FIL = 528 B 栈);③ 日志行缓冲按模块收紧 `DBG_LINE_BUF_MAX 128`
|
||
* + 时间戳缓冲并入行缓冲(每个日志调用点原内联 192+32 B);④ 断言/故障钩子
|
||
* 脱离 HAL 发送回退 + 缓冲静态化(configASSERT 在 FreeRTOS 内部可达 →
|
||
* 钩子在**每个**任务的最坏链尾)。详见 docs/内存管理/RAM使用分析与优化方案.md
|
||
* 栈余量收口(2026-10-12):B7 定值持久化把静态最坏链推到 **3016 B**(storage_bring_up →
|
||
* cfg 写 → f_rename → sync_fs → dhara GC → 控制台回显尾),1024 words 时余
|
||
* 1080 B < 30% 线(4096×30% = 1228 B)→ 抬至 **1280 words**(5120 B),余
|
||
* 2104 B / 41%;堆开销 +1024 B。栈值事实源仍是 `.ioc` 的 `FREERTOS.Tasks01`。
|
||
* netTask 960 words:静态最坏链 1944 B(2026-10-12 监听生命周期模块化后复测,链尾经
|
||
* net_listener_pump→net_relisten_locked→SPI 故障→日志尾;2026-10-10 整改后曾为
|
||
* 1872 B,整改前 2664 B);3840 B 配置余量 1896 B
|
||
* (49%),过 stack_report 规则。运行时水位仅 ~0.9 KB(错误尾未
|
||
* 触发,不可作依据)。曾试 net_drain_chip_rx scratch 静态化,实测仅省 16 B 栈
|
||
* (ARMCC 已叠放不重叠局部量)反增 256 B BSS,已回退——见 net_socket.c 注释。
|
||
* testTask 2048 words:存储套件 deep call chain + 局部缓冲,原 1024(4 KB)实测溢出
|
||
* (2026-09-20)。仅启用测试套件时创建。
|
||
* timeSyncTask 640 words:本区下方手工定义(不在 `.ioc` 的 Tasks01 里 → 重生成会丢,
|
||
* 靠审计 WARN 提示),建监听 → net_bind → CH395F → 日志尾。2026-10-10 整改后
|
||
* 静态最坏链 1024 B(整改前 1736 B)、实测峰值 616 B;⚠ 余量 1024 B **正好**
|
||
* 等于 ISR 底线 → 本任务链上再加任何调用(或新日志行)都必须先抬栈。
|
||
* 栈余量收口(2026-10-12):链已加深到 **1264 B**(同一控制台回显尾:`dbg_console_drain`
|
||
* → `MX_USART1_UART_Init` → `Error_Handler` → 回显 Cycle),512 words 时余
|
||
* 784 B < 1024 B ISR 底线 → 抬至 **640 words**(2.5 KB),余 1296 B / 50%;
|
||
* 堆开销 +512 B。
|
||
* adcTask 640 words:本区下方手工定义(同上,不在 `.ioc` 里)。B1(2026-10-10)
|
||
* ADC 采集消费者;全周傅氏相量(2026-10-11)+ 采样有效性校验(同日)后链上为
|
||
* adc_input_poll + sample_ring/phasor + adc_health_report(DBG_ERROR +
|
||
* syslog_post 回显尾)。静态最坏链 1080 B,512 words 时余 968 B < 1024 B
|
||
* ISR 底线 → 抬至 640 words(2.5 KB,堆余量 min_ever 7152→≈6640 B,可承受)。
|
||
* 2026-10-12 复测链 1320 B(保护判据接线后),余 1240 B / 48% 仍 PASS。
|
||
* 栈值以 `scripts/stack_report.py` 实测最坏链为准,改动前后各跑一次。
|
||
* iec104Task 640 words:B4(2026-10-12)104 上行服务(默认 2404 监听 + link 状态
|
||
* 机)。link/pts 缓冲全静态;stack_report 实测静态最坏链 1456 B(拆链
|
||
* 重建监听 → net_bind → SPI 故障 → 日志尾,日志主导)。512 words 时余
|
||
* 592 B < 1024 B ISR 底线 → 抬至 640(2.5 KB)。
|
||
* protectTask 512 words:B5(2026-10-12)保护判据节拍(prot_elm_run 消费者
|
||
* + 自检失败闭锁 采样闭锁)。stack_report 实测静态最坏链 872 B(float 判据 +
|
||
* phasor 快照 + 日志尾),余 1176 B / 57% ≥ ISR 底线 → 512 保持。
|
||
*/
|
||
/* 存储测试变量(g_storage_stats / s_perf_buf)已迁至 test/board/storage_test_task.c */
|
||
osThreadId_t timeSyncTaskHandle;
|
||
const osThreadAttr_t timeSyncTask_attributes = {
|
||
.name = "timeSyncTask",
|
||
.stack_size = 640 * 4,
|
||
.priority = (osPriority_t) osPriorityNormal,
|
||
};
|
||
/* B1:ADC 采集消费者(TIM3 1 kHz 采样节拍 + 原子快照 + 帧统计)。
|
||
* 手写于此而非 .ioc(同 timeSyncTask:不在 Tasks01 里 → 重生成会丢,靠审计 WARN 提示)。
|
||
* ⚠ 优先级 AboveNormal:消费由 TIM3 ISR 事件驱动,提优先级使"被唤醒 → 立即抢占其余
|
||
* Normal 任务",唤醒延迟固定为 µs 级。
|
||
* 代价:本任务单拍最坏 adcm≈2 ms 会在 AboveNormal 上挤占 net/ftp/default/timeSync;
|
||
* 实测峰值来自每 10 s 心跳日志而非采集(STM32 侧已由 DMA 搬运,单拍取数 µs 量级)
|
||
* → 耗时特征与代价见 docs/子系统设计/ADC采样节拍设计说明.md §9、§10。 */
|
||
osThreadId_t adcTaskHandle;
|
||
const osThreadAttr_t adcTask_attributes = {
|
||
.name = "adcTask",
|
||
.stack_size = 640 * 4,
|
||
.priority = (osPriority_t) osPriorityAboveNormal,
|
||
};
|
||
/* B4(2026-10-12):IEC 60870-5-104 被控站服务(默认 2404 监听 + 链路状态机消费)。
|
||
* 手写于此而非 .ioc(同 timeSyncTask/adcTask:不在 Tasks01 里 → 重生成会丢,
|
||
* 靠审计 WARN 提示)。640 words:stack_report 实测静态最坏链 1456 B(拆链→
|
||
* 重建监听→net_bind→SPI 故障→日志尾,日志主导);512 words 时余 592 B
|
||
* < 1024 B ISR 底线 → 抬至 640(2.5 KB,CCM 堆 min_ever 再降 512 B,可承受)。 */
|
||
osThreadId_t iec104TaskHandle;
|
||
const osThreadAttr_t iec104Task_attributes = {
|
||
.name = "iec104Task",
|
||
.stack_size = 640 * 4,
|
||
.priority = (osPriority_t) osPriorityNormal,
|
||
};
|
||
/* B5(2026-10-12):保护判据节拍(prot_elm_run 消费者 + 自检失败闭锁 采样闭锁)。
|
||
* 手写于此而非 .ioc(同上)。512 words:stack_report 实测静态最坏链 872 B
|
||
* (float 判据 + phasor 快照 + 日志尾),余 1176 B / 57% ≥ ISR 底线 → 保持。
|
||
* 优先级 Normal:V0.1 无出口硬件(风险项见 docs/算法/继电保护功能复现规划.md §9),
|
||
* 判据是 20 ms 软实时,不需要 adcTask 的 AboveNormal;该文档 §4 设想的"1 ms / 高
|
||
* 优先级"要等瞬时值元件落地后再评(当前无出口硬件,快一拍不改变任何出口时序)。 */
|
||
osThreadId_t protectTaskHandle;
|
||
const osThreadAttr_t protectTask_attributes = {
|
||
.name = "protectTask",
|
||
.stack_size = 512 * 4,
|
||
.priority = (osPriority_t) osPriorityNormal,
|
||
};
|
||
/* USER CODE END Variables */
|
||
/* Definitions for defaultTask */
|
||
osThreadId_t defaultTaskHandle;
|
||
const osThreadAttr_t defaultTask_attributes = {
|
||
.name = "defaultTask",
|
||
.stack_size = 1280 * 4,
|
||
.priority = (osPriority_t) osPriorityNormal,
|
||
};
|
||
/* Definitions for netTask */
|
||
osThreadId_t netTaskHandle;
|
||
const osThreadAttr_t netTask_attributes = {
|
||
.name = "netTask",
|
||
.stack_size = 960 * 4,
|
||
.priority = (osPriority_t) osPriorityNormal,
|
||
};
|
||
/* Definitions for ftpTask */
|
||
osThreadId_t ftpTaskHandle;
|
||
const osThreadAttr_t ftpTask_attributes = {
|
||
.name = "ftpTask",
|
||
.stack_size = 1024 * 4,
|
||
.priority = (osPriority_t) osPriorityNormal,
|
||
};
|
||
/* Definitions for testTask */
|
||
osThreadId_t testTaskHandle;
|
||
const osThreadAttr_t testTask_attributes = {
|
||
.name = "testTask",
|
||
.stack_size = 2048 * 4,
|
||
.priority = (osPriority_t) osPriorityBelowNormal,
|
||
};
|
||
/* Definitions for uiTask */
|
||
osThreadId_t uiTaskHandle;
|
||
const osThreadAttr_t uiTask_attributes = {
|
||
.name = "uiTask",
|
||
.stack_size = 4096 * 4,
|
||
.priority = (osPriority_t) osPriorityBelowNormal,
|
||
};
|
||
/* Definitions for netMsgQueue */
|
||
osMessageQueueId_t netMsgQueueHandle;
|
||
const osMessageQueueAttr_t netMsgQueue_attributes = {
|
||
.name = "netMsgQueue"
|
||
};
|
||
/* Definitions for sys_evt */
|
||
osEventFlagsId_t sys_evtHandle;
|
||
const osEventFlagsAttr_t sys_evt_attributes = {
|
||
.name = "sys_evt"
|
||
};
|
||
|
||
/* Private function prototypes -----------------------------------------------*/
|
||
/* USER CODE BEGIN FunctionPrototypes */
|
||
void ui_task_func(void *argument);
|
||
/* USER CODE END FunctionPrototypes */
|
||
|
||
void StartDefaultTask(void *argument);
|
||
void StartNetTask(void *argument);
|
||
void StartFtpTask(void *argument);
|
||
void start_test_task(void *argument);
|
||
void StartUiTask(void *argument);
|
||
|
||
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
|
||
|
||
/* Hook prototypes */
|
||
void vApplicationStackOverflowHook(xTaskHandle xTask, signed char *pcTaskName);
|
||
void vApplicationMallocFailedHook(void);
|
||
|
||
/* USER CODE BEGIN 4 */
|
||
/*
|
||
* 栈溢出钩子(configCHECK_FOR_STACK_OVERFLOW=2,2026-09-20 存储套件死机事故后加):
|
||
* 溢出 = 内核状态已不可信,禁止走 printf/内核对象/控制台锁,裸发固定文本后复位。
|
||
* 2026-09-24 加固:任务名先经范围校验并拷入本地缓冲(不再把 TCB 指针交给 UART),
|
||
* 指针不可信时打印占位文本——避免"上报崩溃的代码自己再崩一次"。
|
||
* ⚠ 落点说明(2026-10-09):整个函数放在 CubeMX 生成的 `USER CODE BEGIN 4` 区内。
|
||
* 该区的存在由 `.ioc` 启用钩子(FREERTOS.configCHECK_FOR_STACK_OVERFLOW=2)保证;
|
||
* 若实现留在 `Application` 区、又让 CubeMX 生成空桩,两处同名定义 = 编译错。
|
||
* 签名与 CubeMX 生成的钩子原型一致(xTaskHandle → TaskHandle_t,即 void *)。
|
||
*/
|
||
void vApplicationStackOverflowHook(xTaskHandle xTask, signed char *pcTaskName)
|
||
{
|
||
static const char msg_head[] = "\r\n[ERR] STACK OVERFLOW task=";
|
||
static const char msg_bad[] = "(name unavailable)";
|
||
uint16_t len = 0u;
|
||
|
||
(void)xTask;
|
||
/* ⚠ 刻意**不**用 dbg_console_tx_begin()/tx_end() 包这三条裸发(道G task-26 定案,
|
||
* 2026-10-26;早先的版本包过,这一笔摘掉,三条理由都要看):
|
||
* ① 语义上空转:异常上下文 IPSR != 0 时 begin 是**放行、不持有凭证**("绝不等待"
|
||
* 这条约束的必然结果),未持有时 end 又是幂等空操作 —— 包上得不到任何整行保护。
|
||
* 本行的整行性来自"Fault 优先级 -1、外设中断 ≥5、任务全冻结 ⇒ 没有第二个写者
|
||
* 能跑进来",与 begin 无关。
|
||
* ② 代价却是全局的:本钩子挂在 configASSERT 子树上(vPortEnterCritical /
|
||
* vPortExitCritical 都可能进断言),挂在那上面的每一句都会被 armlink ×9 条任务链
|
||
* 记账。实测包上后 con_tx_release 子树 24→152 B、发送环单趟贡献 128→208 B,
|
||
* default/net/iec104 三条链各 +80 B,把 iec104Task 的 margin 顶到 1,016 < 1,024
|
||
* (红线数字见 App/log/console_log.c 的 CON_CRIT_ENTER 注释)。
|
||
* ③ 归还所有权在这里没有下游读者:这一行发完就 NVIC_SystemReset()。
|
||
* 对照:**任务态**多段裸发(App/util/task_watchdog.c 的 STALL 行)仍然包 begin/end
|
||
* —— 那里既不空转(同任务后面还要接着发),也不在断言子树上。这条区分写进
|
||
* docs/子系统设计/控制台单写者日志设计说明.md §4.4.1,别让下一个人以为是我们漏了。 */
|
||
dbg_console_write_raw(msg_head, (unsigned)(sizeof(msg_head) - 1u));
|
||
|
||
if ((pcTaskName != NULL) && (hook_ptr_ok(pcTaskName) != 0)) {
|
||
while ((len < 32u) && (pcTaskName[len] != '\0')) {
|
||
len++;
|
||
}
|
||
}
|
||
if (len == 0u) {
|
||
dbg_console_write_raw(msg_bad, (unsigned)(sizeof(msg_bad) - 1u));
|
||
} else {
|
||
char name[33]; /* 拷入本地缓冲:UART 永不接触 TCB 指针 */
|
||
|
||
memcpy(name, pcTaskName, (size_t)len);
|
||
name[len] = '\0';
|
||
dbg_console_write_raw(name, (unsigned)len);
|
||
}
|
||
dbg_console_write_raw("\r\n", 2u);
|
||
/* 这里不需要任何延时:唯一理由点写在 Src/stm32f4xx_it.c 的 hard_fault_report 函数头
|
||
* 第 ② 条(发送口自身已等到 TC;多留一条 HAL_Delay 就多一条 TIM7 tick 依赖,tick
|
||
* 一停摆就卡在复位之前,而启动段 IWDG 尚未启动、没有兜底),此处只引用不复述。
|
||
* 顺序不变:三条裸发全部发完 → 立即 NVIC_SystemReset()。 */
|
||
NVIC_SystemReset();
|
||
}
|
||
/* USER CODE END 4 */
|
||
|
||
/* USER CODE BEGIN 5 */
|
||
/*
|
||
* 堆分配失败钩子:只告警不复位(NULL 由各调用点自行处理并上报自检)。
|
||
* 同样走裸 UART 有界发送——钩子可能在临界区内被调,printf/信号量不可用。
|
||
* ⚠ 落点同上:整个函数在 CubeMX 生成的 `USER CODE BEGIN 5` 区内
|
||
* (由 `.ioc` 的 FREERTOS.configUSE_MALLOC_FAILED_HOOK=1 保证该区存在)。
|
||
*/
|
||
void vApplicationMallocFailedHook(void)
|
||
{
|
||
static const char msg[] = "\r\n[ERR] MALLOC FAILED (heap exhausted)\r\n";
|
||
|
||
dbg_console_write_raw(msg, (unsigned)(sizeof(msg) - 1u));
|
||
}
|
||
/* USER CODE END 5 */
|
||
|
||
/**
|
||
* @brief FreeRTOS initialization
|
||
* @param None
|
||
* @retval None
|
||
*/
|
||
void MX_FREERTOS_Init(void) {
|
||
/* USER CODE BEGIN Init */
|
||
|
||
/* USER CODE END Init */
|
||
|
||
/* USER CODE BEGIN RTOS_MUTEX */
|
||
/* add mutexes, ... */
|
||
/* USER CODE END RTOS_MUTEX */
|
||
|
||
/* USER CODE BEGIN RTOS_SEMAPHORES */
|
||
/* add semaphores, ... */
|
||
/* USER CODE END RTOS_SEMAPHORES */
|
||
|
||
/* USER CODE BEGIN RTOS_TIMERS */
|
||
/* start timers, add new ones, ... */
|
||
/* USER CODE END RTOS_TIMERS */
|
||
|
||
/* Create the queue(s) */
|
||
/* creation of netMsgQueue */
|
||
netMsgQueueHandle = osMessageQueueNew (8, sizeof(net_msg_t), &netMsgQueue_attributes);
|
||
|
||
/* USER CODE BEGIN RTOS_QUEUES */
|
||
/* add queues, ... */
|
||
/* 队列对象由上面生成的语句创建(创建点不可外移:osKernelInitialize() 之前的
|
||
创建类 API 会杀死启动)。这里立刻把句柄交给唯一使用者 net_socket,之后本模块
|
||
不再对外暴露这个全局变量;少了这一行,net_socket 的句柄恒为 NULL,所有 net_*
|
||
请求会被判空丢弃(表现为网络全线静默不通)。 */
|
||
(void)net_msg_queue_adopt(netMsgQueueHandle);
|
||
/* USER CODE END RTOS_QUEUES */
|
||
|
||
/* Create the thread(s) */
|
||
/* creation of defaultTask */
|
||
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
|
||
|
||
/* creation of netTask */
|
||
netTaskHandle = osThreadNew(StartNetTask, NULL, &netTask_attributes);
|
||
|
||
/* creation of ftpTask */
|
||
ftpTaskHandle = osThreadNew(StartFtpTask, NULL, &ftpTask_attributes);
|
||
|
||
/* creation of testTask */
|
||
testTaskHandle = osThreadNew(start_test_task, NULL, &testTask_attributes);
|
||
|
||
/* creation of uiTask */
|
||
uiTaskHandle = osThreadNew(StartUiTask, NULL, &uiTask_attributes);
|
||
|
||
/* USER CODE BEGIN RTOS_THREADS */
|
||
/* netTask = 基础线程:上方生成区的实例是全构建唯一实例(旧
|
||
* #ifdef ENABLE_NET_LAYER_TESTS 的重复创建已删除)。
|
||
* - 产品 / NET 层测试模式:正常运行 bring_up + net_poll
|
||
* - CH395F 硬件层测试模式:net_task_func 入口自挂起(testTask 独占 CH395F) */
|
||
|
||
/* 本地时间同步任务(PC 推送 TCP):依赖 netTask 处理消息队列 */
|
||
timeSyncTaskHandle = osThreadNew(time_sync_task, NULL, &timeSyncTask_attributes);
|
||
|
||
/* B1(2026-10-10):ADC 采集任务。1ms 采样节拍触发 CONVST,消费 tpafe5160
|
||
* 原子快照并喂 IWDG 心跳(TASK_WD_ADC)。不依赖任何就绪屏障(无网络/存储依赖)
|
||
* → 任何构建都创建、都置心跳位。驱动 tpafe5160_init() 已在 app_main_init
|
||
* (内核启动前)完成;本任务只做运行期采样。 */
|
||
adcTaskHandle = osThreadNew(adc_task_func, NULL, &adcTask_attributes);
|
||
|
||
/* B4(2026-10-12):IEC 60870-5-104 上行服务任务。任务体内先等 NET_READY
|
||
* 屏障(100ms 分片 + 每片喂心跳,timeSyncTask 先例),再建默认端口 2404 监听并
|
||
* 100ms 循环服务;纳入 IWDG required_mask(TASK_WD_IEC104,任何构建都创建)。 */
|
||
iec104TaskHandle = osThreadNew(iec104_serv_task, NULL, &iec104Task_attributes);
|
||
protectTaskHandle = osThreadNew(protect_task, NULL, &protectTask_attributes);
|
||
|
||
/* 2026-10-04:产品构建(未选任何 TEST_SUITE_*)不需要 testTask。
|
||
* 上方生成区按 .ioc FREERTOS.Tasks01 无条件创建它,其 2048 words(8KB) 栈 + TCB
|
||
* 取自 CCM 堆;而产品构建下 start_test_task() 仅打印一行后 osDelay(1000) 永久空转,
|
||
* 白白占用 8KB CCM。此处于内核启动前(MX_FREERTOS_Init 先于 osKernelStart 调用)
|
||
* 立即回收:调度器未运行时 vTaskDelete 对非当前任务同步 prvDeleteTCB,栈/TCB 归还
|
||
* 堆(INCLUDE_vTaskDelete=1);置句柄 NULL 后栈水位探针(default_task.c 既有 NULL
|
||
* 分支)自动跳过 test 项。不改生成区 / .ioc —— CubeMX 重生成不丢本逻辑。 */
|
||
#if !defined(TEST_SUITE_SELECTED)
|
||
if (testTaskHandle != NULL) {
|
||
vTaskDelete(testTaskHandle);
|
||
testTaskHandle = NULL;
|
||
}
|
||
#endif
|
||
/* USER CODE END RTOS_THREADS */
|
||
|
||
/* Create the event(s) */
|
||
/* creation of sys_evt */
|
||
sys_evtHandle = osEventFlagsNew(&sys_evt_attributes);
|
||
|
||
/* USER CODE BEGIN RTOS_EVENTS */
|
||
/* add events, ... */
|
||
/* USER CODE END RTOS_EVENTS */
|
||
|
||
}
|
||
|
||
/* USER CODE BEGIN Header_StartDefaultTask */
|
||
/**
|
||
* @brief Function implementing the defaultTask thread.
|
||
* @param argument: Not used
|
||
* @retval None
|
||
*/
|
||
/* USER CODE END Header_StartDefaultTask */
|
||
void StartDefaultTask(void *argument)
|
||
{
|
||
/* USER CODE BEGIN StartDefaultTask */
|
||
/* 任务体已迁出至 App/task/default_task.c(2026-09-25):
|
||
* 本文件只保留 CubeMX 接线(任务创建)与崩溃钩子 */
|
||
default_task_func(argument);
|
||
/* USER CODE END StartDefaultTask */
|
||
}
|
||
|
||
/* USER CODE BEGIN Header_StartNetTask */
|
||
/**
|
||
* @brief Function implementing the netTask thread.
|
||
* @param argument: Not used
|
||
* @retval None
|
||
*/
|
||
/* USER CODE END Header_StartNetTask */
|
||
void StartNetTask(void *argument)
|
||
{
|
||
/* USER CODE BEGIN StartNetTask */
|
||
net_task_func(argument);
|
||
/* USER CODE END StartNetTask */
|
||
}
|
||
|
||
/* USER CODE BEGIN Header_StartFtpTask */
|
||
/**
|
||
* @brief Function implementing the ftpTask thread.
|
||
* @param argument: Not used
|
||
* @retval None
|
||
*/
|
||
/* USER CODE END Header_StartFtpTask */
|
||
__weak void StartFtpTask(void *argument)
|
||
{
|
||
/* USER CODE BEGIN StartFtpTask */
|
||
task_wd_bind(TASK_WD_FTP); /* FTL 心跳路由:登记本任务句柄(入口一次) */
|
||
/* 等网络 + 存储都真正就绪后再 listen,取代拍脑袋的 7s 魔法延时 */
|
||
/*
|
||
sys_evtHandle:等待哪个事件组
|
||
SYS_EVT_NET_READY | SYS_EVT_STORAGE_READY:要等哪几位(1u<<0 网络、1u<<1 存储)
|
||
osFlagsWaitAll:请求的位全部置位才唤醒(即"网络且存储都就绪")
|
||
100u:单次等待分片时长——2026-10-02 台架修复由 osWaitForever 改为 100ms
|
||
分片循环:IWDG 必需掩码含 FTP 位,屏障等待态必须能喂心跳(见下方注释)
|
||
*/
|
||
if (sys_evtHandle != NULL) {
|
||
/* osFlagsNoClear 必须带:就绪位是**状态**不是命令,消费型等待会把位清掉,
|
||
* 导致后面 WaitAll(两位) 的等待者永远等不到(2026-09-21 实测:netTask 先
|
||
* 消费 STORAGE_READY、uiTask/timeSync 再消费 NET_READY → 本任务永不唤醒、
|
||
* FTP 21 端口从未监听)。
|
||
* 2026-10-02 台架修复:等待改 100ms 分片 + 每片喂心跳(与 timeSync/uiTask/
|
||
* netTask 入口同模式)——IWDG 必需掩码含 FTP 位,原 osWaitForever 无心跳,
|
||
* IWDG 启动(存储就绪)时网络未必就绪 → 掩码 800ms 不可达 → 停喂 → 复位循环 */
|
||
for (;;) {
|
||
uint32_t r = osEventFlagsWait(sys_evtHandle,
|
||
SYS_EVT_NET_READY | SYS_EVT_STORAGE_READY,
|
||
osFlagsWaitAll | osFlagsNoClear, 100u);
|
||
if ((r & osFlagsError) == 0u) {
|
||
break; /* 两位均就绪(状态位,未被消费) */
|
||
}
|
||
lftpd_task_wd_beat(); /* 看门狗:分片喂心跳(屏障等待态) */
|
||
}
|
||
}
|
||
lftpd_t ftp;
|
||
lftpd_start("/", 21, &ftp);
|
||
/* 广播"FTP 服务就绪"(预留给依赖 FTP 的后续功能) */
|
||
if (sys_evtHandle != NULL) {
|
||
osEventFlagsSet(sys_evtHandle, SYS_EVT_FTP_READY);
|
||
}
|
||
/* USER CODE END StartFtpTask */
|
||
}
|
||
|
||
/* USER CODE BEGIN Header_start_test_task */
|
||
/**
|
||
* @brief Function implementing the testTask thread.
|
||
* @param argument: Not used
|
||
* @retval None
|
||
*/
|
||
/* USER CODE END Header_start_test_task */
|
||
__weak void start_test_task(void *argument)
|
||
{
|
||
/* USER CODE BEGIN start_test_task */
|
||
/* Infinite loop */
|
||
for(;;)
|
||
{
|
||
osDelay(1);
|
||
}
|
||
/* USER CODE END start_test_task */
|
||
}
|
||
|
||
/* USER CODE BEGIN Header_StartUiTask */
|
||
/**
|
||
* @brief Function implementing the uiTask thread.
|
||
* @param argument: Not used
|
||
* @retval None
|
||
*/
|
||
/* USER CODE END Header_StartUiTask */
|
||
void StartUiTask(void *argument)
|
||
{
|
||
/* USER CODE BEGIN StartUiTask */
|
||
ui_task_func(argument);
|
||
/* USER CODE END StartUiTask */
|
||
}
|
||
|
||
/* Private application code --------------------------------------------------*/
|
||
/* USER CODE BEGIN Application */
|
||
/*
|
||
* 断言失败钩子(2026-09-29,配合 IWDG;见 Inc/FreeRTOSConfig.h 的 configASSERT)。
|
||
* 与栈溢出钩子同为"内核状态已不可信"路径:裸 UART 有界输出 + 主动复位,
|
||
* 不走 printf / 内核对象 / 控制台锁。
|
||
* 设计取舍(docs/子系统设计/看门狗设计说明.md §4.4):
|
||
* - 上报 file/line 是断言唯一有价值的现场信息,故必须打印(早于 TCB 名之类);
|
||
* - 打印后**主动 NVIC_SystemReset()** 而非空转:断言点未必能让 IWDG 抓到
|
||
* (若恰在启动段,IWDG 尚未启动),主动复位保证任何断言都能自恢复;
|
||
* 代价是 RCC_CSR 记 SFTRSTF 而非 IWDGRSTF——故上报串里写死 ASSERT 字样,
|
||
* 使"重启原因"可由日志区分(软件复位 + 上一条是 ASSERT = 断言导致)。
|
||
*/
|
||
void vApplicationAssertFailed(const char *file, long line)
|
||
{
|
||
static const char msg_head[] = "\r\n[ERR] ASSERT FAILED ";
|
||
const char *p = file;
|
||
/* 缓冲一律 static(不放栈上):本钩子是"内核状态已不可信"的终点路径,打印完即
|
||
* 复位,不存在并发/重入;放栈上会让 armlink 把这 81 B 挂进每个任务的最坏链
|
||
* (configASSERT 在 FreeRTOS 内部可达 → 每条链尾都有本钩子) */
|
||
static char num[16];
|
||
int i;
|
||
|
||
/* 文件名只取末段(全路径太长且无信息量) */
|
||
if (p != NULL) {
|
||
const char *q = p;
|
||
while (*q != '\0') {
|
||
if ((*q == '/') || (*q == '\\')) {
|
||
p = q + 1;
|
||
}
|
||
q++;
|
||
}
|
||
/* 有界拷贝:上限 48 字符 */
|
||
{
|
||
static char name[49]; /* static 存储(见上),作用域收到本块 */
|
||
uint16_t len = 0u;
|
||
while ((len < 48u) && (p[len] != '\0')) {
|
||
name[len] = p[len];
|
||
len++;
|
||
}
|
||
name[len] = '\0';
|
||
/* ⚠ 与栈溢出钩子同理,刻意不包 begin/end(三条完整理由写在同文件
|
||
* vApplicationStackOverflowHook 上方那一处,这里不复述以免长成第二真源)。
|
||
* 另外修正先前版本自身的一处不完整:那对 begin/end 只包住了"有文件名"
|
||
* 这一支,else 分支(无文件名)根本不在窗口里 —— 半套独比亚没有更误导人。 */
|
||
dbg_console_write_raw(msg_head, (unsigned)(sizeof(msg_head) - 1u));
|
||
dbg_console_write_raw(name, (unsigned)len);
|
||
}
|
||
} else {
|
||
dbg_console_write_raw(msg_head, (unsigned)(sizeof(msg_head) - 1u));
|
||
dbg_console_write_raw("(no file)", 9u);
|
||
}
|
||
|
||
dbg_console_write_raw(":", 1u);
|
||
|
||
/* 行号转十进制(不用 snprintf:钩子可能在临界区,且避免库依赖) */
|
||
i = 0;
|
||
if (line <= 0) {
|
||
num[i++] = '0';
|
||
} else {
|
||
int n = 0;
|
||
int j;
|
||
|
||
while ((line > 0) && (n < 15)) {
|
||
num[n++] = (char)('0' + (line % 10)); /* 先逆序填入 */
|
||
line /= 10;
|
||
}
|
||
for (j = 0; j < n / 2; j++) { /* 原地反转:省一个 16 B 缓冲 */
|
||
char t = num[j];
|
||
num[j] = num[n - 1 - j];
|
||
num[n - 1 - j] = t;
|
||
}
|
||
i = n;
|
||
}
|
||
dbg_console_write_raw(num, (unsigned)i);
|
||
|
||
dbg_console_write_raw(" (resetting)\r\n", 15u);
|
||
|
||
/* 无需 HAL_Delay:dbg_console_write_raw → con_tx_bounded 已等到 TC(移位寄存器
|
||
* 排空)才返回;去掉 HAL_Delay 亦少一条 HAL 依赖(断言现场 tick 未必可信) */
|
||
NVIC_SystemReset();
|
||
}
|
||
/* USER CODE END Application */
|
||
|