单片机教程10.习题练习与积累
首页 > 硬件电路 > 单片机教程10.习题练习与积累     2018-11-14 暂无标签

      本章内容主要通过一些相关例程,来提高大家的编程技能,并且帮助大家进行一些算法上的累积。同学们在做这部份内容的时候,还是那句话,必定要能够达到不看教程,独立把程序做出来的效果,那样才能基本上掌握相关知识点和内容。
10.1 数字秒表实验
10.1.1 不同数据间的类型转换

  在C语言中,不同数据类型之间是可以混合运算的。当表达式中的数据类型不一致时,首先转换为同一种类型,然后再进行计算。C语言有两种方法实现类型转换,一是自动类型转换,另外一种是强迫类型转换。这块内容是对比繁杂的,因此咱们根据咱们常用的编程运用来说部分相关内容。

  当不同数据类型之间混合运算的时候,不同类型的数据首先会转换为同一类型,转换的主要原则是:短字节的数据向长字节数据转换。

譬如:unsigned char  a ;  unsigned int b;  unsigned int c;  c = a *b;

  在运算的进程中,程序会自动整个按照unsigned int型来计算。譬如a=10,b=200,c的结果就是2000。那当a=100,b=700,那c是70000吗?新手最容易犯这种错误,大家要注意每一个变量的数据类型,c的数据类型是unsigned int型,取值范围是0~65535,70000超过65535溢出了,所以终究c的结果是(70000 - 65536) = 4464。

  那要想让c正常取得70000这个结果,需要把c定义成一个unsigned long型。咱们如果写成:unsigned char  a=100;  unsigned int  b=700;  unsigned long  c=0;  c = a *b;如果有做过实验的同学,会发现这个c的结果还是4464,这个是个甚么情况呢?

  大家注意,C语言不同类型运算的时候数值会转换同一类型运算,然而每步运算都会进行辨认判断,不会进行一个总的分析判断。譬如咱们这个程序,a和b相乘的时候,是按照unsigned int类型运算的,运算的结果也是unsigned int类型的4464,只是终究把unsigned int类型4464赋值给了一个unsigned long型的变量而已。咱们在运算的时候如何防止这种问题的发生呢?可以采取强迫类型转换的方法。

  在一个变量前边加之一个变量类型,并且这个变量类型用小括号括起来,表示把这个变量强迫转换成括号里的变量类型。如 c = (unsigned long)a * b;因为强迫类型转换运算优先级高于*,所以这个处所的运算是先把a转换成一个unsigned long型的变量,而后与b相乘,根据C语言的规则b会自动转换成一个unsigned long型的变量,而后运算终了结果也是一个unsigned long型的,终究赋值给了c。

  当不同类型变量互相赋值时,短字节的数据向长字节的变量赋值时,值不变,譬如unsigned char  a=100;  unsigned int  b=700;  b = a;那么终究b的值就是100了。然而如果咱们的程序是unsigned char  a=100;  unsigned int  b=700;  a=b;那么a的值仅仅是取了b的低8位,咱们首先要把700变成一个16位的二进制数据,然后取它的低8位出来,也就是188,这就是长字节给短字节赋值的结果。

  在51单片机里边,有一种特殊情况,就是bit类型的变量,这个bit类型的强迫类型转换,是不相符上边讲的这个原则的,譬如bit a = 0;  unsigned char b; a = (bit)b;这个处所要特别注意,使用bit做强迫类型转换,不是取b的最低位,而是他会判断b这个变量是0还是非0的值,如果b是0,那么a的结果就是0,如果b是任意非0的其他数字,那么a的结果都是1。

1.1.2 定时时间精准性调剂

  咱们的6.5.2章节有一个数码管秒表显示程序,那个程序是1秒数码管加1,然而仔细的同学做了实验后,经由长期运行会发现,和咱们的实际的时间有了较大误差了,那如何去调剂这种误差呢?要解决问题,先找到问题是甚么原因酿成的。

  先补充介绍一下咱们咱们前边讲的中断的内容。其实单片机做的很智能的,当咱们在看电视的时候,突然发生了水开的中断,咱们必需去提水的时候,第一,咱们从电视跟前跑到厨房需要必定的时间,第二,因为咱们看的电视是智能数字电视,因此在去提水之前咱们可使用遥控器将咱们的电视进行暂停操作,利便回来后继续从刚才的剧情往下进行。那暂停电视,跑到厨房提水,这一点点时间是很短的,在实际糊口中可以忽稍不计,然而在单片机秒表系统,误差会累计的,每1秒钟都差了几个奥妙,时间一久,酿成的累计误差就不可小觑了。

  单片机系统里,硬件进入中断需要必定的时间,大概是几个机器周期,还有要进行原始数据维护,就是把进中断之前程序运行的一些变量先保留起来,这个专业辞汇叫做中断压栈,进入中断后,从新给定时器TH和TL赋值,也需要几个机器周期,这样下来就会损耗必定的时间,咱们患上把这些时间补偿回来。

  方法一,使用软件debug进行补偿。

  咱们前边教程讲过使用debug来视察程序运行时间,那咱们可以把咱们2次进入中断的时间间隔视察出来,看看和咱们实际定时的时间相差了几个机器周期,然后在进行定时器初值赋值的时候,进行一个调剂。咱们用的是11.0592M的晶振,发现差了几个机器周期,就把定时器初值加之几个机器周期,这样至关于进行了一个补偿。

  方法二,使用累计误差计算出来。

  有的时候,除程序自身存在的误差外,硬件精度也可能会影响到时钟的精度,譬如晶振,会跟着温度变化涌现温漂现象,就是精度和标称值要差一点。那么咱们还可以采取累计误差的方法来提高精度。譬如咱们可让时钟运行半个小时或一个小时,看看终究时间差了几秒,然后算算一共进了多少次定时器中断,然后把这差的几秒平均分配到每次的定时器中断中,就能实现时钟的调剂。

  大家要明白,这个世界上本就没有绝对的精度,咱们只能提高精度,然而不可能解除误差的,如果在这个基础上还感觉精度不够的话,不要着急,后边咱们会专门讲时钟芯片的,通常时钟芯片计时的精度比单片机的精度要高一些。

10.1.3 使用字节操作修改位的技能

  这里介绍个编程小技能,在咱们编程序的时候,有的情况下,想要操作一个字节中的某一名或几位的时候,然而又不想改变其他位原有的值,该如何操作呢?

  譬如咱们学定时器的时候遇到一个寄存器TCON,这个寄存器是可以进行位操作的,譬如咱们可以直接写TR0 =1;TR0是TCON的一个位,因为这个寄存器是允许位操作,这样写是没有任何问题的。还有一个寄存器TMOD,这个寄存器是不支撑位操作的,那如果咱们要使用T0的模式1,咱们但愿达到的效果是TMOD的低4位是0001,如果咱们直接写成TMOD = 0x01的话,实际上已同时操作到了高4位,即属于T1的部分,设置成了0000,如果T1定时器没有用到的话,那咱们随意怎样样都行,然而如果程序中既用到了T0,又用到了T1,那咱们设置T0的同时已干扰到了T1的模式配置,这咱们不但愿看到的结果。

  在这种情况下,就能用咱们前边学过的"&"和"|"运算了。对二进制位操作来讲,不管该位原来的值是0还是1,它跟"0"进行"&&"运算,患上到的结果都是0,而跟"1"进行"&&"运算,将保持原来的值不变;不管该位原来的值是0还是1,它跟"1"进行"||"运算,患上到的结果都是1,而跟"0"进行"||"运算,将保持原来的值不变。

  应用上述这个规律,咱们就能着手解决刚才的问题了。如果咱们现在要设置TMOD的定时器0工作在模式1下,又不干扰定时器1的配置,咱们可以进行这样的操作:TMOD = TMOD & 0xF0; TMOD = TMOD | 0x01;第一步与0xF0后,TMOD的高4位不变,低4位清零,变成了xxxx0000;然后再进行第二步和0x01进行或运算,那高7位均不变,最低位变成1了,这样就完成了只将低4位的值修改位0001,而高4位保持原不变的任务,即只设置了T0而不影响T1。熟练掌握并灵便运用这个方法,会给你以后的编程带来便利。

  另外,在C语言中,a &= b;等价于a = a&b;同理,a |= b;等价于a = a|b;那么前边那一段代码就能写成TMOD &= 0xF0;TMOD |= 0x01这样的简写情势。这种写法可以必定程度上简化代码,是C语言的一种编程作风。

10.1.4 数码管刷新函数算法改良

  在学习数码管动态刷新的时候,为了利便大家理解,咱们程序写的细致一些,给大家引入了switch的用法,跟着咱们编程能力的增强,对74HC138这种无比有规律的数字器件,咱们在编程上也可以改良一下逻辑算法,让程序变的更简洁。这种逻辑算法,通常不是靠学一下可以整个掌握的,而是通过不断的编写程序和研究别人的程序一点点累积起来的,从今天开始,大家就要开始累积。

  前边动态刷新函数咱们是这么写的:

  switch(j)

    {

         case 0: ADDR0=0; ADDR1=0; ADDR2=0; j++; P0=LedChar[LedNumber[0]]; break; 

        case 1: ADDR0=1; ADDR1=0; ADDR2=0; j++; P0=LedChar[LedNumber[1]]; break;

         case 2:  ADDR0=0; ADDR1=1; ADDR2=0; j++; P0=LedChar[LedNumber[2]]; break;

         case 3:  ADDR0=1; ADDR1=1; ADDR2=0; j++; P0=LedChar[LedNumber[3]]; break;

        case 4:  ADDR0=0; ADDR1=0; ADDR2=1; j++; P0=LedChar[LedNumber[4]]; break;

         case 5:  ADDR0=1; ADDR1=0; ADDR2=1; j=0; P0=LedChar[LedNumber[5]]; break;

        default: break;

     }

  首先咱们进行第一步改良,写成:

switch(j)

    {

         case 0: ADDR0=0; ADDR1=0; ADDR2=0; break; 

        case 1: ADDR0=1; ADDR1=0; ADDR2=0; break;

         case 2:  ADDR0=0; ADDR1=1; ADDR2=0; break;

         case 3:  ADDR0=1; ADDR1=1; ADDR2=0; break;

        case 4:  ADDR0=0; ADDR1=0; ADDR2=1; break;

         case 5:  ADDR0=1; ADDR1=0; ADDR2=1; break;

        default: break;

     }      

P0=LedChar[LedNumber[j++]];  

if(6==j) j=0;

  这种写法已比上边那种写法简单多了,咱们还要继续简化。咱们来看,ADDR0是P1的第0位,ADDR1是P1的第1位,ADDR2是P1的第2位,咱们可以看出来,程序中的case 0到case 5的进程中,P1的这低3位的值分别是000,001,010,011,100,101。转换成十进制,也就是从0到5。那咱们程序就能进一步改良,写成下列函数情势:

void LedScan()  //LED显示扫描函数

{

    static unsigned char index = 0;

    

    P0 = 0xFF;                 //关闭所有段选位,显示消隐

    P1 = (P1 & 0xF8) | index;  //位选索引值赋值到P1口低3位

    P0 = LedNumber[index];       //相应显示缓冲区的值赋值到P0口

    if (index < 5)             //位选索引0-5循环,因有6个数码管

        index++;

    else

        index = 0;

}

  大家看看,P1 = (P1 & 0xF8) | index;这行代码就应用了上面讲到的"&"和"|"运算来将index的低3位直接赋值到P1口的低3位上,这样写是不是要简洁的多,也巧妙的多,同样可以完善实现动态刷新的功能。

10.1.5 秒表程序

  做了一个秒表程序给同学们做参考,程序中触及到的知识点咱们几乎都讲过了,触及到了定时器、数码管、中断、按键等多个知识点。此程序是多知识点同时运用到一个程序中的小综合,因此需要大家彻底消化掉。这种小综合也是将来做大项目程序的一个基础,因此还是老规矩,大家边抄边理解,理解透辟后独立写出来就算此关通过。

 

#include 

 

sbit  KEY1 = P2^4;

sbit  KEY2 = P2^5;

sbit  KEY3 = P2^6;

sbit  KEY4 = P2^7;

sbit  ADDR3 = P1^3;

sbit  ENLED = P1^4;

 

unsigned char code LedChar[] = {  //数码管显示字符转换表

    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,

    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E

};

unsigned char LedBuff[6] = {  //数码管显示缓冲区

    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF

};

unsigned char KeySta[4] = {  //按键状态缓冲区

    1, 1, 1, 1

};

 

bit StopwatchRunning = 0;  //秒表运行标志

bit StopwatchRefresh = 1;  //秒表计数刷新标志

unsigned char DecimalPart = 0;  //秒表的小数部分

unsigned int  IntegerPart = 0;  //秒表的整数部分

 

unsigned char T0RH = 0;  //T0重载值的高字节

unsigned char T0RL = 0;  //T0重载值的低字节

void ConfigTimer0(unsigned int ms);

void StopwatchDisplay();

void KeyAction();

void main ()

{

    P2 = 0xFE;  //选择第4行按键以进行扫描

    P0 = 0xFF;  //P0口初始化

    ADDR3 = 1;  //选择数码管

    ENLED = 0;  //LED总使能

    EA = 1;     //开总中断

     ConfigTimer0(2);  //配置T0定时2ms

    

    while(1)

    {

        KeyAction();

        StopwatchDisplay();

    }

}

 

void ConfigTimer0(unsigned int ms)  //T0配置函数

{

    unsigned long tmp;

    

    tmp = 11059200 / 12;      //定时器计数频率

    tmp = (tmp * ms) / 1000;  //计算所需的计数值

    tmp = 65536 - tmp;        //计算定时器重载值

    tmp = tmp + 19;           //修正中断响应延时酿成的误差,运行30分钟修正值

    

    T0RH = (unsigned char)(tmp >> 8);  //定时器重载值拆分为高下字节

    T0RL = (unsigned char)tmp;

    TMOD &= 0xF0;   //清零T0的控制位

    TMOD |= 0x01;   //配置T0为模式1

    TH0 = T0RH;     //加载T0重载值

    TL0 = T0RL;

    ET0 = 1;        //使能T0中断

    TR0 = 1;        //启动T0

}

void StopwatchDisplay()  //秒表计数显示函数

{

     unsigned char i;

    unsigned char buff[6];

    

    if (StopwatchRefresh)

    {

        StopwatchRefresh = 0;

        

        i = DecimalPart % 10;   //小数部分转换到低2位

        buff[0] = LedChar[i];

        i = DecimalPart / 10;

        buff[1] = LedChar[i];

        

        buff[2] = IntegerPart % 10;         //整数部分转换到高4位

        buff[3] = (IntegerPart / 10) % 10;

        buff[4] = (IntegerPart / 100) % 10;

        buff[5] = (IntegerPart / 1000) % 10;

        

        for (i=5; i>=3; i--) //高位的0转换为空字符

        {

            if (buff[i] == 0)

                buff[i] = 0xFF;

            else

                break;

        }

        for ( ; i>=2; i--) //有效数字位转换显示字符

        {

            buff[i] = LedChar[buff[i]];

        }

        buff[2] &= 0x7F;  //点亮小数点

        

        for (i=0; i<=5; i++) //一次性拷贝到显示缓冲区,解除可能存在的显示抖动

        {

            LedBuff[i] = buff[i];

        }

    }

}

void StopwatchAction()  //秒表启停函数

{

    if (StopwatchRunning)    //已启动则休止

        StopwatchRunning = 0;

    else                     //未启动则启动

        StopwatchRunning = 1;

}

void StopwatchReset()  //秒表复位函数

{

    StopwatchRunning = 0;  //休止秒表

    DecimalPart = 0;       //清零计数值

    IntegerPart = 0;

    StopwatchRefresh = 1;  //置刷新标志

}

void KeyAction()  //按键动作函数

{

    unsigned char i;

    static unsigned char backup[4] = {1,1,1,1};

 

    for (i=0; i<4; i++)

    {

        if (backup[i] != KeySta[i])

        {

            if (backup[i] != 0)  //按键按下时执行动作

            {

                switch (i)

                {

                    case 1: StopwatchReset(); break;   //Esc键复位秒表

                    case 2: StopwatchAction(); break;  //回车键启停秒表

                    default: break;

                }

            }

            backup[i] = KeySta[i];

        }

    }

}

 

void LedScan()  //LED显示扫描函数

{

    static unsigned char index = 0;

    

    P0 = 0xFF;                 //关闭所有段选位,显示消隐

    P1 = (P1 & 0xF8) | index;  //位选索引值赋值到P1口低3位

    P0 = LedBuff[index];       //相应显示缓冲区的值赋值到P0口

    if (index < 5)             //位选索引0-5循环,因有6个数码管

        index++;

    else

        index = 0;

}

void KeyScan()  //按键扫描函数

{

    unsigned char i;

    static unsigned char keybuf[4] = {  //按键扫描缓冲区,保留一段时间内的扫描值

        0xFF, 0xFF, 0xFF, 0xFF

    };

    

    //按键值移入缓冲区

    keybuf[0] = (keybuf[0] << 1) | KEY1;

    keybuf[1] = (keybuf[1] << 1) | KEY2;

    keybuf[2] = (keybuf[2] << 1) | KEY3;

    keybuf[3] = (keybuf[3] << 1) | KEY4;

 

    //消抖后更新按键状态

    for (i=0; i<4; i++)

    {

        if (keybuf[i] == 0x00)

        {

            KeySta[i] = 0;

        }

        else if (keybuf[i] == 0xFF)

        {

            KeySta[i] = 1;

        }

    }

}

void StopwatchCount()  //秒表计数函数

{

    if (StopwatchRunning)

    {

        DecimalPart++;          //小数部分+1

        if (DecimalPart >= 100) //小数部分计到100时进位到整数部分

        {

            DecimalPart = 0;

            IntegerPart++;

            if (IntegerPart >= 10000)  //整数部分计到10000时归零

            {

                IntegerPart = 0;

            }

        }

        StopwatchRefresh = 1;

    }

}

void InterruptTimer0() interrupt 1  //T0中断服务函数

{

    static unsigned char tmr10ms = 0;

 

    TH0 = T0RH;  //将重载值赋值到计数器

    TL0 = T0RL;

 

    KeyScan(); //按键扫描

    LedScan(); //数码管扫描显示

 

    //定时10ms进行一次秒表计数

    tmr10ms++;

    if (tmr10ms >= 5)

    {

        tmr10ms = 0;

        StopwatchCount();

    }

}

10.2 PWM的学习

  PWM在咱们今后的单片机运用中无比无比多,运用的方向也良多,它的原理很简单,然而常常运用于不同场合上意义不彻底同样,这里我先把基本概念和基本原理给大家介绍一下,后边遇到用的时候起码晓得是个甚么东西。

  PWM是Pulse Width Modulation的缩写,它的中文名字是脉冲宽度调制,一种说法是它应用微处理器的数字输出来对摹拟电路进行控制的一种有效的技术,其实就是使用数字信号达到一个摹拟信号的效果。这是个甚么概念呢?咱们一步步来介绍。

  首先从它的名字来看,脉冲宽度调制,就是改变脉冲宽度来实现不同的效果。咱们先来看三组不同的脉冲信号,如图10-1所示。

PWM波形

图10-1 PWM波形

  这是一个周期是10ms,即频率是100Hz的波形,然而每一个周期内,高下电平脉冲各不相同,这就是PWM的本色。在这里大家要记住一个概念,叫做“占空比”。占空比是指高电平的时间占整个周期的比例。譬如第一部分波形的占空比是40%,第二部分波形占空比是60%,第三部分波形占空比是80%,这就是PWM的解释。

  那为什么它会对摹拟电路进行控制呢?大家想一想,咱们数字电路里,只有0和1两种状态,譬如咱们教程第二课学会的点亮LED小灯那个程序,当咱们写一个LED = 0;小灯就会长亮,当咱们写一个LED = 1;小灯就会灭掉。当咱们让小灯亮和灭间隔运行的时候,小灯是闪烁。如果咱们把这个间隔不断的减小,减小到咱们的肉眼分辨不出来,也就是100Hz以上的频率,这个时候小灯表现出来的现象就是既保持亮的状态,然而亮度没有LED = 0;的时候亮度高。那咱们不断改变时间参数,让LED = 0;的时间大于或小于LED = 1;的时间,会发现亮度都不同样,这就是摹拟电路的感觉了,不再是纯粹的0和1,还有亮度不断变化。大家会发现,如果咱们是100Hz的信号,如图10-1所示,假如高电平熄灭小灯,低电平点亮小灯的话,第一部分波形熄灭4ms,点亮6ms,亮度最高,第二部分熄灭6ms,点亮4ms,亮度次之,第三部分熄灭8ms,点亮2ms,亮度最低。咱们用程序验证一下。

#include 

sbit  PWMOUT = P0^0;

sbit  ADDR0 = P1^0;

sbit  ADDR1 = P1^1;

sbit  ADDR2 = P1^2;

sbit  ADDR3 = P1^3;

sbit  ENLED = P1^4;

unsigned char HReloadH = 0;  //高电平重载值的高字节

unsigned char HReloadL = 0;  //高电平重载值的低字节

unsigned char LReloadH = 0;  //低电平重载值的高字节

unsigned char LReloadL = 0;  //低电平重载值的低字节

void ConfigPWM(unsigned int fr, unsigned char dc);

void ClosePWM();

void main ()

{

    unsigned int i;

 

    P0 = 0xFF;  //P0口初始化

    ADDR0 = 0;  //选择独立LED

    ADDR1 = 1;

    ADDR2 = 1;

    ADDR3 = 1;

    ENLED = 0;  //LED总使能

    EA = 1;     //开总中断

    

    while(1)

    {

        ConfigPWM(100, 10);  //频率100Hz,占空比10%

        for (i=0; i<40000; i++);

        ClosePWM();

        ConfigPWM(100, 40);  //频率100Hz,占空比40%

        for (i=0; i<40000; i++);

        ClosePWM();

        ConfigPWM(100, 90);  //频率100Hz,占空比90%

        for (i=0; i<40000; i++);

        ClosePWM();

        for (i=0; i<40000; i++);

    }

}

 

void ConfigPWM(unsigned int fr, unsigned char dc)  //PWM配置函数,fr-频率,dc-占空比

{

    unsigned int  high, low;

    unsigned long tmp;

    

    tmp  = (11059200 / 12) / fr;  //计算一个周期所需的计数值

    high = (tmp * dc) / 100;      //计算高电平所需的计数值

    low  = tmp - high;            //计算低电平所需的计数值

    high = 65536 - high + 13;     //计算高电平的定时器重载值并修正

    low  = 65536 - low  + 13;     //计算低电平的定时器重载值并修正

    

    HReloadH = (unsigned char)(high >> 8);  //高电平重载值拆分为高下字节

    HReloadL = (unsigned char)high;

    LReloadH = (unsigned char)(low >> 8);   //低电平重载值拆分为高下字节

    LReloadL = (unsigned char)low;

    

    TMOD &= 0xF0;   //清零T0的控制位

    TMOD |= 0x01;   //配置T0为模式1

    TH0 = HReloadH; //加载T0重载值

    TL0 = HReloadL;

    ET0 = 1;        //使能T0中断

    TR0 = 1;        //启动T0

    PWMOUT = 1;     //输出高电平

}

void ClosePWM()  //关闭PWM

{

    TR0 = 0;     //休止定时器

    ET0 = 0;

    PWMOUT = 1;  //输出高电平

}

 

void InterruptTimer0() interrupt 1  //T0中断服务函数

{

    if (PWMOUT == 1)  //当前输出为高电平时,装载低电平值并输出低电平

    {

        TH0 = LReloadH;

        TL0 = LReloadL;

        PWMOUT = 0;

    }

    else              //当前输出为低电平时,装载高电平值并输出高电平

    {

        TH0 = HReloadH;

        TL0 = HReloadL;

        PWMOUT = 1;

    }

}

  大家下载了这个程序,会发现小灯从最亮到灭一共4个亮度等级。如果咱们让亮度等级更多,并且让亮度等级连续起来,会发生一个小灯渐变的效果,和人呼吸有点相似,所以咱们习惯上称之为呼吸灯,程序代码如下,这个程序用了2个定时器2个中断,这是咱们第一次这样用,大家可以学习一下。咱们来试试这个程序,试完了大家必定要自己关闭教程把程序写出来,切记。

 

#include 

 

sbit  PWMOUT = P0^0;

sbit  ADDR0 = P1^0;

sbit  ADDR1 = P1^1;

sbit  ADDR2 = P1^2;

sbit  ADDR3 = P1^3;

sbit  ENLED = P1^4;

 

unsigned long PeriodCnt = 0; //PWM周期计数值

unsigned char HReloadH = 0;  //高电平重载值的高字节

unsigned char HReloadL = 0;  //高电平重载值的低字节

unsigned char LReloadH = 0;  //低电平重载值的高字节

unsigned char LReloadL = 0;  //低电平重载值的低字节

 

unsigned char T1RH = 0;   //T1重载值的高字节

unsigned char T1RL = 0;   //T1重载值的低字节

 

void ConfigTimer1(unsigned int ms);

void ConfigPWM(unsigned int fr, unsigned char dc);

 

void main ()

{

    P0 = 0xFF;  //P0口初始化

    ADDR0 = 0;  //选择独立LED

    ADDR1 = 1;

    ADDR2 = 1;

    ADDR3 = 1;

    ENLED = 0;  //LED总使能

    EA = 1;     //开总中断

    

    ConfigPWM(100, 10); //配置并启动PWM

    ConfigTimer1(50);   //T1定时调剂占空比

    

    while(1);

}

 

void ConfigTimer1(unsigned int ms)  //T1配置函数

{

    unsigned long tmp;

    

    tmp = 11059200 / 12;      //定时器计数频率

    tmp = (tmp * ms) / 1000;  //计算所需的计数值

    tmp = 65536 - tmp;        //计算定时器重载值

    tmp = tmp + 11;           //修正中断响应延时酿成的误差

    

    T1RH = (unsigned char)(tmp >> 8);  //定时器重载值拆分为高下字节

    T1RL = (unsigned char)tmp;

    TMOD &= 0x0F;   //清零T1的控制位

    TMOD |= 0x10;   //配置T1为模式1

    TH1 = T1RH;     //加载T1重载值

    TL1 = T1RL;

    ET1 = 1;        //使能T1中断

    TR1 = 1;        //启动T1

}

void ConfigPWM(unsigned int fr, unsigned char dc)  //PWM配置函数,fr-频率,dc-占空比

{

    unsigned int high, low;

    

    PeriodCnt = (11059200 / 12) / fr;  //计算一个周期所需的计数值

    high = (PeriodCnt * dc) / 100;     //计算高电平所需的计数值

    low  = PeriodCnt - high;           //计算低电平所需的计数值

    high = 65536 - high + 13;          //计算高电平的定时器重载值并修正

    low  = 65536 - low  + 13;          //计算低电平的定时器重载值并修正

    

    HReloadH = (unsigned char)(high >> 8);  //高电平重载值拆分为高下字节

    HReloadL = (unsigned char)high;

    LReloadH = (unsigned char)(low >> 8);   //低电平重载值拆分为高下字节

    LReloadL = (unsigned char)low;

    

    TMOD &= 0xF0;   //清零T0的控制位

    TMOD |= 0x01;   //配置T0为模式1

    TH0 = HReloadH; //加载T0重载值

    TL0 = HReloadL;

    ET0 = 1;        //使能T0中断

    TR0 = 1;        //启动T0

    PWMOUT = 1;     //输出高电平

}

void AdjustDutyCycle(unsigned char dc)  //占空比调剂函数,频率不变只调剂占空比

{

    unsigned int  high, low;

    

    high = (PeriodCnt * dc) / 100;     //计算高电平所需的计数值

    low  = PeriodCnt - high;           //计算低电平所需的计数值

    high = 65536 - high + 13;          //计算高电平的定时器重载值并修正

    low  = 65536 - low  + 13;          //计算低电平的定时器重载值并修正

 

    HReloadH = (unsigned char)(high >> 8);  //高电平重载值拆分为高下字节

    HReloadL = (unsigned char)high;

    LReloadH = (unsigned char)(low >> 8);   //低电平重载值拆分为高下字节

    LReloadL = (unsigned char)low;

}

 

void InterruptTimer0() interrupt 1  //T0中断服务函数,发生PWM

{

    if (PWMOUT == 1)  //当前输出为高电平时,装载低电平值并输出低电平

    {

        TH0 = LReloadH;

        TL0 = LReloadL;

        PWMOUT = 0;

    }

    else              //当前输出为低电平时,装载高电平值并输出高电平

    {

        TH0 = HReloadH;

        TL0 = HReloadL;

        PWMOUT = 1;

    }

}

void InterruptTimer1() interrupt 3  //T1中断服务函数,定时动态调剂占空比

{

    static bit br = 0;

    static unsigned char index = 0;

    unsigned char code table[13] = {  //占空比调剂表

        5, 18, 30, 41, 51, 60, 68, 75, 81, 86, 90, 93, 95

    };

 

    TH1 = T1RH;  //从新加载T1重载值

    TL1 = T1RL;

    

    AdjustDutyCycle(table[index]); //调剂PWM的占空比

    if (br == 0)  //逐渐增大占空比

    {

        index++;

        if (index >= 12)

        {

            br = 1;

        }

    }

    else          //逐渐减小占空比

    {

        index--;

        if (index == 0)

        {

            br = 0;

        }

    }

}

  呼吸灯写出来后,其他各种效果的灯光闪烁都应该可以做出来,大家看到的KTV里边那绚丽的灯光闪烁,其实就是采取的PWM技术控制的。

10.3 交通灯实验

  同学们在学习技术的时候,必定要多动脑筋,遇到问题后,三思而后问。有些时候你斟酌的和真理就差一点点了,没有坚持下去,别人告诉你后你才恍然大悟。这样患上到的结论,可让你学到知识,然而却培育不了你的逻辑思惟能力。不是不能问,而是要在当真思考的基础上发问。

  有同学有疑难,板子上只有8个流水灯,那如果我要做良多个流水灯一块儿名堂显示怎样办呢?那咱们在讲课的时候其实都提到过了,板子上是有8个流水灯,还有6个数码管,还有1个点阵LED,一个数码管至关于8个小灯,一个点阵LED至关于64个小灯,那如果整个算上的话,咱们板子上实际共接了8+6*8+64=120个小灯,你如果单独只接小灯,名堂灯就做出来了。

  还有同学问,板子上流水灯和数码管可以一块儿工作吗?如何一块儿工作呢?咱们刚说了,一个数码管是8个小灯,然而大家反过来想一想,8个流水灯是不是至关于一个数码管吗。那板子上6个数码管咱们可让他们同时亮,7个数码管就不会了吗?固然了,思考的习惯是要慢慢培育的,想不到的同学继续努力,每天前进一小步,坚持一段时间后回头看看,就会发现你学会了良多。

  发一个交通灯的程序给大家做学习参考。因为板子资源有限,所以我把左侧LED8和LED9一块儿亮作为绿灯,把LED5和LED6一块儿亮作为黄灯,把LED2和LED3一块儿亮作为红灯,用数码管做倒计时,做了一个简易的交通灯程序给大家做参考学习,让LED和数码管同时介入工作。

 

#include 

 

sbit  KEY1 = P2^4;

sbit  KEY2 = P2^5;

sbit  KEY3 = P2^6;

sbit  KEY4 = P2^7;

sbit  ADDR3 = P1^3;

sbit  ENLED = P1^4;

 

unsigned char code LedChar[] = {  //数码管显示字符转换表

    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,

    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E

};

unsigned char LedBuff[7] = {  //数码管+独立LED显示缓冲区

    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF

};

 

bit flag1s = 1;          //1秒定时标志

unsigned char T0RH = 0;  //T0重载值的高字节

unsigned char T0RL = 0;  //T0重载值的低字节

 

void ConfigTimer0(unsigned int ms);

void TrafficLight();

 

void main ()

{

    P2 = 0xFE;  //选择第4行按键以进行扫描

    P0 = 0xFF;  //P0口初始化

    ADDR3 = 1;  //选择数码管

    ENLED = 0;  //LED总使能

    EA = 1;     //开总中断

     ConfigTimer0(1);  //配置T0定时1ms

    

    while(1)

    {

        if (flag1s)

        {   //每秒执行一次

            flag1s = 0;

            TrafficLight();

        }

    }

}

 

void ConfigTimer0(unsigned int ms)  //T0配置函数

{

    unsigned long tmp;

    

    tmp = 11059200 / 12;      //定时器计数频率

    tmp = (tmp * ms) / 1000;  //计算所需的计数值

    tmp = 65536 - tmp;        //计算定时器重载值

    tmp = tmp + 17;           //修正中断响应延时酿成的误差

    

    T0RH = (unsigned char)(tmp >> 8);  //定时器重载值拆分为高下字节

    T0RL = (unsigned char)tmp;

    TMOD &= 0xF0;   //清零T0的控制位

    TMOD |= 0x01;   //配置T0为模式1

    TH0 = T0RH;     //加载T0重载值

    TL0 = T0RL;

    ET0 = 1;        //使能T0中断

    TR0 = 1;        //启动T0

}

 

void TrafficLight()

{

    static unsigned char color = 2;  //交通灯颜色索引,0-绿色,1-黄色,2-红色

    static unsigned char timer = 0;  //交通灯倒计时定时器

    

    if (timer == 0) //倒计时到0时,切换交通灯

    {

        switch (color)

        {   //左端两个LED代表绿灯,中间两个LED代表黄灯,右端两个LED代表红灯,

            case 0: color=1; LedBuff[6]=0xE7; timer=2;  break;  //切换到黄色,亮3秒

            case 1: color=2; LedBuff[6]=0xFC; timer=29; break;  //切换到红色,亮30秒

            case 2: color=0; LedBuff[6]=0x3F; timer=39; break;  //切换到绿色,亮40秒

            default: break;

        }

    }

    else //倒计时未到0时,递减其计数值

    {

        timer--;

    }

    LedBuff[0] = LedChar[timer%10];  //倒计时数值个位显示

    LedBuff[1] = LedChar[timer/10];  //倒计时数值十位显示

}

 

void LedScan()  //LED显示扫描函数

{

    static unsigned char index = 0;  //LED位选索引

    

    P0 = 0xFF;                 //关闭所有段选位,显示消隐

    P1 = (P1 & 0xF8) | index;  //位选索引值赋值到P1口低3位

    P0 = LedBuff[index];       //相应显示缓冲区的值赋值到P0口

    if (index < 6)             //位选索引0-6循环,因有6个数码管+一组独立LED

        index++;

    else

        index = 0;

}

 

void InterruptTimer0() interrupt 1  //T0中断服务函数

{

    static unsigned int tmr1s = 0;  //1秒定时器

 

    TH0 = T0RH;  //定时器从新加载重载值

    TL0 = T0RL;

 

    LedScan(); //LED扫描显示

 

    tmr1s++;   //1秒定时的处理

    if (tmr1s >= 1000)

    {

        tmr1s = 0;

        flag1s = 1;

    }

}

10.4 长短按键的运用
10.4.1 51单片机RAM区域划分

  前边介绍单片机资源的时候,咱们提到过咱们的STC89C52RC共有512字节的RAM,就是用来保留数据的,如咱们定义的变量都是直接存在RAM里边。51单片机的这512字节的RAM数据是分块的,因此咱们在走访的时候,也要注意一些问题。

  51单片机的RAM分为两个部分,一块是片内RAM,一块是片外RAM。标准的51的片内RAM地址从0x00H~0x7F共128个字节,而现在咱们用的51系列的单片机都是带扩展片内RAM的,RAM是从0x00~0xFF共256个字节。片外RAM最大可以扩展到0x0000~0xFFFF共64K字节。这里有一点大家要明白,片内RAM和片外RAM的地址不是连起来的,片内是从0x00开始,片外也是从0x0000开始的。下列是几个Keil C51语言中的症结字,代表了RAM不同区域的划分,大家先记一下。

data:片内RAM从0x00~0x7F

idata:片内RAM从0x00~0xFF

pdata:片外RAM从0x0000~0x00FF

xdata:片外RAM从0x0000~0xFFFF

  大家可以看出来,data是idata的一部分,pdata是xdata的一部分。为何还这样去辨别呢?因为RAM分块的走访方式主要和汇编语言有关,因此这块内容大家了解一下便可,只需要记住如何走访速度更快便可。

  咱们定义一个变量a,可以这样:unsigned char data a=0,而咱们前边定义变量时都没有加data这个症结字,是因为咱们在Keil默认设置下,data是可以省略的,即甚么都不加的时候变量就是定义到data区域中的。data区域RAM的走访在汇编语言中用的是直接寻址,走访运行速度是最快的。如果你定义成idata,不单单可以走访data区域,还可以走访0x80H~0xFF的范围,但加了idata症结字后,走访的时候是应用了51单片机的通用寄存器进行间接寻址,速度较data速度慢一些,而且咱们平时大多数情况下不太但愿走访到0x80H~0xFF,因为这块通常用于中断和函数调用的堆栈,所以在绝大多数情况下,咱们使用内部RAM的时候,只用data就能了。

  对外部RAM来讲,使用pdata定义的变量存到了外部RAM的0x00~0xFF的地址范围里,这块地址的走访和idata相似,都是用8位的通用寄存器进行间接寻址,而如果你定义成xdata,可以走访的范围更广泛,从0到64k的地址都可以走访到,然而它需要使用2个8位的寄存器DPTRH和DPTRL来进行间接寻址,速度是最慢的。

  咱们的STC89C52RC共有512字节的RAM,256字节的片内RAM和256字节的片外RAM。一般情况下,咱们是使用data区域,data不够用了,咱们就用xdata,如果但愿程序执行效率点,可使用pdata症结字来定义。其他型号的,有更大的RAM的51系列单片机,如果要使用更大的RAM,就必需患上用xdata来走访了。

10.4.2 长短按键

  在咱们的单片机系统中,如果咱们按下一次按键加1,那咱们第八章学到的技术就能完成,然而咱们想连续加良多数字的时候,要一次次按下这个按键确切不利便,咱们但愿咱们按住按键的时候,数字会持续增加,这就是这节课的长短按键实例。

  当按下一个按键持续时间低于1秒的时候,运行一次按键动作,当按下按键持续时间超过1秒后,每经由200ms则自动再执行一次按键动作,形成一个长按键效果。这个程序做的是一个定时炸弹的效果,打开开关后,数码管显示数字0,按下向上的按键数字加1,按下向下的按键数字减1,长按向上按键1秒后,数字会持续增加,长按向下按键1秒后,数字会持续减小。设定好数字后,按下回车按键,时间就会进行倒计时,当倒计时到0的时候,用蜂鸣器和板子上的8个LED小灯做炸弹效果,蜂鸣器持续响,LED小灯全亮。

 

#include 

 

sbit BUZZ = P1^6;       //蜂鸣器控制引脚

sbit KEY_IN_1  = P2^4;  //矩阵按键的扫描输入引脚1

sbit KEY_IN_2  = P2^5;  //矩阵按键的扫描输入引脚2

sbit KEY_IN_3  = P2^6;  //矩阵按键的扫描输入引脚3

sbit KEY_IN_4  = P2^7;  //矩阵按键的扫描输入引脚4

sbit KEY_OUT_1 = P2^3;  //矩阵按键的扫描输出引脚1

sbit KEY_OUT_2 = P2^2;  //矩阵按键的扫描输出引脚2

sbit KEY_OUT_3 = P2^1;  //矩阵按键的扫描输出引脚3

sbit KEY_OUT_4 = P2^0;  //矩阵按键的扫描输出引脚4

sbit ADDR3 = P1^3;      //LED选择地址线3

sbit ENLED = P1^4;      //LED总使能引脚

 

unsigned char code LedChar[] = {  //数码管显示字符转换表

    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,

    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E

};

unsigned char LedBuff[7] = {  //数码管+独立LED显示缓冲区

    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF

};

unsigned char code KeyCodeMap[4][4] = { //矩阵按键编号到PC标准键盘键码的映照表

    { '1',  '2',  '3', 0x26 }, //数字键1、数字键2、数字键3、向上键

    { '4',  '5',  '6', 0x25 }, //数字键4、数字键5、数字键6、向左键

    { '7',  '8',  '9', 0x28 }, //数字键7、数字键8、数字键9、向下键

    { '0', 0x1B, 0x0D, 0x27 }  //数字键0、ESC键、  回车键、 向右键

};

unsigned char KeySta[4][4] = {  //整个矩阵按键确当前状态

    {1, 1, 1, 1},

    {1, 1, 1, 1},

    {1, 1, 1, 1},

    {1, 1, 1, 1}

};

unsigned long pdata KeyDownTime[4][4] = {  //每一个按键按下的持续时间,单位ms

    {0, 0, 0, 0},

    {0, 0, 0, 0},

    {0, 0, 0, 0},

    {0, 0, 0, 0}

};

 

bit enBuzz = 0;  //蜂鸣器使能标志

unsigned char T0RH = 0;  //T0重载值的高字节

unsigned char T0RL = 0;  //T0重载值的低字节

 

bit flag1s = 0;     //1秒定时标志

bit flagStart = 0;  //倒计时启动标志

unsigned int CountDown = 0; //倒计时计数器

 

void ConfigTimer0(unsigned int ms);

void DisplayNumber(unsigned int dat);

void KeyDrive();

 

void main(void)

{

    P0 = 0xFF;  //P0口初始化

    ADDR3 = 1;  //选择数码管

    ENLED = 0;  //LED总使能

    EA = 1;     //开总中断

    ConfigTimer0(1);  //配置T0定时1ms

    DisplayNumber(CountDown);

 

    while(1)

    {

        KeyDrive();

        if (flagStart && flag1s) //倒计时启动且1秒定时达到时,处理倒计时

        {

            flag1s = 0;

            if (CountDown > 0)   //倒计时未到0时,计数器递减

            {

                CountDown--;

                DisplayNumber(CountDown);

                if (CountDown == 0)    //减到0时,执行声光报警

                {

                    enBuzz = 1;        //启动蜂鸣器发声

                    LedBuff[6] = 0x00; //点亮独立LED

                }

            }

        }

    }

}

 

void ConfigTimer0(unsigned int ms)  //T0配置函数

{

    unsigned long tmp;

    

    tmp = 11059200 / 12;      //定时器计数频率

    tmp = (tmp * ms) / 1000;  //计算所需的计数值

    tmp = 65536 - tmp;        //计算定时器重载值

    tmp = tmp + 31;           //修正中断响应延时酿成的误差

    

    T0RH = (unsigned char)(tmp >> 8);  //定时器重载值拆分为高下字节

    T0RL = (unsigned char)tmp;

    TMOD &= 0xF0;   //清零T0的控制位

    TMOD |= 0x01;   //配置T0为模式1

    TH0 = T0RH;     //加载T0重载值

    TL0 = T0RL;

    ET0 = 1;        //使能T0中断

    TR0 = 1;        //启动T0

}

void DisplayNumber(unsigned int dat)  //将一个无符号整型数转到数码管显示缓冲区以供显示

{

    signed char i;

    unsigned char buf[6];

    

    for (i=0; i<6; i++)  //拆分为十进制的位

    {

        buf[i] = dat % 10;

        dat /= 10;

    }

    for (i=5; i>=1; i--) //高位的0不予显示

    {

        if (buf[i] == 0)

            LedBuff[i] = 0xFF;

        else

            break;

    }

    for ( ; i>=0; i--)   //有效数据位转换为显示字符

    {

        LedBuff[i] = LedChar[buf[i]];

    }

}

void KeyAction(unsigned char keycode)  //按键动作函数,根据键码执行相应动作

{

    if  ((keycode>='0') && (keycode<='9'))  //本程序中对0-9的数字按键不做响应

    {}

    else if (keycode == 0x26)  //向上键,倒计时设定值递增

    {

        if (CountDown < 9999)  //最大计时9999秒

        {

            CountDown++;

            DisplayNumber(CountDown);

        }

    }

    else if (keycode == 0x28)  //向下键,倒计时设定值递减

    {

        if (CountDown > 1)     //最小计时1秒

        {

            CountDown--;

            DisplayNumber(CountDown);

        }

    }

    else if (keycode == 0x0D)  //回车键,启动倒计时

    {

        flagStart = 1;         //启动倒计时

    }

    else if (keycode == 0x1B)  //Esc键,取缔倒计时

    {

        enBuzz = 0;            //关闭蜂鸣器

        LedBuff[6] = 0xFF;     //关闭独立LED

        flagStart = 0;         //休止倒计时

        CountDown = 0;         //倒计时数归零

        DisplayNumber(CountDown);

    }

}

void KeyDrive()  //按键动作驱动函数

{

    unsigned char i, j;

    static unsigned char backup[4][4] = {  //按键值备份,保留前一次的值

        {1, 1, 1, 1},

        {1, 1, 1, 1},

        {1, 1, 1, 1},

        {1, 1, 1, 1}

    };

    static unsigned long pdata TimeThr[4][4] = {  //保持按下时启动快速输入的时间阈值

        {1000, 1000, 1000, 1000},

        {1000, 1000, 1000, 1000},

        {1000, 1000, 1000, 1000},

        {1000, 1000, 1000, 1000}

    };

    

    for (i=0; i<4; i++)  //循环扫描4*4的矩阵按键

    {

        for (j=0; j<4; j++)

        {

            if (backup[i][j] != KeySta[i][j])  //检测按键动作

            {

                if (backup[i][j] != 0)  //按键按下时执行动作

                {

                    KeyAction(KeyCodeMap[i][j]);  //调用按键动作函数

                }

                backup[i][j] = KeySta[i][j];

            }

            if (KeyDownTime[i][j] > 0)  //检测执行快速输入

            {

                if (KeyDownTime[i][j] >= TimeThr[i][j]) //按下时间达到阈值时执行一次动作

                {

                    KeyAction(KeyCodeMap[i][j]);  //调用按键动作函数

                    TimeThr[i][j] += 200;         //间隔200ms执行下一次动作

                }

            }

            else //按键弹起时复位阈值时间

            {

                TimeThr[i][j] = 1000;  //启动快速输入的条件为持续按下超过1000ms

            }

        }

    }

}

 

void LedScan()  //LED显示扫描函数

{

    static unsigned char index = 0;

    

    P0 = 0xFF;                 //关闭所有段选位,显示消隐

    P1 = (P1 & 0xF8) | index;  //位选索引值赋值到P1口低3位

    P0 = LedBuff[index];       //相应显示缓冲区的值赋值到P0口

    if (index < 6)             //位选索引0-6循环,因有6个数码管+一组独立LED

        index++;

    else

        index = 0;

}

void KeyScan()  //按键扫描函数

{

    unsigned char i;

    static unsigned char keyout = 0;  //矩阵按键扫描输出计数器

    static unsigned char keybuf[4][4] = {  //按键扫描缓冲区,保留一段时间内的扫描值

        {0xFF, 0xFF, 0xFF, 0xFF},

        {0xFF, 0xFF, 0xFF, 0xFF},

        {0xFF, 0xFF, 0xFF, 0xFF},

        {0xFF, 0xFF, 0xFF, 0xFF}

    };

 

    //将一行的4个按键值移入缓冲区

    keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;

    keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;

    keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;

    keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;

 

    //消抖后更新按键状态

    for (i=0; i<4; i++)  //每行4个按键,所以循环4次

    {

        if ((keybuf[keyout][i] & 0x0F) == 0x00)

        {   //连续4次扫描值为0,即16ms(4*4ms)内都只检测到按下状态时,可认为按键已按下

            KeySta[keyout][i] = 0;

            KeyDownTime[keyout][i] += 4;  //按下持续时间累加

        }

        else if ((keybuf[keyout][i] & 0x0F) == 0x0F)

        {   //连续4次扫描值为1,即16ms(4*4ms)内都只检测到弹起状态时,可认为按键已弹起

            KeySta[keyout][i] = 1;

            KeyDownTime[keyout][i] = 0;   //按下的持续时间清零

        }

    }

    

    //执行下一次的扫描输出

    keyout++;

    keyout &= 0x03;

    switch (keyout)

    {

        case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;

        case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;

        case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;

        case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;

        default: break;

    }

}

void InterruptTimer0() interrupt 1  //T0中断服务函数

{

    static unsigned int tmr1s = 0;  //1秒定时器

    

    TH0 = T0RH;  //定时器从新加载重载值

    TL0 = T0RL;

    

    if (enBuzz)  //蜂鸣器发声处理

        BUZZ = ~BUZZ;  //驱动蜂鸣器发声

    else

        BUZZ = 1;  //关闭蜂鸣器

    

    KeyScan();  //按键扫描

    LedScan();  //LED扫描显示

    

    if (flagStart) //倒计时启动时处理1秒定时

    {

        tmr1s++;

        if (tmr1s >= 1000)

        {

            tmr1s = 0;

            flag1s = 1;

        }

    }

    else //倒计时为启动时1秒定时器始终归零

    {

        tmr1s = 0;

    }

}

长按键功能实现的重点有两个:第一,是在原来的矩阵按键扫描函数KeyScan内,当检测到按键按下后,持续的对一个时间变量进行累加,其目的是用这个时间变量来记录按键按下的时间;第二,是在按键驱动函数KeyDrive里,除原来的检测到按键按下这个动作时执行按键动作函数KeyAction外,还监测表示按键按下时间的变量,根据它的值来完成长按时的连续快速按键动作功能。

1.5 功课

1、将第一个例程进行倒计时处理,从9999.99开始进行倒计时,并且只显示有效位。

2、理解PWM的实质,在点阵上实现不同亮度的小灯的名堂排列。

3、实现数码管计时和流水灯同时运行的效果。

4、学会长短按键的用法,独立把本章程序整个写出来。

© Copyright 吾爱微电子 | 琥珀川 执行时间:3.34863