FireBeetle 2 ESP32-S3制作迷你网络时钟
本帖最后由 不脱发的程序猿 于 2023-9-7 21:53 编辑本篇博文使用FireBeetle 2 ESP32-S3驱动OLED模块制作迷你网络时钟,效果如下所示:1、硬件连线OLED是一款无需任何背景,自发光式的显示模块,驱动芯片为SSD1306,其分辨率为12864,具有IIC/SPI两种通信方式。这里我们使用IIC连线方式,连线方式如下图所示:2、软件设计思路1、本时钟基于SNTP协议获取时间,使用内部的RTC走时,校时服务器来自阿里云。由于本时钟依赖于芯片内部的RTC,因此不具备掉电走时的特性,每次掉电后时间信息将重置。2、时钟的程序包含三个事件和一个任务。三个事件分别为WiFi连接事件、WiFi断联事件和SNTP同步事件,它们之间各自负责系统时间和系统状态的更新。任务负责OLED屏幕显示内容的刷新,以1Hz频率运行,其在FreeRTOS中的任务优先级1。3、SSD1315的驱动程序为本人杜撰的精简适配版本,基于ESP32的IIC总线API进行的适配,均为线程安全的。4、本时钟上电即尝试连接到AP,在连接到AP后立即向设定的SNTP服务器发送请求,尝试进行校时,后续将按照设定的周期定期对时间进行校正,若在上电时或运行时出现AP断联的情况,程序将每隔5秒尝试重新连接到AP。3、代码实现主程序驱动如下所示:#include "freertos/FreeRTOS.h"
#include "esp_wifi.h"
#include "esp_system.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "driver/gpio.h"
#include "esp_sntp.h"
#include "SNTP_APP.h"
#include "SSD1315.h"
#include "image.h"
/*OLED屏幕刷新线程栈深度*/
#define STACKDEPTH 1000 //线程堆深度
/*SNTP校时周期(分钟)*/
#define SNTPSYNCCYCLE 10
/*全局变量*/
volatile uint8_t updata_sign = 0; //时间更新标志位 1有效
volatile uint8_t sntp_init_sign = 0; //SNTP初始化标志位 1有效
StackType_t thread_stack; //线程堆
StaticTask_t xTask; //存储线程信息的结构体
TaskHandle_t taskHandle;
esp_err_t event_handler(void *ctx, system_event_t *event)
{
return ESP_OK;
}
/*
* WIFI连接事件处理
*/
void wifi_event_connect_handle(void* event_handler_arg,esp_event_base_t event_base,int32_t event_id,void* event_data)
{
/*获取事件数据*/
wifi_event_sta_connected_t* data = (wifi_event_sta_connected_t*)event_data;
printf("Connected to the Wifi Successful & Tiggle Special Event\n");
printf("Event_base:%s\n",event_base);
printf("Event Id:%d\n",event_id);
printf("Wifi SSID:%.13s\n" , data->ssid);
printf("Wifi Channel:%d\n" , data->channel);
/*在OLED上显示WiFi连接成功标志*/
SSD1315_Content_Display(&dev , 112, PAGE_3, 16, 2, wificonnect_icon);
/*检查SNTP是否被初始化过*/
if(!sntp_init_sign)
{
SntpSyncCycleSet(SNTPSYNCCYCLE);
SntpNoticeCallbackSet(SntpCallback); //设置时间同步回调函数
SntpGetTimeImed(50);
SysTimeConsoleOutput();
sntp_init_sign = 1;
}
}
/*
* wifi断联事件处理
*/
void wifi_event_disconnect_handle(void* event_handler_arg,esp_event_base_t event_base,int32_t event_id,void* event_data)
{
wifi_event_sta_disconnected_t* eventdata = (wifi_event_sta_disconnected_t*)event_data;
if(eventdata->reason == WIFI_REASON_NO_AP_FOUND)
{
printf("Can't Found The AP\n");
vTaskDelay(5000/portTICK_PERIOD_MS);
}
esp_wifi_connect();
SSD1315_Content_Display(&dev , 112, PAGE_3, 16, 2, wifierror_icon);
}
/********************************
* OLED内容刷新函数
********************************/
void ScreenReflash_thread(void* data)
{
time_t now;
struct tm timeinfo;
uint8_t ht,hu,mt,mu,st,su; //小时十位个位、分钟十位个位、秒十位个位
uint8_t montht,monthu,dayt,dayu;
uint8_t lastUpdataTime;
for(;;)
{
/*更新时间*/
time(&now);
localtime_r(&now, &timeinfo);
montht = (timeinfo.tm_mon+1)/10;
monthu = (timeinfo.tm_mon+1)%10;
dayt = timeinfo.tm_mday/10;
dayu = timeinfo.tm_mday%10;
ht = timeinfo.tm_hour/10;
hu = timeinfo.tm_hour%10;
mt = timeinfo.tm_min/10;
mu = timeinfo.tm_min%10;
st = timeinfo.tm_sec/10;
su = timeinfo.tm_sec%10;
SSD1315_Content_Display(&dev, 1, PAGE_3, 9, 2, number9x16);
SSD1315_Content_Display(&dev, 10, PAGE_3, 9, 2, number9x16);
SSD1315_Content_Display(&dev, 18, PAGE_3, 8, 2, underline_icon);
SSD1315_Content_Display(&dev, 26, PAGE_3, 9, 2, number9x16);
SSD1315_Content_Display(&dev, 34, PAGE_3, 9, 2, number9x16);
SSD1315_Content_Display(&dev, 1, PAGE_5, 16, 4, number16x32);
SSD1315_Content_Display(&dev, 17, PAGE_5, 16, 4, number16x32);
SSD1315_Content_Display(&dev, 33, PAGE_5, 16, 4, colon);
SSD1315_Content_Display(&dev, 49, PAGE_5, 16, 4, number16x32);
SSD1315_Content_Display(&dev, 65, PAGE_5, 16, 4, number16x32);
SSD1315_Content_Display(&dev, 81, PAGE_5, 16, 4, point);
SSD1315_Content_Display(&dev, 96, PAGE_5, 16, 4, number16x32);
SSD1315_Content_Display(&dev, 112, PAGE_5, 16, 4, number16x32);
/*更新距离上次更新度过的时长*/
uint8_t lht,lhu; //距上次更新小时数十位个位
if(updata_sign)
{
updata_sign = 0;
lastUpdataTime = 0;
SSD1315_Content_Display(&dev , 94, PAGE_3, 16, 2, ntpnormal_icon);
}
else
{
lastUpdataTime = timeinfo.tm_hour;
}
lht = lastUpdataTime/10;
lhu = lastUpdataTime%10;
SSD1315_Content_Display(&dev, 78, PAGE_1, 9, 2, number9x16);
SSD1315_Content_Display(&dev, 86, PAGE_1, 9, 2, number9x16);
vTaskDelay(1000/portTICK_PERIOD_MS); //释放线程1s
}
}
void app_main(void)
{
nvs_flash_init();
tcpip_adapter_init();
ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) );
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
wifi_config_t sta_config = {
.sta = {
.ssid = CONFIG_ESP_WIFI_SSID,
.password = CONFIG_ESP_WIFI_PASSWORD,
.bssid_set = false
}
};
ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &sta_config) );
ESP_ERROR_CHECK( esp_wifi_start() );
ESP_ERROR_CHECK( esp_wifi_connect() );
Esp32I2cInit(); //初始化I2C和SSD1315
SSD1315_Clear_Screen(&dev);
SSD1315_Content_Display(&dev , 94, PAGE_3, 16, 2, ntperror_icon);
SSD1315_Content_Display(&dev , 112, PAGE_3, 16, 2, wifierror_icon);
SSD1315_Content_Display(&dev, 1, PAGE_1, 80, 2, updata_icon);
SSD1315_Content_Display(&dev, 97, PAGE_1, 8, 2, H_icon);
SSD1315_Content_Display(&dev, 107, PAGE_1, 16, 2, qian_icon);
/*注册WIFI连接h和断开连接事件处理函数*/
esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED, wifi_event_connect_handle, NULL, NULL);
esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, wifi_event_disconnect_handle, NULL, NULL);
/*创建屏幕刷新任务*/
taskHandle = xTaskCreateStatic(ScreenReflash_thread, "ScreenReflash", STACKDEPTH, NULL, \
1|portPRIVILEGE_BIT , thread_stack, &xTask);
while (true) {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}SNTP驱动程序如下:#include <stdio.h>
#include "SNTP_APP.h"
extern volatile uint8_t updata_sign;
/****************************************
* 以阻塞方式立刻从NTP服务器获取时间
* 输入: retry_times_max最大重试次数<255
* 输出:校时状态
* 0 SNTP_SUCCESS
* 1SNTP_TIMEOUT
* 注意:调用该函数即开启自动同步功能,
* 同步周期默认1小时,可调用函数
* SntpSyncCycleSet()设置同步周期。
* 如需通知同步结果,使用
****************************************/
SNTP_STATUE SntpGetTimeImed(uint8_t retry_times_max)
{
uint8_t retry=0;
/*初始化SNTP并校时*/
sntp_setoperatingmode(SNTP_OPMODE_POLL);
sntp_setservername(0, NTP_URL);
sntp_init();
/*设置时区并获取系统时间*/
setenv("TZ", "CST-8", 1);
tzset();
while (sntp_get_sync_status() != SNTP_SYNC_STATUS_COMPLETED && retry < retry_times_max) {
retry++;;
vTaskDelay(500 / portTICK_PERIOD_MS);
}
sntp_set_sync_status(SNTP_SYNC_STATUS_RESET); //复位同步结果
/*输出校时结果*/
if(retry == retry_times_max)
{
printf("SNTP Sync Time Out(Retry %d Times)\n" , retry_times_max);
return SNTP_TIMEOUT;
}
else
{
printf("SNTP Sync Successful(Retry %d Times)\n" , retry);
return SNTP_SUCCESS;
}
}
/***************************************
* 控制台输出当前时间
* 输入:无
* 输出:无
**************************************/
void SysTimeConsoleOutput(void)
{
time_t now;
struct tm timeinfo;
char strftime_buf;
time(&now); //获取系统时间s
localtime_r(&now, &timeinfo); //将获取到的系统时间s转换为带有格式的timeinfo信息
strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
printf("System Time: %s\n" , strftime_buf);
}
/**************************************
* SNTP同步周期设定
* 输入:cycle 同步周期:分钟
* 输出:无
**************************************/
void SntpSyncCycleSet(uint16_t cycle)
{
sntp_set_sync_interval(cycle*60000);
}
/**************************************
* 设置SNTP同步通知回调函数
* 输入:callback sntp_sync_time_cb_t样式的函数
**************************************/
void SntpNoticeCallbackSet(sntp_sync_time_cb_t callback)
{
sntp_set_time_sync_notification_cb(callback);
}
/**************************************
* SNTP通知用户回调函数
**************************************/
void SntpCallback(struct timeval *tv)
{
if(sntp_get_sync_status() == SNTP_SYNC_STATUS_COMPLETED)
{
printf("Sntp Auto Sync Finish\n");
SysTimeConsoleOutput();
updata_sign = 1;
}
else
{
printf("Sntp Auto Sync Fail\n");
}
}
OLED驱动程序如下:/*******************************************************
* SSD1315 API Source
* build time: 2021/04/03
* version: 1.0
* author: 锋
* note:
* 1)本驱动仅适配了水平地址模式,因此用于显示的图像取模必须是
* 数据水平、字节竖直、像素数据反序
* 2)使用指南
* 使用SSD1315_INIT初始化,清屏后即可正常显示
******************************************************/
#include "SSD1315.h"
#include "driver/i2c.h"
#define I2CPORT 1 //I2C端口号
/*SSD1315驱动配置结构体*/
SSD1315_CONF_STRUCT dev =
{
.write = IIC_Write,
.read= IIC_Read,
.adress = SSD1315_ADRESS_DC_L,
.pump_level = CHARGE_PUMP_LEVEL_MEDIUM,
.display_mode = INVERSE_DISPLAY
};
/*ESP32 I2C总线配置结构体*/
i2c_config_t i2c =
{
.mode = I2C_MODE_MASTER,
.sda_io_num = GPIO_NUM_32,
.sda_pullup_en = true,
.scl_io_num = GPIO_NUM_33,
.scl_pullup_en = true,
.master.clk_speed = 400000
};
/*
* ESP32 IIC外设初始化函数
*/
void Esp32I2cInit()
{
i2c_param_config(I2CPORT, &i2c); //配置IIC
i2c_driver_install(I2CPORT, I2C_MODE_MASTER, 0, 0, ESP_INTR_FLAG_LEVEL3); //初始化驱动程序
SSD1315_Init(&dev);
}
/*
* SSD1315初始化函数
* &功能:初始化SSD1315并开启显示
* &参数:*dev 包含配置信息的SSD1315_CONF_STRUCT结构体指针
* &返回:函数的运行状态
*/
SSD1315_STATUE SSD1315_Init(SSD1315_CONF_STRUCT* dev)
{
uint8_t txbuffer; //发送缓冲区
SSD1315_STATUE statue;
/*关闭显示*/
txbuffer = 0xae;
statue = dev->write(dev->adress,COMMAND,txbuffer,1);
/*修改显示模式为水平地址模式*/
txbuffer = 0x20;
txbuffer = 0x00;
statue = dev->write(dev->adress,COMMAND,txbuffer,2);
/*设置显示模式*/
txbuffer = dev->display_mode;
statue = dev->write(dev->adress,COMMAND,txbuffer,1);
/*设置充电泵电压*/
txbuffer = 0x8d;
txbuffer = dev->pump_level;
statue = dev->write(dev->adress,COMMAND,txbuffer,2);
/*开启显示*/
txbuffer = 0xaf;
statue = dev->write(dev->adress,COMMAND,txbuffer,1);
return statue;
}
/*******************************基础显示部分*************************/
/*
* SSD1315显示内容写入函数
* &参数:dev 包含SSD1315配置信息的结构体指针
* start_col 图像开始列
* start_page 图像开始页,在枚举类型 PAGE_X 中选取(x = 1-8)
* image_long 图像像素长
* image_page 图像页长(图像宽/8)
* pdata 包含图像信息的数组指针
*/
SSD1315_STATUE SSD1315_Content_Display(SSD1315_CONF_STRUCT* dev,uint8_t start_col,PAGE_X start_page,uint8_t image_long,uint8_t image_page,const uint8_t *pdata)
{
uint8_t txbuffer; //发送缓冲区
SSD1315_STATUE statue;
/*写入显示列地址*/
txbuffer = 0x21;
txbuffer = start_col-1;
txbuffer = start_col + image_long -2;
statue = dev->write(dev->adress,COMMAND,txbuffer,3);
/*写入显示页地址*/
txbuffer = 0x22;
txbuffer = start_page;
txbuffer = start_page + image_page -1;
statue = dev->write(dev->adress,COMMAND,txbuffer,3);
/*写入显示数据*/
statue = dev->write(dev->adress,DATA,pdata,image_long*image_page);
return statue;
}
/*
* SSD1315清屏函数
*/
SSD1315_STATUE SSD1315_Clear_Screen(SSD1315_CONF_STRUCT* dev)
{
uint8_t statue,i;
uint8_t empty = {0,0,0,0,0,0,0,0},txbuffer;
for(i = 1; i <= 128; i++)
{
/*更新显示的列和页的地址指向*/
txbuffer = 0x21;
txbuffer = (i-1)%16*8;
txbuffer = i%16*8-1;
statue = dev->write(dev->adress,COMMAND,txbuffer,3);
txbuffer = 0x22;
txbuffer = (i-1)/16;
txbuffer = (i-1)/16;
statue = dev->write(dev->adress,COMMAND,txbuffer,3);
/*擦除在该地址范围上的显示数据*/
statue = dev->write(dev->adress,DATA,empty,8);
}
return statue;
}
/*******************************进阶显示部分*************************/
/*
* &显示淡出函数
*/
/*
* API适配部分
*/
uint8_t IIC_Write(uint8_t adress , CORD_ENUM CorD , const uint8_t *pdata , int size)
{
uint8_t statue;
i2c_cmd_handle_t handle;
/*******************************************************
* &注意:
* &在该函数内必须实现:
* 1)从机设备地址adress的传送
* 2)控制字CorD的传送
* 3)size个字节数据的传送
* 4)返回传送状态
******************************************************/
handle = i2c_cmd_link_create(); //创建命令链
i2c_master_start(handle); //IIC开始标志
i2c_master_write_byte(handle, adress, true); //写地址
i2c_master_write_byte(handle, CorD, true); //写数据类型
i2c_master_write(handle, pdata, size, true);//写数据
i2c_master_stop(handle); //终止标志
statue = i2c_master_cmd_begin(I2CPORT, handle, 100); //开始IIC指令
i2c_cmd_link_delete(handle);
return statue;
}
uint8_t IIC_Read(uint8_t adress , CORD_ENUM CorD , const uint8_t *pdata , int size)
{
uint8_t statue;
i2c_cmd_handle_t handle;
/*******************************************************
* &注意:
* &在该函数内必须实现:
* 1)从机设备地址adress的传送
* 2)控制字CorD的传送
* 3)size个字节数据的接收
* 4)返回传送状态
******************************************************/
handle = i2c_cmd_link_create(); //创建命令链
i2c_master_start(handle); //IIC开始标志
i2c_master_write_byte(handle, adress|0x01, true); //写地址
i2c_master_write_byte(handle, CorD, true); //写数据类型
i2c_master_read(handle, (uint8_t*)pdata, size, I2C_MASTER_LAST_NACK); //读数据
i2c_master_stop(handle); //终止标志
statue = i2c_master_cmd_begin(I2CPORT, handle, 100); //开始IIC指令
i2c_cmd_link_delete(handle);
return statue;
}
页:
[1]