esp32项目过程中学习笔记
任务A 要做 count++(实际是读→加1→写回三个步骤) 如果这时候被更高优先级的任务B 抢占,B 也改 count,就会出现经典的“数据竞争/赛况”(race condition),最终 count 值错乱。临界区(Critical Section) 是多任务/实时操作系统(如 FreeRTOS)里非常核心的一个概念,一旦进入这个区域,其他任务就不能抢占、中断也不能进来捣乱,直到你自己跑完并
一、临界区
临界区(Critical Section) 是多任务/实时操作系统(如 FreeRTOS)里非常核心的一个概念,一旦进入这个区域,其他任务就不能抢占、中断也不能进来捣乱,直到你自己跑完并主动“退出”为止。
应用场景:
1、保护共享变量/数据 ;比如你有一个全局变量 int count = 0; 任务A 要做 count++(实际是读→加1→写回三个步骤) 如果这时候被更高优先级的任务B 抢占,B 也改 count,就会出现经典的“数据竞争/赛况”(race condition),最终 count 值错乱。
2、严格的硬件时序要求;DHT11 通信靠非常精确的高低电平时间(几十微秒级)来区分 0 和 1。 如果中途被任务切换或被定时器中断打断,哪怕只耽误 10μs,整个 bit 就读错了,数据直接废掉。 所以必须把“测高电平多久→判断是0还是1”这段代码锁死,不允许任何人插队。
3、调用不可重入的函数/操作外设;比如操作 I2C、SPI、LCD 驱动,如果中途被打断,下次进来状态就乱了。
实现代码:
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; // 自旋锁(ESP32 多核专用)
taskENTER_CRITICAL(&mux); // 进入临界区
// 这里写你绝对不能被打断的代码
// 比如读 DHT11 的 40 个 bit
taskEXIT_CRITICAL(&mux); // 退出临界区
内部实现过程:
1、关中断
2、在 ESP32(双核)上还会获取一个自旋锁(portMUX),防止另一个核同时进来
3、禁止任务调度(更高优先级任务也抢不到 CPU)
释放过程相反:释放锁、开中断、恢复调度
TIPS:临界区虽然强大,但越短越好!
spi_master源码阅读
/**
* @brief SPI common used frequency (in Hz)
* @note SPI peripheral only has an integer divider, and the default clock source can be different on other targets,
* so the actual frequency may be slightly different from the desired frequency.
*/
#define SPI_MASTER_FREQ_8M (80 * 1000 * 1000 / 10) ///< 8MHz
#define SPI_MASTER_FREQ_9M (80 * 1000 * 1000 / 9) ///< 8.89MHz
#define SPI_MASTER_FREQ_10M (80 * 1000 * 1000 / 8) ///< 10MHz
#define SPI_MASTER_FREQ_11M (80 * 1000 * 1000 / 7) ///< 11.43MHz
#define SPI_MASTER_FREQ_13M (80 * 1000 * 1000 / 6) ///< 13.33MHz
#define SPI_MASTER_FREQ_16M (80 * 1000 * 1000 / 5) ///< 16MHz
#define SPI_MASTER_FREQ_20M (80 * 1000 * 1000 / 4) ///< 20MHz
#define SPI_MASTER_FREQ_26M (80 * 1000 * 1000 / 3) ///< 26.67MHz
#define SPI_MASTER_FREQ_40M (80 * 1000 * 1000 / 2) ///< 40MHz
#define SPI_MASTER_FREQ_80M (80 * 1000 * 1000 / 1) ///< 80MHz
这些是推荐的 SPI 时钟频率(基于 APB 时钟 80MHz 的整数分频)
为什么不是精确 10MHz 而是 80M/8 = 10M?因为 ESP32 SPI 只能整数分频
设置设备标志位
#define SPI_DEVICE_TXBIT_LSBFIRST (1<<0) // 发送 LSB first
#define SPI_DEVICE_RXBIT_LSBFIRST (1<<1)
#define SPI_DEVICE_3WIRE (1<<2) // 单线半双工 (MOSI 复用收发)
#define SPI_DEVICE_POSITIVE_CS (1<<3) // CS 高有效(默认低有效)
#define SPI_DEVICE_HALFDUPLEX (1<<4) // 先发后收(默认全双工同时收发)
#define SPI_DEVICE_NO_DUMMY (1<<6) // 禁用自动 dummy bit 插入(高频读有风险)
3. 核心结构体:spi_device_interface_config_t(设备配置)
这是挂载一个从设备时最关键的结构体。
关键字段解释(按重要性排序):
| 字段 | 类型 | 典型值 | 含义 & 使用场景 |
|---|---|---|---|
| command_bits | uint8_t | 0 / 8 / 16 | 命令阶段位数(很多芯片有 1 字节命令,如 0x03 读、0x02 写) |
| address_bits | uint8_t | 0 / 8 / 24 | 地址阶段位数(闪存常用 24bit 地址) |
| dummy_bits | uint8_t | 0–16 | 地址 → 数据之间的空周期(高频读补偿 slave 延迟) |
| mode | uint8_t | 0–3 | SPI 模式(CPOL + CPHA):0 最常见,3 也很多 |
| clock_speed_hz | int | 1000000–40000000 | 时钟频率(用上面宏定义最安全) |
| spics_io_num | int | GPIO 号 / -1 | CS 引脚(-1 = 不使用硬件 CS,自己手动控制) |
| queue_size | int | 1–10 | 队列深度(中断模式下可同时排队几个事务,建议 ≥1) |
| flags | uint32_t | 0 或组合标志 | 上面那些 SPI_DEVICE_xxx 标志 |
| pre_cb / post_cb | 函数指针 | NULL 或回调 | 事务前/后回调(在中断里执行,常用于 GPIO 控制 DC/RS 线、CS 手动翻转) |
| input_delay_ns | int | 0 或 几十 ns | slave 输出延迟(高频 >8M 时建议设置,补偿 MISO 时序) |
小结:大多数简单 SPI 器件,只需要设置 mode、clock_speed_hz、spics_io_num、flags 就够了。复杂器件(如 NOR Flash、LCD)才会用到 command/address/dummy。
4. 事务描述结构体:spi_transaction_t(最常操作的)
每次发一次数据,就是填一个这个结构体。
关键字段:
- flags:SPI_TRANS_xxx(如 USE_TXDATA、USE_RXDATA、VARIABLE_CMD 等)
- cmd:命令值(长度由 device config 决定)
- addr:地址值
- length:总发送位数(bits!不是 bytes)
- rxlength:期望接收位数(全双工时可 < length)
- tx_buffer / tx_data[4]:发送数据(用 tx_data 时最多 32bit 直接内嵌)
- rx_buffer / rx_data[4]:接收缓冲
- user:用户自定义指针(常用来传上下文)
小技巧:
- 小数据(≤32bit) → 用 tx_data[] / rx_data[] + 对应 flag,省 DMA
- 大数据 → 用 buffer + DMA
二、缓冲区Buffer
缓冲区就是一块临时存放数据的内存区域,用来“缓冲”(缓和、暂存)数据在生产者和消费者之间速度不匹配的问题。
在LVGL中,缓冲区让 LVGL 可以“先把画面画好存起来,再一次性高效发给 LCD”,避免闪烁、撕裂、卡顿,同时支持部分刷新,极大提高 UI 流畅度和性能。
- LVGL 用 CPU(或 GPU)慢慢画 UI(按钮、文字、动画)
- LCD 通过 QSPI/DMA 很快就能把一整块像素数据显示出来
- 如果没有缓冲区:CPU 画一点推一点 → 画面会闪烁、撕裂(tearing)
- 有缓冲区:CPU 先把一整块画完存到 buf1 → 一次性 DMA 推给 LCD → 画面完整
实现双缓冲(double buffering)避免闪烁
- LVGL 同时用两块 buf:
- 当前显示用 buf1(LCD 正在从 buf1 读)
- CPU 在 buf2 上画下一帧
- 画完后交换:buf2 变成显示缓冲,buf1 给 CPU 画下一帧
- 这样用户永远看到完整的画面,不会看到“半成品”
static lv_color_t *buf1 = NULL; static lv_color_t *buf2 = NULL; lv_disp_draw_buf_init(&draw_buf, buf1, buf2, DISP_BUF_SIZE);
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)