面灵AI→

经纬恒润一面

轮次
一面
时间
2026-10
来源
牛客网

《面试题目》

  1. 自我介绍。
  2. 讲一下四六级和高考成绩。
  3. 关于智能手表 MCU 上的数据,大概是怎么走的?
  4. 传感器的数据是怎么获取的?
  5. 讲一下 Tickless 和 STOP。
  6. 讲一下你是怎么给项目分层的?
  7. 异步 IO 和中断线程化是什么?
  8. 传感器在 IIC 上是怎么获取数据的?不同传感器怎么区分?
  9. 怎么避免 IIC 上传感器的数据冲突问题?
  10. 互斥锁是什么?
  11. 关于事件,你在项目中有没有做过相关的?
  12. IIC 是怎么传输数据的?讲一讲主机与从机的数据获取。
  13. 讲一讲串口。
  14. 讲一讲 SPI,以及项目中你是怎么用 SPI 的?
  15. 讲一讲 Ping Pong Buffer。
  16. 讲一下 DMA 的半满中断、全满中断、空闲中断。
  17. 讲一下 SPI 的四种模式。
  18. 项目中心率传感器和海拔测量这些刷新率,你是怎么设计的?
  19. FreeRTOS 的进程和线程你了解吗?
  20. 你画了原理图,有做出来实际的电路板吗?上拉电阻在原理图里面怎么画?
  21. 你有用过示波器和逻辑分析仪实际解决一些什么问题吗?
  22. static 的类型有哪些?
  23. volatile 做什么用?
  24. 讲一讲函数指针。
  25. 局部变量存在哪里?
  26. 你有操作过寄存器吗?
  27. 讲一讲怎么通过寄存器操作 GPIO?
  28. 重映射具体重映射的什么?

《参考解析》

先把智能手表的整条数据流串起来。 面试官用「数据怎么走」开场,想听的是链路而不是单点。典型链路是:传感器(PPG 心率、六轴 IMU、气压计)按各自接口挂在 MCU 上(PPG 与 IMU 多走 SPI,气压计走 I2C 更省引脚),驱动在中断或 FIFO 水线触发时把采样搬进内存缓冲区,算法任务(心率解算、计步、海拔换算)再从缓冲区取数,结果进 UI 与 BLE 上报。工程上的取舍有三条:采集与算法解耦,中断里只做搬运、绝不做浮点解算;用双缓冲或环形缓冲把「生产者写」和「消费者读」分开;采样率与刷新率分开设计——采样率高是为了算法精度与抗混叠,上报与显示刷新率低是为了省电和减流量。第 18 问的答案就落在最后这条上:心率需要高频采样才能算准,但显示每秒刷新一次就够,两者不该绑成一个周期。

Tickless 与 STOP 低功耗。 RTOS 默认每个系统节拍都唤醒一次 CPU,没事干也在漏电;Tickless Idle 的做法是在确认下一个到期任务之前还有很长时间时,先关掉 SysTick 让 CPU 进入深度睡眠,醒来后把睡过去的时间补回系统时基,保证任务延时语义不变。STOP 是 STM32 的低功耗模式之一:内核时钟停、外设可选择性保留,靠使能过的中断(EXTI、RTC 闹钟、串口唤醒)退出,退出后继续执行而不是复位。两者配合最容易踩的坑:唤醒源必须使能且清标志,否则进不去或反复唤醒;唤醒后通常要重新配置时钟(PLL 被关掉过);DMA 和部分外设在 STOP 下时钟关闭,待发数据要先落地再睡;调试时 SWD 会阻止进入低功耗,测电流必须真的断开调试器。

I2C 上多传感器的区分与冲突。 I2C 是主从结构,SCL/SDA 开漏加外部上拉,时序是 START → 7 位地址加读写位 → ACK/NACK → 数据 → STOP,所以地址相同就等于撞车。同型号传感器靠 ADDR/SA0 引脚改地址,或者用 I2C 多路复用器(TCA9548 一类)把总线分段,再或者拆成两条软件 I2C。总线冲突有两类:多主竞争靠仲裁与时钟拉伸解决,但从机不会自己让路,所以最稳的是 MCU 单主 + 把同一总线的访问串行化——一根总线一把锁,或让一个总线任务统一收发、其他任务只投递请求。细节上:读寄存器是「写地址 → Restart → 读」的复合时序;从机把 SDA 拉死时用 9 个时钟脉冲或硬件复位恢复;上拉电阻与总线电容决定最高速率,400kHz 以上必须先算容性负载。串口那一问同理,重点是波特率误差、DMA 加空闲中断收不定长帧、以及粘包半包的处理。

SPI 四种模式、Ping Pong Buffer 与 DMA 三类中断。 SPI 的四个模式是 CPOL(空闲电平)与 CPHA(采样边沿)的组合,模式 0 和模式 3 最常用;配错的典型现象是数据整体移位一位、恒为 0x00/0xFF 或者丢首 bit,用示波器看采样沿和数据的对齐关系一眼可判。SPI 没有应答机制,片选、时序、DMA 长度都要自己管,多从机时片选必须硬件或软件严格互斥。Ping Pong Buffer 是两块缓冲区轮换:DMA 写 A 时 CPU 处理 B,半满中断处理前半、全满中断处理后半,好处是不用等整块传完,代价是必须保证「处理速度 ≥ 采样速度」,所以工程上会加高水位、丢帧计数和溢出告警。串口的不定长帧靠空闲中断(IDLE)配合 DMA:DMA 负责搬、IDLE 表示一帧结束,两者一起才能既收定长也收变长,注意 IDLE 标志与 DMA 剩余计数要一起清、一起读。异步 IO 与中断线程化是 Linux 侧的对照概念:前者是 aio/io_uring 这类「发起后不等结果」的模型,后者是 request_threaded_irq 把下半部交给内核线程,好处是能睡眠、能拿互斥锁、能被调度,硬中断上下文里这些都不允许。

C 语言与寄存器层面的细节。 static 有三类用法:函数内的静态局部变量(作用域局部、生命周期全局、只初始化一次)、文件作用域的静态全局变量与函数(链接属性为内部,用来做模块私有、避免符号冲突)、以及 C++ 的静态成员。volatile 只保证每次访问都真的去内存读写,它不提供原子性、不提供内存屏障、也不解决多核 Cache 一致性;嵌入式里它主要用于硬件寄存器、中断与主循环共享的变量、DMA 修改的内存,而 DMA 加 D-Cache 的一致性必须靠 clean/invalidate 或者把缓冲区放到 non-cacheable 区。局部变量默认在栈上,开优化后可能被放进寄存器,所以会被中断改写的变量不加 volatile 会被优化掉。函数指针用于回调、状态机跳转表和驱动 ops 结构体。寄存器操作 GPIO 的顺序是 MODER 定模式、OTYPER 定推挽开漏、OSPEEDR 定速率、PUPDR 定上下拉,输出置位用 BSRR(低 16 位写 1 置位、高 16 位写 1 清位,单指令原子),需要读改写时先读 IDR/ODR 再写回。最后「重映射重映射的是什么」要分三层答:外设引脚重映射(AFIO 的 MAPR,把定时器、串口等复用功能挪到别的引脚)、中断向量表重映射(SCB->VTOR,bootloader 加 app 双区必备)、存储地址重映射(boot 引脚把 Flash 或系统存储器映射到 0x00000000)。面试后半段面试官会介绍公司并询问城市意向(北京、天津、西安),这一问也值得提前想清楚再答。