Files
STM32F4-Base/test/host/sb_link/stub/main.h
T
wandering1 7c8669e52a feat(屏板): USART1 700 kbaud + 收中断传输层 sb_link(1-2/1-3)
批 1 的 1-2 / 1-3:把屏板(DTU-LCD)发来的 6 B 帧 A 在 USART1 上稳定收下来。

1-2 波特率:.ioc 不保存任何 UART 的波特率(只有
USART1.IPParameters=VirtualMode),GUI 里改也不落盘,所以只能在
Src/usart.c 的 USER CODE BEGIN USART1_Init 2 区覆盖 huart1.Init.BaudRate
后重调一次 HAL_UART_Init(第二次调用时 gState 已非 RESET,HAL 只重算
BRR、不再跑 MspInit,GPIO/DMA/NVIC 不会被重配)。开机日志实测
BRR=0x0078,与理论值 84 MHz/(16x700 kHz)=7.5 逐位相符,误差 0%。

1-3 收中断:新建 Drivers/BSP/ScreenLink/sb_link.c/h——16 B 缓冲 + IDLE
中断断帧、帧 A 校验、错误中断里按 RM0090 清 ORE/NE/FE/PE、周期统计上报。
HAL_UARTEx_RxEventCallback / HAL_UART_ErrorCallback 是全局单例(唯一实现
在 rs485.c),故只在那里各加一行转发,按句柄过滤放在 sb_link 内。

台架实测(PC + USB 转 TTL 顶替屏板,COM6@700000)撞出两个真问题:
- 错误回调里无条件重挂 -> 出现 err=160 / rearm=160 同步增长,看着像每帧
  都重挂失败。真因是 HAL 把 NE/FE/PE 归为非阻塞型(接收照跑、RxState 仍是
  BUSY_RX),此时 ReceiveToIdle_IT 只会返回 HAL_BUSY。改为仅当
  RxState == HAL_UART_STATE_READY(ORE 那类阻塞型)时才重挂。
- 日志字面量里带中文 -> ARMCC #870-D invalid multibyte character
  sequence。字面量一律纯 ASCII,中文只写注释。

验证:主机单测 81/81 PASSED(test/host/sb_link/,其中按 RM0090 的清除语义
把"必须 SR->DR 顺序才清得掉 ORE"钉成断言);Keil 0 错误 0 警告;台架
rx == 发送帧数、ok == rx、crc=0、len=0、size=6、keys 随按键帧递增;先用
错波特率灌 64 B 垃圾(len=31、bits=0x06)后自动恢复且 rearm=0。
新增 test/host/screenlink_bench.py 作为可复跑的屏板激励(--key / --glitch-baud)。
2026-10-29 20:13:18 +08:00

187 lines
6.1 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)
/* 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;
}
/* ===== ④ 测试用的复位/查询把手 ===== */
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;
}
/* 寄存器读序列是否为期望的字符串(如 "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 */