本帖最后由 不脱发的程序猿 于 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[STACKDEPTH]; //线程堆
- 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[montht]);
- SSD1315_Content_Display(&dev, 10, PAGE_3, 9, 2, number9x16[monthu]);
- SSD1315_Content_Display(&dev, 18, PAGE_3, 8, 2, underline_icon);
- SSD1315_Content_Display(&dev, 26, PAGE_3, 9, 2, number9x16[dayt]);
- SSD1315_Content_Display(&dev, 34, PAGE_3, 9, 2, number9x16[dayu]);
- SSD1315_Content_Display(&dev, 1, PAGE_5, 16, 4, number16x32[ht]);
- SSD1315_Content_Display(&dev, 17, PAGE_5, 16, 4, number16x32[hu]);
- SSD1315_Content_Display(&dev, 33, PAGE_5, 16, 4, colon);
- SSD1315_Content_Display(&dev, 49, PAGE_5, 16, 4, number16x32[mt]);
- SSD1315_Content_Display(&dev, 65, PAGE_5, 16, 4, number16x32[mu]);
- SSD1315_Content_Display(&dev, 81, PAGE_5, 16, 4, point);
- SSD1315_Content_Display(&dev, 96, PAGE_5, 16, 4, number16x32[st]);
- SSD1315_Content_Display(&dev, 112, PAGE_5, 16, 4, number16x32[su]);
- /*更新距离上次更新度过的时长*/
- 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[lht]);
- SSD1315_Content_Display(&dev, 86, PAGE_1, 9, 2, number9x16[lhu]);
-
- 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
- * 1 SNTP_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[64];
-
- 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[4]; //发送缓冲区
- SSD1315_STATUE statue;
-
- /*关闭显示*/
- txbuffer[0] = 0xae;
- statue = dev->write(dev->adress,COMMAND,txbuffer,1);
-
- /*修改显示模式为水平地址模式*/
- txbuffer[0] = 0x20;
- txbuffer[1] = 0x00;
- statue = dev->write(dev->adress,COMMAND,txbuffer,2);
-
- /*设置显示模式*/
- txbuffer[0] = dev->display_mode;
- statue = dev->write(dev->adress,COMMAND,txbuffer,1);
-
- /*设置充电泵电压*/
- txbuffer[0] = 0x8d;
- txbuffer[1] = dev->pump_level;
- statue = dev->write(dev->adress,COMMAND,txbuffer,2);
-
- /*开启显示*/
- txbuffer[0] = 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[3]; //发送缓冲区
- SSD1315_STATUE statue;
-
- /*写入显示列地址*/
- txbuffer[0] = 0x21;
- txbuffer[1] = start_col-1;
- txbuffer[2] = start_col + image_long -2;
- statue = dev->write(dev->adress,COMMAND,txbuffer,3);
-
- /*写入显示页地址*/
- txbuffer[0] = 0x22;
- txbuffer[1] = start_page;
- txbuffer[2] = 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[8] = {0,0,0,0,0,0,0,0},txbuffer[3];
-
- for(i = 1; i <= 128; i++)
- {
- /*更新显示的列和页的地址指向*/
- txbuffer[0] = 0x21;
- txbuffer[1] = (i-1)%16*8;
- txbuffer[2] = i%16*8-1;
- statue = dev->write(dev->adress,COMMAND,txbuffer,3);
- txbuffer[0] = 0x22;
- txbuffer[1] = (i-1)/16;
- txbuffer[2] = (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;
- }
复制代码
|
|