dab0d8ba17
批 2 五项全部落地(docs/任务规划/屏板链路.md §3): - 2-1 sb_link_frame_update():uiTask 渲染后按需组帧,不留 3200 B 影子 副本,直接拿活显存与帧内点阵段 memcmp;组完自校验,过了才置 ready。 静态 4208 B 帧缓冲实测落 RW_IRAM1(0x20006d98),不在 CCM。 - 2-2 空闲中断校完 6 B 召唤帧直接点 TX DMA(sb_tx_kick),不排队不攒批; busy 期间再来的召唤丢帧并计数,绝不叠发(屏板收帧一次 IDLE 一包)。 - 2-3 按键走环形队列(中断入、任务出),uiTask 按 §4.3 表映射 UI_KEY_* 后调 ui_port_queue_key;9/10(RESET/FACTORY)按决策四先丢弃并留痕。 - 2-4 HAL_UART_TxCpltCallback 在 rs485.c 加第三行转发(三个全局回调 落点收一处,防日后重复定义);busy 冻结帧缓冲,错误中断按 gState 收拾被打断的 TX,防残留 busy 把链路永久锁死。 - 2-5 连续 >=10 s 无有效召唤帧才判离线(不按 500 ms——屏板约 5000 s 自复位有 1 s 空窗),恢复即清标志;CRC 错帧静默丢弃并计数。 实测:台架 40 s(PC 顶替屏板,--key-fixed 2 持续改画面)rx=143 ok=143 keys=143 crc=0 / tx=143 done=143 drop=0 abor=0 fb=144 bad=0;脚本一停 10 s 准点出 OFFLINE。组帧耗时 509 µs 典型 / 709 µs 峰值——原 0.3 ms 判据系估算,已按实测在规划文档改口为 1 ms 并给出压降路径(未做)。 附带:rs485_on_rx 补 CODE-RULE-09 文档块(本轮改 rs485.c 触发整文件 重扫被拦);CRC 循环计数改 32 位(消掉 ARMCC 每轮 UXTH);bench 脚本 加 --reply/--key-fixed/--dump,并写明 CH340 收不了 700 kbaud 连续流、 --reply 收不全 4208 B 不是固件问题。 主机单测 146/146 PASSED(+65);Keil 0 错误 0 警告;ci_local 全绿 (SKIP=nested-comment,worktree 限制,主树脚本另查 0 新发)。 Code=238,936 B / RW_IRAM1 65.5 % / RW_CCM 87.9 %。
260 lines
8.6 KiB
C
260 lines
8.6 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;
|
||
|
||
/* ===== ③ 假 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 */
|