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)。
This commit is contained in:
2026-10-29 20:13:18 +08:00
parent 2c69a6ca07
commit 7c8669e52a
19 changed files with 1573 additions and 38 deletions
+58
View File
@@ -0,0 +1,58 @@
/*
* 模块名称:主机侧单测桩 - dbg_log.h(sb_link 模块专用)
* 模块功能:把 DBG_INFO / DBG_ERROR 换成"写进一个可读的行缓冲",于是用例能断言
* 「打了几行、第一行说了什么」,而不是只能靠人看台架串口。
* ⚠ 仅供 test/host/sb_link/ 使用,不参与 MCU 构建。
* 真实 dbg_log.h 会拉进 console_log.h / sys_clock.h(App 层)并带时间戳,
* 本模块的用例只关心"有没有上报",故整份替换掉,不搬那条链。
* 创建日期:2026-10-29
*/
#ifndef DBG_LOG_H
#define DBG_LOG_H
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#define SB_LOG_MAX (32U)
#define SB_LOG_LINE_MAX (192U)
static char sb_log[SB_LOG_MAX][SB_LOG_LINE_MAX];
static uint32_t sb_log_n;
static void sb_log_add(const char *p_lvl, const char *p_fmt, ...)
{
va_list ap;
size_t used;
if (sb_log_n >= SB_LOG_MAX) {
return;
}
(void)snprintf(sb_log[sb_log_n], SB_LOG_LINE_MAX, "%s ", p_lvl);
used = strlen(sb_log[sb_log_n]);
va_start(ap, p_fmt);
(void)vsnprintf(sb_log[sb_log_n] + used, (size_t)(SB_LOG_LINE_MAX - used), p_fmt, ap);
va_end(ap);
sb_log_n++;
}
static void sb_log_reset(void)
{
sb_log_n = 0U;
sb_log[0][0] = '\0';
}
/* 第 idx 行是否包含 needle(idx 越界返回 0) */
static int sb_log_has(uint32_t idx, const char *p_needle)
{
if ((idx >= sb_log_n) || (idx >= SB_LOG_MAX)) {
return 0;
}
return (strstr(sb_log[idx], p_needle) != NULL) ? 1 : 0;
}
#define DBG_INFO(...) sb_log_add("INFO", __VA_ARGS__)
#define DBG_ERROR(...) sb_log_add("ERR", __VA_ARGS__)
#endif /* DBG_LOG_H */
+186
View File
@@ -0,0 +1,186 @@
/*
* 模块名称:主机侧单测桩 - 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 */