自我介绍
我是一名专注于嵌入式开发的工程师,有8年相关经验,主要做MCU固件开发,外设驱动和系统调试这些工作。平时常用 C语言,对RTOS,串口,DMA这些硬件接口比较熟悉,做事习惯从实际需求出发 ,注重代码的稳定性和资源利用率。
堆与栈的基本概念及区别
栈:就像叠盘子,只能从最上面拿或放,系统自动管理,用来存放 函数里面的临时变量,函数调用时的参数和返回地址。特点是快,但是空间小,大小 一般固定,用完自动释放,不用自己操心。
堆:像个杂乱的仓库,能随便存东西,但是需要自己手动申请,比如用malloc 和释放比如用free,空间比栈大,也更灵活,但是速度慢,而且如果忘记释放会造成学浪费,内存泄露
区别简单说,栈是系统自动管,快但空间小,堆是自己手动管,空间大但是麻烦,容易出问题
裸机 rtos里面测量栈最大使用深度的方法
1:填充检测法:
系统启动时,用一个特殊的值 比如0x55 把整个栈空间填满。程序跑一段时间后,看栈里哪些地方的数值被修改了------从栈的起始位置开发,最后一个被改掉的地方,就是栈用到的最大深度。
实际检测时,直接扫一遍栈内存,找到没被改的第一个位置,前面的就是实际用了多少。
2:硬件或系统工具
有些单片机自带栈边界检测功能,栈快满了会报警,rtos 通常有现成的函数,能直接查到每个任务的栈用了多少,最大 用到过多少。
栈内容发生 改变的情况
1:调用函数时,会把参数,返回地址,当前CPU的状态压入栈里面,函数的临时变量也存在栈里面。
2:函数结束返回时:之前压进栈的东西会被弹出来,恢复到调用前的状态。
3:发生中断时,cpu会把当前正在跑的状态,比如下一步要执行的指令地址,寄存器里面的数据,压进栈,中断处理完再弹出来继续跑。
4:临时变量被改时,比如函数里面的变量赋值,计算,都会直接修改栈里面的数据
CPU保存现场时保存的上下文信息
简单说就是 暂停时的所有 状态,主要包括
程序计数器PC :记录下一条要执行的指令地址,恢复后能接着从这里跑
各种寄存器:比如存临时数据的通用寄存器,记录CPU状态,比如有没有进位,开没开中断,的状态寄存器
栈指针(SP):记录栈当前的位置,确保恢复后栈的状态没错。
系统触发HardFault的定位原因的方法
1:查看故障寄存器,单片机里面有专门记录故障原因的寄存器,比如CFSR,里面的每一位对应一种错误,比如栈溢出,访问了无效的内存,除以0等,看这些位就能知道大概是啥问题
2:看栈里面的记录:HardFault发生 时,栈里会存故障前的状态,比如PC值,寄存器值,用调试器看栈里的PC的值,就能找到出问题 的代码位置 。
3:逐步排查:比如注释掉一部分代码,看看还会不会报错,一点一点的缩小 范围,或者加打印信息,看故障前执行到了哪一步
MCU从上电到执行main函数的流程
1:上电复位:单片机一通电,先触发 复位,硬件自动初始化,比如把要执行的第一个指令地址设好
2:跑启动代码
先初始化栈 告诉CPU栈从哪个地址开始用
初始化全局问题,把存在flash里面的初始值复制到RAM,没初始值 全局变量清成0
配置时钟,比如把单片机的主频从默认的低速模式调到高速模式
3:跳去执行main函数:启动代码跑完后,就会调用 我们写的main函数,程序正式开始运行
全局变量的初始化阶段
全局变量的main函数执行之前就初始化好了,具体是在启动代码里面处理的
有初始值的,比如int a = 10 从flash复制到RAM里。
没初始值的,比如int b 启动代码会把对应的RAM区域清0
串口通信的实现方式
我常用 两种方式,根据场景选:
1:中断方式
发数据或收数据时,不用一直等着,硬件处理完一个字节,就触发 中断,在中断里面把数据放到缓冲区,比如 收数据时存到数组,发数据时从数组里面取,优点是不耽误cpu干活,适合数据量不大 速度中等的情况
2:DMA方式:
让DMA控制器直接处理数据传输,CPU不用管,比如收数据时,DMA自动把串口听数据搬到内存缓冲区,满了就触发 中断 ,发数据时,DMA自动从缓冲区取数据发出去,适合数据量大的场景 ,比如大量日志打印,效率很高
缓冲区一般用环形缓冲区,像个圆圈,数据先进先出,避免新数据覆盖还没处理的旧数据
DMA传输完成中断与串口空闲中断的标志位问题
会同时置位,也可能同时触发 中断 ,具体看单片机型号,但是大部分都是这样的
比如:DMA刚把100个字节传完,触发 了FULL中断 ,这里串口刚好没有数据了,触发 idle中断 ,两个标志位会同时被硬件设置为有效
但是中断执行顺序要看优先级,优先级高的先跑,优先级一样的话,看单片机的默认顺序比如按中断编号排,处理时只要在中断里面分别检查并清除对应的标志位就可以