Files
STM32F4-Base/test/host/sb_link/sb_link_test.c
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

483 lines
20 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.
/*
* 模块名称:屏板链路传输层(sb_link)主机侧单元测试
* 模块功能:在 PC 上对 Drivers/BSP/ScreenLink/sb_link.c 做离线验证。
*
* 被测对象:真实源文件(以 #include 原样纳入,保证测的是生产代码而非副本):
* ../../../Drivers/BSP/ScreenLink/sb_proto.c (sb_link 依赖它的帧 A 解析)
* ../../../Drivers/BSP/ScreenLink/sb_link.c
* 依赖面用桩替换:main.h(假 USART 寄存器 + 假 ReceiveToIdle_IT)、dbg_log.h(日志行缓冲)。
*
* 为什么要主机侧单测(而不是只用台架):
* 1. **ORE 清除序列**在台架上只能靠"拔插 TX 线"制造,且失败现象是"链路永久静默"——
* 与屏板没上电、线接反完全同形。主机侧能把"必须按 SR→DR 顺序读"这条钉成断言:
* 桩按 RM0090 的语义实现(先读 SR 再读 DR 才清得掉 ORE),顺序反了清不掉 →
* 下一次挂接收必然失败 → 用例立刻红。
* 2. **重挂接收漏一次 = 链路永久静默**:台架上要等 280 ms 才看得出来,且没有报错。
* 主机侧数 `sb_arm_calls` 即可。
* 3. 坏帧分支(长度不对 / 地址不对 / 功能码不对 / CRC 错)在台架上要造噪声才出现,
* 一出现就被复位冲掉;主机侧是几行断言。
*
* 覆盖点(对应 docs/任务规划/屏板链路.md §3 批 1 的 1-3 判据):
* TC-SBLINK-001 init 参数守卫:空句柄 / 波特率不是 SB_LINK_BAUD(后者必须不挂接收)
* TC-SBLINK-002 init 正常:挂接收一次、缓冲 = SB_LINK_RX_BUF_SIZE(16 B,不是 4208)
* TC-SBLINK-003 未初始化就重挂接收:BSP_ERR_STATE,且不碰外设
* TC-SBLINK-004 非本链路的句柄:rx / error 两个入口都必须直接忽略
* TC-SBLINK-005 周期召唤帧(屏板实测向量 01 03 01 00 F0 48):计数、cmd、key、重挂
* TC-SBLINK-006 按键召唤帧(屏板实测向量 01 03 00 09 31 DE):key = 9
* TC-SBLINK-007 CRC 错帧:只累加 rx_crc_err,不污染 last_cmd / last_key
* TC-SBLINK-008 长度不对:5 B / 16 B(缓冲满)/ 0 B 都记 rx_len_err
* TC-SBLINK-009 地址 / 功能码 / 命令类型不符:记 rx_fmt_err
* TC-SBLINK-010 错误中断(ORE,阻塞型):先读 SR 再读 DR 才清得掉,且必须重挂
* TC-SBLINK-015 错误中断(NE/FE,非阻塞型):接收还在跑,**不得**重挂(否则白记一次失败)
* TC-SBLINK-011 重挂失败:HAL_BUSY / HAL_TIMEOUT / HAL_ERROR 三个映射 + rearm_fail 累计
* TC-SBLINK-012 统计上报:未到周期不出行;有变化才出;无变化不刷屏;首帧立刻单独报一次
* TC-SBLINK-013 统计上报的时基回绕:now_ms 跨 0xFFFFFFFF 仍按差值法工作
* TC-SBLINK-014 统计快照:空指针守卫、字段逐项对得上
*
* 通过:全部 TC PASS(main 返回 0)。
* 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。
*
* 创建日期:2026-10-29
*/
#include <stdint.h>
#include <stdio.h>
#include <string.h>
/* ===================== 断言 ===================== */
static int s_total = 0, s_passed = 0, s_failed = 0;
#define CHECK(cond, name) do { \
s_total++; \
if (cond) { s_passed++; printf(" [PASS] %s\n", name); } \
else { s_failed++; printf(" [FAIL] %s (line %d)\n", name, __LINE__); } \
} while (0)
/* ===================== 被测代码 ===================== */
#include "sb_proto.c"
#include "sb_link.c"
/* ===================== 夹具 ===================== */
/* 本链路的句柄(地址唯一,用来区分"非本链路的句柄") */
static UART_HandleTypeDef s_uart;
/* 另一个句柄:只当参数传进去,验证过滤 */
static UART_HandleTypeDef s_other;
/* 屏板实测向量(出处:屏板固件工程 DTU-LCD 的实机抓包,同 test/host/sb_proto) */
static const uint8_t k_rt[SB_FRAME_A_LEN] = { 0x01U, 0x03U, 0x01U, 0x00U, 0xF0U, 0x48U };
static const uint8_t k_key[SB_FRAME_A_LEN] = { 0x01U, 0x03U, 0x00U, 0x09U, 0x31U, 0xDEU };
/* 复位桩 + 日志,然后以 700000 正常初始化一次;返回 init 的返回码 */
static bsp_err_t setup_link(void)
{
sb_fake_reset();
sb_log_reset();
s_uart.Instance = &g_sb_uart;
s_uart.Init.BaudRate = SB_LINK_BAUD;
s_uart.Init.WordLength = 8U;
s_uart.Init.StopBits = 1U;
s_uart.Init.Parity = 0U;
s_uart.Init.Mode = 3U;
s_uart.Init.OverSampling = 16U;
s_uart.RxState = HAL_UART_STATE_READY;
s_uart.ErrorCode = 0U;
s_other.Instance = &g_sb_uart;
s_other.Init.BaudRate = SB_LINK_BAUD;
return sb_link_init(&s_uart);
}
/* 模拟"HAL 把 n 个字节搬进它拿到的那块缓冲,然后报一次 IDLE 事件" */
static void feed(const uint8_t *p_data, uint16_t len)
{
if ((sb_arm_buf != NULL) && (len > 0U)) {
(void)memcpy(sb_arm_buf, p_data, len);
}
sb_link_on_rx_event(&s_uart, len);
}
/* 按 4 字节负载组一帧(CRC 低字节在前,与 sb_proto 口径一致),用来造反例帧 */
static void make_frame(uint8_t *p_frame, uint8_t addr, uint8_t func, uint8_t cmd, uint8_t key)
{
uint16_t crc;
p_frame[0] = addr;
p_frame[1] = func;
p_frame[2] = cmd;
p_frame[3] = key;
crc = sb_crc16(p_frame, 4U);
p_frame[4] = (uint8_t)(crc & 0xFFU);
p_frame[5] = (uint8_t)((crc >> 8) & 0xFFU);
}
/* ===================== 用例 ===================== */
static void tc_init_guards(void)
{
bsp_err_t ret;
sb_fake_reset();
sb_log_reset();
ret = sb_link_init(NULL);
CHECK(ret == BSP_ERR_PARAM, "TC-SBLINK-001 a NULL handle is rejected");
CHECK(sb_arm_calls == 0U, "TC-SBLINK-001b nothing is armed for a NULL handle");
/* 波特率不是 700000:说明 Src/usart.c 的 USER CODE 覆盖丢了。
* 必须**不挂接收**并打一行 ERR —— 否则就是"115200 收 700 kbaud"的静默错配 */
s_uart.Instance = &g_sb_uart;
s_uart.Init.BaudRate = 115200U;
ret = sb_link_init(&s_uart);
CHECK(ret == BSP_ERR_STATE, "TC-SBLINK-001c a wrong baud rate is rejected (BSP_ERR_STATE)");
CHECK(sb_arm_calls == 0U, "TC-SBLINK-001d nothing is armed when the baud rate is wrong");
CHECK(sb_log_n == 1U, "TC-SBLINK-001e exactly one ERR line is emitted");
CHECK(sb_log_has(0U, "baud=115200") == 1, "TC-SBLINK-001f the ERR line names the actual baud rate");
}
static void tc_init_ok(void)
{
bsp_err_t ret = setup_link();
CHECK(ret == BSP_OK, "TC-SBLINK-002 init with 700000 returns BSP_OK");
CHECK(sb_arm_calls == 1U, "TC-SBLINK-002b reception is armed exactly once");
CHECK(sb_arm_size == SB_LINK_RX_BUF_SIZE, "TC-SBLINK-002c the receive buffer is SB_LINK_RX_BUF_SIZE");
CHECK(sb_arm_size == 16U, "TC-SBLINK-002d ... which is 16 B, not the board's 4208 B frame length");
CHECK(sb_arm_buf != NULL, "TC-SBLINK-002e a receive buffer is handed to HAL");
CHECK(sb_log_n == 0U, "TC-SBLINK-002f a clean init logs nothing");
}
static void tc_not_initialised(void)
{
/* 先把模块打回未初始化态:sb_link_init 之前 s_huart 为 NULL。
* 本 TU 直接可见该静态变量(原样 include 生产源),这里显式还原一次。 */
UART_HandleTypeDef *saved = s_huart;
sb_fake_reset();
sb_log_reset();
s_huart = NULL;
CHECK(sb_link_receive_start() == BSP_ERR_STATE,
"TC-SBLINK-003 re-arming before init returns BSP_ERR_STATE");
CHECK(sb_arm_calls == 0U, "TC-SBLINK-003b ... and never touches the peripheral");
s_huart = saved;
}
static void tc_foreign_handle(void)
{
sb_link_stats_t st;
(void)setup_link();
sb_link_on_rx_event(&s_other, SB_FRAME_A_LEN);
sb_link_on_error(&s_other);
sb_link_get_stats(&st);
CHECK(st.rx_events == 0U, "TC-SBLINK-004 an rx event on another handle is ignored");
CHECK(st.err_irq == 0U, "TC-SBLINK-004b an error event on another handle is ignored");
CHECK(sb_arm_calls == 1U, "TC-SBLINK-004c ... and does not re-arm (no extra arm call)");
CHECK(sb_reg_n == 0U, "TC-SBLINK-004d ... and does not touch the registers");
}
static void tc_rt_frame(void)
{
sb_link_stats_t st;
(void)setup_link();
feed(k_rt, SB_FRAME_A_LEN);
sb_link_get_stats(&st);
CHECK(st.rx_events == 1U, "TC-SBLINK-005 one IDLE event is counted");
CHECK(st.rx_ok == 1U, "TC-SBLINK-005b the board's real RT summon frame parses OK");
CHECK(st.last_size == SB_FRAME_A_LEN, "TC-SBLINK-005c last_size == 6");
CHECK(st.last_cmd == SB_CMD_RT, "TC-SBLINK-005d last_cmd == SB_CMD_RT");
CHECK(st.last_key == 0U, "TC-SBLINK-005e last_key == 0 for a periodic summon");
CHECK(sb_arm_calls == 2U, "TC-SBLINK-005f reception is re-armed after the event");
CHECK(st.rx_crc_err == 0U, "TC-SBLINK-005g no CRC error is counted");
CHECK(st.key_events == 0U, "TC-SBLINK-005h a periodic summon is not a key event");
}
static void tc_key_frame(void)
{
sb_link_stats_t st;
(void)setup_link();
feed(k_key, SB_FRAME_A_LEN);
sb_link_get_stats(&st);
CHECK(st.rx_ok == 1U, "TC-SBLINK-006 the board's real key summon frame parses OK");
CHECK(st.last_cmd == SB_CMD_KEY, "TC-SBLINK-006b last_cmd == SB_CMD_KEY");
CHECK(st.last_key == 9U, "TC-SBLINK-006c last_key == 9 (KEY_RESET on the board)");
CHECK(st.key_events == 1U, "TC-SBLINK-006d the key event counter advances (2-3 consumes this)");
}
static void tc_crc_error(void)
{
uint8_t bad[SB_FRAME_A_LEN];
sb_link_stats_t st;
(void)setup_link();
feed(k_key, SB_FRAME_A_LEN); /* 先来一帧好的,把 last_* 写成 9 */
(void)memcpy(bad, k_rt, SB_FRAME_A_LEN);
bad[SB_FRAME_A_LEN - 1U] ^= 0xFFU; /* 打坏 CRC */
feed(bad, SB_FRAME_A_LEN);
sb_link_get_stats(&st);
CHECK(st.rx_events == 2U, "TC-SBLINK-007 both events are counted");
CHECK(st.rx_ok == 1U, "TC-SBLINK-007b only the good frame counts as OK");
CHECK(st.rx_crc_err == 1U, "TC-SBLINK-007c the corrupted frame is counted as a CRC error");
CHECK(st.last_key == 9U, "TC-SBLINK-007d a bad frame does not overwrite last_key");
CHECK(sb_arm_calls == 3U, "TC-SBLINK-007e reception is re-armed even after a bad frame");
}
static void tc_length_error(void)
{
uint8_t five[5] = { 0x01U, 0x03U, 0x01U, 0x00U, 0xF0U };
uint8_t full[SB_LINK_RX_BUF_SIZE];
sb_link_stats_t st;
(void)setup_link();
feed(five, 5U); /* 少一个字节 */
(void)memset(full, 0x00, sizeof(full));
feed(full, SB_LINK_RX_BUF_SIZE); /* 缓冲被塞满(噪声冲击的典型形态) */
sb_link_on_rx_event(&s_uart, 0U); /* 空事件(防御性) */
sb_link_get_stats(&st);
CHECK(st.rx_events == 3U, "TC-SBLINK-008 every event is counted");
CHECK(st.rx_len_err == 3U, "TC-SBLINK-008b 5 B / 16 B / 0 B all count as length errors");
CHECK(st.rx_ok == 0U, "TC-SBLINK-008c none of them is treated as a valid frame");
CHECK(st.rx_crc_err == 0U, "TC-SBLINK-008d a wrong length is not reported as a CRC error");
CHECK(sb_arm_calls == 4U, "TC-SBLINK-008e reception is re-armed every time");
}
static void tc_format_error(void)
{
uint8_t frame[SB_FRAME_A_LEN];
sb_link_stats_t st;
(void)setup_link();
make_frame(frame, 0x02U, SB_FUNC_READ_HOLDING, SB_CMD_RT, 0U); /* 地址不对 */
feed(frame, SB_FRAME_A_LEN);
make_frame(frame, SB_ADDR, 0x04U, SB_CMD_RT, 0U); /* 功能码不对 */
feed(frame, SB_FRAME_A_LEN);
make_frame(frame, SB_ADDR, SB_FUNC_READ_HOLDING, 0x02U, 0U); /* 命令类型不对 */
feed(frame, SB_FRAME_A_LEN);
sb_link_get_stats(&st);
CHECK(st.rx_fmt_err == 3U, "TC-SBLINK-009 wrong address / function / command all count as format errors");
CHECK(st.rx_crc_err == 0U, "TC-SBLINK-009b ... and none of them is misreported as a CRC error");
CHECK(st.rx_ok == 0U, "TC-SBLINK-009c none of them is accepted");
}
static void tc_error_clears_ore(void)
{
sb_link_stats_t st;
(void)setup_link();
/* 造一次 ORE。ORE 是**阻塞型**错误:HAL 先把接收停掉(RxState 打回 READY)再回调,
* 所以桩也先把 RxState 打回 READY —— 否则测的就不是 ORE 那条路 */
sb_ore_latched = 1U;
s_uart.RxState = HAL_UART_STATE_READY;
s_uart.ErrorCode = HAL_UART_ERROR_ORE;
sb_link_on_error(&s_uart);
CHECK(sb_fake_trace_is("SD") == 1, "TC-SBLINK-010 the error path reads SR then DR (RM0090 clear sequence)");
CHECK(sb_ore_latched == 0U, "TC-SBLINK-010b ORE is actually cleared");
CHECK(sb_arm_calls == 2U, "TC-SBLINK-010c a blocking error (ORE) re-arms reception");
CHECK(sb_arm_force == SB_FAKE_ARM_OK, "TC-SBLINK-010d (sanity) no forced failure is in play");
sb_link_get_stats(&st);
CHECK(st.err_irq == 1U, "TC-SBLINK-010e the error interrupt is counted");
CHECK(st.err_bits == HAL_UART_ERROR_ORE, "TC-SBLINK-010f the error bits are recorded");
CHECK(st.rearm_fail == 0U, "TC-SBLINK-010g re-arming succeeded, so rearm_fail stays 0");
/* 反例:如果哪天有人把清除序列写成 DR→SR,ORE 就清不掉,下一次挂接收必失败。
* 这里直接模拟"ORE 没清掉"的后果,把判据钉住 */
sb_ore_latched = 1U;
sb_sr_seen = 0U;
CHECK(sb_link_receive_start() == BSP_ERR_IO,
"TC-SBLINK-010h an uncleared ORE makes the next arm fail (this is why the clear matters)");
}
/*
* 非阻塞型错误(NE/FE/PE):HAL 不动接收(RxState 仍是 BUSY_RX),**不该**去重挂。
* 2026-10-29 台架实测踩的就是这条:每帧都伴随 NE/FE,旧写法每次都想重挂、每次拿到
* HAL_BUSY,于是 rearm_fail 跟着 err_irq 一起涨(160/160),看着像链路坏了。
*/
static void tc_error_nonblocking(void)
{
sb_link_stats_t st;
(void)setup_link();
s_uart.RxState = HAL_UART_STATE_BUSY_RX; /* 接收还在跑 */
s_uart.ErrorCode = HAL_UART_ERROR_NE;
sb_link_on_error(&s_uart);
CHECK(sb_arm_calls == 1U, "TC-SBLINK-015 a non-blocking error (NE) does not re-arm reception");
CHECK(sb_fake_trace_is("SD") == 1, "TC-SBLINK-015b ... but still clears the flags (SR then DR)");
sb_link_get_stats(&st);
CHECK(st.err_irq == 1U, "TC-SBLINK-015c the error interrupt is counted");
CHECK(st.err_bits == HAL_UART_ERROR_NE, "TC-SBLINK-015d the NE bit is recorded");
CHECK(st.rearm_fail == 0U, "TC-SBLINK-015e ... and no phantom 're-arm failure' is booked");
/* 再来一次 FE:错误位按并集累计 */
s_uart.ErrorCode = HAL_UART_ERROR_FE;
sb_link_on_error(&s_uart);
sb_link_get_stats(&st);
CHECK(st.err_bits == (HAL_UART_ERROR_NE | HAL_UART_ERROR_FE),
"TC-SBLINK-015f error bits accumulate as a union");
}
static void tc_rearm_failure(void)
{
sb_link_stats_t st;
(void)setup_link();
sb_arm_force = SB_FAKE_ARM_BUSY;
CHECK(sb_link_receive_start() == BSP_ERR_BUSY, "TC-SBLINK-011 HAL_BUSY maps to BSP_ERR_BUSY");
sb_arm_force = SB_FAKE_ARM_TIMEOUT;
CHECK(sb_link_receive_start() == BSP_ERR_TIMEOUT, "TC-SBLINK-011b HAL_TIMEOUT maps to BSP_ERR_TIMEOUT");
sb_arm_force = SB_FAKE_ARM_ERROR;
CHECK(sb_link_receive_start() == BSP_ERR_IO, "TC-SBLINK-011c HAL_ERROR maps to BSP_ERR_IO");
sb_link_get_stats(&st);
CHECK(st.rearm_fail == 3U, "TC-SBLINK-011d every failed arm is counted in rearm_fail");
/* 中断里也要如实累计(不是只有任务侧那条路) */
sb_arm_force = SB_FAKE_ARM_BUSY;
sb_link_on_rx_event(&s_uart, SB_FRAME_A_LEN);
sb_link_get_stats(&st);
CHECK(st.rearm_fail == 4U, "TC-SBLINK-011e a failed re-arm inside the rx callback is counted too");
}
static void tc_poll_reporting(void)
{
sb_link_stats_t st;
(void)setup_link();
/* 未到周期:一行都不出(哪怕计数已经变了) */
feed(k_rt, SB_FRAME_A_LEN);
sb_link_poll(0U);
CHECK(sb_log_n == 1U, "TC-SBLINK-012 the first valid frame is reported immediately");
CHECK(sb_log_has(0U, "first frame") == 1, "TC-SBLINK-012b ... by a dedicated 'first frame' line");
CHECK(sb_log_has(0U, "size=6") == 1, "TC-SBLINK-012c ... which carries size=6 (the 1-3 criterion)");
/* 到周期且计数有变化:出一行累计值 */
feed(k_rt, SB_FRAME_A_LEN);
sb_link_poll(SB_LINK_REPORT_MS);
CHECK(sb_log_n == 2U, "TC-SBLINK-012d a periodic report is emitted once the period elapses");
CHECK(sb_log_has(1U, "rx=2") == 1, "TC-SBLINK-012e the report carries the cumulative event count");
CHECK(sb_log_has(1U, "ok=2") == 1, "TC-SBLINK-012f ... and the OK count");
/* 到周期但计数没变:不出行(链路安静时不刷屏) */
sb_link_poll(2U * SB_LINK_REPORT_MS);
CHECK(sb_log_n == 2U, "TC-SBLINK-012g nothing is emitted when no counter changed");
/* 有错帧:再出一行,且 err/crc 列可见 */
{
uint8_t bad[SB_FRAME_A_LEN];
(void)memcpy(bad, k_rt, SB_FRAME_A_LEN);
bad[5] ^= 0xFFU;
feed(bad, SB_FRAME_A_LEN);
}
sb_link_poll(3U * SB_LINK_REPORT_MS);
CHECK(sb_log_n == 3U, "TC-SBLINK-012h a CRC error triggers a new report");
CHECK(sb_log_has(2U, "crc=1") == 1, "TC-SBLINK-012i the report carries the CRC error count");
sb_link_get_stats(&st);
CHECK(st.rx_events == 3U, "TC-SBLINK-012j (sanity) three events were fed in total");
}
static void tc_poll_wraparound(void)
{
uint32_t base = 0xFFFFFFFFU - (SB_LINK_REPORT_MS / 2U);
(void)setup_link();
/* 基准:距上次上报(0)已经远超一个周期 → 首帧行 + 周期行各一行 */
feed(k_rt, SB_FRAME_A_LEN);
sb_link_poll(base);
CHECK(sb_log_n == 2U, "TC-SBLINK-013 the baseline poll emits both the first-frame line and a report");
feed(k_rt, SB_FRAME_A_LEN);
sb_link_poll(base + (SB_LINK_REPORT_MS / 2U)); /* 还没到周期 */
CHECK(sb_log_n == 2U, "TC-SBLINK-013b the period is not up yet");
/* 跨过 0xFFFFFFFF:差值法(now - last)必须仍然成立,且真的绕回了 */
CHECK((base + SB_LINK_REPORT_MS + 1U) < base, "TC-SBLINK-013c (sanity) this poll really wraps around");
sb_link_poll(base + SB_LINK_REPORT_MS + 1U);
CHECK(sb_log_n == 3U, "TC-SBLINK-013d the report still fires across the tick wrap-around");
}
static void tc_stats_snapshot(void)
{
sb_link_stats_t st;
(void)setup_link();
feed(k_key, SB_FRAME_A_LEN);
(void)memset(&st, 0, sizeof(st));
sb_link_get_stats(NULL); /* 空指针守卫:不得崩 */
sb_link_get_stats(&st);
CHECK(st.rx_events == 1U, "TC-SBLINK-014 the snapshot carries rx_events");
CHECK(st.last_size == SB_FRAME_A_LEN, "TC-SBLINK-014b ... last_size");
CHECK(st.last_cmd == SB_CMD_KEY, "TC-SBLINK-014c ... last_cmd");
CHECK(st.last_key == 9U, "TC-SBLINK-014d ... last_key");
CHECK(st.rearm_fail == 0U, "TC-SBLINK-014e ... rearm_fail");
}
int main(void)
{
printf("=== ScreenLink Link Tests ===\n");
printf("-- init: guards --\n");
tc_init_guards();
printf("-- init: normal --\n");
tc_init_ok();
printf("-- re-arm before init --\n");
tc_not_initialised();
printf("-- callback dispatch: foreign handle --\n");
tc_foreign_handle();
printf("-- rx event: periodic summon --\n");
tc_rt_frame();
printf("-- rx event: key summon --\n");
tc_key_frame();
printf("-- rx event: bad CRC --\n");
tc_crc_error();
printf("-- rx event: wrong length --\n");
tc_length_error();
printf("-- rx event: wrong address / function / command --\n");
tc_format_error();
printf("-- error event: ORE clearing --\n");
tc_error_clears_ore();
printf("-- error event: non-blocking (NE/FE) must not re-arm --\n");
tc_error_nonblocking();
printf("-- re-arm failure mapping --\n");
tc_rearm_failure();
printf("-- periodic reporting --\n");
tc_poll_reporting();
printf("-- periodic reporting: tick wrap-around --\n");
tc_poll_wraparound();
printf("-- stats snapshot --\n");
tc_stats_snapshot();
printf("\n=== ScreenLink Link Tests: %d/%d PASSED (failed=%d) ===\n",
s_passed, s_total, s_failed);
return (s_failed == 0) ? 0 : 1;
}