Files
STM32F4-Base/Src/freertos.c
T
wandering1 2525c2d948 docs(104): 端口口径统一成「工厂默认」,登记「改后立即重 bind」待办
用户裁定两条:① 剩余裸写端口的地方加「工厂默认」限定词,不改固件行为;
② 生效语义按需求口径「保存即生效」定,现实现(端口下次重 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
与凭证同码:本笔只碰注释与配图文字,无固件行为变更。
2026-10-30 15:40:27 +08:00

611 lines
29 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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 */