Appearance
练习题答案参考与思路
本文件提供各章思考题的参考答案,编程题只给思路,不给完整代码(自己写才能学会)
第一章思考题
1. vTaskDelay vs sleep(1) 的区别
sleep(1) 是 POSIX 标准函数,在嵌入式 RTOS 中会忙等待(占用 CPU 不释放),导致其他任务无法运行。
vTaskDelay(pdMS_TO_TICKS(1000)) 会将当前任务置为 Blocked 状态,让出 CPU 给其他任务,1 秒后由调度器唤醒。这是 RTOS 的核心机制。
2. 1MB 图片放哪里
放在 外部 PSRAM(8MB) 中。通过 heap_caps_malloc(size, MALLOC_CAP_SPIRAM) 分配。内部 SRAM 只有 512KB,放不下。
3. app_main() vs main()
相同:都是程序入口,都可以调用其他函数。
不同:
app_main()运行在 FreeRTOS 任务中(main_task),有栈大小限制app_main()返回后,main_task 会被删除,其他任务继续运行(不会退出程序)- PC 的
main()返回后程序退出
第二章思考题
1. 高电平亮 vs 低电平亮
- 高电平亮(GPIO → 限流电阻 → LED → GND):GPIO 输出高电平驱动 LED
- 低电平亮(VCC → 限流电阻 → LED → GPIO):GPIO 输出低电平时 LED 亮(灌电流)
低电平有效(灌电流)的优点:MCU 的灌电流能力通常比拉电流强,驱动能力更好。
2. 上拉电阻阻值
内部 45kΩ 上拉:上升沿较慢,不适合高速信号,但省电。 外部 10kΩ 上拉:上升沿更快,适合高速通信(I2C 等)。
阻值越小,上升沿越快,但静态电流越大(功耗增加)。需要权衡速度和功耗。
3. 同时上下拉
理论上会读到不确定值(取决于两个电阻的分压),实际约为 VCC/2 ≈ 1.65V,可能被识别为高或低。实际中不这样用,因为会增加功耗且状态不确定。
4. 1ULL << LED_PIN
1 是 int(32位),如果 LED_PIN = 32,1 << 32 是未定义行为(UB)。 1ULL 是 unsigned long long(64位),可以安全左移 0~63 位。 ESP32-S3 有 GPIO0~GPIO48,需要 64 位掩码。
第三章思考题
1. ISR 中不能用 ESP_LOGI
ESP_LOGI 内部会调用 printf,而 printf 可能需要获取互斥锁(mutex)。如果 ISR 打断了正在持有该锁的任务,就会发生死锁。另外 ISR 执行时间必须极短,printf 太慢。
强行调用会导致系统崩溃或死锁。
2. portYIELD_FROM_ISR 的作用
如果 ISR 中向队列发送数据唤醒了一个高优先级任务,portYIELD_FROM_ISR 会在 ISR 返回后立即切换到该高优先级任务,而不是等到下一个时间片。
不加这行:程序仍能工作,但高优先级任务的响应会延迟到下一个调度时机(最多一个时间片,约 1ms)。
3. ESP Timer vs FreeRTOS 软件定时器
| ESP Timer | FreeRTOS xTimer | |
|---|---|---|
| 精度 | 微秒级 | 毫秒级(tick 精度) |
| 回调执行 | 专用高优先级任务 | Timer 服务任务 |
| 最小间隔 | ~50μs | 1 tick(1ms) |
| 适合场景 | 高精度定时 | 普通超时、周期任务 |
4. LEDC 13位分辨率
13位 = 8192 级,比 8位(256级)细腻 32 倍,LED 亮度变化更平滑,尤其在低亮度时不会出现明显跳变。
分辨率越高,最低可设置的频率越低(分辨率 × 频率 ≤ 时钟频率)。不是越高越好,需要根据频率需求选择。
第四章思考题
1. 115200 波特率传输速率
115200 bps,每帧 10 位(1起始+8数据+1停止),实际字节率 = 115200/10 = 11520 字节/秒。 传输 1KB = 1024 字节需要约 89ms。
2. TX 缓冲区 0 vs 1024
- TX 缓冲区 = 0:
uart_write_bytes同步发送,等待所有数据发完才返回(阻塞) - TX 缓冲区 = 1024:
uart_write_bytes把数据放入缓冲区立即返回,后台异步发送(非阻塞)
需要 TX 缓冲区的场景:发送大量数据时不想阻塞任务。
3. 波特率差 1% 的影响
UART 是异步通信,靠起始位同步。每帧 10 位,1% 误差 = 0.1 位偏移/帧。 累积到第 10 位时偏移 1 位,可能采样到错误位置,导致数据错误。 通常允许误差 < 2%,1% 一般没问题,但接近极限。
4. 事件驱动 vs 轮询
| 事件驱动 | 轮询 | |
|---|---|---|
| CPU 占用 | 低(有数据才处理) | 高(持续检查) |
| 响应延迟 | 低 | 取决于轮询间隔 |
| 代码复杂度 | 较高 | 简单 |
| 适合场景 | 数据量不确定 | 数据量固定、简单场景 |
第五章思考题
1. I2C 上拉电阻
I2C 是开漏(Open-Drain)总线,设备只能拉低,不能主动拉高。上拉电阻负责将总线拉回高电平。
不加上拉:总线无法回到高电平,通信失败。
内部 45kΩ vs 外部 4.7kΩ:高速通信(400kHz+)时,总线电容(走线+设备)需要更快充电,小阻值上拉充电更快,上升沿更陡,信号质量更好。
2. SPI 比 I2C 快,为何还用 I2C
- I2C 只需 2 根线(SDA+SCL),SPI 需要 4 根(MOSI+MISO+SCLK+CS)
- I2C 支持多设备共享总线(靠地址区分),SPI 每设备需要独立 CS 线
- 低速、短距离、多设备场景 I2C 更经济
3. 两个相同地址的 I2C 设备
无法区分,会同时响应,数据冲突,通信失败。 解决方案:
- 选择地址可配置的设备(通过 ADDR 引脚设置)
- 用 I2C 多路复用器(如 TCA9548A)
- 用两条独立的 I2C 总线
4. SPI DMA vs CPU 传输
DMA(Direct Memory Access):数据直接在内存和外设之间传输,不经过 CPU。
- CPU 传输:CPU 逐字节搬运,占用 CPU 时间
- DMA 传输:CPU 只需配置,传输过程 CPU 可做其他事
大数据量(>几十字节)时用 DMA 效率更高。
第六章思考题
1. ADC2 与 Wi-Fi 冲突
ADC2 的参考电压电路与 Wi-Fi RF 模块共用部分硬件资源。Wi-Fi 工作时会干扰 ADC2 的参考电压,导致读数不准确甚至失败。
解决方案:使用 ADC1(GPIO1~GPIO10),或使用外部 ADC 芯片(如 ADS1115)。
2. 12位 ADC 分辨率
3300mV / 4096 ≈ 0.8mV/LSB。测量 1mV 变化需要约 1.25 LSB,理论上够用,但实际 ADC 噪声通常有几个 LSB,实际精度约 2~5mV。
3. 多次采样取平均
这是过采样技术。随机噪声在多次采样中正负抵消,平均后噪声降低。 取 N 次平均,信噪比提高 √N 倍,等效分辨率提高 log₂(√N) = 0.5×log₂(N) 位。 取 16 次平均:提高 0.5×log₂(16) = 2 位分辨率。
4. PWM 模拟 DAC 的缺点
- 输出含有 PWM 频率的纹波,需要低通滤波器
- 滤波器引入延迟,响应速度慢
- 精度受 PWM 分辨率限制
- 不适合音频等需要高精度、快速变化的场合
第七章思考题
1. vTaskDelay 期间 CPU 在做什么
任务进入 Blocked 状态,CPU 切换到其他 Ready 状态的任务执行。如果没有其他任务,CPU 执行空闲任务(Idle Task),可以在此时进行低功耗操作。
2. Mutex vs Binary Semaphore
二值信号量:任何任务都可以 Give,不关心谁 Take 了。 互斥量:必须由 Take 的任务来 Give(所有权概念)。
优先级反转:低优先级任务持有互斥量,高优先级任务等待,中优先级任务抢占低优先级任务,导致高优先级任务被间接阻塞。
互斥量通过优先级继承解决:持有互斥量的低优先级任务临时提升到等待者的优先级,防止被中优先级任务抢占。
3. 队列深度
太小:生产者频繁丢弃数据,消费者来不及处理。 太大:占用过多内存(每个元素都占内存)。
经验值:根据生产速率和消费速率的差异决定,通常 5~20 个元素。
4. xTaskCreate vs xTaskCreatePinnedToCore
xTaskCreate:任务可以在任意核心上运行,调度器决定。 xTaskCreatePinnedToCore:固定在指定核心上运行。
适合固定核心的任务:
- 使用不支持多核的外设驱动
- 对实时性要求极高的任务(固定在 Core 0,避免 Wi-Fi 干扰)
- Wi-Fi/BT 协议栈(通常固定在 Core 0)
5. 栈大小 2048 字节
不一定够。栈大小取决于:局部变量大小、函数调用深度、是否用 printf(约需 512B)。
检测方法:uxTaskGetStackHighWaterMark(NULL) 返回历史最小剩余栈空间。如果接近 0,需要增大栈。
第八章思考题
1. 为什么必须 nvs_commit()
NVS 写操作先写入 RAM 缓冲区,nvs_commit() 才真正写入 Flash。 不调用:断电后数据丢失(只在 RAM 中)。
2. 磨损均衡
Flash 每个扇区擦写次数有限。NVS 通过磨损均衡算法,将写操作分散到不同扇区,避免某个扇区过早损坏。类似于轮换使用多个存储位置。
3. SPIFFS vs FAT
| SPIFFS | FAT | |
|---|---|---|
| 适合 | 小文件、嵌入式 Flash | 大文件、SD 卡 |
| 目录支持 | 有限(模拟) | 完整 |
| 磨损均衡 | 内置 | 需要额外层 |
| 性能 | 较慢(无缓存) | 较快 |
| 兼容性 | 仅 ESP-IDF | 标准,PC 可读 |
4. NVS 分区 16KB 能存多少
每个键值对约占 32~64 字节(含元数据)。16KB ≈ 250~500 个键值对。 需要更多:在分区表中增大 NVS 分区,或使用 SPIFFS 存储大量配置。
第九章思考题
1. 事件组 vs 轮询
轮询需要任务持续运行检查状态,浪费 CPU。 事件组让任务阻塞等待,CPU 可以做其他事,响应更及时,代码更清晰。
2. MQTT QoS
- QoS 0:最多一次,不保证送达(fire and forget)
- QoS 1:至少一次,保证送达但可能重复
- QoS 2:恰好一次,保证且不重复(开销最大)
传感器数据上报:通常用 QoS 1,允许偶尔重复,但不能丢失。
3. Wi-Fi + ADC2 冲突
见第六章思考题1。
4. HTTP vs MQTT
HTTP:请求-响应模式,每次通信需要建立连接,延迟较高(100ms~秒级)。 MQTT:长连接,消息推送,延迟低(10~50ms)。
实时控制(< 100ms):选 MQTT。
第十章思考题
1. BLE vs 经典蓝牙
| BLE | 经典蓝牙 | |
|---|---|---|
| 功耗 | 极低(μA 级) | 较高(mA 级) |
| 速率 | 低(1~2 Mbps) | 高(最高 3 Mbps) |
| 适合 | 传感器、IoT | 音频、文件传输 |
| 连接延迟 | 低 | 较高 |
ESP32-S3 支持 BLE 5.0,不支持经典蓝牙(BR/EDR)。
2. 广播间隔权衡
广播间隔 20ms:发现快,功耗高(约 1mA) 广播间隔 1000ms:发现慢,功耗低(约 20μA)
建议:需要快速发现时用短间隔,发现后连接,连接后可关闭广播。
3. Notification vs Indication
- Notification:服务端推送,客户端不需要确认,可能丢失
- Indication:服务端推送,客户端必须确认(ACK),可靠但慢
实时数据流(如传感器):Notification(速度优先) 重要命令:Indication(可靠性优先)
4. MTU 协商
MTU(Maximum Transmission Unit):单次传输的最大字节数。 默认 MTU = 23 字节(有效数据 20 字节)。 协商后最大 517 字节(BLE 5.0)。
发送超过 MTU 的数据:需要在应用层分包,接收端重组。 或使用 BLE 5.0 的 Extended Data Length(自动分包)。