从单片机基础到程序框架(全集 2019pdf版).pdf - 第357页
借用了函数 入口的局部变 量 u32DelayTim e 来充当 for 循环 的变量,而省 去了一个 i 变量 。因此, “累减型” 比“累加型” 强一点。 【82.4 Delay 函数让初学者容易犯的错误。 】 初学者刚接 触 Delay 函数, 常常容易 犯的错误就是 忽略了 f or 循环变量的 类型,fo r 循环变量的 类型决 定 了 你 能 输 入 的 数 值 范 围 , 比 如 上 面 例 子 中 用 到 的 是 uns…

void Delay(unsigned long u32DelayTime) //产生“阻塞延时”的延时函数
{
static unsigned long i; //函数在频繁调用时,加 static 可以省去一条额外的初始化语句的开销。
for(i=0;i<u32DelayTime;i++);
}
void main()
{
while(1)
{
//第(1)步
P0_0=0; //LED 灯亮。
//第(2)步
Delay(5000); //这里就是阻塞延时,时间就越长,“亮”持续的时间就越长。
//第(3)步
P0_0=1; //LED 灯灭。
//第(4)步
Delay(5000); //这里就是阻塞延时,时间就越长,“灭”持续的时间就越长。
//第(5)步:这里已经触碰到主循环 while(1)的“底线”,所以接着跳转到第(1)步继续循环
}
}
【82.3 累加型和累减型的两种 Delay 函数,哪家强?】
上述 82.2 例子中,用到一个 Delay 函数,该函数内部的 for 循环用的是“累加型”的,比如:
void Delay(unsigned long u32DelayTime)
{
static unsigned long i; //“累加型”函数内部多开销了一个变量 i。
for(i=0;i<u32DelayTime;i++); //因为这里的“i++”是加法运算,所以称为“累加型”。
}
现在在跟大家分享一种“累减型”的 Delay 函数,例子如下:
void Delay(unsigned long u32DelayTime)
{
//“累减型”函数内部节省了一个变量 i。
for(;u32DelayTime>0;u32DelayTime--); //“u32DelayTime--”意味着“累减型”。
}
仔细对比“累加型”和“累减型”,会发现在实现同样“阻塞延时”的功能下,因为“累减型”巧妙的

借用了函数入口的局部变量 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; //累计主循环的计数器