目录
1.设计概述
esp8266是一款性价比极高的wifi蓝牙芯片。它不光集成了wifi蓝牙模组,同时内部集成了单片机内核,可以把它当普通的单片机使用。同时它支arduino编程,micropython编程和自带的idf库编程,使用起来非常方便。
这次设计以esp8266为主控,通过wifi读取ntp时间,来实现wifi时钟。
2.硬件设计
2.1整体设计和功能介绍
整体设计如下图:
硬件电路主要是由以下几个部分组成: WIFI模块(ESP8266)、USB转串口电路、供电电路、外设电路(包括OLED显示屏、MPU-6050模块和SHT31温湿度采集模块)。
主要功能:
1.显示日期
2.通过重力感应切换显示
3.显示温湿度
2.2相关硬件介绍
1.esp8266
其支持802.11b/g/n WIFI协议,内部的微处理器主频范围在80Mhz到160Mhz,有32Mbit的FLASH存储。具体的资料参见其数据手册。
2.mpu6050
MPU-6050对陀螺仪和加速度计分别用了三个16位的ADC, 将其测量的模拟量转化为可输出的数字量。陀螺仪可测范围为±250,上500,1000,±2000°/秒(dps), 加速度计可测范围为±2,±4,±8,±16g。
3.SHT31
它有两个型号(见下图),他们只是量程不一样,使用起来是完全一样的。它比DHT11好用。
4.oled
这是一个四线的oled,建议用1.3存的,这个例子中用了0.96寸的。时间的显示可以通用。图片的显示需要改像素。
3.电路设计
整体的电路设计如下图:
3.1自动下载电路
这里使用了G版本的CH340,所以需要晶振,但它便宜!!!C版本的不需要晶振。其实这部分电路可以省去,接外部的下载器下载也可以。
3.2 完整的esp8266应用电路
运行模式说明如下图:
3.3PCB设计
觉得麻烦没有敷铜。
4.程序设计
4.1程序流程图
采用Smart Config/AirKiss 配置网络。
4.2参考程序
这里采用的是arduino IDE开发。
//**********************************************************************
//主程序:用来显示网络时间,并显示温湿度数据
//**********************************************************************
#include "main.h"
bool autoConfig() //用之前的配网参数自动联网
{
WiFi.mode(WIFI_STA);
u8g2.setCursor(0, 12);
u8g2.print("Connect to Ap ...");
u8g2.sendBuffer();
WiFi.begin();
for (int i = 0; i < 20; i++)
{
int wstatus = WiFi.status();
if (wstatus == WL_CONNECTED)
{
Serial.println("AutoConfig Success");
Serial.printf("SSID: %s\r\n", WiFi.SSID().c_str());
Serial.printf("PSW: %s\r\n", WiFi.psk().c_str());
Serial.print("IP: ");
Serial.println(WiFi.localIP()); //得到IP地址
return true;
}
else
{
Serial.print("AutoConfig Waiting......");
Serial.println(wstatus);
delay(1000);
}
}
Serial.println("AutoConfig Faild!" );
return false;
}
void smartConfig()
{
u8g2.clearBuffer();
u8g2.setCursor(0, 12);
u8g2.print("Waiting smartcfg....");
u8g2.sendBuffer();
WiFi.mode(WIFI_STA);
Serial.println("\r\nWaiting for Smartconfig");
delay(2000);
WiFi.beginSmartConfig(); //等待配网
while (1)
{
Serial.print(".");
delay(400);
if (WiFi.smartConfigDone()) //配网完成
{
Serial.println("SmartConfig Success");
Serial.printf("SSID:%s\r\n", WiFi.SSID().c_str());
Serial.printf("PSW:%s\r\n", WiFi.psk().c_str());
WiFi.setAutoConnect(true); //设置自动连接
break;
}
}
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP()); //得到IP地址
}
void digitalClockDisplay() //打印时间
{
int years, months, days, hours, minutes, seconds, weekdays;
years = year();
months = month();
days = day();
hours = hour();
minutes = minute();
seconds = second();
weekdays = weekday();
if (seconds == 0){ //一分钟打印一次时间
Serial.printf("%d/%d/%d %d:%d:%d Weekday:%d\n", years, months, days, hours, minutes, seconds, weekdays);
}
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_unifont_t_chinese2);
u8g2.setCursor(0, 14);
int temp1, humi1;
temp1 = rtemp * 10;
humi1 = rhumi;
u8g2.print("H:");
if (humi1 < 10){
u8g2.print(" ");
u8g2.print(humi1);
}else
u8g2.print(humi1);
u8g2.print("%");
u8g2.setCursor(64, 14);
u8g2.print("T:");
if (temp1 < 100){
u8g2.print(" ");
u8g2.print(temp1 / 10);
}else
u8g2.print(temp1 / 10);
u8g2.print(".");
u8g2.print(temp1 % 10);
u8g2.drawXBM(112, 0, 16, 16, du);
String currentTime = "";
if (hours < 10)
currentTime += 0;
currentTime += hours;
currentTime += ":";
if (minutes < 10)
currentTime += 0;
currentTime += minutes;
currentTime += ":";
if (seconds < 10)
currentTime += 0;
currentTime += seconds;
String currentDay = "";
currentDay += years;
currentDay += "/";
if (months < 10)
currentDay += 0;
currentDay += months;
currentDay += "/";
if (days < 10)
currentDay += 0;
currentDay += days;
u8g2.setFont(u8g2_font_logisoso24_tr);
u8g2.setCursor(0, 44);
u8g2.print(currentTime);
u8g2.setCursor(0, 61);
u8g2.setFont(u8g2_font_unifont_t_chinese2);
u8g2.print(currentDay);
u8g2.drawXBM(80, 48, 16, 16, xing);
u8g2.setCursor(95, 62);
u8g2.print("期"); //必须是u8g2_font_unifont_t_chinese2里有的字
if (weekdays == 1)
u8g2.print("日");
else if (weekdays == 2)
u8g2.print("一");
else if (weekdays == 3)
u8g2.print("二");
else if (weekdays == 4)
u8g2.print("三");
else if (weekdays == 5)
u8g2.print("四");
else if (weekdays == 6)
u8g2.print("五");
else if (weekdays == 7)
u8g2.drawXBM(111, 49, 16, 16, liu);
u8g2.sendBuffer();
}
void displayimage()
{
u8g2.clearBuffer();
u8g2.drawXBM(0, 0, 128, 64, bmp2);
u8g2.sendBuffer();
}
void printDigits(int digits) //打印时间数据
{
Serial.print(":");
if (digits < 10) //打印两位数字
Serial.print('0');
Serial.print(digits);
}
time_t getNtpTime() //获取NTP时间
{
bool Time_Recv_Flag = false;
IPAddress ntpServerIP; //NTP服务器的IP地址
while (Udp.parsePacket() > 0) ; //之前的数据没有处理的话一直等待 discard any previously received packets
WiFi.hostByName(ntpServerName, ntpServerIP);//从网站名获取IP地址
if (PowerOn_Flag == 0) //第一次才打印
{
Serial.println("Transmit NTP Request");
Serial.print(ntpServerName);
Serial.print(": ");
Serial.println(ntpServerIP);
}
while (Time_Recv_Flag == false) //如果是上电第一次获取数据的话,要一直等待,直到获取到数据,不是第一次的话,没获取到数据,直接返回
{
sendNTPpacket(ntpServerIP); //发送数据包
uint32_t beginWait = millis();
while (millis() - beginWait < 1500)
{
int size = Udp.parsePacket(); //接收数据
if (size >= NTP_PACKET_SIZE)
{
Serial.println("Receive NTP Response");
Udp.read(packetBuffer, NTP_PACKET_SIZE); //从缓冲区读取数据
unsigned long secsSince1900;
secsSince1900 = (unsigned long)packetBuffer[40] << 24;
secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
secsSince1900 |= (unsigned long)packetBuffer[43];
if (PowerOn_Flag == 0) //第一次收到数据
{
u8g2.setCursor(0, 36);
u8g2.print("Ntp data get ok");
u8g2.sendBuffer();
}
Time_Recv_Flag = true;
return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
}
}
if (PowerOn_Flag == 1) //不是第一次
{
return 0;
}
}
Serial.println("No NTP Response :-(");
return 0; //没获取到数据的话返回0
}
void sendNTPpacket(IPAddress &address) //发送数据包到NTP服务器
{
memset(packetBuffer, 0, NTP_PACKET_SIZE); //缓冲区清零
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
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
Udp.beginPacket(address, 123); //NTP服务器端口123
Udp.write(packetBuffer, NTP_PACKET_SIZE); //发送udp数据
Udp.endPacket(); //发送结束
if (PowerOn_Flag == 0)
{
Serial.println("Send Ntp data...");
}
}
void imuupdate()
{
imu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz); //读取六轴原始数值
ax = ax - axo;
ay = ay - ayo;
az = az - azo;
//判断姿态,当左摇或者右摇时,改变状态变量
if (millis() - last_update_time > interval)
{
if (ay > 3000 && flag)
{
i--;
if(i<0)
{
i= 5;
}
flag = 0;
}
else if (ay < -3000 && flag)
{
i++;
flag = 0;
}
else
{
flag = 1;
}
if (az > 5000)
{
j++;
if(j >4)
{
j = 0;
}
}
if(i >5)
{
i = 0;
}
last_update_time = millis();
}
}
void setup()
{
Serial.begin(115200); //初始化串口
Serial.println(); //打印回车换行
u8g2.begin();
u8g2.enableUTF8Print();
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_wqy12_t_gb2312a);
ESP.wdtEnable(8000); //使能软件看门狗的触发间隔MS
ESP.wdtFeed(); //喂狗
if (!autoConfig()) //如果自动联网失败的话,就启动smartconfig
{
Serial.println("Start smartconfig");
smartConfig();
}
u8g2.setCursor(0, 24);
u8g2.print("Connted to AP ok");
u8g2.setCursor(0, 64);
u8g2.print("IP: " + WiFi.localIP().toString());//显示本机的IP
u8g2.sendBuffer();
delay(1000); //延时1S
Serial.println("Starting UDP"); //连接时间服务器
Udp.begin(localPort);
Serial.print("Local port: ");
Serial.println(Udp.localPort());
Serial.println("waiting for sync");
setSyncProvider(getNtpTime);
setSyncInterval(300);
pinMode(LEDB, OUTPUT);
pinMode(LEDR, OUTPUT);
pinMode(LEDG, OUTPUT);
digitalWrite(LEDB, LOW);
digitalWrite(LEDG, LOW);
digitalWrite(LEDR, HIGH);
Serial.println("SHT31 test");
if (!sht31.begin(0x44))
{ // Set to 0x45 for alternate i2c addr
Serial.println("Couldn't find SHT31");
while (1) delay(1);
}
Wire.begin();
Wire.setClock(400000);
imu.initialize(); //初始化
Serial.println(imu.testConnection() ? "MPU6050 connection successful" : "MPU6050 connection failed");
unsigned short times = 200; //采样次数
for(int i=0;i<times;i++)
{
imu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz); //读取六轴原始数值
axo += ax; ayo += ay; azo += az; //采样和
gxo += gx; gyo += gy; gzo += gz;
}
axo /= times; ayo /= times; azo /= times; //计算加速度计偏移
gxo /= times; gyo /= times; gzo /= times; //计算陀螺仪偏移
}
void loop()
{
now1 = millis();
if ((now1 - LastTime1 >= 20000) || (LastTime1 == 0)){ //定时读取数据
LastTime1 = now1;
rtemp = sht31.readTemperature();
rhumi = sht31.readHumidity();
Serial.printf("湿度:%.3f, 温度:%.3f\r\n" ,rhumi,rtemp);
}
imuupdate();
if(i == 0)
{
if (timeStatus() != timeNotSet) //已经获取到数据的话
{
if (now() != prevDisplay) //如果本次数据和上次不一样的话,刷新
{
prevDisplay = now();
digitalClockDisplay();
}
}
}
if(i == 1)
{
u8g2.clearBuffer();
u8g2.drawXBM(0, 0, 128, 64, bmp1);
u8g2.sendBuffer();
}
if(i == 2)
{
u8g2.clearBuffer();
u8g2.drawXBM(0, 0, 128, 64, bmp2);
u8g2.sendBuffer();
}
if(i == 3)
{
u8g2.clearBuffer();
u8g2.drawXBM(0, 0, 128, 64, bmp3);
u8g2.sendBuffer();
}
if(i == 4)
{
u8g2.clearBuffer();
u8g2.drawXBM(0, 0, 128, 64, bmp4);
u8g2.sendBuffer();
}
if(j == 0)
{
digitalWrite(LEDB, LOW);
digitalWrite(LEDG, LOW);
digitalWrite(LEDR, LOW);
}
if(j == 1)
{
digitalWrite(LEDB, HIGH);
digitalWrite(LEDG, LOW);
digitalWrite(LEDR, LOW);
}
if(j == 2)
{
digitalWrite(LEDB, HIGH);
digitalWrite(LEDG, HIGH);
digitalWrite(LEDR, LOW);
}
if(j == 3)
{
digitalWrite(LEDB, HIGH);
digitalWrite(LEDG, HIGH);
digitalWrite(LEDR, HIGH);
}
}
//头文件
#ifndef __MAIN_H__
#define __MAIN_H__
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <TimeLib.h>
#include <U8g2lib.h>
#include "Wire.h"
#include "Adafruit_SHT31.h"
#include "MPU6050.h"
MPU6050 imu; //定义对象
int16_t ax, ay, az, gx, gy, gz; //加速度计陀螺仪原始数据
long axo = 0, ayo = 0, azo = 0; //加速度计偏移量
long gxo = 0, gyo = 0, gzo = 0; //陀螺仪偏移量
int flag = 0; //imu移动标志变量
long last_update_time;
int interval = 400;
#define SCL 14 //GPIO14
#define SDA 12 //GPIO12
#define LEDB 16
#define LEDR 13
#define LEDG 2
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, SCL, SDA, U8X8_PIN_NONE); //scl,sda,rst
float rtemp;
float rhumi;
Adafruit_SHT31 sht31 = Adafruit_SHT31();
//---------------------NTP相关参数---------------------
static const char ntpServerName[] = "cn.ntp.org.cn"; //NTP服务器
const int timeZone = 8; //时区,东八区为北京时间
WiFiUDP Udp;
unsigned int localPort = 8888; //连接时间服务器的本地端口号
time_t prevDisplay = 0; //上一次获取到的时间
const int NTP_PACKET_SIZE = 48; //NTP发送数据包长度
byte packetBuffer[NTP_PACKET_SIZE]; //NTP数据包缓冲区
//---------------------Time 相关参数---------------------
int Led_Flag = HIGH; //默认当前灭灯
bool Led_State = false; //灯状态
unsigned long LastTime1 = 0;
unsigned long now1;
int i = 0; //按键标志变量, 控制切换图片
int j = 1; //控制切换灯
typedef struct
{
uint8_t Month; //RTC Date Month (in BCD format).0x01-0x12
uint8_t Date; //RTC Date.1-31
uint16_t Year; //RTC Date Year.2000-2099
uint8_t Hour;
uint8_t Minute;
uint8_t Second;
uint8_t Week;
}RTC_DateTypeDef; //时间结构体
uint8_t PowerOn_Flag = 0; //上电标志位
RTC_DateTypeDef Time; //本次读到的时间
RTC_DateTypeDef Last_Time; //上次读到的时间
boolean isNTPConnected = false;
const unsigned char xing[] U8X8_PROGMEM = {
0x00, 0x00, 0xF8, 0x0F, 0x08, 0x08, 0xF8, 0x0F, 0x08, 0x08, 0xF8, 0x0F, 0x80, 0x00, 0x88, 0x00,
0xF8, 0x1F, 0x84, 0x00, 0x82, 0x00, 0xF8, 0x0F, 0x80, 0x00, 0x80, 0x00, 0xFE, 0x3F, 0x00, 0x00
}; /*星*/
const unsigned char liu[] U8X8_PROGMEM = {
0x40, 0x00, 0x80, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00,
0x20, 0x02, 0x20, 0x04, 0x10, 0x08, 0x10, 0x10, 0x08, 0x10, 0x04, 0x20, 0x02, 0x20, 0x00, 0x00
}; /*六*/
const unsigned char du[] U8X8_PROGMEM = {
0x06,0x00,0x89,0x2F,0x69,0x30,0x36,0x20,0x10,0x20,0x18,0x00,0x18,0x00,0x18,0x00,
0x18,0x00,0x18,0x00,0x18,0x00,0x10,0x00,0x30,0x20,0x60,0x10,0x80,0x0F,0x00,0x00,//℃
};
//自定义图片 128 X 64
//添加自己的图片数组
static const unsigned char bmp1[] U8X8_PROGMEM = {};
static const unsigned char bmp2[] U8X8_PROGMEM = {};
static const unsigned char bmp3[] U8X8_PROGMEM ={};
static const unsigned char bmp4[] U8X8_PROGMEM ={};
time_t getNtpTime();
void digitalClockDisplay();
void printDigits(int digits);
void sendNTPpacket(IPAddress &address);
void displayimage();
void imuupdate();
#endif
5.效果展示
这个里的led灯买错了,本来是一个RGD彩灯,结果买成了三阶亮度的白灯。上摇可以调节灯的显示状态。
左摇和右摇可以切换图片显示。