7708f12c10
按键召唤答的是按键前的旧帧: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 回填受管区)。
267 lines
9.0 KiB
C
267 lines
9.0 KiB
C
/*
|
||
* 模块名称:主机侧单测桩 - 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 */
|