《MCU职位》面试问题
1. 中断何如设计
- 中断服务函数(ISR)应该尽可能简短,尽量只做通知
- 避免在中断中使用阻塞操作
- 在中断和主程序之间共享的变量必须用 volatile
- 当多个中断或中断与主程序访问共享资源时,需要保护
- 中断优先级管理
- 及时清除中断标志
- 避免递归中断
- 确保足够堆栈
2. 如何通过DMA读取大量数据,避免丢包
- 使用循环模式:确保DMA配置为循环模式,双缓冲方案
- 足够大的缓冲区:根据数据速率和处理能力设置合适的缓冲区大小
- 及时处理数据:在主循环中及时处理DMA收集的数据
- 软件硬件流控制
- 错误检测:实现DMA错误中断处理
- 优先级设置:为DMA中断设置合适的优先级
- 内存管理:确保DMA缓冲区不在缓存行边界上,内存对齐
3. freertos 如何边界区处理
- 保护临界区,即那些必须完整执行、不能被打断的代码段
- 可以考虑使用信号量(Semaphore) 或互斥量(Mutex) 来进行任务间的同步与资源共享
- 任务级 taskENTER_CRITICAL() / taskEXIT_CRITICAL()
- 中断级 taskENTER_CRITICAL_FROM_ISR() / taskEXIT_CRITICAL_FROM_ISR()
- 避免阻塞,保持精简
4. 死锁
- 双锁,解决方法:两个任务都按照同一个顺序A-B
void Task1(void *pvParameters)
{while(1){xSemaphoreTake(xMutexA, portMAX_DELAY); // 先获取AvTaskDelay(pdMS_TO_TICKS(1)); // 增加死锁概率xSemaphoreTake(xMutexB, portMAX_DELAY); // 再请求B// 访问资源A和Bprintf("Task1 critical section\n");xSemaphoreGive(xMutexB);xSemaphoreGive(xMutexA);}
}void Task2(void *pvParameters)
{while(1){xSemaphoreTake(xMutexB, portMAX_DELAY); // 先获取BvTaskDelay(pdMS_TO_TICKS(1)); // 增加死锁概率xSemaphoreTake(xMutexA, portMAX_DELAY); // 再请求A// 访问资源A和Bprintf("Task2 critical section\n");xSemaphoreGive(xMutexA);xSemaphoreGive(xMutexB);}
}
- 自锁,解决方法:超时
void Task(void *pvParameters)
{xSemaphoreTake(xMutex, portMAX_DELAY); // 第一次获取// 某些条件下再次尝试获取同一个互斥量if(some_condition) {xSemaphoreTake(xMutex, portMAX_DELAY); // 第二次获取 - 死锁!}xSemaphoreGive(xMutex);
}
- 获取资源组,一次获取所有的锁
5. 如何解决遇到难处理问题
- 看门狗是否复位
- 计算错误、逻辑流程、数据损坏、通信出错
- 时好时坏、稳定复现
- 日志
- 调试器甚至逻辑分析仪
- 中断、互斥量、越界、空指针、野指针
- 函数重入
- 调试器PC
- 时钟
- 中断服务函数过长、优先级、未清除中断标志、阻塞
- 去除不相关的功能最小排查
- 硬件:电源、时钟、复位、焊接、电磁
6. 项目中遇到的最难的问题,如何解决?
7. 时钟树都有什么
- 选择时钟源HSI/E LSI/E
- PLL设置
- 分频器设置
8. GPIO
- 推挽输出:可以主动输出高电平(推)和低电平(挽)。驱动能力强,适合驱动LED、控制继电器等。
- 开漏输出:只能主动拉低电平,高电平需要外部上拉电阻。方便连接不同电压等级的器件。适合I2C等总线通信。
- 上拉/下拉输入:在引脚悬空时,通过内部电阻给一个确定的电平,防止误触发。上拉默认高电平,下拉默认低电平。
9. 程序代码、常量、全局变量、局部变量、堆栈分别存储在MCU的哪个区域?
- Flash/ROM:存储程序代码(.text)和常量(如const变量)。
- RAM:
- data数据段:存储已初始化的全局变量和静态变量。
- BSS段:存储未初始化的全局变量和静态变量(启动时清零)。
- 堆:动态分配的内存(malloc/free),从低地址向高地址增长。
- 栈:局部变量、函数参数、返回地址等,从高地址向低地址增长。
关键点:栈溢出会覆盖堆或其他数据区域,导致不可预知的错误,非常危险。
10. 什么是内存对齐?为什么需要内存对齐?
- 要求数据在内存中的地址必须是其本身大小的整数倍。一个 uint32_t 变量,其地址最好是4的倍数。
- 不对齐的内存访问可能需要多次总线操作,降低效率。Cortex-M的硬件不支持非对齐访问。
11. 如何让MCU进入低功耗模式?有什么注意事项?
- Sleep, Stop, Standby(功耗依次降低,唤醒时间依次增长)。
- 进入前必须配置好唤醒源(如外部中断、RTC闹钟)。
- 进入前关闭不必要的外设时钟和功能以节省功耗。
- 将未使用的IO引脚设置为模拟输入模式,避免漏电流。
12. 如何优化MCU程序的性能和效率?
- 编译器优化:使用 -O2 或 -Os(优化代码大小)等级别。
- 算法与数据结构:选择时间/空间复杂度更优的算法。
- 减少函数调用开销:对性能关键的短小函数使用 inline 内联\宏函数。
- 查表法代替实时计算:如CRC、三角函数等。
- 使用DMA:将CPU从繁重的数据搬运工作中解放出来。
13. 软件版本管理的
- 主分支main/master、开发分支develop、功能分支feature/xxx、修复分支hotfix/xxx。为发布版本打Tag
14. 在团队协作中,如何保证代码质量
- 相互审查代码
- 使用工具检查代码
- 编写测试用例
15. volatile关键字有什么用
- 告诉编译器,这个变量可能会被意想不到地改变,禁止对其进行优化(如缓存到寄存器),每次都必须从内存中读取。
- 在中断服务程序和主循环之间共享的全局变量。
- 在多线程环境(如RTOS)中共享的全局变量。