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:
@@ -16,6 +16,8 @@
|
||||
|
||||
#include "rs485.h"
|
||||
|
||||
#include "sb_link.h" /* 屏板链路 USART1:本文件是全局 UART 回调的唯一实现处,见下方回调段 */
|
||||
|
||||
/*
|
||||
* 函数功能:把 HAL 的状态码映射成项目统一的 bsp_err_t(边界映射,2026-10-22 试点)
|
||||
* 入口参数:status - HAL_* 返回的状态
|
||||
@@ -170,7 +172,16 @@ uint16_t rs485_rx_get_size(const rs485_handle_t *p_handle) {
|
||||
return p_handle->rx_size;
|
||||
}
|
||||
|
||||
/* ======================== HAL 回调(UART5 / RS-485 专属) ======================== */
|
||||
/* ==================== HAL 回调(本工程唯一实现处) ====================
|
||||
*
|
||||
* ⚠ 这两个回调是 HAL 的**全局单例**:一个工程只能有一份定义
|
||||
* (Inc/stm32f4xx_hal_conf.h 的 USE_HAL_UART_REGISTER_CALLBACKS = 0,
|
||||
* 没法按句柄注册各自的回调)。所以新增通道时**在这里加一行转发**,
|
||||
* 不要在别处再定义一份 —— 重复定义是链接期的 multiple definition,编不过。
|
||||
*
|
||||
* 按句柄过滤放在各通道自己的模块里(如 sb_link_on_rx_event 内部判 p_huart),
|
||||
* 本文件不必知道别的通道用的是哪个 huart,也就不会因为它们换口而改动。
|
||||
*/
|
||||
|
||||
/*
|
||||
* 应用层接收钩子默认实现(弱符号,可被应用重定义)。
|
||||
@@ -196,6 +207,9 @@ void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {
|
||||
/* 重新开启接收 */
|
||||
rs485_receive_start(&g_rs485);
|
||||
}
|
||||
|
||||
/* 屏板链路(USART1):非本链路的句柄在 sb_link 内部被忽略 */
|
||||
sb_link_on_rx_event(huart, Size);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -210,4 +224,7 @@ void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) {
|
||||
/* 重新开启接收 */
|
||||
rs485_receive_start(&g_rs485);
|
||||
}
|
||||
|
||||
/* 屏板链路(USART1):清 ORE 并重挂接收(不清 ORE 会永久静默),见 sb_link.c */
|
||||
sb_link_on_error(huart);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
* 模块名称:屏板链路传输层(USART1 中断接收)
|
||||
* 模块功能:IDLE 空闲中断断帧收 6 B 帧 A、帧 A 校验、错误中断里清 ORE 并重挂接收、
|
||||
* 累计统计与周期上报。发送侧(帧 B 组帧 + TX DMA 起发)属批 2,不在本文件。
|
||||
* 适用平台:STM32F4 系列
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-10-29
|
||||
* 修改记录:
|
||||
* 2026-10-29 王建锋 创建初始版本(批 1 的 1-2 / 1-3)
|
||||
*
|
||||
* 三个容易踩的点(改之前先读,都已经在台架上被别的东西咬过一次):
|
||||
*
|
||||
* ① **每个回调末尾都必须重挂接收**。HAL_UARTEx_ReceiveToIdle_IT 是一次性的:
|
||||
* 一次 IDLE 事件处理完,HAL 就把 RxState 打回 READY 并关掉 RXNEIE/IDLEIE
|
||||
* (stm32f4xx_hal_uart.c 的 UART_IRQHandler 里 IDLE 那一支)。漏挂一次链路就
|
||||
* 永久静默,而现象与"屏板没上电"完全一样——没有报错、没有计数。
|
||||
*
|
||||
* ② **错误回调里必须自己清 ORE**。HAL 在阻塞型错误里已经把接收停掉并打回 READY,
|
||||
* 但它不清 ORE(RM0090 §30.6.1:先读 SR 再读 DR)。ORE 一旦锁存,RXNE 不再置位
|
||||
* → 接收永远进不来。这就是"拔插一次 TX 线之后屏板再也收不到"的真因。
|
||||
*
|
||||
* ③ **本文件只按句柄过滤,不做业务动作**。全局回调(HAL_UARTEx_RxEventCallback /
|
||||
* HAL_UART_ErrorCallback)的唯一实现处在 Drivers/BSP/RS485/rs485.c,那里只做一行
|
||||
* 转发;过滤在这里做,于是新增通道不必去改 rs485.c 的判据。业务动作(注键、
|
||||
* 刷新画面)全部留给任务侧——注键不能在中断里做(docs/任务规划/屏板链路.md 决策三)。
|
||||
*/
|
||||
|
||||
#define DBG_TAG "[SBLINK]"
|
||||
#include "dbg_log.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "sb_link.h"
|
||||
|
||||
/* ======================== 内部状态 ======================== */
|
||||
|
||||
/* 绑定的 USART1 句柄;NULL = 未初始化(所有入口据此直接返回) */
|
||||
static UART_HandleTypeDef *s_huart;
|
||||
|
||||
/* 接收缓冲:IDLE 中断的落地目标,长度 SB_LINK_RX_BUF_SIZE(16 B) */
|
||||
static uint8_t s_rx_buf[SB_LINK_RX_BUF_SIZE];
|
||||
|
||||
/* 累计统计:中断侧自增、任务侧只读,故 volatile */
|
||||
static volatile sb_link_stats_t s_stats;
|
||||
|
||||
/* 上报用:上次上报时刻与上次上报时的计数(用来判"有没有变化") */
|
||||
static uint32_t s_report_ms;
|
||||
static uint32_t s_report_events;
|
||||
static uint32_t s_report_err_irq;
|
||||
static uint32_t s_report_rearm_fail;
|
||||
static uint8_t s_first_frame_logged;
|
||||
|
||||
/* ======================== 内部函数 ======================== */
|
||||
|
||||
/*
|
||||
* 函数功能:把 HAL 的返回状态映射成项目统一的 bsp_err_t
|
||||
* 入口参数:status - HAL_* 返回的状态
|
||||
* 返回值:对应的 bsp_err_t;HAL_BUSY 归 BSP_ERR_BUSY(可重试),HAL_OK 归 BSP_OK
|
||||
* 限定条件:仅本文件内部使用
|
||||
* 函数说明:与 rs485.c 里那个同名映射同构——HAL 的码**不在本模块之外出现**
|
||||
* (bsp_err.h 的边界纪律),所以映射写成 static 而不是往公共头里塞。
|
||||
*/
|
||||
static bsp_err_t sb_err_from_hal(HAL_StatusTypeDef status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case HAL_OK:
|
||||
return BSP_OK;
|
||||
case HAL_BUSY:
|
||||
return BSP_ERR_BUSY;
|
||||
case HAL_TIMEOUT:
|
||||
return BSP_ERR_TIMEOUT;
|
||||
default:
|
||||
return BSP_ERR_IO;
|
||||
}
|
||||
}
|
||||
|
||||
/* ======================== 公开函数实现 ======================== */
|
||||
|
||||
/*
|
||||
* 函数功能:初始化屏板链路并挂上第一轮接收
|
||||
* 入口参数:p_huart - 已由 CubeMX 初始化完成的 USART1 句柄(非空)
|
||||
* 返回值:BSP_OK / BSP_ERR_PARAM / BSP_ERR_STATE / BSP_ERR_BUSY / BSP_ERR_IO(见头文件)
|
||||
* 限定条件:须在 osKernelStart 之前或任一任务上下文调用
|
||||
* 函数说明:波特率由 Src/usart.c 的 USER CODE 区设置,本函数只做一次交叉核对——
|
||||
* 对不上就报 BSP_ERR_STATE 并打一行 ERR,免得"115200 收 700 kbaud"这种
|
||||
* 静默错配被误判成接线或协议问题(那种错配的表象是 100 % CRC 错帧)。
|
||||
*/
|
||||
bsp_err_t sb_link_init(UART_HandleTypeDef *p_huart)
|
||||
{
|
||||
if (p_huart == NULL)
|
||||
{
|
||||
return BSP_ERR_PARAM;
|
||||
}
|
||||
|
||||
s_huart = p_huart;
|
||||
(void)memset((void *)&s_stats, 0, sizeof(s_stats));
|
||||
s_report_ms = 0U;
|
||||
s_report_events = 0U;
|
||||
s_report_err_irq = 0U;
|
||||
s_report_rearm_fail = 0U;
|
||||
s_first_frame_logged = 0U;
|
||||
|
||||
if (p_huart->Init.BaudRate != SB_LINK_BAUD)
|
||||
{
|
||||
/* 串口波特率被打回 CubeMX 的默认值 —— 即 Src/usart.c 的 USER CODE 覆盖丢了。
|
||||
* ⚠ 日志字面量只写 ASCII:ARMCC 按本地代码页解多字节,中文字面量会报
|
||||
* #870-D invalid multibyte character sequence(2026-10-29 实测)。 */
|
||||
DBG_ERROR("baud=%lu expect=%lu (USER CODE override in usart.c lost)",
|
||||
(unsigned long)p_huart->Init.BaudRate, (unsigned long)SB_LINK_BAUD);
|
||||
return BSP_ERR_STATE;
|
||||
}
|
||||
|
||||
return sb_link_receive_start();
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:重新挂上一轮 IDLE 中断接收
|
||||
* 入口参数:无
|
||||
* 返回值:BSP_OK / BSP_ERR_STATE / BSP_ERR_BUSY / BSP_ERR_IO(见头文件)
|
||||
* 限定条件:可在中断上下文调用;不可阻塞
|
||||
* 函数说明:失败只累计 rearm_fail 计数、不重试——在中断里重试等于把一次总线抖动
|
||||
* 放大成中断风暴。真失败了靠任务侧的统计行暴露(rearm 列非 0 就是链路已死)。
|
||||
*/
|
||||
bsp_err_t sb_link_receive_start(void)
|
||||
{
|
||||
bsp_err_t ret;
|
||||
|
||||
if (s_huart == NULL)
|
||||
{
|
||||
return BSP_ERR_STATE;
|
||||
}
|
||||
|
||||
ret = sb_err_from_hal(HAL_UARTEx_ReceiveToIdle_IT(s_huart, s_rx_buf, SB_LINK_RX_BUF_SIZE));
|
||||
if (ret != BSP_OK)
|
||||
{
|
||||
s_stats.rearm_fail++;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:IDLE 事件入口(由 HAL_UARTEx_RxEventCallback 转发进来)
|
||||
* 入口参数:p_huart - 触发事件的句柄(非本链路的直接忽略)
|
||||
* size - 本次断出来的字节数
|
||||
* 返回值:无
|
||||
* 限定条件:中断上下文;只做 CRC 与计数,不得打日志、不得阻塞
|
||||
* 函数说明:先判长度再解析——长度不是 6 时连 CRC 都不必算(屏板只发 6 B 帧 A)。
|
||||
* 解析结论只落统计,业务动作由任务侧取走统计后做(注键不能在中断里做)。
|
||||
*/
|
||||
void sb_link_on_rx_event(UART_HandleTypeDef *p_huart, uint16_t size)
|
||||
{
|
||||
sb_frame_a_t frame;
|
||||
|
||||
if ((s_huart == NULL) || (p_huart != s_huart))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
s_stats.rx_events++;
|
||||
s_stats.last_size = size;
|
||||
|
||||
if (size != SB_FRAME_A_LEN)
|
||||
{
|
||||
s_stats.rx_len_err++;
|
||||
}
|
||||
else
|
||||
{
|
||||
bsp_err_t ret;
|
||||
|
||||
(void)memset((void *)&frame, 0, sizeof(frame));
|
||||
ret = sb_frame_a_parse(s_rx_buf, size, &frame);
|
||||
if (ret == BSP_OK)
|
||||
{
|
||||
s_stats.rx_ok++;
|
||||
s_stats.last_cmd = frame.cmd;
|
||||
s_stats.last_key = frame.key;
|
||||
if (frame.cmd == SB_CMD_KEY)
|
||||
{
|
||||
s_stats.key_events++;
|
||||
}
|
||||
}
|
||||
else if (ret == BSP_ERR_CRC)
|
||||
{
|
||||
s_stats.rx_crc_err++;
|
||||
}
|
||||
else
|
||||
{
|
||||
s_stats.rx_fmt_err++;
|
||||
}
|
||||
}
|
||||
|
||||
(void)sb_link_receive_start();
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:错误事件入口(由 HAL_UART_ErrorCallback 转发进来)
|
||||
* 入口参数:p_huart - 触发事件的句柄(非本链路的直接忽略)
|
||||
* 返回值:无
|
||||
* 限定条件:中断上下文;不得打日志、不得阻塞
|
||||
* 函数说明:先读 SR 再读 DR 清 ORE/NE/FE/PE,再按需重挂接收。两个细节都不能省:
|
||||
* 1. **清除顺序**:RM0090 §30.6.1 规定的序列就是"读 SR 后读 DR",只读 DR 清不掉 ORE。
|
||||
* 2. **只有 HAL 真把接收停了才重挂**:HAL 把错误分成两类(stm32f4xx_hal_uart.c 的
|
||||
* UART_IRQHandler)——
|
||||
* · ORE 是**阻塞型**:HAL 先 UART_EndRxTransfer(RxState 打回 READY、关接收中断)
|
||||
* 再回调 → 必须重挂,否则永久静默;
|
||||
* · NE/FE/PE 是**非阻塞型**:HAL 不动接收,RxState 仍是 BUSY_RX → 此时再调
|
||||
* ReceiveToIdle_IT 只会拿到 HAL_BUSY,白记一次"重挂失败"。
|
||||
* 2026-10-29 台架实测踩的正是这一条:每帧都伴随 NE/FE,于是 rearm_fail 跟着
|
||||
* err_irq 一起涨(160/160),看着像链路坏了,其实一次都没真失败。
|
||||
* 判据用 RxState 而不是 ErrorCode 的位:它直接回答"现在挂着接收没有",
|
||||
* 不依赖 HAL 怎么分组错误位。
|
||||
*/
|
||||
void sb_link_on_error(UART_HandleTypeDef *p_huart)
|
||||
{
|
||||
if ((s_huart == NULL) || (p_huart != s_huart))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
s_stats.err_irq++;
|
||||
s_stats.err_bits |= p_huart->ErrorCode;
|
||||
|
||||
/* STM32F4 通过读 SR + DR 清除 ORE/NE/FE/PE(与 rs485.c 的错误回调同一手法) */
|
||||
(void)p_huart->Instance->SR;
|
||||
(void)p_huart->Instance->DR;
|
||||
|
||||
/* 只有"接收已经停了"才需要重挂(见上面第 2 条) */
|
||||
if (p_huart->RxState == HAL_UART_STATE_READY)
|
||||
{
|
||||
(void)sb_link_receive_start();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:链路统计上报(任务侧周期调用)
|
||||
* 入口参数:now_ms - 当前毫秒时基
|
||||
* 返回值:无
|
||||
* 限定条件:任务上下文调用(内部会打日志)
|
||||
* 函数说明:每 SB_LINK_REPORT_MS 检查一次,计数有变化才打一行;第一帧成功收到时
|
||||
* 立刻单独打一行(不等周期),台架刚上电就能确认"链路通了、长度对、CRC 过"。
|
||||
*/
|
||||
void sb_link_poll(uint32_t now_ms)
|
||||
{
|
||||
sb_link_stats_t st;
|
||||
|
||||
if (s_huart == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
sb_link_get_stats(&st);
|
||||
|
||||
/* 首帧单独报一次:台架刚上电时不必等一个上报周期才知道链路通没通 */
|
||||
if ((s_first_frame_logged == 0U) && (st.rx_ok > 0U))
|
||||
{
|
||||
s_first_frame_logged = 1U;
|
||||
DBG_INFO("first frame: size=%u cmd=%u key=%u",
|
||||
(unsigned)st.last_size, (unsigned)st.last_cmd, (unsigned)st.last_key);
|
||||
}
|
||||
|
||||
if ((uint32_t)(now_ms - s_report_ms) < SB_LINK_REPORT_MS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
s_report_ms = now_ms;
|
||||
|
||||
/* 无变化不刷屏:链路安静时一行都不出,出的一行就是有事实发生 */
|
||||
if ((st.rx_events == s_report_events) &&
|
||||
(st.err_irq == s_report_err_irq) &&
|
||||
(st.rearm_fail == s_report_rearm_fail))
|
||||
{
|
||||
return;
|
||||
}
|
||||
s_report_events = st.rx_events;
|
||||
s_report_err_irq = st.err_irq;
|
||||
s_report_rearm_fail = st.rearm_fail;
|
||||
|
||||
DBG_INFO("rx=%lu ok=%lu keys=%lu crc=%lu len=%lu fmt=%lu err=%lu bits=0x%02lX rearm=%lu size=%u key=%u",
|
||||
(unsigned long)st.rx_events, (unsigned long)st.rx_ok,
|
||||
(unsigned long)st.key_events,
|
||||
(unsigned long)st.rx_crc_err, (unsigned long)st.rx_len_err,
|
||||
(unsigned long)st.rx_fmt_err, (unsigned long)st.err_irq,
|
||||
(unsigned long)st.err_bits, (unsigned long)st.rearm_fail,
|
||||
(unsigned)st.last_size, (unsigned)st.last_key);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:取一份链路统计快照
|
||||
* 入口参数:p_out - 输出缓冲(非空)
|
||||
* 返回值:无
|
||||
* 限定条件:任一上下文可调;不保证同一时刻一致(中断侧仍在自增)
|
||||
* 函数说明:逐字段拷出。内部结构是 volatile,整体赋值会把限定词丢掉(ARMCC 报错),
|
||||
* 故显式逐字段写。
|
||||
*/
|
||||
void sb_link_get_stats(sb_link_stats_t *p_out)
|
||||
{
|
||||
if (p_out == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
p_out->rx_events = s_stats.rx_events;
|
||||
p_out->rx_ok = s_stats.rx_ok;
|
||||
p_out->rx_crc_err = s_stats.rx_crc_err;
|
||||
p_out->rx_len_err = s_stats.rx_len_err;
|
||||
p_out->rx_fmt_err = s_stats.rx_fmt_err;
|
||||
p_out->key_events = s_stats.key_events;
|
||||
p_out->err_irq = s_stats.err_irq;
|
||||
p_out->err_bits = s_stats.err_bits;
|
||||
p_out->rearm_fail = s_stats.rearm_fail;
|
||||
p_out->last_size = s_stats.last_size;
|
||||
p_out->last_cmd = s_stats.last_cmd;
|
||||
p_out->last_key = s_stats.last_key;
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* 模块名称:屏板链路传输层(USART1 中断接收)
|
||||
* 模块功能:把屏板(DTU-LCD)发来的 6 B 帧 A 从 USART1 上稳定收下来:
|
||||
* IDLE 空闲中断断帧、帧 A 校验、错误中断里清 ORE 并重挂接收、统计上报。
|
||||
* **本层不发送、不组帧**(帧 B 的组帧与 TX DMA 起发属批 2,见
|
||||
* docs/任务规划/屏板链路.md §3 的 2-1 / 2-2)。
|
||||
* 适用平台:STM32F4 系列(依赖 HAL UART 的 ReceiveToIdle_IT)
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-10-29
|
||||
* 修改记录:
|
||||
* 2026-10-29 王建锋 创建初始版本(批 1 的 1-2 / 1-3:700 kbaud + 收中断)
|
||||
*
|
||||
* 为什么单独一层、不并进 sb_proto:
|
||||
* sb_proto 是纯逻辑(CRC / 组帧 / 解析),能在 test/host 里原样 include 跑单测;
|
||||
* 本文件依赖 HAL 句柄与中断上下文,属 MCU 专用。两者分开,前者才留得住"零桩单测"。
|
||||
*
|
||||
* 全局回调的落点(改之前先读):
|
||||
* HAL 的 HAL_UARTEx_RxEventCallback / HAL_UART_ErrorCallback 是**全局单例**,
|
||||
* 本工程唯一实现处是 Drivers/BSP/RS485/rs485.c(USE_HAL_UART_REGISTER_CALLBACKS = 0,
|
||||
* 没法按句柄注册回调)。那里只做一行转发,**按句柄过滤在本文件里做**——
|
||||
* 于是 rs485.c 不必知道屏板链路用的是哪个 huart,新增通道也不用改它的判据。
|
||||
*/
|
||||
|
||||
#ifndef SB_LINK_H
|
||||
#define SB_LINK_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "main.h" /* UART_HandleTypeDef / USART_TypeDef / HAL_StatusTypeDef */
|
||||
#include "bsp_err.h" /* bsp_err_t / BSP_OK / BSP_ERR_*:对外只报统一错误码 */
|
||||
#include "sb_proto.h" /* SB_FRAME_A_LEN / sb_frame_a_t / sb_frame_a_parse */
|
||||
|
||||
/* ============================================================================
|
||||
* 一、链路参数
|
||||
* ==========================================================================*/
|
||||
|
||||
/*
|
||||
* 串口参数:700000 8N1、16 倍过采样。
|
||||
* 实际写入发生在 Src/usart.c 的 `USER CODE BEGIN USART1_Init 2`(CubeMX 的 .ioc
|
||||
* **不保存任何 UART 的波特率**,GUI 里改也不落盘,所以只能在 USER CODE 区覆盖)。
|
||||
* 理论值:APB2 84 MHz / (16 x 700 kHz) = 7.5 → BRR = 0x0078,误差 0 %。
|
||||
*/
|
||||
#define SB_LINK_BAUD (700000U)
|
||||
|
||||
/*
|
||||
* 接收缓冲:16 B 足够。
|
||||
* 屏板只发 6 B 的帧 A(周期召唤 / 按键召唤),16 B 留出一次噪声冲击的余量。
|
||||
* ⚠ 不要照抄屏板侧的 4208 B——那是帧 B 的长度,方向相反(主机发、屏板收),
|
||||
* 放进接收侧白占 SRAM(判据见 docs/任务规划/屏板链路.md §1.3)。
|
||||
*/
|
||||
#define SB_LINK_RX_BUF_SIZE (16U)
|
||||
|
||||
/*
|
||||
* 统计上报周期(ms)。只在计数有变化时才打一行,链路空闲不刷屏;
|
||||
* 台架验收靠的就是这一行里的累计值(判据见 docs/任务规划/屏板链路.md §3 的 1-3)。
|
||||
*/
|
||||
#define SB_LINK_REPORT_MS (5000U)
|
||||
|
||||
/* ============================================================================
|
||||
* 二、链路统计
|
||||
* ==========================================================================*/
|
||||
|
||||
/*
|
||||
* 全部字段都是**累计值**,由中断侧自增、任务侧只读(读出来的是一份快照)。
|
||||
* 分门别类记而不只记"错了多少次",是因为台架上这几类错指向完全不同的原因:
|
||||
* · rx_crc_err —— 波特率/接线问题(帧收到了但字节错位),最常见的真故障;
|
||||
* · rx_len_err —— 断帧判据或噪声冲击(收到的不是 6 B),也可能是一次噪声把两帧粘在一起;
|
||||
* · rx_fmt_err —— 地址/功能码/命令字不符(帧完整但内容不是给本机的);
|
||||
* · err_irq —— ORE/NE/FE/PE 触发过错误中断,即线上真的有噪声或溢出。
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
uint32_t rx_events; /* IDLE 事件总数(每来一包 +1,含错帧) */
|
||||
uint32_t rx_ok; /* 帧 A 校验通过(含 cmd = 按键帧) */
|
||||
uint32_t rx_crc_err; /* CRC 不符 */
|
||||
uint32_t rx_len_err; /* 长度不是 SB_FRAME_A_LEN */
|
||||
uint32_t rx_fmt_err; /* 地址/功能码/命令字不符 */
|
||||
uint32_t key_events; /* 其中 cmd == SB_CMD_KEY 的帧数(按键召唤,2-3 的消费源) */
|
||||
uint32_t err_irq; /* 错误中断次数(ORE/NE/FE/PE) */
|
||||
uint32_t err_bits; /* 错误中断里见过哪些位(HAL_UART_ERROR_* 的并集,见下) */
|
||||
uint32_t rearm_fail; /* 重挂接收失败次数(正常应恒 0,非 0 说明链路已静默) */
|
||||
uint16_t last_size; /* 最近一次 IDLE 事件的字节数 */
|
||||
uint8_t last_cmd; /* 最近一帧有效帧的命令类型(SB_CMD_*) */
|
||||
uint8_t last_key; /* 最近一帧有效帧的键值(无键为 0) */
|
||||
} sb_link_stats_t;
|
||||
|
||||
/*
|
||||
* err_bits 的位定义 = HAL 的 HAL_UART_ERROR_* 原值(PE 0x01 / NE 0x02 / FE 0x04 / ORE 0x08)。
|
||||
* 直接照抄 HAL 的值而不是自己另编一套:这一列的唯一用途是**判断物理层哪里不对**,
|
||||
* 与 HAL 手册对照着看最省事。经验判读:
|
||||
* · 只有 ORE(0x08) → 处理不过来(本链路 6 B/280 ms 不该出现,出现了就是重挂漏了);
|
||||
* · NE(0x02)为主 → 线上有毛刺(线太长、无屏蔽、共地不良);
|
||||
* · FE(0x04)为主 → 波特率对不上(对端不是 700000,或分频误差过大);
|
||||
* · 全是 NE/FE 但 rx_ok 照样涨 → 数据没坏、只是采样点被判噪声,属"能用的告警",别当故障。
|
||||
*/
|
||||
|
||||
/* ============================================================================
|
||||
* 三、公开接口
|
||||
* ==========================================================================*/
|
||||
|
||||
/*
|
||||
* 函数功能:初始化屏板链路并挂上第一轮接收
|
||||
* 入口参数:p_huart - 已由 CubeMX 初始化完成的 USART1 句柄(非空)
|
||||
* 返回值:BSP_OK 已挂上接收,后续每包都会进 sb_link_on_rx_event
|
||||
* BSP_ERR_PARAM 句柄为空
|
||||
* BSP_ERR_STATE 句柄的波特率不是 SB_LINK_BAUD(Src/usart.c 的 USER CODE 覆盖丢了)
|
||||
* BSP_ERR_BUSY 首次挂接收失败(HAL 侧忙,通常是上一轮没收完)
|
||||
* BSP_ERR_IO 首次挂接收失败(HAL 侧状态错)
|
||||
* 限定条件:须在 osKernelStart 之前或任一任务上下文调用;本函数不清任何外设。
|
||||
* 函数说明:波特率**不由本函数设置**——那一步在 Src/usart.c 的 USER CODE 区
|
||||
* (CubeMX 重生成只保护那里)。这里只做一次交叉核对:波特率对不上就报
|
||||
* BSP_ERR_STATE 并打一行 ERR,免得"115200 收 700 kbaud"这种静默错配
|
||||
* 被误判成接线或协议问题。
|
||||
*/
|
||||
bsp_err_t sb_link_init(UART_HandleTypeDef *p_huart);
|
||||
|
||||
/*
|
||||
* 函数功能:重新挂上一轮 IDLE 中断接收
|
||||
* 入口参数:无
|
||||
* 返回值:BSP_OK 挂上了
|
||||
* BSP_ERR_STATE 尚未 sb_link_init
|
||||
* BSP_ERR_BUSY HAL 侧忙(上一轮接收未结束)
|
||||
* BSP_ERR_IO HAL 侧状态错
|
||||
* 限定条件:可在中断上下文调用(rx / error 两个回调都靠它续命);不可阻塞。
|
||||
* 函数说明:HAL 的 ReceiveToIdle_IT 是**一次性**的:一次 IDLE 事件处理完就把
|
||||
* RxState 打回 READY 并关掉 RXNEIE/IDLEIE。所以每个回调末尾都必须重挂,
|
||||
* 漏一次链路就永久静默(表象与"屏板没上电"一模一样)。
|
||||
*/
|
||||
bsp_err_t sb_link_receive_start(void);
|
||||
|
||||
/*
|
||||
* 函数功能:IDLE 事件入口(由 HAL_UARTEx_RxEventCallback 转发进来)
|
||||
* 入口参数:p_huart - 触发事件的句柄(非本链路的会被直接忽略)
|
||||
* size - 本次断出来的字节数
|
||||
* 返回值:无
|
||||
* 限定条件:**中断上下文**,只做 CRC 与计数(4 B 校验约 1 µs),不得打日志、不得阻塞。
|
||||
* 函数说明:长度不是 6 就直接记 rx_len_err 并丢弃;是 6 才解析。解析结论只累计计数
|
||||
* 与"最近一帧",**不做任何业务动作**——注键、刷新都由任务侧取走统计后再做
|
||||
* (批 2 的 2-3:注键不能在中断里做)。末尾无条件重挂接收。
|
||||
*/
|
||||
void sb_link_on_rx_event(UART_HandleTypeDef *p_huart, uint16_t size);
|
||||
|
||||
/*
|
||||
* 函数功能:错误事件入口(由 HAL_UART_ErrorCallback 转发进来)
|
||||
* 入口参数:p_huart - 触发事件的句柄(非本链路的会被直接忽略)
|
||||
* 返回值:无
|
||||
* 限定条件:**中断上下文**;不得打日志、不得阻塞。
|
||||
* 函数说明:先读 SR 再读 DR 清掉 ORE/NE/FE/PE(RM0090 §30.6.1 的清除序列),再重挂接收。
|
||||
* ⚠ 这一步不是可选的:HAL 在阻塞型错误里已经把 RxState 打回 READY 并关了
|
||||
* 接收中断,**而它自己不清 ORE**。ORE 一旦锁存,接收就再也进不来 →
|
||||
* 一次噪声就把链路永久打死,而现象与"屏板没上电"完全一样。
|
||||
*/
|
||||
void sb_link_on_error(UART_HandleTypeDef *p_huart);
|
||||
|
||||
/*
|
||||
* 函数功能:链路统计上报(任务侧周期调用)
|
||||
* 入口参数:now_ms - 当前毫秒时基(调用方给,便于主机单测;MCU 侧传 HAL_GetTick())
|
||||
* 返回值:无
|
||||
* 限定条件:任务上下文调用,**不可在中断里调**(内部会打日志)。
|
||||
* 函数说明:每 SB_LINK_REPORT_MS 检查一次,只有计数有变化才打一行累计值。
|
||||
* 第一帧成功收到时立刻打一行(不等周期),台架刚上电就能看到链路通了。
|
||||
* 离线判定(连续 >=10 s 无召唤帧)属批 2 的 2-5,这里只上报不判定。
|
||||
*/
|
||||
void sb_link_poll(uint32_t now_ms);
|
||||
|
||||
/*
|
||||
* 函数功能:取一份链路统计快照
|
||||
* 入口参数:p_out - 输出缓冲(非空)
|
||||
* 返回值:无(p_out 为空时直接返回,不改任何东西)
|
||||
* 限定条件:任一上下文可调;中断侧在自增,快照不保证同一时刻一致(只用于观测与计数)。
|
||||
* 函数说明:逐字段拷出,避免把 volatile 的内部结构整体赋给调用方。
|
||||
*/
|
||||
void sb_link_get_stats(sb_link_stats_t *p_out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SB_LINK_H */
|
||||
Reference in New Issue
Block a user