51单片机之显像管与定时器中断

51单片机之显像管与定时器中断

昨晚和前晚分别学习了51单片机对显像管的控制和内部定时器中断的使用,学起来倒也不算太费力。显像管的控制主要内容就是其动态扫描,这个我在电工实习课上和当时玩Arduino的时候已经了解和学习过,并不算太新的内容,唯一遇到的小困难就是由于我图便宜买的板子其内部连接与郭天祥教程里的板子是不一样的,所以得根据板子的连接重新写程序;定时器中断的主要内容是中断处理,这个在汇编里已经了解过了,需要学习的是单片机内部控制中断的寄存器的使用方法。

郭天祥书里的教程用的都是TX-1C,淘宝上都要100多,除去被学长坑的那两个板子,我又在网上买了一个20多的51板。与TX-1C使用P0加两个锁存器分别控制6位共阴数码显像管的片选端和位选端不同,我买的51板上只有三位显像管,且其片选段通过P1的前三个端口直接控制,位选端直接通过P0控制。其共阴数码显像管连接图和CPU连接图如下图所示。

根据模电知识及共阴数码管的原理不难判断,只有当W1、W2、W3分别为低电平时,第1、2、3位(从右向左)才会分别工作,而若W1、W2、W3为低电平,需保证J1短接(用跳线帽),且DB1、DB2、DB3为高电平即P1.0~P1.2为高电平(板子中已有上拉电阻)。至于位选端,显然a~g及dp分别由P0.0~P0.7控制,高电平有效。为了使用方便,可以提前将0~F对应的位选数据存到一个字符数组中,并声明为code类型(表示该数据会存放到储存程序的存储区中而不是内存中),详情见下面的示例代码。

至于动态扫描,说白了,就是让数码显像管依次单独显示各位,但显示位的转换速率要足够快,快过人眼所能识别的频率。还有一点需要注意的是,需要“消影”,我的具体做法就是在送入位选数据前,片选段全部送入相应值使得各显像管均不显示。

说了这么多,来个简单例子体会下动态扫描和数码管显示吧:

#include<reg52.h>

sbit we1 = P1 ^ 0;
sbit we2 = P1 ^ 1;
sbit we3 = P1 ^ 2;
// According to circuit graph, assign P1.0~P1.2 to we1~3

unsigned char code table[] = {
    0x3f, 0x06, 0x5b, 0x4f,
    0x66, 0x6d, 0x7d, 0x07,
    0x7f, 0x6f, 0x77, 0x7c,
    0x39, 0x5e, 0x79, 0x71
};
// Declare a table for converting from num to the value of 8 segment

unsigned char num;

void delayms(unsigned ms);
// A function used to delay according to circulation

void main()
{
// Display "123" on the 3-digit-tube
    we1 = 0;
    we2 = 0;
    we3 = 0;
    // Elinimate the "GShadow"
    P0 = table[1];
    // Send the segment data to P0
    we1 = 0;
    we2 = 0;
    we3 = 1;
    // Send the selection data to we1~3
    delayms(1);

    we1 = 0;
    we2 = 0;
    we3 = 0;
    P0 = table[2];
    we1 = 0;
    we2 = 1;
    we3 = 0;

    delayms(1);
    we1 = 0;
    we2 = 0;
    we3 = 0;
    P0 = table[3];
    we1 = 1;
    we2 = 0;
    we3 = 0;
    delayms(1);
}

void delayms(unsigned int ms)
{
    //Delay approximately ms(virable) ms(unit);
    unsigned int i, j;
    for (i = ms; i > 0; i--)
        for (j = 110; j > 0; j--);
}

中断的概念在很多地方都有提到,在郭天祥的51单片机C语言教程这本书里讲的也比较形象。将中断处理比作家庭妇女听到水烧开的声音后放下手中正在洗的衣服然后转去接开水而后又接着洗衣服,大概就是这个意思吧。关于中断我们无非要知道两件事情,一是中断类型和如何触发,二是中断处理程序如何编写。51单片机共有5个中断源,而52单片机比其多了一个计时器中断源T2。具体的入口地址、序号及默认中断级别如下表所示:

在C语言中编写中断处理程序时需要用到的是其序号。51单片机中通常需要设置两个与中断有关的寄存器:中断允许寄存器IE和中断优先寄存器IP。这两个寄存器都可通过位寻址,控制着中断的开关与优先级设置(默认为关和上述表中的优先级)。在这一小结中,主要使用的的定时器/计时器(可以这样理解:定时器使用的是内部机器周期脉冲,计时器使用的是外部脉冲)中断,实质是加1计数器(由高8位和低8位两个寄存器组成),其又需要两个寄存器THX和%TLX(X=0、1(52的X可为2))来对该中断进行控制:TMOD是定时器/计数器的工作方式寄存器,确定工作方式和功能;TCON是控制寄存器,控制T0、T1的启动和停止及设置溢出标志。这两个寄存器各位的具体含义和作用请查阅相关资料,这里就不再累述了。需要注意的是,TMOD不能位寻址。除此外,要了解C51中中断处理程序的写法,相对简单,就是在写普通的函数时在函数名后面加上“ (注意与函数名之间有空格)中断号 using 工作组”,其中C51编译器在编译程序时可自动分配工作组,因此“using”这句话通常不写。

最后,给出一个利用51单片机内部计时器中断编写的超简单计秒器(由于本板子的局限,显像管的3位片选端与led1~3由共同的io口控制):

#include<reg52.h>

sbit led8 = P1 ^ 7;
// Assign P1.7 to led8
sbit we1 = P1 ^ 0;
sbit we2 = P1 ^ 1;
sbit we3 = P1 ^ 2;
// According to circuit graph, assign P1.0~P1.2 to we1~3
// Notice that it collide with led1~led3

unsigned char num = 0, num_unit = 0, num_digit = 0;
// num is used to depict the seconds
unsigned char num1 = 0, num2 = 0;
// num1 and num2 are used for counting in T0 and T1
unsigned char code table[] = {
    0x3f, 0x06, 0x5b, 0x4f,
    0x66, 0x6d, 0x7d, 0x07,
    0x7f, 0x6f, 0x77, 0x7c,
    0x39, 0x5e, 0x79, 0x71
};
// Declare a table for converting from num to the value of 8 segment

void delayms(unsigned ms);
// A function used to delay according to circulation
void display(unsigned char digit, unsigned unit);
// Display num_digit and num_unit on the digit tube

void main()
{
    we3 = 0;
    TMOD = 0x11;
    // Set the work mode of T0 and T1
    TH0 = (65536 - 45872) / 256;
    TL0 = (65536 - 45872) % 256;
    // Initial the counting register of T1
    // Notice that 45872 = (50 × 10^-3) / (12 × 1 / (11.0593 × 10^6))
    TH1 = (65536 - 45872) / 256;
    TL1 = (65536 - 45872) % 256;
    // Initial the counting register of T1
    EA = 1;
    // The total switch of IE register
    ET0 = 1;
    // Switch on the insterrupt of T0
    ET1 = 1;
    // Switch on the insterrupt of T1
    TR0 = 1;
    // Run the timer T0
    TR1 = 1;
    // Run the timer T1
    while (1)
    {
        display(num_digit, num_unit);
    }
}

void delayms(unsigned int ms)
{
    unsigned int i, j;
    for (i = ms; i > 0; i--)
        for (j = 110; j > 0; j--);
}

void display(unsigned char digit, unsigned unit)
{
    we1 = 0;
    we2 = 0;
    // Elinimate the "Shadow"
    P0 = table[digit];
    we1 = 0;
    we2 = 1;
    delayms(1);
    we1 = 0;
    we2 = 0;
    P0 = table[unit];
    we1 = 1;
    we2 = 0;
    delayms(1);
}

void T0_time() interrupt 1
{
    TH0 = (65536 - 45872) / 256;
    TL0 = (65536 - 45872) % 256;
    // When T0 reach 0xffff, reset the TH0TL0 to "zero" value
    num1 += 1;
    if (num1== 20)
    {
        num1 = 0;
        led8 = ~led8;
    }
}

void T1_time() interrupt 3
{
    TH1 = (65536 - 45872) / 256;
    TL1 = (65536 - 45872) % 256;
    // When T1 reach 0xffff, reset the TH1TL1 to "zero" value
    num2 += 1;
    if (num2 == 20)
    {
        num2 = 0;
        num += 1;
        if (num == 60)
            num = 0;
        num_digit = num / 10;
        num_unit = num % 10;
    }
}

      下面给出运行示例图:

Leave a Reply

电子邮件地址不会被公开。 必填项已用*标注