因为C51只有一组数码管,但是我们需要显示的东西有很多,所以通过按键切换是我们必须要知道的
按键之间有嵌套,切换,计数,对于按键的使用我们是必须知道的
1. HC573锁存器的选择
我们在之前的基础上对其进行了优化,这样就不会出现冲突的问题
因为在使用前,我们直接把所有的给关闭了,当使用的时候先把P0给起来,在引到每个锁存器
/************锁存器选择******************
功能:打通功能需要的锁存器
参数:channel(选择锁存器) dat(一般为oxff 打开通道,但是我们可以去掉这个参数)
***************************************/
void SelectHC573(unsigned channel,unsigned char dat)
{
P0 = dat; //带设置参数数据
switch(channel)
{
case 4:
P2 = (P2 & 0x1f) | 0x80; //Y4,选择LED控制
break;
case 5:
P2 = (P2 & 0x1f) | 0xa0; //Y5,选择蜂鸣器和继电器控制
break;
case 6:
P2 = (P2 & 0x1f) | 0xc0; //Y6,选择数码管位置
break;
case 7:
P2 = (P2 & 0x1f) | 0xe0; //Y7,选择数码管段码
break;
case 0:
P2 = (P2 & 0x1f) | 0x00; //在完成后关闭所有锁存器
break;
}
P2 = (P2 & 0x1f) | 0x00; // 修改完成,关闭全部锁存器
}
2. 数码管位置选择与数码管显示
<1> 数码管选择是需要延时函数的。我们是利用余辉效果来进行显示的,所以在数码管显示之后我们需要进行延时,所以我们直接把延时函数放到单个数码管显示之后,这样子显示就变得简单了。
<2>延时延时可以选择 char / int 两种类型,但是需要注意时间,如果时间太长会导致抖动,如果时间过短又会导致数码管变暗。
/****************数码管延时函数**************
功能:对数码管进行延时
参数:t--
*******************************************/
void DelaySMG(unsigned int t)
{
while(t--);
}
/*************数码管显示函数***************
功能: 控制每个数码管的亮暗,在之前的基础上我们使用了延时函数,这样在主体显示就不用添加延时函数了
参数: value (内容) pos(位置)
****************************************/
void DisplaySMG_Bit(unsigned char value, unsigned char pos)
{
SelectHC573(6,0x01<<pos); //数码管段位
//P0 = 0X01 << pos
SelectHC573(7,value); //数码管内容
//P0 = value
DelaySMG(500); //延时
SelectHC573(6,0x01<<pos); //数码管段位
SelectHC573(7,0xff); //数码管消隐
}
//如果前面的锁存器没有设置P0的参数,则可以使用下面的老办法
void DisplaySMG_Bit(unsigned char value, unsigned char pos)
{
P0 = 0xff; //消隐
SelectHC573(6);
P0 = 0x01 << pos; //位置
SelectHC573(7);
P0 = value; //内容
}
/************控制全数码管*************
功能:在显示前后进行使用,进行数码管的初始化
参数 value(一般为0xff)
************************************/
void DisPlaySMG_All(unsigned char value)
{
SelectHC573(6, 0xff); //数码管段位
SelectHC573(7,value); //数码管内容
}
既然需要控制数码管,我们就需要不同的标志位来进行记录
/***********数码管显示内容选择***********
功能:通过改变flag的参数来选择不同的锁存器
参数:无
***************************************/
void DisplaySMG_Select()
{
switch(SMG_flag)
{
case 1:
(显示时间的函数 DisplayTime);
break;
case 2:
(显示温度的函数 DisplayTemp);
break;
....
}
}
3. 独立按键进行切换
只进行切换当前模式,不能改变当前的状态,所以我们需要在更换模式时,仍然保持数码管的显示,而进行标志位值的改变,当模式多时,我们就需要使用矩阵按键(参考上一篇文章的扫描)
/*****************按键切换**********
功能:通过按键切换模式,也就是我们之前写过的
参数:无
**********************************/
void Key_Scan()
{
if(S4 == 0)
{
Delay(200); //消抖
if(S4 == 0)
{
if(stat_flag == 1) //初始默认的标志位为1——系统时间记录
{
stat_flag =2; //转换模式类型
}
while(S4 == 0)
{
Display_SMGselect(); //按键按下后仍然能显示当前的数码管,这步是需要的
Delay(500);
}
}
}
//模仿上面的操作,进行数码管的操作
else if(S5 == 0)
{
Delay(200);
if(S5 == 0)
{
if(stat_flag == 2)
{
stat_flag =1;
}
while(S5 == 0)
{
Display_SMGselect();
Delay(500);
}
}
}
}
而进行切换的过程中,我们当前状态需要一直进行的,我们就在while循环中执行
4. 系统初始化函数
/*==================系统初始化函数======================
功能:关闭没用的器件,打开需要使用的。进行锁存器的选择
参数:无
=======================================================*/
void InitSystem()
{
SelectHC573(5); //关闭蜂鸣器,继电器
P0 = 0x00;
SelectHC573(4); //打开灯光
P0 = 0xff;
SelectHC573(0); //完成后关闭
}
5. 系统时间函数
我们通过定时器来进行时间计数,通过测试,按键切换不会改变系统运行时间的值,我们不需要记载,他会一直记录着
/*================定时器初始化函数====================
功能: 初始化定时器
参数: 无
=======================================================*/
void InitTimer0()
{
TMOD = 0x21; // 定时器1/2一起赋值
TH0 = (65535 - 50000) / 256; // 0,05s
TL0 = (65535 - 50000) % 256;
ET0 = 1; //使能定时器T0
EA = 1; //使能总中断
TR0 = 1; //启动定时器T0
}
/*===============定时器服务函数===================
功能:利用定时器进行计数
参数:无
=======================================================*/
void ServiceTimer0() interrupt 1
{
TH0 = (65535 - 50000) / 256; //0.05s
TL0 = (65535 - 50000) % 256;
count++;
if(count == 20)
{
count = 0;
t_s++;
}
if(t_s == 60)
{
t_s = 0;
t_m++;
if(t_m == 60)
{
t_m = 0;
t_h++;
}
}
}
6.系统时间函数显示
中断初始化的配置:
<1> 配置工作模式,即对TMOD寄存器编程。
<2> 计算技术初值,即对THx和TLx寄存器进行赋值。
<3> 使能定时/计数器中断,即ET0或ET1置1。
<4> 打开总中断,即EA =1。
<5> 启动定时器,即TR0或TR1置1。
中断服务函数:
<1> 如果不是自动重装模式,需要对THx和TLx重新赋值。
<2> 进行间隔定时到达的逻辑处理(越少越好)
void DisplayTime()
{
//注意的是,如果使用新版的数码管显示函数,就不再需要再延时了
DisPlay_All(0xff); //消隐
DisplaySMG_Bit(SMG_NoDot[t_s%10],7);
DelaySMG(500);
DisplaySMG_Bit(SMG_NoDot[t_s/10],6);
DelaySMG(500);
DisplaySMG_Bit(SMG_NoDot[16],5);
DelaySMG(500);
DisplaySMG_Bit(SMG_NoDot[t_m%10],4);
DelaySMG(500);
DisplaySMG_Bit(SMG_NoDot[t_m/10],3);
DelaySMG(500);
DisplaySMG_Bit(SMG_NoDot[16],2);
DelaySMG(500);
DisplaySMG_Bit(SMG_NoDot[t_h%10],1);
DelaySMG(500);
DisplaySMG_Bit(SMG_NoDot[t_h/10],0);
DelaySMG(500);
DisPlay_All(0xff); //结尾再来一次
}
同时,在老师的视频内容中有说到,在延时的时候需要继续显示一下数码管的内容
void DisplayTmp(unsigned int t)
{
while(t--)
{
DisplayTemp();
}
]
实际测试之后,我们没有在温度显示函数里面再进行延时,也是可以正常显示的
7- 温度读取函数
/*********温度读取函数************
功能:进行温度读取(具体在上一篇文章讲过,之后会把内容移动过来)
参数:无
******************************/
void Read_DS18B20_temp()
{
unsigned char LSB,MSB;
init_ds18b20();
Write_DS18B20(0XCC);
Write_DS18B20(0X44);
//Delay_temp(1000);
init_ds18b20();
Write_DS18B20(0XCC);
Write_DS18B20(0Xbe);
LSB = Read_DS18B20();
MSB = Read_DS18B20();
temp = 0x000;
temp = MSB;
temp <<= 8;
temp = temp | LSB;
if((temp & 0xf800) == 0x0000)
{
temp>>=4;
temp = temp *10;
temp = temp +(LSB&0x0f)*0.625;
}
}
需要注意的是,在数码管显示温度之前,我们都要在显示函数前进行温度的读取,也就是把它写在 DisplayTemp 前面
温度显示函数
void DisplayTemp()
{
Read_DS18B20_temp();
DisPlay_All(0xff);
ShowSMG_bit(7,SMGnodot_CA[temp%10]);
DelaySMG(400);
ShowSMG_bit(6,SMGdot_CA[(temp%100)/10]);
DelaySMG(400);
ShowSMG_bit(5,SMGnodot_CA[temp/100]);
DelaySMG(400);
ShowSMG_bit(4,0XFF);
DelaySMG(400);
ShowSMG_bit(3,0XFF);
DelaySMG(400);
ShowSMG_bit(2,0XFF);
DelaySMG(400);
ShowSMG_bit(1,0XFF);
DelaySMG(400);
ShowSMG_bit(0,0XFF);
DelaySMG(400);
DisPlay_All(0xff);
}
DS18B20 温度的底层代码
onewire.c文件
#include "onewire.h"
#include "reg52.h"
sbit DQ = P1^4;
void Delay_OneWire(unsigned int t)
{
while(t--);
}
void Write_DS18B20(unsigned char dat)
{
char i;
for(i=0;i<8;i++)
{
DQ = 0;
DQ = dat&0x01;
Delay_OneWire(50);
DQ = 1;
dat >>= 1;
}
Delay_OneWire(50
);
}
unsigned char Read_DS18B20(void)
{
unsigned char i;
unsigned char dat;
for(i=0;i<8;i++)
{
DQ = 0;
dat >>= 1;
DQ = 1;
if(DQ)
{
dat |= 0x80;
}
Delay_OneWire(50);
}
return dat;
}
bit init_ds18b20(void)
{
bit initflag = 0;
DQ = 1;
Delay_OneWire(120);
DQ = 0;
Delay_OneWire(800);
DQ = 1;
Delay_OneWire(100);
initflag = DQ;
Delay_OneWire(50);
return initflag;
}
onewire.h文件
我们之后也可以把前面写的函数给封装到文件里
#ifndef __ONEWIRE_H
#define __ONEWIRE_H
unsigned char rd_temperature(void);
bit init_ds18b20(void);
void Write_DS18B20(unsigned char dat);
unsigned char Read_DS18B20(void);
#endif
8-实时时钟DS1302
DS1302有关日历和时钟的寄存器有12个,我们最常用的有7个。
什么是BCD码?
就是用十六进制来表示十进制。什么意思?怎么理解?
例如,十六进制数0x13的值为整数19,但BCD码表示的是整数13。
DS1302将地址和读写控制放到一个字节里面,形成一个控制字,格式如下:
我们往DS1304里面写入一个8为的数据(指令),把它分为上面8个
通过上面的控制字格式,大家就可以明白为什么DS1302读寄存器和写寄存器的地址是不一样的了,因为这个地址包含了读写控制位。为了方便程序设计,我们把
读寄存器地址、写寄存器地址和日历时钟寄存器方面用三个数组定义。
BCD码,用16进制来表示10进制
0x30(秒) 0x50(50分) 如上图所示,依次类推
底层驱动代码实现可参考如下:
DS1302.c
unsigned char DS1302_ReadByte(unsigned char addr)
{
unsigned char n,dat,tmp;
RST = 0;
_nop_();
SCLK = 0;
_nop_();
RST = 1;
_nop_();
for(n=0; n<8; n++) //发送要读出数据的内存地址
{
DSIO = addr & 0x01;
addr >>= 1;
SCLK = 1;
_nop_();
SCLK = 0;
_nop_();
}
for(n=0; n<8; n++) //读出该地址内存的数据
{
tmp = DSIO;
dat = (dat>>1) | (tmp<<7);
SCLK = 1;
_nop_();
SCLK = 0;
_nop_();
}
RST = 0;
_nop_();
SCLK = 1;
_nop_();
DSIO = 0;
_nop_();
DSIO = 1;
_nop_();
return dat;
}
void DS1302_WriteByte(unsigned char addr, unsigned char dat)
{
unsigned char n;
RST = 0;
_nop_();
SCLK = 0;
_nop_();
RST = 1;
_nop_();
for (n=0; n<8; n++) //发送要写入数据的内存地址
{
DSIO = addr & 0x01;
addr >>= 1;
SCLK = 1;
_nop_();
SCLK = 0;
_nop_();
}
for (n=0; n<8; n++) //将指定内容写入该地址的内存
{
DSIO = dat & 0x01;
dat >>= 1;
SCLK = 1;
_nop_();
SCLK = 0;
_nop_();
}
RST = 0;
_nop_();
}
有了上面两个SPI底层代码,我们读出DS1302的数据就变得非常简单了
我们可以参考下面的源码:
#include "reg52.h"
#include "intrins.h"
sbit HC138_A = P2^5;
sbit HC138_B = P2^6;
sbit HC138_C = P2^7;
sbit SCLK = P1^7;
sbit RST = P1^3;
sbit DSIO = P2^3;
unsigned char code READ_RTC_ADDR[7] = {0x81, 0x83, 0x85, 0x87, 0x89, 0x8b, 0x8d};
unsigned char code WRITE_RTC_ADDR[7] = {0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c};
unsigned char TIME[7] = {0x30, 0x50, 0x23, 0x17, 0x02, 0x06, 0x18};
unsigned char code SMG_NoDot[18] =
{0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,
0x80,0x90,0x88,0x80,0xc6,0xc0,0x86,0x8e,
0xbf,0x7f};
void DelaySMG(unsigned int time)
{
while(time--);
}
void Init74HC138(unsigned char n)
{
switch(n)
{
case 4:
HC138_A = 0;
HC138_B = 0;
HC138_C = 1;
break;
case 5:
HC138_A = 1;
HC138_B = 0;
HC138_C = 1;
break;
case 6:
HC138_A = 0;
HC138_B = 1;
HC138_C = 1;
break;
case 7:
HC138_A = 1;
HC138_B = 1;
HC138_C = 1;
break;
case 8:
HC138_A = 0;
HC138_B = 0;
HC138_C = 0;
break;
}
}
void DispaySMG_Bit(unsigned char value, unsigned char pos)
{
Init74HC138(6);
P0 = (0x01 << pos);
Init74HC138(7);
P0 = value;
}
void DS1302_WriteByte(unsigned char addr, unsigned char dat)
{
unsigned char n;
RST = 0;
_nop_();
SCLK = 0;
_nop_();
RST = 1;
_nop_();
for (n=0; n<8; n++)
{
DSIO = addr & 0x01;
addr >>= 1;
SCLK = 1;
_nop_();
SCLK = 0;
_nop_();
}
for (n=0; n<8; n++)
{
DSIO = dat & 0x01;
dat >>= 1;
SCLK = 1;
_nop_();
SCLK = 0;
_nop_();
}
RST = 0;
_nop_();
}
unsigned char DS1302_ReadByte(unsigned char addr)
{
unsigned char n,dat,tmp;
RST = 0;
_nop_();
SCLK = 0;
_nop_();
RST = 1;
_nop_();
for(n=0; n<8; n++)
{
DSIO = addr & 0x01;
addr >>= 1;
SCLK = 1;
_nop_();
SCLK = 0;
_nop_();
}
for(n=0; n<8; n++)
{
tmp = DSIO;
dat = (dat>>1) | (tmp<<7);
SCLK = 1;
_nop_();
SCLK = 0;
_nop_();
}
RST = 0;
_nop_();
SCLK = 1;
_nop_();
DSIO = 0;
_nop_();
DSIO = 1;
_nop_();
return dat;
}
//DS1302的初始化
void DS1302_Config()
{
unsigned char n;
DS1302_WriteByte(0x8E,0x00); //写入保护
for (n=0; n<7; n++)
{
//写入时分秒年月日
DS1302_WriteByte(WRITE_RTC_ADDR[n],TIME[n]);
}
DS1302_WriteByte(0x8E,0x80);
}
//DS1302读取当前时间
void DS1302_ReadTime()
{
unsigned char n;
for (n=0; n<7; n++)
{
//读取的内存地址,读7个时分秒年月日
TIME[n] = DS1302_ReadByte(READ_RTC_ADDR[n]);
}
}
//数码管显示年月日
void XMF_ShowRealTime()
{
//小时
DispaySMG_Bit(SMG_NoDot[TIME[2]/16],0);
DelaySMG(500);
DispaySMG_Bit(0xff,0);
DispaySMG_Bit(SMG_NoDot[TIME[2]&0x0f],1);
DelaySMG(500);
DispaySMG_Bit(0xff,1);
DispaySMG_Bit(SMG_NoDot[16],2);
DelaySMG(500);
DispaySMG_Bit(0xff,2);
//分钟
DispaySMG_Bit(SMG_NoDot[TIME[1]/16],3);
DelaySMG(500);
DispaySMG_Bit(0xff,3);
DispaySMG_Bit(SMG_NoDot[TIME[1]&0x0f],4);
DelaySMG(500);
DispaySMG_Bit(0xff,4);
DispaySMG_Bit(SMG_NoDot[16],5);
DelaySMG(500);
DispaySMG_Bit(0xff,5);
//秒
DispaySMG_Bit(SMG_NoDot[TIME[0]/16],6);
DelaySMG(500);
DispaySMG_Bit(0xff,6); //消隐
DispaySMG_Bit(SMG_NoDot[TIME[0]&0x0f],7); //&0x0f可以改为 %16
DelaySMG(500);
//关闭全部数码管,不关闭会导致亮度可能不同
DispaySMG_Bit(0xff,7);
}
void main()
{
DS1302_Config(); //DS1302初始化
while(1)
{
// 显示前需要先读取1302的数据
DS1302_ReadTime();
XMF_ShowRealTime();
}
}
9- 串口中断
TH1和TL1:设置波特率参数。
TMOD:设置定时器1的工作模式。
SBUF:串行通信数据的发送和接收缓冲器。
SCON:串行接口控制寄存器。
TR1: 定时器
ES: 串口中断
EA: 总中断
/*=================串口初始化函数========================
功能:将串口设置为模式1,波特率9600,允许接收
参数
=======================================================*/
void InitUart()
{
TMOD = 0x21; //T0与T1一起赋值
TH1 = 0xfd; //设置9600波特率
TL1 = 0xfd;
TR1 = 1; // 启动定时器1
SCON = 0x50; //8位UART
AUXR = 0x00; //辅助寄存器
ES = 1; //使能串口中断
EA = 1; //使能总中断
}
/*=================串口中断服务函数====================
功能:接收上位机所发送的字符
=======================================================*/
void ServiceUart() interrupt 4
{
if(RI == 1)
{
command = SBUF; //½«½ÓÊÕµ½µÄÊý¾Ý±£´æµ½command±äÁ¿
RI = 0; //½«½ÓÊÕÍê³É±êÖ¾RIÇå0
}
}
/*=================串口服务函数====================
功能:接收上位机发送的数据并保持在command里
参数:无
=======================================================*/
void SendByte(unsigned char dat)
{
SBUF = dat;
while(TI == 0);
TI = 0;
}
void SendString(unsigned char *str)
{
while(*str != '\0')
{
SendByte(*str++);
}
}
/*===============串口信息接收执行函数==================
功能:接收上位机消息,进行灯光控制
参数:无
=======================================================*/
void ExecuteCommand()
{
if(command != 0x00) //接收的消息不为空
{
switch(command & 0xf0) //将命令类型取出
{
case 0xa0: //远程控制灯光
SelectHC573(4);
stat_led = (stat_led | 0x0f) & (~command | 0xf0);
P0 = stat_led;
SelectHC573(0);
command = 0x00;
break;
case 0xb0: //读取系统运行时间
SendByte((t_h / 10 << 4) | (t_h % 10));
SendByte((t_m / 10 << 4) | (t_m % 10));
SendByte((t_s / 10 << 4) | (t_s % 10));
command = 0x00;
break;
}
}
}