拓竹嵌入式软件开发 一面 面经

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编程等)、开发工具(交叉编译、调试工具等)以及实际项目经验分享。专栏将采用理论结合实践的方式,每个知识点都会附带相关的面试真题和答案解析。

全部评论

相关推荐

评论
1
2
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务