接上一篇的硬件,本篇说说开发环境和作品功能的实现及部分核心代码
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);
同步NTP时间,在Arduino IDE中需安装NTP库,库示例代码如下,串口持续打印 .... 直到网络连接成功,loop函数中每秒进行时间更新和打印当前的 时-分-秒
#include <NTPClient.h>
#include <ESP8266WiFi.h>
#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>
int DIN = D7;
int CS = D6;
int CLK = D5;
LedControl DC = LedControl(DIN,CLK,CS,4);
void LEDInit()
{
for(int i = 0;i<4;i++)
{
DC.shutdown(i,false);
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>
int DS1302_RST = D0;
int DS1302_DAT = D1;
int DS1302_CLK = D2;
ThreeWire DS1302Wire(DS1302_DAT,DS1302_CLK,DS1302_RST);
RtcDS1302<ThreeWire> Rtc(DS1302Wire);
上电时,在setup中先更新DS1302的内置时间(NTP获取的时间),可以直接调用函数写入
if(timeClient.update()){
RtcDateTime DS_time_update(DS_time.Year(),DS_time.Month(),DS_time.Day(),timeClient.getHours(),
timeClient.getMinutes(),timeClient.getSeconds());
Rtc.SetDateTime(DS_time_update);
Rtc.SetIsWriteProtected(false);
Rtc.SetIsRunning(true);
Serial.println("RTC update Success !!");
}
DS1302模块的内置日期不一定是对的,偶尔断电或者电流不够,重新上电时,就会变成2000年1月1日,第一次写入日期可以是下面这样,然后再更新一次上面的代码进去,日期会随着时间去更新
if(timeClient.update()){
RtcDateTime DS_time_update(2021,11,24,timeClient.getHours(),
timeClient.getMinutes(),timeClient.getSeconds());
Rtc.SetDateTime(DS_time_update);
Rtc.SetIsWriteProtected(false);
Rtc.SetIsRunning(true);
Serial.println("RTC update Success !!");
}
5. 初始化函数整合
setup函数初始化上面列出来的几个部分,大致如下(不确定完整性),注意需要添加看门狗,防程序跑死
#include <NTPClient.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <ThreeWire.h>
#include <RtcDS1302.h>
#include <LedControl.h>
int DS1302_RST = D0;
int DS1302_DAT = D1;
int DS1302_CLK = D2;
int DIN = D7;
int CS = D6;
int CLK = D5;
LedControl DC = LedControl(DIN,CLK,CS,4);
WiFiUDP ntp_udp;
NTPClient timeClient(ntp_udp,"ntp7.aliyun.com",60*60*8,60000);
ThreeWire DS1302Wire(DS1302_DAT,DS1302_CLK,DS1302_RST);
RtcDS1302<ThreeWire> Rtc(DS1302Wire);
void RTC_ClockUpdate()
{
if(timeClient.update()){
RtcDateTime DS_time_update(DS_time.Year(),DS_time.Month(),DS_time.Day(),timeClient.getHours(),
timeClient.getMinutes(),timeClient.getSeconds());
Rtc.SetDateTime(DS_time_update);
Rtc.SetIsWriteProtected(false);
Rtc.SetIsRunning(true);
Serial.println("RTC update Success !!");
}
}
void setup(){
LEDInit();
Serial.begin(115200);
WiFi.begin(SSID,PASS);
uint8_t connect_time = 0;
while(WiFi.status() != WL_CONNECTED)
{
for(int i = 0;i<2;i++)
{
...
}
connect_time++;
if(connect_time == 4) break;
}
CatDisplay();
delay(750);
Rtc.Begin();
timeClient.begin();
RTC_ClockUpdate();
ESP.wdtEnable(5000);
}
void loop(){
ESP.wdtFeed();
}
6. 手势识别
安装手势识别相关库 SparkFun_APDS9960 ,手势识别模块型号 APDS9960
#include <Wire.h>
#include <SparkFun_APDS9960.h>
#define APDS9960_INT 2
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);
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();
if((millis() - loop_times) >= UPUATE_TIME){
Time_DisplayUpdate();
DayorNight_Update();
RTC_DATETIME_GET;
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列点阵
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库的函数,传参包括点阵设备号,要显示的二维数组行数,及偏移位
void Display_3col(byte character[][3],int device_num,int col,int point)
{
for(int i = 0;i<3;i++)
{
DC.setColumn(device_num,point+i,character[col][i]);
}
}
8.手势切换显示
不做手势切换时,物联网时钟的显示界面是这样的 手势识别做了另外三个界面,分别是日期(左)、猫脸互动(上),当前秒数(右),切换界面没做成滚动的,直接是界面刷新 当在时间界面手势下滑时,猫脸互动(在时间界面上方)出来了,不做其他操作的话就是间接眨眼 在猫脸互动界面,进行手势左滑及右滑,猫咪眼睛会跟随手势做同向移动并回到原位 要回到时间界面是反向滑动,也就是做上滑手势,回时间界面后,需要看左侧的日期界面是右滑,年份显示在3号点阵,下面是显示日期 同样回去时间界面为相反手势-左滑,看右侧秒数界面为时间界面手势左滑,3号点阵显示小时钟转动,0-2号点阵显示分钟-秒钟,中间同样加跳动冒号,回时间界面为右滑
9.物联网时钟外壳
外壳经过建模光固化打印后整个外观图片如下 外壳打印出来后需要跟控制板,点阵,锂电池那些整合到一起,还需要再做适配调试,和按键控制电路的焊接
|