Files
STM32F4-Base/Src/main.c
T
wandering1 7708f12c10 fix(ui/screenlink): 按键应答扣到按键后画面,反馈典型 250-450 ms 降至 70-135 ms
按键召唤答的是按键前的旧帧:uiTask 还没消化键答案就发走了,新画面
只能等下一次周期召唤(280 ms/轮)才带得出——真机反馈 250-450 ms 的
主构成(根因账本 docs/子系统设计/屏板链路设计说明.md §7.1)。

改动(用户批准范围 A+B+C;屏板固件未动,决策三不变):
- sb_link:按键召唤改为只入队不起发;uiTask 同拍重绘并组帧成功后交
  凭据(sb_link_key_rendered),TIM3 1 kHz 的 sb_link_ms_tick 以 ≤1 ms
  放行新帧。140 ms 截止(SB_KEY_RESP_MAX_MS,+59.6 ms 线上 <205 ms
  屏板放弃线)保底发旧帧,"一召一答"契约不破;线忙时同旧丢单、
  不留扣件。起发两中断源(USART1 IDLE/TIM3)共用 sb_tx_kick,
  "判忙→占位"在 PRIMASK 临界区原子完成。
- ui_task:排空注键挪到组帧之前,注键→即派即绘→组帧同拍闭环;
  组帧返回 BSP_OK 才交重绘凭据(BUSY 拍帧还没反映按键)。
- ui_port:注键单槽改 8 深环(连按不再互相覆盖,满丢最旧+计数);
  新增 ui_port_service_keys:同拍 lv_indev_read + lv_refr_now,
  省掉 indev ≤20 ms + 刷新 ≤20 ms 两段等待。
- ui_menu_view:菜单移动选中只失效新旧两行带(黄金参考零像素变化),
  滚动/异常回退整框失效。
- simulator:窗口循环 sleep 扣除本轮已耗时(原先无条件睡满
  lv_timer_handler 返回值,叠加 Windows Sleep 15.6 ms 粒度放大观感)。
- 串口 TX 上报新增 dfok/dfold(凭据放行新帧数/超时保底旧帧数):
  dfold 连涨 = uiTask 被饿 ≥140 ms 的直接现场证据。

验证:test/host/sb_link 164/164(新增 18 条:不立刻起发/凭据放行/
140 ms 截止保底/忙时不留扣件/陈旧凭据不放行等边界);菜单 8 状态 +
内容页 66 张黄金像素 PASS;Keil @build 0 错误 0 警告(两次);
BOM 99 文件无漂移、代码规范、doc-links 本次文件零问题、cppcheck PASS。
文档:屏板规划文档已被主干收编进设计说明,延迟账本落
docs/子系统设计/屏板链路设计说明.md §7.1,并同步 §4 口径、§8 接口、
§9.1 单测台账 146→164、§10 坑账本补条。

RAM 台账(分支不跑 --write,并行分支提交约定):本笔盖戳行
RW_IRAM1 94,336 B (72.0%) / RW_CCM 57,376 B (87.9%),较上一台账行
+4,400 B——为距上次台账构建以来分支累计差(含批 3 状态灯等未盖戳
提交),非全部本笔引入;本次自身增量按对象计为 ui_port.o +12 B
(8 深键环+丢计数+indev 指针)与 sb_link/o 数十字节级。
gate: only mem-budget/mem-numbers pending lead ledger backfill
(合并落主干后 Lead 统一 --write 回填受管区)。
2026-10-30 09:50:31 +08:00

328 lines
13 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 : main.c
* @brief : Main program body
******************************************************************************
* @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 "main.h"
#include "cmsis_os.h"
#include "adc.h"
#include "dma.h"
#include "i2c.h"
#include "iwdg.h"
#include "spi.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <string.h>
#include <stdio.h>
#include "ch395f.h"
#include "gd5f2gq5ue.h"
#include "tpafe5160.h"
#include "sd2506.h"
#include "rs485.h"
#include "net_socket.h"
#include "net_select.h"
#include "app_main.h"
#include "sys_clock.h"
#include "task_watchdog.h" /* task_wd_tick_probe():第 8b 轮时间源存活性取证 */
#include "adc_task.h" /* adc_tick_irq_notify():TIM3 采样节拍 → 唤醒 adcTask */
#include "sb_link.h" /* sb_link_ms_tick():TIM3 1 kHz 顺手做按键召唤延迟应答的毫秒时钟 */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* Error_Handler 统一降级(2026-10-12):本文件唯一的 DBG_* 调用点是 Error_Handler;其帧经
* MX_*_Init 调用链叠在 MSP 上,而 MSP 仅 1KB(startup Stack_Size 0x400)
* → 行缓冲收紧到 128 B(本文件最长行 = Error_Handler 行 ≈78 B < 128,不截断)。
* 日志成本口径见 dbg_log.h 的 DBG_LINE_BUF_MAX 注释。 */
#define DBG_LINE_BUF_MAX 128U
#define DBG_TAG "[MAIN]"
#include "dbg_log.h"
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* RS-485 实例与接收缓冲已收归 rs485.c(见 rs485.h 的 extern 导出) */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void MX_FREERTOS_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* 第 8b 轮取证:**必须在 HAL_Init() 之前**把上一轮的"时间源存活性"终值
* 快照到 .noinit 的 prev 槽——TIM7 在 HAL_Init 里就 HAL_TIM_Base_Start_IT
* 开始 1ms 中断,而开机上报要到 app_main_init(≈117ms)才跑;不先搬走,
* 上一轮终值会被本轮开机瞬间的 tick 覆盖(见 docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录)。 */
task_wd_tick_snapshot();
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* 复位原因锁存(2026-09-29,配合 IWDG):RCC_CSR 的复位标志只由上电复位(POR)
* 清除,其余复位(看门狗/软件/引脚/欠压)会跨复位保留——若不主动清除,
* 上一次的 IWDGRSTF 会一直挂着,导致每次启动都被误记为"看门狗复位"。
* 故在 HAL_Init()(此时 RCC 可用、串口尚未就绪)之后**立即读取并清除**,
* 锁存到 RCC_CSR 影子变量,待日志子系统起来后由 app_main_init 打印。
* 注意顺序:读 → 写 RMVF 清除,两步之间不可插入其它可能复位的代码。 */
{ /* 符号声明见 App/app/app_main.h */
g_rcc_csr_latched = RCC->CSR; /* 锁存:含 IWDGRSTF/SFTRSTF/PORRSTF 等 */
RCC->CSR |= RCC_CSR_RMVF; /* 写 1 清全部复位标志(只清标志,不影响时钟) */
}
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
MX_SPI2_Init();
MX_SPI1_Init();
MX_I2C1_Init();
MX_UART4_Init();
MX_UART5_Init();
MX_USART2_UART_Init();
MX_USART3_UART_Init();
MX_IWDG_Init();
MX_ADC1_Init();
MX_TIM3_Init();
/* USER CODE BEGIN 2 */
HAL_Delay(100);
/* 应用层初始化(时间子系统优先,详见 app_main_init) */
app_main_init();
/* USER CODE END 2 */
/* Init scheduler */
osKernelInitialize(); /* Call init function for freertos objects (in cmsis_os2.c) */
MX_FREERTOS_Init();
/* Start scheduler */
osKernelStart();
/* We should never get here as control is now taken by the scheduler */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI|RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.LSIState = RCC_LSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 4;
RCC_OscInitStruct.PLL.PLLN = 168;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 4;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/*
* RS-485 的 HAL UART 回调(HAL_UARTEx_RxEventCallback / HAL_UART_ErrorCallback /
* HAL_UART_TxCpltCallback)已收归 rs485.c 实现,本文件不再定义它们。如需处理
* 接收到的 RS-485 数据,请重定义 rs485.h 中的弱钩子 rs485_on_rx(uint8_t *buf, uint16_t size)。
* 屏板链路(USART1)在同样的三个回调里转发给 sb_link_on_rx_event / sb_link_on_error /
* sb_link_on_tx_cplt——那些回调是全局单例,全工程只能有一份定义,**不要在本文件再写一份**。
*/
/* USER CODE END 4 */
/**
* @brief Period elapsed callback in non blocking mode
* @note This function is called when TIM7 interrupt took place, inside
* HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
* a global variable "uwTick" used as application time base.
* @param htim : TIM handle
* @retval None
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* USER CODE BEGIN Callback 0 */
/* USER CODE END Callback 0 */
if (htim->Instance == TIM7)
{
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
/* 第 8b 轮取证:TIM7 每 1ms 把"时间源存活性"写进复位不丢的 .noinit 槽
* (uwTick + 中断计数)。复位后由 task_wd_seg_report() 打 TICKRUN 行,
* 与"进段打点"时刻对比,判定 TIM7(HAL 时基)是否在冻结同刻停摆
* (见 docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录)。
* ⚠ task_wd_tick_probe() 内不得调用任何 FreeRTOS API:TIM7 在 HAL_Init
* 阶段即开始中断,远早于调度器启动,此时 ISR 版内核 API 会触发 port.c 的
* 优先级断言并把系统卡死在本 ISR 内(2026-10-02 台架实测)。
* 放 USER CODE 区(L2):CubeMX 重生成不丢。 */
if (htim->Instance == TIM7)
{
task_wd_tick_probe();
}
/* 1 kHz 采样节拍:TIM3 更新事件内脉冲 CONVST(仅两次 GPIO 写,亚 µs),
* 再唤醒 adcTask 消费这一拍(ISR 只发唤醒,计算全留在任务)。
* 同一个更新事件还经 TRGO 硬件触发 STM32 ADC1 一轮 9 通道转换(DMA 自动搬运)
* → 两片转换器由同一硬件事件驱动,采样时刻对齐。
* TIM3 与 BUSY EXTI(EXTI9_5_IRQn) 同为抢占优先级 6 → 互不抢占,
* 读 RD 时序不会被下一次 CONVST 脉冲打断。详见
* docs/子系统设计/ADC采样节拍设计说明.md §3.3。放 USER CODE 区(L2)。
* ⚠ 节拍源必须能触发 ADC1:F4 上 ADC1 的外部触发清单里没有 TIM6/TIM7 的 TRGO
* (只接 DAC),判据见 docs/子系统设计/ADC采样节拍设计说明.md §3.2。
* ⚠ 唤醒必须走 adc_tick_irq_notify(),**不要**在这里直接 osThreadFlagsSet:
* TIM3 在 app_main_init() 就启动,远早于建任务与 osKernelStart,
* "消费者就绪"门在 adc_task.c 里(同本文件 TIM7 分支 2026-10-02 的实测坑)。 */
if (htim->Instance == TIM3)
{
tpafe5160_start_conv_irq();
adc_tick_irq_notify();
/* 按键召唤延迟应答的毫秒时钟(2026-10-30 按键延迟整治):只读写几个标志位 +
* 必要时点一次已经在 IDLE 分支验证过的 DMA 起发,µs 级、无内核 API;
* sb_link_init 之前 s_huart 为空直接返回——TIM3 比调度器早 ≈100 ms 就开跑。
* ⚠ 别把它挪去任务里:扣件的放行截止(140 ms)依赖 1 kHz 硬时钟,
* uiTask 自己被饿的时候恰恰最需要这个时钟还在走。 */
sb_link_ms_tick();
}
/* USER CODE END Callback 1 */
}
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* Error_Handler 统一降级(2026-10-12):旧形态 __disable_irq()+while(1) = 单处外设初始化
* 失败即整机挂死,只剩 IWDG 兜底且无任何自检日志。现:打 1 条自检日志 +
* NVIC_SystemReset()(与 4 个 Fault 处理同款自恢复;IWDG 是最后兜底而非唯一
* 出路;复位归因 SFTRSTF → 开机日志 "software (NVIC_SystemReset)")。
* ⚠ 不得 __disable_irq():日志必须能走串口。
* 调用方识别(为何不打地址):本函数无参数、调用点全在 CubeMX 生成区(不可加标签);
* ⚠ __builtin_return_address(0) **不可用**——AC5 前端接受但编成库函数调用,
* MicroLIB 无该符号 → 链接错 L6218E(2026-10-12 台架实证,勿再尝试);
* ARMCC __asm 只有整函数形态且取不到 C 符号、带不动 C 日志(看门狗设计说明 §10.4)。
* → 子系统定位靠启动日志锚点:失败点在"最后一条成功日志"与 main() 的
* MX_*_Init 顺序(GPIO→DMA→USART1→SPI2→SPI1→I2C1→UART4→UART5→USART2→
* USART3→IWDG→ADC1→TIM3→app_main_init)之间。
* 日志路径内核前安全:调用点全部在 osKernelStart 之前,控制台写者未注册
* → con_append 走 dbg_console_write_raw(DWT/计数有界轮询,huart1 未就绪
* 即返回),sys_clock_get_str 未初始化时打占位串——全程无内核 API。
* 日志成本:仅这 1 条(≈90 B Flash;行缓冲已在本文件 PM 区收到 128 B)。
* 重生成存活:函数体在 USER CODE Error_Handler_Debug 区(L2,KeepUserCode
* 逐字保留);另登记 check_cubemx_survival.py 审计项兜底(L3)。 */
DBG_ERROR("boot init failure, resetting (locate MX_*_Init via boot order)");
NVIC_SystemReset();
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */