ESP8266便携式物联网时钟(软件篇) 代号:喵

Posted GenCoder

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ESP8266便携式物联网时钟(软件篇) 代号:喵相关的知识,希望对你有一定的参考价值。

接上一篇的硬件,本篇说说开发环境和作品功能的实现及部分核心代码


1.开发环境 - Arduino IDE for ESP8266

使用Arduino开发平台来开发ESP8266,可以延用Arduino的变成语言,便捷高效,就是安装环境和编译代码的时候稍微费点时间,环境安装参考链接
Arduino IDE – ESP8266开发环境搭建
当然,也不是所有人都能一次性安装成功的,这里提供安装失败的参考方法
ESP8266 – Arduino IDE开发环境配置失败解决方式参考

成功搭建开发环境后,在IDE开发板选项中可以找到ESP8266系列的开发板,如果你用的板子是ESP-12F,那么就选如图的开发板就行,其他参数默认即可

2.NTP网络时间获取

对于物联网时钟来说,联网和实时时间获取都是必要且基础的
ESP8266网络连接,核心代码如下,是否联网成功可以从 WiFi.status() 的返回值得知,具体可以参考文章 ESP8266(ESP-12F) 学习笔记1 – 网络连接

char *ssid = "WiFi_name";		//路由器/热点名称
char *pass = "123456789";		//路由器/热点密码

WiFi.begin(ssid,pass);					//ESP8266连接外部路由器/热点

同步NTP时间,在Arduino IDE中需安装NTP库,库示例代码如下,串口持续打印 .... 直到网络连接成功,loop函数中每秒进行时间更新和打印当前的 时-分-秒

#include <NTPClient.h>
// change next line to use with another board/shield
#include <ESP8266WiFi.h>
//#include <WiFi.h> // for WiFi shield
//#include <WiFi101.h> // for WiFi 101 shield or MKR1000
#include <WiFiUdp.h>

const char *ssid     = "<SSID>";
const char *password = "<PASSWORD>";

WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP);

void setup()
  Serial.begin(115200);

  WiFi.begin(ssid, password);

  while ( WiFi.status() != WL_CONNECTED ) 
    delay ( 500 );
    Serial.print ( "." );
  

  timeClient.begin();


void loop() 
  timeClient.update();

  Serial.println(timeClient.getFormattedTime());

  delay(1000);

需要注意的地方,构造函数timeClient可传参连接的NTP服务器,以及获取时间的时区(重点),不指定时区的话默认是东一区的时间,北京时间是东八区,服务器不指定的话默认是 pool.ntp.org

WiFiUDP    ntp_udp;
timeClient(ntp_udp,"ntp1.aliyun.com",60*60*8,60000);

也可以调用 setTimeOffset 函数设置时区

timeClient.setTimeOffset(60*60*8);	//东八区时间设置

时间获取相关函数,周几(返回值0-6,星期天是0),小时,分钟,秒数

    int getDay() const;
    int getHours() const;
    int getMinutes() const;
    int getSeconds() const;

详细参考文章链接 ESP8266(ESP12F)学习笔记2 – NTP网络时间获取

3.32x8 LED点阵显示

显示主体定制的PCB点阵,在代码中需要做LED点阵初始化和各种显示取模
软件篇中提到的某平台大佬做的物联网时钟,分享的代码部分相关显示的库已经在Arduino上找不到支持库了,所以NTP和点阵显示都需要重新找支持库(点阵屏用 LedControl 库)
LedControl库使用可参考链接 [ESP8266(ESP-12F) 第三方库使用 – (https://blog.csdn.net/qq_36955622/article/details/120077367)

#include <LedControl.h>

// CONNECTIONS:
// 32*8 LED Display DIN --> ESP-12F D7
// 32*8 LED Display CS  --> ESP-12F D6
// 32*8 LED Display CLK --> ESP-12F D5
int DIN = D7;
int CS =  D6;
int CLK = D5;

//LED Display DEFINE
LedControl DC = LedControl(DIN,CLK,CS,4);

// 点阵初始化
void LEDInit()

    //初始化address 0-3的8*8点阵屏
    for(int i = 0;i<4;i++)
    
      DC.shutdown(i,false);       //启动时,MAX72XX处于省电模式
      DC.setIntensity(i,12);       //亮度设置
      DC.clearDisplay(i);         //清除显示
    

至于点阵要显示的内容就要靠取模软件来生成字库了,将生成的16进制字库保存在代码数组中
在上电之后进行联网的同时,可以在点阵屏做点表情互动来表示联网状态,联网中就眨巴眼睛,联网成功了或者限定时间内未成功联网则表情显示为球形眼珠的

眨眼表情的代码放在联网检测代码中

while(WiFi.status() != WL_CONNECTED)

	// 联网过程中点阵显示互动表情   
	...


// 切换表情表示已联网成功或超时
...

点阵显示可以参考链接 ESP8266(ESP-12F)案例实操 – 8x32点阵显示(MAX7219)

4.RTC时钟更新写入

RTC时钟使用到DS1302时钟模块,跟点阵一样,都需要3个IO(DAT/CLK/RST)来写入数据和读出数据,用到 RtcDS1302

#include <RtcDS1302.h>

//RTC DS1302 CONNECTIONS:
// DS1302 CLK/SCLK --> ESP-12F D2
// DS1302 DAT/IO   --> ESP-12F D1
// DS1302 RST/CE   --> ESP-12F D0
int DS1302_RST = D0;
int DS1302_DAT = D1;
int DS1302_CLK = D2;

//DS1302 DEFINE
ThreeWire DS1302Wire(DS1302_DAT,DS1302_CLK,DS1302_RST);   // DAT, CLK, RST
RtcDS1302<ThreeWire> Rtc(DS1302Wire);

上电时,在setup中先更新DS1302的内置时间(NTP获取的时间),可以直接调用函数写入

if(timeClient.update())
		// 将时间写入DS_time_update,包括年月日(DS1302内置),时分秒(直接那NTP服务器的时间)
        RtcDateTime DS_time_update(DS_time.Year(),DS_time.Month(),DS_time.Day(),timeClient.getHours(),
                                 timeClient.getMinutes(),timeClient.getSeconds());
        Rtc.SetDateTime(DS_time_update);	// 写入时间到DS1302
        Rtc.SetIsWriteProtected(false);		// 关闭写保护
        Rtc.SetIsRunning(true);				// 运行DS1302
        Serial.println("RTC update Success !!");
    

DS1302模块的内置日期不一定是对的,偶尔断电或者电流不够,重新上电时,就会变成2000年1月1日,第一次写入日期可以是下面这样,然后再更新一次上面的代码进去,日期会随着时间去更新

if(timeClient.update())
		// 将时间写入DS_time_update,包括年月日(手动写入),时分秒(直接那NTP服务器的时间)
        RtcDateTime DS_time_update(2021,11,24,timeClient.getHours(),
                                timeClient.getMinutes(),timeClient.getSeconds());
        Rtc.SetDateTime(DS_time_update);	// 写入时间到DS1302
        Rtc.SetIsWriteProtected(false);		// 关闭写保护
        Rtc.SetIsRunning(true);				// 运行DS1302
        Serial.println("RTC update Success !!");

5. 初始化函数整合

setup函数初始化上面列出来的几个部分,大致如下(不确定完整性),注意需要添加看门狗,防程序跑死

// NTP时间获取
#include <NTPClient.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>

// DS1302模块
#include <ThreeWire.h>
#include <RtcDS1302.h>

// 8*(8+24)LED点阵显示
#include <LedControl.h>

//RTC DS1302 CONNECTIONS:
// DS1302 CLK/SCLK --> ESP-12F D2
// DS1302 DAT/IO   --> ESP-12F D1
// DS1302 RST/CE   --> ESP-12F D0
int DS1302_RST = D0;
int DS1302_DAT = D1;
int DS1302_CLK = D2;

// CONNECTIONS:
// 32*8 LED Display DIN --> ESP-12F D7
// 32*8 LED Display CS  --> ESP-12F D6
// 32*8 LED Display CLK --> ESP-12F D5
int DIN = D7;
int CS =  D6;
int CLK = D5;

//LED Display DEFINE
LedControl DC = LedControl(DIN,CLK,CS,4);

//NTP DEFINE
WiFiUDP    ntp_udp;
NTPClient  timeClient(ntp_udp,"ntp7.aliyun.com",60*60*8,60000);   //定位为东八区的时间

//DS1302 DEFINE
ThreeWire DS1302Wire(DS1302_DAT,DS1302_CLK,DS1302_RST);   // DAT, CLK, RST
RtcDS1302<ThreeWire> Rtc(DS1302Wire);

//更新DS1302时间(联网状态下)
void RTC_ClockUpdate()

	if(timeClient.update())
		// 将时间写入DS_time_update,包括年月日(DS1302内置),时分秒(直接那NTP服务器的时间)
        RtcDateTime DS_time_update(DS_time.Year(),DS_time.Month(),DS_time.Day(),timeClient.getHours(),
                                 timeClient.getMinutes(),timeClient.getSeconds());
        Rtc.SetDateTime(DS_time_update);	// 写入时间到DS1302
        Rtc.SetIsWriteProtected(false);		// 关闭写保护
        Rtc.SetIsRunning(true);				// 运行DS1302
        Serial.println("RTC update Success !!");
    


void setup()
	LEDInit();        // LED点阵初始化

    Serial.begin(115200);
    WiFi.begin(SSID,PASS);    // 连接WiFi

   uint8_t connect_time = 0;  // 循环眨眼次数

   // 联网检测,联网成功可在超时前退出while
   while(WiFi.status() != WL_CONNECTED)
   
     // 循环眨眼
     for(int i = 0;i<2;i++)
     
       // 眨眼代码
       ...
     
     // 眨眼1次,变量自加
     connect_time++;  
     // 超时眨眼次数 累计4次后未联网直接跳出while
     if(connect_time == 4)   break;
   

   //显示眼球图案(联网/超时)
   CatDisplay();
   delay(750);

   Rtc.Begin();             //初始化RTC - DS1302模块
   timeClient.begin();      //初始化NTP服务
   RTC_ClockUpdate();       //更新DS1302时间(联网状态下)
  
   ESP.wdtEnable(5000);     //开启看门狗,需要5秒喂狗一次


void loop() 
   ESP.wdtFeed();		// 喂狗

6. 手势识别

安装手势识别相关库 SparkFun_APDS9960,手势识别模块型号 APDS9960

#include <Wire.h>
#include <SparkFun_APDS9960.h>

// Pins
#define APDS9960_INT    2 // 需要一个中断引脚

// Global Variables
SparkFun_APDS9960 apds = SparkFun_APDS9960();
int isr_flag = 0;

void setup() 

  // 设置中断引脚为输入模式
  pinMode(APDS9960_INT, INPUT);

  // 初始化串口
  Serial.begin(115200);
  Serial.println();
  Serial.println(F("--------------------------------"));
  Serial.println(F("SparkFun APDS-9960 - GestureTest"));
  Serial.println(F("--------------------------------"));
  
  // 初始化外部中断
  attachInterrupt(0, interruptRoutine, FALLING);

  // 初始化APDS9960模块(I2C)
  if ( apds.init() ) 
    Serial.println(F("APDS-9960 initialization complete"));
   else 
    Serial.println(F("Something went wrong during APDS-9960 init!"));
  
  
  // 开始运行模块
  if ( apds.enableGestureSensor(true) ) 
    Serial.println(F("Gesture sensor is now running"));
   else 
    Serial.println(F("Something went wrong during gesture sensor init!"));
  


void loop() 
  if( isr_flag == 1 ) 
    detachInterrupt(0);
    handleGesture();
    isr_flag = 0;
    attachInterrupt(0, interruptRoutine, FALLING);
  


void interruptRoutine() 
  isr_flag = 1;


// 处理手势,将对应手势通过串口打印出来
void handleGesture() 
    if ( apds.isGestureAvailable() ) 
    switch ( apds.readGesture() ) 
      case DIR_UP:
        Serial.print("UP");
        break;
      case DIR_DOWN:
        Serial.print("DOWN");
        break;
      case DIR_LEFT:
        Serial.print("LEFT");
        break;
      case DIR_RIGHT:
        Serial.print("RIGHT");
        break;
      case DIR_NEAR:
        Serial.print("NEAR");
        break;
      case DIR_FAR:
        Serial.print("FAR");
        break;
      default:
        Serial.print("NONE");
    
  

该手势识别库不兼容ESP8266,需要用手势识别时要另外用一块Arduino控制板与ESP8266做串口交互

7.点阵显示

除了上面说到的联网时的造型显示外,点阵还需要显示当前的时间

定义时间段,在顶部的8x8点阵显示时间状态(白天或晚上),凌晨6点至晚上18点定义为白天,18点至凌晨6点定义为晚上,点阵根据时间段显示太阳图案和月亮图案,如上图为月亮图案

#define	IS_DAY   (6 <= DS_time.Hour()) && (DS_time.Hour() < 18)
#define IS_NIGHT (DS_time.Hour() < 6) || (DS_time.Hour() >= 18)

Arduino的程序都是以单线程的形式在运行,要执行类似多线程可以比较实时的运行多个任务很难实现,因此有个比较巧妙的方法可以做到,利用 millis 函数可以比较ok的完成类似多个实时任务的执行,原理是 millis - 最后一次任务运行记录的时间 >= 定时执行该任务的时间 但最好也别超过5个

void loop() 
   ESP.wdtFeed();		//循环喂狗
	  // 每秒执行一次,UPUATE_TIME = 1000毫秒
      if((millis() - loop_times) >= UPUATE_TIME)
         Time_DisplayUpdate();    // 检测0-2点阵上的时间是否需要刷新(小时-分钟)
         DayorNight_Update();     // 根据时间段判断3号点阵该显示太阳或月亮
  	
         RTC_DATETIME_GET;		// 读取DS1302的时间

		 // 判断DS1302读取的秒数,奇数和偶数做不同显示(小时与分钟显示之间冒号的亮灭)
         if((time.Second()%2)==0)  	  DC.setColumn(1,3,st_display[0]);
         else                         DC.setColumn(1,3,st_undisplay[0]);
         // 记录该次循环的时间
         loop_times = millis();
     

下面部分时间刷新代码,num_display 是0-9的数字取模,各占3列点阵

// 如果小时和分钟只有个位数的数值需要更新,那么只刷新LED点阵的个位数显示,只在时间有变化时才做刷新
if(last_DS_minutes != DS_minute)

    if(DS_minute >= 10)
    
    	if((DS_minute/10) == (last_DS_minutes/10))
    	
    		Display_3col(num_display,2,DS_minute%10,1);
    	else
    		Display_3col(num_display,1,DS_minute/10,5);
         Display_3col(num_display,2,DS_minute%10,1);
    	
    else
    if(DS_minute < 10)
    
    	if(DS_minute == 0)
    	
    		Display_3col(num_display,1,0,5);
         Display_3col(num_display,2,0,1);
    	else
          Display_3col(num_display,1,0,5);
          Display_3col(num_display,2,DS_minute,1);
    	
    
    last_DS_minutes = DS_minute;

显示函数是包装了LedControl库的函数,传参包括点阵设备号,要显示的二维数组行数,及偏移位

// display three col
void Display_3col(byte character[][3],int device_num,int col,int point)

  for(int i = 0;i&l

以上是关于ESP8266便携式物联网时钟(软件篇) 代号:喵的主要内容,如果未能解决你的问题,请参考以下文章

ESP8266便携式物联网时钟(软件篇) 代号:喵

ESP8266便携式物联网时钟(硬件篇) 代号:喵

ESP8266便携式物联网时钟(硬件篇) 代号:喵

ESP 保姆级教程 疯狂点灯篇 —— 案例:ESP8266 + LED + 按键 + OneNet物联网平台 + Web应用

ESP 保姆级教程 疯狂点灯篇 —— 案例:ESP8266 + WS2812 + 阿里云物联网平台 + 自开发小程序

ESP 保姆级教程 疯狂点灯篇 —— 案例:ESP8266 + 舵机+ 按键 + 阿里云物联网平台 + 阿里云物联网Web应用 + 自开发App