51单片机实战:定时器与数码管的应用

文章框架

51单片机实战:定时器与数码管的应用_第1张图片
框架脑图


前言

好久不《扯单》了!今儿来一发应用篇:用定时器和数码管做一个单十六进制位的计时器。从去年六月底辞职到现在已经过去半年多了,这方面的东西好像也扔的七七八八了(哭笑)。最近考驾照,需要花半天的时间练车,另半天的时间就是复习,然后总结到这里给需要的朋友参考。我在公司的项目是关于智能仓拣的,为了省成本选择51单片机,为了通信方便也用到了无线通信。所以我的《扯单》系列的第一周目会扯到ESP8266的无线通信部分(对物联网感兴趣的朋友有福,可慢慢等我更新)。
今天这部分也算是对《扯会儿单片机开发:中断》的一个应用,顺便也用到了数码管,都是很基本的东西。下一步写通信,先有线的串口通信,我会配合LCD做示范。然后就是一周目BOSS:无线网络(Wi-Fi)通信。

嗯,总之我目前是这么打算的,到时候会不会这样我就不知道了哈(任性到不能自己)。好,下面开始今天的正文。


知识点

数码管(7-Segment Display)

图片来源于百度


51单片机实战:定时器与数码管的应用_第2张图片
数码管图示

简介
我觉得看完图片我也不用做什么介绍,大家应该都见过这东西,最多最多可能就是不知道它叫数码管。
数码管的本质是几个并联的发光二极管(LED)。如果不熟悉发光二极管在单片机中应用的可参考文章:《51单片机实战:Proteus、Keil入门及点亮一个虚拟LED》
本篇应用了上图的七段一位数码管,也就是说,它本质上就是由七个发光二极管并联起来组成的。虽说上图的1位数码管是有小数点的,你可以理解为那是第八个LED。但是本篇运用的是Proteus中模拟的数码管,并没有那个小数点,看到后面你就知道啦,但要注意区分。

分类

  • 共阳(Common Anode)


    51单片机实战:定时器与数码管的应用_第3张图片
    Proteus模拟元件:7SEG-COM-ANODE

    如图就是Proteus中模拟的共阳数码管,上面单独的那个接线是7个数码管共同的阳极接线,左边七个接线是它们各自的阴极接线,所以你也就理解了“共阳”就是他们的阳极是在一起的。

  • 共阴(Common Cathod)


    51单片机实战:定时器与数码管的应用_第4张图片
    Proteus模拟元件:7SEG-COM-CATHODE

    与共阳同理,下面单独的那个接线是7个数码管共同的阴极接线,左边七个接线是他们各自的阳极接线,“共阴”就是他们的阴极是在一起的。

  • BCD(Binary Coded Decimal Display)


    51单片机实战:定时器与数码管的应用_第5张图片
    Proteus模拟元件:7SEG-BCD

    这个就比较特殊了,它是四个接线(分别对应十进制数字的8、4、2、1)。他只能显示十六进制的0~15(详细请自查阅二进制、十进制、十六进制的转换,这里不作讨论),也就是0、1、2、3、4、5、6、7、8、9、A、b(大写的没法区分)、C、d(大写的没法区分)、E、F。所以如果只作上述十六进制数字的表示,这个操作起来最方便,但是没有灵活性,不像共阳和共阴的可以表示很多鬼畜的东西出来。

演示
你看了BCD可能还清楚要怎么给它数据,但共阳共阴的哪知道它哪个接线对应哪个LED啊?
好,我给你图,有点丑别太在意:

51单片机实战:定时器与数码管的应用_第6张图片
接线与其对应的LED示意图

解释一下哈,这是我用平板画的,圈里面的数字对应的就是上面 分类中共阳和共阴数码管左侧接线从上到下的次序。旁边的八位二进制数字是代表哪一位控制哪一个LED(第八位没用。共阳0亮1灭,共阴0灭1亮)。
下面用Proteus + Keil(若不会使用详见:《 51单片机实战:Proteus、Keil入门及点亮一个虚拟LED》)做一个小程序给大家演示一下三者的区别。

  • 电路


    51单片机实战:定时器与数码管的应用_第7张图片
    电路

    其中左侧的是BCD(Proteus模拟元件:7SEG-BCD),右上为共阳(Proteus模拟元件:7SEG-COM-ANODE),右下为共阴(这个是不带共阴接线的,接起来比较简单,带接线的接起来有点麻烦,但原理一样。Proteus模拟元件:7SEG-DIGITAL)。

  • 代码

#include 
#define VALUE 0x04  //对应的二进制为:0000 0100

void main() {
    P0 = VALUE;     //连接电路中的共阳数码管
    P1 = VALUE;     //连接电路中的BCD
    P2 = VALUE;     //连接电路中的共阳数码管
    while(1);         //让单片机运行卡在这里,为了一直运行,不然数码管都会灭掉,大家以后写程序也要注意
}

#define为预编译指令中的宏定义指令,只由编译器解释,译为将文中所有的VALUE替换为0x04。关于其二进制0000 0100可以回头看一下那个丑不拉几的草图。

  • 效果


    51单片机实战:定时器与数码管的应用_第8张图片
    效果

    可以看出共阳和共阴都是对LED亮灭的操作,且正好相反。而BCD可以直接解读你给的数字。所以如果我们用共阳或共阴的数码管显示信息,就需要在代码中制作一个编码表。

定时器/计数器

简介
首先,“定时器/计数器”说的是一个东西,因为它既能计时也能计数。其次,它与数码管不一样,不是独立出来的配件,而是存在于单片机内部的一个独立的硬件部分,依赖晶振产生固定的时间间隔,产生了一定量的固定时间间隔后会引发定时器中断(参见:《扯会儿单片机开发:中断》),从而将其产生的时间信息传送给由CPU执行的主程序中。
相关寄存器

  • TMOD
    TMOD为定时器/计数器工作方式寄存器,用于确定其工作方式和功能选择。
    字节地址:89H,不能位寻址,reg52.h中已定义,单片机复位时全部清零。
位序号 7 6 5 4 3 2 1 0
位符号 GATE C/T M1 M0 GATE C/T M1 M0

你会发现,低四位和高四位格式是一样的,因为低四位(03)用于设置定时器0,高四位(47)用于设置定时器1。设置的内容都是GATE、C/T、M1、M0。

GATE C/T M1M0
门控制位 计数器还是定时器 工作方式
0:仅受TCON的TR位控制。1:由TR和外部中断一起控制。 0:定时器。1:计数器 见下表
M1 M0 工作方式
0 0 方式0,为13位定时器/计数器
0 1 方式1,为16位定时器/计数器
1 0 方式2,8位初值自动重装的8位定时器/计数器
1 1 方式3,仅适用于T0,分成两个8位计数器,T1停止计数
  • TCON
    TCON为定时器/计数器控制寄存器,用于控制其启动、停止,标志其溢出和中断情况。
    字节地址:88H,能位寻址,reg52.h中已定义,单片机复位时全部清零。
位序号 7 6 5 4 3 2 1 0
位符号 TF1 TR1 TF0 TR0 IE1 IT1 IE0 IT0

高八位与其运行和溢出有关,第八位与外部中断有关。

高八位中:

TF TR
溢出标志位 运行控制位
溢出时,由硬件置1 1:启动定时器

TF0和TR0对应定时器0,TF1和TR1对应定时器1。

低八位中:

IE IT
外部中断请求标志 外部中断触发方式选择位

IE0与IT0对应外部中断0,IE1与IT1对应外部中断1。因为此例与这里无关,不作详细介绍,有兴趣请自查资料。

初值

  • 简介
    定时器的实质是,由机器频率向一个16位寄存器累加,累加满溢出时触发中断。为了产生一个我们想要的时间间隔,比如说1s,所以我们要在这个寄存器里设定一个初值,以至于让它在这个初值上累加可以产生一个1s的倍数。这样我们就得到了稳定的时间间隔。
    这个寄存器分为TH(高八位)和TL(低八位)。所以我们需要把计算好的初值分成两部分分别放入THTL

  • 过程
    首先,我们通过单片机的晶振频率得知其时钟周期,再尤其乘以12得到机器周期。每一个机器周期在寄存器内+1,直到加满溢出产生中断

  • 例子
    若单片机频率为12Mhz,其时钟周期就是1/12μs,机器周期为1μs,也就是每1μs寄存器+1。16位的寄存器加到溢出最多需要(2^16)-1=65535μs,溢出也需要一个机器周期,所以总共要65536μs。但这个值太别扭,和我们要的1s没什么关系。我们最好让它记50000μs产生一次中断,所以其初值就设为65536-50000=15536。但我们还要将这个值分别放在高八位和低八位,所以要将这个十进制数,转换为4位十六进制数再分开赋值。
    十进制计算法:TH = 15536/256; TL = 15536%256;,进制计算问题这里不细讨论。
    这样的话,每50ms就会产生一次中断。我们只要用程序判断其中断20次就记1s


电路

这里开始进入今天实践正题,虽说电路和上面知识点的电路是一样的,但要记得把单片机的时钟频率(Clock Frequency)要调成12MHz。


51单片机实战:定时器与数码管的应用_第9张图片
电路

代码

代码部分我分为两个部分,一个是自定义的关于数码管编码表的头文件7seg.h,另一个是主程序源代码文件main.c
本例要实现的是在12MHz的时钟频率下,每隔一秒数码管显示的数字加一。

  • 7seg.h
#ifndef __7SEG_H__
#define __7SEG_H__

//共阳数码管的十六进制数编码表
unsigned char code hexForCommonAnode[] = {
    0x40, 0x79, 0x24, 0x30,
    0x19, 0x12, 0x02, 0x78,
    0x00, 0x10, 0x08, 0x03,
    0x46, 0x21, 0x06, 0x0e
};

//共阴数码管的十六进制数编码表
unsigned char code hexForCommonCathode[] = {
    0x3f, 0x06, 0x5b, 0x4f,
    0x66, 0x6d, 0x7d, 0x07,
    0x7f, 0x6f, 0x77, 0x7c,
    0x39, 0x5e, 0x79, 0x71
};

#endif
  • main.c
#include 
#include <7seg.h>    //包含自定义的编码表头文件

unsigned char THx = (65536-50000)/256;    //存储高八位寄存器初值的临时变量
unsigned char TLx = (65536-50000)%256;    //存储低八位寄存器初值的临时变量
unsigned char counter = 0;    //用于记录产生中断的次数
unsigned char digit = 0;      //用作编码表索引

//初始化函数:用于初始化各种参数
void init() {
    TMOD = 0x01;    //设置定时器0,GATE = 0, C/T = 0 , M1M0 = 01(方式1,16位定时器/计数器)

    //赋初值
    TH0 = THx;
    TL0 = TLx;

    EA = 1;    //中断总闸·开!
    ET0 = 1;  //定时器0中断·开!
    TR0 = 1;  //定时器0·运行!
}

//刷新函数:每调用一次就更新一次数码表的显示方式。
void refresh(){
    counter = 0;    //中断计数器清零
    P0 = hexForCommonAnode[digit];    //根据索引找编码表,将显示方式的二进制位丢给共阳数码管
    P2 = hexForCommonCathode[digit];  //根据索引找编码表,将显示方式的二进制位丢给共阴数码管
    P1 = digit;    //索引就是要显示的那个数字,可直接丢给BCD显示出来

    //数满后索引(数字)清零
    if(++digit >= 16)
        digit = 0;
}

//主函数
void main() {
    init();     //初始化
    refresh();  //先把0显示出来
    while(1);   //卡住
}

//定时器0的中断函数:由定时器中断自动调用,你只需要写好中断后要怎么处理就好
void timeInt_T0 () interrupt 1 {
    //每中断一次都要重新赋初值
    TH0 = THx;
    TL0 = TLx;

    //记够20次中断后,刷新显示
    if(++counter == 20)
        refresh();
}

效果

51单片机实战:定时器与数码管的应用_第10张图片
效果

结语

好了,今天就《扯单》到这里,下次不出意外的话就开始扯串口通信了。如果你对本文有什么感想和意见,欢迎在评论区畅所欲言!关于深度的话,我会慢慢加,不过也是在力所能及的范围内,毕竟只是个软工狗,还请谅解。

本次实践文中加入了小部分的理论知识介绍和文章框架脑图,不知道大家喜不喜欢,有什么想法就在评论区告诉我。


参考资料

1.《51单片机C语言教程》,郭天祥 著,电子工业出版社

你可能感兴趣的:(51单片机实战:定时器与数码管的应用)