#include <REGX52.H>
sbit S7=P3^0;
sbit L1=P0^0;
void Delay(unsigned int t)
{
while(t--);
}
void InitHC573()
{
P2=(P2&0x1f)|0x80;
}
unsigned char stat=0;
unsigned char pwm_duty;
//======================按键扫描模块
void scankey()
{
if(S7==0)
{
Delay(100);
if(S7==0)
{
switch(stat)
{
case 0://按下1次,LED由灭转亮(亮度10%)
L1=0;
TR0=1;
pwm_duty=10;
stat=1;
break;
case 1://按下2次,亮度50%
pwm_duty=50;
stat=2;
break;
case 2://按下3次,亮度90%
pwm_duty=90;
stat=3;
break;
case 3://按下4次,LED熄灭
L1=1;
stat=0;
TR0=0;//注意一定要关闭定时器,否则定时器继续运行,灯会以90%亮度继续点亮
break;
}
while(S7==0);
}
}
}
//=========================
//=========================定时器模块
void InitTimer0()
{
TMOD=0x01;
TH0=(65535-100)/256;
TL0=(65535-100)%256;
EA=1;
ET0=1;
}
unsigned char count=0;
void ServiceT0()interrupt 1
{
TH0=(65535-100)/256;
TL0=(65535-100)%256;
count++;
if(count==pwm_duty)//当count<pwm_duty时,L1默认为0
L1=1;
else if(count==100)
{
count=0;
L1=0;
}
}
//=========================
void main()
{
InitHC573();//控制锁存器进入LED模式
InitTimer0();//初始化定时器
while(1)
{
scankey();//按键扫描
}
}
博主看的是小蜜蜂老师的视频。最开始我采用的是按键扫描和LED点亮分开编程的思路,但总是无法实现功能,猜想是按键扫描过程中定时器仍然在运行所致。
原本思路:
LED处于模式1(定时器不断计数)-按下按键,进行扫描(此时定时器仍在计数,LED按原来亮度点亮)-消抖(LED原亮度)-确认按下,刷新标志变量(LED原亮度)-等待按键松开(LED原亮度)-LED函数读取标志变量,刷新pwm波的占空比,模式2(LED下一亮度)
原本代码:
void LEDRunning()
{
switch(stat)
{
case 1:
L1=0;
TR0=1;
pwm_duty=10;
// stat=1;
break;
case 2:
pwm_duty=50;
// stat=2;
break;
case 3:
pwm_duty=90;
// stat=3;
break;
case 4:
L1=1;
// stat=0;
TR0=0;
break;
}
}
void scankey()
{
if(S7==0)
{
Delay(100);
if(S7==0)
{
stat++;
if(stat==5)
stat=0;
while(S7==0);
}
}
}
void main()
{
InitHC573();
InitTimer0();
L1=1;
while(1)
{
scankey();
LEDRunning();
}
}
原本代码产生的现象:
蓝桥杯PWM波-错误示范版
如视频所示,上电后LED1熄灭。按下按键一次,LED1高亮;按下两次,LED1中亮;按下三次,LED1高亮;按下四次,LED1熄灭。与题目要求相比,发现第一次按下按键现象不符。
因此分析问题出在case1。
我的代码在LEDRunning()中,用switch语句时,case1中多写了一个令L1=0的操作,由于IAP15速度很快,导致在只按下一次按键时,会反复多次执行L1=0造成亮度过高,远远超过case1的10%亮度。
老师的代码在第一次按下按键之后就刷新stat=1,避免了重复执行L1=0;的操作。
附上改进后代码:
#include <REGX52.H>
#include "INTRINS.H"
sbit S7=P3^0;//S7位定义
sbit L1=P0^0;//LED1位定义
void Delay(unsigned int t)//延时
{ unsigned char i, j;
while(t--){ // @11.0592M
_nop_();
_nop_();
_nop_();
i = 11;
j = 190;
do
{
while (--j);
} while (--i);
}
}
void InitHC573()//选择LED模式
{
P2=(P2&0x1f)|0x80;
}
unsigned char stat=0;//LED状态变量
unsigned char pwm_duty;//PWM占空比
void LEDRunning()//LED显示函数
{
switch(stat)
{
case 1:
// L1=0;这一步错了,case1本来是亮度10%,IAP15快速执行会使亮度过高
TR0=1;
pwm_duty=10;//亮度10%
break;
case 2:
pwm_duty=50;//亮度50%
break;
case 3:
pwm_duty=90;//亮度90%
break;
case 4:
L1=1;
TR0=0;//熄灭
break;
}
}
void scankey()//按键扫描
{
if(S7==0)
{
Delay(10);
if(S7==0)
{
stat++;
if(stat==5)
stat=1;
while(S7==0);//防止重复扫描
}
}
}
//=========================定时器模块
void InitTimer0()
{
TMOD=0x01;
TH0=(65535-100)/256;
TL0=(65535-100)%256;
EA=1;
ET0=1;
}
unsigned char count=0;
void ServiceT0()interrupt 1
{
TH0=(65535-100)/256;
TL0=(65535-100)%256;
count++;
if(count<pwm_duty)
L1=0;
else if(count==pwm_duty)
L1=1;
else if(count==100)
{
count=0;
L1=0;
}
}
//=========================
void main()
{
InitHC573();//LED模式
InitTimer0();//初始化T0
L1=1;//上电L1熄灭
while(1)
{
scankey();
LEDRunning();
}
}
感悟:单片机是要软件和硬件结合起来玩的,只考虑编程不考虑硬件情况,就可能在细节上栽跟头。希望自己能更清楚单片机各个外设的底层原理,理解单片机是如何通过控制IO口和不断扫描读取执行来实现外设的功能的。其中,“扫描”是单片机初学时经常遇到的概念,我认为不仅读取按键状态是一种扫描,反复执行某段代码、某个函数也是一种扫描,单片机的应用是在死循环“不断扫描”代码中实现的。循环、重复的操作,在人看来枯燥乏味,执行起来效率也很低,对单片机来说却不是这样。单片机CPU速度很快,不同单片机的速度也不尽相同,这一点编写代码时必须考虑。不同于电脑有操作系统,可以多线程运行,单片机是单线程运行却用很快的循环速度弥补了这一缺陷,使得它可以实现多种功能。也正因为单片机单线程运行的特点,在写代码时要注意避免长时间的空指令或局部死循环,以免浪费CPU的运行资源。