标题: 关于单片机定时器中断解决数码管抖动的问题 [打印本页]

作者: 1997肖    时间: 2020-1-8 16:07
标题: 关于单片机定时器中断解决数码管抖动的问题
我有个疑问:当cnt=1000时,执行循环中的语句,当中断触发时进入中断,执行动态刷新数码管程序,但是万一跳出去时,LedBuff[5]这个数组没有计算更新完的话,那么进入中断时位选数码管但是赋给P0的值没有更新到最新的值岂不是会有数码管秒数显示错误?除非一个理由可以解释,就是程序执行那一段32位整型数的除法运算的运行时间小于1ms,但是我调试了代码发现这段32位整型数的除法运算程序运行时长长达3ms,希望大佬指点一下
  1. #include <reg52.h>

  2. sbit ADDR0 = P1^0;
  3. sbit ADDR1 = P1^1;
  4. sbit ADDR2 = P1^2;
  5. sbit ADDR3 = P1^3;
  6. sbit ENLED = P1^4;

  7. unsigned char code LedChar[] =
  8. {  //数码管显示字符转换表     
  9.                 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,     
  10.     0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
  11. };
  12. unsigned char LedBuff[6] =
  13. {  //数码管显示缓冲区,初值 0xFF 确保启动时都不亮     
  14.                 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
  15. };

  16. void main()
  17. {     
  18.         unsigned char i = 0;    //动态扫描的索引     
  19.         unsigned int  cnt = 0;  //记录 T0 中断次数     
  20.         unsigned long sec = 0;  //记录经过的秒数

  21.   ENLED = 0;    //使能 U3,选择控制数码管     
  22.         ADDR3 = 1;    //因为需要动态改变 ADDR0-2 的值,所以不需要再初始化了     
  23.         TMOD = 0x01;  //设置 T0 为模式 1     
  24.         TH0  = 0xFC;  //为 T0 赋初值 0xFC67,定时 1ms     
  25.         TL0  = 0x67;     
  26.         TR0  = 1;     //启动 T0         
  27.         while (1)
  28.         {         
  29.                 if (TF0 == 1)         //判断 T0 是否溢出         
  30.                 {             TF0 = 0;           //T0 溢出后,清零中断标志            
  31.                         TH0 = 0xFC;       //并重新赋初值            
  32.                         TL0 = 0x67;            
  33.                         cnt++;              //计数值自加 1            
  34.                         if (cnt >= 1000)  //判断 T0 溢出是否达到 1000 次            
  35.                         {                                 
  36.                                 sec++;         //秒计数自加 1                 
  37.                                 //以下代码将 sec 按十进制位从低到高依次提取并转为数码管显示字符           //这段运行时长大概3ms
  38.                                 LedBuff[0] = LedChar[sec%10];                 
  39.                                 LedBuff[1] = LedChar[sec/10%10];                 
  40.                                 LedBuff[2] = LedChar[sec/100%10];                 
  41.                                 LedBuff[3] = LedChar[sec/1000%10];                 
  42.                                 LedBuff[4] = LedChar[sec/10000%10];                 
  43.                                 LedBuff[5] = LedChar[sec/100000%10];     
  44.         cnt = 0;       //达到 1000 次后计数值清零        
  45.                         }             //以下代码完成数码管动态扫描刷新            
  46.                         switch (i)            
  47.                         {                 
  48.                                         case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0]; break;                 
  49.                                         case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1]; break;                 
  50.                                         case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2]; break;                 
  51.                                         case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; break;                 
  52.                                         case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4]; break;                 
  53.                 case 5: ADDR2=1; ADDR1=0; ADDR0=1; i=0; P0=LedBuff[5]; break;                 
  54.                 default: break;            
  55.             }         
  56.           }     
  57.         }
  58. }
复制代码



作者: ahshmj    时间: 2020-1-8 17:05
每秒大于25次扫描就不会感觉到频闪。也就是40ms一次。这个时间够长的了。如果有长延时循环,就要用定时器,不可以让单片机“当”在那儿。
作者: wulin    时间: 2020-1-8 21:36
你这不是中断,是查询中断请求标志。如果执行if内语句超过1ms会造成计时不准,超时过多还会引起数码管闪烁。数码管动态显示一轮时间不得超过20ms,6个数码管每个显示时间平均3.33ms。建议不要使用大数据运算,6位十进制数改成3段2位十进制,控制在一个字节范围可以明显缩短运算时间。使主循环查询周期小于1ms。
作者: 1997肖    时间: 2020-1-11 15:20
额,不好意思,复制错代码了,我贴一下中断的代码,这段代码说是解决了之前第一段代码的抖动问题,但是我觉得利用中断也就是下面这段代码会有我上面说的疑问:当cnt=1000时也就是到了1s时,执行循环中的if语句,当中断触发时进入中断也就是1s+1ms时,执行switch动态刷新数码管程序,但是跳出去时,LedBuff[5]这个数组没有计算完,我调试了代码发现这段32位整型数的除法运算程序运行时长长达6.8ms,也就是当到1s时sec++,然后开始计算那6行32位运算,需要6.8ms,但是6.8ms还没到还没算完时在1s+1ms时触发中断,进入switch给P0赋值,那么进入中断时P0的值没有更新到那6行32位运算完的值岂不是会有数码管秒数显示错误?但是,我把代码下载到单片机里面,却没有显示错误,显示是正确的,只不过我发现单片机的1s要更快,大概单片机上过了60s时,电脑上过了61s.所以疑问:按理来说第二段代码解决了抖动但应该会有数码管显示错误,但是下载到单片机上显示没有任何问题,只是时间上有点不准,不知道为什么显示会没有问题,应该是第7个ms那6行运算全部计算完之后P0的赋值才应该是sec++后的正确秒数啊?难道原因是7个ms也就是刷新完一轮数码管的时间太短暂了,把错误的秒数显示出来肉眼看不到吗,麻烦各位大佬再帮我看一下,小弟初学乍道,劳烦各位了
  1. #include <reg52.h>

  2. //数码管显示字符转换表
  3. unsigned char code LedChar[10] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};  //0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71
  4. unsigned char LedBuff[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; //数码管显示缓冲区,初值 0xFF 确保启动时都不亮
  5. unsigned char code wei[6]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf}; //数码管各位的码表
  6. unsigned char i = 0;    //动态扫描的索引     
  7. unsigned int  cnt = 0;  //记录 T0 中断次数
  8. //sbit dula=P2^6;                //段选信号的锁存器控制
  9. sbit wela=P2^7;                //位选信号的锁存器控制

  10. void main()
  11. {         
  12.         unsigned long sec = 0;  //记录经过的秒数
  13.         EA = 1;        //使能总中断
  14.         ET0  = 1;     //使能 T0 中断
  15.         TMOD = 0x01;  //设置 T0 为模式 1     
  16.         TH0  = 0xFC;  //为 T0 赋初值 0xFC67,定时 1ms     
  17.         TL0  = 0x67;     
  18.         TR0  = 1;     //启动 T0         
  19.         while (1)
  20.         {                    
  21.                 if (cnt == 1000)  //判断 T0 溢出是否达到 1000 次            
  22.                 {                                 
  23.                         sec++;         //秒计数自加 1                 
  24.                         //以下代码将 sec 按十进制位从低到高依次提取并转为数码管显示字符                 
  25.                         LedBuff[0] = LedChar[sec%10];                 
  26.                         LedBuff[1] = LedChar[sec/10%10];                 
  27.                         LedBuff[2] = LedChar[sec/100%10];                 
  28.                         LedBuff[3] = LedChar[sec/1000%10];                 
  29.                         LedBuff[4] = LedChar[sec/10000%10];                 
  30.                         LedBuff[5] = LedChar[sec/100000%10];     
  31.                         cnt = 0;       //达到 1000 次后计数值清零        
  32.                 }             //以下代码完成数码管动态扫描刷新
  33.         }
  34. }
  35. /* 定时器 0 中断服务函数 */
  36. void InterruptTimer0() interrupt 1
  37. {     
  38.         TH0 = 0xFC;  //重新加载初值     
  39.         TL0 = 0x67;     
  40.         cnt++;        //中断次数计数值加 1     
  41.         //以下代码完成数码管动态扫描刷新     
  42.         P0 = 0x00;   //显示消隐     
  43.         switch (i)            
  44.         {                 
  45.                 case 0: wela=0;P0=wei[5];wela=1;wela=0;i++;P0=LedBuff[0]; break;                 
  46.                 case 1: wela=0;P0=wei[4];wela=1;wela=0;i++;P0=LedBuff[1]; break;                 
  47.                 case 2: wela=0;P0=wei[3];wela=1;wela=0;i++;P0=LedBuff[2]; break;                 
  48.                 case 3: wela=0;P0=wei[2];wela=1;wela=0;i++;P0=LedBuff[3]; break;                 
  49.                 case 4: wela=0;P0=wei[1];wela=1;wela=0;i++;P0=LedBuff[4]; break;                 
  50.                 case 5: wela=0;P0=wei[0];wela=1;wela=0;i=0;P0=LedBuff[5]; break;                 
  51.                 default: break;            
  52.         }                                           
  53. }
复制代码

作者: 1997肖    时间: 2020-1-11 15:21
ahshmj 发表于 2020-1-8 17:05
每秒大于25次扫描就不会感觉到频闪。也就是40ms一次。这个时间够长的了。如果有长延时循环,就要用定时器, ...

大佬,我这段代码贴错了,后面进行了补充,劳烦再帮我看看指点一下小弟
作者: 1997肖    时间: 2020-1-11 15:21
wulin 发表于 2020-1-8 21:36
你这不是中断,是查询中断请求标志。如果执行if内语句超过1ms会造成计时不准,超时过多还会引起数码管闪烁 ...

大佬,我这段代码贴错了,后面进行了补充,劳烦再帮我看看指点一下小弟
作者: wulin    时间: 2020-1-11 22:16
1997肖 发表于 2020-1-11 15:21
大佬,我这段代码贴错了,后面进行了补充,劳烦再帮我看看指点一下小弟

1.你想象的没错,中断会打断主程序的运行。缓存数组每秒更新一次,显示函数1ms刷新1位,6ms刷新1轮。如果恰巧中断发生时,缓存数组更新到一半,数码管显示可能出错,这种状态是完全可能发生,但这个过程非常短暂,肉眼根本察觉不到。
2.《if(cnt == 1000)  //判断 T0 溢出是否达到 1000 次》这句有缺陷。如果完成if内语句需要6.8ms,那么假设某一次判断时cnt = 999,等到下一次判断时cnt已经是1005,这样就会丢秒,一直加到溢出。所以计时会慢许多。正确写法是:if(cnt >= 1000),或者放在中断里判断清0cnt并输出一个标志。
作者: 1997肖    时间: 2020-1-12 14:12
wulin 发表于 2020-1-11 22:16
1.你想象的没错,中断会打断主程序的运行。缓存数组每秒更新一次,显示函数1ms刷新1位,6ms刷新1轮。如果 ...

谢谢大佬,原来的问题我现在清楚了,只是因为肉眼察觉不到。但是您指出的cnt=999时,为什么等到下一次判断时cnt为1005了呢,这里我不太明白,cnt=999时,if语句里面的程序没有执行啊,所以那6.8ms在cnt=999时没有执行,所以下一次判断cnt还是为1000吧?而且我觉得尽管这里有6.8ms,但是每隔1ms进入中断cnt++,所以我觉得这6.8ms影响不到这个1000次1ms的计时,也就是说1s的时间按我的想法也应该是准确的吧?只是前7ms每隔1ms进入一次中断之后继续执行6.8ms的代码然后再进入中断再执行6.8ms的代码直到这段代码运算结束,前7ms同时完成了6.8ms的程序运算也完成了7次中断扫描数码管。这是我的分析和想法,有什么错误或者考虑不全的地方还希望前辈不吝赐教,谢谢




欢迎光临 (http://www.51hei.com/bbs/) Powered by Discuz! X3.1