从单片机基础到程序框架(全集 2019pdf版).pdf - 第358页

第八十三节: 累计主循环的“非阻塞”延时控制 LED 闪烁。 【83.1 累计主循环的“非阻塞” 。 】 上一节提到 , 当 D elay 的 “阻塞” 时间超过 1 ms 并且 被频繁 调用的时候 , 由于 Delay 做 “独占式无用 功” 而消耗的延 时太长,会影 响其它任务的 并行处 理,整个系统 给人的感觉非 常卡顿不流畅 。为了 解决此问题, 本节引入累 计主循环的 “非阻塞 ” , 同时, 希望通过此 例子, 让大家第一 …

100%1 / 836
借用了函数入口的局部变 u32DelayTime 来充当 for 循环的变量,而省去了一个 i 变量。因此,“累减型”
比“累加型”强一点。
【82.4 Delay 函数让初学者容易犯的错误。
初学者刚接 Delay 函数,常常容易犯的错误就是忽略了 for 循环变量的类型,for 循环变量的类型决
unsigned long
Delay(4294967295)。如果是 unsigned int 变量,最大可以输入 Delay(65535)。如果是 unsigned char
量,最大可以输入 Delay(255)。
【82.5 Delay 内部的 for 循环嵌套可产生无穷长的时间。
刚才讲到,如果用最大的变量类型 unsigned long ,最大的输入是 Delay(4294967295),那么问题来,
难道 Delay 函数的阻塞延时的时间有最大极限?其实不存在最大极限理论上,你要多大的延时都可以
需要在 Delay 函数内部用上 for 循环的嵌套,就可以产生“乘法级”的无穷长的时间,例子如下:
void Delay(unsigned long u32DelayTime)
{
static unsigned long i;
static unsigned long k;
for(i=0;i<u32DelayTime;i++)
{
for(k=0;k<5000;k++); //内部嵌套的 for 循环,意味着乘法的关系 u32DelayTime 5000 倍!
}
}
【82.6 “阻塞延时”与“非阻塞延时”的各自应用范围。
“阻塞延时”一般应用在两个地方,一个是上电初始化进入主循环之前的延时,另一个是进入主循环之
后,跟外部驱动芯片通信时候产生的时钟节拍小延时,而这个类延时一般是低于 1ms 的小延时。
“非阻塞延时”在项目中是被大量应用的,进入主循环之后只要大于或等于 1ms 的延时,大多数都采
样“非阻塞延时”因为进“任务框架级”的层面,只有“非阻塞延”才能保证项目可以继续“多任
并行处理”非阻塞延时”的方式后续章节会讲到
综上所述,1ms 是“塞延时”与“非阻塞延时”的一个分界线,1ms 个时间不是绝对的,只是一
经验值。
第八十三节: 累计主循环的“非阻塞”延时控制 LED 闪烁。
【83.1 累计主循环的“非阻塞”
上一节提到 Delay “阻塞”时间超过 1ms 并且被频繁调用的时候由于 Delay “独占式无用功”
而消耗的延时太长,会影响其它任务的并行处理,整个系统给人的感觉非常卡顿不流畅。为了解决此问题,
本节引入累计主循环的“非阻塞同时,希望通过此例子,让大家第一次感受到 switch 语句在多任务并行
处理时候的优越性。switch 的精髓在于“根据某个判断条件实现步骤之间的灵活跳转”,这个思路是以后做
所有大项目的框架性思路
为什么“累计主循环”可以兼顾到其它任务的并行处理?因为单片机进入 main 函数以后,在一个主
环里要扫描 N 个任务,从头到尾,把 N 任务扫描一遍,每扫描一遍算“一次主循环每一次“主循环”
都是要消耗一点时间,累计的“主循环”次数越多,所要消耗的时间就越长,但是跟 Delay 唯一的差别是,
Delay 做延时的时候没有办法扫描其它任务,而“累计主循环”内部本身就是在不断扫描其它任务,产生
间越长扫描其它任务的次数就越多,两者是完全相互促进而没有矛盾的。具体内容,请看下面的例子。
【83.2 累计主循环“非阻塞”的一个例子。
现在利用“累计主循环非阻塞”编写一个练习程序,让一 LED 灯闪烁。例子如下
83.2.1 灌入式驱动 8 LED
#include "REG52.H"
#define CYCLE_SUM 5000 //累计主循环次数的设定阀值,该值决定了 LED 闪烁频率
sbit P0_0=P0^0; //利用 sbit 和符号“^”的组合,把变量名字 P0_0 P0.0 引脚关联起
unsigned char Gu8CycleStep=0; //switch 的跳转步骤
unsigned long Gu32CycleCnt=0; //累计主循环的计数器
void main()
{
while(1)
{
switch(Gu8CycleStep)
{
case 0:
Gu32CycleCnt++; //这里就是累 main 函数内部的主循环 while(1)的次
if(Gu32CycleCnt>=CYCLE_SUM) //累计的次数达到设定值 CYCLE_SUM 就跳到下一步骤
{
Gu32CycleCnt=0; //及时清零计数器,为下一步骤的新一轮计数准
P0_0=0; //LED 灯亮。
Gu8CycleStep=1; //跳到下一步骤
}
break;
case 1:
Gu32CycleCnt++; //这里就是累 main 函数内部的主循环 while(1)的次
if(Gu32CycleCnt>=CYCLE_SUM) //累计的次数达到设定值 CYCLE_SUM 就返回上一步骤
{
Gu32CycleCnt=0; //及时清零计数器,为返回上一步骤的新一轮计数准备
P0_0=1; //LED 灯灭。
Gu8CycleStep=0; //返回到上一个步骤
}
break;
}
}
}
【83.3 累计主循环的不足。
上述 83.2 例子中,“累计主循环次数”实现时间延时是一个不错的选择。这种方法能胜任多任务处理的
程序框架,但是本身也有一个小小的不足,比如“阀值 CYCLE_SUM 到底应该取多少才能产生多长的时间”
没有标准的,只能依靠不断上机实验来拿到一个你所需要的数值,这种“不规范”当程序要移植到其它单
片机平台上的时候就特别麻烦,需要重新修正阀值 CYCLE_SUM。除此之外,哪怕在同样的一个单片机里,随
着主函数里任务量的增加,累计一次主循环所消耗的时间长度也会发生变化,意味着靠“累计主循环次数”
所获得的时间也会发生变化而导致不准确,此时,为了保证延时时间的准确性,必须要做的就是再一次修正
“设定累计主循环次数”的阀 CYCLE_SUM,这样显然给我们带来了一丝不便,怎么办?假设单片机没“定
时中断”这个资源,那么这种“累计主循环次数”在多任务处理中确实是不二之选,但是,因为现在几乎所
有的单片机内部都有“定时中断”这个资源,所以,大家不用为这个“不足”而烦恼,我们只要用上本节的
switch 思路,再外加一个“定时中断”,就可以轻松解决此问题,下一节就跟大家讲“定时中断”的内容。