打印
[51单片机]

从单片机基础到程序框架(连载)

[复制链接]
楼主: jianhong_wu
手机看帖
扫描二维码
随时随地手机跟帖
381
jianhong_wu|  楼主 | 2018-4-12 09:32 | 只看该作者 |只看大图 回帖奖励 |倒序浏览
本帖最后由 jianhong_wu 于 2018-4-12 19:36 编辑

第一百一十三节: 动态扫描的数码管显示数字。
第一百一十三节_pdf文件.pdf (99.13 KB)
【113.1   动态扫描的数码管。】
   
                上图113.1.1  数码管

       上一节,看到打开显示的数码管右起第1个(com4)和第3个(com2)在任意时刻显示的数字是一样的,为什么?因为四个数码管的8个段码a,b,c,d,e,f,g,h所连接的单片机IO口是共用的,如果把四个数码管全部打开(com1,com2,com3,com4全部输出低电平),会发现四个数码管在任意时刻显示的四个数字也是一样的!实际应用中,要四个数码管能各自独立显示不同的数字,就需要用到“分时动态扫描”的方式。所谓分时,就是在任意时刻只能显示其中一个数码管(某个com输出低电平),其它三个数码管关闭(其它三个com输出高电平),每个数码管显示停留的时间固定一致并且非常短暂,四个数码管依次循环的切换显示,只要切换画面的速度足够快,人的视觉就分辨不出来,感觉八个数码管是同时亮的(实际不是同时亮),跟动画片“1秒钟动态切换显示多少幅画面”的原理一样。现在编写一个程序例子,四个数码管要显示四个不同的数字“1234”,程序代码如下:
#include "REG52.H"  

/* 注释一:
*  SCAN_TIME是每个数码管停留显示的短暂时间。这里称为“扫描时间”。这个时间既不能太长也不能
*  太短,要调试到恰到好处。太长,则影响其它数码管的显示,会让人觉得画面不连贯不是同时亮;
*  太短,又会影响显示的亮度。具体的时间应该根据实际项目不断调试修正而得到最佳显示的数值。
*/

#define SCAN_TIME  1   


void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;

void DisplayScan(void);    //数码管的动态扫描函数,放在定时中断里。

sbit P1_0=P1^0;  //右起第1位数码管的公共端com4
sbit P1_1=P1^1;  //右起第2位数码管的公共端com3
sbit P1_2=P1^2;  //右起第3位数码管的公共端com2
sbit P1_3=P1^3;  //右起第4位数码管的公共端com1

//根据原理图得出的共阴数码管编码转换表,类似于一个字库表
code unsigned char Cu8DigTable[]=
{
0x3f,  //0       序号0
0x06,  //1       序号1
0x5b,  //2       序号2
0x4f,  //3       序号3
0x66,  //4       序号4
0x6d,  //5       序号5
0x7d,  //6       序号6
0x07,  //7       序号7
0x7f,  //8       序号8
0x6f,  //9       序号9
0x00,  //不显示  序号10
};

volatile unsigned char vGu8ScanTimerFlag=0;  //动态扫描的定时器
volatile unsigned int vGu16ScanTimerCnt=0;  

/* 注释二:
*  vGu8Display_Righ_4,vGu8Display_Righ_3,vGu8Display_Righ_2,vGu8Display_Righ_1,这四个
*  全局变量用来传递每位数码管需要显示的数字,作为对上面应用层调用的接口变量。
*/

volatile unsigned char vGu8Display_Righ_4=1;  //右起第4位数码管显示的变量。这里显示“1”
volatile unsigned char vGu8Display_Righ_3=2;  //右起第3位数码管显示的变量。这里显示“2”
volatile unsigned char vGu8Display_Righ_2=3;  //右起第2位数码管显示的变量。这里显示“3”
volatile unsigned char vGu8Display_Righ_1=4;  //右起第1位数码管显示的变量。这里显示“4”

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)  
{  
    }
}

/* 注释三:
*  DisplayScan数码管的动态扫描函数,之所以放在定时中断里,是因为动态扫描数码管对时间均匀度
*  要求很高,如果放在main主函数中,期间稍微出现一些延时滞后或者超前执行的情况,都会导致
*  数码管出现“闪烁”或者“忽暗忽亮”的显示效果。
*/

void DisplayScan(void)   
{
static unsigned char Su8GetCode;  //从编码转换表中提取出来的编码。
static unsigned char Su8ScanStep=1;  //扫描步骤

if(0==vGu16ScanTimerCnt)  //定时的时间到,切换显示下一个数码管,依次动态快速循环切换显示
{

/* 注释四:
*  在即将切换显示到下一个新的数码管之前,应该先关闭显示所有的数码管,避免因关闭不彻底而导致
*  数码管某些段位出现“漏光”,也就是数码管因程序处理不善而出现常见的“鬼影”显示情况。
*/

    P0=0x00; //输出显示先清零,先关闭显示所有的数码管
    //先关闭所有的com口,先关闭显示所有的数码管
P1_0=1;  //右起第1位数码管的公共端com4,“总开关”关闭,输出低电平1
P1_1=1;  //右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
P1_2=1;  //右起第3位数码管的公共端com2,“总开关”关闭,输出低电平1
P1_3=1;  //右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1

    switch(Su8ScanStep)
{
       case 1: //显示右起第1个数码管
Su8GetCode=Cu8DigTable[vGu8Display_Righ_1]; //从编码转换表中提取出来的编码。
    P0=Su8GetCode; //段码端输出需要显示的编码
P1_0=0;  //右起第1位数码管的公共端com4,“总开关”打开,输出低电平0
P1_1=1;  //右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
P1_2=1;  //右起第3位数码管的公共端com2,“总开关”关闭,输出高电平1
P1_3=1;  //右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1            
break;

       case 2: //显示右起第2个数码管
Su8GetCode=Cu8DigTable[vGu8Display_Righ_2]; //从编码转换表中提取出来的编码。
    P0=Su8GetCode; //段码端输出需要显示的编码
P1_0=1;  //右起第1位数码管的公共端com4,“总开关”关闭,输出高电平1
P1_1=0;  //右起第2位数码管的公共端com3,“总开关”打开,输出低电平0
P1_2=1;  //右起第3位数码管的公共端com2,“总开关”关闭,输出高电平1
P1_3=1;  //右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1            
break;

       case 3: //显示右起第3个数码管
Su8GetCode=Cu8DigTable[vGu8Display_Righ_3]; //从编码转换表中提取出来的编码。
    P0=Su8GetCode; //段码端输出需要显示的编码
P1_0=1;  //右起第1位数码管的公共端com4,“总开关”关闭,输出高电平1
P1_1=1;  //右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
P1_2=0;  //右起第3位数码管的公共端com2,“总开关”打开,输出低电平0
P1_3=1;  //右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1            
break;

       case 4: //显示右起第4个数码管
Su8GetCode=Cu8DigTable[vGu8Display_Righ_4]; //从编码转换表中提取出来的编码。
    P0=Su8GetCode; //段码端输出需要显示的编码
P1_0=1;  //右起第1位数码管的公共端com4,“总开关”关闭,输出高电平1
P1_1=1;  //右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
P1_2=1;  //右起第3位数码管的公共端com2,“总开关”关闭,输出高电平1
P1_3=0;  //右起第4位数码管的公共端com1,“总开关”打开,输出低电平0            
break;

}

Su8ScanStep++;
if(Su8ScanStep>4) //如果扫描步骤大于4,继续从第1步开始扫描
{
Su8ScanStep=1;

}

vGu8ScanTimerFlag=0;
vGu16ScanTimerCnt=SCAN_TIME;  
vGu8ScanTimerFlag=1;  //启动新一轮的定时器
}
}

void T0_time() interrupt 1     
{
DisplayScan(); //数码管的动态扫描函数

if(1==vGu8ScanTimerFlag&&vGu16ScanTimerCnt>0)  //数码管显示切换时间的定时器
{
vGu16ScanTimerCnt--;
}

   
TH0=0xfc;   
TL0=0x66;   
}


void SystemInitial(void)
{
//初始化上电瞬间数码管的状态,关闭显示所有的数码管
P0=0x00;
P1_0=1;  //右起第1位数码管的公共端com4,“总开关”关闭,输出低电平1
P1_1=1;  //右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
P1_2=1;  //右起第3位数码管的公共端com2,“总开关”关闭,输出低电平1
P1_3=1;  //右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1
   
TMOD=0x01;  
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;      
}

void Delay(unsigned long u32DelayTime)
{
    for(;u32DelayTime>0;u32DelayTime--);
}

void PeripheralInitial(void)
{

}






使用特权

评论回复
382
y15067805290| | 2018-4-12 16:29 | 只看该作者

期待楼主给我们带来更多的干货哦~~

使用特权

评论回复
383
wangwenwuaisiyu| | 2018-4-13 09:22 | 只看该作者
支持一下楼主

使用特权

评论回复
384
wangwenwuaisiyu| | 2018-4-13 16:20 | 只看该作者
讲的太好了,看了一天都不觉的的累,越看越来劲了,

使用特权

评论回复
385
michael1982| | 2018-4-13 18:33 | 只看该作者
膜拜大牛

使用特权

评论回复
386
jianhong_wu|  楼主 | 2018-4-16 12:53 | 只看该作者
第一百一十四节: 动态扫描的数码管显示小数点。
第一百一十四节_pdf文件.pdf (78.88 KB)
【114.1   动态扫描的数码管显示小数点。】
   
                上图114.1.1  数码管

       如上图,小数点的段码是h,对应单片机的P0.7口。数码管编码转换表(类似字库)的11个以字节为单位的数据,把它们从十六进制转换成二进制后,可以发现第7位(对应P0.7口)都是0。因此,从转换表里取数据后,得到的数据默认是让数码管的小数点不显示的。如果想显示这个小数点,就需要用到“或(|)”语句操作。比如,本节程序需要显示“1.234”这个带小数点的数值,代码如下:



#include "REG52.H"  

#define SCAN_TIME  1   

void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;

void DisplayScan(void);   

sbit P1_0=P1^0;  
sbit P1_1=P1^1;  
sbit P1_2=P1^2;  
sbit P1_3=P1^3;

//转换表,里面的11个数据,转换成二进制后,第7位数据都是0默认不显示小数点
code unsigned char Cu8DigTable[]=
{
0x3f,  //0       序号0
0x06,  //1       序号1
0x5b,  //2       序号2
0x4f,  //3       序号3
0x66,  //4       序号4
0x6d,  //5       序号5
0x7d,  //6       序号6
0x07,  //7       序号7
0x7f,  //8       序号8
0x6f,  //9       序号9
0x00,  //不显示  序号10
};

volatile unsigned char vGu8ScanTimerFlag=0;  
volatile unsigned int vGu16ScanTimerCnt=0;  


volatile unsigned char vGu8Display_Righ_4=1;  //右起第4位数码管显示的变量。这里显示“1”
volatile unsigned char vGu8Display_Righ_3=2;  //右起第3位数码管显示的变量。这里显示“2”
volatile unsigned char vGu8Display_Righ_2=3;  //右起第2位数码管显示的变量。这里显示“3”
volatile unsigned char vGu8Display_Righ_1=4;  //右起第1位数码管显示的变量。这里显示“4”

/* 注释一:
*  vGu8Display_Righ_Dot_4,vGu8Display_Righ_Dot_3,vGu8Display_Righ_Dot_2,
*  vGu8Display_Righ_Dot_1,这四个全局变量用来传递每位数码管是否需要显示它的小数点,如果是1
*  代表需要显示其小数点,如果是0则不显示小数点。这四个变量作为对上面应用层调用的接口变量。
*/

volatile unsigned char vGu8Display_Righ_Dot_4=1;  //右起第4位数码管的小数点。1代表打开显示。
volatile unsigned char vGu8Display_Righ_Dot_3=0;  //右起第3位数码管的小数点。0代表关闭显示。
volatile unsigned char vGu8Display_Righ_Dot_2=0;  //右起第2位数码管的小数点。0代表关闭显示。
volatile unsigned char vGu8Display_Righ_Dot_1=0;  //右起第1位数码管的小数点。0代表关闭显示。

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)  
{  
    }
}


void DisplayScan(void)   
{
static unsigned char Su8GetCode;  
static unsigned char Su8ScanStep=1;  

if(0==vGu16ScanTimerCnt)  
{


    P0=0x00;
P1_0=1;  
P1_1=1;  
P1_2=1;  
P1_3=1;  

    switch(Su8ScanStep)
{
       case 1:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_1];

/* 注释二:
*  这里是本节的关键。通过判断全局的接口变量的数值,来决定是否打开显示小数点。
*  从转换表取出字模数据后再跟0x80进行“或”运算即可把第7位数据改为1。
*/

if(1==vGu8Display_Righ_Dot_1) //如果打开了需要显示第1个数码管的小数点
{
Su8GetCode=Su8GetCode|0x80;  //把第7位数据改为1,显示小数点
}
    P0=Su8GetCode;
P1_0=0;
P1_1=1;  
P1_2=1;  
P1_3=1;            
break;

       case 2:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_2];
if(1==vGu8Display_Righ_Dot_2) //如果打开了需要显示第2个数码管的小数点
{
Su8GetCode=Su8GetCode|0x80;  //把第7位数据改为1,显示小数点
}
    P0=Su8GetCode;
P1_0=1;  
P1_1=0;
P1_2=1;
P1_3=1;         
break;

       case 3:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_3];
if(1==vGu8Display_Righ_Dot_3) //如果打开了需要显示第3个数码管的小数点
{
Su8GetCode=Su8GetCode|0x80;  //把第7位数据改为1,显示小数点
}
    P0=Su8GetCode;
P1_0=1;  
P1_1=1;  
P1_2=0;  
P1_3=1;         
break;

       case 4:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_4];
if(1==vGu8Display_Righ_Dot_4) //如果打开了需要显示第4个数码管的小数点
{
Su8GetCode=Su8GetCode|0x80;  //把第7位数据改为1,显示小数点
}
    P0=Su8GetCode;
P1_0=1;  
P1_1=1;  
P1_2=1;  
P1_3=0;           
break;

}

Su8ScanStep++;
if(Su8ScanStep>4)
{
Su8ScanStep=1;
}

vGu8ScanTimerFlag=0;
vGu16ScanTimerCnt=SCAN_TIME;  
vGu8ScanTimerFlag=1;  
}
}

void T0_time() interrupt 1     
{
DisplayScan();

if(1==vGu8ScanTimerFlag&&vGu16ScanTimerCnt>0)  
{
vGu16ScanTimerCnt--;
}


TH0=0xfc;   
TL0=0x66;   
}


void SystemInitial(void)
{
P0=0x00;
P1_0=1;  
P1_1=1;  
P1_2=1;  
P1_3=1;  

TMOD=0x01;  
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;      
}

void Delay(unsigned long u32DelayTime)
{
    for(;u32DelayTime>0;u32DelayTime--);
}

void PeripheralInitial(void)
{

}


使用特权

评论回复
387
jianhong_wu|  楼主 | 2018-4-22 12:07 | 只看该作者
第一百一十五节: 按键控制数码管的秒表。
第一百一十五节_pdf文件.pdf (128.74 KB)
【115.1   按键控制数码管的秒表。】
   
                上图115.1.1  数码管




                上图115.1.2  独立按键

       本节通过一个秒表的小项目,让大家学会以下4个知识点:
      (1)上层的界面显示框架几乎都要用到更新变量,更新变量包括整屏更新和局部更新,本节只用到整屏更新。更新变量是用全局变量在函数之间传递信息。作用是,当有某个需要显示的数据发生改变的时候,就要给更新变量置1,让显示函数重新更新一次显示,确保最新的数据能及时显示出来,平时没有数据更新改变的时候不用频繁更新显示避免占用CPU过多的时间。
      (2)凡是需要显示数字的地方,都必须涉及如何把一个数据按“个十百千万...”的位逐个提取出来的算法。这个算法比较简单,主要用“求余”和“求商”这两个运算语句就可以随心所欲的把数据位提取出来。除此之外,还要学会如何用if语句判断数据的范围,来把高位尚未用到的某个数码管屏蔽,让该位数码管只显示一个“不显示”的数据(避免直接显示一个0)。
      (3)我常把单片机程序简化成4个代表:按键(人机输入),数码管(人机界面),跑马灯(应用程序),串口(通信)。本节的“应用程序”不是跑马灯,而是秒表。不管是跑马灯,还是秒表,都要用到一个总启动Gu8RunStart和一个总运行步骤Gu8RunStep。建议大家,总启动Gu8RunStart和总运行步骤Gu8RunStep应该成双成对的出现(这样关断响应更及时,并且结构更紧凑,漏洞更少),比如,凡是总启动Gu8RunStart发生改变的时候,总运行步骤Gu8RunStep都复位归零一下。
      (4)一个硬件的定时器中断,可以衍生出N个软件定时器,之前跟大家介绍的是“递减式”的软件定时器,而且实际应用中,“递减式”的软件定时器也是用得最多。本节因为项目的需要,需要用到的是“累加式”的软件定时器。不管是哪种软件定时器,大家都要注意定时器变量在定义时所用到的数据类型,这个数据类型决定了定时时间的长度,比如在51单片机中,unsigned int的范围是0到65535,最大一次性定时65.535秒。而unsigned long的范围是0到4294967295,最大一次性定时4294967.295秒。本节秒表的时间超过65.535秒,因此需要用到unsigned long类型的定时器变量。
本节秒表程序的功能:K1按键是复位按键,每按一次,秒表都停止并且重新归零。K2按键是启动和暂停按键,当秒表处于复位后停止的状态时按一次则开始启动,当秒表处于正在工作的状态时按一次则处于暂停状态,当秒表处于暂停的状态时按一次则继续处于工作的状态。本节4位数码管,显示的时间是带2位小数点的,能显示的时间范围是:0.00秒到99.99秒。代码如下:

#include "REG52.H"  

#define KEY_FILTER_TIME  25

#define SCAN_TIME  1   

void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;

void KeyScan(void);  
void KeyTask(void);  

void DisplayScan(void);  //底层显示的驱动函数  
void DisplayTask(void);  //上层显示的任务函数

void RunTask(void);  //秒表的应用程序

sbit KEY_INPUT1=P2^2;  
sbit KEY_INPUT2=P2^1;  

sbit P1_0=P1^0;  
sbit P1_1=P1^1;  
sbit P1_2=P1^2;  
sbit P1_3=P1^3;

//数码管转换表
code unsigned char Cu8DigTable[]=
{
0x3f,  //0       序号0
0x06,  //1       序号1
0x5b,  //2       序号2
0x4f,  //3       序号3
0x66,  //4       序号4
0x6d,  //5       序号5
0x7d,  //6       序号6
0x07,  //7       序号7
0x7f,  //8       序号8
0x6f,  //9       序号9
0x00,  //不显示  序号10
};

//数码管底层驱动扫描的软件定时器
volatile unsigned char vGu8ScanTimerFlag=0;  
volatile unsigned int vGu16ScanTimerCnt=0;  

//秒表的软件定时器,注意,这里是unsigned long类型,范围是0到4294967295毫秒
volatile unsigned char vGu8StopWatchTimerFlag=0;  
volatile unsigned long vGu32StopWatchTimerCnt=0;  

//数码管上层每10ms就定时刷新一次显示的软件定时器。用于及时更新显示秒表当前的实时数值
volatile unsigned char vGu8UpdateTimerFlag=0;  
volatile unsigned int vGu16UpdateTimerCnt=0;  


unsigned char Gu8RunStart=0;  //应用程序的总启动
unsigned char Gu8RunStep=0;  //应用程序的总运行步骤。建议跟vGu8RunStart成双成对出现
unsigned char Gu8RunStatus=0; //当前秒表的状态。0代表停止,1代表正在工作中,2代表暂停

unsigned char Gu8WdUpdate=1;  //开机默认整屏更新一次显示。此变量在显示框架中是非常重要的变量

volatile unsigned char vGu8Display_Righ_4=10;  //开机默认最高位数码管显示一个“不显示”数据
volatile unsigned char vGu8Display_Righ_3=0;  
volatile unsigned char vGu8Display_Righ_2=0;  
volatile unsigned char vGu8Display_Righ_1=0;  

volatile unsigned char vGu8Display_Righ_Dot_4=0;  
volatile unsigned char vGu8Display_Righ_Dot_3=1;    //开机默认保留显示2个小数点
volatile unsigned char vGu8Display_Righ_Dot_2=0;  
volatile unsigned char vGu8Display_Righ_Dot_1=0;  

volatile unsigned char vGu8KeySec=0;  

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)  
{  
KeyTask();      //按键的任务函数
DisplayTask();  //数码管显示的上层任务函数
RunTask();      //秒表的应用程序
    }
}

void KeyTask(void)    //按键的任务函数
{
if(0==vGu8KeySec)
{
return;
}

switch(vGu8KeySec)
{
   case 1:     //复位按键

Gu8RunStatus=0; //秒表返回停止的状态

Gu8RunStart=0;  //秒表停止
            Gu8RunStep=0;  //总运行步骤归零。建议跟vGu8RunStart成双成对出现

vGu8StopWatchTimerFlag=0;  
vGu32StopWatchTimerCnt=0;   //秒表的软件定时器清零

            Gu8WdUpdate=1;  //整屏更新一次显示

vGu8KeySec=0;  
break;

   case 2:     //启动与暂停的按键
        if(0==Gu8RunStatus) //在停止状态下
        {
            Gu8RunStatus=1;  //秒表处于工作状态

vGu8StopWatchTimerFlag=0;
vGu32StopWatchTimerCnt=0;  
vGu8StopWatchTimerFlag=1;  //启动秒表的软件定时器


Gu8RunStart=1;   //秒表总开关启动
                Gu8RunStep=0;    //总运行步骤归零。建议跟vGu8RunStart成双成对出现
}
        else if(1==Gu8RunStatus) //在工作状态下
        {
            Gu8RunStatus=2;  //秒表处于暂停状态
}
        else  //在暂停状态下
        {
            Gu8RunStatus=1;  //秒表处于工作状态
}

        Gu8WdUpdate=1;  //整屏更新一次显示,确保在暂停的时候能显示到最新的数据

vGu8KeySec=0;
break;

}
}

void DisplayTask(void) //数码管显示的上层任务函数
{
//需要借用的中间变量,用来拆分数据位
static unsigned char Su8Temp_4,Su8Temp_3,Su8Temp_2,Su8Temp_1; //需要借用的中间变量

/* 注释一:
*  此处为什么要多加4个中间过渡变量Su8Temp_X?是因为vGu32StopWatchTimerCnt分解数据的时候
*  需要进行除法和求余数的运算,就会用到好多条指令,就会耗掉一点时间,类似延时了一会。我们
*  的定时器每隔一段时间都会产生中断,然后在中断里驱动数码管显示,当vGu32StopWatchTimerCnt
*  还没完全分解出4位有效数据时,这个时候来的定时中断,就有可能导致显示的数据瞬间产生不完整,
*  影响显示效果。因此,为了把需要显示的数据过渡最快,所以采取了先分解,再过渡显示的方法。
*/

if(1==Gu8WdUpdate) //如果需要整屏更新
{
Gu8WdUpdate=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

          //先分解数据

          //Su8Temp_4提取“十秒”位。
Su8Temp_4=vGu32StopWatchTimerCnt/10000; //实际精度是0.0001秒,但显示精度是0.01秒
Su8Temp_4=Su8Temp_4%10;

         //Su8Temp_3提取“个秒”位。
Su8Temp_3=vGu32StopWatchTimerCnt/1000; //实际精度是0.0001秒,但显示精度是0.01秒
Su8Temp_3=Su8Temp_3%10;

         //Su8Temp_2提取“百毫秒”位。
Su8Temp_2=vGu32StopWatchTimerCnt/100; //实际精度是0.0001秒,但显示精度是0.01秒
Su8Temp_2=Su8Temp_2%10;

          //Su8Temp_1提取“十毫秒”位。
Su8Temp_1=vGu32StopWatchTimerCnt/10; //实际精度是0.0001秒,但显示精度是0.01秒
Su8Temp_1=Su8Temp_1%10;

          //判断数据范围,来决定最高位数码管是否需要显示。
          if(vGu32StopWatchTimerCnt<10000) //10.000秒。实际4位数码管最大只能显示99.99秒
          {
             Su8Temp_4=10;  //在数码管转换表里,10代表一个“不显示”的数据
}

//上面先分解数据之后,再过渡需要显示的数据到底层驱动变量里,让过渡的时间越短越好
vGu8Display_Righ_4=Su8Temp_4;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_3=Su8Temp_3;  
vGu8Display_Righ_2=Su8Temp_2;  
vGu8Display_Righ_1=Su8Temp_1;  

vGu8Display_Righ_Dot_4=0;  
vGu8Display_Righ_Dot_3=1;   //保留显示2位小数点
vGu8Display_Righ_Dot_2=0;  
vGu8Display_Righ_Dot_1=0;  

}
}

void RunTask(void)  //秒表的应用程序
{
if(0==Gu8RunStart)
{
   return;  // 如果秒表处于停止状态,则直接退出当前函数,不执行该函数以下的其它代码
}

switch(Gu8RunStep)
{
    case 0:  //在这个步骤里,主要用来初始化一些参数

vGu8UpdateTimerFlag=0;
vGu16UpdateTimerCnt=10;  //每10ms更新显示一次当前秒表的时间
vGu8UpdateTimerFlag=1;

Gu8RunStep=1; //跳转到每10ms更新显示一次的步骤里
break;

    case 1:  //每10ms更新一次显示,确保实时显示秒表当前的时间
       if(0==vGu16UpdateTimerCnt) //每10ms更新显示一次当前秒表的时间
       {
    vGu8UpdateTimerFlag=0;
vGu16UpdateTimerCnt=10;  //重置定时器,为下一个10ms更新做准备
vGu8UpdateTimerFlag=1;

            Gu8WdUpdate=1;  //整屏更新一次显示当前秒表的时间
}
break;

}

}


void KeyScan(void)  //按键底层的驱动扫描函数,放在定时中断函数里
{
   static unsigned char Su8KeyLock1;
   static unsigned int  Su16KeyCnt1;
   static unsigned char Su8KeyLock2;
   static unsigned int  Su16KeyCnt2;

   if(0!=KEY_INPUT1)
   {
      Su8KeyLock1=0;
      Su16KeyCnt1=0;   
   }
   else if(0==Su8KeyLock1)
   {
      Su16KeyCnt1++;
      if(Su16KeyCnt1>=KEY_FILTER_TIME)
      {
         Su8KeyLock1=1;  
         vGu8KeySec=1;   
      }
   }

   if(0!=KEY_INPUT2)
   {
      Su8KeyLock2=0;
      Su16KeyCnt2=0;      
   }
   else if(0==Su8KeyLock2)
   {
      Su16KeyCnt2++;
      if(Su16KeyCnt2>=KEY_FILTER_TIME)
      {
         Su8KeyLock2=1;  
         vGu8KeySec=2;   
      }
   }
}

void DisplayScan(void)    //数码管底层的驱动扫描函数,放在定时中断函数里
{
static unsigned char Su8GetCode;  
static unsigned char Su8ScanStep=1;  

if(0==vGu16ScanTimerCnt)  
{


    P0=0x00;
P1_0=1;  
P1_1=1;  
P1_2=1;  
P1_3=1;  

    switch(Su8ScanStep)
{
       case 1:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_1];

if(1==vGu8Display_Righ_Dot_1)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=0;
P1_1=1;  
P1_2=1;  
P1_3=1;            
break;

       case 2:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_2];
if(1==vGu8Display_Righ_Dot_2)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=1;  
P1_1=0;
P1_2=1;
P1_3=1;         
break;

       case 3:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_3];
if(1==vGu8Display_Righ_Dot_3)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=1;  
P1_1=1;  
P1_2=0;  
P1_3=1;         
break;

       case 4:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_4];
if(1==vGu8Display_Righ_Dot_4)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=1;  
P1_1=1;  
P1_2=1;  
P1_3=0;           
break;

}

Su8ScanStep++;
if(Su8ScanStep>4)
{
Su8ScanStep=1;
}

vGu8ScanTimerFlag=0;
vGu16ScanTimerCnt=SCAN_TIME;  
vGu8ScanTimerFlag=1;  
}
}

void T0_time() interrupt 1     
{
KeyScan();      //按键底层的驱动扫描函数
DisplayScan();  //数码管底层的驱动扫描函数

if(1==vGu8ScanTimerFlag&&vGu16ScanTimerCnt>0)  
{
vGu16ScanTimerCnt--;  //递减式的软件定时器
}

//每10ms就定时更新一次显示的软件定时器
if(1==vGu8UpdateTimerFlag&&vGu16UpdateTimerCnt>0)  
{
vGu16UpdateTimerCnt--;  //递减式的软件定时器
}

//秒表实际走的时间的软件定时器,注意,这里是“累加式”的软件定时器
if(1==vGu8StopWatchTimerFlag&&1==Gu8RunStatus) //秒表处于工作的状态
{
vGu32StopWatchTimerCnt++;  //累加式的软件定时器
}


TH0=0xfc;   
TL0=0x66;   
}


void SystemInitial(void)
{
P0=0x00;
P1_0=1;  
P1_1=1;  
P1_2=1;  
P1_3=1;  

TMOD=0x01;  
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;      
}

void Delay(unsigned long u32DelayTime)
{
    for(;u32DelayTime>0;u32DelayTime--);
}

void PeripheralInitial(void)
{

}


使用特权

评论回复
388
arima| | 2018-4-23 12:36 | 只看该作者
加油!!!

使用特权

评论回复
389
伯乐121| | 2018-4-24 09:43 | 只看该作者

本公司诚聘电子工程师:
一.岗位要求
1. 本科以上学历,机电一体化、电子、电子信息等电子类相关专业;
2.2年以上电子设计相关经验
3. 熟悉基本的数字电路和模拟电路,能分析常见电路的工作原理
4. 熟练掌握单片机C语言编程,有良好的编程风格;至少熟练使用一种单片机(STM8/STM32等)进行产品开发
5. 良好的逻辑思维能力,能比较好的对产品进行整体把控;
6. 能积极主动解决产品设计中遇到的各种问题
7. 具有良好的团队合作精神,服从工作安排
8. 参加电子设计大赛并获奖者,或参加过学校电子协会的优先;
9、要求英语4级以上,能较流畅的阅读元器件英文版datasheet
10、有较好的学习能力和一定的抗压能力,能够独立解决技术问题,根据项目需要主动学习和掌握新的技术。

二.岗位职责
从事智能门锁(指纹密码锁、酒店锁、柜锁、联网锁等)的软硬件设计
具体职责包括:1. 客户需求整理(客户提供功能定义、电路板尺寸图等)
2. 原理图设计和审核,包括指纹、触摸密码、13.56MHz卡片读写、125KHz卡片读写、语音、遥控、OLED液晶显示等,这些都已经实现了模块化,只需根据客户要求做少量调整;
3.重点是 MCU单片机程序编写,主要使用STM8/STM32
4. 生产文档制作,包括物料清单、生产指导书、生产测试文档等,都是基于公司的模板来做。
5. 编写产品使用说明书
6.各种新技术/新方案的研发和集成




三.补充职位亮点
1.根据个人能力月薪8-12K,按国家法定节假日放假,过试用期购买保险。
2一般有月平均实发工资的2倍左右作为年终奖              
3.每天有工作下午茶:水果、点心、咖啡等
4.每几月不定期的举行集体活动:徒步、登山、看电影、烧烤等。
5.一年两次旅游
工作地点:东莞市石碣镇
联系人:李'S       QQ:244714897

使用特权

评论回复
390
chentiaotiao| | 2018-4-25 14:36 | 只看该作者
jianhong_wu 发表于 2017-9-4 19:36
第八十五节: 定时中断的寄存器配置。

【85.1   寄存器配置的本质。】

吴老师,如果我把案例里的if判断语句>=改成<=的话,我发现LED灯还是会闪烁,而且频率更快了,这是怎么回事呢?

使用特权

评论回复
391
yhchen2001| | 2018-4-26 14:00 | 只看该作者
为无私的老师点赞!

使用特权

评论回复
392
xiaofengfeng| | 2018-4-28 22:41 | 只看该作者
希望中国工程师 联合

使用特权

评论回复
393
jianhong_wu|  楼主 | 2018-4-30 11:50 | 只看该作者
第一百一十六节: 按键控制数码管的倒计时。
第一百一十六节_pdf文件.pdf (128.97 KB)
【116.1   按键控制数码管的倒计时。】
  
                上图116.1.1  数码管




                上图116.1.2  独立按键

         
                上图116.1.3  有源蜂鸣器

       上一节讲“累加式”的秒表,这一节讲“递减式”的倒计时。通过此小项目,加深理解在显示框架中常用的更新变量(整屏更新和局部更新),以及应用程序与按键是如何关联的框架。同时,在拆分“个十百千万...”的时候,有一处地方必须注意,“先整除后求余”必须用一行代码一气呵成,不能拆分成两行代码“先整除;后求余”,否则会有隐患会有bug。除非,把四个临时变都改成unsigned long类型。上一节在此处残留了一个bug我发帖后还来不及修改过来,敬请大家注意。比如:

        以下这样是对的:
static unsigned char Su8Temp_1;
Su8Temp_1=vGu32CountdownTimerCnt/10%10; //一气呵成,没毛病。


        以下这样是有隐患有bug的(除非把Su8Temp_1改成unsigned long类型):
static unsigned char Su8Temp_1;
Su8Temp_1=vGu32CountdownTimerCnt/10;
Su8Temp_1=Su8Temp_1%10; //拆分成两行代码后,有隐患有bug。数据溢出的原因引起。


        本节倒计时程序的功能:K1按键是复位按键,每按一次,倒计时停止并且重新恢复初始值10.00秒。K2按键是启动按键,当秒表处于复位后停止的状态时按一次则开始启动倒计时,当倒计时变为0.00秒的时候,蜂鸣器发出一次“滴”的提示声。此时,如果想启动新一轮的倒计时,只需按一次K1复位键,然后再按K2启动按键,就可以启动新一轮10.00秒的倒计时。代码如下:

#include "REG52.H"  

#define KEY_FILTER_TIME  25

#define SCAN_TIME  1   

#define VOICE_TIME   50     //蜂鸣器一次“滴”的声音长度 50ms  

void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;

void KeyScan(void);  
void KeyTask(void);  

void VoiceScan(void);  //蜂鸣器的驱动函数
void DisplayScan(void);  //底层显示的驱动函数  
void DisplayTask(void);  //上层显示的任务函数

void RunTask(void);  //倒计时的应用程序

void BeepOpen(void);   
void BeepClose(void);

sbit KEY_INPUT1=P2^2;  
sbit KEY_INPUT2=P2^1;  

sbit P1_0=P1^0;  
sbit P1_1=P1^1;  
sbit P1_2=P1^2;  
sbit P1_3=P1^3;

sbit P3_4=P3^4;

//数码管转换表
code unsigned char Cu8DigTable[]=
{
0x3f,  //0       序号0
0x06,  //1       序号1
0x5b,  //2       序号2
0x4f,  //3       序号3
0x66,  //4       序号4
0x6d,  //5       序号5
0x7d,  //6       序号6
0x07,  //7       序号7
0x7f,  //8       序号8
0x6f,  //9       序号9
0x00,  //不显示  序号10
};

//数码管底层驱动扫描的软件定时器
volatile unsigned char vGu8ScanTimerFlag=0;  
volatile unsigned int vGu16ScanTimerCnt=0;  

//倒计时的软件定时器,注意,这里是unsigned long类型,范围是0到4294967295毫秒
volatile unsigned char vGu8CountdownTimerFlag=0;  
volatile unsigned long vGu32CountdownTimerCnt=10000;  //开机默认显示初始值10.000秒

//数码管上层每10ms就定时刷新一次显示的软件定时器。用于及时更新显示秒表当前的实时数值
volatile unsigned char vGu8UpdateTimerFlag=0;  
volatile unsigned int vGu16UpdateTimerCnt=0;  

//蜂鸣器的软件定时器
volatile unsigned char vGu8BeepTimerFlag=0;  
volatile unsigned int vGu16BeepTimerCnt=0;  

unsigned char Gu8RunStart=0;  //应用程序的总启动
unsigned char Gu8RunStep=0;  //应用程序的总运行步骤。建议跟vGu8RunStart成双成对出现
unsigned char Gu8RunStatus=0; //当前倒计时的状态。0代表停止,1代表正在工作中

unsigned char Gu8WdUpdate=1;  //开机默认整屏更新一次显示。此变量在显示框架中是非常重要的变量

volatile unsigned char vGu8Display_Righ_4=1;  //显示“1”,跟vGu32CountdownTimerCnt高位一致
volatile unsigned char vGu8Display_Righ_3=0;  
volatile unsigned char vGu8Display_Righ_2=0;  
volatile unsigned char vGu8Display_Righ_1=0;  

volatile unsigned char vGu8Display_Righ_Dot_4=0;  
volatile unsigned char vGu8Display_Righ_Dot_3=1;    //开机默认保留显示2个小数点
volatile unsigned char vGu8Display_Righ_Dot_2=0;  
volatile unsigned char vGu8Display_Righ_Dot_1=0;  

volatile unsigned char vGu8KeySec=0;  

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)  
{  
KeyTask();      //按键的任务函数
DisplayTask();  //数码管显示的上层任务函数
RunTask();      //倒计时的应用程序
    }
}

void KeyTask(void)    //按键的任务函数
{
if(0==vGu8KeySec)
{
return;
}

switch(vGu8KeySec)
{
   case 1:     //复位按键

Gu8RunStatus=0; //倒计时返回停止的状态

Gu8RunStart=0;  //倒计时的运行步骤的停止
            Gu8RunStep=0;  //总运行步骤归零。建议跟vGu8RunStart成双成对出现

vGu8CountdownTimerFlag=0;    //禁止倒计时开始(而Gu8RunStart是启动开关,不矛盾)
vGu32CountdownTimerCnt=10000;   //倒计时的软件定时器恢复初始值10.000秒

            Gu8WdUpdate=1;  //整屏更新一次显示

vGu8KeySec=0;  
break;

   case 2:     //启动的按键
        if(0==Gu8RunStatus) //在停止状态下
        {

vGu8CountdownTimerFlag=0;
vGu32CountdownTimerCnt=10000;  //初始值是10.000秒
vGu8CountdownTimerFlag=1;      //允许倒计时的软件定时器的启动

            Gu8RunStatus=1;  //倒计时处于工作状态(并且,这一瞬间才正式启动倒计时)

Gu8RunStart=1;   //倒计时的运行步骤的总开关开启
                Gu8RunStep=0;    //总运行步骤归零。建议跟vGu8RunStart成双成对出现

            Gu8WdUpdate=1;  //整屏更新一次显示,确保在启动的时候能显示到最新的数据
}


vGu8KeySec=0;
break;

}
}

void DisplayTask(void) //数码管显示的上层任务函数
{
//需要借用的中间变量,用来拆分数据位。
static unsigned char Su8Temp_4,Su8Temp_3,Su8Temp_2,Su8Temp_1; //需要借用的中间变量

if(1==Gu8WdUpdate) //如果需要整屏更新
{
Gu8WdUpdate=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

          //先分解数据,注意,这里分解的时候,“先整除后求余”必须用一行代码一气呵成,不能拆
          //分成两行代码,否则会有隐患会有bug。除非,把四个临时变都改成unsigned long类型。

          //Su8Temp_4提取“十秒”位。
Su8Temp_4=vGu32CountdownTimerCnt/10000%10; //实际精度是0.001秒,但显示精度是0.01秒

         //Su8Temp_3提取“个秒”位。
Su8Temp_3=vGu32CountdownTimerCnt/1000%10; //实际精度是0.001秒,但显示精度是0.01秒

         //Su8Temp_2提取“百毫秒”位。
Su8Temp_2=vGu32CountdownTimerCnt/100%10; //实际精度是0.001秒,但显示精度是0.01秒

          //Su8Temp_1提取“十毫秒”位。
Su8Temp_1=vGu32CountdownTimerCnt/10%10; //实际精度是0.001秒,但显示精度是0.01秒

          //判断数据范围,来决定最高位数码管是否需要显示。
          if(vGu32CountdownTimerCnt<10000) //10.000秒。实际4位数码管最大只能显示99.99秒
          {
             Su8Temp_4=10;  //在数码管转换表里,10代表一个“不显示”的数据
}

//上面先分解数据之后,再过渡需要显示的数据到底层驱动变量里,让过渡的时间越短越好
vGu8Display_Righ_4=Su8Temp_4;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_3=Su8Temp_3;  
vGu8Display_Righ_2=Su8Temp_2;  
vGu8Display_Righ_1=Su8Temp_1;  

vGu8Display_Righ_Dot_4=0;  
vGu8Display_Righ_Dot_3=1;   //保留显示2位小数点
vGu8Display_Righ_Dot_2=0;  
vGu8Display_Righ_Dot_1=0;  

}
}

void RunTask(void)  //倒计时的应用程序
{
if(0==Gu8RunStart)
{
   return;  // 如果总开关处于停止状态,则直接退出当前函数,不执行该函数以下的其它代码
}

switch(Gu8RunStep)
{
    case 0:  //在这个步骤里,主要用来初始化一些参数

vGu8UpdateTimerFlag=0;
vGu16UpdateTimerCnt=10;  //每10ms更新显示一次当前倒计时的时间
vGu8UpdateTimerFlag=1;

Gu8RunStep=1; //跳转到每10ms更新显示一次的步骤里
break;

    case 1:  //每10ms更新一次显示,确保实时显示当前倒计时的时间
       if(0==vGu16UpdateTimerCnt) //每10ms更新显示一次当前倒计时的时间
       {


    vGu8UpdateTimerFlag=0;
vGu16UpdateTimerCnt=10;  //重置定时器,为下一个10ms更新做准备
vGu8UpdateTimerFlag=1;

            Gu8WdUpdate=1;  //整屏更新一次显示当前倒计时的时间

            if(0==vGu32CountdownTimerCnt) //如果倒计时的时间到,则跳转到结束的步骤
            {
Gu8RunStep=2; //跳转到倒计时结束的步骤
}

}
break;

case 2:  //倒计时结束的步骤
//Gu8RunStatus=0; //这行代码注释掉,让每次新启动之前都必须按一次K1复位按键才有效

Gu8RunStart=0;  //倒计时的运行步骤的停止
            Gu8RunStep=0;   //总运行步骤归零。建议跟vGu8RunStart成双成对出现

vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=VOICE_TIME;  //蜂鸣器发出“滴”一声
vGu8BeepTimerFlag=1;  

        Gu8WdUpdate=1;  //整屏更新一次显示当前倒计时的时间

break;

}

}


void KeyScan(void)  //按键底层的驱动扫描函数,放在定时中断函数里
{
   static unsigned char Su8KeyLock1;
   static unsigned int  Su16KeyCnt1;
   static unsigned char Su8KeyLock2;
   static unsigned int  Su16KeyCnt2;

   if(0!=KEY_INPUT1)
   {
      Su8KeyLock1=0;
      Su16KeyCnt1=0;   
   }
   else if(0==Su8KeyLock1)
   {
      Su16KeyCnt1++;
      if(Su16KeyCnt1>=KEY_FILTER_TIME)
      {
         Su8KeyLock1=1;  
         vGu8KeySec=1;   
      }
   }

   if(0!=KEY_INPUT2)
   {
      Su8KeyLock2=0;
      Su16KeyCnt2=0;      
   }
   else if(0==Su8KeyLock2)
   {
      Su16KeyCnt2++;
      if(Su16KeyCnt2>=KEY_FILTER_TIME)
      {
         Su8KeyLock2=1;  
         vGu8KeySec=2;   
      }
   }
}

void DisplayScan(void)    //数码管底层的驱动扫描函数,放在定时中断函数里
{
static unsigned char Su8GetCode;  
static unsigned char Su8ScanStep=1;  

if(0==vGu16ScanTimerCnt)  
{


    P0=0x00;
P1_0=1;  
P1_1=1;  
P1_2=1;  
P1_3=1;  

    switch(Su8ScanStep)
{
       case 1:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_1];

if(1==vGu8Display_Righ_Dot_1)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=0;
P1_1=1;  
P1_2=1;  
P1_3=1;            
break;

       case 2:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_2];
if(1==vGu8Display_Righ_Dot_2)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=1;  
P1_1=0;
P1_2=1;
P1_3=1;         
break;

       case 3:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_3];
if(1==vGu8Display_Righ_Dot_3)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=1;  
P1_1=1;  
P1_2=0;  
P1_3=1;         
break;

       case 4:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_4];
if(1==vGu8Display_Righ_Dot_4)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=1;  
P1_1=1;  
P1_2=1;  
P1_3=0;           
break;

}

Su8ScanStep++;
if(Su8ScanStep>4)
{
Su8ScanStep=1;
}

vGu8ScanTimerFlag=0;
vGu16ScanTimerCnt=SCAN_TIME;  
vGu8ScanTimerFlag=1;  
}
}


void VoiceScan(void) //蜂鸣器的驱动函数
{

          static unsigned char Su8Lock=0;  

if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
          {
                  if(0==Su8Lock)
                  {
                   Su8Lock=1;  
BeepOpen();
     }
    else  
{     

                       vGu16BeepTimerCnt--;         

                   if(0==vGu16BeepTimerCnt)
                   {
                           Su8Lock=0;     
BeepClose();  
                   }

}
          }         
}

void BeepOpen(void)
{
P3_4=0;  
}

void BeepClose(void)
{
P3_4=1;  
}

void T0_time() interrupt 1     
{
VoiceScan();    //蜂鸣器的驱动函数
KeyScan();      //按键底层的驱动扫描函数
DisplayScan();  //数码管底层的驱动扫描函数

if(1==vGu8ScanTimerFlag&&vGu16ScanTimerCnt>0)  
{
vGu16ScanTimerCnt--;  //递减式的软件定时器
}

//每10ms就定时更新一次显示的软件定时器
if(1==vGu8UpdateTimerFlag&&vGu16UpdateTimerCnt>0)  
{
vGu16UpdateTimerCnt--;  //递减式的软件定时器
}

//倒计时实际走的时间的软件定时器,注意,这里还附加了启动状态的条件“&&1==Gu8RunStatus”
if(1==vGu8CountdownTimerFlag&&vGu32CountdownTimerCnt>0&&1==Gu8RunStatus)
{
vGu32CountdownTimerCnt--;  //递减式的软件定时器
}


TH0=0xfc;   
TL0=0x66;   
}


void SystemInitial(void)
{
P0=0x00;
P1_0=1;  
P1_1=1;  
P1_2=1;  
P1_3=1;  

TMOD=0x01;  
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;      
}

void Delay(unsigned long u32DelayTime)
{
    for(;u32DelayTime>0;u32DelayTime--);
}

void PeripheralInitial(void)
{

}


使用特权

评论回复
394
天命风流| | 2018-5-3 11:26 | 只看该作者
厉害啊

使用特权

评论回复
395
maglasbirl| | 2018-5-4 11:35 | 只看该作者
怎么收藏主题啊?

使用特权

评论回复
396
凡逸浩| | 2018-5-10 15:15 | 只看该作者
大概看了一下,还是蛮有用的

使用特权

评论回复
397
jianhong_wu|  楼主 | 2018-5-12 10:39 | 只看该作者
第一百一十七节: 按键切换数码管窗口来设置参数。
第一百一十七节.pdf (120.49 KB)
【117.1   按键切换数码管窗口来设置参数。】
     
                上图117.1.1  数码管




                上图117.1.2  独立按键

         
                上图117.1.3  有源蜂鸣器

        单片机是“数据”驱动型的。按什么逻辑跑,以什么方式跑,都是“数据”决定的。人机交互的核心就是“人”以什么渠道去更改“机”内部的某些“数据”。在程序框架层面,按键更改或者编辑某些数据,我的核心思路都是“在某个窗口下去更改某个特定的数据”,如果某个窗口的数据很多,就需要在此窗口下进一步细分,细分为“某个窗口下的某个局部(子菜单、光标选择)”。可见,“窗口”是支点,“局部”是支点中再细分出来的支点。窗口对应一个名叫 “窗口选择”的全局变量Gu8Wd,局部(子菜单、光标选择)对应一个名叫“局部选择”的全局变量Gu8Part。数据发生变化的时候,才需要更新显示到数码管上,平时不用一直更新显示,因此,与“窗口选择”Gu8Wd还对应一个名叫“整屏更新”的全局变量Gu8WdUpdate,与“局部选择”Gu8Part还对应一个名叫“局部更新”的全局变量Gu8PartUpdate。本节的小项目程序只用到“窗口”,没有用到“局部”。
        本节小项目的程序功能,利用按键与数码管的人机交互,可以对单片机内部三个参数Gu8SetData_1,Gu8SetData_2,Gu8SetData_3进行编辑。这三个参数分别在三个窗口下进行编辑,这三个窗口是数码管显示“1-XX”,“2-YY”,“3-ZZ”。其中,XX代表Gu8SetData_1数据,YY代表Gu8SetData_2数据,ZZ代表Gu8SetData_3数据,这三个数据的范围是从0到99。K1是窗口切换按键,每按一次,窗口会在“1-XX”,“2-YY”,“3-ZZ”三个窗口之间进行切换。K2是数字累加按键,每按一次,显示的数字会累加1。K3是数字递减按键,每按一次,显示的数字会递减1。代码如下:

#include "REG52.H"  

#define KEY_FILTER_TIME  25
#define SCAN_TIME  1   
#define VOICE_TIME   50   

void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;

void KeyScan(void);  
void KeyTask(void);  

void VoiceScan(void);  
void DisplayScan(void);  
void DisplayTask(void);  //上层显示的任务函数
void Wd1(void);   //窗口1显示函数
void Wd2(void);   //窗口2显示函数
void Wd3(void);   //窗口3显示函数

void BeepOpen(void);   
void BeepClose(void);

sbit KEY_INPUT1=P2^2;  
sbit KEY_INPUT2=P2^1;  
sbit KEY_INPUT3=P2^0;  

sbit P1_0=P1^0;  
sbit P1_1=P1^1;  
sbit P1_2=P1^2;  
sbit P1_3=P1^3;

sbit P3_4=P3^4;

//数码管转换表
code unsigned char Cu8DigTable[]=
{
0x3f,  //0       序号0
0x06,  //1       序号1
0x5b,  //2       序号2
0x4f,  //3       序号3
0x66,  //4       序号4
0x6d,  //5       序号5
0x7d,  //6       序号6
0x07,  //7       序号7
0x7f,  //8       序号8
0x6f,  //9       序号9
0x00,  //不显示  序号10
0x40,  //横杠-   序号11
};

volatile unsigned char vGu8ScanTimerFlag=0;  
volatile unsigned int vGu16ScanTimerCnt=0;  

volatile unsigned char vGu8BeepTimerFlag=0;  
volatile unsigned int vGu16BeepTimerCnt=0;  

unsigned char Gu8SetData_1=0; //单片机内部第1个可编辑的参数
unsigned char Gu8SetData_2=0; //单片机内部第2个可编辑的参数
unsigned char Gu8SetData_3=0; //单片机内部第3个可编辑的参数

unsigned char Gu8Wd=1;   //窗口选择变量。人机交互程序框架的支点。初始化开机后显示第1个窗口。
unsigned char Gu8WdUpdate=1;  //整屏更新变量。初始化为1开机后整屏更新一次显示。

volatile unsigned char vGu8Display_Righ_4=1;  //显示窗口“1”
volatile unsigned char vGu8Display_Righ_3=11; //显示横杠“-”  
volatile unsigned char vGu8Display_Righ_2=0;  //显示十位数值“0”
volatile unsigned char vGu8Display_Righ_1=0;  //显示个位数值“0”

volatile unsigned char vGu8Display_Righ_Dot_4=0;  
volatile unsigned char vGu8Display_Righ_Dot_3=0;     
volatile unsigned char vGu8Display_Righ_Dot_2=0;  
volatile unsigned char vGu8Display_Righ_Dot_1=0;  

volatile unsigned char vGu8KeySec=0;  

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)  
{  
KeyTask();      //按键的任务函数
DisplayTask();  //数码管显示的上层任务函数
    }
}

void KeyTask(void)    //按键的任务函数
{
if(0==vGu8KeySec)
{
return;
}

switch(vGu8KeySec)
{
   case 1:     //窗口切换的按键
Gu8Wd++;  //窗口切换到下一个窗口
if(Gu8Wd>3)  //一共3个窗口。切换第3个窗口之后,继续返回到第1个窗口
{
Gu8Wd=1;   //返回到第1个窗口
}
            Gu8WdUpdate=1;  //整屏更新一次显示

vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=VOICE_TIME;  //蜂鸣器发出“滴”一声
vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  
break;

   case 2:     //累加的按键
        switch(Gu8Wd)  //以窗口选择Gu8Wd为支点,去编辑对应的数据。又一次用到switch语句
        {
             case 1:     //在第1个窗口下编辑Gu8SetData_1数据
                  Gu8SetData_1++;
                  if(Gu8SetData_1>99) //把最大范围限定在99
{
Gu8SetData_1=99;
}
                      Gu8WdUpdate=1;  //整屏更新一次显示
                  break;

             case 2:     //在第2个窗口下编辑Gu8SetData_2数据
                  Gu8SetData_2++;
                  if(Gu8SetData_2>99) //把最大范围限定在99
{
Gu8SetData_2=99;
}
                      Gu8WdUpdate=1;  //整屏更新一次显示
                  break;

             case 3:     //在第3个窗口下编辑Gu8SetData_3数据
                  Gu8SetData_3++;
                  if(Gu8SetData_3>99) //把最大范围限定在99
{
Gu8SetData_3=99;
}
                      Gu8WdUpdate=1;  //整屏更新一次显示
                  break;
}

vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=VOICE_TIME;  //蜂鸣器发出“滴”一声
vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  
break;

   case 3:     //递减的按键
        switch(Gu8Wd)  //以窗口选择Gu8Wd为支点,去编辑对应的数据。又一次用到switch语句
        {
             case 1:     //在第1个窗口下编辑Gu8SetData_1数据
                  if(Gu8SetData_1>0) //把最小范围限定在0
{
Gu8SetData_1--;
}
                      Gu8WdUpdate=1;  //整屏更新一次显示
                  break;

             case 2:     //在第2个窗口下编辑Gu8SetData_2数据
                  if(Gu8SetData_2>0) //把最小范围限定在0
{
Gu8SetData_2--;
}
                      Gu8WdUpdate=1;  //整屏更新一次显示
                  break;

             case 3:     //在第3个窗口下编辑Gu8SetData_3数据
                  if(Gu8SetData_3>0) //把最小范围限定在0
{
Gu8SetData_3--;
}
                      Gu8WdUpdate=1;  //整屏更新一次显示
                  break;
}

vGu8BeepTimerFlag=0;  
vGu16BeepTimerCnt=VOICE_TIME;  //蜂鸣器发出“滴”一声
vGu8BeepTimerFlag=1;  

vGu8KeySec=0;  
break;
}
}

void DisplayTask(void) //数码管显示的上层任务函数
{
  switch(Gu8Wd)  //以窗口选择Gu8Wd为支点,去执行对应的窗口显示函数。又一次用到switch语句
{
    case 1:
        Wd1();   //窗口1显示函数
        break;
    case 2:
        Wd2();   //窗口2显示函数
        break;
    case 3:
        Wd3();   //窗口3显示函数
        break;
}

}

void Wd1(void)   //窗口1显示函数
{
//需要借用的中间变量,用来拆分数据位。
static unsigned char Su8Temp_4,Su8Temp_3,Su8Temp_2,Su8Temp_1; //需要借用的中间变量

if(1==Gu8WdUpdate) //如果需要整屏更新
{
Gu8WdUpdate=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

Su8Temp_4=1;  //窗口“1”
Su8Temp_3=11; //横杠“-”
Su8Temp_2=Gu8SetData_1/10%10; //十位数值
Su8Temp_1=Gu8SetData_1/1%10;  //个位数值

//上面先分解数据之后,再过渡需要显示的数据到底层驱动变量里,让过渡的时间越短越好
vGu8Display_Righ_4=Su8Temp_4;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_3=Su8Temp_3;  
vGu8Display_Righ_2=Su8Temp_2;  
vGu8Display_Righ_1=Su8Temp_1;  

//不显示任何一个小数点
vGu8Display_Righ_Dot_4=0;  
vGu8Display_Righ_Dot_3=0;  
vGu8Display_Righ_Dot_2=0;  
vGu8Display_Righ_Dot_1=0;  
}
}

void Wd2(void)   //窗口2显示函数
{
//需要借用的中间变量,用来拆分数据位。
static unsigned char Su8Temp_4,Su8Temp_3,Su8Temp_2,Su8Temp_1; //需要借用的中间变量

if(1==Gu8WdUpdate) //如果需要整屏更新
{
Gu8WdUpdate=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

Su8Temp_4=2;  //窗口“2”
Su8Temp_3=11; //横杠“-”
Su8Temp_2=Gu8SetData_2/10%10; //十位数值
Su8Temp_1=Gu8SetData_2/1%10;  //个位数值

//上面先分解数据之后,再过渡需要显示的数据到底层驱动变量里,让过渡的时间越短越好
vGu8Display_Righ_4=Su8Temp_4;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_3=Su8Temp_3;  
vGu8Display_Righ_2=Su8Temp_2;  
vGu8Display_Righ_1=Su8Temp_1;  

//不显示任何一个小数点
vGu8Display_Righ_Dot_4=0;  
vGu8Display_Righ_Dot_3=0;  
vGu8Display_Righ_Dot_2=0;  
vGu8Display_Righ_Dot_1=0;  
}
}

void Wd3(void)   //窗口3显示函数
{
//需要借用的中间变量,用来拆分数据位。
static unsigned char Su8Temp_4,Su8Temp_3,Su8Temp_2,Su8Temp_1; //需要借用的中间变量

if(1==Gu8WdUpdate) //如果需要整屏更新
{
Gu8WdUpdate=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

Su8Temp_4=3;  //窗口“3”
Su8Temp_3=11; //横杠“-”
Su8Temp_2=Gu8SetData_3/10%10; //十位数值
Su8Temp_1=Gu8SetData_3/1%10;  //个位数值

//上面先分解数据之后,再过渡需要显示的数据到底层驱动变量里,让过渡的时间越短越好
vGu8Display_Righ_4=Su8Temp_4;  //过渡需要显示的数据到底层驱动变量
vGu8Display_Righ_3=Su8Temp_3;  
vGu8Display_Righ_2=Su8Temp_2;  
vGu8Display_Righ_1=Su8Temp_1;  

//不显示任何一个小数点
vGu8Display_Righ_Dot_4=0;  
vGu8Display_Righ_Dot_3=0;  
vGu8Display_Righ_Dot_2=0;  
vGu8Display_Righ_Dot_1=0;  
}
}

void KeyScan(void)  //按键底层的驱动扫描函数,放在定时中断函数里
{
   static unsigned char Su8KeyLock1;
   static unsigned int  Su16KeyCnt1;
   static unsigned char Su8KeyLock2;
   static unsigned int  Su16KeyCnt2;
    static unsigned char Su8KeyLock3;
   static unsigned int  Su16KeyCnt3;

   if(0!=KEY_INPUT1)
   {
      Su8KeyLock1=0;
      Su16KeyCnt1=0;   
   }
   else if(0==Su8KeyLock1)
   {
      Su16KeyCnt1++;
      if(Su16KeyCnt1>=KEY_FILTER_TIME)
      {
         Su8KeyLock1=1;  
         vGu8KeySec=1;   
      }
   }

   if(0!=KEY_INPUT2)
   {
      Su8KeyLock2=0;
      Su16KeyCnt2=0;      
   }
   else if(0==Su8KeyLock2)
   {
      Su16KeyCnt2++;
      if(Su16KeyCnt2>=KEY_FILTER_TIME)
      {
         Su8KeyLock2=1;  
         vGu8KeySec=2;   
      }
   }

   if(0!=KEY_INPUT3)
   {
      Su8KeyLock3=0;
      Su16KeyCnt3=0;      
   }
   else if(0==Su8KeyLock3)
   {
      Su16KeyCnt3++;
      if(Su16KeyCnt3>=KEY_FILTER_TIME)
      {
         Su8KeyLock3=1;  
         vGu8KeySec=3;   
      }
   }

}

void DisplayScan(void)    //数码管底层的驱动扫描函数,放在定时中断函数里
{
static unsigned char Su8GetCode;  
static unsigned char Su8ScanStep=1;  

if(0==vGu16ScanTimerCnt)  
{


    P0=0x00;
P1_0=1;  
P1_1=1;  
P1_2=1;  
P1_3=1;  

    switch(Su8ScanStep)
{
       case 1:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_1];

if(1==vGu8Display_Righ_Dot_1)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=0;
P1_1=1;  
P1_2=1;  
P1_3=1;            
break;

       case 2:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_2];
if(1==vGu8Display_Righ_Dot_2)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=1;  
P1_1=0;
P1_2=1;
P1_3=1;         
break;

       case 3:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_3];
if(1==vGu8Display_Righ_Dot_3)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=1;  
P1_1=1;  
P1_2=0;  
P1_3=1;         
break;

       case 4:
Su8GetCode=Cu8DigTable[vGu8Display_Righ_4];
if(1==vGu8Display_Righ_Dot_4)
{
Su8GetCode=Su8GetCode|0x80;  
}
    P0=Su8GetCode;
P1_0=1;  
P1_1=1;  
P1_2=1;  
P1_3=0;           
break;

}

Su8ScanStep++;
if(Su8ScanStep>4)
{
Su8ScanStep=1;
}

vGu8ScanTimerFlag=0;
vGu16ScanTimerCnt=SCAN_TIME;  
vGu8ScanTimerFlag=1;  
}
}


void VoiceScan(void) //蜂鸣器的驱动函数
{

          static unsigned char Su8Lock=0;  

if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
          {
                  if(0==Su8Lock)
                  {
                   Su8Lock=1;  
BeepOpen();
     }
    else  
{     

                       vGu16BeepTimerCnt--;         

                   if(0==vGu16BeepTimerCnt)
                   {
                           Su8Lock=0;     
BeepClose();  
                   }

}
          }         
}

void BeepOpen(void)
{
P3_4=0;  
}

void BeepClose(void)
{
P3_4=1;  
}

void T0_time() interrupt 1     
{
VoiceScan();    //蜂鸣器的驱动函数
KeyScan();      //按键底层的驱动扫描函数
DisplayScan();  //数码管底层的驱动扫描函数

if(1==vGu8ScanTimerFlag&&vGu16ScanTimerCnt>0)  
{
vGu16ScanTimerCnt--;  //递减式的软件定时器
}

TH0=0xfc;   
TL0=0x66;   
}


void SystemInitial(void)
{
P0=0x00;
P1_0=1;  
P1_1=1;  
P1_2=1;  
P1_3=1;  

TMOD=0x01;  
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;      
}

void Delay(unsigned long u32DelayTime)
{
    for(;u32DelayTime>0;u32DelayTime--);
}

void PeripheralInitial(void)
{

}


使用特权

评论回复
398
tanik| | 2018-5-13 21:46 | 只看该作者
走到哪里,都要顶!

使用特权

评论回复
399
波哥有气质| | 2018-5-15 16:09 | 只看该作者
jianhong_wu 发表于 2017-10-2 11:54
第八十九节: 跑马灯的三种境界。

【89.1   跑马灯的三种境界。】

学到了很多,感谢您的无私奉献!!

使用特权

评论回复
400
zxf6641| | 2018-5-21 20:27 | 只看该作者
感谢楼主无私奉献科普知识,国家需要的就是这踏实做事的人才!

使用特权

评论回复
发新帖 本帖赏金 42.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则