面灵AI→

泰凌微嵌入式一面:24 问覆盖 RTOS 与通信

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

《面试题目》

  1. 实习中 10ms、100ms 的任务如何保证准确?1ms 的任务怎么保证准?10ms 和 100ms 任务之间有通信吗?
  2. 实习用到 FreeRTOS 了吗?
  3. 中断与共享内存是如何实现的?core0 和 core1 的通信方式是什么?
  4. 怎么确保任务栈够用?
  5. 单元测试和静态测试怎么做的、怎么测的?
  6. CAN 和 CAN-FD 熟悉吗?CAN 的多设备怎么仲裁?ID 号大的数据会丢吗?
  7. ADC 采集电源电压,12V 电源会烧吗?热敏电阻采集的是电压还是电阻?
  8. SPI 的特点是什么?看门狗怎么用?
  9. 10ms 任务被其他任务抢占了导致没法喂狗怎么处理?如果 100ms 的任务代码写得不好、死循环也不触发喂狗呢?
  10. 有没有统计每个任务的执行时间、1s 内执行了几次?
  11. TCP 以太网的移植是系统自带的还是自己搭的?
  12. 芯片的启动流程是什么?
  13. 上电了但是芯片无法运行,怎么排查?
  14. FIFO、FMC、DMA 的实现
  15. EEPROM 的使用,硬件 I2C 还是软件 I2C,为什么不用 flash 模拟 EEPROM?
  16. UART 的大小端和字节序,0x55 在总线上是怎么样的?UART 通信没收到怎么排查?
  17. 为什么需要使用硬件看门狗和软件看门狗?
  18. CPU 利用率是怎么统计的?
  19. Bootloader 的设计、断电保护设计
  20. 低功耗设计
  21. 大循环和小循环,break、continue、return 的区别
  22. 大端和小端的区别,0x12345678 在地址上怎么分布?
  23. 英语口语:介绍大学生活,学到了什么
  24. 英语口语:介绍一门课程

《参考解析》

周期任务怎么保证准时

RTOS 里不要用 vTaskDelay 去凑周期(它会累积漂移),要用 vTaskDelayUntil 以绝对时间为基准。更准的做法是靠硬件定时器中断触发,任务只在信号量或任务通知上等;1ms 级的任务最好放在定时器中断里做最轻量的部分,重活交给任务。优先级分配上按周期单调调度:周期越短优先级越高,1ms > 10ms > 100ms,这样短周期任务不会被长任务阻塞。10ms 和 100ms 任务之间的通信走队列、任务通知或事件组,不要用全局变量裸传。

喂狗和任务抢占

喂狗本身是一个强实时动作,不能让低优先级任务负责。做法是把喂狗放在最高优先级的任务里,或者对喂狗做「所有关键任务都打卡才算喂」的看门狗矩阵:每个任务有自己的计数位,喂狗任务检查所有位都被置位后才喂狗并清零,这样任何一个任务卡死都会让狗超时复位。任务被抢占导致喂不上狗,本质是优先级或时间片分配不合理,要缩短临界区和长耗时操作。另一个坑是用硬件看门狗加独立时钟源,避免系统时钟挂了连狗都停摆。更细的场景可以用窗口看门狗,喂太早也会复位,能抓出「循环跑飞但每次都喂狗」的情况。

核间通信与中断

多核(如 core0 和 core1)通信的通用方案是共享内存加核间中断:先在内存里约定一块区域和数据结构(环形缓冲或邮箱),发送方写入数据、加内存屏障保证顺序,然后触发对方的核间中断(IPI)或写 mailbox 寄存器;接收方在中断里只取走指针、真正的解析放到任务里做。同步要靠自旋锁或硬件信号量保护共享区,并注意 cache 一致性(必要时用非 cache 区域或手动 clean/invalidate)。中断和共享内存的关系在于:中断是通知机制,共享内存是数据通道,两者配合才能做到「中断里做最少的事」。

任务栈和 CPU 利用率怎么算

任务栈大小不能靠猜。可以先静态估算(函数调用深度、局部变量、嵌套中断的上下文),再用运行时的水位线验证:FreeRTOS 的 uxTaskGetStackHighWaterMark 返回历史最小剩余量,或者创建任务时把栈填成固定字节(如 0xA5)再扫描未被改动的区域。留 20% 到 30% 余量,中断和 printf 这类函数吃栈很凶。

CPU 利用率有两种统计口径:一是空闲任务计数器(vTaskGetRunTimeStats 打开后统计每个任务占用 CPU 的 tick 数),二是硬件周期计数器(DWT->CYCCNT 或定时器)测量每个任务的执行时间。要回答「1s 内执行了几次」,就在任务入口做计数并每秒清零。

CAN 仲裁与 CAN-FD

CAN 2.0 用非破坏性逐位仲裁:总线上显性位(0)会覆盖隐性位(1),所以 ID 越小、越早出现显性位的报文优先级越高。仲裁失败的节点会自动退让并在总线空闲时重发,因此ID 大的数据不会丢,只是延迟变大;真正会丢的是发送错误计数溢出导致节点进入错误被动或总线关闭,需要恢复流程。CAN-FD 的区别是数据段可以切到更高的速率、单帧数据最长 64 字节(不再有远程帧),并改用更强的 CRC,仲裁段速率保持兼容。CAN 没收到数据的排查顺序:终端电阻(两端各 120Ω)、波特率与采样点、收发器供电与 TX/RX 是否接反、总线电平、错误计数器与总线关闭状态。

大小端与字节序

大端是高位字节放在低地址,小端是低位字节放在低地址。0x12345678 在大端内存里从低地址到高地址是 12 34 56 78,小端是 78 56 34 12。UART 是串行逐位发送,通常先发 LSB(最低位在前),所以「大小端」在 UART 上主要影响的是多字节数据的先后顺序而不是单个字节的位序;协议里要明确约定先发高字节还是低字节,跨平台通信时统一按网络字节序(大端)处理。作者复盘时提到的 LSB、MSB 没搞清楚,指的就是最低有效位和最高有效位,以及串行先发哪一位、多字节先发哪一字节这两件事。

Bootloader 与低功耗

Bootloader 的基本结构是上电后先判断升级标志与固件校验(CRC/签名),合法就跳转到 app,不合法就进升级模式收固件,收完写 flash 并回写标志。断电保护靠「状态机 + 标志位」:升级前擦状态页写标记、写入完成后再写成功标记,重启时只认完整且校验通过的镜像,失败则回滚或停在升级模式;flash 驱动本身要按页对齐、考虑磨损,关键区域可以双份存。低功耗设计从时钟、外设、模式三处下手:关闭不用的外设时钟,降低主频,用 tickless idle 让空闲时进 sleep,用 RTC 或外部中断唤醒,注意唤醒源的配置和唤醒后的重新初始化,以及 IO 口的上下拉漏电。