Files
STM32F4-Base/Src/freertos.c
T

493 lines
19 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"
/* 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 一并搬到此标准保护位(L2)。
*/
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 1024 words:扛 mount+mkfs+FAT 探针+cfg 加载+syslog 写+配置引擎
* (全深层 FatFS I/O)。原 512,2KB 实测峰值溢出——开启
* configCHECK_FOR_STACK_OVERFLOW=2 后被钩子抓到(2026-09-21)。
* netTask 960 words:静态最坏链 2664 B(发送停滞→断连→重监听→SPI 故障→日志尾,
* 真实直调链,日志主导:链上 5 帧各含 192 B __con_line + 尾部 ~696 B);3840 B
* 配置余量 1176 B(30.6%),过 stack_report 规则。运行时水位仅 ~1.1 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 512 words:本区下方手工定义(不在 `.ioc` 的 Tasks01 里 → 重生成会丢,
* 靠审计 WARN 提示),只做读 RTC + 置就绪事件位,无深调用链。
*/
/* 存储测试变量(g_storage_stats / s_perf_buf)已迁至 test/board/storage_test_task.c */
osThreadId_t timeSyncTaskHandle;
const osThreadAttr_t timeSyncTask_attributes = {
.name = "timeSyncTask",
.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 = 1024 * 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_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);
HAL_Delay(200);
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, ... */
/* 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 = 基础线程:上方生成区的实例是全构建唯一实例(P1-6:旧
* #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);
/* P2-13(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.6):
* - 上报 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;
char num[16];
int i;
/* 文件名只取末段(全路径太长且无信息量) */
if (p != NULL) {
const char *q = p;
while (*q != '\0') {
if ((*q == '/') || (*q == '\\')) {
p = q + 1;
}
q++;
}
/* 有界拷贝:上限 48 字符 */
{
uint16_t len = 0u;
char name[49];
while ((len < 48u) && (p[len] != '\0')) {
name[len] = p[len];
len++;
}
name[len] = '\0';
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 {
char rev[16];
int n = 0;
while ((line > 0) && (n < 15)) {
rev[n++] = (char)('0' + (line % 10));
line /= 10;
}
while (n > 0) {
num[i++] = rev[--n];
}
}
dbg_console_write_raw(num, (unsigned)i);
dbg_console_write_raw(" (resetting)\r\n", 15u);
HAL_Delay(200); /* 等 UART 移位寄存器排空 */
NVIC_SystemReset();
}
/* USER CODE END Application */