拓竹嵌入式软件开发 一面 面经
1. FreeRTOS 中任务栈溢出是如何检测的?configCHECK_FOR_STACK_OVERFLOW 的两种模式有什么区别?
答:FreeRTOS 提供两种栈溢出检测方式,通过 configCHECK_FOR_STACK_OVERFLOW 配置:
- 模式1:在任务切换时检查栈指针是否超出栈边界。速度快,但如果溢出后栈指针又回来了,可能漏检。
- 模式2:在任务创建时用固定值(0xA5)填充整个栈空间,切换时检查栈末尾的几个字节是否被改写。检测更可靠,但有轻微性能开销。
两种模式都会在检测到溢出时调用 vApplicationStackOverflowHook(),用户在这里处理错误(比如打印任务名、复位系统)。
实际项目中建议开模式2,调试阶段用 uxTaskGetStackHighWaterMark() 查看各任务的栈使用水位,合理分配栈大小。
2. volatile 关键字的作用是什么?在嵌入式开发中哪些场景必须使用?
答:volatile 告诉编译器,该变量的值可能在程序控制流之外被修改,每次访问都必须从内存中重新读取,不能被优化掉或缓存在寄存器里。
必须使用的场景:
- 内存映射的硬件寄存器,比如
__IO uint32_t *reg = (uint32_t*)0x40020000,每次读写都要实际访问硬件 - 中断服务函数(ISR)和主循环之间共享的标志变量,比如
volatile uint8_t flag,否则编译器可能把主循环里的读操作优化成只读一次 - DMA 传输的缓冲区,CPU 不直接写,但 DMA 会修改内存内容
- 多核或多任务环境下的共享变量(注意 volatile 不能替代互斥锁,只保证可见性,不保证原子性)
3. 全局变量、静态全局变量、局部变量的存储区域及生命周期差异是什么?
答:
- 全局变量:存放在
.data(有初始值)或.bss(初始值为0)段,程序启动时分配,整个程序运行期间有效,所有文件可见 - 静态全局变量:同样在
.data/.bss段,生命周期与全局变量相同,但作用域限制在当前文件内,其他文件无法通过extern访问,用于模块内部封装 - 局部变量:存放在栈上,函数调用时分配,函数返回时释放,作用域仅在函数内部
- 静态局部变量:存放在
.data/.bss段,生命周期是整个程序运行期间,但作用域仍限于函数内部,只在第一次调用时初始化
嵌入式中需注意:栈空间有限,大数组不要定义为局部变量;.bss 段在启动文件中会被清零,.data 段会从 Flash 复制到 RAM。
4. STM32 的 DMA 传输完成后,CPU 如何得知传输结束?DMA 与中断配合使用时需要注意什么?
答:DMA 传输完成后通知 CPU 的方式:
- 轮询:主循环里不断检查
DMA_GetFlagStatus(),简单但浪费 CPU - 中断:使能
DMA_IT_TC(传输完成中断),传输完成后触发 ISR,效率高,是常用方式 - 半传输中断
DMA_IT_HT:传输到一半时触发,适合双缓冲乒乓操作
配合使用注意事项:
- 在 ISR 里清除标志位,否则会持续触发中断
- DMA 缓冲区必须是全局或静态变量,不能是局部变量(栈上),否则 DMA 传输期间栈帧可能被覆盖
- 使用
volatile修饰缓冲区或在读取前加内存屏障,防止编译器优化导致读到旧数据 - 循环模式下注意数据覆盖问题,处理速度要跟上 DMA 填充速度
5. I2C 多主仲裁的机制是什么?
答:I2C 总线支持多主设备,当两个主设备同时发起通信时,通过仲裁机制决定谁获得总线控制权。
仲裁基于"线与"特性:SDA 是开漏结构,任何设备拉低都会使总线为低。仲裁过程:
- 两个主设备同时发送起始条件和地址
- 每发送一位后,主设备读回总线上的实际电平
- 如果某主设备发送的是高电平,但读回的是低电平,说明另一个主设备拉低了总线,当前主设备仲裁失败,立即停止发送,退出总线
- 发送低电平的主设备不受影响,继续传输,最终获得总线
仲裁失败的主设备会等待总线空闲后重试。整个过程对数据透明,不会产生错误数据。
实际使用中还需注意时钟同步:多主设备的 SCL 也是线与,低电平持续时间取所有主设备中最长的那个。
6. 内存泄漏的原因有哪些?在嵌入式裸机和 RTOS 环境下分别如何排查?
答:内存泄漏的原因:
malloc之后忘记free,或者free的指针不是malloc返回的原始指针- 异常退出路径(return、break)跳过了
free - 循环中反复申请内存但只释放一次
- 指针被覆盖,原来指向的内存块找不到了
裸机环境排查:
- 嵌入式裸机一般不建议频繁使用动态内存,堆碎片化问题严重
- 可以重写
malloc/free,加计数器统计申请和释放次数 - 监控堆的使用水位,定期打印剩余堆大小
RTOS 环境排查:
- FreeRTOS 提供
xPortGetFreeHeapSize()查看剩余堆,xPortGetMinimumEverFreeHeapSize()查看历史最小值 - 使能
configUSE_MALLOC_FAILED_HOOK,在申请失败时触发钩子函数 - 任务创建后用
uxTaskGetStackHighWaterMark()检查栈使用情况 - 优先使用静态分配(
xTaskCreateStatic),避免动态内存问题
7. SPI 通信中 CPOL 和 CPHA 的含义,四种模式分别对应什么?
答:
- CPOL(时钟极性):定义 SCK 空闲时的电平。CPOL=0 空闲低电平,CPOL=1 空闲高电平
- CPHA(时钟相位):定义数据采样的时机。CPHA=0 在第一个时钟边沿采样,CPHA=1 在第二
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
这是一个全面的嵌入式面试专栏。主要内容将包括:操作系统(进程管理、内存管理、文件系统等)、嵌入式系统(启动流程、驱动开发、中断管理等)、网络通信(TCP/IP协议栈、Socket编程等)、开发工具(交叉编译、调试工具等)以及实际项目经验分享。专栏将采用理论结合实践的方式,每个知识点都会附带相关的面试真题和答案解析。

