ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

正浩创新嵌入式一面

2026/9/16 7:31:18 拓冰建站 浏览量
正浩创新嵌入式一面 1. 自我介绍。2. 项目介绍3. RTOS 项目中有哪些任务任务之间如何协同GUI 任务中等优先级执行lv_timer_handler处理界面渲染、动画接收触摸事件调用 LCD 异步 DMA 刷新接口。大部分时间阻塞等待事件。触摸采集任务低优先级周期性读取触摸芯片获取触摸坐标将触摸事件打包发送给 GUI 任务。日志打印任务最低优先级缓存串口打印信息串行输出避免多任务串口并发冲突。可选补充如果面试官追问还可以增加系统巡检任务用来喂看门狗、堆栈监控。任务间协同FreeRTOS 常用这几种机制消息队列 Queue用来传递带数据的事件。触摸任务读到坐标后把触摸数据包入队GUI 任务阻塞读取队列收到后处理触摸输入。适合传递数据包。任务通知 Task Notify我项目里 LCD DMA 使用轻量级占用资源很小。DMA 刷屏完成的中断回调里给 GUI 任务发送任务通知唤醒 GUI 任务通知它刷新完成调用lv_disp_flush_ready。中断通知任务首选。互斥锁 Mutex保护共享资源。比如串口资源多个任务不能同时打印用互斥锁保证同一时刻只有一个任务访问防止输出乱码自带优先级继承缓解优先级反转。事件组 EventGroup当一个任务需要等待多种不同事件触摸、DMA 完成等时使用。4. 为什么裸机无法满足性能要求换成 RTOS 后可以裸机是 while (1) 轮询所有逻辑串行跑一旦某段代码耗时其他事件响应延迟不可控只能不停轮询标志CPU 空耗。 RTOS 采用抢占式调度高优先级任务可以抢占任务无事时可以阻塞释放 CPU。5. 调用 vTaskDelay 后什么任务在休眠为什么反而能提升效率调用vTaskDelay当前正在执行的任务进入阻塞休眠。 它不是忙等待任务休眠期间主动释放 CPU调度器可以调度其他就绪任务运行CPU 不会空循环等待所以提升 CPU 使用效率。6. 什么情况下会发生任务切换FreeRTOS 任务切换触发时机① 任务调用阻塞 API 主动放弃 CPU② 有更高优先级任务就绪发生抢占③ 同优先级任务时间片耗尽触发轮转④ 调用 taskYIELD 手动触发任务切换。7. 任务切换时RTOS 内核主要做什么任务切换本质就是上下文保存与恢复内核主要做这几件事保存当前任务上下文把正在运行任务的现场CPU 寄存器R0~R15、PSR 等压入该任务自己的任务栈保存当前任务断点方便以后切回来继续执行。更新 TCB任务控制块更新任务状态标记这个任务现在是就绪 / 阻塞。挑选新任务调度器从就绪列表选出优先级最高的就绪任务。恢复新任务上下文从新任务的任务栈把寄存器弹出到 CPU 寄存器恢复新任务运行现场。程序回到新任务上次暂停的断点继续运行。8. 调度器找到更高优先级的就绪任务后如何真正切换过去找到更高优先级就绪任务之后不会立刻在当前代码里直接切换靠 PendSV 异常来完成实际上下文切换标记需要触发任务切换置位PendSV挂起异常标志等当前执行流任务或者中断执行完毕、退出的时候进入 PendSV 异常在 PendSV 异常服务函数内部保存旧任务 CPU 寄存器上下文到旧任务的任务栈更新 TCB切换栈指针到新高优先级任务的栈从新任务栈恢复寄存器上下文退出 PendSV 异常CPU 就运行新的高优先级任务。关键点 如果是在任务代码里发现更高优先级任务就绪置 PendSV 后立刻进入 PendSV完成切换 如果是在中断里唤醒高优先级任务不会在中断内部切换等到退出中断的那一刻触发 PendSV再做任务切换保证硬件中断优先执行。9. 上下文切换时哪些寄存器由硬件压栈哪些由软件保存硬件自动压栈CPU 硬件自动完成进入异常时R0、R1、R2、R3、R12、LR、PC、xPSR。一共 8 个寄存器。软件手动保存PendSV 服务函数汇编代码手动入栈剩下的 R4~R11 这 8 个通用寄存器。这几个硬件不会自动保存FreeRTOS 在 PendSV 汇编里手动压入当前任务栈。简单逻辑 进入 PendSV 异常 → CPU 硬件自动压 8 个寄存器 然后 PendSV 汇编代码把 R4-R11 手动保存到任务栈 切到新任务时反过来先软件弹出 R4~R11退出异常时硬件自动恢复那 8 个寄存器。LR 链接寄存器存放函数 / 异常的返回地址。xPSR 是程序状态寄存器存放运算标志、Thumb 指令标记、异常状态记录 CPU 当前运行状态。这两个都是 Cortex-M 进入异常时硬件自动压栈的 8 个寄存器中的两个。10. Bootloader 和 OTA 是怎么实现的Flash 分为 Bootloader 区、App 运行区、OTA 下载备份区。上电先跑 BootloaderOTA 升级时先把新固件收到备份分区CRC 校验无误后写升级标记软复位。重启进 Bootloader检测到升级标记把备份固件拷贝到 App 区完成后跳 App。跳转前需要修改 VTOR 中断向量表偏移并且加载 App 的栈顶指针。Bootloader独立的小程序放在 Flash 最开头芯片上电优先运行 Bootloader。功能上电先判断是否需要升级如果不需要升级就跳转到 App 分区启动用户应用如果需要升级就执行固件更新。编译特点Bootloader 的链接脚本指定起始地址是 Flash 起始 0x08000000中断向量表在本区。OTA在线升级① 通过通信接口串口 / 以太网 / WiFi接收新固件写入 OTA 备份分区不直接覆盖正在运行的 App防止中途断电变砖。 ② 固件接收完成校验固件CRC 校验确认固件完整合法。 ③ 在 Flash 的标志区或者 Flash 最后一个扇区设置升级标记然后软件复位单片机。 ④ 重启后进入 Bootloader检测到升级标记将备份分区的新固件搬运拷贝到 App 运行区清除升级标记。 ⑤ 拷贝完成Bootloader 跳转到 App运行新版程序。11. Bootloader 跳转到应用程序前需要做哪些操作Bootloader 跳转到 App 前核心操作分为这几步关闭 Bootloader 中启用的外设关闭串口、DMA、定时器等停止相关中断防止跳转后外设继续工作产生异常。关闭全局中断避免跳转过程被中断打断保证跳转过程原子性。读取 App 向量表App 的向量表首地址第一个 4 字节是初始 MSP 栈顶第二个 4 字节是 App 复位入口地址。把 MSP 设置为 App 的初始栈顶。配置中断向量表偏移寄存器 SCB-VTOR把向量表基地址指向 App 的向量表起始 Flash 地址。通过函数指针跳转到 App 的复位入口地址。注意不能直接普通函数调用跳转必须用函数指针跳转。12. 为什么跳转前需要修改中断向量表Cortex-M 内核发生中断时CPU 会根据VTOR 寄存器指向的地址去读取中断向量表找到对应的中断服务函数入口。 芯片上电默认 VTOR0指向 Flash 起始位置也就是 Bootloader 的向量表。 当 Bootloader 跳转到 App 后如果不修改 VTOR App 运行过程中一旦触发中断CPU 依然会去 0 地址Bootloader 的向量表查找中断函数而不是 App 自己的中断服务函数就会跑到错误的代码直接 HardFault、程序跑飞。 所以跳转前修改 VTOR让 VTOR 指向 App 的中断向量表起始地址。后续产生中断CPU 就能找到 App 里正确的中断服务函数。13. 中断向量表是什么中断向量表本质是一张存储各个异常、中断服务函数入口地址的数组存放在 Flash 中。 向量表第 0 项应用初始 MSP 栈顶地址 第 1 项复位异常的入口地址 后面依次存放 HardFault、SysTick、串口、DMA 等所有异常和外设中断的服务函数地址。当发生中断 / 异常时CPU 通过 VTOR 找到向量表起始地址根据中断编号去查表拿到对应的中断服务函数入口跳转执行。Bootloader 和 App 各自拥有独立的中断向量表放在 Flash 不同位置。14. 编译结果中的 RO、RW 等区域分别代表什么O、RW、ZI 是 Keil 编译后存储器的三个段RORead-Only只读段包含代码段 (.text)和常量只读数据 (.const)。就是程序代码、const修饰的常量。放在 Flash上电不会改变只读。RWRead-Write可读可写段已经初始化的全局变量 / 静态变量带初始值例如int a10;。 存储Flash 里存放初始值上电启动时启动文件把 Flash 里的初始值拷贝到 RAM之后在 RAM 读写修改。ZIZero Initial零初始化段没有初始化的全局 / 静态变量启动时自动清零。 不占用 Flash 空间只占用 RAM上电后启动文件把这块 RAM 全部置 0。局部栈变量不属于 ZI在任务栈里。15. 代码中的哪些内容会放入只读区和读写区16. C 语言中的指针是什么指针是保存内存地址的变量。32 位系统指针占 4 字节取地址*解引用通过地址访问内存。17. 结构体和联合体有什么区别结构体 struct各个成员拥有独立内存空间总大小大于等于所有成员大小之和存在内存对齐。成员同时共存可以同时读写所有成员。联合体 union所有成员共用同一块内存空间联合体大小等于它最大成员的大小。同一时刻只能使用其中一个成员修改一个成员会覆盖其他成员的值。18. 为什么需要结构体内存对齐为了适配 CPU 的内存访问机制。对齐访问可以让 CPU 一次读取数据提高访问效率部分内核访问未对齐内存会直接触发硬件错误。编译器自动填充填充位 padding 完成对齐。19. 结构体具体按照什么规则对齐第一每个成员起始地址是自身大小整数倍不够就前面补填充字节第二结构体整体大小必须等于最大基础成员大小的整数倍不够末尾补填充。20. sizeof 和 strlen 有什么区别sizeof 是 C 语言运算符在编译阶段计算用来求整个类型 / 变量占用内存的总字节数包含数组、结构体、指针。会把字符串末尾的\0终止符也算进去。strlen 是库函数运行时计算专门用来统计字符串有效字符个数遇到\0就停止计数不包含末尾的\0