拓竹嵌入式软件开发 二面 面经
1. 介绍一下你做过的最复杂的嵌入式项目,整体架构是怎么设计的?
答:以一个基于 STM32F407 + FreeRTOS 的 OTA 远程升级系统为例。
整体架构分三层:
- 硬件驱动层:UART(DMA收发)、SPI Flash、I2C EEPROM、GPIO
- OS 服务层:FreeRTOS 任务调度,包含 WiFi 通信任务、UART 数据处理任务、LED 状态任务、EEPROM 管理任务
- 应用层:OTA 升级协议解析、固件分包接收、Flash 存储管理、bootloader 跳转逻辑
关键设计决策:
- 使用 UART + DMA + 空闲中断接收 ESP8266 数据,避免 CPU 轮询,接收完成后通过二值信号量通知 WiFi 任务处理
- 固件数据先缓存到环形缓冲区,凑满一个扇区(4096字节)再写入 SPI Flash,减少擦写次数
- bootloader 和 APP 分区隔离,bootloader 固定在 Flash 起始地址,APP 从 0x08010000 开始,升级标志存 EEPROM,断电可恢复
- 任务间通信全部通过信号量和消息队列,不共享全局变量,避免竞态条件
2. 你在项目中用过 DMA 吗?遇到过什么问题,怎么解决的?
答:用过,主要用在 UART 收发和 SPI Flash 读写上。
遇到的典型问题:
问题一:DMA 接收不定长数据时不知道何时结束。解决:配合 UART 空闲中断,DMA 用循环模式接收,空闲中断触发时读取 DMA 剩余计数器,用总长度减去剩余量得到本次接收字节数,然后把数据搬到环形缓冲区,重启 DMA。
问题二:DMA 发送时,发送函数返回了但数据还没发完,下次调用又启动 DMA,导致数据错乱。解决:发送前检查上一次 DMA 是否完成(检查 DMA 使能状态),用互斥锁保护发送操作,确保上一次传输完成后再启动下一次。
问题三:DMA 缓冲区定义为局部变量,函数返回后栈帧被覆盖,DMA 还在往已经无效的地址写数据,导致 RAM 数据乱。解决:DMA 使用的缓冲区必须定义为全局或静态变量,保证生命周期覆盖整个 DMA 传输过程。
3. 介绍一下你参加过的竞赛,遇到的最难的技术问题是什么,怎么解决的?
答:参加过全国大学生电子设计竞赛(或蓝桥杯嵌入式赛道),以其中一个项目为例。
项目:基于 STM32 的无线数据采集与控制系统。
最难的问题:多路传感器数据采集时,ADC 采样和 UART 发送之间存在时序冲突,高速采样时数据丢失。
分析过程:
- 最初用中断方式采集 ADC,每次转换完成触发中断,在中断里直接发送数据
- 当采样率提高后,UART 发送速度跟不上 ADC 中断频率,中断嵌套导致数据覆盖
- 用示波器和逻辑分析仪抓波形,确认是发送时序问题
解决方案:
- ADC 改为 DMA 模式,连续采样结果直接搬到缓冲区,不占用 CPU
- 引入双缓冲机制,ADC 填充一个缓冲区时,CPU 处理另一个缓冲区的数据并发送
- UART 发送也改为 DMA 模式,发送期间 CPU 可以继续处理其他事务
- 最终采样率提升了3倍,数据零丢失
4. FreeRTOS 任务优先级如何分配?高优先级任务长期占用 CPU 会导致什么问题,如何避免?
答:优先级分配原则:
- 实时性要求高、响应时间短的任务优先级高,比如传感器数据采集、通信协议处理
- 后台任务、显示任务、日志任务优先级低
- 不要把所有任务设置成相同优先级,会退化成时间片轮转,失去实时性意义
- 中断服务相关的任务(被 ISR 唤醒的任务)优先级应该较高,保证及时处理
高优先级任务长期占用 CPU 的问题:
- 低优先级任务得不到执行,出现"饥饿"现象
- 如果低优先级任务负责喂狗,可能导致系统复位
- 如果低优先级任务持有某个资源,高优先级任务等待该资源时形成优先级反转
避免方法:
- 高优先级任务执行完关键操作后主动调用
vTaskDelay或等待信号量,让出 CPU - 避免在高优先级任务里做耗时操作,耗时操作放到低优先级任务里异步处理
- 使用
uxTaskGetStackHighWaterMark和 CPU 使用率统计,定期检查各任务的资源占用情况 - 合理使用互斥锁的优先级继承机制
5. 你的 OTA 升级方案中,如果升级过程中突然断电,如何保证系统能正常恢复?
答:设计思路是:升级状态持久化 + 旧固件保留 + 完整性校验。
具体流程:
- 升级开始前,在 EEPROM 里写入升级标志和固件总大小,这两个信息在断电后不丢失
- 固件数据暂存在外部 SPI Flash,不直接写内部 Flash,旧的 APP 固件在整个过程中保持完整
- 所有数据接收完毕后,先做 CRC 校验,校验通过才允许进入下一步
- 校验通过后触发系统复位,bootloader 启动时读取 EEPROM 里的标志
- 如果标志有效,bootloader 从 SPI Flash 把新固件搬运到内部 Flash,搬运完成后清除 EEPROM 标志,跳转执行新固件
- 如果标志无效(正常启动或上次升级未完成),直接跳转执行现有固件
断电场景分析:
- 数据接收阶段断电:EEPROM 标志未写入或标志无效,bootloader 直接跳转旧固件,等待重新升级
- 数据接收完成但复位前断电:同上,标志未写入,跳旧固件
- bootloader 搬运过程中断电:内部 Flash 数据不完整,但 SPI Flash 里的数据还在,重新上电后 bootloader 检测到标志有效,重新搬运即可
- 搬运完成但清除标志前断电:bootloader 再次搬运,覆盖写入,幂等操作,不影响结果
6. 环形缓冲区在你的项目中是如何使用的?多任务环境下如何保证线程安全?
答:使用场景:
- UART 接收:ISR 里把 DMA 搬来的数据写入环形缓冲区,任务里从环形缓冲区读取并处理,解耦生产者(ISR)和消费者(任务)
- OTA 固件接收:把每包固件数据写入环形缓冲区,凑满一个扇区后批量写入 SPI Flash,减少 Flash 擦写次数
线程安全问题:
- ISR 写、任务读的场景:如果是单核 MCU,ISR 不会被任务打断,任务可能被 ISR 打断。读操作(任务)需要在修改读指针前关中断或进临界区,写操作(ISR)本身是原子的不需要额外保护
- 任务写、任务读的场景:需要用互斥锁保护,或者用 FreeRTOS 的队列代替环形缓冲区,队列内部已经处理了线程安全
- 注意读写指针的更新顺序:先写数据再更新写指针,先读数据再更新读指针,防止另一端看到不完整的数据
实际项目中的做法:ISR 和任务之间的环形缓冲区,写操作在 ISR 里,读操作在任务里,读之前关中断保护读指针更新,操作完立即开中断,临界区尽量短。
7. 你用过逻辑分析仪或示波器调试嵌入式问题吗?举一个具体的例子。
答:用过,举一个 SPI 通信调试的例子。
问题现象:SPI Flash 读出的数据偶尔出错,不是每次都复现,概率性失败。
调试过程:
- 先用软件打印读出的数据和期望值,确认确实是读数据错误,不是上层逻辑问题
- 用逻辑分析仪抓 SPI 的 SCK、MOSI、MISO、CS 四根线的波形
- 分析仪解码后发现,CS 拉低到第一个 SCK 上升沿之间的建立时间不够,偶尔第一个 bit 采样到的是无效数据
- 对比 Flash 芯片手册,确认 CS 建立时间要求是 5ns,而代码里 CS 拉低后立即开始 SPI 传输,没有任何延时
解决:在 CS 拉低后加一个 NOP 指令延时,保证满足建立时间要
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
这是一个全面的嵌入式面试专栏。主要内容将包括:操作系统(进程管理、内存管理、文件系统等)、嵌入式系统(启动流程、驱动开发、中断管理等)、网络通信(TCP/IP协议栈、Socket编程等)、开发工具(交叉编译、调试工具等)以及实际项目经验分享。专栏将采用理论结合实践的方式,每个知识点都会附带相关的面试真题和答案解析。



