经纬恒润一面
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 自我介绍。
- 讲一下四六级和高考成绩。
- 关于智能手表 MCU 上的数据,大概是怎么走的?
- 传感器的数据是怎么获取的?
- 讲一下 Tickless 和 STOP。
- 讲一下你是怎么给项目分层的?
- 异步 IO 和中断线程化是什么?
- 传感器在 IIC 上是怎么获取数据的?不同传感器怎么区分?
- 怎么避免 IIC 上传感器的数据冲突问题?
- 互斥锁是什么?
- 关于事件,你在项目中有没有做过相关的?
- IIC 是怎么传输数据的?讲一讲主机与从机的数据获取。
- 讲一讲串口。
- 讲一讲 SPI,以及项目中你是怎么用 SPI 的?
- 讲一讲 Ping Pong Buffer。
- 讲一下 DMA 的半满中断、全满中断、空闲中断。
- 讲一下 SPI 的四种模式。
- 项目中心率传感器和海拔测量这些刷新率,你是怎么设计的?
- FreeRTOS 的进程和线程你了解吗?
- 你画了原理图,有做出来实际的电路板吗?上拉电阻在原理图里面怎么画?
- 你有用过示波器和逻辑分析仪实际解决一些什么问题吗?
- static 的类型有哪些?
- volatile 做什么用?
- 讲一讲函数指针。
- 局部变量存在哪里?
- 你有操作过寄存器吗?
- 讲一讲怎么通过寄存器操作 GPIO?
- 重映射具体重映射的什么?
《参考解析》
先把智能手表的整条数据流串起来。 面试官用「数据怎么走」开场,想听的是链路而不是单点。典型链路是:传感器(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)。面试后半段面试官会介绍公司并询问城市意向(北京、天津、西安),这一问也值得提前想清楚再答。