51单片机实现智能手表(秒表功能、读取温度、显示和修改时间日期、设置闹钟、显示星期)
时间:2022-12-23 01:30:00
项目说明
用普中51和keil uVision4实现智能手表的功能包括:显示秒表、时间、日期、星期、设置闹钟、读取温度、指示灯亮等。综合实验主要包括独立按钮、指示灯亮、温度传感器、定时器、动态数字管显示、蜂鸣器实验,本博客重要解释定时器和温度传感器实验,其他组合实验相对简单,可以自行查阅数据。
硬件设计
温度传感器
我用这个项目精度高的外部 DS18B20 由于该传感器是单总线接口,需要使用数字温度传感器 51 单片机的一个 IO 口模拟单总线口模拟 DS18B20 通信,读取检测到的环境温度。
DS18B20 外观实物如下图所示:
**注意:**如果将传感器插入向,电源短路,传感器会发热,容易损坏。因此,我们必须注意传感器的方向。传感器的凸起通常标记在开发板上,因此只需将传感器的凸起方向插入开发板的凸起方向即可。
DS18B20 数字温度传感器内部的一些高速临时存储器自行查阅数据,在这里跳过,直接了解它是如何计算温度值的。
计算温度
若温度大于 0,前5 位为‘ 只要将测量值乘以0 0.0625(默认精度为 12 如果温度小于,可以获得实际温度; 0,这 5 位为‘ 1’测量值需要取反加 1 再乘以 0.0625 实际温度际温度。例如,我们需要计算 85 度,数据输出16进制 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
//---重定义关键词---//
#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]=