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)
forESP8266便携式物联网时钟(软件篇) 代号:喵
ESP 保姆级教程 疯狂点灯篇 —— 案例:ESP8266 + LED + 按键 + OneNet物联网平台 + Web应用
ESP 保姆级教程 疯狂点灯篇 —— 案例:ESP8266 + WS2812 + 阿里云物联网平台 + 自开发小程序
ESP 保姆级教程 疯狂点灯篇 —— 案例:ESP8266 + 舵机+ 按键 + 阿里云物联网平台 + 阿里云物联网Web应用 + 自开发App