天气时钟:WiFi模块、OLED模块开发
- WiFi模块
- WiFi库介绍
- 代码实现
- OLED模块
- OLED屏幕介绍
- 基本原理
- OLED的优点
- OLED的缺点
- OLED在嵌入式系统中的应用
- OLED屏幕的引脚及编程
- u8g2库介绍
- 特点
- 常用函数
- 初始化和设置
- 文本和图形绘制
- 缓冲区管理
- 其他功能
- NTP模块开发
- NTP介绍
- NTPClient类介绍
- 构造函数
- 常用函数
- gmtime介绍
- 函数原型
- 参数
- 返回值
- 注意事项
- 使用场景
上一篇文章,我们了解了天气时钟用到的材料、以及串口模块的开发,本篇文章,我们将把重心移到WiFi模块、OLED模块和NTP模块的开发,但在此之前,我们仍有必要学习相关知识。
WiFi模块
WiFi库介绍
WiFi
库是用于Arduino平台上的WiFi模块(如ESP8266等)进行无线网络连接的一个重要库。它提供了丰富的功能来管理WiFi连接、网络配置和数据传输。以下是WiFi
库的一些关键功能和用法:
- 模式设置:
WiFi.mode(mode)
: 设置WiFi的工作模式。常用模式包括:WIFI_STA
: 站点模式,设备作为客户端连接到路由器。WIFI_AP
: 接入点模式,设备作为热点供其他设备连接。WIFI_AP_STA
: 同时支持站点和接入点模式。
关于更多解释,可以参考之前的文章: 【STA模式、AP模式、体验天气时钟】
-
连接管理:
WiFi.begin(ssid, password)
: 开始连接到指定的WiFi网络,ssid
是网络名称,password
是网络密码。WiFi.disconnect()
: 断开当前的WiFi连接。WiFi.status()
: 获取当前的WiFi连接状态,返回值包括WL_CONNECTED
(已连接)、WL_DISCONNECTED
(未连接)等。
-
网络信息:
WiFi.localIP()
: 获取设备的本地IP地址。WiFi.SSID()
: 获取当前连接的网络SSID。WiFi.RSSI()
: 获取当前连接的信号强度。
-
其他功能:
WiFi.scanNetworks()
: 扫描周围可用的WiFi网络。WiFi.softAP(ssid, password)
: 设置设备为接入点模式,创建一个新的WiFi网络。
WiFi
库通常用于物联网(IOT)项目中,使设备能够连接到互联网或局域网,从而实现远程数据传输、设备控制和监控等功能。通过结合其他网络协议(如HTTP、MQTT等),可以实现更复杂的网络应用。
代码实现
头文件
#ifndef WIFI_H
#define WIFI_H#include <Arduino.h>
#include <ESP8266WiFi.h>// 定义WiFi名称及密码
#define WiFi_SSID "josh"
#define WiFi_PASSWORD "38669836"void wifiInit();
bool wifiConnect(const char *ssid = WiFi_SSID, const char *password = WiFi_PASSWORD);#endif
cpp文件
#include "wifi.h"
#include "serial.h"// WiFi初始化函数
void wifiInit() {WiFi.mode(WIFI_STA); // 设置为无线终端模式,方便连接网络WiFi.disconnect(); // 先断开连接再重连,以免发生某些故障delay(100); // 等待WIFI模块稳定serialPrint("WIFI模块初始化完成");
}// WiFi连接函数,返回布尔值
bool wifiConnect(const char *ssid, const char *password) {serialPrint("正在连接WiFi...");WiFi.begin(ssid, password);int attempts = 0; // 记录连接次数while(WiFi.status() != WL_CONNECTED && attempts < 20) {delay(1000);serialPrint("等待连接..." + String(attempts + 1));attempts++;}// 如果WiFi连接成功if(WiFi.status() == WL_CONNECTED){serialPrint("WiFi连接成功");serialPrint("IP地址:" + WiFi.localIP().toString()); // 转成字符串并输出return true;} else {serialPrint("WiFi连接失败");return false;}
}
OLED模块
OLED模块中,我们将对OLED屏幕进行操作,导入并调用U8g2库中的内容。
OLED屏幕介绍
OLED(有机发光二极管)屏幕是一种显示技术,广泛应用于各种电子设备中,包括智能手机、电视、可穿戴设备和嵌入式系统。
基本原理
- 自发光: OLED屏幕由有机材料制成,这些材料在通电时会发光。与LCD不同,OLED不需要背光源,因此可以实现更薄的显示器。
- 像素独立控制: 每个像素都可以独立控制其亮度和颜色,这使得OLED屏幕能够显示深邃的黑色和高对比度的图像。
OLED的优点
- 高对比度和深黑色: 由于OLED像素可以完全关闭,因此可以显示真正的黑色,提供极高的对比度。
- 广视角: OLED屏幕在广视角下仍能保持色彩和亮度的一致性。
- 快速响应时间: OLED的响应时间非常快,适合显示快速移动的图像,如视频和游戏。
- 轻薄设计: 由于不需要背光,OLED屏幕可以设计得非常轻薄。
- 灵活性: OLED材料可以制成柔性显示器,适用于可弯曲或可折叠的设备。
OLED的缺点
- 寿命问题: OLED的有机材料可能会随着时间的推移而降解,导致亮度下降或色彩变化。
- 烧屏现象: 长时间显示静态图像可能导致某些像素永久性地保持某种状态,造成“烧屏”。
- 成本较高: 生产OLED屏幕的成本通常高于LCD,特别是在大尺寸显示器中。
OLED在嵌入式系统中的应用
在嵌入式系统中,OLED屏幕通常用于显示简单的图形和文本信息。常见的应用包括:
- 微型显示器: 用于智能手表、健身追踪器等小型设备。
- 信息面板: 用于家用电器、工业设备的状态显示。
- 开发板显示: 在Arduino、Raspberry Pi等开发板项目中,OLED屏幕常用于显示传感器数据、系统状态等。
OLED屏幕的引脚及编程
在四针OLED屏幕中,SCL(Serial Clock Line)
表示串行时钟线,用于同步数据传输的时钟信号,它由微控制器等主设备生成,控制数据的传输速率;SDA(Serial Data Line)
表示串行数据线,用于传输实际的数据,主设备和从设备之间通过这条线进行数据的发送和接收。
以上提到的主设备和从设备,通常而言,主设备
是控制通信的设备,负责发起数据传输,发送时钟信号和管理从设备,例如Arduino通常充当主设备,负责读取传感器数据或者向显示器发送信息;从设备
是被主设备控制的设备,负责等待主设备的指令,并且响应主设备的请求,例如OLED显示屏可以作为从设备。
在编程中,使用OLED屏幕通常需要一个专用的库来处理显示控制。以Arduino平台为例,常用的库有U8g2
、Adafruit_SSD1306
等。这些库提供了丰富的API来绘制文本、图形和图像。
u8g2库介绍
U8g2库是一个用于小型显示器(如OLED和LCD)的图形库,支持多种显示器类型和接口。它是U8glib库的升级版,提供了更好的性能和更多的功能。以下是U8g2库的详细介绍:
特点
- 多种显示器支持: U8g2支持多种显示器,包括
SSD1306(本次使用的类型)
、SH1106、ST7920等,适用于不同的分辨率和接口(I2C、SPI等)。
多种字体支持: 提供了丰富的字体选择,支持不同大小和风格的字体,适合各种显示需求。
-
UTF-8支持: U8g2支持UTF-8编码,可以显示多语言字符,包括中文、日文等。
-
图形绘制功能: 提供了绘制基本图形(如线条、矩形、圆形)的功能,以及图像和位图的显示。
-
双缓冲和单缓冲模式: 支持双缓冲模式(U8g2)和单缓冲模式(U8x8),以适应不同的内存需求和性能要求。
常用函数
U8g2
库是一个强大的图形库,专为小型显示器(如OLED和LCD)设计,支持多种显示器类型和接口。以下是U8g2
库中一些常用函数的详细介绍:
初始化和设置
-
begin()
- 功能: 初始化显示器。
- 用法: 在
setup()
函数中调用,确保在使用其他绘图函数之前已正确初始化显示器。
-
setFont(const uint8_t *font)
- 功能: 设置当前使用的字体。
- 参数:
font
是字体的指针,U8g2
库提供了多种字体可供选择。 - 示例:
u8g2.setFont(u8g2_font_ncenB08_tr);
-
enableUTF8Print()
- 功能: 启用UTF-8编码支持,使
print()
函数能够正确显示UTF-8编码的字符。 - 用法: 在初始化时调用,以确保显示器能够处理多语言字符。
- 示例:
u8g2.enableUTF8Print();
- 功能: 启用UTF-8编码支持,使
文本和图形绘制
-
setCursor(int x, int y)
- 功能: 设置文本光标的位置。
- 参数:
x
,y
: 光标的起始坐标。
- 示例:
u8g2.setCursor(0, 10);
-
print()
-
功能: 类似于Arduino的
Serial.print()
,用于在显示器上打印文本。 -
用法: 需要在
setCursor()
设置光标位置后使用。 -
示例:
u8g2.setCursor(0, 10); u8g2.print("Hello, World!");
-
-
drawStr(int x, int y, const char *s)
- 功能: 在指定位置绘制字符串。
- 参数:
x
,y
: 字符串的起始坐标。s
: 要绘制的字符串。
- 示例:
u8g2.drawStr(0, 10, "Hello, World!");
-
drawLine(int x1, int y1, int x2, int y2)
- 功能: 绘制一条线。
- 参数:
x1
,y1
: 线的起点坐标。x2
,y2
: 线的终点坐标。
- 示例:
u8g2.drawLine(0, 0, 128, 64);
-
drawBox(int x, int y, int w, int h)
- 功能: 绘制一个填充矩形。
- 参数:
x
,y
: 矩形的左上角坐标。w
,h
: 矩形的宽度和高度。
- 示例:
u8g2.drawBox(10, 10, 20, 20);
缓冲区管理
-
clearBuffer()
-
功能: 清空显示缓冲区。
-
用法: 在开始新的绘制之前调用,以清除之前的内容。
-
示例:
u8g2.clearBuffer();
-
-
sendBuffer()
-
功能: 将缓冲区内容发送到显示器。
-
用法: 在完成所有绘制操作后调用,以更新显示器内容。
-
示例:
u8g2.sendBuffer();
-
其他功能
-
setContrast(uint8_t contrast)
-
功能: 设置显示器的对比度。
-
参数:
contrast
为对比度值,范围通常为0到255。 -
示例:
u8g2.setContrast(128);
-
-
setDisplayRotation(uint8_t rotation)
-
功能: 设置显示器的旋转角度。
-
参数:
rotation
可以是0(正常)、1(90度旋转)、2(180度旋转)、3(270度旋转)。 -
示例:
u8g2.setDisplayRotation(U8G2_R1);
-
通过这些函数,U8g2
库提供了丰富的功能,帮助开发者在小型显示器上实现复杂的图形和文本显示。结合使用这些函数,可以在嵌入式系统中创建直观的用户界面和信息显示。
头文件
#ifndef OLED_H
#define OLED_H#include <Arduino.h>
#include <U8g2lib.h>extern U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2; // 设置OLED屏幕型号及相关信息void oledInit();
void oledShow(uint8_t x, uint8_t y, String text);
void oledClear();#endif
cpp文件
#include "oled.h"
#include "serial.h"U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ 5, /* data=*/ 4, /* reset=*/ U8X8_PIN_NONE);void oledInit() {serialPrint("初始化OLED显示屏...");u8g2.begin(); // 初始化显示器u8g2.clearBuffer(); // 清空显示缓冲区// 设置中文字体并启用UTF8编码支持u8g2.setFont(u8g2_font_wqy14_t_gb2312); // 设置字体u8g2.enableUTF8Print(); // 初始化以确保能够正确显示字符// 刷新显示u8g2.sendBuffer(); // 把将缓冲区信息送到显示屏serialPrint("OLED初始化完成");
}void oledShow(uint8_t x, uint8_t y, String text) {// 设置光标位置并显示文本u8g2.setCursor(x, y); // 指定显示坐标u8g2.print(text); // 指定输出的信息u8g2.sendBuffer(); // 刷新显示
}void oledClear() {// 清空显示缓冲区并刷新显示u8g2.clearBuffer();u8g2.sendBuffer();
}
NTP模块开发
NTP介绍
NTP(Network Time Protocol,网络时间协议)是一种用于在计算机网络中同步时间的协议。它的主要功能和特点包括:
-
时间同步:
- NTP的主要作用是确保网络中所有设备的时间保持一致。它通过从时间服务器获取精确的时间信息来调整本地时钟。
-
分层结构:
- NTP使用分层结构,称为“层级”(Stratum)。Stratum 0是参考时钟(如原子钟、GPS时钟),Stratum 1是直接连接到Stratum 0的服务器,依此类推。每个层级的服务器从上一级获取时间信息。
-
高精度:
- NTP可以提供毫秒级的时间精度,甚至在理想条件下达到微秒级。这对于需要精确时间的应用(如金融交易、科学实验)非常重要。
-
自我调整:
- NTP不仅可以调整时钟的时间,还可以调整时钟的频率,以补偿硬件时钟的漂移。
-
广泛使用:
- NTP是互联网中最古老的协议之一,广泛用于各种操作系统和网络设备中。
-
安全性:
- 尽管NTP本身是一个开放协议,但在现代应用中,通常会结合其他安全措施(如NTPsec)来防止时间欺骗攻击。
在嵌入式系统中,NTP通常用于确保设备的时间与互联网时间保持同步,这对于时间戳记录、定时任务和其他时间敏感的操作非常重要。
NTPClient类介绍
NTPClient
是一个用于与网络时间协议(NTP)服务器进行通信的客户端类,广泛用于物联网设备和嵌入式系统中,以确保设备的时钟与全球标准时间保持同步。以下是对 NTPClient
的详细介绍,包括其构造函数和常用函数。
构造函数
NTPClient
的构造函数用于初始化客户端实例,配置与NTP服务器的通信细节。常见的构造函数原型如下:
NTPClient(UDP& udp, const char* poolServerName = "pool.ntp.org", int timeOffset = 0, int updateInterval = 60000
);
UDP& udp
: 引用一个UDP对象(通常是WiFiUDP
),用于通过UDP协议进行通信。const char* poolServerName
: NTP服务器的地址,默认是"pool.ntp.org"
。int timeOffset
: 时区偏移量,以秒为单位。用于调整时间以匹配本地时区。int updateInterval
: 自动更新时间的间隔,以毫秒为单位。默认是60000
毫秒(1分钟)。
常用函数
NTPClient库
提供了一些常用的函数,用于初始化、更新和获取时间:
-
begin()
:- 初始化NTP客户端,准备与NTP服务器进行通信。
-
update()
:- 从NTP服务器获取最新的时间戳,并更新内部时间。通常在需要获取最新时间之前调用。
-
forceUpdate()
:- 强制从NTP服务器获取时间,即使在设定的更新间隔内。用于需要立即同步时间的场合。
-
getEpochTime()
:- 返回当前时间的epoch时间戳(自1970年1月1日以来的秒数)。适用于需要处理时间戳的应用。
-
getFormattedTime()
:- 返回格式化的时间字符串,通常为
HH:MM:SS
格式。
- 返回格式化的时间字符串,通常为
-
getHours()
:- 返回当前时间的小时部分。
-
getMinutes()
:- 返回当前时间的分钟部分。
-
getSeconds()
:- 返回当前时间的秒部分。
-
setTimeOffset(int offset)
:- 设置时区偏移量,以秒为单位。用于调整时间以匹配本地时区。
-
setUpdateInterval(unsigned long interval)
:- 设置自动更新时间的间隔,以毫秒为单位。用于控制多久从NTP服务器更新一次时间。
通过这些构造函数和方法,NTPClient
提供了一个简单而强大的接口,用于在联网设备中实现时间同步功能。
gmtime介绍
gmtime
是C和C++标准库中的一个函数,用于将时间戳(epoch time)转换为协调世界时(UTC)的时间结构。它是 time.h
头文件的一部分。以下是对 gmtime
函数的详细介绍:
函数原型
struct tm *gmtime(const time_t *timer);
参数
const time_t *timer
: 指向一个time_t
类型的指针,表示自1970年1月1日00:00:00 UTC以来的秒数(即epoch时间)。
返回值
- 返回一个指向
tm
结构的指针,该结构包含了转换后的UTC时间信息。 tm
结构包含以下成员:int tm_sec
: 秒,范围为0到59。int tm_min
: 分钟,范围为0到59。int tm_hour
: 小时,范围为0到23。int tm_mday
: 一个月中的第几天,范围为1到31。int tm_mon
: 月份,范围为0到11(0表示一月)。int tm_year
: 自1900年以来的年数。int tm_wday
: 一周中的第几天,范围为0到6(0表示星期天)。int tm_yday
: 一年中的第几天,范围为0到365。int tm_isdst
: 夏令时标志。
注意事项
- 返回的
tm
结构指针指向的是一个静态分配的内存区域,因此每次调用gmtime
后,之前的结果会被覆盖。 - 如果需要保留多个时间转换结果,应该将返回的
tm
结构复制到用户定义的结构中。
使用场景
gmtime
通常用于需要将时间戳转换为可读的UTC时间格式的场合,例如日志记录、时间显示等。它与 localtime
函数相对,后者用于转换为本地时间。
头文件
#ifndef NTP_H
#define NTP_H#include <Arduino.h>
#include <NTPClient.h>
#include <WiFiUdp.h> // 用于初始化NTP客户端void ntpInit(); // 初始化函数
bool ntpSync(); // 同步时间
String ntpGetDate(); // 获取日期
String ntpGetTime(); // 获取具体时间
#endif
cpp文件
#include "ntp.h"
#include "serial.h"
#include "oled.h"WiFiUDP ntpUDP; // 定义一个UDP对象,用于初始化NTPClient对象
NTPClient timeClient(ntpUDP, "ntp.aliyun.com", 8 * 3600); // UDP对象、NTP服务器地址(这里使用阿里云)、中国位于8时区,单位是秒,所以 * 3600// 初始化函数
void ntpInit() {serialPrint("初始化NTP客户端...");timeClient.begin(); // 初始化,准备与NTP服务器通信serialPrint("初始化NTP客户端完成");
}// 获取时间
String ntpGetTime() {timeClient.update(); // 先更新时间,从服务器获取最新的时间戳// 获取时间int hours = timeClient.getHours();int minutes = timeClient.getMinutes();int seconds = timeClient.getSeconds();// 转换成字符串char timeStr[9];// 格式化字符串sprintf(timeStr, "%02d:%02d:%02d", hours, minutes, seconds);return String(timeStr);
}// 获取日期
String ntpGetDate() {timeClient.update(); // 先更新时间,从服务器获取最新的时间戳// 获取时间戳time_t epochTime = timeClient.getEpochTime(); // 获取时间戳struct tm *ptm = gmtime((time_t *)&epochTime); // 获取准确时间,返回一个时间结构体int year = ptm->tm_year + 1900; // 获取年份int month = ptm->tm_mon + 1; // 获取月份int day = ptm->tm_mday; // 获取日期// 转换成字符串char dateStr[9];// 格式化字符串sprintf(dateStr, "%04d-%02d-%02d", year, month, day);return String(dateStr);
}// 同步时间
bool ntpSync() {serialPrint("正在同步网络时间...");oledShow(0, 16, "正在同步网络时间...");if(timeClient.forceUpdate()) { // 强行更新时间serialPrint("时间同步成功");oledClear(); // 清屏操作oledShow(0, 16, "时间同步成功");oledShow(0, 32, "日期: " + ntpGetDate()); // 输出日期oledShow(0, 48, "时间: " + ntpGetTime()); // 输出时间return true;} else {serialPrint("时间同步失败");oledClear();oledShow(0, 16, "时间同步失败");return false;}
}
至此完成了WiFi、OLED、NTP三个模块的开发。下一篇文章我们将利用心知天气完成天气模块,并且对主函数进行编写,完成天气时钟的制作。