ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

第 6 章 日志与错误处理:串口输出、错误码与断言机制

第 6 章 日志与错误处理:串口输出、错误码与断言机制 本章从底层讲清①日志是怎么通过串口喊出来的UART 帧、格式化②日志五级的语义③esp_err_t错误码体系④ESP_ERROR_CHECK出错时芯片内部发生了什么打印 → abort → 默认配置下自动重启。这些是嵌入式调试的核心工具。6.1 日志的底层链路字符怎么从芯片到屏幕ESP_LOGI(TAG, 格式串, 各个参数) │ ① 按格式串把参数拼成文本printf 家族C 语言最老的按格式拼装 │ 并输出函数们ESP_LOG 就是加了等级 署名的 printf 变体。 │ 逐个字符处理 ▼ 一长串字节就是那行日志的每个字符的 ASCII 码一字一字节 │ ② 交给 UART 驱动 ▼ 按帧格式一个个字节发出去起始位8数据位停止位第 0 章 │ ③ 走 UART 口经 USB 转串口芯片或走原生 USB 口2.5 的两条路 ▼ 电脑串口程序monitor按 115200 波特率收帧、还原文字、显示波特率双方约定每秒传多少位不一致 乱码日志默认是同步阻塞的谁调用ESP_LOGI谁就停下手里的事把这条日志的字节一个个塞进 UART塞完才继续跑下一行代码。所以正常情况下一条都不会丢代价是打印得多、程序就慢——嵌入式里加点日志时序就变了就是这个原因。只有主动换成非阻塞方案才可能出现迟到或丢行日志先进缓冲区、由后台任务FreeRTOS 里的一个任务第 13 章慢慢搬运缓冲区塞满时高频打印确实会丢行。那是模式选择的代价不是日志系统的日常。本书用的 IDF v6.0.1 经典日志 V1 就是全同步非阻塞打印模式CONFIG_LOG_PRINT_MODE是另一些 IDF 版本/日志实现提供的选项没有特意去开就不要假设日志是异步的。一句话丢日志不是正常现象。屏幕上一个字都没有先查 COM 口、波特率、监视器占用少了几行中间才考虑是不是自己开了缓冲方案。6.2 日志五级从正常到完蛋宏等级语义典型用途ESP_LOGEERROR (E)出错程序可能无法继续致命错误ESP_LOGWWARN (W)有问题但还能跑边界情况ESP_LOGIINFO (I)正常进度主要流程ESP_LOGDDEBUG (D)调试细节排查时开ESP_LOGVVERBOSE (V)最琐碎极少用过滤机制编译时按CONFIG_LOG_DEFAULT_LEVEL决定保留哪些等级低于阈值的调用被直接删掉——不进固件。这组开关在 Kconfig 里是一个choice 互斥组呼应 3.6 讲过的全名陷阱CONFIG_LOG_DEFAULT_LEVEL_NONE / _ERROR / _WARN / _INFO / _DEBUG / _VERBOSE六选一sdkconfig 里同时只会有一行y其余全是# ... is not set。本书工程真实 sdkconfig 生效的就是CONFIG_LOG_DEFAULT_LEVEL_INFOy # 只保留 INFO 及以上实际生效值 # CONFIG_LOG_DEFAULT_LEVEL_DEBUG is not set想看 DEBUG 日志去 menuconfig 把这一组选到 DEBUG工具会自动把INFO 那行改成 “is not set”而不是两行并排都写y——defaults 里这么写只会让后处理的值纠结半天事与愿违。为什么调试完要关 DEBUG因为 DEBUG 的字符串本身会进固件增加体积而且打印过程耗时拖慢时序。发布用 INFO。格式串与参数ESP_LOGI(TAG,速度%d 占空比%u 十六进制%x,speed,duty,val);%d有符号整数、%u无符号、%x十六进制、%s字符串、%f浮点类型必须匹配参数在内存里的解释方式由格式符决定不匹配就乱打想输出一个%要写%%格式化器把%当转义起点。6.3 TAG 的作用日志的分区TAG 是日志字符串里的名牌如l298n、DCMotor、ledc。作用一眼看出这条日志是谁打的哪一层可以按 TAG 开关日志esp_log_level_set(TAG, ESP_LOG_DEBUG)。排查时看完整链路从系统驱动如 ledc到我们的代码DCMotor到main——层级递进的日志能还原事故经过6.6 有实例。6.4 esp_err_t统一的体检报告typedefintesp_err_t;// 本质是 int// typedef给已有类型起个更有意义的别名——// esp_err_t 就是错误码这个用途下的 intESP_OK0// 成功ESP_ERR_*负数// 失败每个负数值是一种错误每个返回 esp_err_t 的函数返回的不是结果数据而是成功/失败原因。配合esp_err_to_name(err)可以打印错误的名字如ESP_FAIL。三种处理姿势按场景选姿势 1检查 自己处理灵活esp_err_t errmotor.init();if(err!ESP_OK){ESP_LOGE(l298n,init 失败: %s,esp_err_to_name(err));returnerr;// 向上层报告}注意这段外面套的是一个返回esp_err_t的函数才有return err可言。如果就是在app_main里检查——app_main返回void那里只能是ESP_LOGE 光秃秃的return;。本书真实main.cpp就是这么写的constesp_err_t errmotor.init();if(err!ESP_OK){ESP_LOGE(TAG,电机初始化失败: %s程序终止,esp_err_to_name(err));return;}姿势 2ESP_ERROR_CHECK出错就崩退打印后自动重启ESP_ERROR_CHECK(motor.init());宏展开后大致是esp_err_t errmotor.init();if(err!ESP_OK){// __FILE__/__LINE__编译内置宏编译时自动替换成本文件名/当前行号// 宏见 5.1——所以它不用你手写就能精确报出出错位置ESP_LOGE(TAG,ESP_ERROR_CHECK failed: %s at %s:%d,esp_err_to_name(err),__FILE__,__LINE__);abort();// 主动崩退不是安静退出见下}abort 内部发生了什么底层打印错误信息 → 触发 panic芯片的崩溃应急机制→ 转储寄存器和调用回溯Backtrace第 11 章教你翻译→默认配置下自动重启开机日志从头再滚一遍。程序绝不会停在崩溃那一步继续跑错误逻辑。所以看到 Guru Meditation / “rebooting” 不等于板子坏了——那正是程序自杀重启的标准流程真正要读的是重启前的最后几行。适合启动阶段出错了继续跑只会更糟。姿势 3ESP_RETURN_ON_ERROR出错返回程序继续活ESP_RETURN_ON_ERROR(motor.init(),TAG,初始化失败);出错 → 打印日志 →return err不崩不退由上层决定怎么办。适合出错后可以优雅降级继续的场合。6.5 动手让 DEBUG 日志亲手出现、再亲手让它消失6.2 说过低于阈值的日志编译期直接被删掉。这一节就用你自己写的程序验证这句话——不需要新电路把 4.8 的点灯程序改三行。第 1 步给 4.8 程序加署名和两行日志片段接在 4.8 完整程序上不能单独编译——TAG那行放在app_main之前两行ESP_LOG插在gpio_set_level(LED_PIN, 1);之后staticconstchar*TAGblink;// 日志署名它的作用见 6.3ESP_LOGW(TAG,马上点亮);// WARN默认阈值下一定看得到ESP_LOGD(TAG,点亮DEBUG 细节行);// DEBUG默认阈值下看不到【动手框】把 DEBUG 调出来再调回去① 在哪执行4.8 点灯工程的目录里用 2.3 打开的 IDF 终端VS Code 用户用它的 ESP-IDF 终端。② 敲什么先idf.py build flash monitor确认只看得到 W 那行再idf.py menuconfig方向键进入Component config → Log output →Default log level回车选Debug log level按s保存、q退出然后idf.py build flash monitor重来一遍——必须重新编译并烧录阈值是编译期裁进固件的6.2只重启监视器没有用。③ 预期看到第一次跑串口每隔 1 秒滚出W (1055) blink: 马上点亮D 行不见踪影改成 Debug 重烧后每次亮灯变成两行W (1055) blink: 马上点亮D (1055) blink: 点亮DEBUG 细节行。再调回 Info 重烧一次D 行消失——你亲手编译掉了它。④ 没看到① D 行不出现 → 重开 menuconfig 看*号是不是真停在Debug 上② 只改了配置没重编译烧录 →idf.py build flash monitor整串重来③ 一个日志都没有或满屏乱码 → 回 2.11 查端口和波特率。退出 monitor 按 Ctrl]。6.6 真实案例三层日志还原事故本书实际踩过固件烧录成功Calling app_main()已出现但电机初始化失败。下面这段是修复前的历史日志——括号里的init(80)是当时代码版本的行号现在的motor_control.cpp行号已经不同对照时别被行号绕晕。这个坑本书后来已经修好现在照抄本书代码无法复现这段日志——这一节的目的不是让你制造事故而是先学会读法第 11 章教翻译工具E (749) ledc: Fade service not installed, call ledc_fade_func_install E (759) ledc: ledc_set_duty_and_update(1650): LEDC fade channel init error, not enough memory or service not installed E (769) DCMotor: init(80): 占空比初始化失败 E (779) l298n: 电机初始化失败: ESP_FAIL程序终止逐层解读行谁打的告诉我们1ledc 驱动根因fade 服务没装2ledc 驱动具体函数返回了错误3DCMotor我们的封装层收到错误并上报4main最上层决定程序终止这就是日志的价值从驱动层到应用层把哪一步、为什么失败完整还原。修复方法在第 7 章先配置 timer/channel 再装 fade 服务。另外记住中断服务函数里不能打日志printf 太慢且非中断安全原因与替代方案见第 15 章。6.7 日志与错误处理的选择表场景用正常流程汇报ESP_LOGI可疑但继续ESP_LOGW必须停机ESP_LOGE ESP_ERROR_CHECK可降级继续ESP_RETURN_ON_ERROR / 手动检查排查细节ESP_LOGD用完调回6.8 常见问题速查现象原因解决日志一个不显示串口没连对/波特率错检查 COM、115200乱码波特率不一致统一波特率%d 打出巨大数类型不匹配检查格式符与实参DEBUG 看不到阈值是 INFO调 CONFIG_LOG_DEFAULT_LEVEL操作见 6.5ESP_ERROR_CHECK 打出 Guru Meditation 后重启确实出错panic 默认自动重启6.4 姿势 2读重启前最后几行日志找根因板子没坏6.9 想一想 小测验想一想为什么波特率不一致会乱码从帧格式角度回答ESP_ERROR_CHECK和ESP_RETURN_ON_ERROR的本质区别是什么为什么驱动层的日志比应用层日志更能揭示根因小测验日志I (779) l298n: 电机初始化失败: ESP_FAIL程序终止中I是什么等级ESP_OK的值是多少A. 0 B. 1 C. -1想输出占空比 50%格式串怎么写ESP_ERROR_CHECK出错时内部会调用什么函数A. abort B. reboot C. exit6.10 本章总结日志 字符 → UART 帧 → 电脑显示波特率一致才不乱码五级E/W/I/D/V编译期按阈值裁剪DEBUG 用完关掉6.5 亲手试过esp_err_t int0 成功负数失败esp_err_to_name翻译ESP_ERROR_CHECK 出错打印 abortpanic 后默认配置自动重启不是死机不动适合启动必停场景看日志要读完整链路驱动层根因 → 封装层 → 应用层。下一章主角登场PWM 和 LEDC——让引脚输出可调速的信号。
返回列表