|
玩单片机以来比较喜欢折腾时钟,可能是比较简单的缘故。
之前用51做了一个,一直放在客厅里用着,用的是DS1302。时间一直跑快,虽然代码中有定期调整功能,但功能不太好用。
近期一直在学习使用ESP8266这个芯片,个人比较喜欢其既可以联网又能作为MCU使用。因此又把之前的闹钟捡起来使用此芯片做了一个小时钟玩玩。
废话不多少说了,直接上,欢迎感兴趣的小伙伴拍砖。
器件:
Max7219 LED屏一个
NodeMCU模块一个
DS3231时钟模块一个
线若干
连线:
NodeMCU DS3231 LED屏
D1 <-> SCL
D2 <-> SDA
D4 <-> CLK
D5 <-> CS
D6 <-> DIN
功能:
1、显示时间、日期、温度(从DS3231中读取);
2、定期、手动通过NTP校时(手动校时通过短按NodeMCU的Flash键,校时失败会在左下方显示一个点)
3、支持无DS3231模块时显示时间、日期;
无手动调整时间、日期功能。
代码:需要的库请看代码部分,自行下载
注:本人非码农,因此代码基本是网上扒来改一下,以能跑起来为原则,高手勿喷。 - #include <LedControl.h>
- #include <ESP8266WiFi.h>
- #include <WiFiUdp.h>
- #include <Time.h>
- #include <TimeLib.h>
- #include <pgmspace.h>
- #include <Wire.h> // must be included here so that Arduino library object file references work
- #include <RtcDS3231.h>
- #define MX_CLOCK 2 // NodeMCU-D4 黑 CLK
- #define MX_CS 14 // NodeMCU-D5 白 CS
- #define MX_DIN 12 // NodeMCU-D6 灰 DataIn
- #define KEY 0 //按键,GPIO0 NodeMCU Flash Key
- // I2C For ESP8266, these default to SDA = GPIO04(NodeMCU-D2) and SCL = GPIO05(NodeMCU-D1).
- LedControl lc=LedControl(MX_DIN, MX_CLOCK, MX_CS, 4);
- unsigned long lastdisp = millis(); //LED屏刷新标志
- unsigned char bn = 0; // 显示缓冲区加载指针变量;
- byte clear_buf[32] = {byte(0)};
- byte disp[32] = {byte(0)}; //显示缓冲区;
- byte disp_buf[32] = {byte(0)}; //待显缓冲区;
- unsigned int k = 0; //用于“:”闪烁
- unsigned int t = 0; //用于切换日期时间显示
- RtcDS3231<TwoWire> Rtc(Wire);
- char ssid[] = "XXX"; // WIFI SSID
- char pass[] = "xxxxx"; // WIFI password
- unsigned int localPort = 2390; // UDP本地监听端口
- const unsigned long NtpInterval = 3600*1000UL; //NTP自动校时间隔(毫秒)
- //const unsigned long SeventyYears = 2208988800UL; //1900-1970的秒数
- //const unsigned long ThirtyYears = 946684800UL; //1970-2000的秒数
- const unsigned long Century = 3155673600UL; //1900.01.01-2000.01.01的秒数
- const long TimeZoneOffset = +28800; //GMT +8
- unsigned long lastNtpupdate = millis(); //NTP校时标志
- unsigned int rtcstatus = 1; //检测RTC是否有效
- unsigned int ntpstatus = 0; //检测NTP同步状态
- unsigned int keys = 0; //按键检测结果
- IPAddress timeServerIP; // 用于存放解析后的NTP server IP;
- const char* ntpServerName = "cn.ntp.org.cn"; //NTP服务器域名:尽量不要直接填写IP,
- const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
- byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
- // A UDP instance to let us send and receive packets over UDP
- WiFiUDP udp;
- //-----------------------------------数字字符表
- unsigned char dp[][8]{
- // 5*8字模
- {0x7E,0x81,0x81,0x81,0x7E,'z'}, // -0- 字符0
- {0x00,0x41,0xFF,0x01,0x00,'z'}, // -1- 字符1
- {0x43,0x85,0x89,0x91,0x61,'z'}, // -2- 字符2
- {0x82,0x81,0x91,0xA9,0xC6,'z'}, // -3- 字符3
- {0x18,0x28,0x48,0xFF,0x08,'z'}, // -4- 字符4
- {0xF2,0x91,0x91,0x91,0x8E,'z'}, // -5- 字符5
- {0x7E,0x91,0x91,0x91,0x4E,'z'}, // -6- 字符6
- {0x80,0x8F,0x90,0xA0,0xC0,'z'}, // -7- 字符7
- {0x6E,0x91,0x91,0x91,0x6E,'z'}, // -8- 字符8
- {0x62,0x91,0x91,0x91,0x7E,'z'}, // -9- 字符9
- {0x66,0x66,'z'}, // -10- ":"时间分隔符
- {0x18,0x18,0x18,'z'}, // -11- "-"日期分隔符
- {0xC0,0xC0,0x3C,0x66,0x42,0x42,0x24,'z'}, // -12- "oC"温度符号
- {0x00,0x00,'z'}, // -13- 2空列
- {0x06,0x06,'z'}, // -14- "."小数点
- {0x01,0x00,'z'}, // -15- 首列带点,用于NTP校时不成功时提醒
- };
- byte connwifi[32]{ //开机显示Connwifi;
- 0x7C,0x82,0x82,0x82,0x44,0x00, // -C-
- 0x1C,0x22,0x22,0x22,0x1C,0x00, // -o-
- 0x1E,0x20,0x20,0x1E,0x00, // -n-
- 0x7E,0x04,0x18,0x04,0x7E,0x00, // -W-
- 0x5E,0x00, // -i-
- 0x7E,0x50,0x50,0x50,0x00, // -F-
- 0x5E,0x00, // -i-
- };
- unsigned long sendNTPpacket(IPAddress& address) // send an NTP request to the time server at the given address
- {
- Serial.print("sending NTP packet...");
- memset(packetBuffer, 0, NTP_PACKET_SIZE); // set all bytes in the buffer to 0
- // Initialize values needed to form NTP request
- // (see URL above for details on the packets)
- packetBuffer[0] = 0b11100011; // LI, Version, Mode
- packetBuffer[1] = 0; // Stratum, or type of clock
- packetBuffer[2] = 6; // Polling Interval
- packetBuffer[3] = 0xEC; // Peer Clock Precision
- // 8 bytes of zero for Root Delay & Root Dispersion
- packetBuffer[12] = 49;
- packetBuffer[13] = 0x4E;
- packetBuffer[14] = 49;
- packetBuffer[15] = 52;
-
- // all NTP fields have been given values, now
- // you can send a packet requesting a timestamp:
- udp.beginPacket(address, 123); //NTP requests are to port 123
- udp.write(packetBuffer, NTP_PACKET_SIZE);
- udp.endPacket();
- }
- #define countof(a) (sizeof(a) / sizeof(a[0]))
- void printDateTime(const RtcDateTime& dt)
- {
- char datestring[20];
- snprintf_P(datestring,
- countof(datestring),
- PSTR("%04u/%02u/%02u %02u:%02u:%02u"),
- dt.Year(),
- dt.Month(),
- dt.Day(),
- dt.Hour(),
- dt.Minute(),
- dt.Second() );
- Serial.print(datestring);
- }
- void scankey()
- {
- if (digitalRead(KEY) == 0){
- unsigned int Keytime = 0;
- while(!digitalRead(KEY)){
- Keytime ++;
- delay(10);
- }
- if (Keytime >= 300) keys = 2;
- if (Keytime >= 2 & Keytime < 300) keys = 1;
- }
- }
- void putin_buf (unsigned char u){ //将字符装入显示缓冲区
-
- unsigned char a = 0; //定义变量用于数据提取指针
- do{
- disp_buf[bn] = dp[u][a]; //将二维数组中的一组数据放入显示缓冲区
- a++; //换下一个提取字节
- bn++; //换下一个显示缓冲字节
- }
- while(dp[u][a] != 'z'); //当显示数据为'z'时结束循环
- bn++;
- disp_buf[bn] = 0; //显示一列的空位,便于字符区分
- }
- void Load_time(void){ //时间组合与显示
- RtcDateTime Rtctime;
- if (rtcstatus == 1){
- Rtctime = Rtc.GetDateTime(); //读取RTC日期时间数据
- }
- else{
- Rtctime = now(); //RTC不可用时,读取系统日期时间;
- }
-
- bn = 0; //初始化填充指针
- memcpy(disp_buf,clear_buf,32); //清显示缓冲
- if (ntpstatus == 0){
- putin_buf(15); //插入带点的空列,显示NTP校时异常;
- }
- else{
- putin_buf(13); //在前面显示两列空格,让内容居中;
- }
- if(Rtctime.Hour()/10 != 0){
- putin_buf(Rtctime.Hour()/10); //显示小时值(十位,为0时消隐)
- putin_buf(Rtctime.Hour()%10); //显示小时值(个位)
- }
- else{
- putin_buf(13); // 十位为0时,插入2列空格保持显示居中
- putin_buf(Rtctime.Hour()%10); //显示小时值(个位)
- }
- if(k == 1){
- putin_buf(10); //显示冒号“:"
- }
- else{
- putin_buf(13); //显示":"闪烁效果
- }
- if(Rtctime.Minute()/10 != 0){
- putin_buf(Rtctime.Minute()/10); //显示分钟值(十位)
- putin_buf(Rtctime.Minute()%10); //显示分钟值(个位)
- }
- else{
- putin_buf(0);
- putin_buf(Rtctime.Minute()%10); //显示分钟值(个位)
- }
- }
- void Load_date(void){ //日期组合与显示
- RtcDateTime Rtctime;
- if (rtcstatus == 1){
- Rtctime = Rtc.GetDateTime(); //读取RTC日期时间数据
- }
- else{
- Rtctime = now(); //RTC不可用时,读取系统日期时间;
- }
-
- bn = 0; //初始化填充指针
- memcpy(disp_buf,clear_buf,32); //清显示缓冲
- if (ntpstatus == 0){
- putin_buf(15); //插入带点的空列,显示NTP校时异常;
- }
- else{
- putin_buf(13); //在前面显示两列空格,让内容居中;
- }
- if(Rtctime.Month()/10 != 0){
- putin_buf(Rtctime.Month()/10); //显示月份值(十位)
- putin_buf(Rtctime.Month()%10); //显示月份值(个位)
- }
- else{
- putin_buf(13); // 十位为0时,插入2列空格保持显示居中
- putin_buf(Rtctime.Month()%10); //显示月份值(个位)
- }
- putin_buf(11); //显示日期分割符“-"
- if(Rtctime.Day()/10 != 0){
- putin_buf(Rtctime.Day()/10); //显示日期值(十位)
- putin_buf(Rtctime.Day()%10); //显示日期值(个位)
- }
- else{
- putin_buf(0);
- putin_buf(Rtctime.Day()%10); //显示日期值(个位)
- }
- }
- void Load_temp(void){ //温度组合与显示
-
- float temp = 0;
- if (rtcstatus == 1){
- RtcTemperature Temperature = Rtc.GetTemperature(); // 读取DS3231中的温度数据
- temp = Temperature.AsFloat();
- }
- else{ //RTC不可用时,使用一个错误值代替;
- temp = 99.0;
- }
-
- bn = 0; //初始化填充指针
- memcpy(disp_buf,clear_buf,32); //清显示缓冲区
- putin_buf(13); //在前面显示两列空格,让内容居中;
- if((temp > 60.0)|(temp < -20.0)){
- putin_buf(11); //超出温度范围,显示"--";
- putin_buf(11);
- }
- else{
- putin_buf(int(temp*10)/100); //显示温度值(十位)
- putin_buf((int(temp*10)%100)/10);//显示温度值(个位)
- putin_buf(14); //显示小数点“."
- putin_buf(int(temp*10)%10); //显示温度值小数部分
- }
- putin_buf(12); //显示温度符号;
- }
- void Display() {
- int num = 0;
- for(int address=3; address>=0; address--){ //逐个扫描四个LED屏
- for(int col=0;col<8;col++) { //每块LED屏逐列扫描;
- lc.setColumn(address,col,disp[num]); //使用列方式显示;
- num ++;
- }
- }
- }
- void setup() {
-
- Serial.begin(115200);
- Serial.println();
-
- //初始化LED屏
- int devices=lc.getDeviceCount(); //获取LED屏的数量
- for(int address=0; address<devices; address++) {
- lc.shutdown(address,false); //初始化LED
- lc.setIntensity(address,6); //设置LED亮度,0-15;
- lc.clearDisplay(address); //清屏
- }
- memcpy(disp,connwifi,sizeof(connwifi)); //显示ConWiFi字样;
- Display();
-
- //连接WiFi
- Serial.print("Connecting to ");
- Serial.print(ssid);
- WiFi.begin(ssid, pass);
- unsigned int count = 0;
- while ((count < 30 ) & (WiFi.status() != WL_CONNECTED)) {
- delay(500);
- Serial.print(".");
- count ++;
- }
- Serial.println("WiFi connected");
- Serial.print("IP address: ");
- Serial.println(WiFi.localIP());
-
- //初始化UDP服务
- Serial.print("Starting UDP. ");
- udp.begin(localPort);
- Serial.print("Local port: ");
- Serial.println(udp.localPort());
- //初始化RTC时钟,并设定本地系统时间
- Rtc.Begin();
- if (!Rtc.GetIsRunning())
- {
- Serial.println("RTC was not actively running, starting now");
- Rtc.SetIsRunning(true);
- if (!Rtc.GetIsRunning()){
- Serial.println("RTC error!! Using System Time");
- rtcstatus = 0; //设置RTC无效
- RtcDateTime compiled = RtcDateTime(__DATE__, __TIME__); //获取固件编译时间
- setTime(compiled); //设定本地系统时间
- }
- }
- else {
- if (!Rtc.IsDateTimeValid())
- {
- Serial.println("RTC lost confidence in the DateTime!");
- RtcDateTime compiled = RtcDateTime(__DATE__, __TIME__); //获取固件编译时间
- Rtc.SetDateTime(compiled); //设定RTC时间
- }
-
- Rtc.Enable32kHzPin(false);
- Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeNone);
- RtcDateTime rtctime = Rtc.GetDateTime();
- setTime(rtctime); //获取RTC时间,设置本地系统时间
- rtcstatus == 1; //设置RTC有效
- Serial.print("Load RTC Time: "); printDateTime(rtctime);
- Serial.println("\n");
- }
- }
- void loop() {
- scankey(); // 按键扫描
- if ((lastNtpupdate != millis() / NtpInterval) | (keys == 1)) { //依据计时或按键(短按)进入NTP更新
- keys = 0;
- WiFi.hostByName(ntpServerName, timeServerIP); //get a random server from the pool
- sendNTPpacket(timeServerIP); // send an NTP packet to a time server
- delay(1000); // wait to see if a reply is available
- int cb = udp.parsePacket();
- if (!cb) {
- Serial.println("no packet yet\n"); //没有接收到NTP响应报文
- ntpstatus = 0;
- }
- else { // 收到NTP响应报文
- udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
- //the timestamp starts at byte 40 of the received packet and is four bytes,
- // or two words, long. First, esxtract the two words:
- unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
- unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
- unsigned long secsSince1900 = highWord << 16 | lowWord; // 得到NTP时间,NTP返回的为自1900.01.01以来的秒数;
- unsigned long realtime = secsSince1900 - Century + TimeZoneOffset; // 转换为本地时间;系统时间与RTC一般是自2000.01.01开始;
- setTime(realtime); // 设置系统时间与NTP时间同步。
- Rtc.SetDateTime(realtime); // RTC时钟与NTP时间同步
- ntpstatus = 1;
- Serial.println("Time Update!!");
- Serial.print("Current time:"); printDateTime(realtime);
- Serial.println("\n");
- }
- lastNtpupdate = millis() / NtpInterval;
- }
- if (millis() / 1000 != lastdisp){ //主显示程序,循环显示时间、日期、温度
- lastdisp = millis() / 1000;
- if (t <= 6){ //时间显示保持6秒
- Load_time();
- }
- else if(t <= 8){ //日期显示保持2秒
- Load_date();
- }
- else if(t <= 10){ //温度显示保持2秒
- Load_temp();
- }
- else{
- t = 0;
- }
- t ++;
- memcpy(disp,disp_buf,sizeof(disp_buf)); //将待显缓冲区内容移入显示缓冲
- Display();
- k = 1 - k;
- }
- }
复制代码
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有帐号?注册
x
|