跳到主要内容

线程与设备接口

learning-app 的 main 在不断循环,但它仍是一个线程。你不是拥有了整个 CPU,而是在 Zephyr 调度器管理的上下文中运行。

线程休眠​

k_sleep(K_MSEC(500)) 表示当前线程等待指定时长后重新具备运行条件,不保证恰好在第 500 ms 获得 CPU。循环中的打印、驱动访问及其他线程竞争也会影响实际周期。

对比三种写法:

写法含义常见用途
k_sleep阻塞当前线程,让出 CPU周期任务、等待一段时间
k_busy_wait忙等,持续占用执行资源驱动中很短的时序等待
空 for 循环时间依赖编译优化与 CPU 速度不用作可靠延时

把长忙等放到高优先级线程里,可能让其他任务得不到运行机会。需要精确定时或波形时,考虑硬件定时器/PWM,不靠打印循环模拟。

线程与调度​

新线程至少有入口、独立栈和优先级。栈不是自动无限增长的,局部大数组、深调用和格式化输出都会增加占用。

Zephyr 常规线程优先级的数值越小优先级越高;负值用于协作式优先级,非负值用于抢占式优先级,具体可用范围由 Kconfig 决定。初学阶段保留默认设置,不用随意提到最高优先级解决卡顿。

学习实验可以先让 main 每秒打印,另一个线程每两秒打印。观察两个计数交错,再思考:它们是否共享变量?共享变量是否需要互斥锁、信号量或消息队列?volatile 不会使复合操作自动线程安全。

中断上下文​

中断回调应尽量短。不要在 ISR 中执行会休眠的调用、长时间绘图或等待互斥锁。通常把事件通知给线程或 work queue,再执行较长工作。具体 API 是否可在 ISR 调用,以该 API 文档为准。

例如触摸事件最终触发 LVGL 按钮,不代表应用应该在硬件 IRQ 里直接创建界面对象。当前圆屏示例把 LVGL 对象更新放在统一的处理路径中;以后增加后台线程时,要继续遵守 LVGL 的线程同步要求。

设备初始化与错误处理​

DEVICE_DT_GET(...) 获得构建生成的设备对象引用。它不会当场重新初始化设备;device_is_ready() 用于检查设备是否已经可用。

就算 ready 为真,一次 SPI、I2C 或 PWM 操作仍可能失败,所以驱动调用返回值也要检查:

int ret = gpio_pin_set_dt(&output, true);
if (ret < 0) {
printk("GPIO write failed: %d\n", ret);
}

-22 通常是 -EINVAL,说明参数或状态不符合该 API/驱动要求,不能单凭数字认定 GPIO 接错。本项目 PWM 曾打印 Prescaler out of range,应沿驱动时钟、频率与分频参数检查;不能把一次特定版本的修复概括为所有 ESP32 都要改驱动。

日志分析​

先分层判断:ROM 是否启动固件、Zephyr 是否启动、设备是否 ready、API 是否成功、应用是否实现预期。编译成功仅覆盖其中最早的一部分。

练习:将计数程序的周期改为 100、1000,分别记录十行 uptime,计算间隔;解释为什么它不是严格采样时钟。然后查看 main 栈配置,并比较 LVGL 示例为什么需要更大的栈与内存池。

参考:Zephyr 线程、本机 include/zephyr/kernel.h 和 include/zephyr/device.h。