feat(屏板): 帧 B 组帧 + 中断起发 TX DMA + 按键队列 + 离线判定(批 2)

批 2 五项全部落地(docs/任务规划/屏板链路.md §3):
- 2-1 sb_link_frame_update():uiTask 渲染后按需组帧,不留 3200 B 影子
  副本,直接拿活显存与帧内点阵段 memcmp;组完自校验,过了才置 ready。
  静态 4208 B 帧缓冲实测落 RW_IRAM1(0x20006d98),不在 CCM。
- 2-2 空闲中断校完 6 B 召唤帧直接点 TX DMA(sb_tx_kick),不排队不攒批;
  busy 期间再来的召唤丢帧并计数,绝不叠发(屏板收帧一次 IDLE 一包)。
- 2-3 按键走环形队列(中断入、任务出),uiTask 按 §4.3 表映射 UI_KEY_*
  后调 ui_port_queue_key;9/10(RESET/FACTORY)按决策四先丢弃并留痕。
- 2-4 HAL_UART_TxCpltCallback 在 rs485.c 加第三行转发(三个全局回调
  落点收一处,防日后重复定义);busy 冻结帧缓冲,错误中断按 gState
  收拾被打断的 TX,防残留 busy 把链路永久锁死。
- 2-5 连续 >=10 s 无有效召唤帧才判离线(不按 500 ms——屏板约 5000 s
  自复位有 1 s 空窗),恢复即清标志;CRC 错帧静默丢弃并计数。

实测:台架 40 s(PC 顶替屏板,--key-fixed 2 持续改画面)rx=143 ok=143
keys=143 crc=0 / tx=143 done=143 drop=0 abor=0 fb=144 bad=0;脚本一停
10 s 准点出 OFFLINE。组帧耗时 509 µs 典型 / 709 µs 峰值——原 0.3 ms
判据系估算,已按实测在规划文档改口为 1 ms 并给出压降路径(未做)。

附带:rs485_on_rx 补 CODE-RULE-09 文档块(本轮改 rs485.c 触发整文件
重扫被拦);CRC 循环计数改 32 位(消掉 ARMCC 每轮 UXTH);bench 脚本
加 --reply/--key-fixed/--dump,并写明 CH340 收不了 700 kbaud 连续流、
--reply 收不全 4208 B 不是固件问题。

主机单测 146/146 PASSED(+65);Keil 0 错误 0 警告;ci_local 全绿
(SKIP=nested-comment,worktree 限制,主树脚本另查 0 新发)。
Code=238,936 B / RW_IRAM1 65.5 % / RW_CCM 87.9 %。
This commit is contained in:
2026-10-29 20:59:36 +08:00
parent e5b4182a9f
commit dab0d8ba17
15 changed files with 1263 additions and 132 deletions
+390 -2
View File
@@ -34,6 +34,20 @@
* TC-SBLINK-013 统计上报的时基回绕:now_ms 跨 0xFFFFFFFF 仍按差值法工作
* TC-SBLINK-014 统计快照:空指针守卫、字段逐项对得上
*
* 覆盖点(对应 docs/任务规划/屏板链路.md §3 批 2 的五项):
* TC-SBLINK-016 组帧入口守卫:未初始化 / 空显存指针
* TC-SBLINK-017 帧 B 内容:点阵逐字节、info[0..3] 小端掩码、info[4..7] 非 LOGO、
* 998 B 填充全 0、CRC 覆盖 [0, 4205](2-1 判据)
* TC-SBLINK-018 变化检测:内容不变不重组;点阵改一字节要重组;只改掩码也要重组
* TC-SBLINK-019 中断起发:未组帧不发;一召唤一帧 4208 B;TX 期间不叠发(2-2 判据)
* TC-SBLINK-020 TX 占用期间帧缓冲逐字节冻结,TxCplt 后补组(2-4 的"无脏帧")
* TC-SBLINK-021 起发失败要清 busy,否则链路此后永不发送
* TC-SBLINK-022 错误中断里清残留 busy;但 TX 真的还在发时不得判死(2-4 的"无残留 busy")
* TC-SBLINK-023 按键队列:单次交付、周期帧不带键、溢出丢新并计数、FIFO 顺序(2-3)
* TC-SBLINK-024 离线判定:9.999 s 不判、10 s 判、不重复刷屏、一帧即恢复(2-5 判据)
* TC-SBLINK-025 组帧耗时换算:周期数 → µs 的除数(SystemCoreClock/1e6)
* TC-SBLINK-026 上报行:fb/tx 列可见;离线行排在周期行之前且不重复
*
* 通过:全部 TC PASS(main 返回 0)。
* 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。
*
@@ -83,6 +97,7 @@ static bsp_err_t setup_link(void)
s_uart.Init.Mode = 3U;
s_uart.Init.OverSampling = 16U;
s_uart.RxState = HAL_UART_STATE_READY;
s_uart.gState = HAL_UART_STATE_READY;
s_uart.ErrorCode = 0U;
s_other.Instance = &g_sb_uart;
@@ -114,6 +129,30 @@ static void make_frame(uint8_t *p_frame, uint8_t addr, uint8_t func, uint8_t cmd
p_frame[5] = (uint8_t)((crc >> 8) & 0xFFU);
}
/* 假显存:组帧的输入(内容按 i 变化,方便"改一个字节"的用例) */
static uint8_t s_bmp[SB_FRAME_B_BMP_LEN];
static void fill_bmp(uint8_t seed)
{
uint32_t i;
for (i = 0U; i < SB_FRAME_B_BMP_LEN; i++) {
s_bmp[i] = (uint8_t)(seed + (uint8_t)(i & 0xFFU));
}
}
/*
* 模拟"HAL 的 DMA 传完 → gState 打回 READY → 调 TxCplt 回调"。
* 真机这两步在 HAL_DMA_IRQHandler 里连着做(UART_DMATransmitCplt 里先改 gState
* 再回调),所以测试也必须按这个顺序——否则"错误中断里靠 gState 判残留 busy"
* 那条用例的前提就不成立了。
*/
static void tx_complete(void)
{
s_uart.gState = HAL_UART_STATE_READY;
sb_link_on_tx_cplt(&s_uart);
}
/* ===================== 用例 ===================== */
static void tc_init_guards(void)
@@ -383,20 +422,24 @@ static void tc_poll_reporting(void)
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 列可见 */
/* 有错帧:再出一行,且 crc 列可见。
* 同时喂一帧正常召唤:本用例的时基是每步 +5 s,单靠错帧会让"连续 10 s 无**有效**
* 召唤帧"在第三步就成立(那是 2-5 的离线判定,由 TC-SBLINK-027 专测),
* 那行 OFFLINE 会插进来把行号错开,让本用例悄悄变成在测别的东西。 */
{
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);
feed(k_rt, 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");
CHECK(st.rx_events == 4U, "TC-SBLINK-012j (sanity) four events were fed in total");
}
static void tc_poll_wraparound(void)
@@ -438,6 +481,328 @@ static void tc_stats_snapshot(void)
CHECK(st.rearm_fail == 0U, "TC-SBLINK-014e ... rearm_fail");
}
/* ===================== 批 2 用例(帧 B 组帧 / 起发 / 收尾 / 按键 / 离线) ===================== */
static void tc_frame_update_guards(void)
{
fill_bmp(0x11U);
/* 未初始化:STATE。直接清 s_huart(本用例白盒:测试 TU 里 include 了 sb_link.c,
* 静态量可见)——否则本用例能否通过就取决于"有没有人先跑过 setup_link",
* 那是用例顺序依赖,加一条用例就可能把它弄红。 */
s_huart = NULL;
sb_fake_reset();
sb_log_reset();
CHECK(sb_link_frame_update(s_bmp, 0U) == BSP_ERR_STATE,
"TC-SBLINK-016 a frame update before init is refused");
(void)setup_link();
CHECK(sb_link_frame_update(NULL, 0U) == BSP_ERR_PARAM,
"TC-SBLINK-016b a NULL framebuffer is refused");
}
static void tc_frame_build_content(void)
{
uint32_t i;
uint32_t pad_bad = 0U;
sb_link_stats_t st;
(void)setup_link();
fill_bmp(0x11U);
CHECK(sb_link_frame_update(s_bmp, 0xA5A55A5AU) == BSP_OK,
"TC-SBLINK-017 the first frame is built");
CHECK(memcmp(s_frame, s_bmp, SB_FRAME_B_BMP_LEN) == 0,
"TC-SBLINK-017b ... the bitmap region is a verbatim copy of the live framebuffer");
/* 0xA5A55A5A 小端 = 5A 5A A5 A5 */
CHECK((s_frame[SB_FRAME_B_INFO_OFF + 0U] == 0x5AU) &&
(s_frame[SB_FRAME_B_INFO_OFF + 1U] == 0x5AU) &&
(s_frame[SB_FRAME_B_INFO_OFF + 2U] == 0xA5U) &&
(s_frame[SB_FRAME_B_INFO_OFF + 3U] == 0xA5U),
"TC-SBLINK-017c ... the LED mask lands in info[0..3] little-endian");
CHECK((s_frame[SB_FRAME_B_INFO_OFF + 4U] == 0U) &&
(s_frame[SB_FRAME_B_INFO_OFF + 5U] == 0U),
"TC-SBLINK-017d ... info[4..5] is not the LOGO magic");
CHECK((s_frame[SB_FRAME_B_INFO_OFF + 6U] == 0U) &&
(s_frame[SB_FRAME_B_INFO_OFF + 7U] == 0U),
"TC-SBLINK-017e ... info[6..7] reserved is zero");
for (i = SB_FRAME_B_PAD_OFF; i < SB_FRAME_B_CRC_OFF; i++) {
if (s_frame[i] != 0U) {
pad_bad++;
}
}
CHECK(pad_bad == 0U, "TC-SBLINK-017f ... all 998 padding bytes are zero");
CHECK(sb_frame_b_verify(s_frame, (uint16_t)SB_LINK_FRAME_LEN) == BSP_OK,
"TC-SBLINK-017g ... and the CRC covers [0, 4205]");
sb_link_get_stats(&st);
CHECK(st.frame_build == 1U, "TC-SBLINK-017h ... counted exactly once");
CHECK(st.frame_bad == 0U, "TC-SBLINK-017i ... self-check clean");
}
static void tc_frame_unchanged(void)
{
sb_link_stats_t st;
(void)setup_link();
fill_bmp(0x11U);
(void)sb_link_frame_update(s_bmp, 0U);
/* 同样的内容再来一次:不重组。uiTask 每 10 ms 调一次,这条决定了常态成本只有 memcmp */
CHECK(sb_link_frame_update(s_bmp, 0U) == BSP_OK,
"TC-SBLINK-018 an unchanged framebuffer returns OK");
sb_link_get_stats(&st);
CHECK(st.frame_build == 1U, "TC-SBLINK-018b ... without rebuilding the frame");
/* 点阵改一个字节:必须重组 */
s_bmp[1234] ^= 0xFFU;
CHECK(sb_link_frame_update(s_bmp, 0U) == BSP_OK,
"TC-SBLINK-018c a changed byte is picked up");
sb_link_get_stats(&st);
CHECK(st.frame_build == 2U, "TC-SBLINK-018d ... and triggers a rebuild");
/* 点阵没变、只有掩码变:也要重组(info[0..3] 在 CRC 覆盖区内) */
CHECK(sb_link_frame_update(s_bmp, 0x00000001U) == BSP_OK,
"TC-SBLINK-018e a changed LED mask is picked up");
sb_link_get_stats(&st);
CHECK(st.frame_build == 3U, "TC-SBLINK-018f ... and triggers a rebuild too");
CHECK(s_frame[SB_FRAME_B_INFO_OFF] == 0x01U,
"TC-SBLINK-018g ... with the new mask in info[0]");
}
static void tc_tx_kick(void)
{
sb_link_stats_t st;
(void)setup_link();
fill_bmp(0x22U);
/* 还没组帧:召唤来了也不发(发一帧全 0 的图等于让屏板白屏,而且它不报错) */
feed(k_rt, SB_FRAME_A_LEN);
sb_link_get_stats(&st);
CHECK(st.tx_kick == 0U, "TC-SBLINK-019 no frame is sent before the first build");
CHECK(st.tx_busy_drop == 1U, "TC-SBLINK-019b ... the summon is counted as dropped instead");
(void)sb_link_frame_update(s_bmp, 0U);
feed(k_rt, SB_FRAME_A_LEN);
sb_link_get_stats(&st);
CHECK(st.tx_kick == 1U, "TC-SBLINK-019c a valid summon kicks the transfer");
CHECK(sb_tx_calls == 1U, "TC-SBLINK-019d ... through exactly one HAL call");
CHECK(sb_tx_len == SB_LINK_FRAME_LEN, "TC-SBLINK-019e ... of 4208 bytes");
CHECK(sb_tx_buf == s_frame,
"TC-SBLINK-019f ... from the static frame buffer (the DMA-reachable one)");
/* 屏板会一直召唤:上一帧没发完就再来一次 → 绝不叠发 */
feed(k_rt, SB_FRAME_A_LEN);
sb_link_get_stats(&st);
CHECK(sb_tx_calls == 1U,
"TC-SBLINK-019g a second summon during TX does not stack another transfer");
CHECK(st.tx_busy_drop == 2U, "TC-SBLINK-019h ... it is counted as dropped");
/* TxCplt 之后又能发了 */
tx_complete();
sb_link_get_stats(&st);
CHECK(st.tx_done == 1U, "TC-SBLINK-019i TxCplt is counted");
feed(k_rt, SB_FRAME_A_LEN);
CHECK(sb_tx_calls == 2U, "TC-SBLINK-019j ... and the next summon goes out again");
}
static void tc_tx_busy_freezes_frame(void)
{
uint8_t before[SB_FRAME_B_BMP_LEN];
sb_link_stats_t st;
(void)setup_link();
fill_bmp(0x33U);
(void)sb_link_frame_update(s_bmp, 0U);
feed(k_rt, SB_FRAME_A_LEN); /* 起发 → busy */
(void)memcpy(before, s_frame, sizeof(before));
fill_bmp(0x77U); /* 画面变了,但正在发 */
CHECK(sb_link_frame_update(s_bmp, 0U) == BSP_ERR_BUSY,
"TC-SBLINK-020 a frame update during TX is refused");
CHECK(memcmp(s_frame, before, sizeof(before)) == 0,
"TC-SBLINK-020b ... the buffer DMA is reading is left byte-for-byte untouched");
tx_complete();
CHECK(sb_link_frame_update(s_bmp, 0U) == BSP_OK,
"TC-SBLINK-020c ... and the pending change is picked up right after TxCplt");
CHECK(memcmp(s_frame, s_bmp, sizeof(before)) == 0,
"TC-SBLINK-020d ... so no stale frame survives the busy window");
sb_link_get_stats(&st);
CHECK(st.frame_build == 2U, "TC-SBLINK-020e ... exactly one rebuild, no double build");
}
static void tc_tx_kick_failure(void)
{
sb_link_stats_t st;
(void)setup_link();
fill_bmp(0x44U);
(void)sb_link_frame_update(s_bmp, 0U);
sb_tx_force = SB_FAKE_TX_BUSY;
feed(k_rt, SB_FRAME_A_LEN);
sb_link_get_stats(&st);
CHECK(st.tx_kick_fail == 1U, "TC-SBLINK-021 a failed HAL kick is counted");
CHECK(st.tx_kick == 0U, "TC-SBLINK-021b ... and is not counted as a transfer");
/* 失败必须把 busy 清掉,否则此后每次召唤都被判"上一帧还在发",链路再也不发 */
sb_tx_force = SB_FAKE_TX_OK;
feed(k_rt, SB_FRAME_A_LEN);
sb_link_get_stats(&st);
CHECK(st.tx_kick == 1U, "TC-SBLINK-021c ... and the next summon retries successfully");
}
static void tc_tx_abort_on_error(void)
{
sb_link_stats_t st;
(void)setup_link();
fill_bmp(0x55U);
(void)sb_link_frame_update(s_bmp, 0U);
feed(k_rt, SB_FRAME_A_LEN); /* 起发,gState = BUSY_TX */
/* 真机:错误中断打断 TX 时 HAL 把 gState 打回 READY,但**不会**再调 TxCplt →
* busy 会永久挂着。这正是"拔掉屏板后无残留 busy"要防的那条。 */
sb_ore_latched = 1U;
s_uart.RxState = HAL_UART_STATE_READY;
s_uart.gState = HAL_UART_STATE_READY;
s_uart.ErrorCode = HAL_UART_ERROR_ORE;
sb_link_on_error(&s_uart);
sb_link_get_stats(&st);
CHECK(st.tx_abort == 1U, "TC-SBLINK-022 a stuck TX busy is cleared by the error callback");
s_uart.gState = HAL_UART_STATE_READY;
feed(k_rt, SB_FRAME_A_LEN);
CHECK(sb_tx_calls == 2U, "TC-SBLINK-022b ... so the link keeps transmitting afterwards");
/* 反例:TX 真的还在发(gState 仍是 BUSY_TX)时不得判死 */
sb_ore_latched = 0U;
s_uart.RxState = HAL_UART_STATE_BUSY_RX; /* 非阻塞型错误:接收照跑 */
s_uart.ErrorCode = HAL_UART_ERROR_NE;
sb_link_on_error(&s_uart); /* gState 仍 BUSY_TX */
sb_link_get_stats(&st);
CHECK(st.tx_abort == 1U, "TC-SBLINK-022c ... but a still-running TX is not declared dead");
CHECK(sb_tx_calls == 2U, "TC-SBLINK-022d ... and no extra transfer was started");
}
static void tc_key_queue(void)
{
uint8_t i;
sb_link_stats_t st;
(void)setup_link();
CHECK(sb_link_key_take() == 0U, "TC-SBLINK-023 an empty queue yields 0");
feed(k_key, SB_FRAME_A_LEN); /* 屏板实测向量:key = 9 */
CHECK(sb_link_key_take() == 9U, "TC-SBLINK-023b the key byte reaches the consumer");
CHECK(sb_link_key_take() == 0U, "TC-SBLINK-023c ... exactly once");
/* 周期召唤的 key 恒 0:不占队列(屏板自己的语义就是"没有按键事件") */
feed(k_rt, SB_FRAME_A_LEN);
CHECK(sb_link_key_take() == 0U, "TC-SBLINK-023d a periodic summon never queues a key");
/* 队列满:丢**新**的并计数(旧键已经在路上,新键还会有下一次召唤)。
* 环形队列留一格判满 → 容量 = SB_LINK_KEY_QUEUE_LEN - 1 = 3,推 6 次丢 3 次。 */
for (i = 0U; i < (uint8_t)(SB_LINK_KEY_QUEUE_LEN + 2U); i++) {
feed(k_key, SB_FRAME_A_LEN);
}
sb_link_get_stats(&st);
CHECK(st.key_drop == 3U, "TC-SBLINK-023e overflow drops the newest and counts it");
for (i = 0U; i < (uint8_t)(SB_LINK_KEY_QUEUE_LEN - 1U); i++) {
CHECK(sb_link_key_take() == 9U, "TC-SBLINK-023f ... FIFO order is preserved");
}
CHECK(sb_link_key_take() == 0U, "TC-SBLINK-023g ... and the queue is empty again");
}
static void tc_offline(void)
{
sb_link_stats_t st;
(void)setup_link();
feed(k_rt, SB_FRAME_A_LEN);
sb_link_poll(0U);
sb_link_get_stats(&st);
CHECK(st.offline == 0U, "TC-SBLINK-024 a fresh link is online");
/* 9.999 s 无召唤:还不算离线(屏板自复位约 1 s 空窗,按 500 ms 判会反复误报) */
sb_link_poll(SB_LINK_OFFLINE_MS - 1U);
sb_link_get_stats(&st);
CHECK(st.offline == 0U, "TC-SBLINK-024b just under 10 s of silence is still online");
/* 满 10 s:置离线,并且只打一行 */
sb_log_reset();
sb_link_poll(SB_LINK_OFFLINE_MS);
sb_link_get_stats(&st);
CHECK(st.offline == 1U, "TC-SBLINK-024c 10 s of silence marks the board offline");
CHECK(sb_log_has(0U, "OFFLINE") == 1, "TC-SBLINK-024d ... with one dedicated line");
/* 再久也不重复刷屏 */
sb_log_reset();
sb_link_poll(SB_LINK_OFFLINE_MS + 1000U);
CHECK(sb_log_n == 0U, "TC-SBLINK-024e the offline line is not repeated every poll");
/* 屏板回来:一帧就够,立刻清标志并报恢复 */
sb_log_reset();
feed(k_rt, SB_FRAME_A_LEN);
sb_link_poll(SB_LINK_OFFLINE_MS + 1010U);
sb_link_get_stats(&st);
CHECK(st.offline == 0U, "TC-SBLINK-024f a single valid summon brings it back online");
CHECK(sb_log_has(0U, "back online") == 1, "TC-SBLINK-024g ... and says so");
}
static void tc_frame_timing(void)
{
sb_link_stats_t st;
(void)setup_link();
fill_bmp(0x66U);
/* 每次读 CYCCNT 前进 16800 周期 = 100 µs @168 MHz;组帧读两次 → 差值正好 16800。
* 这条钉的是"周期 → µs"的除数(SystemCoreClock/1e6),换算错了会在台架上
* 把 0.17 ms 报成 17 ms 或 1.7 µs,两种都会被当成"组帧太慢/没测到"。 */
sb_cyccnt_step = 16800U;
(void)sb_link_frame_update(s_bmp, 0U);
sb_link_get_stats(&st);
CHECK(st.frame_us == 100U, "TC-SBLINK-025 the cycle count is converted with SystemCoreClock");
CHECK(st.frame_us_max == 100U, "TC-SBLINK-025b ... and the peak is tracked");
s_bmp[0] ^= 0xFFU;
sb_cyccnt_step = 50400U; /* 300 µs */
(void)sb_link_frame_update(s_bmp, 0U);
sb_link_get_stats(&st);
CHECK(st.frame_us == 300U, "TC-SBLINK-025c ... the last build duration");
CHECK(st.frame_us_max == 300U, "TC-SBLINK-025d ... and the peak follows the slower build");
}
static void tc_tx_report(void)
{
(void)setup_link();
fill_bmp(0x88U);
(void)sb_link_frame_update(s_bmp, 0U);
sb_log_reset();
sb_link_poll(SB_LINK_REPORT_MS);
CHECK(sb_log_n == 1U, "TC-SBLINK-026 a changed frame counter produces a report");
CHECK(sb_log_has(0U, "fb=1") == 1, "TC-SBLINK-026b ... carrying the build count");
CHECK(sb_log_has(0U, "tx=0") == 1, "TC-SBLINK-026c ... and the transfer counters");
/* 这一拍会先出一行 OFFLINE(10 s 无有效召唤,2-5),但不该再出 TX 行。
* 断言行数而不是"没看见 tx=",是为了把"离线判定排在周期上报之前"这个顺序也钉住。 */
sb_log_reset();
sb_link_poll(2U * SB_LINK_REPORT_MS);
CHECK(sb_log_n == 1U, "TC-SBLINK-026d only the offline line is emitted, no TX report");
CHECK(sb_log_has(0U, "OFFLINE") == 1, "TC-SBLINK-026e ... and it is the offline one");
}
int main(void)
{
printf("=== ScreenLink Link Tests ===\n");
@@ -476,6 +841,29 @@ int main(void)
printf("-- stats snapshot --\n");
tc_stats_snapshot();
printf("-- batch 2: frame update guards --\n");
tc_frame_update_guards();
printf("-- batch 2: frame B content --\n");
tc_frame_build_content();
printf("-- batch 2: change detection --\n");
tc_frame_unchanged();
printf("-- batch 2: TX kick from the RX callback --\n");
tc_tx_kick();
printf("-- batch 2: frame frozen while TX runs --\n");
tc_tx_busy_freezes_frame();
printf("-- batch 2: TX kick failure --\n");
tc_tx_kick_failure();
printf("-- batch 2: TX aborted by an error interrupt --\n");
tc_tx_abort_on_error();
printf("-- batch 2: key queue --\n");
tc_key_queue();
printf("-- batch 2: offline detection --\n");
tc_offline();
printf("-- batch 2: frame build timing --\n");
tc_frame_timing();
printf("-- batch 2: TX report line --\n");
tc_tx_report();
printf("\n=== ScreenLink Link Tests: %d/%d PASSED (failed=%d) ===\n",
s_passed, s_total, s_failed);
return (s_failed == 0) ? 0 : 1;
+74 -1
View File
@@ -35,6 +35,7 @@ typedef enum {
/* RxState 的两个取值:错误回调里"要不要重挂"就靠它判(见 sb_link.c 的 on_error 说明) */
#define HAL_UART_STATE_READY (0x20U)
#define HAL_UART_STATE_BUSY_RX (0x22U)
#define HAL_UART_STATE_BUSY_TX (0x24U)
/* HAL 的错误位(ErrorCode):ORE 是阻塞型,NE/FE/PE 是非阻塞型 */
#define HAL_UART_ERROR_PE (0x01U)
@@ -146,7 +147,70 @@ static HAL_StatusTypeDef HAL_UARTEx_ReceiveToIdle_IT(UART_HandleTypeDef *p_huart
return HAL_OK;
}
/* ===== ④ 测试用的复位/查询把手 ===== */
/* ===== ④ 假 Transmit_DMA(批 2:起发在中断里,收尾在 TxCplt) ===== */
#define SB_FAKE_TX_OK (0U)
#define SB_FAKE_TX_BUSY (1U)
#define SB_FAKE_TX_ERROR (2U)
static uint32_t sb_tx_calls;
static uint32_t sb_tx_force; /* SB_FAKE_TX_* */
static const uint8_t *sb_tx_buf;
static uint16_t sb_tx_len;
static HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *p_huart,
const uint8_t *p_buf,
uint16_t size)
{
sb_tx_calls++;
sb_tx_buf = p_buf;
sb_tx_len = size;
if (sb_tx_force == SB_FAKE_TX_BUSY) {
return HAL_BUSY;
}
if (sb_tx_force == SB_FAKE_TX_ERROR) {
return HAL_ERROR;
}
/* 真机成功起发后 gState = BUSY_TX,TxCplt 才打回 READY。
* 错误回调里"TX 是不是还在发"就靠这一位判(防残留 busy)。 */
p_huart->gState = HAL_UART_STATE_BUSY_TX;
return HAL_OK;
}
/* ===== ⑤ 假 DWT / CoreDebug / SystemCoreClock(组帧耗时统计用) ===== */
/* 真机由 core_cm4.h 提供。宿主侧要能"推进"计数器,所以 CYCCNT 也做成函数指针
* (与 SR / DR 同一手法):每读一次前进 sb_cyccnt_step,于是 sb_link 里那两次
* 读之间的差值 = 一个 step,测试就能钉住 "周期 → µs" 的除数。 */
typedef struct {
volatile uint32_t CTRL;
uint32_t (*cyccnt_cell)(void);
} DWT_TypeDef;
typedef struct {
volatile uint32_t DEMCR;
} CoreDebug_TypeDef;
#define CYCCNT cyccnt_cell()
static uint32_t sb_cyccnt_val;
static uint32_t sb_cyccnt_step; /* 每次读前进多少周期(0 = 恒 0,默认) */
static uint32_t sb_cyccnt_cell(void)
{
sb_cyccnt_val += sb_cyccnt_step;
return sb_cyccnt_val;
}
static DWT_TypeDef g_sb_dwt = { 0U, sb_cyccnt_cell };
static CoreDebug_TypeDef g_sb_coredebug;
static uint32_t SystemCoreClock = 168000000U;
#define DWT (&g_sb_dwt)
#define CoreDebug (&g_sb_coredebug)
#define CoreDebug_DEMCR_TRCENA_Msk (0x01000000U)
#define DWT_CTRL_CYCCNTENA_Msk (0x00000001U)
/* ===== ⑥ 测试用的复位/查询把手 ===== */
static void sb_fake_reset(void)
{
uint32_t i;
@@ -161,6 +225,15 @@ static void sb_fake_reset(void)
sb_arm_force = SB_FAKE_ARM_OK;
sb_arm_buf = NULL;
sb_arm_size = 0U;
sb_tx_calls = 0U;
sb_tx_force = SB_FAKE_TX_OK;
sb_tx_buf = NULL;
sb_tx_len = 0U;
g_sb_dwt.CTRL = 0U;
sb_cyccnt_val = 0U;
sb_cyccnt_step = 0U;
}
/* 寄存器读序列是否为期望的字符串(如 "SD"):长度不等或内容不符都返回 0 */