代码拉取完成,页面将自动刷新
同步操作将从 建伟F4nniu/FN1895E-MCU101 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
#第四十八节:利用DS1302做一个实时时钟 。
开场白:
DS1302有两路独立电源输入,我们只要在其中一路电源上挂一个纽扣电池就可以实现掉电时钟继续跑的功能,纽扣电池作为备用电源必须比主电源的电压低一点。DS1302还给我们预留了一片RAM区,我们可以把一些数据存入到DS1302,只要DS1302的电池有电,那么它就相当于一个EEPROM。这个RAM区有什么用呢?因为RAM区的数据只要一掉电,所有的数据都会变成0x00或者0xff,也就是数据掉电会丢失,我们可以利用这个特点,可以在里面存入标志位数据,一旦发现这个数据改变了,就知道时钟的数据需要重新设置过,或者说明电池没电了。
在移植DS1302驱动程序中,有一个地方最容易出错,就是DS1302芯片的数据线DIO。我们编程时要特别留意这个IO口什么时候作为数据输入,什么时候作为数据输出,以便及时更改方向寄存器。对于51单片机,IO口在读取数据之前,要先置1。
这一节要教会大家六个知识点:
具体内容,请看源代码讲解。
- 基于朱兆祺51单片机学习板。
- 旧版的朱兆祺51学习板在硬件上有一个bug,DS1302芯片附近的R43,R42两个电阻应该去掉,并且把R41的电阻换成0欧姆的电阻,或者直接短接起来。新版的朱兆祺51学习板已经改过来了。
- 本程序有2两个窗口。
- 第1个窗口显示日期。显示格式“年-月-日”。注意中间有“-”分开。
- 第2个窗口显示时间。显示格式“时 分 秒”。注意中间没“-”,只有空格分开。
- 系统上电后,默认显示第2个窗口,实时显示动态的“时 分 秒”时间。此时按下S13按键不松手就会切换到显示日期的第1个窗口。松手后自动切换回第2个显示动态时间的窗口。
- 需要更改时间的时候,长按S9按键不松手超过3秒后,系统将进入修改时间的状态,切换到第1个日期窗口,并且显示“年”的两位数码管会闪烁,此时可以按S1或者S5加减按键修改年的参数,修改完年后,继续短按S9按键,会切换到“月”的参数闪烁状态,只要依次不断按下S9按键,就会依次切换年,月,日,时,分,秒的参数闪烁状态,最后修改完秒的参数后,系统会自动把我们修改设置的日期时间一次性写入DS1302芯片内部,达到修改日期时间的目的。
- S13是电平变化按键,用来切换窗口的,专门用来查看当前日期。按下S13按键时显示日期窗口,松手后返回到显示实时时间的窗口。
- 本程序在使用过程中的注意事项:
- (a) 第一次上电时,蜂鸣器会报警,只要DS1302芯片的备用电池电量充足,这个时候断电再重启一次,就不会报警了。
- (b) 第一次上电时,时间没有走动,需要重新设置一下日期时间才可以。长按S9按键可以进入修改日期时间的状态。 *(3)源代码讲解如下:
#include "REG52.H"
#define const_dpy_time_half 200 //数码管闪烁时间的半值
#define const_dpy_time_all 400 //数码管闪烁时间的全值 一定要比const_dpy_time_half 大
#define const_voice_short 40 //蜂鸣器短叫的持续时间
#define const_key_time1 20 //按键去抖动延时的时间
#define const_key_time2 20 //按键去抖动延时的时间
#define const_key_time3 20 //按键去抖动延时的时间
#define const_key_time4 20 //按键去抖动延时的时间
#define const_key_time17 1200 //长按超过3秒的时间
#define const_ds1302_0_5s 200 //大概0.5秒的时间
#define const_ds1302_sampling_time 360 //累计主循环次数的时间,每次刷新采样时钟芯片的时间
#define WRITE_SECOND 0x80 //DS1302内部的相关地址
#define WRITE_MINUTE 0x82
#define WRITE_HOUR 0x84
#define WRITE_DATE 0x86
#define WRITE_MONTH 0x88
#define WRITE_YEAR 0x8C
#define WRITE_CHECK 0xC2 //用来检查芯片的备用电池是否用完了的地址
#define READ_CHECK 0xC3 //用来检查芯片的备用电池是否用完了的地址
#define READ_SECOND 0x81
#define READ_MINUTE 0x83
#define READ_HOUR 0x85
#define READ_DATE 0x87
#define READ_MONTH 0x89
#define READ_YEAR 0x8D
#define WRITE_PROTECT 0x8E
void initial_myself(void);
void initial_peripheral(void);
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
//驱动数码管的74HC595
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09, unsigned char ucDigStatusTemp08_01);
void display_drive(void); //显示数码管字模的驱动函数
void display_service(void); //显示的窗口菜单服务程序
//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09, unsigned char ucLedStatusTemp08_01);
void T0_time(void); //定时中断函数
void key_service(void); //按键服务的应用程序
void key_scan(void);//按键扫描函数 放在定时中断里
void ds1302_alarm_service(void); //ds1302出错报警
void ds1302_sampling(void); //ds1302采样程序,内部每秒钟采集更新一次
void Write1302 ( unsigned char addr, unsigned char dat );//修改时间的驱动
unsigned char Read1302 ( unsigned char addr );//读取时间的驱动
unsigned char bcd_to_number(unsigned char ucBcdTemp); //BCD转原始数值
unsigned char number_to_bcd(unsigned char ucNumberTemp); //原始数值转BCD
//日调整 每个月份的日最大取值不同,有的最大28日,有的最大29日,有的最大30,有的最大31
unsigned char date_adjust(unsigned char ucYearTemp, unsigned char ucMonthTemp, unsigned char ucDateTemp); //日调整
sbit SCLK_dr = P1 ^ 3;
sbit DIO_dr_sr = P1 ^ 4;
sbit DS1302_CE_dr = P1 ^ 5;
sbit key_sr1 = P0 ^ 0; //对应朱兆祺学习板的S1键
sbit key_sr2 = P0 ^ 1; //对应朱兆祺学习板的S5键
sbit key_sr3 = P0 ^ 2; //对应朱兆祺学习板的S9键
sbit key_sr4 = P0 ^ 3; //对应朱兆祺学习板的S13键
sbit key_gnd_dr = P0 ^ 4; //模拟独立按键的地GND,因此必须一直输出低电平
sbit beep_dr = P2 ^ 7; //蜂鸣器的驱动IO口
sbit eeprom_scl_dr = P3 ^ 7; //时钟线
sbit eeprom_sda_dr_sr = P3 ^ 6; //数据的输出线和输入线
sbit dig_hc595_sh_dr = P2 ^ 0; //数码管的74HC595程序
sbit dig_hc595_st_dr = P2 ^ 1;
sbit dig_hc595_ds_dr = P2 ^ 2;
sbit hc595_sh_dr = P2 ^ 3; //LED灯的74HC595程序
sbit hc595_st_dr = P2 ^ 4;
sbit hc595_ds_dr = P2 ^ 5;
unsigned int uiSampingCnt = 0; //采集Ds1302的计时器,每秒钟更新采集一次
unsigned char ucKeySec = 0; //被触发的按键编号
unsigned int uiKeyTimeCnt1 = 0; //按键去抖动延时计数器
unsigned char ucKeyLock1 = 0; //按键触发后自锁的变量标志
unsigned int uiKeyTimeCnt2 = 0; //按键去抖动延时计数器
unsigned char ucKeyLock2 = 0; //按键触发后自锁的变量标志
unsigned int uiKeyTimeCnt3 = 0; //按键去抖动延时计数器
unsigned char ucKeyLock3 = 0; //按键触发后自锁的变量标志
unsigned int uiKey4Cnt1 = 0; //在软件滤波中,用到的变量
unsigned int uiKey4Cnt2 = 0;
unsigned char ucKey4Sr = 1; //实时反映按键的电平状态
unsigned char ucKey4SrRecord = 0; //记录上一次按键的电平状态
unsigned int uiVoiceCnt = 0; //蜂鸣器鸣叫的持续时间计数器
unsigned char ucVoiceLock = 0; //蜂鸣器鸣叫的原子锁
unsigned char ucDigShow8; //第8位数码管要显示的内容
unsigned char ucDigShow7; //第7位数码管要显示的内容
unsigned char ucDigShow6; //第6位数码管要显示的内容
unsigned char ucDigShow5; //第5位数码管要显示的内容
unsigned char ucDigShow4; //第4位数码管要显示的内容
unsigned char ucDigShow3; //第3位数码管要显示的内容
unsigned char ucDigShow2; //第2位数码管要显示的内容
unsigned char ucDigShow1; //第1位数码管要显示的内容
unsigned char ucDigDot8; //数码管8的小数点是否显示的标志
unsigned char ucDigDot7; //数码管7的小数点是否显示的标志
unsigned char ucDigDot6; //数码管6的小数点是否显示的标志
unsigned char ucDigDot5; //数码管5的小数点是否显示的标志
unsigned char ucDigDot4; //数码管4的小数点是否显示的标志
unsigned char ucDigDot3; //数码管3的小数点是否显示的标志
unsigned char ucDigDot2; //数码管2的小数点是否显示的标志
unsigned char ucDigDot1; //数码管1的小数点是否显示的标志
unsigned char ucDigShowTemp = 0; //临时中间变量
unsigned char ucDisplayDriveStep = 1; //动态扫描数码管的步骤变量
unsigned char ucWd = 2; //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
unsigned char ucPart = 0; //本程序的核心变量,局部显示变量。类似于二级菜单的变量。代表显示不同的局部。
unsigned char ucWd1Update = 0; //窗口1更新显示标志
unsigned char ucWd2Update = 1; //窗口2更新显示标志
unsigned char ucWd1Part1Update = 0; //在窗口1中,局部1的更新显示标志
unsigned char ucWd1Part2Update = 0; //在窗口1中,局部2的更新显示标志
unsigned char ucWd1Part3Update = 0; //在窗口1中,局部3的更新显示标志
unsigned char ucWd2Part1Update = 0; //在窗口2中,局部1的更新显示标志
unsigned char ucWd2Part2Update = 0; //在窗口2中,局部2的更新显示标志
unsigned char ucWd2Part3Update = 0; //在窗口2中,局部3的更新显示标志
unsigned char ucYear = 0; //原始数据
unsigned char ucMonth = 0;
unsigned char ucDate = 0;
unsigned char ucHour = 0;
unsigned char ucMinute = 0;
unsigned char ucSecond = 0;
unsigned char ucYearBCD = 0; //BCD码的数据
unsigned char ucMonthBCD = 0;
unsigned char ucDateBCD = 0;
unsigned char ucHourBCD = 0;
unsigned char ucMinuteBCD = 0;
unsigned char ucSecondBCD = 0;
unsigned char ucTemp1 = 0; //中间过渡变量
unsigned char ucTemp2 = 0; //中间过渡变量
unsigned char ucTemp4 = 0; //中间过渡变量
unsigned char ucTemp5 = 0; //中间过渡变量
unsigned char ucTemp7 = 0; //中间过渡变量
unsigned char ucTemp8 = 0; //中间过渡变量
unsigned char ucDelayTimerLock = 0; //原子锁
unsigned int uiDelayTimer = 0;
unsigned char ucCheckDs1302 = 0; //检查Ds1302芯片是否正常
unsigned char ucDs1302Error = 0; //Ds1302芯片的备用电池是否用完了的报警标志
unsigned char ucDs1302Lock = 0; //原子锁
unsigned int uiDs1302Cnt = 0; //间歇性蜂鸣器报警的计时器
unsigned char ucDpyTimeLock = 0; //原子锁
unsigned int uiDpyTimeCnt = 0; //数码管的闪烁计时器,放在定时中断里不断累加
//根据原理图得出的共阴数码管字模表
code unsigned char dig_table[] =
{
0x3f, //0 序号0
0x06, //1 序号1
0x5b, //2 序号2
0x4f, //3 序号3
0x66, //4 序号4
0x6d, //5 序号5
0x7d, //6 序号6
0x07, //7 序号7
0x7f, //8 序号8
0x6f, //9 序号9
0x00, //无 序号10
0x40, //- 序号11
0x73, //P 序号12
};
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
key_service(); //按键服务的应用程序
ds1302_sampling(); //ds1302采样程序,内部每秒钟采集更新一次
display_service(); //显示的窗口菜单服务程序
ds1302_alarm_service(); //ds1302出错报警
}
}
/* 注释一:
* 系统不用时时刻刻采集ds1302的内部数据,每隔一段时间更新采集一次就可以了。
* 这个间隔时间应该小于或者等于1秒钟的时间,否则在数码管上看不到每秒钟的时间变化。
*/
void ds1302_sampling(void) //ds1302采样程序,内部每秒钟采集更新一次
{
if(ucPart == 0) //当系统不是处于设置日期和时间的情况下
{
++uiSampingCnt; //累计主循环次数的时间
if(uiSampingCnt > const_ds1302_sampling_time) //每隔一段时间就更新采集一次Ds1302数据
{
uiSampingCnt = 0;
ucYearBCD = Read1302(READ_YEAR); //读取年
ucMonthBCD = Read1302(READ_MONTH); //读取月
ucDateBCD = Read1302(READ_DATE); //读取日
ucHourBCD = Read1302(READ_HOUR); //读取时
ucMinuteBCD = Read1302(READ_MINUTE); //读取分
ucSecondBCD = Read1302(READ_SECOND); //读取秒
ucYear = bcd_to_number(ucYearBCD); //BCD转原始数值
ucMonth = bcd_to_number(ucMonthBCD); //BCD转原始数值
ucDate = bcd_to_number(ucDateBCD); //BCD转原始数值
ucHour = bcd_to_number(ucHourBCD); //BCD转原始数值
ucMinute = bcd_to_number(ucMinuteBCD); //BCD转原始数值
ucSecond = bcd_to_number(ucSecondBCD); //BCD转原始数值
ucWd2Update = 1; //窗口2更新显示时间
}
}
}
//修改ds1302时间的驱动 ,注意,此处写入的是BCD码,
void Write1302 ( unsigned char addr, unsigned char dat )
{
unsigned char i, temp; //单片机驱动DS1302属于SPI通讯方式,根据我的经验,不用关闭中断
DS1302_CE_dr = 0; //CE引脚为低,数据传送中止
delay_short(1);
SCLK_dr = 0; //清零时钟总线
delay_short(1);
DS1302_CE_dr = 1; //CE引脚为高,逻辑控制有效
delay_short(1);
//发送地址
for ( i = 0; i < 8; i++ ) //循环8次移位
{
DIO_dr_sr = 0;
temp = addr;
if(temp & 0x01)
{
DIO_dr_sr = 1;
}
else
{
DIO_dr_sr = 0;
}
delay_short(1);
addr >>= 1; //右移一位
SCLK_dr = 1;
delay_short(1);
SCLK_dr = 0;
delay_short(1);
}
//发送数据
for ( i = 0; i < 8; i++ ) //循环8次移位
{
DIO_dr_sr = 0;
temp = dat;
if(temp & 0x01)
{
DIO_dr_sr = 1;
}
else
{
DIO_dr_sr = 0;
}
delay_short(1);
dat >>= 1; //右移一位
SCLK_dr = 1;
delay_short(1);
SCLK_dr = 0;
delay_short(1);
}
DS1302_CE_dr = 0;
delay_short(1);
}
//读取Ds1302时间的驱动 ,注意,此处读取的是BCD码,
unsigned char Read1302 ( unsigned char addr )
{
unsigned char i, temp, dat1;
DS1302_CE_dr = 0; //单片机驱动DS1302属于SPI通讯方式,根据我的经验,不用关闭中断
delay_short(1);
SCLK_dr = 0;
delay_short(1);
DS1302_CE_dr = 1;
delay_short(1);
//发送地址
for ( i = 0; i < 8; i++ ) //循环8次移位
{
DIO_dr_sr = 0;
temp = addr;
if(temp & 0x01)
{
DIO_dr_sr = 1;
}
else
{
DIO_dr_sr = 0;
}
delay_short(1);
addr >>= 1; //右移一位
SCLK_dr = 1;
delay_short(1);
SCLK_dr = 0;
delay_short(1);
}
/* 注释二:
* 51单片机IO口的特点,在读取数据之前必须先输出高电平,
* 如果是PIC,AVR等单片机,这里应该把IO方向寄存器设置为输入
*/
DIO_dr_sr = 1; //51单片机IO口的特点,在读取数据之前必须先输出高电平,
temp = 0;
for ( i = 0; i < 8; i++ )
{
temp >>= 1;
if(DIO_dr_sr == 1)
{
temp = temp + 0x80;
}
DIO_dr_sr = 1; //51单片机IO口的特点,在读取数据之前必须先输出高电平
delay_short(1);
SCLK_dr = 1;
delay_short(1);
SCLK_dr = 0;
delay_short(1);
}
DS1302_CE_dr = 0;
delay_short(1);
dat1 = temp;
return (dat1);
}
unsigned char bcd_to_number(unsigned char ucBcdTemp) //BCD转原始数值
{
unsigned char ucNumberResult = 0;
unsigned char ucBcdTemp10;
unsigned char ucBcdTemp1;
ucBcdTemp10 = ucBcdTemp;
ucBcdTemp10 = ucBcdTemp10 >> 4;
ucBcdTemp1 = ucBcdTemp;
ucBcdTemp1 = ucBcdTemp1 & 0x0f;
ucNumberResult = ucBcdTemp10 * 10 + ucBcdTemp1;
return ucNumberResult;
}
unsigned char number_to_bcd(unsigned char ucNumberTemp) //原始数值转BCD
{
unsigned char ucBcdResult = 0;
unsigned char ucNumberTemp10;
unsigned char ucNumberTemp1;
ucNumberTemp10 = ucNumberTemp;
ucNumberTemp10 = ucNumberTemp10 / 10;
ucNumberTemp10 = ucNumberTemp10 << 4;
ucNumberTemp10 = ucNumberTemp10 & 0xf0;
ucNumberTemp1 = ucNumberTemp;
ucNumberTemp1 = ucNumberTemp1 % 10;
ucBcdResult = ucNumberTemp10 | ucNumberTemp1;
return ucBcdResult;
}
//日调整 每个月份的日最大取值不同,有的最大28日,有的最大29日,有的最大30,有的最大31
unsigned char date_adjust(unsigned char ucYearTemp, unsigned char ucMonthTemp, unsigned char ucDateTemp) //日调整
{
unsigned char ucDayResult;
unsigned int uiYearTemp;
unsigned int uiYearYu;
ucDayResult = ucDateTemp;
switch(ucMonthTemp) //根据不同的月份来修正不同的日最大值
{
case 2: //二月份要计算是否是闰年
uiYearTemp = 2000 + ucYearTemp;
uiYearYu = uiYearTemp % 4;
if(uiYearYu == 0) //闰年
{
if(ucDayResult > 29)
{
ucDayResult = 29;
}
}
else
{
if(ucDayResult > 28)
{
ucDayResult = 28;
}
}
break;
case 4:
case 6:
case 9:
case 11:
if(ucDayResult > 30)
{
ucDayResult = 30;
}
break;
}
return ucDayResult;
}
void ds1302_alarm_service(void) //ds1302出错报警
{
if(ucDs1302Error == 1) //备用电池的电量用完了报警提示
{
if(uiDs1302Cnt > const_ds1302_0_5s) //大概0.5秒钟蜂鸣器响一次
{
ucDs1302Lock = 1; //原子锁加锁
uiDs1302Cnt = 0; //计时器清零
ucDs1302Lock = 0; //原子锁解锁
ucVoiceLock = 1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
uiVoiceCnt = const_voice_short; //蜂鸣器声音触发,滴一声就停。
ucVoiceLock = 0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
}
}
}
void display_service(void) //显示的窗口菜单服务程序
{
switch(ucWd) //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
{
case 1: //显示日期窗口的数据 数据格式 NN-YY-RR 年-月-日
if(ucWd1Update == 1) //窗口1要全部更新显示
{
ucWd1Update = 0; //及时清零标志,避免一直进来扫描
ucDigShow6 = 11; //显示一杠"-"
ucDigShow3 = 11; //显示一杠"-"
ucWd1Part1Update = 1; //局部年更新显示
ucWd1Part2Update = 1; //局部月更新显示
ucWd1Part3Update = 1; //局部日更新显示
}
if(ucWd1Part1Update == 1) //局部年更新显示
{
ucWd1Part1Update = 0;
ucTemp8 = ucYear / 10; //年
ucTemp7 = ucYear % 10;
ucDigShow8 = ucTemp8; //数码管显示实际内容
ucDigShow7 = ucTemp7;
}
if(ucWd1Part2Update == 1) //局部月更新显示
{
ucWd1Part2Update = 0;
ucTemp5 = ucMonth / 10; //月
ucTemp4 = ucMonth % 10;
ucDigShow5 = ucTemp5; //数码管显示实际内容
ucDigShow4 = ucTemp4;
}
if(ucWd1Part3Update == 1) //局部日更新显示
{
ucWd1Part3Update = 0;
ucTemp2 = ucDate / 10; //日
ucTemp1 = ucDate % 10;
ucDigShow2 = ucTemp2; //数码管显示实际内容
ucDigShow1 = ucTemp1;
}
//数码管闪烁
switch(ucPart) //相当于二级菜单,根据局部变量的值,使对应的参数产生闪烁的动态效果。
{
case 0: //都不闪烁
break;
case 1: //年参数闪烁
if(uiDpyTimeCnt == const_dpy_time_half)
{
ucDigShow8 = ucTemp8; //数码管显示实际内容
ucDigShow7 = ucTemp7;
}
else if(uiDpyTimeCnt > const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
{
ucDpyTimeLock = 1; //原子锁加锁
uiDpyTimeCnt = 0; //及时把闪烁记时器清零
ucDpyTimeLock = 0; //原子锁解锁
ucDigShow8 = 10; //数码管显示空,什么都不显示
ucDigShow7 = 10;
}
break;
case 2: //月参数闪烁
if(uiDpyTimeCnt == const_dpy_time_half)
{
ucDigShow5 = ucTemp5; //数码管显示实际内容
ucDigShow4 = ucTemp4;
}
else if(uiDpyTimeCnt > const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
{
ucDpyTimeLock = 1; //原子锁加锁
uiDpyTimeCnt = 0; //及时把闪烁记时器清零
ucDpyTimeLock = 0; //原子锁解锁
ucDigShow5 = 10; //数码管显示空,什么都不显示
ucDigShow4 = 10;
}
break;
case 3: //日参数闪烁
if(uiDpyTimeCnt == const_dpy_time_half)
{
ucDigShow2 = ucTemp2; //数码管显示实际内容
ucDigShow1 = ucTemp1;
}
else if(uiDpyTimeCnt > const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
{
ucDpyTimeLock = 1; //原子锁加锁
uiDpyTimeCnt = 0; //及时把闪烁记时器清零
ucDpyTimeLock = 0; //原子锁解锁
ucDigShow2 = 10; //数码管显示空,什么都不显示
ucDigShow1 = 10;
}
break;
}
break;
case 2: //显示时间窗口的数据 数据格式 SS FF MM 时 分 秒
if(ucWd2Update == 1) //窗口2要全部更新显示
{
ucWd2Update = 0; //及时清零标志,避免一直进来扫描
ucDigShow6 = 10; //显示空
ucDigShow3 = 10; //显示空
ucWd2Part3Update = 1; //局部时更新显示
ucWd2Part2Update = 1; //局部分更新显示
ucWd2Part1Update = 1; //局部秒更新显示
}
if(ucWd2Part1Update == 1) //局部时更新显示
{
ucWd2Part1Update = 0;
ucTemp8 = ucHour / 10; //时
ucTemp7 = ucHour % 10;
ucDigShow8 = ucTemp8; //数码管显示实际内容
ucDigShow7 = ucTemp7;
}
if(ucWd2Part2Update == 1) //局部分更新显示
{
ucWd2Part2Update = 0;
ucTemp5 = ucMinute / 10; //分
ucTemp4 = ucMinute % 10;
ucDigShow5 = ucTemp5; //数码管显示实际内容
ucDigShow4 = ucTemp4;
}
if(ucWd2Part3Update == 1) //局部秒更新显示
{
ucWd2Part3Update = 0;
ucTemp2 = ucSecond / 10; //秒
ucTemp1 = ucSecond % 10;
ucDigShow2 = ucTemp2; //数码管显示实际内容
ucDigShow1 = ucTemp1;
}
//数码管闪烁
switch(ucPart) //相当于二级菜单,根据局部变量的值,使对应的参数产生闪烁的动态效果。
{
case 0: //都不闪烁
break;
case 1: //时参数闪烁
if(uiDpyTimeCnt == const_dpy_time_half)
{
ucDigShow8 = ucTemp8; //数码管显示实际内容
ucDigShow7 = ucTemp7;
}
else if(uiDpyTimeCnt > const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
{
ucDpyTimeLock = 1; //原子锁加锁
uiDpyTimeCnt = 0; //及时把闪烁记时器清零
ucDpyTimeLock = 0; //原子锁解锁
ucDigShow8 = 10; //数码管显示空,什么都不显示
ucDigShow7 = 10;
}
break;
case 2: //分参数闪烁
if(uiDpyTimeCnt == const_dpy_time_half)
{
ucDigShow5 = ucTemp5; //数码管显示实际内容
ucDigShow4 = ucTemp4;
}
else if(uiDpyTimeCnt > const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
{
ucDpyTimeLock = 1; //原子锁加锁
uiDpyTimeCnt = 0; //及时把闪烁记时器清零
ucDpyTimeLock = 0; //原子锁解锁
ucDigShow5 = 10; //数码管显示空,什么都不显示
ucDigShow4 = 10;
}
break;
case 3: //秒参数闪烁
if(uiDpyTimeCnt == const_dpy_time_half)
{
ucDigShow2 = ucTemp2; //数码管显示实际内容
ucDigShow1 = ucTemp1;
}
else if(uiDpyTimeCnt > const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
{
ucDpyTimeLock = 1; //原子锁加锁
uiDpyTimeCnt = 0; //及时把闪烁记时器清零
ucDpyTimeLock = 0; //原子锁解锁
ucDigShow2 = 10; //数码管显示空,什么都不显示
ucDigShow1 = 10;
}
break;
}
break;
}
}
void key_scan(void)//按键扫描函数 放在定时中断里
{
if(key_sr1 == 1) //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock1 = 0; //按键自锁标志清零
uiKeyTimeCnt1 = 0; //按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock1 == 0) //有按键按下,且是第一次被按下
{
uiKeyTimeCnt1++; //累加定时中断次数
if(uiKeyTimeCnt1 > const_key_time1)
{
uiKeyTimeCnt1 = 0;
ucKeyLock1 = 1; //自锁按键置位,避免一直触发
ucKeySec = 1; //触发1号键
}
}
if(key_sr2 == 1) //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock2 = 0; //按键自锁标志清零
uiKeyTimeCnt2 = 0; //按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock2 == 0) //有按键按下,且是第一次被按下
{
uiKeyTimeCnt2++; //累加定时中断次数
if(uiKeyTimeCnt2 > const_key_time2)
{
uiKeyTimeCnt2 = 0;
ucKeyLock2 = 1; //自锁按键置位,避免一直触发
ucKeySec = 2; //触发2号键
}
}
/* 注释三:
* 注意,此处把一个按键的短按和长按的功能都实现了。
*/
if(key_sr3 == 1) //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock3 = 0; //按键自锁标志清零
uiKeyTimeCnt3 = 0; //按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock3 == 0) //有按键按下,且是第一次被按下
{
uiKeyTimeCnt3++; //累加定时中断次数
if(uiKeyTimeCnt3 > const_key_time3)
{
uiKeyTimeCnt3 = 0;
ucKeyLock3 = 1; //自锁按键置位,避免一直触发
ucKeySec = 3; //短按触发3号键
}
}
else if(uiKeyTimeCnt3 < const_key_time17) //长按3秒
{
uiKeyTimeCnt3++; //累加定时中断次数
if(uiKeyTimeCnt3 == const_key_time17) //等于3秒钟,触发17号长按按键
{
ucKeySec = 17; //长按3秒触发17号键
}
}
/* 注释四:
* 注意,此处是电平按键的滤波抗干扰处理
*/
if(key_sr4 == 1) //对应朱兆祺学习板的S13键
{
uiKey4Cnt1 = 0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
uiKey4Cnt2++; //类似独立按键去抖动的软件抗干扰处理
if(uiKey4Cnt2 > const_key_time4)
{
uiKey4Cnt2 = 0;
ucKey4Sr = 1; //实时反映按键松手时的电平状态
}
}
else
{
uiKey4Cnt2 = 0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
uiKey4Cnt1++;
if(uiKey4Cnt1 > const_key_time4)
{
uiKey4Cnt1 = 0;
ucKey4Sr = 0; //实时反映按键按下时的电平状态
}
}
}
void key_service(void) //按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
case 1:// 加按键 对应朱兆祺学习板的S1键
switch(ucWd) //在不同的窗口下,设置不同的参数
{
case 1:
switch(ucPart) //在不同的局部变量下,相当于二级菜单
{
case 1: //年
ucYear++;
if(ucYear > 99)
{
ucYear = 99;
}
ucWd1Part1Update = 1; //更新显示
break;
case 2: //月
ucMonth++;
if(ucMonth > 12)
{
ucMonth = 12;
}
ucWd1Part2Update = 1; //更新显示
break;
case 3: //日
ucDate++;
if(ucDate > 31)
{
ucDate = 31;
}
ucWd1Part3Update = 1; //更新显示
break;
}
break;
case 2:
switch(ucPart) //在不同的局部变量下,相当于二级菜单
{
case 1: //时
ucHour++;
if(ucHour > 23)
{
ucHour = 23;
}
ucWd2Part1Update = 1; //更新显示
break;
case 2: //分
ucMinute++;
if(ucMinute > 59)
{
ucMinute = 59;
}
ucWd2Part2Update = 1; //更新显示
break;
case 3: //秒
ucSecond++;
if(ucSecond > 59)
{
ucSecond = 59;
}
ucWd2Part3Update = 1; //更新显示
break;
}
break;
}
ucVoiceLock = 1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
uiVoiceCnt = const_voice_short; //按键声音触发,滴一声就停。
ucVoiceLock = 0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
ucKeySec = 0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 2:// 减按键 对应朱兆祺学习板的S5键
switch(ucWd) //在不同的窗口下,设置不同的参数
{
case 1:
switch(ucPart) //在不同的局部变量下,相当于二级菜单
{
case 1: //年
ucYear--;
if(ucYear > 99)
{
ucYear = 0;
}
ucWd1Part1Update = 1; //更新显示
break;
case 2: //月
ucMonth--;
if(ucMonth < 1)
{
ucMonth = 1;
}
ucWd1Part2Update = 1; //更新显示
break;
case 3: //日
ucDate--;
if(ucDate < 1)
{
ucDate = 1;
}
ucWd1Part3Update = 1; //更新显示
break;
}
break;
case 2:
switch(ucPart) //在不同的局部变量下,相当于二级菜单
{
case 1: //时
ucHour--;
if(ucHour > 23)
{
ucHour = 0;
}
ucWd2Part1Update = 1; //更新显示
break;
case 2: //分
ucMinute--;
if(ucMinute > 59)
{
ucMinute = 0;
}
ucWd2Part2Update = 1; //更新显示
break;
case 3: //秒
ucSecond--;
if(ucSecond > 59)
{
ucSecond = 0;
}
ucWd2Part3Update = 1; //更新显示
break;
}
break;
}
ucVoiceLock = 1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
uiVoiceCnt = const_voice_short; //按键声音触发,滴一声就停。
ucVoiceLock = 0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
ucKeySec = 0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 3://短按设置按键 对应朱兆祺学习板的S9键
switch(ucWd) //在不同的窗口下,设置不同的参数
{
case 1:
ucPart++;
if(ucPart > 3)
{
ucPart = 1;
ucWd = 2; //切换到第二个窗口,设置时分秒
ucWd2Update = 1; //窗口2更新显示
}
ucWd1Update = 1; //窗口1更新显示
break;
case 2:
if(ucPart > 0) //在窗口2的时候,要第一次激活设置时间,必须是长按3秒才可以,这里短按激活不了第一次
{
ucPart++;
if(ucPart > 3) //设置时间结束
{
ucPart = 0;
/* 注释五:
* 每个月份的天数最大值是不一样的,在写入ds1302时钟芯片内部数据前,应该做一次调整。
* 有的月份最大28天,有的月份最大29天,有的月份最大30天,有的月份最大31天,
*/
ucDate = date_adjust(ucYear, ucMonth, ucDate); //日调整 避免日的数值在某个月份超范围
ucYearBCD = number_to_bcd(ucYear); //原始数值转BCD
ucMonthBCD = number_to_bcd(ucMonth); //原始数值转BCD
ucDateBCD = number_to_bcd(ucDate); //原始数值转BCD
ucHourBCD = number_to_bcd(ucHour); //原始数值转BCD
ucMinuteBCD = number_to_bcd(ucMinute); //原始数值转BCD
ucSecondBCD = number_to_bcd(ucSecond); //原始数值转BCD
Write1302 (WRITE_PROTECT, 0X00); //禁止写保护
Write1302 (WRITE_YEAR, ucYearBCD); //年修改
Write1302 (WRITE_MONTH, ucMonthBCD); //月修改
Write1302 (WRITE_DATE, ucDateBCD); //日修改
Write1302 (WRITE_HOUR, ucHourBCD); //小时修改
Write1302 (WRITE_MINUTE, ucMinuteBCD); //分钟修改
Write1302 (WRITE_SECOND, ucSecondBCD); //秒位修改
Write1302 (WRITE_PROTECT, 0x80); //允许写保护
}
ucWd2Update = 1; //窗口2更新显示
}
break;
}
ucVoiceLock = 1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
uiVoiceCnt = const_voice_short; //按键声音触发,滴一声就停。
ucVoiceLock = 0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
ucKeySec = 0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 17://长按3秒设置按键 对应朱兆祺学习板的S9键
switch(ucWd) //在不同的窗口下,设置不同的参数
{
case 2:
if(ucPart == 0) //处于非设置时间的状态下,要第一次激活设置时间,必须是长按3秒才可以
{
ucWd = 1;
ucPart = 1; //进入到设置日期的状态下
ucWd1Update = 1; //窗口1更新显示
}
break;
}
ucVoiceLock = 1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
uiVoiceCnt = const_voice_short; //按键声音触发,滴一声就停。
ucVoiceLock = 0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
ucKeySec = 0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
}
/* 注释六:
* 注意,此处就是第一次出现的电平按键程序,跟以往的下降沿按键不一样。
* ucKey4Sr是经过软件滤波处理后,直接反应IO口电平状态的变量.当电平发生
* 变化时,就会切换到不同的显示界面,这里多用了一个ucKey4SrRecord变量
* 记录上一次的电平状态,是为了避免一直刷新显示。
*/
if(ucKey4Sr != ucKey4SrRecord) //说明S13的切换按键电平状态发生变化
{
ucKey4SrRecord = ucKey4Sr; //及时记录当前最新的按键电平状态 避免一直进来触发
if(ucKey4Sr == 1) //松手后切换到显示时间的窗口
{
ucWd = 2; //显示时分秒的窗口
ucPart = 0; //进入到非设置时间的状态下
ucWd2Update = 1; //窗口2更新显示
}
else //按下去切换到显示日期的窗口
{
ucWd = 1; //显示年月日的窗口
ucPart = 0; //进入到非设置时间的状态下
ucWd1Update = 1; //窗口1更新显示
}
}
}
void display_drive(void)
{
//以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
switch(ucDisplayDriveStep)
{
case 1: //显示第1位
ucDigShowTemp = dig_table[ucDigShow1];
if(ucDigDot1 == 1)
{
ucDigShowTemp = ucDigShowTemp | 0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp, 0xfe);
break;
case 2: //显示第2位
ucDigShowTemp = dig_table[ucDigShow2];
if(ucDigDot2 == 1)
{
ucDigShowTemp = ucDigShowTemp | 0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp, 0xfd);
break;
case 3: //显示第3位
ucDigShowTemp = dig_table[ucDigShow3];
if(ucDigDot3 == 1)
{
ucDigShowTemp = ucDigShowTemp | 0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp, 0xfb);
break;
case 4: //显示第4位
ucDigShowTemp = dig_table[ucDigShow4];
if(ucDigDot4 == 1)
{
ucDigShowTemp = ucDigShowTemp | 0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp, 0xf7);
break;
case 5: //显示第5位
ucDigShowTemp = dig_table[ucDigShow5];
if(ucDigDot5 == 1)
{
ucDigShowTemp = ucDigShowTemp | 0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp, 0xef);
break;
case 6: //显示第6位
ucDigShowTemp = dig_table[ucDigShow6];
if(ucDigDot6 == 1)
{
ucDigShowTemp = ucDigShowTemp | 0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp, 0xdf);
break;
case 7: //显示第7位
ucDigShowTemp = dig_table[ucDigShow7];
if(ucDigDot7 == 1)
{
ucDigShowTemp = ucDigShowTemp | 0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp, 0xbf);
break;
case 8: //显示第8位
ucDigShowTemp = dig_table[ucDigShow8];
if(ucDigDot8 == 1)
{
ucDigShowTemp = ucDigShowTemp | 0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp, 0x7f);
break;
}
ucDisplayDriveStep++;
if(ucDisplayDriveStep > 8) //扫描完8个数码管后,重新从第一个开始扫描
{
ucDisplayDriveStep = 1;
}
}
//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09, unsigned char ucDigStatusTemp08_01)
{
unsigned char i;
unsigned char ucTempData;
dig_hc595_sh_dr = 0;
dig_hc595_st_dr = 0;
ucTempData = ucDigStatusTemp16_09; //先送高8位
for(i = 0; i < 8; i++)
{
if(ucTempData >= 0x80)dig_hc595_ds_dr = 1;
else dig_hc595_ds_dr = 0;
dig_hc595_sh_dr = 0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
dig_hc595_sh_dr = 1;
delay_short(1);
ucTempData = ucTempData << 1;
}
ucTempData = ucDigStatusTemp08_01; //再先送低8位
for(i = 0; i < 8; i++)
{
if(ucTempData >= 0x80)dig_hc595_ds_dr = 1;
else dig_hc595_ds_dr = 0;
dig_hc595_sh_dr = 0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
dig_hc595_sh_dr = 1;
delay_short(1);
ucTempData = ucTempData << 1;
}
dig_hc595_st_dr = 0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(1);
dig_hc595_st_dr = 1;
delay_short(1);
dig_hc595_sh_dr = 0; //拉低,抗干扰就增强
dig_hc595_st_dr = 0;
dig_hc595_ds_dr = 0;
}
//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09, unsigned char ucLedStatusTemp08_01)
{
unsigned char i;
unsigned char ucTempData;
hc595_sh_dr = 0;
hc595_st_dr = 0;
ucTempData = ucLedStatusTemp16_09; //先送高8位
for(i = 0; i < 8; i++)
{
if(ucTempData >= 0x80)hc595_ds_dr = 1;
else hc595_ds_dr = 0;
hc595_sh_dr = 0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
hc595_sh_dr = 1;
delay_short(1);
ucTempData = ucTempData << 1;
}
ucTempData = ucLedStatusTemp08_01; //再先送低8位
for(i = 0; i < 8; i++)
{
if(ucTempData >= 0x80)hc595_ds_dr = 1;
else hc595_ds_dr = 0;
hc595_sh_dr = 0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
hc595_sh_dr = 1;
delay_short(1);
ucTempData = ucTempData << 1;
}
hc595_st_dr = 0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(1);
hc595_st_dr = 1;
delay_short(1);
hc595_sh_dr = 0; //拉低,抗干扰就增强
hc595_st_dr = 0;
hc595_ds_dr = 0;
}
void T0_time(void) interrupt 1 //定时中断
{
TF0 = 0; //清除中断标志
TR0 = 0; //关中断
if(ucVoiceLock == 0) //原子锁判断
{
if(uiVoiceCnt != 0)
{
uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
beep_dr = 0; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
}
else
{
; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
beep_dr = 1; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
}
}
if(ucDs1302Error > 0) //EEPROM出错
{
if(ucDs1302Lock == 0) //原子锁判断
{
uiDs1302Cnt++; //间歇性蜂鸣器报警的计时器
}
}
if(ucDpyTimeLock == 0) //原子锁判断
{
uiDpyTimeCnt++; //数码管的闪烁计时器
}
key_scan(); //按键扫描函数
display_drive(); //数码管字模的驱动函数
TH0 = 0xfe; //重装初始值(65535-500)=65035=0xfe0b
TL0 = 0x0b;
TR0 = 1; //开中断
}
void delay_short(unsigned int uiDelayShort)
{
unsigned int i;
for(i = 0; i < uiDelayShort; i++)
{
; //一个分号相当于执行一条空语句
}
}
void delay_long(unsigned int uiDelayLong)
{
unsigned int i;
unsigned int j;
for(i = 0; i < uiDelayLong; i++)
{
for(j = 0; j < 500; j++) //内嵌循环的空指令数量
{
; //一个分号相当于执行一条空语句
}
}
}
void initial_myself(void) //第一区 初始化单片机
{
key_gnd_dr = 0; //模拟独立按键的地GND,因此必须一直输出低电平
beep_dr = 1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
hc595_drive(0x00, 0x00); //关闭所有经过另外两个74HC595驱动的LED灯
TMOD = 0x01; //设置定时器0为工作方式1
TH0 = 0xfe; //重装初始值(65535-500)=65035=0xfe0b
TL0 = 0x0b;
}
void initial_peripheral(void) //第二区 初始化外围
{
ucDigDot8 = 0; //小数点全部不显示
ucDigDot7 = 0;
ucDigDot6 = 0;
ucDigDot5 = 0;
ucDigDot4 = 0;
ucDigDot3 = 0;
ucDigDot2 = 0;
ucDigDot1 = 0;
EA = 1; //开总中断
ET0 = 1; //允许定时中断
TR0 = 1; //启动定时中断
/* 注释七:
* 检查ds1302芯片的备用电池电量是否用完了。
* 在上电的时候,在一个特定的地址里把数据读出来,如果发现不等于0x5a,
* 则是因为备用电池电量用完了,导致保存在ds1302内部RAM数据区的数据被更改,
* 与此同时,应该重新写入一次0x5a,为下一次通电判断做准备
*/
ucCheckDs1302 = Read1302(READ_CHECK); //判断ds1302内部的数据是否被更改
if(ucCheckDs1302 != 0x5a)
{
Write1302 (WRITE_PROTECT, 0X00); //禁止写保护
Write1302 (WRITE_CHECK, 0x5a); //重新写入标志数据,方便下一次更换新电池后的判断
Write1302 (WRITE_PROTECT, 0x80); //允许写保护
ucDs1302Error = 1; //表示ds1302备用电池没电了,报警提示更换新电池
}
}
总结陈词:
下一节开始讲单片机驱动温度传感器芯片的内容,欲知详情,请听下回分解-----利用DS18B20做一个温控器。
(未完待续,下节更精彩,不要走开哦)
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。