51单片机入门

您所在的位置:网站首页 时钟单片机原理图 51单片机入门

51单片机入门

2024-07-12 16:55:35| 来源: 网络整理| 查看: 265

文章目录 设计要求原理图按键部分介绍 代码解析

设计要求 实现正确稳定地显示小时(两位数)、分钟(两位数)、秒钟(两位数),同时数码管应无闪烁问题。通过按键分别实现时、分信息的调整,方便用户对时间的校准。加入闹铃功能在(本设计中用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_ #include typedef unsigned char uchar ; typedef unsigned int uint ; typedef unsigned 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 语句要更为简洁吧,而效果完全一样。



【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭