嵌入式面试1103
1:简单的自我介绍
我是一名有8年嵌入式开发经验的工程师,主要聚集于单片机固件开发和实时系统应用,熟悉c C++ 和freeRtos,常和stm32 arm架构的芯片打交道,工作中做过底层驱动比如 串口,spi, dma,也搭建过系统框架,比如多任务调试,设备状态管理,习惯从硬件特性出发设计软件,注重 代码的可维护性和系统的稳定性,平时喜欢钻研芯片手册 和调试工具,解决过不少内存溢出,中断冲突这类的底层问题
2:项目的系统框架 以智能环境监控终端为例
整个系统分三层,从上到下大概是:
应用层:负责业务逻辑,比如数据解析,把传感器数据转成温度,湿度值,报警判断,超标了就触发蜂鸣器,远程通信(通过4G模块发数据给服务器)
中间层:包括freeRtos任务调试,比如传感器采集,数据上传,按键响应,三个任务,按优先级切换,硬件抽象接口,统一的传感器 读写函数,不管接的是i2c还是SPI传感器,上层调用 方式一样。
硬件驱动层:直接操作寄存器,比如STM32的GPIO驱动,控制指示灯,i2c驱动(读温湿度传感器 ) ,USART驱动 (和4G模块通信) ,DMA驱动(高速传数据减少CPU占用)。
硬件上就是MCU (STM32L4)外接传感器 ,4G模块,按键,蜂鸣器,电源模块给整个系统供电。
3: 对C++多态的理解及实现机制
多态简单说就是 同一操作,不同对象有不同反应,比如一个动物类有叫的动作,猫和狗继承它,叫的具体实现不一样,调用 时不用管具体是猫还是狗,直接用 动物 的接口就行 。
实现机制分两种:
动态多态,运行时确定 ,靠虚函数实现,编译器会给带虚函数的类建一张虚函数表,存虚函数地址,每个对象里面有个虚表指针指向这张表,当子类重写虚函数时,会替换表中对应的地址 ,调用 时通过虚表指针找到实际的函数,所以运行时才能确定执行子类还是父类的函数
静态多态编译时确定 ,比如 函数重载 同一函数名,参数不同,模板 比如写一个add函数,既能加int ,也能加float ,编译时编译器就根据参数类型确定调用 哪个版本,速度快,但是灵活性低。
4:freeRtos的内存管理策略及特点
freertos有4种内存管理方式 ,各有优点和缺点
1:heap1:最简单,只支持pvportmalloc分配 ,不支持释放 ,适合内存固定分配的场景 ,比如 任务栈初始化后不再修改,优点是快,不会碎片,缺点是不灵活 。
2:heap2:运行分配 和释放,但用最佳匹配算法 ,频繁分配 释放 小块内存容易产生碎片,比如释放的内存块太小,没法再用,适合内存块大小 固定 的情况
3:heap3:直接封装标准库的malloc和free ,优点是通用,缺点是线程不安全,需要关中断保护,且碎片问题和标准库一样
4:heap4:用相邻块合并 算法 ,释放内存时会把相邻的空闲块合并成大块,减少碎片,最常用 ,适合需要频繁动态分配内存的场景 ,比如消息队列
5:典型的单片机以stm32为例,的完整启动流程
1:上电复位 :单片机通电后,硬件自动 触发 复位。把CPU状态,寄存器复位,程序计数器pc 指向复位向量表的首地址 0x800000,flash起始地址 。
2:读取向量表:从复位向量表中找到栈顶地址 ,和复位中断服务程序地址 ,先初始化栈,把栈顶地址 写到sp寄存器,再跳去执行复位中断服务 程序
3:执行启动代码(汇编)
初始化数据段 把flash里面的全局变量初始化值复制到RAM
初始化BSS段,把未初始化的全局变量所在的RAM区域清0
配置系统时钟,比如从内部低速时钟切换到外部高速时钟,提高主频
4:跳转到main函数,启动代码执行 完,调用 c语言的main函数,程序开始跑业务逻辑
6:是否直接阅读或分析过汇编代码文件
是的,主要在两种场景下用过
调试底层问题时,比如hardfault报错,会看汇编反编译代码,通过PC寄存器的值定位到具体哪条指令出了问题,比如访问了无效的LDR指令。
优化关键代码时,比如 某个传感器 数据处理函数耗时过长,会看编译器生成的汇编,发现循环里面有多余 的寄存器压栈操作,改C代码后,减少了指令数,提高了速度,平时 也会 看启动文件 的汇编代码,比如 stm32的startup_stm32.s,理解栈初始化,中断向量表的加载过程
7:stm32的内存模型,地址空间分配
stm32的内存按功能分几块,地址是固定 的
flash 程序存储器,从0x0800000开始,用来存放 代码,全局变量的初始值,常量 ,大小 因型号而异,比如 f103c8t6是64kb,程序运行时,cpu从这里取指令。
RAM随机存储器:地址 从0x200000开始,用来 存放 全局变量,局部变量,堆,栈,比如 F103c8t6是20KB RAM,程序运行中数据的读写都在这里,掉电后数据丢失。
外设寄存器,地址 从0x400000开始,比如GPIO,USART, spi的控制寄存器都在这里,操作外设其实就是读写这些地址寄存器,比如写0x40010800地址 控制pa口输出。
还有一些特殊区域,比如系统寄存器,0xE000开始,里面有调试相关的寄存器,比如 DWT计数器,用来 测试代码的执行时间。
8:中断的硬件实现基本原理
简单说,中断是硬件打断CPU正常运行,让它先处理紧急事件的机制,硬件层面主要靠这几步:
1:中断请求:外设比如串口收到数据产生一个电信号 ,发给CPU的中断控制器,比如STM32 的NVIC
2:优先级判断,中断控制器检查这个中断 的优先级,比当前CPU正在处理的任务优先级高,就会打断CPU
3:保存现场:CPU自动把当前的PC 下条指令地址,寄存器值压入栈,相当于记住现在干到哪里了
4:执行中断服务程序ISR :CPU从中断向量表里找到对应中断的处理函数地址,跳过去执行,比如串口中断里读数据。
5:恢复现场,ISR执行完,CPU从栈里面把之前保存的PC和寄存器值弹出来,继续执行被打断的程序
整个过程由硬件自动触发 ,速度很快,确保紧急事件能及时处理
9:对linux操作系统的学习或使用经验
用过Ubuntu系统开发,比如交叉编译嵌入式程序,用gdb调试程序
学过Linux的基本命令,比如ls cd make 进程管理 ps kill 文件系统结构
简单了解过linux驱动开发的框架,比如字符设备驱动的open read,write 接口,知道应用层通过设备文件 /dev/xxx和驱动交互,但是没有做过复杂的驱动开发
平时 查资料,跑脚本时也常用 linux,感觉它的多任务调试,权限管理比RTOS更复杂,但是功能更强大。
10:了解或接触过的设备驱动类型
接触过这些类型
1:通用外设驱动,比如GPIO,控制LED,按键,UART (串口通信),SPI(接FLASH,屏幕),i2c(接温湿茺传感器 ,陀螺仪) ADC(读电压,光照传感器 )
2:存储类驱动:比如SPI flash驱动 实现数据擦除,写入,读取, sd卡驱动,用FATFS文件系统管理文件
3:通信类驱动,比如4G模块驱动,通过AT指令控制联网,发数据,蓝牙模块驱动(BLE广播,数据透传)
4:特殊外设驱动,比如DMA驱动(配合串口,ADC高速传数据) 定时器 驱动(PWM输出控制电机 转速)
写驱动时主要靠查芯片手册的寄存器描述 ,先实现基础读写,再加错误和超时重试,确保稳定。
11:对公司的想了解 的问题
1:这个岗位主要负责的项目是偏向底层驱动开发,还是更侧重系统应用或算法 实现?技术栈上会有哪些主流的芯片或操作系统。
2:团队目前在开发中遇到的比较有挑战性的技术问题时什么? 这个岗位会如何参与 解决这些问题
3:公司对工程师的技术成长有什么支持,比如是否有内部技术分享,外部培训,或者接触前沿技术,如AI在嵌入式端的应用 的机会。
栈溢出排查
1:看关键寄存器
pc 存储当前执行的指令地址,指向触发故障的代码位置,可能是内核检测函数或用户函数。可能是内核检测到溢出,若指向用户函数,可能是溢出触发点
LR 存储函数返回地址,指示当前函数由哪个函数调用
PSP  线程栈指针,指向当前线程栈的栈顶
MSP 中断栈指针 系统栈,若中断处理中溢出,会体现在MSP异常,通常配合Fault during interrupt handling打印,说明中断栈溢出
Xpsr 包含溢出相关标志位,如T位指示Thumb模式,ISR位指示是否在中断中,ISR = 1说明溢出发生在中断处理中,需检查中断服务函数ISR的栈使用
