本次实验设计多功能低功耗手表,能够完成校时、计时、设置闹钟等功能,并且能够实时检测环境的温湿度。
首先会从DS1302中读取时间与初始化时间进行比较,若符合条件则无须再进行设置。直接在LCD屏幕上显示当前时间。
按键说明:
| 按键 | 初始菜单 | 设置菜单 | 校时菜单 | 计数菜单 | 闹钟设置 |
|---|---|---|---|---|---|
| SW0 | 闹钟工作时关闭 | - | 加 | 归零 | 加 |
| SW1 | - | - | 减 | - | 减 |
| SW2 | 按住查看闹钟时间 | 选择 | 选择 | 计数暂停/开始 | 选择 |
| SW3 | 进入设置菜单 | 确定 | 确定并返回初态 | 回到初始菜单 | 确定并返回 |
打开电源后进入初始状态,此时在LCD屏幕上会显示当前的时间,包括年、月、日、时、分、秒,也会显示环境的温湿度。按住SW2可以查看当前的闹钟时间,初始为0点0分,按下SW3可以进入设置菜单。在设置菜单中,LCD下面两行会展示选项,当前选项会闪烁,按SW2可以在选项间跳转,按SW3可以进入当前选项的下一级界面。
若在校时菜单,同样的当前选项会闪烁,按SW0向上调整,按SW1向下调整,年份的调整区间为200~2099,调整秒时无论按SW0还是SW1都会清零,按住SW0或SW1可连续调整。
若在计数菜单,初始为00:00:00,表示时、分、秒,其中小时的范围为0~255。按SW2可以开始或暂停计数,按SW0会使计数值归零并停止计数,按SW3则会回到初始菜单。若在计数时返回初始菜单,则计数仍会进行,暂停的计数结果保留。
若在闹钟设置菜单,开始会显示当前闹钟,若未进行过设置,则为00:00,表示时、分。按SW0,向上调整;按SW1,向下调整;按SW2在时和分之间进行选择;按SW3则会保存当前的闹钟时间并返回初始菜单。若要查看闹钟时间,在初始菜单按住SW2即可。当时间与设置的闹钟时间相等时,蜂鸣器会响,直到时间不再与闹钟时间相等,按SW0可以暂时关闭闹钟。
本次实验设计了一个多功能手表,利用教学实验箱(皮赛电子 stm32f407tg)上的DS1302芯片存储时间,DS1302能够存储年、月、日、周、时、分、秒,并且能够自动判别闰年闰月,针对不同月份有不同的天数。实验以课程中使用的DS1302历程为基础,辅以定时器TIM、按键KEY、蜂鸣器BEEP、液晶显示屏LCD,还外接了温湿度传感器DHT11。下面主要对DS1302和DHT11进行介绍。
DS1302芯片包含一个实时时钟/日历和31字节的静态RAM。它通过一个简单的串行接口与微处理器通信。 实时时钟/日历提供秒、分钟、小时、日、日、月和年信息。 月末的日期会自动调整天数少于31天的月份,包括闰年的更正。时钟运行在24小时或12小时的格式与AM/PM指示器使用同步串行通信简化了DS1302与微处理器的接口。 只需要三根电线与时钟/RAM: CE通信 I/O(数据线) SCLK(串行时钟) DS1302被设计成在非常低的功率下工作,并且以低于1uW的速度保留数据和时钟信息。
DHT11 是一款湿温度一体化的数字传感器。该传感器包括一个电阻式测湿元件和一个 NTC测温元件,并与一个高性能 8 位单片机相连接。通过单片机等微处理器简单的电路连接就能够实时的采集本地湿度和温度。DHT11 与单片机之间能采用简单的单总线进行通信(使用
DHT11 数字湿温度传感器采用单总线数据格式。即,单个数据引脚端口完成输入输出双向传输。其数据包由 5Byte(40Bit)组成。数据分小数部分和整数部分,一次完整的数据传输为40bit,高位先出。DHT11 的数据格式为:8bit 湿度整数数据+8bit 湿度小数数据+8bit 温度整数数据+8bit 温度小数数据+8bit 校验和。其中校验和数据为前四个字节相加。
传感器数据输出的是未编码的二进制数据。数据(湿度、温度、整数、小数)之间应该分开
处理。例如,某次从 DHT11 读到的数据如图1所示:

由以上数据就可得到湿度和温度的值,计算方法: 湿度= byte4 . byte3=45.0 (%RH) 温度= byte2 . byte1=28.0 ( °C) 校验= byte4+ byte3+ byte2+ byte1=73(=湿度+温度)(校验正确) 可以看出,DHT11 的数据格式是十分简单的,DHT11 和 MCU 的一次通信最大为 3ms 左右,建议主机连续读取时间间隔不要小于 100ms。

首先主机发送开始信号,即:拉低数据线,保持 t1(至少 18ms)时间,然后拉高数据线 t2(20~40us)时间,然后读取 DHT11 的响应,正常的话,DHT11 会拉低数据线,保持 t3(40~50us)时间,作为响应信号,然后 DHT11 拉高数据线,保持 t4(40~50us)时间后,开始输出数据。 DHT11 输出数字‘0’的时序如图3:

DHT11 输出数字‘1’的时序如图4:

教学所用试验箱没有DHT11芯片,所以需要外接。本次使用的DHT11有3个引脚,其中GND与电源引脚直接接在电路扩展模块的地和电源上。而data引脚可以用杜邦线接在DS18B20的temp引脚上。
DHT11配置的头文件dht11.h如下:
x123
4567
8//将IO_DHT11定义为PG1491011
121314
15void DHT11_IO_OUT(void); //将PG14设置为输出模式,主机发送数据16void DHT11_IO_IN (void); //将PG14设置为输入模式,主机接收数据17void DHT11_Init(void); //DHT11初始化18void DHT11_Rst(void); //主机向从机发送信号19u8 DHT11_Read_Data(u8 *temp,u8 *humi); //主机读取DHT11芯片的数据20u8 DHT11_Read_Byte(void); //主机读取DHT11发送的字节21u8 DHT11_Read_Bit(void); //主机读取DHT11发送的位22u8 DHT11_Check(void); //检测从机时候存在或准备好发送数据23
24DHT11配置的源文件dht11.c如下:
14712
3
4//将PG14设置为输入模式,主机接收数据5void DHT11_IO_IN (void)6{7 GPIO_InitTypeDef GPIO_InitStructure;8 9 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE);10 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;11 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;12 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;13 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;14 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;15 GPIO_Init(GPIOG, &GPIO_InitStructure);16 17 GPIO_SetBits(GPIOG,GPIO_Pin_14);18 19}20
21//将PG14设置为输出模式,主机发送数据22void DHT11_IO_OUT (void)23{24 GPIO_InitTypeDef GPIO_InitStructure;25 26 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE);27 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;28 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;29 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;30 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;31 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;32 GPIO_Init(GPIOG, &GPIO_InitStructure);33 34 GPIO_SetBits(GPIOG,GPIO_Pin_14);35
36}37
38void DHT11_Rst(void)39{40 DHT11_IO_OUT(); //SET OUTPUT41 DHT11_DQ_Low; //拉低 DQ42 delay_ms(20); //拉低至少 18ms43 DHT11_DQ_High; //DQ=144 delay_us(30); //主机拉高 20~40us45
46}47
48
49//等待 DHT11 的回应50//返回 1:未检测到 DHT11 的存在51//返回 0:存在52u8 DHT11_Check(void)53{54 u8 retry=0;55 DHT11_IO_IN(); //SET INPUT56 57 while ((GPIO_ReadInputDataBit(GPIO_DHT11,IO_DHT11)==1)&&retry<100) //DHT11 会拉低 40~80us58 {59 retry++;60 delay_us(1);61 };62 63 if(retry>=100)64 return 1;65 else retry=0;66 67 while ((GPIO_ReadInputDataBit(GPIO_DHT11,IO_DHT11)==0)&&retry<100) //DHT11 拉低后会再次拉高 40~80us68 {69 retry++;70 delay_us(1);71 }; 72 if(retry>=100)73 return 1;74 return 0;75 76}77
78
79//从 DHT11 读取一个位80//返回值:1/081u8 DHT11_Read_Bit(void)82{83 u8 retry=0;84 while ((GPIO_ReadInputDataBit(GPIO_DHT11,IO_DHT11)==1)&&retry<100) //等待变为低电平85 { 86 retry++;87 delay_us(1);88 };89 retry=0;90 while ((GPIO_ReadInputDataBit(GPIO_DHT11,IO_DHT11)==0)&&retry<100) //等待变为高电平91 {92 retry++;93 delay_us(1);94 }; 95 delay_us(50);96 if(GPIO_ReadInputDataBit(GPIO_DHT11,IO_DHT11)==1)97 return 1;98 else 99 return 0;100
101}102
103//从 DHT11 读取一个字节104//返回值:读到的数据105u8 DHT11_Read_Byte(void)106{107 u8 i,dat;108 dat=0;109 for (i=0;i<8;i++)110 {111 dat<<=1;112 dat|=DHT11_Read_Bit();113 }114 return dat;115}116
117//从 DHT11 读取一次数据118//temp:温度值(范围:0~50°)119//humi:湿度值(范围:20%~90%)120//返回值:0,正常;1,读取失败121u8 DHT11_Read_Data(u8 *temp,u8 *humi)122{123 u8 buf[5];124 u8 i;125 DHT11_Rst();126 if(DHT11_Check()==0)127 {128 for (i=0;i<5;i++) //读取 40 位数据129 {130 buf[i]=DHT11_Read_Byte(); 131 }132 if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4])133 {134 *temp=buf[2];135 *humi=buf[0];136 } 137 }138 else return 1;139 return 0;140}141
142//初始化 DHT11 的 IO 口 143void DHT11_Init(void)144{145 DHT11_Rst();146 DHT11_Check();147}主要的工作流程:
主函数如下:
821int main(void)2{ 3 u8 t=0,wd=0,sd=0; 4 u8 str_w[10];5 u8 str_s[10];6 u8 time[15]; 7 //u8 i=0;8 //u8 j=0;9 u8 key;10 11 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组212 delay_init(168); //初始化延时函数13 //pcb_Init(); //初始化LCD1286414 LCD_GPIO_Init();15 LCD_Init();16 KEY_Init(); //初始化按键17 delay_ms(20);18 lcd_clear(); 19// LCD_Clear();20 DS1302_Init();21 BEEP_Init(); //初始化蜂鸣器22 //LED_Init(); 23 DHT11_Init();24 TIM3_Init(42000-1,1000-1); //初始化定时器,频率为1Hz,并且关闭更新事件25 count_s_s=0;26 delay_ms(100);27 DS1302_Read_Time();28 for(i=1;i<8;i++){ //读取芯片时间,若出现异常则恢复出厂时间29 if(time_buf[i]>time_init[i]) break;30 else if(time_buf[i]==time_init[i]);31 else DS1302_Write_Time(time_init);32 }33// LCD_Display_Words(2,5,"秒");34 while(1)35 {36 37 key=KEY_Scan(0); 38 if(key==KEY3){ //进入设置菜单39 set_mode();40 }41 42 else if(key==KEY2){ //展示设置的闹钟43 lcd_clear();44 delay_ms(10);45 lcd_pos(3,3);46 //展示闹钟的时47 Lcd_WriteData(0x30+(alarm_time[0]>>4)); 48 Lcd_WriteData(0x30+(alarm_time[0]&0x0f)); 49 Lcd_WriteData(0x3A);50 //展示闹钟的分51 Lcd_WriteData(0x30+(alarm_time[1]>>4)); 52 Lcd_WriteData(0x30+(alarm_time[1]&0x0f)); 53 while(key==KEY2); //按住SW2会一直显示,松开则回到初始界面54 delay_ms(10);55 lcd_clear();56 }57 58 else if(key==KEY0){ //若蜂鸣器正在工作,关闭蜂鸣器59 if((alarm_time[0]==time_buf[4])&&(alarm_time[1]==time_buf[5]))60 alarm_time[2]=0x0;61 }62 63 64 65 display_time(); //展示时间66 67 DHT11_Read_Data(&wd,&sd); //读取温湿度度传感器的数据68 69 //将读到的数据转换为字符串70 int_to_string(wd,str_w);71 int_to_string(sd,str_s);72 73 //在LCD上展示当前环境的温度与湿度74 LCD_Display_Words(2,0,"温度:");75 LCD_Display_Words(2,3,str_w);76 LCD_Display_Words(2,4,"`C");77 LCD_Display_Words(3,0,"湿度:");78 LCD_Display_Words(3,3,str_s);79 LCD_Display_Words(3,4,"%");80 EXTI0_IRQHandler(); //检查是否满足闹钟条件,控制蜂鸣器81 }82}校时代码如下:
1441void correct_time(){ //校正时间2 u8 i=1;3 u8 key;4 lcd_clear();5 while(1){6 key=KEY_Scan(0);7 8 display_time();9 10 delay_ms(200);11 12 if(key==KEY2){ //在菜单上选择13 if(i==6) i=1;14 else i++;15 }16 else if(key==KEY3){17 return;18 }19 else if(key==KEY0){ //向上调整20 //调整规范化21 switch(i){22 case 1: if(time_buf[i]==0x99) time_buf[i]=0x0;23 else if((time_buf[i]&0x0f)==0x09) time_buf[i]=time_buf[i]+0x10-0x09;24 else time_buf[i]++;25 break;26 case 2: if(time_buf[i]==0x12) time_buf[i]=0x1;27 else if((time_buf[i]&0x0f)==0x09) time_buf[i]=time_buf[i]+0x10-0x09;28 else time_buf[i]++;29 break;30 case 3: 31 32 switch(time_buf[2]){33 case 2:34 if((time_buf[1] ==0)||((((time_buf[1]>>4)&0x0f)*10 + (time_buf[1]&0x0f))%4==0)){35 if(time_buf[i]>=0x29) time_buf[i]=0x1;36 else if((time_buf[i]&0x0f)==0x09) time_buf[i]=time_buf[i]+0x10-0x09;37 else time_buf[i]++;38 }39 else {40 if(time_buf[i] >= 0x28) time_buf[i]=0x1;41 else if((time_buf[i]&0x0f)>=0x09) time_buf[i]=time_buf[i]+0x10-0x09;42 else time_buf[i]++;43 }44 break;45 case 0x1:46 case 0x3:47 case 0x5:48 case 0x7:49 case 0x8:50 case 0x10:51 case 0x12:52 if(time_buf[i]>=0x31) time_buf[i]=0x1;53 else if((time_buf[i]&0x0f)>=0x09) time_buf[i]=time_buf[i]+0x10-0x09;54 else time_buf[i]++;55 break;56 default:57 if(time_buf[i]>=0x30) time_buf[i]=0x1;58 else if((time_buf[i]&0x0f)>=0x09) time_buf[i]=time_buf[i]+0x10-0x09;59 else time_buf[i]++;60 break;61 }62 break;63 64 case 4:if(time_buf[i]>=0x23) time_buf[i]=0x0;65 else if((time_buf[i]&0x0f)>=0x09) time_buf[i]=time_buf[i]+0x10-0x09;66 else time_buf[i]++;67 break;68 case 5:if(time_buf[i]>=0x59) time_buf[i]=0x0;69 else if((time_buf[i]&0x0f)>=0x09) time_buf[i]=time_buf[i]+0x10-0x09;70 else time_buf[i]++;71 break;72 case 6:time_buf[6]=0;73 default:break;74 }75 }76 else if(key==KEY1){ //向下调整77 switch(i){78 case 1: if(time_buf[i]==0x0) time_buf[i]=0x99;79 else if((time_buf[i]&0x0f)==0x0) time_buf[i]=time_buf[i]-0x10+0x09;80 else time_buf[i]--;81 break;82 case 2: if(time_buf[i]<=0x1) time_buf[i]=0x12;83 else if((time_buf[i]&0x0f)==0x0) time_buf[i]=time_buf[i]-0x10+0x09;84 else time_buf[i]--;85 break;86 case 3: 87 switch(time_buf[2]){88 case 2:89 if((time_buf[1] ==0)||((((time_buf[1]>>4)&0x0f)*10 + (time_buf[1]&0x0f))%4==0)){90 if(time_buf[i]<=0x1) time_buf[i]=0x29;91 else if((time_buf[i]&0x0f)==0x0) time_buf[i]=time_buf[i]-0x10+0x09;92 else time_buf[i]--;93 }94 else {95 if(time_buf[i] <= 0x1) time_buf[i]=0x28;96 else if((time_buf[i]&0x0f)==0x0) time_buf[i]=time_buf[i]-0x10+0x09;97 else time_buf[i]--;98 }99 break;100 case 0x1:101 case 0x3:102 case 0x5:103 case 0x7:104 case 0x8:105 case 0x10:106 case 0x12:107 if(time_buf[i]<=0x1) time_buf[i]=0x31;108 else if((time_buf[i]&0x0f)==0x0) time_buf[i]=time_buf[i]-0x10+0x09;109 else time_buf[i]--;110 break;111 default:112 if(time_buf[i]<=0x1) time_buf[i]=0x30;113 else if((time_buf[i]&0x0f)==0x0) time_buf[i]=time_buf[i]-0x10+0x09;114 else time_buf[i]--;115 break;116 }117 break;118 case 4:if(time_buf[i]<=0x0) time_buf[i]=0x23;119 else if((time_buf[i]&0x0f)==0x0) time_buf[i]=time_buf[i]-0x10+0x09;120 else time_buf[i]--;121 break;122 case 5:if(time_buf[i]<=0x0) time_buf[i]=0x59;123 else if((time_buf[i]&0x0f)==0x0) time_buf[i]=time_buf[i]-0x10+0x09;124 else time_buf[i]--;125 break;126 case 6:time_buf[6]=0;127 default:break;128 }129 }130 131 DS1302_Write_Time(time_buf);132 switch(i){ //实现闪烁效果133 case 1:LCD_Display_Words(0,0," ");break;134 case 2:LCD_Display_Words(0,3," ");break;135 case 3:LCD_Display_Words(0,5," ");break;136 case 4:LCD_Display_Words(1,0," ");break;137 case 5:LCD_Display_Words(1,2," ");break;138 case 6:LCD_Display_Words(1,4," ");break;139 default:break;140 }141 delay_ms(10);142 }143 144}
计数模块代码如下:
471void count_time(void){ //计数2 u8 i=1;3 u8 key;4 5 //TIM_ITConfig(TIM3,TIM_IT_Update,DISABLE);6 lcd_clear();7 delay_ms(10);8 while(1){9 key=KEY_Scan(0);10 11 12 lcd_pos(3,3);13 Lcd_WriteData(0x30+(count[0]>>4)); 14 Lcd_WriteData(0x30+(count[0]&0x0f)); 15 Lcd_WriteData(0x3A); 16 17 Lcd_WriteData(0x30+(count[1]>>4)); 18 Lcd_WriteData(0x30+(count[1]&0x0f)); 19 Lcd_WriteData(0x3A); 20 21 Lcd_WriteData(0x30+(count[2]>>4)); 22 Lcd_WriteData(0x30+(count[2]&0x0f));23 delay_ms(500);24 if(key==KEY0){ //计数归零并停止计数25 count[0]=0x0;26 count[1]=0x0;27 count[2]=0x0;28 TIM_ITConfig(TIM3,TIM_IT_Update,DISABLE); //停止计数29 count_s_s =0;30 delay_ms(10);31 }32 else if(key==KEY2){33 if(count_s_s==1){34 TIM_ITConfig(TIM3,TIM_IT_Update,DISABLE); //停止计数35 delay_ms(10);36 count_s_s=0;37 }38 else{39 TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //继续\开始计数40 delay_ms(10);41 count_s_s=1;42 }43 }44 else if(key==KEY3)45 return;46 }47}闹钟模块代码如下:
581void alarm_clock(void){ //闹钟设置2 u8 i=0;3 u8 key;4 //delay_ms(10);5 lcd_clear();6 delay_ms(10); //清屏7 while(1){8 key=KEY_Scan(0);9// lcd_clear();10 lcd_pos(2,2);11 //展示时12 Lcd_WriteData(0x30+(alarm_time[0]>>4)); 13 Lcd_WriteData(0x30+(alarm_time[0]&0x0f)); 14 LCD_Display_Words(1,3,"时"); 15 //展示分16 Lcd_WriteData(0x30+(alarm_time[1]>>4));17 Lcd_WriteData(0x30+(alarm_time[1]&0x0f)); 18 LCD_Display_Words(1,5,"分"); 19 delay_ms(200);20 if(key==KEY2){ //在时和分之间选择21 if(i==1) i=0;22 else i=1;23 }24 else if(key==KEY3) //返回初始状态25 return;26 else if(key==KEY0) //向上调整27 switch(i){28 case 0:if(alarm_time[i]>=0x23) alarm_time[i]=0x0;29 else if((alarm_time[i]&0x0f)>=0x09) alarm_time[i]=alarm_time[i]+0x10-0x09;30 else alarm_time[i]++;31 break;32 case 1:if(alarm_time[i]==0x59) alarm_time[i]=0x0;33 else if((alarm_time[i]&0x0f)>=0x09) alarm_time[i]=alarm_time[i]+0x10-0x09;34 else alarm_time[i]++;35 break;36 default:break;37 }38 else if(key==KEY1){ //向下调整39 switch(i){40 case 0:if(alarm_time[i]==0x0) alarm_time[i]=0x23;41 else if((alarm_time[i]&0x0f)==0x0) alarm_time[i]=alarm_time[i]-0x10+0x09;42 else alarm_time[i]--;43 break;44 case 1:if(alarm_time[i]==0x0) alarm_time[i]=0x59;45 else if((alarm_time[i]&0x0f)==0x0) alarm_time[i]=alarm_time[i]-0x10+0x09;46 else alarm_time[i]--;47 break;48 default:break;49 }50 }51 switch(i){52 case 0:LCD_Display_Words(1,2," ");break;53 case 1:LCD_Display_Words(1,4," ");break;54 default:break;55 }56 delay_ms(200);57 }58}
