文章目录

  • 设计要求
  • 原理图
    • 按键部分介绍
  • 代码解析

设计要求

  1. 实现正确稳定地显示小时(两位数)、分钟(两位数)、秒钟(两位数),同时数码管应无闪烁问题。
  2. 通过按键分别实现时、分信息的调整,方便用户对时间的校准。
  3. 加入闹铃功能在(本设计中用LED代替)。

原理图

按键部分介绍


key1用于切换时分秒的加减。例:第一次按下后,按key2\key3时“秒”加\减,第二次按下后,按key2\key3时“分”加\减,第三次按下后,按key2\key3时“时”加\减,第四次按下后,按key2\key3不起作用,依次循环。

key2用于加。

key3用于减。

key4用于切换时间显示和闹钟显示切换。

代码解析

mian.c

#include #include "SMG.H"sbit KEY1 = P3^0;sbit KEY2 = P3^1;sbit KEY3 = P3^2;sbit KEY4 = P3^3;sbit LED = P3^4;uchar T0RH = 0 ; // T0 重载值的高字节uchar T0RL = 0 ; // T0 重载值的低字节uchar sec =10, min = 10, hour = 10; // 初始化时分秒uchar alarmSec, alarmMin, alarmHour; // 闹铃时间bit alarm = 0; // 闹铃标志位bit key1,key2,key3,key4; // 按键标志位uchar b = 0; // 切换标志位void ConFigTimer0(uchar ms);void LedDriver();void KeyDriver();void main(){EA = 1;ConFigTimer0(2);while (1){LedDriver();KeyDriver();(alarmMin == min && alarmHour == hour) " />(LED = 1) : (LED = 0);}}void LedDriver(){static unsigned char i;if (!alarm){switch (i){case 0 : ledBuff[0] = ledChar[sec % 10]; break;case 1 : ledBuff[1] = ledChar[sec / 10]; break;case 2 : ledBuff[2] = ledChar[10]; break;case 3 : ledBuff[3] = ledChar[min % 10]; break;case 4 : ledBuff[4] = ledChar[min / 10]; break;case 5 : ledBuff[5] = ledChar[10]; break;case 6 : ledBuff[6] = ledChar[hour % 10]; break;case 7 : ledBuff[7] = ledChar[hour / 10]; break;}}else{switch (i){case 0 : ledBuff[0] = ledChar[alarmSec % 10]; break;case 1 : ledBuff[1] = ledChar[alarmSec / 10]; break;case 2 : ledBuff[2] = ledChar[10]; break;case 3 : ledBuff[3] = ledChar[alarmMin % 10]; break;case 4 : ledBuff[4] = ledChar[alarmMin / 10]; break;case 5 : ledBuff[5] = ledChar[10]; break;case 6 : ledBuff[6] = ledChar[alarmHour % 10]; break;case 7 : ledBuff[7] = ledChar[alarmHour / 10]; break;}}i ++;i &= 0x07;}void KeyDriver(){if (KEY1 == 0 && key1 == 0){key1 = 1;}else if (KEY1 == 1 && key1 == 1){key1 = 0;b ++;b &= 0x03;}if (KEY2 == 0 && key2 == 0){key2 = 1;}else if (KEY2 == 1 && key2 == 1 ){key2 = 0;if(!alarm){switch (b){case 1 : sec ++; break;case 2 : min ++; break;case 3 : hour ++; break;}}else{switch (b){case 1 : alarmSec ++; break;case 2 : alarmMin ++; break;case 3 : alarmHour ++; break;}}}if (KEY3 == 0 && key3 == 0){key3 = 1;}else if (KEY3 == 1 && key3 == 1 ){key3 = 0;if(!alarm){switch (b){case 1 : sec --; break;case 2 : min --; break;case 3 : hour --; break;}}else{switch (b){case 1 : alarmSec --; break;case 2 : alarmMin --; break;case 3 : alarmHour --; break;}}}if (KEY4 == 0 && key4 == 0){key4 = 1;}else if (KEY4 == 1 && key4 == 1){key4 = 0;alarm = ~alarm;}}/* 配置并启动T0 ,12MHz */void ConFigTimer0(uchar ms){ulong tmp ;tmp = 12000000 / 12;tmp = (tmp * ms) / 1000;tmp = 65536 - tmp;tmp += 2;T0RH = (uchar)(tmp >> 8);T0RL = (uchar)tmp;TMOD &= 0xF0;TMOD |= 0x01;TH0 = T0RH;TL0 = T0RL;ET0 = 1;TR0 = 1;}void InterruptTimer0() interrupt 1 {uint i;TH0 = T0RH;TL0 = T0RL;LedScan();i ++;if (i >= 500){i = 0;sec ++;if (sec >= 60){sec = 0;min ++;if (min >= 60){min = 0;hour ++;if (hour >= 24)hour = 0;}}}} 

SMG.H

#ifndef _SMG_H_#define _SMG_H_#includetypedef unsigned char uchar ;typedef unsigned int uint ;typedefunsigned long ulong ;extern uchar ledChar[12], ledBuff[8];extern void LedScan();#endif

SMG.C

#include"SMG.H"uchar ledChar[12] = { //共阴极数码管显示字符转换表0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x40, 0x00};uchar ledBuff[8] = { //数码管显示缓冲区 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};/* 数码管动态扫描刷新函数,需在定时中断中调用 */void LedScan(){static uchar i = 0; //动态扫描的索引 想·P0 = ledChar[11]; //显示消隐P2 = ~(0x80 >> i);P0 = ledBuff[i];i ++;i &= 0x07;}

在学习数码管动态扫描的时候,为了方便大家理解,我们程序写的细致一些,给大家引入了 switch 的用法,随着编程能力与领悟能力的增强,我们在编程上也可以改进一下逻辑算法,让程序变的更简洁。这种逻辑算法,通常不是靠学一下可以全部掌握的,而是通过不断的编写程序以及研究他人程序的过程中一点点积累起来的。

前边动态扫描刷新函数我们是这么写的:

P0 = 0x00;switch (i){case 0 : P2 = 0xfe; P0 = ledTemp[0]; break;case 1 : P2 = 0xfd; P0 = ledTemp[1]; break;case 2 : P2 = 0xfb; P0 = ledTemp[2]; break;case 3 : P2 = 0xf7; P0 = ledTemp[3]; break;case 4 : P2 = 0xef; P0 = ledTemp[4]; break;case 5 : P2 = 0xdf; P0 = ledTemp[5]; break;case 6 : P2 = 0xbf; P0 = ledTemp[6]; break;case 7 : P2 = 0x7f; P0 = ledTemp[7]; break;}i ++;i &= 0x07;

我们来分析每一个 case 分支,它们的结构是相同的,即改变 P2、改变索引 i、取数据写入 P0,只要把 case 后的常量与 P2和 LedBuff 的下标对比,就可以发现它们其实是相等的,那么我们可以直接把常量值(实际上就是 i 在改变前的值)赋值给它们即可,而不必写上 6 遍。还剩下一个 i 的操作,它进行了 7 次相同的++与一次归 0 操作,那么很明显用++和 if 判断就可以替代这些操作。下面就是我们据此改进后的代码:

P0 = 0x00; //显示消隐P2 = ~(0x80 >> i);P0 = ledBuff[i];i ++;i &= 0x07;

大家看一下,P2 = ~(0x80 >> i);这行代码就利用了上面讲到的~和>>,利用右移运算符来实现1的右移,因为我们使用的是共阴数码管,所以有一个取反来实现0的右移,而 P0 的赋值也只需要一行代码,i 的处理也很简单。这样写成的代码是不是要简洁的多,也巧妙的多,而功能与前面的 switch 是一样的,同样可以完美实现动态显示刷新的功能。

而且一般的if在该代码中我们也换成了&=,例如i &= 0x07;b &= 0x03;在这里我是要让b 在 0~3 之间变化,加到 4 就自动归零,i在0~7之间变化,加到8就自动清零按照常规你可以用前面讲过的 if 语句轻松实现,但是你现在看一下这样程序是不是同样可以做到这一点呢?因为 0、1、2、3 这四个数值正好占用 2 个二进制的位(0~7刚好占3个),所以我们把一个字节的高 6 (5)位一直清零的话,这个字节的值自然就是一种到 4 (8)归零的效果了。看一下,这样一句代码比 if 语句要更为简洁吧,而效果完全一样。