7c8669e52a
批 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)。
187 lines
6.1 KiB
C
187 lines
6.1 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)
|
||
|
||
/* 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 */
|