51单片机之计时器

 

最后我附上了问题,如果有同学知道希望可以解答,谢谢

 

 

一、实验目的:

让六个数码管动态显示数字,计算秒数

二、实验分析

a.因为要显示秒数,所以我们首先应该定义一个数组,用来存放0-9数字在数码管上对应的    十六进制数

b.3/8译码器的使能设置

c.定时器的理解。首先应该知道定时器与TMOD和和TCON有关。对于TMOD值得我们特别注意的是TMODGate位(门控位)和C/T位。Gate=0时,仅由运行控制位TR1/TR0来启动定时器运行。而当Gate=1时,仅由运行控制位TR1/TR0和外中断引脚(INT0INT1)上的高电平共同来启动定时器运行。C/T=0为定时模式,C/T=1为计数模式。我这次用到的是定时器1的方式一,为了不影响TMOD上的其他位,我这次用了逻辑运算:TMOD=TMOD|0x10;MOD=TMOD&0x0DF。对于定时方式一实际上是有一个16位数(THTL组成,各占8位)进行累加。上接着看TCON,我们主要用到的TF(定时溢出标志)和TR(定时器运行控制位)。上面说的16位数溢出时,TF=1。(特别需要注意的是:当执行溢出中断时TF位可由硬件清零,当不执行溢出中断时TF位一定要由软件清零)。而对于TR位,当TR=1时开始计时,TR=0,停止计时。因为方式一最长定时是100多毫秒,所以可以每1ms进行一次中断,中断1000次就是1s

d.定时器的初始化:

因为方式一是16位,所以用公式:(65536-X*12/11.0592M=0.001,可以得到x=FC66H

所以我们令TH1=0xFCTL1=66

e.中断的初始化。我们主要用到中断请求寄存器IE。为了能够响应定时器1中断,我们需要另总中断使能位EA=1,定时器1中断使能位ET1=1

f.中断服务程序。这里特别注意我们需要重新设置TH1=0xFCTL1=66,因为如果不设置,那么TH1=0x00TL1=0x00,这样下次进入中断的时间久不是1ms了。同时我们在这里每3ms进行一次刷新一个数码管。这样在18ms就可以将全部数码管刷新一个。(因为人视线残留为20ms,而20/6=3.33s所以每3ms刷新一次,这样就不会占用太多cpu!!)。

g. 刷新函数

需要指出的是在用C和汇编还是有一些方面是不一样的,在进行汇编编写是遇到的一个

比较棘手的问题就是六位数的存放。因为C中有32数,但是汇编没有,最后我用到的是用R0--R5分别存放数码管显示的数字。

三、实验代码

1.C语言实现:

 #include <reg52.h>

typedef  unsigned char   uint8;

typedef  unsigned int    uint16;

typedef  unsigned long   uint32;  

//共阳数码管0-9

uint8 number[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};

uint8 a[6];

uint32 sec=0; //用于记录秒数,因为六个数码管已经超过16位可以表示最大范围,所以用32 

uint16 count=0;//用于记录中断次数,因为我设置每次中断时1ms,所以count=1000时就是一秒 

sbit ENLED1 = P1^4;

sbit ENLED2= P1^3;

sbit ADDR0 = P1^0;

sbit ADDR1 = P1^1;

sbit ADDR2 = P1^2;

//首先3/8译码器使能

void en()

{

 ENLED1=0;

 ENLED2=1;

}

//定时器初始化

void init_timer()

{

TMOD=TMOD|0x10;

TMOD=TMOD&0x0DF;

TH1=0x0FC;

TL1=0x66;

TR1=1;

}

//中断初始化

void init_interrupr()

{

  EA=1;//总中断使能位

  ET1=1;//定时器1中断使能位 

}

void display()

{

 static uint8 j = 0;

switch(j)

   {

   case 0: ADDR0 = 0;ADDR1 = 0;ADDR2 = 0;j++;P0 = number[a[0]];break;

case 1: ADDR0 = 1;ADDR1 = 0;ADDR2 = 0;j++;P0 = number[a[1]];break;

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

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

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

case 5: ADDR0 = 1;ADDR1 = 0;ADDR2 = 1;j=0;P0 = number[a[5]];break;

default: break;

}

}

void ov_interrupt() interrupt 3

{

   count++;

   TF1=0;// 这里因为我们进行了中断,所以硬件会自动清零,所以不一定要加上这一句

   TH1=0x0FC;

   TL1=0x66;

   if(count==1000)//说明是一秒

   {

    sec++;

 count=0;

 a[0] = sec%10;

 a[1] = sec/10%10;

 a[2] = sec/100%10;

 a[3] = sec/1000%10;

 a[4] = sec/10000%10;

 a[5] = sec/100000%10;

}

if(count%3==0)

   display();//进行刷新,这里我们可以看出,我们是3ms进行一次刷新,这样就不会占用太多cpu

}

void main()

{

  en();  //首先3/8译码器使能

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

  init_interrupr();//中断初始化

  while(1); //让程序一直处于运行中

}

2.汇编语言实现:

org 00H

ljmp start

 org 1BH //定时器1中断入口地址

ljmp ov_interrupt  

org 30H

start:      

   mov R6,#00//用于记录产生几次中断,如果(R6=100,说明为1s

   mov R0,#00//R0-R5用于存储数码管上数字

   mov R1,#00

   mov R2,#00

   mov R3,#00

   mov R4,#00

   mov R5,#00

   mov dptr,#numb

   lcall en

   lcall init_interrupt

   lcall init_timer

       here: sjmp here  //好比是一个while1)循环

;首先3/8译码器使能

en:

   clr P1.4

setb  P1.3

ret

//中断初始化

init_interrupt:

 setb EA

 setb ET1

 ret

 //定时器初始化

init_timer:

   /*orl  TMOD,#0x10

   anl  TMOD,#0x0DF*///这两技能说明工作在T1的方式1  且计时有TR1启动

   mov TMOD,#10H

   mov  TH1,#0xDC

   mov  TL1,#0x00//一个10ms的定时初值设置

   setb TR1  //让定时器1开始工作

 ret

delay:   

         

      mov 20H,#3                 ;一个机器周期

    Dd2:

       mov 30H,#10               ;一个机器周期

    Dd1:

       mov 40H,#3                ;一个机器周期

    djnz 40H,$                    ;2个机器周期

    djnz 30H,Dd1                   ;2个机器周期

    djnz 20H,Dd2                   ;个机器周期

Ret

 //刷新函数

display:

 D0: 

    mov   A,R0

movc  A,@A+dptr

clr   P1.0

clr   P1.1

clr   P1.2

mov   P0,A

    lcall delay 

 D1:

 mov   A,R1

movc  A,@A+dptr

setb  P1.0

clr   P1.1

clr   P1.2

mov   P0,A

 lcall delay 

 D2:

 mov   A,R2

movc  A,@A+dptr

clr   P1.0

setb  P1.1

clr   P1.2

mov   P0,A 

 lcall delay 

 D3:

 mov   A,R3

movc  A,@A+dptr

setb  P1.0

setb  P1.1

clr   P1.2

 mov   P0,A 

   lcall delay 

 D4:

 mov   A,R4

movc  A,@A+dptr

clr   P1.0

clr   P1.1

setb  P1.2

mov   P0,A

 lcall delay  

 D5:

 mov   A,R5

movc  A,@A+dptr

setb  P1.0

clr   P1.1

setb  P1.2

mov   P0,A

lcall delay 

ret

//TF1=1中断服务程序

ov_interrupt:

  push DPH

  push DPL

  push psw

  mov  TH1,0xDC//一个0.01s的定时初值设置

  mov  TL1,0x00 

  inc R6 //中断计数器加1,当(R6=100,就是1s

  clr TF1//  这里因为我们进行了中断,所以硬件会自动清零,所以不一定要加上这一句

  cjne R6,#100,return //判断中断计数器R6,如果不为100则转移到here

  mov R6,#00H 

 led0: 

 inc R0//数码管0数字加1

 cjne R0,#10,return  //判断(R0),如果不为10,转移到display

                  //否则为10就变为0,然后转移到led1

 mov R0,#00H    

 led1:

 inc R1

 cjne R1,#10,return

 mov R1,#00H

 led2:

 inc R2

 cjne R2,#10,return

 mov R2,#00H

 led3:

 inc R3

 cjne R4,#10,return

 mov R4,#00H

 led4:

 inc R4

 cjne R4,#10,return

 mov R4,#00H

 led5:

 inc R5

 cjne R5,#10,return                

 mov R5,#00H

 //如果执行这一句说明超过了999999

return:     

 pop psw

 pop DPL

 pop DPH

dis:

 mov A,R6//R6为偶数,进行display,也就是每两次中断(20ms)进行一次刷新

 anl A,#01H

 cjne A,#0,quit

 call display

   quit:

    reti  

//0-9

numb:DB 0C0H,0F9H,0A4H,0B0H,99H,92H,82H,0F8H,80H,90H

end  

 

实验结果:

和自己实验前的分析结果一样。

四、实验问题

1.定时问题:我的目的是为了得到1s,但是按照我的方法得到的只是一个非常接近于1s的数字,不知道有没有什么办法可以精确的得到1s

2.在进行汇编编程的时候,我用6个寄存器表示一个六位数,但是总是感觉这样消耗的内存大了,并且一个8位是只用了0-9,好像不是很节约内存,不知道有没有好的办法,可以解决这个问题。

3.不知道为什么第一个数码管比其他数码管亮很多

你可能感兴趣的:(编程,timer,工作,汇编,存储,语言)