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

P0_1=1; //定时 中断的 LE D 灯灭。 Gu8Inter ruptStep=0; //返回 到上一个步骤 } break; } TH0=0xfc ; //重装初值,不能忘 。 TL0=0x66 ; //重装初值,不能忘 。 }

100%1 / 836
{
case 0:
Gu32CycleCnt++;
if(Gu32CycleCnt>=CYCLE_SUM)
{
Gu32CycleCnt=0;
P0_0=0; //主循环的 LED 灯亮。
Gu8CycleStep=1;
}
break;
case 1:
Gu32CycleCnt++;
if(Gu32CycleCnt>=CYCLE_SUM)
{
Gu32CycleCnt=0;
P0_0=1; //主循环的 LED 灯灭。
Gu8CycleStep=0;
}
break;
}
}
}
void T0_time() interrupt 1 //定时器 0 的中断函数,每 1ms 单片机自动执行一次此函数
{
switch(Gu8InterruptStep)
{
case 0:
Gu32InterruptCnt++; //累计中断次数的次
if(Gu32InterruptCnt>=INTERRUPT_SUM) //次数达到设定值就跳到下一步骤
{
Gu32InterruptCnt=0; //及时清零计数器,为下一步骤的新一轮计数准
P0_1=0; //定时中断的 LED 灯亮。
Gu8InterruptStep=1; //跳到下一步骤
}
break;
case 1:
Gu32InterruptCnt++; //累计中断次数的次
if(Gu32InterruptCnt>=INTERRUPT_SUM) //次数达到设定值就返回上一步骤
{
Gu32InterruptCnt=0; //及时清零计数器,为返回上一步骤的新一轮计数准备
P0_1=1; //定时中断的 LED 灯灭。
Gu8InterruptStep=0; //返回到上一个步骤
}
break;
}
TH0=0xfc; //重装初值,不能忘
TL0=0x66; //重装初值,不能忘
}
第八十六节: 定时中断的“非阻塞”延时控制 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; //全局变量的赋值,就是“写操作”