本周作者推荐:鱼鹰单片机
今天,你的栈溢出了吗?
在C语言的世界里,栈的地位非常举足轻重,许多错误都可能和栈设置有关,那么该如何确定栈的大小? 今天分享一点栈知识,帮助你确定栈的大小,参考链接请点击下方的阅读原文。其实很多编译工具是可以获取函数调用信息的,比如:KEIL 平台 可以使用参数:
--callgraph:生成一个静态调用图(html或文本格式),显示堆栈使用情况--info=stack:列出所有全局符号的堆栈使用情况以上两个参数分别生成Objects目录下的 xxx.htm文件(事实上这个--callgraph是默认参数,用浏览器打开这个文件即可)和 Listings目录下的xxx.map 文件(keil或者记事本打开)。
xxx.htm
xxx.map 事实上这个文件生成也是由默认参数 --map 管理的,不同的是增加 --info=stack 参数后,文件中还会增加如下信息:
它描述了每一个函数调用情况,并且把最大调用链和最大使用栈给你找出来了(这个文件打开有一个小技巧,双击 Target 1,即可打开这个文件,方便快捷)。
事实上,这些信息只能用于参考,而不能作为最终的栈大小,计算实际栈大小远比这个复杂,这个原文进行了深入阐述。事实上,一般设置栈大小都不会采用如此复杂的计算,而是通过长时间运行来简单确定栈大小(一般 RTOS 都有栈检查功能函数),并且会在长时间运行获取栈大小的前提下预留一定大小的栈空间,这样一般来说都能保证有足够的栈空间。IAR 平台 这个平台鱼鹰不是很熟悉,原文是这样写的:IAR Embedded Workbench for Arm (EWARM) provides stack size report in the linker map file. To enable this, the following project settings are required:
Enable linker map file generation in Linker settings (List tap)Enable stack usage analysis (Advanced tap).谷歌浏览器自动翻译结果是:
用这个开发平台的道友可以试试。GCC 使用参数: -fstack-usage 并且 gcc上提供了堆栈保护功能,使用以下选项
-fstack-protector:在堆栈帧上为每个易受攻击的函数插入一个保护变量。-fstack-protector-all:在堆栈帧上为所有函数插入一个保护变量。然后在看看文中的几个有意思的点:
01 栈布局
一般情况下的栈布局有如下两种:
KEIL 平台下的属于第一种,这就是为什么栈空间小了会很大可能影响用户变量的原因(栈往低地址生长)。而 gcc 采用第二种情况,这种情况可以完全利用 RAM 空间,如果说这种布局还会导致栈空间不够,那么你怎么设置栈大小都没用了。
02 函数调用
需要特别了解来自开发工具的堆栈使用报告仅涵盖每个函数或调用树的堆栈使用情况。 它们不包括异常处理程序所需的额外堆栈空间 。
假设应用程序仅对外设中断使用两个中断优先级,由于可能发生HardFault异常和NMI异常(如果应用程序使用),可能存在4级嵌套异常,如图所示。
(原作者为Joseph Yi)这就是为什么栈计算复杂的原因了。栈计算可用以下流程图:
(原作者为Joseph Yi)
03 栈大小难以计算原因
软件开发人员可能会发现,在许多情况下,报告无法提供有关应用程序某些部分的堆栈要求的信息。堆栈使用情况报告生成对某些代码不起作用的原因有很多:
在应用程序中使用函数指针可能意味着该工具无法生成调用树。在许多工具中,C运行时库中函数的堆栈使用是未知的。该应用程序包含递归函数调用或自修改代码。在这些情况下,您可能必须手动计算这些函数的最大堆栈使用量,或者通过试验来估计这些函数。例如,您可以在运行程序之前使用调试器以某种数据模式填充堆栈内存空间,然后执行代码,并检查堆栈内存空间以查看程序执行已修改了多少堆栈空间。
也可以通过在项目中添加检测代码来处理堆栈估算。例如,附录I显示了gcc Arm Embedded(使用NewLib)的堆栈检查实用程序代码。
最后再说一点,鱼鹰知道 PID 系列文章很多道友都不是很感兴趣(从阅读量可以看出),但是鱼鹰还是会继续更新下去的,因为鱼鹰写这部分笔记的初衷本来就是给自己看的,所以不会因为你们不感兴趣而停止更新。
另外鱼鹰还想说的就是,一旦 PID 系列文章更新完毕,鱼鹰可能将停止更新。
为什么呢,从去年 11 月份开始到,因为做公众号而花费了很大精力,但收获很小,而且最近的状态是总想着怎么写出一篇好的文章,而不是说怎么研究技术问题。而本公众号的文章里面,只有 KEIL 调试系列和少数几篇文章还算满意,其他文章的价值很少 。在鱼鹰看来,先有好的技术积累,才会有好的文章出现。最近一段时间一直在输出,而输入很少,所以鱼鹰准备暂停更新,直到认为有足够的技术积累再继续更新。
那什么时候恢复更新呢?鱼鹰无法确定,所以说如果你觉得本公众号没有价值了,那么可以取消关注。但是鱼鹰想说的是,当公众号恢复更新时,将呈现三大系列文章:
1、USB (包括如何自制一个调试器,将以它作为这个系列的最终目标)
2、FAT32FS 文件系统(源码理解,图解文件系统)
3、uCOS II (鱼鹰已全部看完并理解源码,只是需要点时间写出相应的笔记)
最近鱼鹰也算处于一种转型状态了,从单片机开发转向 linux 开发,不懂的知识点太多了,祝我好运咯!
查看原文:https://www.dianyuan.com/eestar/article-7724.html
固件升级经验总结
上面理论+实践当初花了3天时间弄完的,但是,当你真正做项目的时候,你会发现,只有上面的这些知识还不够,还有更多的细节要去处理:
1. APP程序怎么跳转到BootLoader程序?
2. APP程序和BootLoader之间是否会互相影响
3. APP和BootLoader之间如何传递参数?
4. 固件更新一到一半,因为某种原因失败了(通信错误、掉电),该如何处理?
5. 如何确保更新的APP是你需要的APP,而不是别的一个APP?
经过一个项目的固件升级功能洗礼,以上问题都得到了较好的解决,为了避免以后忘记,在此记录一下。芯片为:STM32F103ZET6
第一个问题,APP程序怎么跳转到BootLoader程序?看似很简单,因为这是基本的功能,但是实际情况并不简单。
由前面的小节了解到,从BootLoader跳转到APP可以通过指针进行跳转,但是当你从APP通过指针跳转到BootLoader时,发现会出现问题(具体原因不明,有机会的话去研究一下)。那么又该怎么办?
可以通过复位的方式,让程序重新从开始地址运行,有以下几种方式复位:
1、 内核复位
2、 系统复位
3、 上电复位
第一、第二种方式都是通过设置相关寄存器使单片机发生复位的,两者的区别就是,内核复位只复位芯片的内核,但对单片机的片上外设并不进行复位,比如USART、SPI、USB等外设是不会进行复位的。
系统复位的话,就会对整个芯片进行复位,不管是外设还是内核,都会回到最初始的状态,就如按下复位按键一样。
最后一种上电复位,其实和系统复位、按键复位的效果差不多,都是会进行全部复位的,不过这个需要外部硬件控制单片机的电源的开启与关闭,增加了额外的硬件。
一开始鱼鹰准备采用系统复位的,直接设置寄存器触发导致复位,因为这样更彻底,测试发现项目中的单片机根本无法复位,而我自己的开发板是能进行复位的,后来经过硬件工程师的查找,发现是看门狗电路导致无法复位,这样一来,系统复位这条路堵死了(因为项目的硬件已经确定,无法再更改了)。
那么是否有其它方法,前面提到的指针跳转的方式发现会出现问题,因为项目比较急,就没怎么花心思解决。后来在调试过程中,突然发现KEIL中的复位按钮是能进行复位的,那么问题就简单了,既然调试器能进行复位,那我应该也能进行复位才对,之前说了系统复位不好使,那么按下复位按钮时应该是采用的内核复位(实际上CMSIS-DAP调试器是有单独的一条复位线的,但是当时没考虑它可能采用了这种方式,只考虑可能采用了内核复位,阴差阳错)。
那么就试试内核复位吧,一试发现果然有效,但是因为内核复位不彻底,导致出现了问题。
这就到了第二个问题,两个程序之间是否会有影响?
第一,首先从BootLoader对APP的影响考虑,我们知道,BootLoader程序也是需要一些资源的,比如串口之类的用于固件的传输,如果说BootLoader的寄存器和APP的寄存器配置要求不一样,那么就可能出现问题(鱼鹰的项目中还用了一个定时器喂狗,发现一进入APP程序就挂了,后来才找到这个原因)。
比如BootLoader采用串口查询的方式接收数据,而APP为了提高效率,使用DMA+空闲中断的方式处理,那么两者的寄存器配置肯定不同,那么该怎么消除BootLoader程序对APP的影响呢?
有人说,让BootLoader程序用完串口之后自动复位串口外设即可,确实,这是一种方法,但是你是否考虑过两个程序是独立的,万一后面的人在BootLoader程序中忘记了复位串口呢?所以说,靠别人不如靠自己,与其担心害怕别人不靠谱,不如APP自己去复位串口,即APP在配置串口之前,可以先复位串口,再进行配置(从这里可以知道,为什么有些代码会使用XXX_DeInit()之类的函数在配置前复位片上外设,一开始以为是多余的,毕竟一般程序开始运行的时候一般都是上电之后才运行的,这个时候已经复位外设了,为什么还要多此一举,直到现在才明白这才是安全的做法)。
第二,从APP对BootLoader的影响考虑,APP程序使用的资源一般比BootLoader的资源多,如果两者之间使用了相同的资源,比如串口,那么肯定得考虑两者的差异性,所以根据上面的考虑,也可以让BootLoader程序在使用串口之前先进行复位,然后再进行配置,这是比较安全的做法。但是仅仅如此就足够了吗?
在项目里的APP程序中,有一个加热过程,如果说APP跳转到BootLoader之前没有考虑这一点就盲目的运行到BootLoader,那么很可能出现APP正在加热,但是因为跳转到了BootLoader中运行,导致无法对温度进行控制,那么结果将是灾难性的,轻点的只是设备烧毁,重的可能就引发火灾了。
所以说,两者之间的影响一定要慎重考虑。
事实上,如果采用系统复位或者上电复位的方式,第二点关于APP对BootLoader的影响是可以不考虑的,因为系统复位或者上电复位自动将外设进行初始化了,但是你不能肯定你现在采用这些方式,以后就不会采用内核复位的方式,所以为了安全,还是要考虑进去。
现在说说第三个问题,APP和BootLoader之间如何传递参数?
首先思考为什么要传递参数?
在前面的小节中,选择让BootLoader程序在开始复位时等待一段时间再进入APP运行,在等待的过程中,就可以判断是否需要固件升级,比如等待时,由上位机发送一条特殊的命令确定是否升级,或者通过引脚电平等方式,反正就是要让BootLoader程序知道,下面我要开始升级了,别急着进入APP运行。
但这里有一个问题就是,这里需要一个冷启动的过程,即先上电后再接收命令,而且时间短暂,有一个好处就是,即使单片机中暂时没有APP程序,也能够实现固件升级过程,这样保证了由BootLoader接收升级命令而不是由APP接收,所以当初在无法解决升级到一半时如何恢复时有考虑使用这种强制升级的方式。
那么有没有更好一点方式,不需要冷启动过程,而是由APP决定是否升级?有的。
既然是APP决定是否升级,那么肯定需要在进入BootLoader之前给它传递一个参数,告诉它,这次复位需要升级,不能直接跳到APP中运行,那么BootLoader就会乖乖地等着升级了。
那么怎么传递呢?有人说往FLASH中写入参数,这样复位的时候就可以判断是否需要升级了,这确实是一个方法,但是我们知道,如果我们要往FLASH写入参数,那么必须先进行擦除工作才行,而擦除的往往是一个扇区,为了写入几个字节的参数,擦除几K的数据,鱼鹰感觉实在是太浪费了;还有这个参数保存地址也是需要好好考虑的,放在APP区还是BootLoader区?那么有没有更好的方式?
有的。还不只一种。
一开始鱼鹰想到的是利用后备域保存参数,因为如果有电池存在的话,它的数据是不会丢失的,但不巧的是,这个项目没有这个功能。
还有可以使用外部的FLASH空间,有些FLASH芯片是可以进行字节编程的,不需要整片擦除,挺合适的。但是缺点就是,你的项目要有这种芯片,而你的BootLoader需要写相应的代码驱动这个芯片,显然很麻烦。
最后鱼鹰采用的是RAM传参。鱼鹰在之前的小节说过,APP和BootLoader共用RAM,如果说能用RAM传递参数的话,只是操作一个变量,相当方便。
但是怎么保证两者之间顺利传递参数呢?
我们知道,C语言申请的变量空间是由编译器自动分配的,也就是说,同样申明一个同名变量,APP和BootLoader申请的变量地址不一定是一样的,而且还有一点就是,即使你申请的变量通过某些方法让它地址固定,也会有问题,因为申请的变量会在进入main函数之前会被初始化掉,当然你可以说通过某种方式让它不被初始化,但是鱼鹰想到了更好的方法。
通过指针直接操作RAM空间最后几个字节用于参数传递(之前有看到说STM32单片机中有个寄存器可以直接掉电不丢失,但具体不知道是哪一个)。
因为采用指针操作,所以编译器并不会对你指向的地址进行初始化,这样可以很方便的绕过编译器的处理。其次,通过操作最后几个字节,保证了这个空间不会被程序的其他变量占用(其实占用了也关系不大,只要你传递的参数足够特别,比如0x05055555,就问题不大)。
这样,BootLoader在复位后只要检查这个地址的值,就可以轻松知道是否该升级了。
但是还有一个隐患就是,在上电那一刻,如果这个地址的值刚好是你设置的特殊值(因为上电后,RAM的值是随机的),那么必然会出现问题,但这种可能性微乎其微,因为要让四个字节在复位哪一刻刚好都变成你设置的特殊值,简直比中彩票还要困难。
不过即使你真的中彩票了,重新上电复位一下就好了,如果说第一次中彩票还能接受,第二次还如此,那就需要烧烧香、拜拜佛了。
需要注意的是,一旦使用完这个参数,必须清零,防止下次内核复位又进入升级了(比如在线调试时可能会使用KEIL中的复位按钮)。
第四个问题,固件更新一到一半,因为某种原因失败了(通信错误,掉电),该如何处理?
我们知道,升级过程中很大可能是会失败的,但是单片机升级不像电脑升级,这次升级不成功,恢复成原来的系统就是了。单片机空间有限,没办法同时保存两份APP程序的,那么又该如何处理呢?
现在换个角度思考,你如何确定你升级失败了?如果能做到这一点,那么你的BootLoader程序就可以在升级失败后继续运行BootLoader的程序,而不进入APP运行那只升级到一半的程序(运行这种半残程序,鬼知道会发生什么怪异事件呢)。
如果从这个角度来看,其实就简单了,只要上位机把bin文件的大小发下来,然后由BootLoader程序判断是否升级完成就可以了,而Ymodem刚好可以有这个功能。
但是这样一来,就出现了一个问题,需要一个掉电不丢失的参数来保存是否升级成功,否则下次上电又会继续运行APP。但是鱼鹰对整个扇区擦除的方式很反感,就是不愿使用这种方式,怎么办?
苦思冥想之下,终于找到了一个巧妙的方式去处理。
我们知道,运行APP之前,一般会对APP的前面8个字节的栈顶指针、复位地址的合法性进行判断,判断是否是有效的APP程序,毕竟随便拿一个程序去升级,还不乱套了。
不管怎样,APP程序都是要往FLASH更新程序的,那么我们是否能利用这个过程呢?
APP更新之前,必定把该擦除的扇区进行擦除了,如果说我们一开始,就对写入的工作进行特殊处理,那么是否可以达到我们想要的效果呢?
比如说,前面8个字节,本来是在一开始的时候就会被写入的,如果我们在一开始写入数据的时候,跳过这8个字节的写入,然后把剩余的代码全部写入,当判断已全部接收到(大于等于bin文件大小,因为Ymodem协议稍微有点特殊,最后一帧数据可能填充0)固件之后,最后再对前面8个字节写入,这样一来,就保证了程序的完整性,如果说你中途数据中断了(掉电或上位机中断),那么前面那8个字节肯定不会写入,也就无法正常进入APP中运行了。
第五个问题,如何确保更新的APP是你需要的APP,而不是别的一个APP?
前面的问题保证了更新的程序是完整的,但是完整的程序不一定就是你需要的程序,那么怎么确定是你需要的APP呢
通过bin文件名来确定吗?这是一个方法,但是bin文件名可以被用户轻易更改,那就只能从bin文件内容本身入手了。
常规方法是,通过某些工具,在bin文件中加几个字节标着文件的特殊性,但是众所周知的是,鱼鹰比较懒,看似简单的只是加入几个字节的事情,如果产品成型的话比较好说,更新次数比较少,但是一旦产品处于测试阶段,更新频繁,累人不说,还可能出错。
那么有什么办法呢?就从代码本身入手好了。
方案确定下来了,但是怎么处理呢,标志位放在哪里,怎么放?又是一番苦思冥想。
一开始想到的是想将标志位放在bin文件最后,但是bin文件的大小是不固定的,虽然说上位机可以把bin文件大小传下来,但是怎么放是个问题,开始打算通过修改链接文件实现,但是发现自己对链接过程不熟,对汇编语言(汇编可以指定地址)也不熟,怎么办?
最终鱼鹰选择把程序标志放在向量表的后面,即通过指定变量地址的方式保证地址唯一性(需要注意一点的就是,通过指定地址的方式,不一定就确保最终的地址就是你设定的地址,如果和其它地址冲突了的话,可能不一致,需要看map文件确定)。而且在设置APP程序的时候,一般都会重新定义向量表的位置,那么可以在定义标志地址时利用这个地址进行偏移。
当BootLoader在写前面8个字节前,只要再判断这个地址的标志位是否正确即可。
到此,固件升级方面的知识应该比较完善了,但还有一个问题,如何对bin文件加密与解密呢?
这个问题只有下次项目需要的时候再研究了。
查看原文:https://www.dianyuan.com/eestar/article-7725.html
PID 调节之 P(proportion)调节
首先看 P 调节的公式:
PWM 为输出电压,Kp 为放大系数,e(K)当前误差,
即设定速度值和当前测得的速度 n 的偏差。
现在开始进行分析。先假定一些情况,便于理解(只用于定性理解,不能定量分析):
速度最大为 1000(即单位时间的测得的脉冲数);输出 PWM 的值最大为 8000,即 8000 时占空比为百分百。Kp 为 10现在假定速度设定值为 700(目标),电机还没有启动,即速度为零,那么偏差 e(K)为 700,此时输出 PWM =700*Kp=7000,占空比为 7000/8000,此时的控制电压很大,但是速度为零,根据之前的知识可知此时电机电流(启动电流)很大,大于负载所需电流,即电磁转矩大于负载转矩,电机开始加速,转速上升,并且电流开始下降。
截取其中一个时间点,假如转速上升至 300,偏差 e(K)=700-300=400,此时 PWM=400*10=4000,这个 PWM 比上一次的 7000 更小,即控制电机的电压小,使电流更小,这时如果电流大于负载电流,速度还是会继续上升的,直到达到一个平衡:转速上升时,电流下降至刚好和负载电流相等,即转速不再上升了,既然转速不再上升,那么电流就不再下降,即达到一个平衡情况。现在速度到达平衡速度:转速上升至 400,e(K)=700-400=300,PWM=300*10=3000。这就是使用 P=10 后最终的调节速度。
即在 P=10,负载不变的情况下,最终 P 调节后的速度是 400。明显和 700 的目标速度相差较大,但是在 P=10 的情况下只能调节到这种程度。那有没有办法再提高呢?不说提高到刚好 700,600 也行啊,这样就知道该怎么减小误差了。
还是看公式理解:
PWM=Kpe(K)
从刚才的分析可以发现最终的输出 PWM=10*300=3000,所以导致误差太大,如果最终把 PWM 升高至 6000 而不是 3000,速度必然是不会只达到 400 的程度,肯定有更高的速度,也就更接近 700 了,所以现在把 Kp 设置为 20,我们可以重新分析一下,可以知道在速度再次达到 400 时,PWM 已经到了 20*300=6000,之前在 PWM 为 10*300=4000 时达到平衡,这次是 6000,因为负载不变,必然电流大于负载电流,速度还是要继续上升的。最终我们假定平衡时速度为 600(其实这些数据只是一个方向,最终是怎样的需要精确计算,比如最终是 600 这种情况下是经不起分析的,因为 e(K)=100,PWM=2000,这是不对的,所以最终的误差不可能是 100,肯定大于该数的,最终的 PWM 比 3000 大,不可能小,不然速度不可能提高的),这样的速度离设定目标 700 又更近了。
从这里我们可以发现 P 调节的本质就是将误差放大,利用非常小的误差通过 Kp 进行放大得到我们的所需要的 PWM(即对应的控制电压)。如果我们要想使误差为零,我们必须把 Kp 设置为无穷大才可能,事实上这是不可能的(书上 P25)。
事实上 P 调节系统就是依靠被调量的偏差进行控制,如果没有偏差,那么根据 PWM=Kpe(K) PWM 输出就是零了,也就没有控制电压了。所以这样的调速系统是有静差调速系统。这是 P 调节的本质导致不能消除静差。
但是如果为了获得最小的偏差,是不是 Kp 越大越好呢,理论上是这样,但是它有一个放大倍数 Kcr 限制(书上 P33),如果放大倍数 Kp≥Kcr,系统不稳定。对于一个自动控制系统来说,稳定性是它能否工作的首要条件,是必须保证的。
所以在 Kp<Kcr 的条件下,Kp 一般比较大,还是上面的例子,若 Kp=30,在设定值为 700 的条件下,初始 PWM=30*700=21000,而 PWM 最大为 8000,所以必须限幅,把它限制在 8000 以下。而 PWM 为 21000 的情况下,其实就是饱和的情况,此时因为限幅的作用,使它对速度的变化是没有反应的,只有在速度上上升至 8000/30 才会有调节作用。其实这种饱和对调节没有多大关系,因为它是只要在到达设定速度前开始调节就行,因为它只和当前偏差有关,和之前的偏差没有关系。
之前分析的是负载不变的情况下,P 的作用。现在分析负载变化的情况。我们在之前的分析知道,P 就是将小的误差放大,得到我们所需的控制 PWM,所以 P 是一定需要的。如果需要比之前更小的误差,在同样的控制 PWM 下,我们需要更大的 P 值。
但是 P 值不能大于 Kcr,否则系统不稳定。同样的,在负载变化的时候,也是根据误差放大来获得控制电压的。负载增加时,速度必然下降(暂时),即反馈后的误差 e(K)增加,而 P 值在正常情况下设定之后是不能变的,导致获得更大的 PWM 来抵抗负载的干扰,这样控制电压就会增加,此时的电机运行曲线就不是刚才的那一条,而是向上移动,进入另一条运行曲线,这样就能抵抗刚才的负载扰动。
看下图:
这是开环曲线和闭环曲线。假设原始工作点为 A,负载电流为 Id1,当负载增加到 Id2 时,开环系统的转速必然降到 A’点所对应的数值,闭环后,由于误差增加,电压可升到 Ud02,使工作点变成 B,稳态速降比开环系统小的多。这样,在闭环系统中,每增加(或减少)一点负载,就相应地提高(或降低)一点电枢电压,因而就改换一条机械特性。闭环系统的静特性就是这样在许多开环机械特性上各取一个相应的工作点,如图的 A、B、C、D、……,再由这些工作点连接而成。
由此看来,闭环调节系统能过减小稳态速降的实质在它的自动调节作用,能过随着负载的变化而相应的改变电枢电压,以补偿电枢回路电阻压降的变化。
但是只用比例放大器的反馈控制系统,其被调量仍是有静差的。从静特性分析中可以知道,闭环系统的放大系数 K(不是 Kp,但和 Kp 有关)越大,系统的稳态性能越好。还有一点需要说明的是在同样的闭环条件下,随着负载的增加,误差会越来越大。可以看闭环系统的稳态速降:
△ncl 为闭环系统的稳态速降,K 为闭环系统的开环放大系数。从这里我们可以看到,随着负载增加,Id 增加,稳态速降也增加,也就是和目标值的偏差越来越大。
查看原文:https://www.dianyuan.com/eestar/article-7721.html
延时功能进化论之概述
在这篇长达万字的长文中,鱼鹰将通过延时这种刚需功能聊聊溢出、可重入、编程思想、共享变量保护等方面内容,以延时功能为载体,能更好的理解这些缥缈的知识点。
本篇长文将分成五篇陆续发布:概述、V1.0~V1.5、V1.7、V2.0~V2.4、V2.5~V2.7。
在生活中,时间与我们的生活息息相关,日出而作,日落而息,说的就是利用太阳来大概判断时间,从而规划自己的作息。而在单片机领域,同样需要一个时间去控制你的代码运行情况。
玩单片机的应该都了解过晶振,很多初学者可能会从前辈那里得到这样一个比喻:单片机的心脏。从功能的角度来说确实如此,因为单片机代码确实是依靠晶振执行的,比如说晶振输出一个脉冲,CPU执行一条指令,就像心脏跳动一次,你做一个抬手动作。只不过你的心脏可能1秒只能跳动几十次,而晶振1秒输出脉冲几十兆赫兹(1 MHz = 1,000 KHz = 1000,000 Hz),而一般单片机还会将晶振输出的频率进行倍频,倍频后的频率才最终用于驱动CPU的运行。
如果说现实生活中,时间的最小单位是秒(应该没人在生活和工作中去精确到毫秒吧,更多可能是分钟),而在单片机领域,常用的时间单位应该是毫秒、微秒、纳秒,而决定时间精度的就是晶振(更准确的说是经过晶振分频、倍频后的系统时钟,比如说 STM32 的 8 M 晶振分频成 1 M,然后倍频成 72 M 作为系统时钟驱动 CPU,进而执行存储器中的代码)。
对于应用开发来说,可能他不会去了解晶振频率多少,系统频率多少,一条指令运行时间又是多少?他们更多的需求是,在多少毫秒、多少秒(微秒很少用)之后这段代码执行一遍,这个界面刷新一遍,然后以这个时间为周期,循环执行。这是利用延时功能去达到特定代码周期执行的目的。
延时可分为相对延时和绝对延时,相对延时与绝对延时的差别可看下图理解:
(在后面介绍的几种延时演进版本中,可自行思考采用何种方式延时)
本篇笔记将根据鱼鹰多年编程经验介绍自己如何实现延时功能。说是进化论,不如说是鱼鹰个人延时功能的使用演进过程。
说到延时函数,51单片机过来的道友脑中应该会想起郭天祥老师视频中的延时函数,而使用原子例程的道友会想起例程中使用systick定时器实现的延时功能,尽管他们的实现方式有所不同,但他们都是采用死等的方式达到延时功能(所谓死等,有个比喻鱼鹰觉得挺恰当的:驴拉磨,让CPU一直在一个地方打转,时间到了就离开这个地方)。
死等方式确实很容易理解,也很容易实现,但是它的弊病也很明显,不说它极大的浪费了CPU的资源(在延时过程中,除了能处理中断,后面的代码无法处理),更重要的是影响的代码的执行效果,比如说你有一个功能时需要20毫秒执行一次,而你的另一个功能却需要30毫秒执行,那你如何采用死等方式处理其中的矛盾呢?既然是进化论,为了笔记的完整性,鱼鹰将根据自己使用过的延时函数进行一一说明。
查看原文:https://www.dianyuan.com/eestar/article-7719.html
更多精彩内容,尽在电子星球 APP(https://www.eestar.com/)
六篇技术文章,让你秒懂电容的脾气秉性
七篇DIY技术文章献给你,让你脑洞全开
五篇文章帮你开启DSP的学习思路
汇总篇:关于PID知识,重点在此
价格仅需一块多!国产32位030系列单片机,主频最高48兆。
这两年可以说是国产芯片厂商遍地开花的两年,经历了前两年“缺芯危机”之后,现在可以供客户选择的高性价比厂商也非常多了,价格也随之内卷起来。今天给大家介绍价格仅需一块多,号称卷王的32位通用型单片机,PY32F030 。配置64KB flash 和8KB SRAM 工作主频最高48兆 ,脚位上兼容ST GD 等,开发环境和工具上也与其他国产厂商类同。
PY32F030具体配置 :采用M0+内核, 64Kbytes flash 和 8Kbytes SRAM 存储器,最高工作频率 48MHz。包含多种不同封装类型多款产品。芯片集成多路 I2C、SPI、USART 等通讯外设,1 路 12bit ADC,5 个 16bit 定时器,以及 2 路比较器。工作温度范围为-40℃~85℃,工作电压范围 1.7V~5.5V。芯片提供 sleep 和stop 低功耗工作模式,可以满足不同的低功耗应用。
相关问答
晶振频率为8MHz时,一个机器周期为多少?1、51单片机的机器周期=12/fOSC;当fOSC=8MHz时,机器周期=3/2微秒。2、fOSC代表晶振频率的意思。3、单片机(Microcontrollers)是一种集成电路芯片,是采用超....
请问:1Hz的频率闪烁是什么概念?谢谢?二极管以1HZ闪烁,意思是1秒内亮灭一次,HZ单位是次/秒(次每秒),1HZ就是一秒1次。单片机的晶振频率应该是11.0592MHZ(兆赫兹),也就是11059200次每秒,频...二...
用22微法电容和1k电阻可以给12 兆 晶振的 单片机 复位吗?勉强可以,但效果不好,尤其在掉电复位时不可靠。现在这种阻容复位电路早已被淘汰,专用的复位芯片已广泛应用,最常见的如MAX809、MAX810、MAX813及其兼容型号...
pic16f系列的 单片机 pwm频率最高是只有20K吗?能提高吗?可以使用PWM直接肛,前提要求是你的PWM频率足够高(之前测试用的上兆的效果差不多了,同时音频采样率别忘记弄上去了),否则的话音质会特别呵呵哒。三极管建议...
举例说比特率和波特率的联系和区别 - ?糖妈? 的回答 - 懂得比特率:比特率是指每秒传送的比特(bit)数。单位为bps(BitPerSecond),比特率越高,传送数据速度越快。每秒钟通过信道传输的信息量称为位传输速率,...
智能电视怎样看无线网密码-ZOL问答我家20兆宽带,安一个电脑和路由器,智能电视和手机收到的网速很低,怎么设置带宽...STC12C5204AD单片机4541浏览7回答电脑上所有的图标都变成了WPS图标了,怎么...
5G时代,智能家居市场前景怎么样?——以上数据及分析均来自于前瞻产业研究院《中国智能家居设备行业市场前瞻与投资策略规划报告》。信息技术的快速发展给人们的生活带来了巨大的改变,智能家居...
门遥控钥匙匹配方法?电子遥控器匹配方法对拷遥控器的出现,使电动遥控门遥控器的匹配变得非常简单,轻松两步就能快速完成固定码遥控器的匹配。(1)特点不需要拷贝机,不...电子遥...
我现在有一个数字万用表,怎么样用字测量电路的通断?-ZOL问答JAVA、C、C++、Python同样是高级语言,为什么只有C和C++可以编写单片机程序?17...为何打印一个100多K的word文档,到打印机里却变成好几十兆?4754浏览6回答.....
51 单片机 定时器初值为0x00,计数时0x00计算在内吗?没理解你的问题,看来你对定时器的工作还有一些疑惑,用STC89C52单片机来说,它有3个定时器,分别是T0,T1和T2。其中T2有捕捉功能。举例如定时器配置为16位...没...