资源地址: ESP32+TM1638网络校时闹钟ARDUINO代码
? ? ? ? 之前买了块TM1638驱动的LED数码管显示模块,加上手头有块ESP32最小系统板,找根五芯线连接起来,趁这几天天气炎热,窝在家没事做,搞个带NTP网络校时功能的简单闹钟,就当没事练练手。
一、硬件配置。
? ? ? ? 下面先上图,左图是硬件连接和实验效果,除电源外,TM1638的三根信号线STB、CLK、DIO分别连接ESP32模块的GPIO14、GPIO26、GPIO32。右图则展示了功能设计方案。
???????
二、功能设计。
1、首先,本实验最重要的是时钟计时功能。要求在网络断开情况下可以正常计时,自由调校。而在网络连接情况下,可以通过NTP时间服务器进行校准。
2、充分利用TM1638显示模块上的资源,提供日期、时间、星期显示功能和时间、闹钟设置功能。
? ? ? ? 最上边的LED分别指示星期日到星期六,最后一只(右边)LED作为闹钟指示;8只数码管可以分时显示日期和时间;最下边的8个按键为功能键。
? ? ? ? 按“模式”键,8只数码管分别显示日期或时间;按“设置”键(与“模式”键配合),可以分别调整时间的“时”、“分”、“秒”或者调整日期的“年”、“月”、“日”(调整项会闪烁);“减键”和“加键”分别对调整的项目进行加减操作,并支持长按快速调整模式。“定闹”按键可以对当前时间设置/取消闹钟,而“清闹”按键会清除全部闹钟设置。按“配网”键,ESP32板载LED将闪烁,此时需打开微信公众号“安信可科技”进行配网,WIFI连接成功,ESP32板载LED将常亮(失败将熄灭)。按“同步”键,将自动连接NTP服务器进行时间校准。
3、关于闹钟,是本实验的一个附属功能,因为只是实验目的,功能相对简单。程序里用一个字符串保存闹钟数据(对定闹次数没有限制),但没有设计断电保存功能。定闹数据只保存“小时”和“分种”数据,不保存“秒钟”数据,故每个闹钟输出时长为一分钟,而输出也只是简单地通过一只LED进行显示,没有更进一步的音乐或电器控制设计,以避免系统复杂化。
三、软件环境。
? ? ? ? 本实验使用ARDUINO集成开发环境,相关软件及ESP32编译环境请自行百度。这里提供一个比较实用的资源:ARDUINO编译ESP32开发环境文件(注意只是开发环境,不包括ARDUINO软件,主要解决无法下载的问题)。
? ? ? ? 下面是本实验的全部代码资源:ESP32+TM1638网络校时闹钟ARDUINO代码。
四、程序说明。
? ? ? ? 本程序使用ARDUINO软件编程,采用模块化设计,共四个文件:TM1638.C为显示驱动模块,提供LED灯珠、数码管、按键处理功能。TIMERS.C是时钟处理模块,用于设置和读取ESP32内部RTC时钟源,以及时区设置,NTP校时功能。ESP32OBJ.C则提供配网及WIFI连接功能。主程序NTP_TIME.INO整合各模块,进行初始化,并负责实现各设计功能。
? ? ? ? 下面贴出主程序NTP_TIME.INO代码:
#include <Arduino.h>
#include <WiFi.h>
#include "ESP32OBJ.C"
#include "TM1638.C"
#include "TIMERS.C"
ESP32OBJ esp32;
TM1638 tm1638;
USER_TIMERS timers;
uint8_t days[12]={31,28,31,30,31,30,31,31,30,31,30,31};
String dingShi="";//形如:"0000,1200,1530,2300";/*四个数字字符,前两个表示时,后两个表示分,定时之间用逗号隔开,默认没有定时*/
struct tm info;/*时间结构体*/
uint32_t keyUpTime=0;/*按键释放时间*/
uint32_t keyDownTime=0;/*按键按下时间*/
uint8_t dispMode=0;/*显示模式(0--显示时间;1--显示日期;2--显示星期)*/
uint8_t setMode=0;/*调整模式(0--正常状态;1--低位调整状态;2--中位调整状态;3--高位调整状态)*/
void setup ()
{
Serial.begin(115200);
esp32.begin();
timers.begin();
tm1638.begin(14,26,32);/*显示模块引脚设置*/
}
void loop(){
uint8_t key;uint8_t nowKey;
if(millis()-keyUpTime>10000){dispMode=0;setMode=0;}/*10秒未操作自动转入时间显示状态,并取消调整状态。*/
key=tm1638.key();
if(key!=8){keyDownTime=millis();}/*如按下按键则记录按键时的系统时间*/
if(dispMode!=0||setMode!=1){info=timers.getTime();}/*获取时间信息(校秒时不刷新)*/
keySet(key);
ScreenDisplay(false);
while(key!=8){
keyUpTime=millis();
nowKey=tm1638.key();
if(nowKey==8&&(key==1||setMode>0&&key>1&&key<4)){timers.setTime(info);break;}else{key=nowKey;}/*与调整有关的按键释放时更新时间,写入RTC*/
if(keyUpTime-keyDownTime>1500){delay(100);if(key==2||key==3){keySet(key);}}/*按键1.5秒未释放,则进行长按键(只支持"加键"和"减键")连续处理)*/
if(setMode==0){info=timers.getTime();}/*非调校状态即使按键未释放也会刷新时间*/
ScreenDisplay(true);/*按键未释放状态不会闪烁*/
;
}/*按键阻塞*/
}
void keySet(uint8_t key){
/*按键处理函数*/
switch(key){
case 0:/*模式键*/
dispMode=(dispMode+1)%2;
setMode=0;
break;
case 1:/*调整键*/
if(dispMode<2){setMode=(setMode+1)%4;}
break;
case 2:/*减号键*/
switch(setMode){
case 1:/*调整低位*/
switch(dispMode){
case 0:/*秒减一*/
if(info.tm_sec>0){info.tm_sec--;}else{info.tm_sec=59;}break;
case 1:/*日减一*/
if(info.tm_mday>1){info.tm_mday--;}else{info.tm_mday=days[info.tm_mon-1];if(info.tm_mon==2&&info.tm_year%4==0){info.tm_mday++;}}break;
}break;
case 2:/*调整中位*/
switch(dispMode){
case 0:/*分减一*/
if(info.tm_min>0){info.tm_min--;}else{info.tm_min=59;}break;
case 1:/*月减一*/
if(info.tm_mon>1){info.tm_mon--;}else{info.tm_mon=12;}if(info.tm_mday>days[info.tm_mon-1]){info.tm_mday=days[info.tm_mon-1];if(info.tm_mon==2&&info.tm_year%4==0){info.tm_mday++;}}break;
}break;
case 3:/*调整高位*/
switch(dispMode){
case 0:/*时减一*/
if(info.tm_hour>0){info.tm_hour--;}else{info.tm_hour=23;}break;
case 1:/*年减一*/
if(info.tm_year>1970){info.tm_year--;if(info.tm_year%4!=0&&info.tm_mon==2&&info.tm_mday>days[info.tm_mon-1]){info.tm_mday=days[info.tm_mon-1];}}break;
}break;
}
break;
case 3:/*加号键*/
switch(setMode){
case 1:/*调整低位*/
switch(dispMode){
case 0:/*秒加1*/
info.tm_sec=(info.tm_sec+1)%60;break;
case 1:/*日加1*/
info.tm_mday++;if(info.tm_mday>days[info.tm_mon-1]&&(info.tm_mon!=2||info.tm_year%4!=0||info.tm_mday>29)){info.tm_mday=1;}break;
}break;
case 2:/*调整中位*/
switch(dispMode){
case 0:/*分加一*/
info.tm_min=(info.tm_min+1)%60;break;
case 1:/*月加一*/
info.tm_mon=info.tm_mon%12+1;if(info.tm_mday>days[info.tm_mon-1]){info.tm_mday=days[info.tm_mon-1];if(info.tm_mon==2&&info.tm_year%4==0){info.tm_mday++;}}break;
}break;
case 3:/*调整高位*/
switch(dispMode){
case 0:/*时加一*/
info.tm_hour=(info.tm_hour+1)%24;break;
case 1:/*年加一*/
if(info.tm_year<2037){info.tm_year++;}if(info.tm_mon==2&&info.tm_year%4!=0&&info.tm_mday>days[info.tm_mon-1]){info.tm_mday=days[info.tm_mon-1];}break;
}break;
}
break;
case 4:/*响铃设置*/alarmClock(info.tm_hour,info.tm_min,1);break;
case 5:/*响铃清空*/dingShi="";break;
case 6:/*配网键*/esp32.smartConfig();break;
case 7:/*校时键*/timers.ntpUpdate();dispMode=0;setMode=0;break;
default:/*默认操作*/break;
}
}
void ScreenDisplay(boolean mode){
/*显示处理函数*/
uint16_t tem;uint8_t i;uint16_t temtime;
temtime=millis()%1000;
switch(dispMode){
case 1:/*显示日期*/
tem=info.tm_year;
for(i=0;i<4;i++){
if(setMode!=3||temtime<500||mode){tm1638.display(3-i,tem%10);tem=tem/10;}else{tm1638.display(3-i,-1);tem=tem/10;}
}
tem=info.tm_mon;
for(i=0;i<2;i++){
if(setMode!=2||temtime<500||mode){tm1638.display(5-i,tem%10);tem=tem/10;}else{tm1638.display(5-i,-1);tem=tem/10;}
}
tem=info.tm_mday;
for(i=0;i<2;i++){
if(setMode!=1||temtime<500||mode){ tm1638.display(7-i,tem%10);tem=tem/10;}else{tm1638.display(7-i,-1);tem=tem/10;}
}
break;
default:/*显示时间*/
tm1638.display(2,16);tm1638.display(5,16);/*显示横线.*/
tem=info.tm_hour;
if(setMode!=3||temtime<500||mode){tm1638.display(0,tem/10);tm1638.display(1,tem%10);}else{tm1638.display(0,-1);tm1638.display(1,-1);}
tem=info.tm_min;
if(setMode!=2||temtime<500||mode){tm1638.display(3,tem/10);tm1638.display(4,tem%10);}else{tm1638.display(3,-1);tm1638.display(4,-1);}
tem=info.tm_sec;
if(setMode!=1||temtime<500||mode){tm1638.display(6,tem/10);tm1638.display(7,tem%10);}else{tm1638.display(6,-1);tm1638.display(7,-1);}
break;
}
/*使用独立LED提示星期几,第一个LED表示星期日*/
for(i=0;i<7;i++){
if(info.tm_wday==i){tm1638.led(i,1);}else{tm1638.led(i,0);}
}
alarmClock(info.tm_hour,info.tm_min,0);
}
void alarmClock(uint16_t in_hour,uint16_t in_min,uint8_t in_mode){
//根据小时和分钟数判断是否打开闹铃(每次响铃一分钟).
//由于使用字符串保存定时数据,实际使用场景基本不用考虑定时控制的数量限制问题(可以使用24*60=1440个定时,字符串最长可达1440*5=7200个字节.).
//由于没有使用闪存保存,所以断电或重启后定时数据将丢失.
String temStr="";int i;
temStr=temStr+char(in_hour/10+48)+char(in_hour%10+48)+char(in_min/10+48)+char(in_min%10+48);/*四个字符代表一个定时时间*/
switch(in_mode){
case 0:/*响铃判断*/
if(dingShi.indexOf(temStr)==-1){tm1638.led(7,0);}else{tm1638.led(7,1);}/*这里可以更改定时控制方式,示例以最后一个LED亮灭为标志*/
break;
case 1:/*响铃设置*/
i=dingShi.indexOf(temStr);
if(i!=-1){
/*已有此定时则删除*/
if(i==0){
if(dingShi==temStr){
dingShi="";
}else{
dingShi=dingShi.substring(5);
}
}else{
if(i==dingShi.length()-4){
dingShi=dingShi.substring(0,dingShi.length()-5);
}else{
dingShi=dingShi.substring(0,i-1)+dingShi.substring(i+4);
}
}
}else{
/*没有此定时则添加*/
if(dingShi==""){dingShi=temStr;}else{dingShi=dingShi+","+temStr;}
}
break;
}
}
? ? ? ? 主要位置都作了中文注释,故不再对程序作进一步说明,请各位下载资源实验便可。这里要特别提醒大家的是,TIMERS.C中关于日期数据增加了修正,年号+1900自动调整为通常的表现形式,而月号也是加了1,时区修正使用的是configTime函数,第一个参数8*3600表示修正为北京时间(东八区)。整个程序只依赖ESP32基础库,不涉及第三方库引用。所有模块都是ARDUINO标准C语言程序,可以随意修改,增删功能。一切都由你自己掌控。
|