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

vGu8Time Flag=1; //互斥 量 vGu8T imeFlag 的“解 锁” 。同时也起 到“启动计 时器”的开 关作用 while(1) / /主循环 { if(0==vGu1 6TimeCnt) //时间 变量为 0 则表 示时间到了 { ...在这里执行 具体的 功能代码 } } } void T0_ti me() inte rrupt 1 / /每 1ms 中断一次的 定时中断函数 { if(1==vG u8Time…

100%1 / 836
第八十六节: 定时中断的“非阻塞”延时控制 LED 闪烁。
【86.1 定时中断应用的四大关键词。
本节主要内容有四大个关键词:1ms,互斥量,volatile,switch。
(1)1ms。把定时中断设置为 1ms 中断一次,几乎是单片机界公认的“标配”。这个 1 ms 是系统时间
节拍来源,有了 1ms“标配”意识,你的程序在不同单片机平台上移植的时候会得心应手运用自如。
(2)互斥量。“主函数”与“定时中断函数”,本质上是两个独立进程在不断切换并行运行,两个进程
间不就会全保据的对多变量 int (2
个字节),long 类型(4 个字节)但是单字节 char 变量不用额外保护,因为“字节”是变量中的最小单位
(在不考虑“位”的情况下),这里的“最小单位不可分”就像“原子是最小单位不可分”一样,因此也
很多前辈把“斥量”称为“原子锁”为什么要用互斥量?因为,在多个线程同时访问同一个全局变量
时候,如果双方都是“读操作”,则不会出现问题,但是,如果双方都“既有写操作也有读操作”的情
下,比如,我在主函数里正在修改(写操作)一个 unsigned int 类型的变量,unsigned int 类型的变量
2 个字节,在更改数据的时候至少需 2 条指令,当我刚执行完第 1 条指令还没来得及执行第 2 指令的时
候,突然来了一个定时中断,并且在定时中断函数里也对这个变量进行了修改(写操作)并且还进行了读取
判断操作,这个瞬间就可能给程序带来了隐患。话说回来,互斥量到底有没有必要,其实还是有点争议的,
我曾经为这个问题纠结过很久,毕竟,如果不用互斥量,这么微观的隐患到底存不存在目前很难做一个
故障重现”的实验去证明,最后,我是本着“宁可信其有不可信其无”的态度,把互斥量应用在了我的工作
中。
(3)volatile。volatile 是一个前缀的修饰关键词,也是用来保护主函数与中断函数共用的全局变量
的,只不过,volatile 是针 C 译器的,预防“C 编译器在优化代码的时候误伤一些重要的共享数据”
就像预防毒软用力猛把些合件当病毒误杀加了 volatile 的全变量就能 C
编译器不要对这类特殊变量擅作主张去优化。
(4)switch。switch “非阻塞程序框架”的核心语句,在以 switch 为核心的框架下,进行不同步骤
之间的程序跳转,是做大型裸机程序的常态。
【86.2 主函数与定时中断函数的程序框架。
主函数与定时中断函数之间相互配合主函数负责做什么,中断函数负责做什么,对于初学者来说可能
是一头雾水,但是对于像我这种在单片机界深耕多年即将修炼成精的工程师来说,我心中是有很清晰的模板
和套路的,这种模板和套路是经过多年沉淀下来的经验。比如,定时中断函数尽量放一些精简的计时器代码,
一般不调用函数,但是“输入 IO 口的消抖动”(按键扫描)以及“蜂鸣器鸣叫”这两类特殊函数我是喜欢
例放在定时中断函数里调用的。定时中断如何产生时间,这个时间如何跟主函数关联起来请看下面的框架
代码:
volatile unsigned char vGu8TimeFlag=0; //互斥量变量标志
volatile unsigned int vGu16TimeCnt=0; //计时器变量
void main()
{
vGu8TimeFlag=0; //在“写操作”vGu16TimeCnt 全局变量之前,互斥量 vGu8TimeFlag 的“加锁”
vGu16TimeCnt=1000; //全局变量的赋值,就是“写操作”
vGu8TimeFlag=1; //互斥 vGu8TimeFlag 的“解锁”。同时也起到“启动计时器”的开关作用
while(1) //主循环
{
if(0==vGu16TimeCnt) //时间变量为 0 则表示时间到了
{
...在这里执行具体的功能代码
}
}
}
void T0_time() interrupt 1 //每 1ms 中断一次的定时中断函数
{
if(1==vGu8TimeFlag&&vGu16TimeCnt>0) //判断 vGu8TimeFlag 是否等于 1,就是互斥量的判断。
{
vGu16TimeCnt--; //“自减一”的操
}
}
分析:上述代码中,vGu8TimeFlag 是一箭双雕,既起到互斥量的作用,也起到了计数器 vGu16TimeCnt
开始计时的启动开关作用
【86.3 练习例程。
现在根据上述程序框架,编写一个 LED 灯闪烁的程序。
86.3.1 灌入式驱动 8 LED
#include "REG52.H"
#define BLINK_TIME 500 //时间是 500ms
sbit P0_0=P0^0;
volatile unsigned char vGu8TimeFlag=0; //互斥量变量标志
volatile unsigned int vGu16TimeCnt=0; //计时器变量
unsigned char Gu8Step=0; //switch 的切换步骤
void main()
{
TMOD=0x01; //设置定时器 0 为工作方式 1
TH0=0xfc; //产生 1ms 中断的 TH0 初始
TL0=0x66; //产生 1ms 中断的 TL0 初始
EA=1; //开总中断
ET0=1; //允许定时 0 的中断
TR0=1; //启动定时 0 的中断
while(1) //主循环
{
switch(Gu8Step)
{
case 0:
if(0==vGu16TimeCnt) //时间
{
P0_0=0; //LED 灯亮
vGu8TimeFlag=0; //互斥量“加锁”
vGu16TimeCnt=BLINK_TIME; //计时器的写操作。设定计时的长
vGu8TimeFlag=1; //互斥量“解锁”,同时蕴含了计时器“启动”的动作
Gu8Step=1; //切换到 case 1 个步骤
}
break;
case 1:
if(0==vGu16TimeCnt) //时间
{
P0_0=1; //LED 灯灭。
vGu8TimeFlag=0; //互斥量“加锁”
vGu16TimeCnt=BLINK_TIME; //计时器的写操作。设定计时的长
vGu8TimeFlag=1; //互斥量“解锁”,同时蕴含了计时器“启动”的动作