fix(console): gate adcTask logs on console-writer readiness

B1 on-board closed loop surfaced a byte-level console interleave: before
the console writer (defaultTask) registers, con_append() routes to
dbg_console_write_raw() -> con_tx_bounded(), which is register-level
polling with no mutual exclusion. adcTask logged every 1 ms, so its
first-frame line collided with defaultTask's [NAND_FTL] init line and
the required marker spanned two interleaved lines -> false FAIL.

- console_log: add dbg_console_writer_ready() (returns s_con_writer_up)
- adc_task: gate first-frame and 10 s stats logs on writer readiness;
  defer the first-frame report until the writer is up
- docs: close B1/P1-1 in the registry; flip stale "remaining @test"
  notes; correct the console doc's obsolete "drop-only, no interleave"
  claim (that held for HAL gState, not register-level raw send)

Bench (run_boot_test.py): VERDICT PASS, readback OK. adcTask
frames/seq +10000 per 10 s = 1000 Hz, idle=0, frst_err=0; stack
496/2048 (75%). @build 0/0.
This commit is contained in:
2026-10-10 14:53:36 +08:00
parent 7c4b724057
commit da66c500ea
12 changed files with 103 additions and 68 deletions
+18
View File
@@ -26,6 +26,11 @@
* 都换成 DWT 周期计数硬上界(DWT 使能失败时退回旧 HAL 行为)。
* 另加 USART1 未初始化(huart1.Instance == NULL)短路:防止在
* HAL_Init 早期/断言-故障上报路径上解引用 NULL 读写地址 0。
* 2026-10-10 王建锋 新增 dbg_console_writer_ready() 查询:写者未注册时
* con_append 走无锁直发(前提是"此窗口只有 defaultTask 记日志"),
* adcTask(B1)等周期任务此时落日志会与 defaultTask 的存储初始化
* 日志字节级交错(台架实测 [ADC_TASK] 首帧行与 [NAND_FTL] 行交错)。
* 提供就绪查询供周期任务门控自身日志,不改直发路径本身。
*/
#include "console_log.h"
@@ -223,6 +228,19 @@ void dbg_console_writer_attach(void) {
s_con_writer_up = 1U;
}
/*
* 函数功能:查询控制台写者是否已注册(1=已注册,此后日志走环形缓冲行级原子路径)
* 返回值:s_con_writer_up(volatile 读,任意任务上下文可调)
* 限定条件:无
* 函数说明:供**周期性任务**在写者就绪前抑制自身日志。写者未注册时 con_append 走
* dbg_console_write_raw 无锁直发(设计前提是"此窗口只有 defaultTask 记日志"),
* 其余任务此时落日志会与 defaultTask 的存储初始化日志逐字节交错
* (2026-10-10 台架:adcTask 首帧日志与 [NAND_FTL] 初始化行交错)。
*/
uint8_t dbg_console_writer_ready(void) {
return s_con_writer_up;
}
/*
* 函数功能:USART1 僵尸通道自愈(清错误锁存 + 整外设重初始化)
* 函数说明:RX 悬空噪声(PA10 未接线)锁存的 ORE/NE/FE/PE 使 HAL 轮询 TX 恒快速失败
+5
View File
@@ -34,6 +34,10 @@ extern "C" {
* DBG_PRINT 宏内用库 snprintf 完成)
* dbg_console_write_raw - 异常/早期路径:有界单次直发(不加锁)
* dbg_console_writer_attach- 写者任务(defaultTask)注册自己为唯一发送者
* dbg_console_writer_ready - 写者是否已注册(1=已注册)。供**周期性任务**在写者就绪前
* 抑制日志用:写者未注册时 con_append 走无锁直发路径
* (仅假设 defaultTask 在日志),其余任务此时落日志会与
* defaultTask 的存储初始化日志字节级交错(2026-10-10 台架实测)
* dbg_console_drain - 写者任务排空缓冲并发送(单次有界;TX 失败自动软复位自愈)
* dbg_console_dropped - 因缓冲满丢弃的行数(诊断)
* dbg_console_tx_fault - 因僵尸通道软复位自愈的次数(诊断)
@@ -47,6 +51,7 @@ extern "C" {
int dbg_console_write_line(const char *line, unsigned len);
void dbg_console_write_raw(const char *buf, unsigned len);
void dbg_console_writer_attach(void);
uint8_t dbg_console_writer_ready(void);
void dbg_console_drain(void);
uint32_t dbg_console_dropped(void);
uint32_t dbg_console_tx_fault(void);
+25 -12
View File
@@ -20,12 +20,17 @@
* v1.1 2026-10-05 王建锋 P3-11:注明"预留桩(未启用)",避免误认为已实现
* v2.0 2026-10-10 王建锋 B1:实现真实消费者(1ms 节拍 + 原子快照 + 帧统计 +
* 看门狗心跳),由 Src/freertos.c 创建为 adcTask
* v2.1 2026-10-10 王建锋 日志门控:首帧/周期统计仅在 dbg_console_writer_ready()
* 为真后落日志。写者未注册时控制台走无锁直发,与
* defaultTask 的存储初始化日志字节级交错(台架实测);
* 就绪前挂起上报,就绪后补报最近一帧。
*/
#include "adc_task.h"
#include "cmsis_os.h"
#include "tpafe5160.h"
#include "task_watchdog.h"
#include "console_log.h"
#define DBG_TAG "[ADC_TASK]"
#include "dbg_log.h"
@@ -82,31 +87,39 @@ void adc_task_func(void *arg) {
r = tpafe5160_snapshot(s_frame, (uint8_t)TPAFE5160_CH_NUM);
if (r == 1) {
s_frames_consumed++;
if (first_logged == 0u) {
first_logged = 1u;
/* 只报 TPAFE 三路(UA=buf[6] UN=buf[5] IA=buf[2]),验证取数契约 */
DBG_INFO("first frame: UA=%d UN=%d IA=%d",
s_frame[6], s_frame[5], s_frame[2]);
}
} else if (r == 0) {
s_frames_idle++;
} else {
/* r == -1:参数错。调用点 count 为常量,不可达;不计入 idle 以区分异常 */
}
/* 2b) 首帧码值上报:**写者就绪后**才落日志。写者未注册时 con_append 走
* 无锁直发路径,与 defaultTask 的存储初始化日志会字节级交错
* (2026-10-10 台架实测);此时挂起上报,就绪后补报最近一帧。
* 只报 TPAFE 三路(UA=buf[6] UN=buf[5] IA=buf[2]),验证取数契约 */
if ((first_logged == 0u) && (s_frames_consumed != 0u) &&
(dbg_console_writer_ready() != 0u)) {
first_logged = 1u;
DBG_INFO("first frame: UA=%d UN=%d IA=%d",
s_frame[6], s_frame[5], s_frame[2]);
}
/* 3) 看门狗心跳(本任务已纳入 required_mask) */
task_wd_beat(TASK_WD_ADC);
/* 4) 周期心跳日志:帧统计 + 驱动侧 FRSTDATA 错误计数 */
/* 4) 周期心跳日志:帧统计 + 驱动侧 FRSTDATA 错误计数。
* 写者就绪前抑制(同 2b 的交错理由);到点无论是否落日志都推进下个上报点 */
{
uint32_t now = osKernelGetTickCount();
if ((int32_t)(now - report_at) >= 0) {
DBG_INFO("frames=%lu idle=%lu seq=%lu frst_err=%u",
(unsigned long)s_frames_consumed,
(unsigned long)s_frames_idle,
(unsigned long)tpafe5160_frame_seq(),
(unsigned)tpafe5160_frstdata_err());
if (dbg_console_writer_ready() != 0u) {
DBG_INFO("frames=%lu idle=%lu seq=%lu frst_err=%u",
(unsigned long)s_frames_consumed,
(unsigned long)s_frames_idle,
(unsigned long)tpafe5160_frame_seq(),
(unsigned)tpafe5160_frstdata_err());
}
report_at = now + ADC_TASK_REPORT_MS;
}
}