做嵌入式开发,绕不开串口,更绕不开串口日志。很多团队在项目初期把串口日志当成“printf裸奔”,等系统复杂之后才发现:日志乱、日志丢、日志影响主流程,甚至崩溃时根本没有日志可看。这篇主要聊的是嵌入式架构实践里的一个基础模块——EPLAT+AI 场景下的串口日志系统,也就是在 EPLAT 这类自研平台框架里,把串口日志从“随手打印”升级成“稳定、可读、可回溯、能被 AI 辅助分析”的系统模块到底该怎么做。
如果你正在做嵌入式 Linux、MCU 固件或者车控、物联网设备端开发,这篇可以直接按落地顺序看。最值得关注的不是某个花哨的打印函数,而是传输链路设计、缓冲区策略、时间戳规范,以及 AI 分析日志是放在哪个位置,才能真正提高排查效率。
1. 先想清楚:串口日志系统到底解决什么问题
1.1 “能看到输出”和“关键时候能回溯”是两回事
我见过很多嵌入式项目,日志代码写得很随意:
printf("temp is %d\n", temp);这个写法在小 Demo 里没问题。等设备跑起来几小时后崩溃,你想知道崩溃前一个周期的状态,结果什么都找不到。因为你没有统一缓冲,没有时间戳,没有按模块分级,没有处理输出阻塞,没有搞清什么时候会丢日志。
串口日志系统要解决的第一个问题不是“如何打印”,而是“设备运行过程中,产生的一系列事件能不能完整、有序、可区分地记录下来”。它要能回答这几个问题:
- 这条日志是什么时间产生的
- 来自哪个模块
- 属于调试、信息、警告还是错误
- 在日志流里的前后顺序是什么
- 高负载或异常复位时,最近的日志是否还在
EPLAT 这种平台化思路的核心,就是把这类公共能力从业务代码里抽出来。串口日志模块就是 EP 平台最早要建的地基之一。没有这套东西,后面接 AI 辅助分析也没有数据源。
1.2 嵌入式串口日志和服务器端 ELK、Loki 的区别
很多做后端转过来的朋友会想到 ELK 或者 Loki,觉得日志系统应该带采集、索引、检索、可视化。嵌入式串口日志系统确实可以借鉴这类思想,但不能照搬。
嵌入式环境有几个天然限制:
- 存储空间有限,不能长时间保存全量日志
- 串口带宽有限,按 115200 波特率算,每秒钟裸数据大约只有 11KB 左右
- 目标板通常没有标准文件系统,更没有 Elasticsearch
- 日志产生速度和串口发送速度不匹配,必须有缓冲和丢弃策略
所以嵌入式串口日志系统更像是一条“窄带宽、低存储、实时性要求高”的轻量数据链路。它的目标是“在有限的资源里,尽量把最关键的运行信息传出去”,而不是“把十年日志都存下来让你随便查”。
真正需要像 ELK 那样分析的时候,通常会通过 4G、Wi-Fi 或上位机转发,把设备端采集到的日志送到开发机或服务器,再做聚合分析。那一步才是后端的日志平台,设备端只负责采集、格式化、上传和本地兜底。
2. EPLAT 平台层和串口日志的关系
2.1 日志不只是一个 uart_send 函数
设计串口日志系统前,先理解 EPLAT 平台的思路。EPLAT 不是一个具体的芯片 SDK,它更像嵌入式团队内部沉淀出来的一个可复用平台框架,包含硬件驱动抽象、任务调度、通信管理、存储管理、日志服务等模块。日志服务是其中非常重要的一个基础服务。
如果把日志功能做在业务代码里,会出现这些情况:
- 每个模块自己定义打印格式,有的带时间,有的不带
- 不同模块日志直接写到同一个串口,互相穿插
- 某个模块用了阻塞发送,串口 FIFO 满的时候把整个任务卡住
- 想开日志等级,需要改很多源码,不敢在发布版里关调试输出
EPLAT 要做的是统一出口。业务模块只要调用日志模块提供的 API,不用关心底层是 UART、USB 虚拟串口,还是某种测试板上的蓝牙串口。
这套设计的关键是“分离”:
- 业务层只描述事件,调用
log_debug、log_info、log_error - 日志模块负责加时间戳、模块名、优先级
- 发送层负责把格式化后的日志写入环形缓冲区,再通过中断或 DMA 发到串口
- 平台启动时决定日志输出到哪个通道,运行时可以动态调整
这样业务代码不会绑死在某一种硬件上,测试时也方便替换输出通道。
2.2 EPLAT 日志模块应提供哪些接口
我在实际项目里习惯把日志模块设计成下面几类接口:
- 初始化接口:初始化串口、缓冲区、时间基准
- 输出接口:按日志级别输出的宏或函数
- 等级控制接口:全局等级和按模块等级
- 通道绑定接口:默认走 UART,也可以切到 Flash 或上行通道
- 转储接口:把异常复位前的缓冲内容导出
接口数量不用太多,但每个都要稳定。日志模块是最容易被所有业务模块调用的部分,如果接口改来改去,整个项目都会跟着返工。
示例性质的接口声明可以长这样:
typedef enum { LOG_LEVEL_DEBUG = 0, LOG_LEVEL_INFO, LOG_LEVEL_WARN, LOG_LEVEL_ERROR, LOG_LEVEL_NONE } log_level_t; void log_init(const log_config_t *cfg); void log_set_level(log_level_t level); void log_set_module_level(const char *module, log_level_t level); void log_printf(log_level_t level, const char *module, const char *fmt, ...);实际发送时,可以在log_printf内部统一加系统 tick 时间戳和模块名,再写入发送队列。
比起每个模块各自调printf,这个设计要安全得多,因为只有日志模块有权限直接操作串口发送资源,业务模块不会因为误用共用资源而产生冲突。
3. 搭建串口日志系统前,先理顺硬件和驱动条件
3.1 串口本身:UART 通道、波特率、电平转换、驱动
串口日志依赖物理 UART。第一步是确认板子上有没有可用的调试串口,以及调试串口是否和功能串口冲突。
常见判断项包括:
- 使用哪个 UART 外设,默认引脚是哪两个
- 是否需要电平转换芯片,比如 USB 转 TTL 模块
- 调试上位机那边用的驱动是什么,常见有 CH340、CP2102、FTDI 系列
- 波特率是多少,超过 460800 后线材和干扰影响会明显变大
- 串口是否支持硬件流控 RTS/CTS
如果是 PC 上用串口调试助手接收日志,CH340 驱动的兼容性通常不错,很多开发板自带电路,插上就能识别。FTDI 方案在专业调试工具里比较常见,稳定性和驱动跨平台表现都更好,但成本高一些。选型时可以直接按现有调试器来,不必强求。
波特率的问题容易被忽略。默认 115200 适合入门和低速率调试。如果日志量大,可以提高到 230400 或 460800,但前提是:线材不能太长,两端晶振偏差不能太大,USB 转串口芯片质量要可靠。否则高速率下误码率会上升,日志就开始乱码,越排查越头疼。
3.2 为什么阻塞发送很危险
很多初学者在 MCU 上第一次写串口发送,用的是轮询等待发送完成:
while (UART_SendData(...) == BUSY);这就是阻塞发送。如果日志量不大,系统不忙,可能感觉不到问题。问题是当业务模块也走同一个串口,或者中断频率很高时,一个错误日志里频繁调用阻塞发送,会让任务卡在发送循环里,整个系统的实时性就崩掉了。
日志模块在 EPLAT 里的定位不能“喧宾夺主”。输出日志不能影响控制逻辑的运行。
解决办法是改成非阻塞方式。应用写好日志后,把数据放进缓冲区,由 UART 中断或者 DMA 搬走。发送完成时回调事件,这样业务代码不会长时间停在日志发送上。
可以参考下面的选择:
| 发送方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 轮询阻塞 | 简单、可靠,少量日志可用 | 大量日志会卡任务 | 调试初期、日志量少 |
| 中断发送 | 不阻塞主流程,实现适中 | 发送期间占用 CPU 中断 | 中等日志量 |
| DMA 发送 | 占用 CPU 少,吞吐高 | 配置和内存管理更复杂 | 日志量较大、平台支持 DMA |
如果工程里只是打印很少的状态信息,用轮询方式也能接受。但当我在 EPLAT 平台里做串口日志系统时,一定会上非阻塞发送,因为日志模块要被所有业务模块公用,不能确定调用方会在什么上下文里打日志。
注意:不是所有日志都适合放进中断里直接格式化。中断里调用耗时操作会破坏实时性,一般做法是在中断里只搬运数据,格式化日志的活放到任务上下文完成。
4. 串口日志数据链路设计:从应用到调试助手
4.1 单条日志最小格式:时间戳、级别、模块、消息
串口日志看起来只是“把文本发出去”,但为了后续处理和 AI 分析,格式要足够统一。日志系统产生的数据是给两类人看的:一类是开发者在终端里阅读,另一类是上位机脚本或 AI 程序解析。
我的建议是最小格式长这样:
[时间戳][级别][模块] 消息内容例如:
[001532.440][WARN][modbus] read timeout, retry=2时间戳用系统 tick 拼接成秒加毫秒的形式,级别用 DEBUG、INFO、WARN、ERROR 这样的英文单词,模块名越短越好,避免占用太多串口带宽。
如果你要让 AI 做日志分析,格式统一是前提。AI 对规律格式的日志能更快提取关键信息;如果日志一会儿是“hello world”,一会儿是“温度过高啦!!!”这种毫无结构的文本,再强的分析模型也拿不准哪些是状态字段,哪些是错误原因。
这里我可以给一个常用级别表:
| 级别 | 含义 | 典型场景 |
|---|---|---|
| DEBUG | 调试细节 | 变量变化、函数进入退出 |
| INFO | 关键步骤 | 初始化完成、连接成功 |
| WARN | 可能有问题 | 重试、超时、阈值接近 |
| ERROR | 已经出错 | 驱动失败、协议错误 |
| NONE | 关闭日志 | 发布版本静默运行 |
4.2 环形缓冲区为什么比直接 printf 更合适
直接调用printf时,如果底层往串口写数据,而串口速度跟不上,数据会在驱动缓冲区里排队,或者直接丢掉。更麻烦的是,多个任务同时调用printf,内部锁竞争和输出顺序会变得不可控。
在 EPLAT 这种多任务平台里,日志要先进一个环形缓冲区。业务模块把格式化好的日志写入缓冲区,发送任务或中断再从缓冲区取数据发送。
环形缓冲区的好处是:
- 解耦产生者和消费者
- 写入操作通常很快,不会长时间阻塞业务
- 可以统计丢弃量和缓冲占用
- 发送通道忙时也不会覆盖业务代码正在产生的数据
设计环形缓冲区要注意“满”的处理。如果缓冲区满,常见做法是直接丢弃新日志,并记录丢了多少条。比丢数据更糟糕的是写指针覆盖了还没发送的旧日志,这会让日志顺序错乱。
我一般会给日志模块增加一个统计计数器:
dropped_countsent_countpeak_buffer_usage
有了这三个指标,你才知道“日志丢了吗”“丢了多少”“缓冲区够不够”。如果没有这些统计,用户反馈日志不全时根本无从排查。
4.3 从单条日志到日志流:换行、转义和粘包
串口日志本质上是连续字节流。接收方要区分一条条的日志,核心靠换行符。
这里要统一约定:
- 每条日志以
\r\n结尾,方便兼容 Windows 串口助手和 Linux minicom - 日志内容里不要混入裸换行
- 如果日志内容可能包含特殊符号,要做转义或限制字符集
当多个模块同时输出时,日志流会出现并发穿插。比如任务 A 写到一半,任务 B 插了一条日志,结果串行流里变成两段文字交错。解决办法是格式化好完整的一行后,再一次性写入缓冲区,不要在格式化过程中多次写。
如果确实要在日志系统里支持长文本,可以允许分行,但尽量在结构化字段里带上序列号,例如多行日志的第一行是[log_start],结束行是[log_end],上位机在合并时才能准确还原。
5. 单任务跑通:从最小可用代码开始
5.1 准备环境与验证目标
不管项目规模多大,我建议先把最小链路跑通。这一步不要追求日志系统一次支持多模块、DMA、Flash 存储,先把“应用调用日志 API -> 串口 -> PC 串口助手显示”这条路打通。
准备内容:
- 一块带调试串口的开发板
- USB 转串口模块,或用开发板自带的串口芯片
- 电脑串口助手,或命令行工具如
minicom、screen - 编译工具链,能编译固件并烧录
验证目标非常明确:
- 开发板烧录程序后,串口助手能收到日志
- 日志带时间戳、级别、模块名
- 调用
log_error时不会卡死系统
5.2 一个可参考的日志模块雏形
下面的代码不是某个芯片 SDK 的完整实现,而是我在组织这类日志模块时常用的结构。重点是让你理解格式化和缓冲分离。
#include <stdio.h> #include <stdarg.h> #include <string.h> #include "eplat_log.h" #define LOG_RB_SIZE 1024 static char g_fmt_buf[128]; static char g_rb[LOG_RB_SIZE]; static volatile uint16_t g_rd; static volatile uint16_t g_wr; static volatile uint32_t g_dropped; static uint32_t g_last_tick_ms; static void rb_write(const char *data, uint16_t len) { uint16_t i; for (i = 0; i < len; i++) { uint16_t next = (g_wr + 1) % LOG_RB_SIZE; if (next == g_rd) { g_dropped++; continue; } g_rb[g_wr] = data[i]; g_wr = next; } } void eplat_log_out(LOG_LEVEL level, const char *module, const char *fmt, ...) { va_list ap; int len; if (level < g_log_level) { return; } va_start(ap, fmt); len = vsnprintf(g_fmt_buf, sizeof(g_fmt_buf), fmt, ap); va_end(ap); if (len < 0) { return; } if (len > (int)sizeof(g_fmt_buf) - 1) { len = sizeof(g_fmt_buf) - 1; } printf("[%u.%03u][%s][%s] %s\r\n", g_last_tick_ms / 1000, g_last_tick_ms % 1000, eplat_log_level_name(level), module, g_fmt_buf); rb_write(g_fmt_buf, (uint16_t)len); } void eplat_log_task(void) { while (g_rd != g_wr) { char c = g_rb[g_rd]; g_rd = (g_rd + 1) % LOG_RB_SIZE; eplat_uart_send_byte(c); } }上面的做法里,eplat_log_out把格式化的文本做一次printf输出,同时写入环形缓冲区,由后台任务通过eplat_log_task把缓冲数据发送出去。真正如果要严格避免双通道消耗串口,需要二选一;这里重点是展示两个思路:及时输出和缓冲输出。
更规范的平台做法是只把日志写入环形缓冲区,由串口发送任务或者 DMA 负责搬运。如果还有实时调试需求,可以在终端侧通过上位机再补一份即时输出,不要在设备端同时往同一个串口用两个路径写日志。
5.3 怎么判断单任务是否成功
单任务验证成功后,观察结果应该是稳定的,不是偶尔能收到。判断标准包括:
- 打开串口助手后,日志按时间顺序一条条出现
- 时间戳单调递增,不会突然回退
- 日志内容里没有乱码
- 系统跑一段时间后没有因为日志发送而复位
- 串口助手里可以看到 WARN、ERROR 等级别能正常命中
如果日志里带了\r\n还是全部挤在一行,检查串口助手是否开启了“显示换行”或者“发送新行”。这类问题经常不是固件的问题,是终端把\n当成普通字符处理了。
6. 用 AI 辅助嵌入式日志分析:不是把大模型塞进板子
6.1 AI 分析日志的可行位置
标题里有“EPLAT+AI”,很多人第一反应是“在嵌入式板卡上直接跑大模型”。这个方向不是不可以,但现在算力和内存都很受限,普通 MCU 或低配嵌入式 Linux 板跑通用大模型并不现实。
在串口日志系统这个场景里,AI 更合理的位置是开发机和上位机侧。设备端把日志通过串口记录成文件,或者在网络可用时上传到服务器,然后用通用 AI 工具读日志,让它完成以下几件事:
- 对日志做初步分类
- 找明显的异常关键字
- 对比正常启动日志和异常日志的差异
- 从协议超时报错中定位涉及哪个模块
- 根据日志推理可能的排查方向
这套流程不要求板子有大算力,只需要日志格式规范,源数据能保存下来,AI 就有分析基础。
6.2 让 AI 有效分析日志的典型流程
我用 AI 分析串口日志时,一般按这个流程走:
- 先从串口助手或日志文件里导出一份完整日志
- 去掉和当前问题无关的大段 DEBUG 输出
- 把日志先发给 AI,附上背景:这是一个什么设备、跑的什么业务、复现了什么现象
- 要求 AI 总结时间线,标出 ERROR 和 WARN 出现的上下文
- 针对某一段异常日志,让 AI 列出最可能的排查方向
我举个例子,一次排查设备频繁重启问题,时间线整理下来是:
[000012.345][INFO][system] power on [000012.400][INFO][wifi] start connect [000012.800][WARN][wifi] connect timeout [000013.100][WARN][watchdog] feed late [000013.150][ERROR][system] reset by watchdogAI 对比正常板子日志后,很快提醒我关注watchdog feed late的时序。顺着这个方向找到了一个高优先级任务长时间占用 CPU 导致喂狗延迟的问题。如果只看一行 ERROR,很容易误判成硬件复位。
但要注意,AI 给的结论始终是“建议”,不能直接代替仪器测量和代码审查。嵌入式问题通常需要确认电压、时序、中断频率、任务调度,这些必须看波形、看 CPU 占用、看真实代码,AI 不能凭空确认。
6.3 AI 辅助生成解析脚本和排查模版
除了直接分析日志内容,AI 在串口日志系统的另一大用途是生成解析工具。
比如你每周要手动打开几十份串口日志,统计里面有多少条 ERROR、哪些模块报错最多、复位发生了几次。完全可以给 AI 一段格式说明,再让它生成 Python 解析脚本,把串口日志转成 CSV 或 JSON 结构。结构化之后再看问题,比直接在黑色终端里翻页要高效得多。
例如,一份标准格式日志:
[001532.440][WARN][modbus] read timeout, retry=2可以映射成字段:
{ "time_ms": 1532440, "level": "WARN", "module": "modbus", "message": "read timeout, retry=2" }有了结构化文件,再做统计、筛选和异常检测就很简单了。这一步使用 Python 在开发机上处理,对嵌入式目标板没有任何压力。
7. 从单串口到多模块、批量日志:怎么保证不丢不乱
7.1 日志丢不丢,关键看缓冲策略和消费速度
单条日志能通了,下一步是批量任务。嵌入式里最经典的场景是系统启动后多模块一起报日志,启动日志量大,如果串口速度跟不上,缓冲区会被填满。
这时要做的不是无限加大缓冲区,而是建立丢日志策略。我的常用规则是:
- 启动阶段允许丢 DEBUG,不允许丢 ERROR
- 按模块设置等级,只有需要调试的模块输出 DEBUG
- 高负载阶段临时把全局 DEBUG 关闭,保留 INFO 以上
- 如果错误日志也丢,先提高缓冲区,再考虑提高日志吞吐
日志模块最好有一个当前“丢弃总量”的统计值。这样当用户反馈“日志不全”时,你能从串口最后一条日志里看到明确的drop=xxx统计,而不是猜测丢了多少。
7.2 批量日志导出和按模块分区
开发阶段可以把日志实时输出到串口助手,是没问题的。可要是设备不在你身边,比如小车在场地测试、设备在客户现场,实时串口就不好用了。
这种情况下,平台需要支持把日志同时写入板上的 Flash 或外部存储。常见做法是:
- 在 RAM 里开一个循环缓冲
- 日志等级为 WARN 以上的强制写入 Flash 分区
- 设备掉电后,通过串口命令导出保存段
- 网络可用时,把历史日志文件上传到日志服务器
如果板子有文件系统,可以考虑按天或按启动次数命名。没有文件系统的 MCU 上,通常只保存最近一两次复位前的关键事件。
我习惯把“异常复位前的最后一段日志”做成独立任务处理。每次系统启动时检查复位原因寄存器,如果发现是看门狗复位或硬件异常,就保留旧的日志分区;如果是正常上电,才覆盖旧日志。这个机制能显著提高现场问题复现效率。
7.3 多串口和远程转发场景
有些平台有两个串口,一个用于调试输出,一个用于通信。这时日志模块要支持通道抽象,不能把日志死绑在哪个串口上。EPLAT 设计里的做法是给日志模块注册一个发送句柄:
typedef void (*log_send_fn)(const char *data, uint16_t len); void eplat_log_set_output(log_send_fn fn, void *user_data);当板子上有网络功能时,可以注册一个新输出函数,把日志通过 TCP 或 MQTT 发给远程日志服务。前端的串口助手、后端的 Loki、ELK 这些分析平台就能收到统一格式的日志流。
在嵌入式 Linux 上,还可以参考 systemd journal 的思路,把内核日志、应用程序日志、业务模块日志统一到同一个时间轴上。时间不同步是很多远程日志问题的根源。
8. 常见问题排查链路与避坑清单
8.1 日志乱码、丢字节、粘包先查什么
串口日志问题最容易误判,看起来是代码问题,实际很多是物理层或终端配置问题。我建议按下面的顺序排查。
先看乱码:
- 确认波特率、数据位、停止位、校验位是否一致
- 确认 USB 转串口模块是否有虚焊、线材是否过长
- 确认 TX 和 RX 是否接反
- 尝试换成 9600 或 115200 低波特率,排除高速率不稳定
- 检查代码里发送的字符编码和终端默认编码是否一致
再看丢字节:
- 看串口助手缓冲区是否溢出
- 看设备端日志缓冲区是否满,有没有
drop统计 - 确认系统里是否有多个任务同时发送同一个串口
- 确认 DMA 和中断是否共享缓冲区,是否存在访问冲突
- 提高调试等级或降低波特率,对比丢字节的位置
最后看粘包:
- 确认每条日志是否都带
\r\n - 确认日志格式化是否一次性完成
- 防止日志内容里本身带裸换行
- 检查上位机解析时是否按固定行尾切分
8.2 系统卡死或任务超时,先怀疑日志发送阻塞
设备运行一会儿后系统卡死,但直接在串口日志里看不到明显原因时,不要急着改业务逻辑。先检查日志发送任务本身有没有出现问题。
排查顺序:
- 关闭所有日志输出,看系统是否恢复正常
- 如果恢复正常,说明日志模块占用了过多资源或引入了死锁
- 检查日志 API 是否在中断上下文被调用,内部是否用了不可重入函数
- 检查环形缓冲区的读写是否加了必要保护
- 检查
vsnprintf是否被频繁调用,格式化耗时是否过长
一个常见的低级错误是:在串口中断服务函数里调用日志模块,而日志模块又想通过同一个串口发送数据,结果造成中断递归或者死等,整个中断永远出不来。日志模块一般不直接放在中断回调里,除非你单独设计了一套无锁中断级接口。
8.3 时间戳为什么不准
嵌入式日志的时间戳一般来自系统 tick 或者 RTC。系统 tick 计时不准,常见原因是中断频繁导致 tick 被抢占,或者 tick 定时器没有校准。RTC 时间不准,则先检查晶振和 RTC 电池。
如果你用的是 tick 计数,要确保拿到 tick 的方式在中断与任务之间是安全一致的。否则日志里可能看到时间戳乱跳。排查方法是在固定频率的外部中断里打印时间戳,看间隔是否均匀。
注意:不要在日志格式里混用“开机运行时间”和“墙上时钟时间”。AI 分析时最怕两种时间混在一起,导致它误判事件顺序。可以在同一行里分别标记,但要有明确的字段名。
8.4 AI 分析结果不可用时怎么修正
AI 分析日志不准确,很多时候不是 AI 能力问题,而是输入数据质量太差。
常见输入质量问题:
- 日志里没有时间戳,顺序靠猜
- 日志里夹杂大量无关 DEBUG,信号被淹没
- 同一个错误有多种写法,比如
timeout、timed out、超时混在一起 - 没有模块名,无法定位是哪个驱动或业务组件
- 日志行被截断或粘包,AI 无法正确切分
遇到 AI 分析结果不可用,我会先把日志整理成干净文本,再做一次字段抽取。抽取后再给 AI 的就不该是原始文本,而是一段带结构的文本:
事件: 连接Wi-Fi超时 模块: wifi 首次出现时间: 12.800 出现次数: 3 相关日志片段: ...这比直接把几百行串口日志丢给 AI 要可靠得多。AI 的作用是把“结构化的日志数据”转换成“人类能理解的排查建议”,而不是在垃圾数据里做无米之炊。
写在最后的落地建议
串口日志系统看起来是嵌入式里比较基础的东西,但真正做好并不容易。我个人的经验是:先把单条日志链路做稳,再考虑多模块、批量导出,最后才是 AI 辅助分析和上传服务器。不要一开始就把架构设计得过于复杂,不然到最后可能连最基础的打印都没理顺。
如果你们团队的项目里也叫 EPLAT,或者是类似的自研平台框架,串口日志模块应该放在很靠前的位置优先建设。它不需要华丽,但接口要稳定、格式要统一、缓冲要可靠、丢日志要有统计、异常复位要能回溯。满足这几条之后,后续接 AI 辅助分析或者远程日志服务,都会顺畅很多。
很多嵌入式疑难杂症,最后不是靠盯着代码看出来的,而是靠一份清晰、完整、有时间线和模块标记的日志推出来的。串口日志系统就是帮你保留这条现场证据链的重要基础设施。