Files
STM32F4-Base/test/host/sb_link/stub/main.h
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

267 lines
9.0 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.
/*
* 模块名称:主机侧单测桩 - main.h(sb_link 模块专用)
* 模块功能:给 Drivers/BSP/ScreenLink/sb_link.c 提供"可脚本控制"的 HAL 面:
* ① 假 USART 寄存器:SR / DR 的**读取顺序与次数**可记账,ORE 锁存可注入;
* ② 假 ReceiveToIdle_IT:记录挂接收的缓冲/长度,返回值可脚本指定(OK/BUSY/TIMEOUT/ERROR)。
* ⚠ 仅供 test/host/sb_link/ 使用,不参与 MCU 构建(MCU 走真实 main.h -> stm32f4xx_hal.h)。
*
* 为什么必须桩:真实链 main.h -> stm32f4xx_hal.h -> stm32f4xx.h -> core_cm4.h 里是
* __IO 寄存器绝对地址与 ARMCC 内联汇编,宿主 MSVC 既编不动也访问不了。
*
* ★ 本桩唯一的技巧点:`->SR` / `->DR` 的读没法用普通结构体成员钩住(成员访问不能替换)。
* 于是把 USART_TypeDef 的两个成员做成**函数指针**,再用宏把成员名改写成「成员名()」:
* 生产代码 p_huart->Instance->SR
* 预处理后 p_huart->Instance->sr_cell()
* 即一次真实的函数调用 —— 测试因此能记账"读了什么、什么顺序",并让 ORE 的清除
* 严格按 RM0090 §30.6.1 的序列生效(**先读 SR 再读 DR 才清得掉**,顺序反了清不掉)。
* 改成员名之前先确认生产代码只用 `->SR` / `->DR` 这一种写法(sb_link.c 里就两处)。
*
* 创建日期:2026-10-29
*/
#ifndef MAIN_H
#define MAIN_H
#include <stddef.h>
#include <stdint.h>
/* ===== 与真实头同值的枚举(stm32f4xx_hal_def.h / stm32f4xx_hal_uart.h) ===== */
typedef enum {
HAL_OK = 0x00U,
HAL_ERROR = 0x01U,
HAL_BUSY = 0x02U,
HAL_TIMEOUT = 0x03U
} HAL_StatusTypeDef;
/* RxState 的两个取值:错误回调里"要不要重挂"就靠它判(见 sb_link.c 的 on_error 说明) */
#define HAL_UART_STATE_READY (0x20U)
#define HAL_UART_STATE_BUSY_RX (0x22U)
#define HAL_UART_STATE_BUSY_TX (0x24U)
/* HAL 的错误位(ErrorCode):ORE 是阻塞型,NE/FE/PE 是非阻塞型 */
#define HAL_UART_ERROR_PE (0x01U)
#define HAL_UART_ERROR_NE (0x02U)
#define HAL_UART_ERROR_FE (0x04U)
#define HAL_UART_ERROR_ORE (0x08U)
/* ===== ① 假 USART 寄存器(读法见文件头的★) ===== */
typedef struct {
uint32_t (*sr_cell)(void);
uint32_t (*dr_cell)(void);
} USART_TypeDef;
#define SR sr_cell()
#define DR dr_cell()
#define SB_FAKE_SR_ORE (0x0008U) /* USART_SR_ORE,bit3 */
#define SB_FAKE_SR_CLEAN (0x00C0U) /* TXE|TC 置位,无错误位 */
#define SB_FAKE_DR_VALUE (0x00A5U)
#define SB_FAKE_TRACE_MAX (8U)
static uint32_t sb_reg_trace[SB_FAKE_TRACE_MAX]; /* 'S' = 读了 SR,'D' = 读了 DR */
static uint32_t sb_reg_n;
static uint32_t sb_ore_latched; /* 1 = ORE 处于锁存态 */
static uint32_t sb_sr_seen; /* 1 = 刚读过 SR(DR 才清得掉 ORE) */
static uint32_t sb_sr_cell(void)
{
if (sb_reg_n < SB_FAKE_TRACE_MAX) {
sb_reg_trace[sb_reg_n] = (uint32_t)'S';
}
sb_reg_n++;
sb_sr_seen = 1U;
return (sb_ore_latched != 0U) ? SB_FAKE_SR_ORE : SB_FAKE_SR_CLEAN;
}
static uint32_t sb_dr_cell(void)
{
if (sb_reg_n < SB_FAKE_TRACE_MAX) {
sb_reg_trace[sb_reg_n] = (uint32_t)'D';
}
sb_reg_n++;
/* RM0090 §30.6.1:读 SR 之后再读 DR 才清 ORE;顺序反了清不掉(真机也这样) */
if (sb_sr_seen != 0U) {
sb_ore_latched = 0U;
}
sb_sr_seen = 0U;
return SB_FAKE_DR_VALUE;
}
static USART_TypeDef g_sb_uart = { sb_sr_cell, sb_dr_cell };
/* ===== ② 假 UART 句柄 ===== */
typedef struct {
uint32_t BaudRate;
uint32_t WordLength;
uint32_t StopBits;
uint32_t Parity;
uint32_t Mode;
uint32_t HwFlowCtl;
uint32_t OverSampling;
} UART_InitTypeDef;
typedef struct {
USART_TypeDef *Instance;
UART_InitTypeDef Init;
volatile uint32_t gState;
volatile uint32_t RxState;
volatile uint32_t ErrorCode;
} UART_HandleTypeDef;
/* 关中断三件套(2026-10-30 sb_tx_kick 的原子占位闸用)。宿主单线程、没有真中断,
* 空实现即可——临界区的"不交错"性质本来就不是本套件能测的面,逻辑面(busy/
* frame_ready 闸门、扣件状态机、超时保底)全部照测。 */
#define __get_PRIMASK() (0U)
#define __disable_irq() ((void)0)
#define __set_PRIMASK(x) ((void)(x))
/* ===== ③ 假 ReceiveToIdle_IT ===== */
#define SB_FAKE_ARM_OK (0U)
#define SB_FAKE_ARM_BUSY (1U)
#define SB_FAKE_ARM_TIMEOUT (2U)
#define SB_FAKE_ARM_ERROR (3U)
static uint32_t sb_arm_calls;
static uint32_t sb_arm_force; /* SB_FAKE_ARM_* */
static uint8_t *sb_arm_buf;
static uint16_t sb_arm_size;
static HAL_StatusTypeDef HAL_UARTEx_ReceiveToIdle_IT(UART_HandleTypeDef *p_huart,
uint8_t *p_buf,
uint16_t size)
{
sb_arm_calls++;
sb_arm_buf = p_buf;
sb_arm_size = size;
if (sb_arm_force == SB_FAKE_ARM_BUSY) {
return HAL_BUSY;
}
if (sb_arm_force == SB_FAKE_ARM_TIMEOUT) {
return HAL_TIMEOUT;
}
if (sb_arm_force == SB_FAKE_ARM_ERROR) {
return HAL_ERROR;
}
/* ORE 锁存时真机是收不了的(RXNE 不再置位)——桩照这个语义走,
* 于是"ORE 没清干净"必然表现为挂接收失败,用例能抓住 */
if (sb_ore_latched != 0U) {
return HAL_ERROR;
}
/* 挂上了:RxState 进 BUSY_RX。错误回调据此判断"接收是不是还在跑"
* (真机也是这样:非阻塞型错误不动 RxState,阻塞型错误会把它打回 READY) */
p_huart->RxState = HAL_UART_STATE_BUSY_RX;
return HAL_OK;
}
/* ===== ④ 假 Transmit_DMA(批 2:起发在中断里,收尾在 TxCplt) ===== */
#define SB_FAKE_TX_OK (0U)
#define SB_FAKE_TX_BUSY (1U)
#define SB_FAKE_TX_ERROR (2U)
static uint32_t sb_tx_calls;
static uint32_t sb_tx_force; /* SB_FAKE_TX_* */
static const uint8_t *sb_tx_buf;
static uint16_t sb_tx_len;
static HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *p_huart,
const uint8_t *p_buf,
uint16_t size)
{
sb_tx_calls++;
sb_tx_buf = p_buf;
sb_tx_len = size;
if (sb_tx_force == SB_FAKE_TX_BUSY) {
return HAL_BUSY;
}
if (sb_tx_force == SB_FAKE_TX_ERROR) {
return HAL_ERROR;
}
/* 真机成功起发后 gState = BUSY_TX,TxCplt 才打回 READY。
* 错误回调里"TX 是不是还在发"就靠这一位判(防残留 busy)。 */
p_huart->gState = HAL_UART_STATE_BUSY_TX;
return HAL_OK;
}
/* ===== ⑤ 假 DWT / CoreDebug / SystemCoreClock(组帧耗时统计用) ===== */
/* 真机由 core_cm4.h 提供。宿主侧要能"推进"计数器,所以 CYCCNT 也做成函数指针
* (与 SR / DR 同一手法):每读一次前进 sb_cyccnt_step,于是 sb_link 里那两次
* 读之间的差值 = 一个 step,测试就能钉住 "周期 → µs" 的除数。 */
typedef struct {
volatile uint32_t CTRL;
uint32_t (*cyccnt_cell)(void);
} DWT_TypeDef;
typedef struct {
volatile uint32_t DEMCR;
} CoreDebug_TypeDef;
#define CYCCNT cyccnt_cell()
static uint32_t sb_cyccnt_val;
static uint32_t sb_cyccnt_step; /* 每次读前进多少周期(0 = 恒 0,默认) */
static uint32_t sb_cyccnt_cell(void)
{
sb_cyccnt_val += sb_cyccnt_step;
return sb_cyccnt_val;
}
static DWT_TypeDef g_sb_dwt = { 0U, sb_cyccnt_cell };
static CoreDebug_TypeDef g_sb_coredebug;
static uint32_t SystemCoreClock = 168000000U;
#define DWT (&g_sb_dwt)
#define CoreDebug (&g_sb_coredebug)
#define CoreDebug_DEMCR_TRCENA_Msk (0x01000000U)
#define DWT_CTRL_CYCCNTENA_Msk (0x00000001U)
/* ===== ⑥ 测试用的复位/查询把手 ===== */
static void sb_fake_reset(void)
{
uint32_t i;
for (i = 0U; i < SB_FAKE_TRACE_MAX; i++) {
sb_reg_trace[i] = 0U;
}
sb_reg_n = 0U;
sb_ore_latched = 0U;
sb_sr_seen = 0U;
sb_arm_calls = 0U;
sb_arm_force = SB_FAKE_ARM_OK;
sb_arm_buf = NULL;
sb_arm_size = 0U;
sb_tx_calls = 0U;
sb_tx_force = SB_FAKE_TX_OK;
sb_tx_buf = NULL;
sb_tx_len = 0U;
g_sb_dwt.CTRL = 0U;
sb_cyccnt_val = 0U;
sb_cyccnt_step = 0U;
}
/* 寄存器读序列是否为期望的字符串(如 "SD"):长度不等或内容不符都返回 0 */
static int sb_fake_trace_is(const char *p_expect)
{
uint32_t i;
uint32_t len = (uint32_t)0U;
while (p_expect[len] != '\0') {
len++;
}
if (sb_reg_n != len) {
return 0;
}
for (i = 0U; i < len; i++) {
if (sb_reg_trace[i] != (uint32_t)(unsigned char)p_expect[i]) {
return 0;
}
}
return 1;
}
#endif /* MAIN_H */