Bootstrap

【蓝桥杯】单片机学习(3)——数码管的显示+定时器+中断

一、定时器

1、定时器基础知识

实现定时/计数,有3种主要方法:软件延时数字电路硬件定时可编程定时/计数器

  • 软件定时:让机器执行一个程序段,这个程序段本身没有具体的目的,通过挑选适当的指令和设置合适的循坏次数来实现软件延时,由于执行每条指令都需要时间,执行这个程序段所需要的时间就是延时时间。常见的指令有for循环,通过改过循环次数来改变延时的时间。缺点:软件延时占用CPU,降低CPU的利用率。

  • 数字电路硬件定时:采用小规模集成电路器件,外接定时部件(电阻和电容)。通过改变电阻和电容的值来改变定时范围。缺点:硬件电路连接好之后,不方便修改。

  • 可编程定时/计数器:硬件定时,同时可以通过软件来确定和改变它的定时值,通过初始化编程,能够满足各种定时和计数需求,也是应用较多的一种方式。

时钟周期,即时钟源频率频率(晶振)分之一,一个机器周期为12个时钟周期。机器周期就是定时器的计数周期,在我们设定好初值之后,每经过一个机器周期,定时器自动加1,加到一定数值之后就会溢出。对于8位的定时器,加到255后再加1溢出;对于16位的定时器,加到65535后再加1溢出。

定时器既可以工作在定时方式也可以工作在计数方式。这两种方式本质都是对脉冲进行计数,不过所计脉冲的来源不同。常用的是定时方式,(以T0为例)每过一个机器周期,计数器的初值加1,直至计满预设的个数,TH0和TL0回零,溢出中断标志位TF0置1,产生溢出中断。当我们设定了定时器的工作方式并启动后,定时器就按照规定的方式工作,不占用CPU的操作时间,此时CPU可以执行其他其他程序,除非定时器溢出,才会中断CPU正在执行的程序(类似于人在工作或者睡觉的时候,手表依然滴滴滴滴在走,到了设定的时间,闹钟会响)。

2、定时器的寄存器(TCON&TMOD)

标准的51单片机内,常用的定时器有T0和T1。TH0和TL0分别用于存储定时器T0定时初值的高八位和低八位;TH1和TL1用于存储定时器T1定时初值的高八位和低八位。

  • 定时器控制寄存器——TCON(定时器控制只用高四位)
76543210
符号TF1TR1TF0TR0IE1IT1IE0IT0

TF1:T1溢出中断请求标志。T1有溢出中断请求,TF1硬件置 1;T1无溢出中断请求,TF1 =0。有两种清零方式,软件清零和进入定时器中断时硬件清零。

TR1:T1运行控制位。TR1=1,启动T1工作;TR1=0,停止T1工作。
TF0:T0溢出中断请求标志。T1有溢出中断请求,TF0硬件置1;T0无溢出中断请求,TF0 =0。有两种清零方式,软件清零和进入定时器中断时硬件清零。
TR0:T0运行控制位。TR0=1,启动T1工作;TR0=0,停止T0工作。

注:硬件置1或清零是指符合条件后,单片机自动完成;软件置1或清零是指需要通过编写程序实现。

  • 定时器模式寄存器——TMOD
76543210
符号GATE(T1)C/T(T1)M1(T1)M0(T1)GATE(T0)C/T(T0)M1(T0)M0(T0)

GATE:门控信号。GATE=0, TRx=1时,可启动定时器工作;GATE=1,除TRx=1外,还需INTx= 1才可启动定时器工作。

C /T:定时/计数器选择控制位。置1为计数器,置0为定时器。

M1M0:定时器的工作模式选择位。
定时器工作模式选择位
定时器初值的计算:(设定时时间为T)
①方式0:13位定时器。T=(2^13-T0初值)* 12 / fosc
TH0 = (2^13-T * fosc/12) / 32
TL0 = (2^13-T * fosc/12) / 32;
②方式1:16位定时器。T=(2^16-T0初值)* 12 / fosc
TH0 = (2^16-T * fosc/12) /256,
TL0 = (2^16-T * fosc/12) / 256;
③方式2:8位自动重载定时器。T=(2^8-T0初值)* 12 / fosc
TH0 = TL0 = T=(2^8-T0初值)* 12 / fosc

3、定时器的应用

定时器应用可以按照以下四个步骤:
①设置特殊功能寄存器TMOD,配置好工作模式。
②设置计数寄存器THO和TL0的初值。
③设置TCON,通过TRO置1来让定时器开始计数。
④判断TCON寄存器的TF0位,监测定时器溢出情况。

二、中断

1、中断的概念

  • 中断的发生:CPU在处理某一事件A时,发生了另一事件B请求CPU迅速去处理。
  • 中断响应和中断服务:CPU暂时中断当前的工作,转去处理事件B
  • 中断返回:CPU将事件B处理完成后,再回到原来事件A被中断的地方继续处理事件A。

以上整个过程即为中断。8XX51有5个中断源,3个在片内,2个在片外(如下图)。它们在程序存储器中有固定的中断入口地址,当CPU响应中断时,硬件自动形成这些地址,由此进入中断服务程序;5个中断源有两级中断优先级,可形成中断嵌套。
51单片机中断源
使用定时器时一般也会用到中断,但要注意定时器和中断不是一个概念,定时器是单片机模块的一个资源,而中断只是单片机的一种中断机制

2、51单片机内与中断控制有关的寄存器

  • 中断控制寄存器——IE
76543210
符号EAET 2ESET1EX1ET0EX0

IE寄存器的各位对应相应的中断源,如果允许该中断源中断,则该位置1,禁止中断则该位为0。其中EA中断总控开关,是CPU是否响应中断的前提。ES串行口中断允许位,ET2为定时器T2中断允许位,ET1定时器T1中断允许位,ET0定时器T0中断允许位,EX0外部中断INT0允许位,EX1外部中断INT1允许位。

  • 定时器控制寄存器——TCON(低四位)
76543210
符号TF1TR1TF0TR0IE1IT1IE0IT0

IE1:外部中断1(INT1)运行控制位。IE1=1.启动INT1控制;
IE1=0,停止INT1控制。
IT1:外部中断1(INT1)触发方式控制位。IT1=1,下降沿触发;IT1=0,低电平触发。
IE0:外部中断0(INT0)运行控制位。IE0=1.启动INT1控制;
IE0=0,停止INT1控制。
IT0:外部中断0(INT0)触发方式控制位。IT0=1,下降沿触发;IT0=0,低电平触发。

  • 中断优先级管理寄存器——IP
76543210
符号PT2PSPT1PX1PT0PX0

当某几个中断源同时发出中断请求时,由内部查询确定优先级,查询的顺序为:(INT0的优先级最高)
INT0——T0——INT1——T1——串行口 ——T2

中断查询序列如下:
中断查询序列

3、中断的响应条件及应用

响应条件:

  • 中断源有中断请求
  • 此中断源的中断允许位为1
  • 总中断打开(允许),即EA=1

中断的应用:

void timer() interrupt n
   {

   }

注:timer()为函数名,自己随意取;interrupt为关键字,不能更改,n为中断函数序列编号。

三、数码管

1、数码管的原理介绍

数码管的原理图如图3-1,本质就是把8段LED小灯组合在一起。
数码管原理图
数码管可以分为共阴极和共阳极,内部结构如下图3-2所示:

共阴极数码管,即把8只LED灯的阴极连接在一起,由阳极来控制单个小灯的亮灭(1亮0灭);同理,共阳数码管就是阳极连接在一起,通过阴极来控制小灯的亮灭(1灭0亮)。

2、数码管的真值表(code关键字介绍)

将数码管的8个段当成8个LED小灯来控制,即a、b、c、d. e、f、g、dp- -共8个LED小灯。以图3-1,如果点亮b和c这两个LED小灯,也就是数码管的b段和c段,其他的所有的段都熄灭的话,就可以让数码管显示出一个数字1,以共阳极数码管为例,二进制数字为0b11111001,十六进制为0xF9。同理可得其他数字多对应的数码管的真值,如下:

  • 共阳极数码管的真值表:
字符01234567
数值0XC00XF90XA40XB00X990X920X820XF8
字符89ABCDEF
数值0X800X900X880X830XC60XA10X860X8E
unsigned char code LEDchar[]= {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8, 
       0x80,0x90,0x88,0x80,0xc6,0xc0,0x86,0x8e,0xbf,0x7f};  
                    //共阳极数码管从0到F的真值
  • 共阴极数码管的真值表
字符01234567
数值0X3F0X060X5B0X4F0X660X6D0X7D0X07
字符89ABCDEF
数值0X7F0X6F0X770X7C0X390X5E0X790X71
unsigned char code LEDchar[]={0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 
         0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71}; 
          //共阴极数码管从0到F的真值
  • code关键字:C51单片机中,使用关键字unsigned char或者 unsigned int定义的变量都是放在单片机的RAM中,这些变量的值可以通过程序改变。而在定义时使用code修饰的变量是存储到存储空间FLASH里的,这样可以大大的节省的RAM的空间,但是这些变量的值在程序中不可以修改,所以我们在定义一些程序中不需要修改的变量时,可以使用code关键字,例如上面提到的数码管的真值的定义:unsigned char code LEDchar[]={};

3、数码管的静态显示

(1)以普中科技的开发板为例(共阴极数码管)

原理图如下:
普科技开发版原理图(部分)代码功能:一位数码管静态显示0到F,数字(字母)切换时间为1秒。

//数码管静态显示,0到F

#include<reg52.h>

sbit LSA = P2^2;
sbit LSB = P2^3;
sbit LSC = P2^4;

typedef unsigned char u8;

unsigned char code LEDchar[]={0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 
         0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71};  //共阴极数码管从0到F的真值

void TimeInit();   //定时器初始化函数
void main()
{
	u8 cnt = 0;    //记录T0中断次数
	u8 sec =0;     //记录经过的秒数

    LSA = 0;       //数码管的位选
	LSB = 0;
	LSC = 0;

    TimeInit();    //定时器初始化

	while(1)
	{
	      if(TF0 == 1)                       //判断T0是否溢出
		  {
		      TF0 = 0;                       //T0溢出后,清楚中断标志
			  TH0 = (65536-2*110592/12)/256; //0xB8,定时20ms 0.02秒*11059200 
              TL0 = (65536-2*110592/12)%256; //0x00
			  cnt++;
			  if(cnt>=50)                    //判断是否达到50次,即一秒钟
			  {
			      cnt = 0;                   //清零
			      P0 = LEDchar[sec];         //当前秒数对应的真值表中的真值送到P0口
			      sec++;                     //秒数记录加1
			      if(sec>=16)                //秒数达到0x0F后,重新从0开始
			        {
			            sec = 0;
			        }
			  }
		  }
	}
}

void TimeInit()
{
    TMOD = (TMOD & 0xf0)|0x01;     //设置定时器工作在模式1
    TH0 = (65536-2*110592/12)/256; //0xB8,定时20ms  
    TL0 = (65536-2*110592/12)%256; //0x00
    TR0 = 1;                       //允许定时器T0工作
}

(2)以CT107D开发板为例(共阳级数码管)

CT107D开发板是蓝桥比赛用开发板,原理图如下:
CT107原理图(部分)代码如下:

//数码管静态显示,0到F

#include<reg52.h>

typedef unsigned char u8;

unsigned char code LEDchar[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,
                    0x80,0x90,0x88,0x80,0xc6,0xc0,0x86,0x8e,
                    0xbf,0x7f};  //共阳极数码管从0到F的真值

void TimeInit();   //定时器初始化函数
void HC138(u8 channel);            //138译码器通道选择函数
void ShowSMG_Bit(u8 date,u8 pos);  //数码管显示函数

void main()
{
	u8 cnt = 0;    //记录T0中断次数
	u8 sec =0;     //记录经过的秒数

    TimeInit();    //定时器初始化

	while(1)
	{
	      if(TF0 == 1)                          //判断T0是否溢出
		  {
		      TF0 = 0;                          //T0溢出后,清楚中断标志
			  TH0 = (65536-2*110592/12)/256;    //重新加载初值
              TL0 = (65536-2*110592/12)%256;    
			  cnt++;
			  if(cnt>=50)                       //判断是否达到50次,即一秒钟
			  {
			      cnt = 0;                      //清零
			      ShowSMG_Bit(LEDchar[sec],7);  //数码管显示对应的秒数
			      sec++;                        //秒数记录加1
			      if(sec>=16)                   //秒数达到0x0F后,重新从0开始
			        {
			            sec = 0;
			        }
			  }
		  }
	}
}

void HC138(u8 channel)
{
    switch(channel)
    {
        case 4:
            P2 = (P2&0x1f)|0x80;   //选择Y4对应模块(LED小灯) ,运算结果为P2高三位为100
        break;
        case 5:
            P2 = (P2&0x1f)|0xa0;   //选择Y5对应模块(蜂鸣器),运算结果为P2高三位为101
        break;
        case 6:
            P2 = (P2&0x1f)|0xc0;   //选择Y6对应模块(数码管的位选),运算结果为P2高三位为110
        break;
        case 7:
            P2 = (P2&0x1f)|0xe0;   //选择Y7对应模块(数码管的段选),运算结果为P2高三位为111
        break;
    }
}

void ShowSMG_Bit(u8 date,u8 pos)
{
    HC138(6);                       //Y6模块工作,数码管进行位选
    P0 = 0x01<<pos;                 //数码管的位选传到P0口
    HC138(7);                       //Y7模块工作,数码管进行段选
    P0 = date;                      //当前数码管要显示数值对应的真值传到P0口
}

void TimeInit()
{
    TMOD = (TMOD & 0xf0)|0x01;     //设置定时器工作在模式1
    TH0 = (65536-2*110592/12)/256; //0xB8,定时20ms 0.02秒*11059200 
    TL0 = (65536-2*110592/12)%256; //0x00
    TR0 = 1;                       //允许定时器T0工作
}

4、数码管的动态显示

(1)原理分析

动态数码管,即同时有多个数码管显示数字,实际上是轮流点亮各个数码管(同一时刻只有一个数码管被点亮),利用人眼的视觉暂留现象(余晖效应),使我们看到多个数码管同时点亮的效果。就像我们看到的电影也是由一帧一帧的画面组成的,但因为速度足够快,我们看到它就是动态的。刷新时间小于10毫秒时,对于人眼来说就是无闪烁的。

(2)数码管动态扫描的一般步骤。
  • 给对应的IO口赋值,位选编码选择哪一位亮
  • 给选中的数码管段选赋值,让其显示对应的数字或字母
  • 适当延时使其稳定(延时不易过长)
  • 消隐,避免上一次IO口确定的段码对本次段码产生影响

注:数码管的显示涉及段选和位选。段选即选择数码管的哪一段 (a、b、c、d、e、f、g、dp),位选即选择哪一个数码管。

(3)例程(普中·共阴)

以普中科技的开发板为例(共阴极数码管),原理图如下:
普中科技开发板代码功能:电子钟,从右到左,1、2位为秒,3、4位为分,5、6位为时,7、8位为天。

#include<reg52.h>

sbit LSA = P2^2;
sbit LSB = P2^3;
sbit LSC = P2^4;

typedef unsigned char u8;
typedef unsigned int u16;

u8 LedChar[] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f}; //共阴极数码管从0到9的真值
u8 LedBuff[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};  //数码管显示缓冲区,初值0xFF确保启动时都不亮
u8 flags = 1; //1秒定时标志

void TimeInit(); //定时器中断初始化函数

int main()
{
    u8 sec = 0, minutes = 0, hours = 0, days = 0; //定义记录秒,分,时,天的变量
    TimeInit(); //定时器中断初始化
    while(1)
    {
        if(flags == 1)            //判断1秒定时标志
        {
            flags = 0;            //标志位清零
            sec++;                //秒加1
            if(sec == 60)         //判断秒变量到达60秒
            {
                sec = 0;          //清0零
                minutes++;        //分钟位加1
            }
            if(minutes == 60)     //判断达到60分
            {
                minutes = 0;      //清零
                hours++;          //小时位加1
            }
            if(hours == 24)       //判断达到24小时
            {
                hours =0;         //清零
                days++;           //天数加1
            }
            LedBuff[0]= LedChar[sec%10];        //秒的低位
            LedBuff[1]= LedChar[sec/10%10];     //秒的高位
            LedBuff[2]= LedChar[minutes%10];    //分钟的低位
            LedBuff[3]= LedChar[minutes/10%10]; //分钟的高位
            LedBuff[4]= LedChar[hours%10];      //小时的低位
            LedBuff[5]= LedChar[hours/10%10];   //小时的高位
            LedBuff[6]= LedChar[days%10];       //天数的低位
            LedBuff[7]= LedChar[days/10%10];    //天数的高位
        }
    }
}

void TimeInit()
{
    EA = 1;                       //打开中断总开关
    ET0 = 1;                      //打开T0中断开关
    TMOD = (TMOD & 0xf0)|0x01;    //设置定时器工作在模式1
    TH0 = (65536-1*11059/12)/256; //0xFC,定时1ms  0.001秒*11059200 
    TL0 = (65536-1*11059/12)%256; //0x67
    TR0 = 1;                      //允许定时器T0工作
}

void InterruptTimer0() interrupt 1
{
    static u8 i = 0;                            //动态扫描的索引
    static u16 cnt = 0;  //用来记录中断次数,设置的中断定时为1ms,中断1000次即一秒钟

    TH0 = (65536-1*11059/12)/256;               //重新加载初值 
    TL0 = (65536-1*11059/12)%256;
    cnt++;                                      //中断次数计数值加1
    if(cnt>=1000)                                  //中断次数达到1000次即为1秒
    {
        cnt = 0;                                //清零,重新记录中断次数
        flags = 1;                              //设置1秒定时标志位1
    }

    P0 = 0xff;                                  //消隐
    switch(i)                                   //根据动态索引i的值进行位选,依次点亮八个数码管
    {
        case 0 :LSA = 0; LSB = 0; LSC = 0;i++; P0 = LedBuff[0];break;
        case 1 :LSA = 1; LSB = 0; LSC = 0;i++; P0 = LedBuff[1];break;
        case 2 :LSA = 0; LSB = 1; LSC = 0;i++; P0 = LedBuff[2];break;
        case 3 :LSA = 1; LSB = 1; LSC = 0;i++; P0 = LedBuff[3];break;
        case 4 :LSA = 0; LSB = 0; LSC = 1;i++; P0 = LedBuff[4];break;
        case 5 :LSA = 1; LSB = 0; LSC = 1;i++; P0 = LedBuff[5];break;
        case 6 :LSA = 0; LSB = 1; LSC = 1;i++; P0 = LedBuff[6];break;
        case 7 :LSA = 1; LSB = 1; LSC = 1;i=0; P0 = LedBuff[7];break;
        default:break;
    }
} 
(4)例程(CT107D·共阳)

以CT107D开发板为例(共阳极数码管),原理图如下:
CT107D开发板原理图(部分)功能同上,代码如下:

#include<reg52.h>

typedef unsigned char u8;
typedef unsigned int u16;

u8 LedChar[] ={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; //共阳极数码管从0到9的真值
u8 LedBuff[] = {0xFF, 0xFF, 0xFF,0xFF, 0xFF, 0xFF, 0xFF, 0xFF};    //数码管显示缓冲区,初值0xFF确保启动时都不亮
u8 flags = 1; //1秒定时标志

void TimeInit();                   //定时器中断初始化函数
void HC138(u8 channel);            //138译码器通道选择函数
void ShowSMG_Bit(u8 date,u8 pos);  //数码管显示函数

int main()
{
    u8 sec = 0, minutes = 0, hours = 0, days = 0; //定义记录秒,分,时,天的变量
    TimeInit();                   //定时器中断初始化
    while(1)
    {
        if(flags == 1)            //判断1秒定时标志
        {
            flags = 0;            //标志位清零
            sec++;                //秒加1
            if(sec == 60)         //判断秒变量到达60秒
            {
                sec = 0;          //清0零
                minutes++;        //分钟位加1
            }
            if(minutes == 60)     //判断达到60分
            {
                minutes = 0;      //清零
                hours++;          //小时位加1
            }
            if(hours == 24)       //判断达到24小时
            {
                hours = 0;        //清零
                days++;           //天数加1
            }
            LedBuff[0]= LedChar[sec%10];        //秒的低位
            LedBuff[1]= LedChar[sec/10%10];     //秒的高位
            LedBuff[2]= LedChar[minutes%10];    //分钟的低位
            LedBuff[3]= LedChar[minutes/10%10]; //分钟的高位
            LedBuff[4]= LedChar[hours%10];      //小时的低位
            LedBuff[5]= LedChar[hours/10%10];   //小时的高位
            LedBuff[6]= LedChar[days%10];       //天数的低位
            LedBuff[7]= LedChar[days/10%10];    //天数的高位
        }
    }
}

void TimeInit()
{
    EA = 1;                       //打开中断总开关
    ET0 = 1;                      //打开T0中断开关
    TMOD = (TMOD & 0xf0)|0x01;    //设置定时器工作在模式1
    TH0 = (65536-1*11059/12)/256; //0xFC,定时1ms  0.001秒*11059200 
    TL0 = (65536-1*11059/12)%256; //0x67
    TR0 = 1;                      //允许定时器T0工作
}

void HC138(u8 channel)
{
    switch(channel)
    {
        case 4:
            P2 = (P2&0x1f)|0x80;   //选择Y4对应模块(LED小灯) ,运算结果为P2高三位为100
        break;
        case 5:
            P2 = (P2&0x1f)|0xa0;   //选择Y5对应模块(蜂鸣器),运算结果为P2高三位为101
        break;
        case 6:
            P2 = (P2&0x1f)|0xc0;   //选择Y6对应模块(数码管的位选),运算结果为P2高三位为110
        break;
        case 7:
            P2 = (P2&0x1f)|0xe0;   //选择Y7对应模块(数码管的段选),运算结果为P2高三位为111
        break;
    }
}

void ShowSMG_Bit(u8 date,u8 pos)
{
    HC138(6);                       //Y6模块工作,数码管进行位选
    P0 = 0x01<<pos;                 //数码管的位选传到P0口
    HC138(7);                       //Y7模块工作,数码管进行段选
    P0 = date;                      //当前数码管要显示数值对应的真值传到P0口
}

void InterruptTimer0() interrupt 1
{
    static u8 i = 0;                           //动态扫描的索引
    static u16 cnt = 0;  //用来记录中断次数,设置的中断定时为1ms,中断1000次即一秒钟

    TH0 = (65536-1*11059/12)/256;              //重新加载初值 
    TL0 = (65536-1*11059/12)%256;
    cnt++;                                     //中断次数计数值加1
    if(cnt>=1000)                                 //中断次数达到1000次即为1秒
    {
        cnt = 0;                               //清零,重新记录中断次数
        flags = 1;                             //设置1秒定时标志位1
    }

    P0 = 0xff;                                 //消隐
    switch(i)                                  //根据动态索引i的值进行位选,依次点亮八个数码管
    {
        case 0 : i++; ShowSMG_Bit(LedBuff[0],7); break; //最右边一位数码管(com8)对应P0^7口
        case 1 : i++; ShowSMG_Bit(LedBuff[1],6); break;
        case 2 : i++; ShowSMG_Bit(LedBuff[2],5); break;
        case 3 : i++; ShowSMG_Bit(LedBuff[3],4); break;
        case 4 : i++; ShowSMG_Bit(LedBuff[4],3); break;
        case 5 : i++; ShowSMG_Bit(LedBuff[5],2); break;
        case 6 : i++; ShowSMG_Bit(LedBuff[6],1); break;
        case 7 : i=0; ShowSMG_Bit(LedBuff[7],0); break;
        default:break;
    }
} 

前一篇: 单片机学习(2)——点亮LED小灯

下一篇: 单片机学习(4)——点阵LED

;