Skip to content

练习题答案参考与思路

本文件提供各章思考题的参考答案,编程题只给思路,不给完整代码(自己写才能学会)


第一章思考题

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

1int(32位),如果 LED_PIN = 321 << 32 是未定义行为(UB)。 1ULLunsigned 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 TimerFreeRTOS xTimer
精度微秒级毫秒级(tick 精度)
回调执行专用高优先级任务Timer 服务任务
最小间隔~50μs1 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 设备

无法区分,会同时响应,数据冲突,通信失败。 解决方案:

  1. 选择地址可配置的设备(通过 ADDR 引脚设置)
  2. 用 I2C 多路复用器(如 TCA9548A)
  3. 用两条独立的 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

SPIFFSFAT
适合小文件、嵌入式 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(自动分包)。

个人知识库