嵌入式面试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的栈使用

相关推荐
Lee川9 小时前
优雅进化的JavaScript:从ES6+新特性看现代前端开发范式
javascript·面试
Lee川12 小时前
从异步迷雾到优雅流程:JavaScript异步编程与内存管理的现代化之旅
javascript·面试
晴殇i14 小时前
揭秘JavaScript中那些“不冒泡”的DOM事件
前端·javascript·面试
绝无仅有15 小时前
Redis过期删除与内存淘汰策略详解
后端·面试·架构
绝无仅有15 小时前
Redis大Key问题排查与解决方案全解析
后端·面试·架构
AAA梅狸猫16 小时前
Looper.loop() 循环机制
面试
AAA梅狸猫16 小时前
Handler基本概念
面试
Wect16 小时前
浏览器缓存机制
前端·面试·浏览器
掘金安东尼17 小时前
Fun with TypeScript Generics:玩转 TS 泛型
前端·javascript·面试
掘金安东尼17 小时前
Next.js 企业级落地
前端·javascript·面试