项目说明
用普中51和keil uVision56实现智能手表,功能包括:显示秒表、时间、日期、星期、设置闹钟,读取温度,指示灯亮等。综合的实验主要有独立按键、指示灯亮、温度传感器、定时器、动态数码管显示、蜂鸣器实验,其中本篇博文重要讲解定时器和温度传感器实验,其他结合的实验比较简单,自行查阅资料即可明白。
硬件设计
温度传感器
项目我用的是精度较高的外部 DS18B20 数字温度传感器,由于此传感器是单总线接口,所以需要使用 51 单片机的一个 IO 口模拟单总线时序与 DS18B20 通信,将检测的环境温度读取出来。
DS18B20 外观实物如下图所示:
DS18B20 数字温度传感器内部的一些高速暂存存储器自行查阅资料,这里就跳过了,直接理解它是怎么计算温度值的吧。
计算温度
如果测得的温度大于 0,前5 位为‘ 0’,只要将测到的数值乘以 0.0625(默认精度是 12 位)即可得到实际温度;如果温度小于 0,这 5 位为‘ 1’,测到的数值需要取反加 1 再乘以 0.0625 即可得到实际温度。比如我们要计算+85 度,数据输出十六进制是 0X0550,因为高字节的高 5位为 0,表明检测的温度是正温度,0X0550 对应的十进制为 1360,将这个值乘以 12 位精度 0.0625,所以可以得到+85 度。
读取温度
知道了怎么计算温度,接下来我们就来看看如何读取温度数据,由于DS18B20 是单总线器件,所有的单总线器件都要求采用严格的信号时序,以保证 数据的完整性。DS18B20 时序包括如下几种:初始化时序、写(0 和 1)时序、读(0 和 1)时序。 DS18B20 发送所有的命令和数据都是字节的低位在前。 这里主要有这几个信号的时序:(1) 初始化时序、(2)写时序、(3)读时序。时序的详细介绍自行查阅,其实根据字面意思大致也可以猜到是什么意思的。 DS18B20 的典型温度读取过程为:复位→发 SKIP ROM 命令(0XCC)→发开始转换命令(0X44)→延时→复位→发送 SKIP ROM 命令(0XCC)→发读存储器命令(0XBE)→连续读出两个字节数据(即温度)→结束。
定时器中断
学习定时器前需要明白: ①51 单片机有两组定时器/计数器,因为既可以定时,又可以计数,故称之 为定时器/计数器。 ②定时器/计数器和单片机的 CPU 是相互独立的。定时器/计数器工作的过程是自动完成的,不需要 CPU 的参与。 ③51 单片机中的定时器/计数器是根据机器内部的时钟或者是外部的脉冲信 号对寄存器中的数据加 1。 有了定时器/计数器之后,可以增加单片机的效率,一些简单的重复加 1 的 工作可以交给定时器/计数器处理。CPU 转而处理一些复杂的事情,同时可以实现精确定时作用。
STC89C5X 单片机内有两个可编程的定时/计数器 T0、T1 和一个特殊功能定时器 T2。
工作方式
这里着重讲解定时器常用的工作方式:
计数位数是 16 位,由 TL0 作为低 8 位,TH0 作为高 8 位,组成了16 位加 1 计数器。
计数初值与计数个数的关系为:X=2(16)-N。
定时器配置
这里以定时器 0 为例介绍配置定时器工作方式 1、设定 1ms 初值,开启定时器计数功能以及总中断,如下:
void Timer0Init()
{
TMOD|=0X01;//选择为定时器 0 模式,工作方式 1,仅用 TR0 打开启动
TH0=0XFC; //给定时器赋初值,定时 1ms
TL0=0X18;
ET0=1;//打开定时器 0 中断允许
EA=1;//打开总中断
TR0=1;//打开定时器
}
定时器赋初值百度上会教你怎么赋值的,对于定时器 1 的使用方法是一样的,只是将上述的 0 变为 1 即可。
硬件实物连接
上面就是用到的GPIO口,LSA、LSB、LSC指的就是74HC138模块,k1到k8就是独立按键模块,led到led4指的是LED交通灯模块,beep指蜂鸣器模块,DS18B20我连接的是P3^7接口,动态数码管我连接的是P0口(在普中51单片机上是J22连接到J6,就是上图中的8排杜邦线)。
软件设计
上述DS18B20温度传感器的代码实现如下
temp.h
#ifndef _TEMP_H_
#define _TEMP_H_
#include <reg52.h>
//---重定义关键词---//
#ifndef uchar
#define uchar unsigned char
#endif
#ifndef uint
#define uint unsigned int
#endif
//--定义使用的 IO 口--//
sbit DSPORT=P3^7;
//--声明全局函数--//
void Delay1ms(uint );
uchar Ds18b20Init();
void Ds18b20WriteByte(uchar com);
uchar Ds18b20ReadByte();
void Ds18b20ChangTemp();
void Ds18b20ReadTempCom();
int Ds18b20ReadTemp();
#endif
temp.c
#include "temp.h"
void Delay1ms(uint y) //延时函数
{
uint x;
for(;y>0;y--)
{
for(x=110;x>0;x--);
}
}
uchar Ds18b20Init() //初始化函数
{
uchar i;
DSPORT = 0; //将总线拉低 480us~960us
i = 70;
while(i--);//延时 642us
DSPORT = 1; //然后拉高总线,如果 DS18B20 做出反应会将在 15us~60us 后总线拉低
i = 0;
while(DSPORT) //等待 DS18B20 拉低总线
{
Delay1ms(1);
i++;
if(i>5)//等待>5ms
{
return 0;//初始化失败
}
}
return 1;//初始化成功
}
void Ds18b20WriteByte(uchar dat) //向 18B20 写入一个字节
{
uint i, j;
for(j=0; j<8; j++)
{
DSPORT = 0; //每写入一位数据之前先把总线拉低 1us
i++;
DSPORT = dat & 0x01; //然后写入一个数据,从最低位开始
i=6;
while(i--); //延时 68us,持续时间最少 60us
DSPORT = 1; //然后释放总线,至少 1us 给总线恢复时间才能接 着写入第二个数值
dat >>= 1;
}
}
uchar Ds18b20ReadByte() //读取一个字节
{
uchar byte, bi;
uint i, j;
for(j=8; j>0; j--)
{
DSPORT = 0;
i++;
DSPORT = 1;
i++;
i++;//延时 6us 等待数据稳定
bi = DSPORT; //读取数据,从最低位开始读取
/*将 byte 左移一位,然后与上右移 7 位后的 bi,注意移动之后移掉 那位补 0*/
byte = (byte >> 1) | (bi << 7);
i = 4;
while(i--);
}
return byte;
}
void Ds18b20ChangTemp() //让 18b20 开始转换温度
{
Ds18b20Init();
Delay1ms(1);
Ds18b20WriteByte(0xcc); //跳过 ROM 操作命令
Ds18b20WriteByte(0x44); //温度转换命令
//Delay1ms(100);
//等待转换成功,而如果你是一直刷着的话,就不用这个延时了
}
void Ds18b20ReadTempCom() //发送读取温度命令
{
Ds18b20Init();
Delay1ms(1);
Ds18b20WriteByte(0xcc); //跳过 ROM 操作命令
Ds18b20WriteByte(0xbe); //发送读取温度命令
}
int Ds18b20ReadTemp() //读取温度
{
int temp = 0;
uchar tmh, tml;
Ds18b20ChangTemp(); //先写入转换命令
Ds18b20ReadTempCom(); //然后等待转换完后发送读取温度命令
tml = Ds18b20ReadByte(); //读取温度值共 16 位,先读低字节
tmh = Ds18b20ReadByte(); //再读高字节
temp = tmh;
temp <<= 8;
temp |= tml;
return temp;
}
最后在主函数一并实现定时器中断实验:
main.c
#include "reg52.h" //此文件中定义了单片机的一些特殊功能寄存器
#include "temp.h"
typedef unsigned int u16; //对数据类型进行声明定义
typedef unsigned char u8;
//--定义使用的 IO 口--//
sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;
sbit k1=P3^1;
sbit k2=P3^2;
sbit k3=P3^3;
sbit k4=P3^4;
sbit k5=P3^5;
sbit k6=P3^6;
sbit k7=P3^0;
sbit led=P2^0;
sbit led1=P2^1;
sbit led2=P2^5;
sbit led3=P2^6;
sbit beep=P2^7;
sbit k8=P1^0;
sbit led4=P1^1;
u8 code smgduan[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};//显示0~F的值
u16 hour=15,minute=32,second=30;//自己设置初始时间,全局变量
u16 year=2020,month=6,day=30,week=2;//自己设置初始日期、星期,全局变量
u8 ssec,sec,min; //毫秒,秒,分
//不同功能的数码管显示//
u8 DisplayData[8];
u8 DisplayData1[8];
u8 DisplayData2[8];
u8 DisplayData3[8];
u8 DisplayData4[8];
void delay(u16 i)
{
while(i--);
}
void keypros() //按键处理函数
{
if(k1==0) //检测按键 K1 是否按下
{
delay(1000); //消除抖动 一般大约 10ms
if(k1==0) //再次判断按键是否按下
{
//--只留当前指示灯亮,其他指示灯灭--//
led1=1;
led=1;
led3=1;
led4=1;
led=~led; //led 状态取反
}
while(!k1); //检测按键是否松开
}
//--其它按键注释一样,下面就不注释了--//
if(k2==0)
{
delay(1000);
if(k2==0)
{
led=1;
led2=1;
led3=1;
led4=1;
led1=~led1;
}
while(!k2);
}
if(k3==0)
{
delay(1000);
if(k3==0)
{
led=1;
led1=1;
led3=1;
led4=1;
led2=~led2;
}
while(!k3);
}
if(k4==0)
{
delay(1000);
if(k4==0)
{
led=1;
led1=1;
led2=1;
led4=1;
led3=~led3;
}
while(!k4);
}
if(k8==0)
{
delay(1000);
if(k8==0)
{
led=1;
led1=1;
led2=1;
led3=1;
led4=~led4;
}
while(!k8);
}
}
void datapros1(int temp) //温度传感器数码管数字设置
{
float tp;
if(temp< 0) //当温度值为负数
{
DisplayData[0] = 0x40; //因为读取的温度是实际温度的补码,所以减 1,再取反求出原码
temp=temp-1;
temp=~temp;
tp=temp;
temp=tp*0.0625*100+0.5;
}
else
{
DisplayData[0] = 0x00;
tp=temp;//因为数据处理有小数点所以将温度赋给一个浮点型变量
//如果温度是正的那么,那么正数的原码就是补码它本身
temp=tp*0.0625*100+0.5;
//留两个小数点就*100,+0.5 是四舍五入,因为 C 语言浮点数转换为整型的时候把小数点 //后面的数自动去掉,不管是否大于 0.5,而+0.5 之后大于 0.5 的 就是进 1 了,小于 0.5 的就 //算加上 0.5,还是在小数点后面。
}
DisplayData1[1] = smgduan[temp / 10000];
DisplayData1[2] = smgduan[temp % 10000 / 1000];
DisplayData1[3] = smgduan[temp % 1000 / 100] | 0x80;
DisplayData1[4] = smgduan[temp % 100 / 10];
DisplayData1[5] = smgduan[temp % 10];
}
void Timer0Init()//定时器0初始化
{
TMOD|=0X01;//选择为定时器0模式,工作方式1,仅用TR0打开启动。
TH0=0Xd8; //给定时器赋初值,定时10ms
TL0=0Xf0;
ET0=1;//打开定时器0中断允许
EA=1;//打开总中断
TR0=1;//打开定时器
}
void DigDisplay1() //温度传感器数码管数字显示
{
u8 i;
for(i=0;i<6;i++)
{
switch(i) //位选,选择点亮的数码管,
{
case(0): LSA=0;LSB=0;LSC=0; break;//显示第 0 位
case(1): LSA=1;LSB=0;LSC=0; break;//显示第 1 位
case(2): LSA=0;LSB=1;LSC=0; break;//显示第 2 位
case(3): LSA=1;LSB=1;LSC=0; break;//显示第 3 位
case(4): LSA=0;LSB=0;LSC=1; break;//显示第 4 位
case(5): LSA=1;LSB=0;LSC=1; break;//显示第 5 位
}
P0=DisplayData1[i];//发送数据
delay(100); //间隔一段时间扫描
P0=0x00;//消隐
}
}
void DigDisplay2()//时间数码管数字显示
{
u8 i;
for(i=0;i<8;i++)
{
switch(i) //位选,选择点亮的数码管,
{
case(0):
LSA=0;LSB=0;LSC=0; break;//显示第0位
case(1):
LSA=1;LSB=0;LSC=0; break;//显示第1位
case(2):
LSA=0;LSB=1;LSC=0; break;//显示第2位
case(3):
LSA=1;LSB=1;LSC=0; break;//显示第3位
case(4):
LSA=0;LSB=0;LSC=1; break;//显示第4位
case(5):
LSA=1;LSB=0;LSC=1; break;//显示第5位
case(6):
LSA=0;LSB=1;LSC=1; break;//显示第6位
case(7):
LSA=1;LSB=1;LSC=1; break;//显示第7位
}
P0=DisplayData2[i];//发送段码
delay(100); //间隔一段时间扫描
P0=0x00;//消隐
}
}
void datapros2()//时间数码管数字设置
{
DisplayData2[0]=smgduan[hour/10];
DisplayData2[1]=smgduan[hour%10];
DisplayData2[2]=0x40;
DisplayData2[3]=smgduan[minute/10];
DisplayData2[4]=smgduan[minute%10];
DisplayData2[5]=0x40;
DisplayData2[6]=smgduan[second/10];
DisplayData2[7]=smgduan[second%10];
}
void datapros4()//星期数码管数字设置
{
DisplayData4[0]=0x00;
DisplayData4[1]=0x00;
DisplayData4[2]=0x00;
DisplayData4[3]=0x00;
DisplayData4[4]=0x00;
DisplayData4[5]=0x00;
DisplayData4[6]=0x00;
DisplayData4[7]=smgduan[week%10];
}
void DigDisplay4()//星期数码管数字显示
{
u8 i;
for(i=0;i<8;i++)
{
switch(i) //位选,选择点亮的数码管,
{
case(0):
LSA=0;LSB=0;LSC=0; break;//显示第0位
case(1):
LSA=1;LSB=0;LSC=0; break;//显示第1位
case(2):
LSA=0;LSB=1;LSC=0; break;//显示第2位
case(3):
LSA=1;LSB=1;LSC=0; break;//显示第3位
case(4):
LSA=0;LSB=0;LSC=1; break;//显示第4位
case(5):
LSA=1;LSB=0;LSC=1; break;//显示第5位
case(6):
LSA=0;LSB=1;LSC=1; break;//显示第6位
case(7):
LSA=1;LSB=1;LSC=1; break;//显示第7位
}
P0=DisplayData4[i];//发送段码
delay(100); //间隔一段时间扫描
P0=0x00;//消隐
}
}
void DigDisplay3()//日期数码管数字显示
{
u8 i;
for(i=0;i<8;i++)
{
switch(i) //位选,选择点亮的数码管,
{
case(0):
LSA=0;LSB=0;LSC=0; break;//显示第0位
case(1):
LSA=1;LSB=0;LSC=0; break;//显示第1位
case(2):
LSA=0;LSB=1;LSC=0; break;//显示第2位
case(3):
LSA=1;LSB=1;LSC=0; break;//显示第3位
case(4):
LSA=0;LSB=0;LSC=1; break;//显示第4位
case(5):
LSA=1;LSB=0;LSC=1; break;//显示第5位
case(6):
LSA=0;LSB=1;LSC=1; break;//显示第6位
case(7):
LSA=1;LSB=1;LSC=1; break;//显示第7位
}
P0=DisplayData3[i];//发送段码
delay(100); //间隔一段时间扫描
P0=0x00;//消隐
}
}
void datapros3()//日期数码管数字设置
{
DisplayData3[0]=smgduan[year/1000];
DisplayData3[1]=smgduan[year/100%10];
DisplayData3[2]=smgduan[year/10%10];
DisplayData3[3]=smgduan[year%10];
DisplayData3[4]=smgduan[month/10];
DisplayData3[5]=smgduan[month%10];
DisplayData3[6]=smgduan[day/10];
DisplayData3[7]=smgduan[day%10];
}
void DigDisplay()//秒表数码管数字显示
{
u8 i;
for(i=0;i<8;i++)
{
switch(i) //位选,选择点亮的数码管,
{
case(0):
LSA=0;LSB=0;LSC=0; break;//显示第0位
case(1):
LSA=1;LSB=0;LSC=0; break;//显示第1位
case(2):
LSA=0;LSB=1;LSC=0; break;//显示第2位
case(3):
LSA=1;LSB=1;LSC=0; break;//显示第3位
case(4):
LSA=0;LSB=0;LSC=1; break;//显示第4位
case(5):
LSA=1;LSB=0;LSC=1; break;//显示第5位
case(6):
LSA=0;LSB=1;LSC=1; break;//显示第6位
case(7):
LSA=1;LSB=1;LSC=1; break;//显示第7位
}
P0=DisplayData[i];//发送段码
delay(100); //间隔一段时间扫描
P0=0x00;//消隐
}
}
void datapros()//秒表数码管数字设置
{
DisplayData[0]=smgduan[min/10];
DisplayData[1]=smgduan[min%10];
DisplayData[2]=0x40;
DisplayData[3]=smgduan[sec/10];
DisplayData[4]=smgduan[sec%10];
DisplayData[5]=0x40;
DisplayData[6]=smgduan[ssec/10];
DisplayData[7]=smgduan[ssec%10];
}
void main()
{
//--初始化指示灯全灭--//
led=1;
led1=1;
led2=1;
led3=1;
led4=1;
Timer0Init(); //定时器0初始化
while(1)
{
keypros();
while(hour==16 && minute==50)//设置闹钟
{
beep=~beep;
delay(100);
if(led2==0)
{
datapros2();
DigDisplay2();
}
}
if(led==0)//秒表功能
{
datapros();
DigDisplay();
}
if(led1==0)//读取温度
{
datapros1(Ds18b20ReadTemp()); //数据处理函数
DigDisplay1();//数码管显示函数
}
if(led2==0)//显示时间
{
datapros2();
DigDisplay2();
}
if(led3==0)//显示日期
{
datapros3();
DigDisplay3();
}
if(led4==0)//显示星期
{
datapros4();
DigDisplay4();
}
}
}
void Timer0() interrupt 1//定时器0中断函数
{
if(k5==0 && led2==0)//小时中断
{
delay(1000);
if(k5==0)
{
hour++;
if(hour==24)
{
hour=0;
week++;
day++;
}
}
while(!k5);
}
if(k6==0 && led2==0)//分钟中断
{
delay(1000);
if(k6==0)
{
minute++;
if(minute==60)
{
minute=0;
hour++;
}
}
while(!k6);
}
if(k7==0 && led2==0)//秒中断
{
delay(1000);
if(k7==0)
{
second++;
if(second==60)
{
second=0;
minute++;
}
}
while(!k7);
}
if(k5==0 && led3==0)//年中断
{
delay(1000);
if(k5==0)
{
year++;
if(year==10000)
{
year=0;
}
}
while(!k5);
}
if(k6==0 && led3==0)//月中断
{
delay(1000);
if(k6==0)
{
month++;
if(month==13)
{
month=1;
year++;
}
}
while(!k6);
}
if(k7==0 && led3==0)//天中断
{
delay(1000);
if(k7==0)
{
day++;
week++;
if(week==8)//星期中断
{
week=1;
}
if(month==1 || month==3 || month==5 || month==7 || month==8 || month==10)//月份对应的天数中断
{
if(day==32)
{
day=1;
month++;
}
}
if(month==4 || month==6 || month==9 || month==11)
{
if(day==31)
{
day=1;
month++;
}
}
//2月中断,判断闰年和平年//
if(month==2)
{
if(year%4==0 && year%100!=0 &&day==30)
{
day=1;
month++;
}
if(year%400==0 && day==30)
{
day=1;
month++;
}
if(year%4!=0 && day==29)
{
day=1;
month++;
}
if(year%4==0 && year%100==0 && year%400!=0 && day==29)
{
day=1;
month++;
}
}
}
while(!k7);
}
TH0=0Xd8; //给定时器赋初值,定时10ms
TL0=0Xf0;
ssec++;
if(ssec>=100) //100*ms=1s
{
ssec=0;
sec++;
second++;
if(second==60)//秒中断
{
second=0;
minute++;
if(minute==60)
{
minute=0;
hour++;
if(hour==24)
{
hour=0;
day++;
}
//与上面的注释雷同//
if(month==1 || month==3 || month==5 || month==7 || month==8 || month==10)
{
if(day==32)
{
day=1;
month++;
}
}
if(month==4 || month==6 || month==9 || month==11)
{
if(day==31)
{
day=1;
month++;
}
}
if(month==2)
{
if(year%4==0 && year%100!=0 &&day==30)
{
day=1;
month++;
}
if(year%400==0 && day==30)
{
day=1;
month++;
}
if(year%4!=0 && day==29)
{
day=1;
month++;
}
if(year%4==0 && year%100==0 && year%400!=0 && day==29)
{
day=1;
month++;
}
}
if(month==12)
{
month=1;
year++;
if(year==10000)
{
year=0;
}
}
}
}
if(sec>=60)
{
sec=0;
min++;
if(min>=60)
{
min=0;
}
}
}
}
结合主函数main.c和temp.h和temp.c函数在keil4里面运行即可产生hex文件,接下来烧录单片机即可。
Comments NOTHING