因为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<
既然需要控制数码管,我们就需要不同的标志位来进行记录
/***********数码管显示内容选择***********
功能:通过改变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,05sTL0 = (65535 - 50000) % 256;ET0 = 1; //使能定时器T0EA = 1; //使能总中断TR0 = 1; //启动定时器T0
}/*===============定时器服务函数===================
功能:利用定时器进行计数
参数:无
=======================================================*/
void ServiceTimer0() interrupt 1
{TH0 = (65535 - 50000) / 256; //0.05sTL0 = (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();}
]
实际测试之后,我们没有在温度显示函数里面再进行延时,也是可以正常显示的
/*********温度读取函数************
功能:进行温度读取(具体在上一篇文章讲过,之后会把内容移动过来)
参数:无
******************************/
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_Hunsigned char rd_temperature(void);
bit init_ds18b20(void);
void Write_DS18B20(unsigned char dat);
unsigned char Read_DS18B20(void);#endif
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可以改为 %16DelaySMG(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; // 启动定时器1SCON = 0x50; //8位UARTAUXR = 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;}}
}
上一篇:近期 NOI\(P\) 总结
下一篇:1-货物摆放