零零碎碎隔段时间捣鼓一下,历时几个月,终于把网络时钟制作齐了我想要的功能。外观看图。

这个闹钟功能比较全面了,采用了D1 mini开发板(esp8266)、LCD1602显示屏、DS1302时钟芯片、DHT11温湿度模块、无源蜂鸣器等配件,Arduino开发,实现的功能列表如下。

  1. 显示日期、星期、时间和温湿度,外带一个Wifi小图标。
  2. 可以联网自动对时。
  3. 断电不影响走时(DS1302)。
  4. 可设置一路闹钟,通过web页面设置,响铃为两只老虎旋律。
  5. 可web页面配置wifi,没有设置wifi时自动开启wifi热点,以供手机链接时钟进行配置。
  6. 两个按钮,一个重启模块,一个清楚WiFi配置信息。

下面是进行WiFi设置和闹钟设置的两个网页。

程序代码如下:

/*  ---By www.yizu.org---
              Connect
        D1 mini       I2C_LCD
          5V            VCC
          GND           GND
          D2            SDA
          D1            SCL
        D1 mini       DS1302
          3V3           VCC
          GND           GND
          D6            CLK
          D7            DAT
          D8            RST
*/
#include <ESP8266WiFi.h>
#include <EEPROM.h>
#include <WiFiUdp.h>
#include <NTPClient.h>
#include <LiquidCrystal_I2C.h>
#include <TimeLib.h>
#include <Arduino.h>
#include <DS1302.h>
#include <dht11.h>
#include <ESP8266WebServer.h>
#include <FS.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);

int32_t t_xd=0;

//定义web服务
ESP8266WebServer server(80);

//设置NTPClient
#define NTP_OFFSET   60 * 60 * 8  // 时区偏移量
#define NTP_ADDRESS  "ntp1.aliyun.com"  // NTP服务器
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, NTP_ADDRESS, NTP_OFFSET);

//设置时钟模块
DS1302 rtc(12,13,15);

//设置校对时间间隔(单位秒)
#define CALIBRATION_INTERVAL 60
unsigned long t_stamp=0;
unsigned long t_stamp2=0;

//设置蜂鸣器
#define BUZZER_PIN 14 //设置D5脚
#define ALARM_INTERVAL 300 //一个节拍的时间/毫秒
unsigned long b_stamp = 0;
unsigned long b_stamp2 = 0;
unsigned long duration = 0;//闹铃持续时间
boolean isAlarm=0;//蜂鸣器发声标记
uint8_t tonesTag=0;
uint8_t tonesLength=33;//长度
//两只老虎音乐
char toneNotes[] = "cdeccdecefgefggagfecgagfecdgcdgc "; //'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C'
float beats[] = { 1,1,1,1,1,1,1,1,1,1,2,1,1,2,0.5,0.5,0.5,0.5,1,1,0.5,0.5,0.5,0.5,1,1,1,1,2,1,1,2,4 }; 
bool ringing=false;

//闹钟设置
bool alarm_on=false;
int alarm_hour=0;
int alarm_min=0;

//设置AP账号密码
const char* ssid = "myAP000";
const char* password = "12345678";

//设置温湿度传感器
#define DHT11PIN 2//定义dht11引脚
dht11 myDht;
int tem=0;//温度
int hum=0;//湿度

//显示字符
#if defined(ARDUINO) && ARDUINO >= 100
#define printByte(args)  write(args);
#else
#define printByte(args)  print(args,BYTE);
#endif

//要显示的特殊字符
//uint8_t nian[8] = {0x08,0x0f,0x12,0x0f,0x0a,0x1f,0x02,0x02,};//年
//uint8_t yue[8]  = {0x0f,0x09,0x0f,0x09,0x0f,0x09,0x0b,0x11,};//月
//uint8_t ri[8]   = {0x1F,0x11,0x11,0x1F,0x11,0x11,0x1F,0x00,};//日
uint8_t dian[8] = {0x02,0x05,0x02,0x00,0x00,0x00,0x00,0x00,};//点
uint8_t dianC[8] ={0x00,0x17,0x08,0x08,0x08,0x08,0x07,0x00,};//温度单位
uint8_t wificon[8] ={0x00,0x0E,0x11,0x04,0x0A,0x00,0x04,0x00,};//WIFI链接
uint8_t wificon2[8]={0x00,0x11,0x0A,0x04,0x0A,0x11,0x00,0x00,};//断网

const char * days[] = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"} ;

//连接WIFI
void connectToWiFi()
{
  //读取eeprom
  EEPROM.begin(68);
  //读取闹铃设置
  if (EEPROM.read(66) == 1){
    alarm_on = true;
  }
  else{
    alarm_on = false;
  }
  alarm_hour = EEPROM.read(64);
  alarm_min = EEPROM.read(65);
  //读取闹铃设置结束
  byte len=EEPROM.read(0);
  if(len>0&&len<30){
    char ssid2[len+1];
    for(int i=0;i<len;i++){
      ssid2[i]=EEPROM.read(i+1);
    }
    ssid2[len]='\0';
    Serial.printf("ssid:%s\n",ssid2);
    byte len2=EEPROM.read(32);
    if(len2>0&&len2<30){
      char passwd2[len2+1];
      for (int i = 0; i < len2; i++){
        passwd2[i] = EEPROM.read(i + 32+1);
      }
      passwd2[len2]='\0';
      EEPROM.end();
      Serial.printf("password:%s\n",passwd2);
      //Serial.printf("password Lenth:%d\n",len2);
      WiFi.mode(WIFI_STA);
      WiFi.begin(ssid2,passwd2);
      Serial.println("");
      Serial.print("Connected to WiFi at ");
      Serial.print(WiFi.localIP());
      Serial.println("");
      unsigned int count = 0;
      while ((count < 10) & (WiFi.status() != WL_CONNECTED)){
        delay(500);
        Serial.print(".");
        count++;
      }
    }else{
      WiFi.mode(WIFI_STA);
      WiFi.begin(ssid2);
      Serial.println("");
      Serial.print("Connected to WiFi at ");
      Serial.print(WiFi.localIP());
      Serial.println("");
      unsigned int count = 0;
      while ((count < 10) & (WiFi.status() != WL_CONNECTED)){
        delay(500);
        Serial.print(".");
        count++;
      }
    }
  }else{
    WiFi.mode(WIFI_AP);
    WiFi.softAP(ssid, password);
    Serial.println("");
    Serial.print("AP IP address:");
    Serial.println(WiFi.softAPIP());
  }
}

//显示WIFI连接状态
void showWifiStatus()
{
  lcd.setCursor(11, 0);
  if(WiFi.status() == WL_CONNECTED)
  {
    lcd.write(byte(5));
  }
  else
  {
    lcd.write(byte(6));
  }
}

//校对时间
void correctionTime()
{
  time_t tnow=now();
  if(t_stamp==0||tnow-t_stamp>=5)//每10秒校对系统时钟
  {
    Serial.println("");
    Serial.print(rtc.getTimeStr());
    Serial.print(" Correction the time.");
    if(WiFi.status() == WL_CONNECTED&&(t_stamp2==0||tnow-t_stamp2>=CALIBRATION_INTERVAL))
    {
      if(timeClient.update())
      {
        unsigned long t=timeClient.getEpochTime();
        //设置系统时间
        setTime(t);
        Serial.println("");
        Serial.print("SysTime=NTP:");
        Serial.printf("%02d:%02d:%02d",hour(t), minute(t), second(t));
        //设置RTC时钟
        if(rtc.peek(0)==88)
        {
          rtc.writeProtect(false);
          rtc.setDOW(weekday(t)+1);
          rtc.setTime(hour(t), minute(t), second(t));
          rtc.setDate(day(t), month(t), year(t));
          rtc.writeProtect(true);
        }
      }
      else
      {
        if(rtc.peek(0)==88)
        {
          //设置系统时间
          Time t=rtc.getTime();
          setTime(t.hour,t.min,t.sec,t.date,t.mon,t.year);
          Serial.println("");
          Serial.print("SysTime=RTC:");
          Serial.printf("%02d:%02d:%02d",t.hour,t.min,t.sec);
        }
      }
      t_stamp2=now();
    }
    else
    {
      if(rtc.peek(0)==88)
      {
        //设置系统时间
        Time t=rtc.getTime();
        setTime(t.hour,t.min,t.sec,t.date,t.mon,t.year);
        Serial.println("");
        Serial.print("SysTime=RTC:");
        Serial.printf("%02d:%02d:%02d",t.hour,t.min,t.sec);
      }
    }
    t_stamp=now();
    tempAndHun();//读取温度
  }
}

void tempAndHun()
{
  //--------------------------
  Serial.println("\n");
  int chk = myDht.read(DHT11PIN);
  Serial.print("Read sensor: ");
  switch (chk)
  {
    case DHTLIB_OK: 
                Serial.println("OK"); 
                break;
    case DHTLIB_ERROR_CHECKSUM: 
                Serial.println("Checksum error"); 
                break;
    case DHTLIB_ERROR_TIMEOUT: 
                Serial.println("Time out error"); 
                break;
    default: 
                Serial.println("Unknown error"); 
                break;
  }
  tem=myDht.temperature;
  hum=myDht.humidity;
  Serial.print("Humidity (%): ");
  Serial.println(hum);
  Serial.print("Temperature (oC): ");
  Serial.println(tem);
}

//音频频率
unsigned int toneFrequency(char note) { 
  char names[] = { 'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C' }; 
  int tones[] = { 261, 294, 329, 349, 392, 440, 493, 523 }; 

  for (int i = 0; i < 8; i++) { 
    if (names[i] == note) { 
      return tones[i]; 
    } 
  } 
} 

//驱动蜂鸣器
void alarm()
{
  unsigned long currentMillis =millis();
  if(b_stamp2==0) {
    b_stamp2=currentMillis;
  }
  if(currentMillis-b_stamp2<duration) {
    if(!isAlarm) {
      if(b_stamp==0) {
        tone(BUZZER_PIN,toneFrequency(toneNotes[tonesTag]));
        isAlarm=true; 
        b_stamp=currentMillis;
      }
      else if(currentMillis-b_stamp>0.2*ALARM_INTERVAL) {
        if(toneNotes[tonesTag]!=' ') {
          tone(BUZZER_PIN,toneFrequency(toneNotes[tonesTag]));
        }
        isAlarm=true;
        b_stamp=currentMillis;
      }
    }
    else {
      if(currentMillis-b_stamp>beats[tonesTag]*ALARM_INTERVAL) {
        noTone(BUZZER_PIN);
        isAlarm=false;
        b_stamp=currentMillis;
        if(tonesTag<tonesLength-1) {
          tonesTag++;
        }
        else {
          tonesTag=0;
        }
      }
    }
  }
  else
  {
    b_stamp2=0;
    duration=0;
    noTone(BUZZER_PIN);
  }
}

void handleRouter() {
  String path = server.uri(); 
  if(path=="/"){
    path="/index.html";
  }
  if (SPIFFS.exists(path)){
    File file=SPIFFS.open(path,"r");
    String contentType="text/html";
    if(path.substring(path.lastIndexOf('.'))==".css"){
      contentType="text/css";
    }
    server.streamFile(file,contentType);
    file.close();
  }
  else{
    server.send(404, "text/plain", "File Not Found!\n\n");
  }
}

void jsonRouter() {
  if(server.hasArg("nz")){
    EEPROM.begin(68); // 4的倍数
    String json = "{\"alarm\":\"";
    json+=EEPROM.read(66);
    json+="\",\"hour\":\"";
    json+=EEPROM.read(64);
    json+="\",\"min\":\"";
    json+=EEPROM.read(65);
    json+="\"}";
    EEPROM.end(); 
    server.send(200, "text/plain",json);
  }
  else{
    server.send(404, "text/plain", "File Not Found!\n\n");
  }
}

void postRouter() {
  EEPROM.begin(68);//4的倍数
  if(server.hasArg("ssid")){
    String ssid2 = server.arg("ssid");
    Serial.printf("Write ssid:%s\n",ssid2.c_str());
    EEPROM.write(0,ssid2.length());
    for(int i=0;i<ssid2.length();i++){
      EEPROM.write(i+1,ssid2[i]);
    }
  }
  if(server.hasArg("passwd")){
    String passwd2 = server.arg("passwd");
    passwd2.trim();
    Serial.printf("Write password:%s\n",passwd2.c_str());
    if(passwd2.length()>0){
      EEPROM.write(32, passwd2.length());
      for (int i = 0; i < passwd2.length(); i++) {
        EEPROM.write(i + 32 + 1, passwd2[i]);
      }
    }else{
      EEPROM.write(32,0);
    }
  }
  //post?alarm=on&hour=03&min=02
  if(server.hasArg("hour")){
    duration==0;
    String h=server.arg("hour");
    EEPROM.write(64,byte(h.toInt()));
    if (server.hasArg("min")){
      String m = server.arg("min");
      EEPROM.write(65, byte(m.toInt()));
    }
    if (server.hasArg("alarm")){
      EEPROM.write(66, 1);
    }
    else{
      EEPROM.write(66, 0);
    }
  }
  EEPROM.end();
  server.send(200, "text/plain", "设置成功,即将重启时钟!\n\n");
  delay(200);
  ESP.restart();
}

void setup()
{
  Serial.begin(115200);

  lcd.init();// 初始化lcd
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("Starting...");
  lcd.setCursor(0, 1);
  lcd.print("Please wait.");
  //联网
  connectToWiFi();
  //初始化NTPClient
  timeClient.begin();
  //开启时钟
  if(rtc.peek(0)!=88)
  {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("System reseted!");
    rtc.writeProtect(false);
    rtc.halt(false);
    rtc.poke(0,88);//写入启动标记
    rtc.writeProtect(true);
    delay(1000);
  }
  //对时
  correctionTime();
  //读取温湿度
  tempAndHun();
  lcd.clear();
  pinMode(BUZZER_PIN,OUTPUT);
  //开启web服务
  SPIFFS.begin(); 
  server.on("/",handleRouter);
  server.on("/post",postRouter);
  server.on("/json",jsonRouter);
  server.onNotFound(handleRouter);
  server.begin();
  Serial.println("HTTP server started");

  pinMode(16,INPUT);
}

void loop()
{
  correctionTime();
  // 打印到屏幕
  //lcd.createChar(1, nian);
  //lcd.createChar(2, yue);
  //lcd.createChar(3, ri);
  lcd.createChar(4, dianC);
  lcd.createChar(5, wificon);
  lcd.createChar(6, wificon2);
  
  time_t t=now();
  lcd.setCursor(0, 0);
  //lcd.printf("%04d",year(t));
  //lcd.write(byte(1));
  //lcd.printf("%02d",month(t));
  //lcd.write(byte(2));
  //lcd.printf("%02d",day(t));
  //lcd.write(byte(3));
  lcd.printf("%04d-%02d-%02d",year(t),month(t),day(t));
  lcd.setCursor(9, 1);
  lcd.print(days[weekday(t)-1]);
  lcd.setCursor(0, 1);
  lcd.printf("%02d:%02d:%02d",hour(t),minute(t),second(t));
  lcd.setCursor(13, 0);
  lcd.print(tem);//显示温度
  lcd.write(byte(4));
  lcd.setCursor(13, 1);
  lcd.print(hum);//显示湿度
  lcd.print("%");
  showWifiStatus();

  server.handleClient();
  //长按下按钮2,清除wifi设置
  if(digitalRead(16)==LOW){
    if(t_xd==0){
      t_xd=millis();
      duration=0;
    }else{
      if(millis()-t_xd>2000){
        EEPROM.begin(64);
        EEPROM.write(0, 0);
        EEPROM.end();
        Serial.println("EEPROM is clear.");
        delay(200);
        ESP.reset();
      }
    }
  }else{
    if(t_xd!=0) t_xd=0;
  }
  //闹钟
  if(hour(t)==alarm_hour&&minute(t)==alarm_min){
    if(duration==0&&!ringing&&alarm_on){
      duration = 50000;//设置持续时间
      ringing=true;
    }
  }
  else{
    ringing=false;
  }
  alarm();
}

零敲碎打的,一点一点加上的功能,所以代码比较乱,但用也是一个比较完整的Arduino开发实例,用到的知识点还是比较多的,用来学习不错。完整代码打包下载