不脱发的程序猿 发表于 2023-9-7 21:35:54

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]
查看完整版本: FireBeetle 2 ESP32-S3制作迷你网络时钟