初学51单片机的灵魂中断以及数码管与C语言实践

数码管鬼影与晃动的产生

看程序(可以直接找后面的程序,有注释)           注:笔者开发板子来自金沙滩工作室

# include<reg52.h>

sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
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[] = {
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0XFF      //6个数码管初始化
  };

    void main()
    {
    unsigned int cnt = 0;
    unsigned long    sec = 0;//
    unsigned char i = 0;    
   
        ENLED = 0;
    ADDR3 = 1;
    TMOD  = 0x01;
    TH0 = 0xFC;
    TL0 = 0x76;
    TR0 = 1;
       while(1)
       {
                  if(TF0 == 1)
            {
                           TF0 = 0;
                             TH0 = 0xFC; // 1ms溢出
                             TL0 = 0x67;
                              cnt++;
                              if(cnt >= 1000)  
                                {
                                   cnt = 0;
                                     sec++; //每过1秒加1
                                     LedBuff[0] = LedChar[sec%10];
                                     LedBuff[1] = LedChar[sec/10%10];
                                     LedBuff[2] = LedChar[sec/100%10];
                                     LedBuff[3] = LedChar[sec/1000%10];
                                     LedBuff[4] = LedChar[sec/10000%10];
                                     LedBuff[5] = LedChar[sec/100000%10]; //5句减法运算花了5.66ms
                                 }    
                            
                        P0 = 0xFF;     (该句可以消除鬼影)
                          switch(i)
                            {
                              case 0: ADDR2 = 0; ADDR1 = 0; ADDR0 = 0; i++; P0 = LedBuff[0]; break;
                                case 1: ADDR2 = 0; ADDR1 = 0; ADDR0 = 1; i++; P0 = LedBuff[1]; break;
                                case 2: ADDR2 = 0; ADDR1 = 1; ADDR0 = 0; i++; P0 = LedBuff[2]; break;
                                case 3: ADDR2 = 0; ADDR1 = 1; ADDR0 = 1; i++; P0 = LedBuff[3]; break;
                                case 4: ADDR2 = 1; ADDR1 = 0; ADDR0 = 0; i++; P0 = LedBuff[4]; break;
                                case 5: ADDR2 = 1; ADDR1 = 0; ADDR0 = 1; i = 0; P0 = LedBuff[5]; break;
                                default: break;          //数码管刷新语句
                         }
                     }  
                }                 
          }

看下结果视数码管鬼影和晃动的产生_哔哩哔哩_bilibili

可以看到数码管每加一秒它都会晃动一下,而且还有点淡淡的红印子产生,

这是如何产生的呢?

           我们取个值假设 sec=122 时假设我们刚运行了case1 语句,过了若干机器周期的时间第二次执行到该处由于i++语句,这次要执行case2,虽然单片机执行速度很快但它依然需要一句句执行语句的功能表达象。

因此可以看到当执行case2语句ADDR2=0;这句的时候 数码管的使能端口依然是(ADDR2 = 0; ADDR1 = 0; ADDR0 = 1;)事实上001是数码管1的使能值。

接着执行ADDR=1;这句,现在三个端口是ADDR2 = 0; ADDR1 = 1; ADDR0 = 1;事实上011是数码管3的使能值,这就存在了个瞬间态即使时间非常非常短,但是其实需要的执行的是数码管2的刷新。只有执行到ADDR0=0;才正式执行数码管2的刷新。单片机的执行速度很快1秒钟的时间这些场景不知道发生了多少次,每次case语句转换的时候都会发生瞬态。因此看到了淡淡的残影。

如何避免呢?break语句之前加个delay()可以解决这个问题 不过我没试过,笔者还没学到delay语句。 还有一种就是在执行刷新的时候单片机P0端口短暂的关闭一下 P0 = 0xFF;     (该句可以消除鬼影)

看结果消除鬼影视频数码管无鬼影有晃动_哔哩哔哩_bilibili

对比着看比较明显。

数码管鬼影解决了但是晃动依然没解决:我们看程序这个程序用Debug看1.00571s

1.01137s  该程序的执行时间1.01137s-1.0057s=0.00567s=5.67ms

也就是说每过5.67ms才能执行一下刷新语句,相比刷新语句这个数码管取值时间过于久了。因为是每过一秒就会进入该程序,而且该程序还在while()前面这就会导致某个数码管的点亮时间比别的数码管时间长,因为要加上5.67ms的等待时间,事实上你把while()函数放在数码管的取值函数前面,它依然会影响。诸位自行想象一下。刷新不均匀导致视觉上的闪烁。

因此这边就引入了中断

首先介绍下中断使能寄存器

中断使能寄存器可以位寻址的EA = 1;置1的时候中断功能激活。

为了消除晃动引入了中断,看程序

# include<reg52.h>

sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
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        //数码管0-F的真值数组
  };
unsigned char LedBuff[] = {
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0XFF             // 6个数码管的顺序数组初始化
  };
unsigned char i = 0;        //定义switch()的值初始化
unsigned int cnt = 0;       //定义寄存器溢出次数标记
unsigned char flag1s = 0;	 //定义1秒标志
	void main()
	{
	
	unsigned long	sec = 0;  //定义数码管真值数组下标
		EA = 1;               //IE中断使能寄存器 使能位 置1 打开中断
		ENLED = 0;            //三八译码器使能
    ADDR3 = 1;            //三八译码器使能
    TMOD  = 0x01;        // 模式寄存器赋值使工作在模式1
    TH0 = 0xFC;          //设置寄存器一次溢出时间高8位的初值
    TL0 = 0x76;          //设置寄存器一次溢出时间低8位的初值 设置是1ms
		ET0 = 1;             //启动定时器0中断
    TR0 = 1;             //启动T0
       while(1)
       {
			      if(flag1s == 1) 
            {
						   flag1s = 0;
							 sec++;
							
									 LedBuff[0] = LedChar[sec%10];          //取当前秒数(十进制)的个位数的值给数码管0
									 LedBuff[1] = LedChar[sec/10%10];       //取当前秒数(十进制)的十位数的值给数码管1
									 LedBuff[2] = LedChar[sec/100%10];      //取当前秒数(十进制)的百位数的值给数码管2
									 LedBuff[3] = LedChar[sec/1000%10];     //取当前秒数(十进制)的千位数的值给数码管3
									 LedBuff[4] = LedChar[sec/10000%10];    //取当前秒数(十进制)的万位数的值给数码管4
									 LedBuff[5] = LedChar[sec/100000%10];   //取当前秒数(十进制)的十万位数的值给数码管5
							         //6个减法运算花了5.66ms
						 }	 	
							
						 
			  }	 
	 }			 
						 
						 
	/*定时器0中断服务函数*/

	  void itrTimer0() interrupt 1  //定时0中断函数标志,进入中断TF0硬件置0
		{
		  TH0 = 0xFC;   //定时器重装初值
			TL0 = 0x67;
			cnt++;
			if(cnt >= 1000) //1000次溢出后进入该函数
			{
			  cnt = 0;
				flag1s = 1;  //秒数标志赋值1
			
			}
			   P0 = 0xFF;  //刷新数码管前P0口8位全部置1使LED都不工作。
			
			        switch(i)
							{
							  case 0: ADDR2 = 0; ADDR1 = 0; ADDR0 = 0; i++; P0 = LedBuff[0]; break;  //数码管1刷新
								case 1: ADDR2 = 0; ADDR1 = 0; ADDR0 = 1; i++; P0 = LedBuff[1]; break;  //数码管2刷新
								case 2: ADDR2 = 0; ADDR1 = 1; ADDR0 = 0; i++; P0 = LedBuff[2]; break;  //数码管3刷新
								case 3: ADDR2 = 0; ADDR1 = 1; ADDR0 = 1; i++; P0 = LedBuff[3]; break;   //数码管4刷新
								case 4: ADDR2 = 1; ADDR1 = 0; ADDR0 = 0; i++; P0 = LedBuff[4]; break;   //数码管5刷新
								case 5: ADDR2 = 1; ADDR1 = 0; ADDR0 = 1; i = 0; P0 = LedBuff[5]; break; //数码管6刷新
								default: break;
							 }
		
		  }
							
						  
							
							
							
			
	
	

结果视频:无鬼影无晃动_哔哩哔哩_bilibili

该程序应用的是定时器0的中断,它会在在储存寄存器溢出的时候即TF0=1的时候进入中断 如果已经中断使能过 即EA =1以及控制寄存器使能 即TR0=1. 并且进入中断后TF0会被硬件置0,不在需要软件清零了。

void itrTimer0() interrupt

这里interrupt 是关键字代表进入中断,后面的1代表进入T0中断(定时器0中断)

可以看到timer 0的智能中断查询次序是1,这个值就是中断的函数标号即你输入interrupt 1 就是定时器0的中断 。interrupt 3 就是定时器1的中断 

它具体的算法是X(函数编号)*8+3=向量地址  取定时器1的向量地址001B=27 则 3*8+3=17 这个3就是定时器1的中断标记。往后看可一看到TF1是它的中断请求,即置1就发生一次中断请求如果有中断函数的话。

       从程序结构中可以知道,中断函数并没有包含在主函数中。

但是程序都是从主函数的第一句开始执行的并且一直执行主函数中的语句!(目前笔者的理解

什么时候开始执行和主函数并列的中断函数呢?那就需要一个信号或者请求告诉主程序停一下去执行中断的函数内容。因为51单片机是串行工作的(不能同时执行两句不同的语句),因此对于固有优先级的中断函数来说,在收到中断请求的时候,主函数会停止执行的语句,先执行中断函数,中断函数执行结束后再回到主函数停顿那句继续执行。

中断函数都是有执行时间的,如果在执行比如定时器1的中断函数,程序还没执行完毕又收到定时器0的中断请求,我们看到定时器0的中断优先级1是高于定时器1的优先级3的。这时要进入定时器0的中断吗?不需要,在没有设置定时器0的中断优先级的时候,程序会先执行完定时1的中断函数再接着执行定时器0的中断函数。

如此引入了中断优先级寄存器

如上述定时器0设置中断优先寄存器PT0 = 1那么再发生刚才情况程序就不会等待定时器1的中断执行完毕,会先执行定时器0的中断然后返回执行定时1的中断。

再假设定时器1设置了中断优先即PT1=1,而定时器0没有设置PT0=0,再发生中断响应的时候,无论程序是在执行主程序语句还是已经在执行定时器0的中断函数语句,都会停止去执行定时器1的中断函数。

但是假如定时器0也设置了中断优先,那么比较定时器之间的优先级哪个高哪个就先执行。

该程序由于引入了中断,一旦TF0置1就会进入中断函数,然后某个数码管就会刷新一下,6个数码管全部刷新完是6ms,且不受其他的因素的影响非常的稳定,视觉上数码管就不会明显的跳动了

            总结:这个中断之间干活就像排队打饭,一开始几个中断按照个子高低打饭,个子高的先打饭(毕竟高个力气大),一开始大家规规巨巨的按照这个顺序打发,有新来的和没排到打饭的比较下高低,大家也客客气气的重新定个次序排队。即使来了个大高个子也是等在打饭的打完饭再上。

 但是就有不开眼的小个子一上来就把还在打饭的大个子踹开,众人大怒,一看这小子头顶着SVIP打饭优先),虽忿忿不平也只能偃旗息鼓。此时有头绑黄带子小伙,愤而高呼“苍天已死黄天当立”,遂取鱼腹之宝裹于头上只见几个大字(俺也一样)。龙行虎步之间一脚踹在小个子的屁股上,曰:此乃天降正义,遂送上饭盆。 忽而一阵狂风飘过,众人一度东倒西歪,只听远方有人高呼“秦失其鹿,天下共逐之”众人定睛一看只见几个大字。。。

果然VIP不得人心

之前的数码管在显示的时候,高位的数码管一直是显示0的这个会过于刺眼。因此要通过程序实现高位不显o。

# include<reg52.h>

sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
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        //数码管0-F的真值数组
  };
unsigned char LedBuff[] = {
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0XFF             // 6个数码管的顺序数组初始化
  };
unsigned char i = 0;        //定义switch()的值初始化
unsigned int cnt = 0;       //定义寄存器溢出次数标记
unsigned char flag1s = 0;	 //定义1秒标志
unsigned long	sec = 0;    //记录经过的秒数
	void main()
	{
	
	
		EA = 1;               //IE中断使能寄存器 使能位 置1 打开中断
		ENLED = 0;            //三八译码器使能
    ADDR3 = 1;            //三八译码器使能
    TMOD  = 0x01;        // 模式寄存器赋值使工作在模式1
    TH0 = 0xFC;          //设置寄存器一次溢出时间高8位的初值
    TL0 = 0x67;          //设置寄存器一次溢出时间低8位的初值 设置是1ms
		ET0 = 1;             //启动定时器0中断
    TR0 = 1;             //启动T0
       while(1)
       {
			      if(flag1s == 1) 
            {
						   flag1s = 0;
							 sec++;
							
									 LedBuff[0] = LedChar[sec%10];          //取当前秒数(十进制)的个位数的值给数码管0
									 LedBuff[1] = LedChar[sec/10%10];       //取当前秒数(十进制)的十位数的值给数码管1
									 LedBuff[2] = LedChar[sec/100%10];      //取当前秒数(十进制)的百位数的值给数码管2
									 LedBuff[3] = LedChar[sec/1000%10];     //取当前秒数(十进制)的千位数的值给数码管3
									 LedBuff[4] = LedChar[sec/10000%10];    //取当前秒数(十进制)的万位数的值给数码管4
									 LedBuff[5] = LedChar[sec/100000%10];   //取当前秒数(十进制)的十万位数的值给数码管5
							         //6个减法运算花了6.7ms
						 }	 	
							   
						 
			  }	 
	 }			 
						 
						 
	/*定时器0中断服务函数*/

	  void itrTimer0() interrupt 1  //定时0中断函数标志,进入中断TF0硬件置0
		{
		  TH0 = 0xFC;   //定时器重装初值
			TL0 = 0x67;
			cnt++;
			if(cnt >= 500) //1000次溢出后进入该函数
			{
			  cnt = 0;
				flag1s = 1;  //秒数标志赋值1
			
			}
			   P0 = 0xFF;  //刷新数码管前P0口8位全部置1使LED都不工作。
			
			        switch(i)
							{
							  case 0: 
									   if(sec >= 0)
								       {ADDR2 = 0; ADDR1 = 0; ADDR0 = 0; i = 0; P0 = LedBuff[0];    //数码管1刷新
											  if(sec >= 10)
												  {
												    i  = 1;
												  }
											 } 
								     break;                                                         
								case 1:
                     if(sec >= 10  )									
									     {ADDR2 = 0; ADDR1 = 0; ADDR0 = 1; i = 0; P0 = LedBuff[1];    //数码管2刷新
											  if(sec >= 100)
												  {
												   i = 2;
												  }
											  }
									   break;                                                         
								case 2: 
									   if(sec >= 100)
										   {ADDR2 = 0; ADDR1 = 1; ADDR0 = 0; i = 0; P0 = LedBuff[2];    //数码管3刷新
											  if(sec >= 1000)
												  {
												   i = 3;
												 }
											 
											 
											 }
									   break;                                                         
								case 3: 
									   if(sec >= 1000)
										   {ADDR2 = 0; ADDR1 = 1; ADDR0 = 1; i = 0; P0 = LedBuff[3];  //数码管4刷新
											   if(sec >= 10000)
												  {
												    i = 4;
												  }
											 
											 
											 } 
										  break;                                                        
								case 4: 
									    if(sec >= 10000)
											  {ADDR2 = 1; ADDR1 = 0; ADDR0 = 0; i = 0; P0 = LedBuff[4];   //数码管5刷新
												 if(sec >= 100000)
												   {
												     i = 5;
												   } 
												
												}
											break;                                                       
								case 5:
                      if(sec >= 100000)									
											  {ADDR2 = 1; ADDR1 = 0; ADDR0 = 1; i = 0; P0 = LedBuff[5];   //数码管6刷新
												 
												}
											
											 break;                                                     
								default: break;
							 }
		
		  }
							
						  
							
							
							
			
	
	

 void itrTimer0() interrupt 1  //定时0中断函数标志,进入中断TF0硬件置0
        {
          TH0 = 0xFC;   //定时器重装初值
            TL0 = 0x67;
            cnt++;
            if(cnt >= 1000) //1000次溢出后进入该函数,这里是1000,截图的500是我写错了。
            {
              cnt = 0;
                flag1s = 1;  //秒数标志赋值1
            
            }
               P0 = 0xFF;  //刷新数码管前P0口8位全部置1使LED都不工作。
            
                    switch(i)
                            {
                              case 0: 
                                       if(sec >= 0)
                                       {ADDR2 = 0; ADDR1 = 0; ADDR0 = 0; i = 0; P0 = LedBuff[0];    
                                              if(sec >= 10)
                                                  {
                                                    i  = 1;  //Sec大于等于10的时候执行case语句1
                                                  }
                                             } 
                                     break;                                                         
                                case 1:
                                  if(sec >= 10  )                                    
                                         {ADDR2 = 0; ADDR1 = 0; ADDR0 = 1; i = 0; P0 = LedBuff[1];   
                                              if(sec >= 100)
                                                  {
                                                   i = 2;  //Sec大于等于100的时候执行case语句2
                                                  }
                                              }
                                       break;                                                         
                                case 2: 
                                       if(sec >= 100)
                                          {ADDR2 = 0; ADDR1 = 1; ADDR0 = 0; i = 0; P0 = LedBuff[2];    
                                              if(sec >= 1000)
                                                  {
                                                   i = 3;//Sec大等于1000的时候执行case语句3
                                                 }
                                             
                                             
                                             }
                                       break;                                                         
                                case 3: 
                                       if(sec >= 1000)
                                         {ADDR2 = 0; ADDR1 = 1; ADDR0 = 1; i = 0; P0 = LedBuff[3];  
                                               if(sec >= 10000)
                                                  {
                                                    i = 4; //Sec大等于10000的时候执行case语句4
                                                  }
                                             
                                             
                                             } 
                                          break;                                                        
                                case 4: 
                                        if(sec >= 10000)
                                           {ADDR2 = 1; ADDR1 = 0; ADDR0 = 0; i = 0; P0 = LedBuff[4];   
                                                 if(sec >= 100000)
                                                   {
                                                     i = 5;//Sec大等于100000的时候执行case语句5
                                                   } 
                                                
                                                }
                                            break;                                                       
                                case 5:
                      if(sec >= 100000)                                    
                            {ADDR2 = 1; ADDR1 = 0; ADDR0 = 1; i = 0; P0 = LedBuff[5];  }  break;    
                                                                                                                                                                                          default: break;
                             }
        
          }
     程序的主要区别是中断函数 部分,我的思路是。既然高位不让显示0那么在没进入高位的时候高位就不刷新,不刷新高位数码管就是默认的0XFF关闭。

这个中断程序的执行流程是这样的,假设sec=99,那么无论之前语句执行到哪里,都可以进入 case0 case1的if条件语句判断 ,无论如何程序都能进入case0了(除非你逻辑设计错误)程序执行完毕后下次进入会执行case1      ,执行cas1语句的判断语句 如果sec到了100,那个下次进入程序的就是case2,如果sec还没到100那么会回到case0执行。  当sec进入100后就能执行case2,当执行完case2因为没满1000程序还会执行到case0也就是这样的一中循环, 看程序逻辑图


 不过这个程序有个瑕疵它在第一次进位的时候会跳动一下。   

看结果: 有瑕疵的数码管计时显示_哔哩哔哩_bilibili                    

这是如何产生的呢?之前那个鬼影以及抖动的原因我们都避免了。

然后来看下宋老师的数码管显示程序

#include <reg52.h>

sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
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, 0xFF
};
unsigned char i = 0;   //动态扫描的索引
unsigned int cnt = 0;  //记录T0中断次数
unsigned char flag1s = 0;  //1秒定时标志

void main()
{
    char j;  //循环变量
    unsigned long sec = 0;  //记录经过的秒数
    unsigned char buf[6];   //中间转换缓冲区

    EA = 1;       //使能总中断
    ENLED = 0;    //使能U3
    ADDR3 = 1;    //因为需要动态改变ADDR0-2的值,所以不需要再初始化了
    TMOD = 0x01;  //设置T0为模式1
    TH0  = 0xFC;  //为T0赋初值0xFC67,定时1ms
    TL0  = 0x67;
    ET0  = 1;     //使能T0中断
    TR0  = 1;     //启动T0
    
    while (1)
    {
        if (flag1s == 1)  //判断1秒定时标志
        {
            flag1s = 0;   //1秒定时标志清零
            sec++;        //秒计数自加1
            //将sec按十进制位从低到高依次提取到buf数组中
            buf[0] = sec%10;
            buf[1] = sec/10%10;
            buf[2] = sec/100%10;
            buf[3] = sec/1000%10;
            buf[4] = sec/10000%10;
            buf[5] = sec/100000%10;
            //从最高为开始,遇到0不显示,遇到非0退出循环
            for (j=5; j>=1; j--)
            {
                if (buf[j] == 0)
                    LedBuff[j] = 0xFF;
                else
                    break;
            }
            //将剩余的有效数字位如实转换
            for ( ; j>=0; j--)  //for()起始未对j操作,j即保持上个循环结束时的值
            {
                LedBuff[j] = LedChar[buf[j]];
            }
        }
    }
}
/* 定时器0中断服务函数 */
void InterruptTimer0() interrupt 1
{
    TH0 = 0xFC;  //重新加载初值
    TL0 = 0x67;
    cnt++;       //中断次数计数值加1
    if (cnt >= 1000)  //中断1000次即1秒
    {
        cnt = 0;      //清零计数值以重新开始下1秒计时
        flag1s = 1;   //设置1秒定时标志为1
    }
    //以下代码完成数码管动态扫描刷新
    P0 = 0xFF;   //显示消隐
    switch (i)
    {
        case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0]; break;
        case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1]; break;
        case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2]; break;
        case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; break;
        case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4]; break;
        case 5: ADDR2=1; ADDR1=0; ADDR0=1; i=0; P0=LedBuff[5]; break;
        default: break;
    }
}

运行结果:宋老师数码管显示_哔哩哔哩_bilibili

可以看到宋老师的程序,数码管工作平稳也没有跳动。两者的区别是我的程序刷新速度是动态,sec越小的时候刷新的越快,(一开始只执行case0,每进入一次中断就刷新一下case0。只有sec大于等于10的时候,这时进入两次中断才会刷新一次case0,依此类推sec大于等于100的时候每3次中断才会刷新一次case0)也因此一开始我的数码管会刺眼一些,因为我控制的是刷新位来确定高位是否显示,而宋老师的程序的刷新率是均匀的,他是通过高位赋值0xFF让高位的0不显示来达成效果。

所以这个跳动是什么照成的,而且只发生在第一次进位的时候。

假设时间刚好在9.999s,再过一次中断即1ms那么程序会在进入中断

此时flag1s置1,(这个时候时间上是已经达到10s了)后续中断函数判断因为sec依然不是10只有中断结束进入主函数的时候sec执行sec++完毕的时候sec才赋值为10

我前面提到过除法求余计算很花时间。可知10.001s时主函数已然执行语句sec++完毕sec赋值完成,再次进入中断,这时case0语句会给i赋值i=1,下次进入中断会执行case1语句即10.002s的时候刷新数码管的时候需要第二个数码管的值。(时间必然不是精确的其他语句也有执行时间)我debug一下数码管0和数码管1的取值时间

数码管0前是时间是1.03826s

数码管2前的时间是1.0404937s 则数码管0和1取值的执行时间是1.0409371-1.03826=0.00267即2.67ms. 之前我们当第10.002s的时候需要刷新case1这个时候第二个数码管取值还没完成。但是如果是这个原因造成的那宋老师的程序也是1ms中断为什么不会出问题。

所以可能的推论:刷新率的变化即从只刷新case0跳到需要刷新case1和case0,刷新率降了一半。可能是现象产生的主要推手,目前结论不明朗。因此我直觉性的调整下程序的中断时间,即中断时间变成2ms,那么在原先10.002s执行case1语句变成10.004s执行case1语句。

我们取TH0 = 0xF8 TL0=0xCF  cnt >500.  即中断时间变成2ms 再看一下结果视频

可以看到数码管在第一次进位的时候的时候,没有明显的跳动了。改变中断时间的刷新视频_哔哩哔哩_bilibili

Debug_哔哩哔哩_bilibili

我debug各个程序 程序数码管0和数码管1取值的时间发现下面数据,

1:无中断函数(即本案会产生鬼影和晃动的那个程序)它的时间是(1.00571072,1.00757595)间隔1.86ms

2:引入中断函数(即本案无鬼影无晃动视频的程序)它的时间是(1.01131619,1.01323676)它的时间是1.92ms

3:数码管高位不显示0(即本案数码管显示有瑕疵视频的那个程序 )它的时(1.0382606,1.04049371)2.67ms

4:数码管高位不显示0(即本案改变中断刷新视频的程序)它的时间是(1.01847114,1.02051758)2ms

5数码管高位不显示0(宋老师数码管显示视频的程序)它的时间是(1.01133247,1.01324002)

是1.9ms

从结果上看如果数码管在取值过程中不产生中断这个程序执行大概是1.9ms左右,之前那个“数码管显示有瑕疵”的视频中克可以看到第一次进位的时候会跳动一下。而它的取值时间也是最长的2.67ms相比起它的程序大概多了2.67-1.9=0.77ms,也就是说该句在取值的过程必然发生了中断打断了它的取值过程。后续我们的改进方案是,把中断时间从1ms变成2ms。而后它的取值时间仅是2ms,

问题是初步解决了但是产生原因还需要继续探索。

笔者在后续的学习中,了解了相关知识,实践后证实是我之前猜想的原因,确实是取值部分,造成该现象。我更改了程序,在取值前关闭中断,取值结束打开中断,测试了一下,确实消除了进位跳动的现象且无需改动中断时间。(这相当于取值变化的时候数码管的显示不动,取值结束后再显示取值后的值,原先应是第10s还是显示的值是9。  10.0067s的时候才取值结束显示10,这中间约6.7ms,进入了中断6次显示了数字按次序应该是,9 ,0 ,10这三个数字不过这个速度很快这最终看着只显示了9 和10)

 while(1)
       {
			      if(flag1s == 1) 
            {
						   flag1s = 0;
							 sec++;
							 EA = 0;//关闭中断
							
									 LedBuff[0] = LedChar[sec%10];          //取当前秒数(十进制)的个位数的值给数码管0
									 LedBuff[1] = LedChar[sec/10%10];       //取当前秒数(十进制)的十位数的值给数码管1
									 LedBuff[2] = LedChar[sec/100%10];      //取当前秒数(十进制)的百位数的值给数码管2
									 LedBuff[3] = LedChar[sec/1000%10];     //取当前秒数(十进制)的千位数的值给数码管3
									 LedBuff[4] = LedChar[sec/10000%10];    //取当前秒数(十进制)的万位数的值给数码管4
									 LedBuff[5] = LedChar[sec/100000%10];   //取当前秒数(十进制)的十万位数的值给数码管5
							 EA = 1;//打开中断
							         //6个减法运算花了6.7ms
						 }	 	
							   
						 
			  }	 

在取值的过程中,我先关断了中断,取值结束后我再打开中断。中断时间依然是1ms而不再需要2ms,数码管在进位的时候确实不在抖动了。但是现在程序也不在是1s数码管 加1了,是1006.7ms加了一位,因此如果要更加精确的1s数码管加1,中断函数需要改动一下。


	  void itrTimer0() interrupt 1  //定时0中断函数 标志,进入中断TF0硬件置0
		{
		  TH0 = 0xFC;   //定时器重装初值
			TL0 = 0x67;
			cnt++;
			if(cnt >= 993) //993ms加6.7ms约等于1s
			{
			  cnt = 0;
				flag1s = 1;  //秒数标志赋值1
			
			}

原先1000的改成993。事实即使是改成993经过测试它每进一位的时间依然超过了1s,因此993还要取小一点的数。当然你可能会问宋老师的程序为什么不需要改动,笔者觉得是笔者的程序的问题,该程序的刷新率在进位的时候会变成原先的一半,这个应该就是它抖动的最主要原因。   

这个进位跳动用另一种方式解决了,但是产生的具体原因还要进一步探讨,现在的问题是为什么中断用2ms就能解决跳动的问题,如果是刷新率跳动造成的进位跳动 。原先的解决问题的方法变成了问题的来源。                                                                                                                                      

  • 21
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值