759浏览
查看: 759|回复: 0

[ESP8266/ESP32] FireBeetle 2 ESP32-S3制作迷你网络时钟

[复制链接]
本帖最后由 不脱发的程序猿 于 2023-9-7 21:53 编辑

本篇博文使用FireBeetle 2 ESP32-S3驱动OLED模块制作迷你网络时钟,效果如下所示:
FireBeetle 2 ESP32-S3制作迷你网络时钟图1
1、硬件连线
OLED是一款无需任何背景,自发光式的显示模块,驱动芯片为SSD1306,其分辨率为12864,具有IIC/SPI两种通信方式。
FireBeetle 2 ESP32-S3制作迷你网络时钟图2
这里我们使用IIC连线方式,连线方式如下图所示:
FireBeetle 2 ESP32-S3制作迷你网络时钟图3
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、代码实现
主程序驱动如下所示:
  1. #include "freertos/FreeRTOS.h"
  2. #include "esp_wifi.h"
  3. #include "esp_system.h"
  4. #include "esp_event.h"
  5. #include "nvs_flash.h"
  6. #include "driver/gpio.h"
  7. #include "esp_sntp.h"
  8. #include "SNTP_APP.h"
  9. #include "SSD1315.h"
  10. #include "image.h"
  11. /*OLED屏幕刷新线程栈深度*/
  12. #define STACKDEPTH                1000                                //线程堆深度
  13. /*SNTP校时周期(分钟)*/
  14. #define SNTPSYNCCYCLE        10
  15. /*全局变量*/
  16. volatile   uint8_t updata_sign = 0;        //时间更新标志位 1有效
  17. volatile   uint8_t sntp_init_sign = 0; //SNTP初始化标志位 1有效
  18. StackType_t thread_stack[STACKDEPTH];        //线程堆
  19. StaticTask_t xTask;                                                //存储线程信息的结构体
  20. TaskHandle_t taskHandle;
  21. esp_err_t event_handler(void *ctx, system_event_t *event)
  22. {
  23.    return ESP_OK;
  24. }
  25. /*
  26. * WIFI连接事件处理
  27. */
  28. void wifi_event_connect_handle(void* event_handler_arg,esp_event_base_t event_base,int32_t event_id,void* event_data)
  29. {
  30.        /*获取事件数据*/
  31.        wifi_event_sta_connected_t* data = (wifi_event_sta_connected_t*)event_data;
  32.        printf("Connected to the Wifi Successful & Tiggle Special Event\n");
  33.        printf("Event_base:%s\n",event_base);
  34.        printf("Event Id:%d\n",event_id);
  35.        printf("Wifi SSID:%.13s\n" , data->ssid);
  36.        printf("Wifi Channel:%d\n" , data->channel);
  37.        /*在OLED上显示WiFi连接成功标志*/
  38.        SSD1315_Content_Display(&dev , 112, PAGE_3, 16, 2, wificonnect_icon);
  39.        /*检查SNTP是否被初始化过*/
  40.        if(!sntp_init_sign)
  41.        {
  42.                SntpSyncCycleSet(SNTPSYNCCYCLE);
  43.                SntpNoticeCallbackSet(SntpCallback);        //设置时间同步回调函数
  44.                SntpGetTimeImed(50);
  45.                SysTimeConsoleOutput();
  46.                sntp_init_sign = 1;
  47.        }
  48. }
  49. /*
  50. * wifi断联事件处理
  51. */
  52. void wifi_event_disconnect_handle(void* event_handler_arg,esp_event_base_t event_base,int32_t event_id,void* event_data)
  53. {
  54.        wifi_event_sta_disconnected_t* eventdata = (wifi_event_sta_disconnected_t*)event_data;
  55.        if(eventdata->reason == WIFI_REASON_NO_AP_FOUND)
  56.        {
  57.                printf("Can't Found The AP\n");
  58.                vTaskDelay(5000/portTICK_PERIOD_MS);
  59.        }
  60.        esp_wifi_connect();
  61.        SSD1315_Content_Display(&dev , 112, PAGE_3, 16, 2, wifierror_icon);
  62. }
  63. /********************************
  64. * OLED内容刷新函数
  65. ********************************/
  66. void ScreenReflash_thread(void* data)
  67. {
  68.        time_t now;
  69.        struct tm timeinfo;
  70.        uint8_t ht,hu,mt,mu,st,su;        //小时十位个位、分钟十位个位、秒十位个位
  71.        uint8_t montht,monthu,dayt,dayu;
  72.        uint8_t lastUpdataTime;
  73.        for(;;)
  74.        {
  75.                /*更新时间*/
  76.                time(&now);
  77.                localtime_r(&now, &timeinfo);
  78.                montht = (timeinfo.tm_mon+1)/10;
  79.                monthu = (timeinfo.tm_mon+1)%10;
  80.                dayt   = timeinfo.tm_mday/10;
  81.                dayu   = timeinfo.tm_mday%10;
  82.                ht = timeinfo.tm_hour/10;
  83.                hu = timeinfo.tm_hour%10;
  84.                mt = timeinfo.tm_min/10;
  85.                mu = timeinfo.tm_min%10;
  86.                st = timeinfo.tm_sec/10;
  87.                su = timeinfo.tm_sec%10;
  88.                SSD1315_Content_Display(&dev, 1, PAGE_3, 9, 2, number9x16[montht]);
  89.                SSD1315_Content_Display(&dev, 10, PAGE_3, 9, 2, number9x16[monthu]);
  90.                SSD1315_Content_Display(&dev, 18, PAGE_3, 8, 2, underline_icon);
  91.                SSD1315_Content_Display(&dev, 26, PAGE_3, 9, 2, number9x16[dayt]);
  92.                SSD1315_Content_Display(&dev, 34, PAGE_3, 9, 2, number9x16[dayu]);
  93.                SSD1315_Content_Display(&dev, 1, PAGE_5, 16, 4, number16x32[ht]);
  94.                SSD1315_Content_Display(&dev, 17, PAGE_5, 16, 4, number16x32[hu]);
  95.                SSD1315_Content_Display(&dev, 33, PAGE_5, 16, 4, colon);
  96.                SSD1315_Content_Display(&dev, 49, PAGE_5, 16, 4, number16x32[mt]);
  97.                SSD1315_Content_Display(&dev, 65, PAGE_5, 16, 4, number16x32[mu]);
  98.                SSD1315_Content_Display(&dev, 81, PAGE_5, 16, 4, point);
  99.                SSD1315_Content_Display(&dev, 96, PAGE_5, 16, 4, number16x32[st]);
  100.                SSD1315_Content_Display(&dev, 112, PAGE_5, 16, 4, number16x32[su]);
  101.                /*更新距离上次更新度过的时长*/
  102.                uint8_t lht,lhu;        //距上次更新小时数十位个位
  103.                if(updata_sign)
  104.                {
  105.                        updata_sign = 0;
  106.                        lastUpdataTime = 0;
  107.                        SSD1315_Content_Display(&dev , 94, PAGE_3, 16, 2, ntpnormal_icon);
  108.                }
  109.                else
  110.                {
  111.                        lastUpdataTime = timeinfo.tm_hour;
  112.                }
  113.                lht = lastUpdataTime/10;
  114.                lhu = lastUpdataTime%10;
  115.                SSD1315_Content_Display(&dev, 78, PAGE_1, 9, 2, number9x16[lht]);
  116.                SSD1315_Content_Display(&dev, 86, PAGE_1, 9, 2, number9x16[lhu]);
  117.                vTaskDelay(1000/portTICK_PERIOD_MS);        //释放线程1s
  118.        }
  119. }
  120. void app_main(void)
  121. {
  122.    nvs_flash_init();
  123.    tcpip_adapter_init();
  124.    ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) );
  125.    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
  126.    ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
  127.    ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );
  128.    ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
  129.    wifi_config_t sta_config = {
  130.        .sta = {
  131.            .ssid = CONFIG_ESP_WIFI_SSID,
  132.            .password = CONFIG_ESP_WIFI_PASSWORD,
  133.            .bssid_set = false
  134.        }
  135.    };
  136.    ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &sta_config) );
  137.    ESP_ERROR_CHECK( esp_wifi_start() );
  138.    ESP_ERROR_CHECK( esp_wifi_connect() );
  139.    Esp32I2cInit();                                                                //初始化I2C和SSD1315
  140.    SSD1315_Clear_Screen(&dev);
  141.    SSD1315_Content_Display(&dev , 94, PAGE_3, 16, 2, ntperror_icon);
  142.        SSD1315_Content_Display(&dev , 112, PAGE_3, 16, 2, wifierror_icon);
  143.        SSD1315_Content_Display(&dev, 1, PAGE_1, 80, 2, updata_icon);
  144.        SSD1315_Content_Display(&dev, 97, PAGE_1, 8, 2, H_icon);
  145.        SSD1315_Content_Display(&dev, 107, PAGE_1, 16, 2, qian_icon);
  146.    /*注册WIFI连接h和断开连接事件处理函数*/
  147.    esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED, wifi_event_connect_handle, NULL, NULL);
  148.    esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, wifi_event_disconnect_handle, NULL, NULL);
  149.    /*创建屏幕刷新任务*/
  150.    taskHandle = xTaskCreateStatic(ScreenReflash_thread, "ScreenReflash", STACKDEPTH, NULL, \
  151.                                                                    1|portPRIVILEGE_BIT , thread_stack, &xTask);
  152.    while (true) {
  153.        vTaskDelay(1000 / portTICK_PERIOD_MS);
  154.    }
  155. }
复制代码
SNTP驱动程序如下:
  1. #include <stdio.h>
  2. #include "SNTP_APP.h"
  3. extern volatile   uint8_t updata_sign;
  4. /****************************************
  5. * 以阻塞方式立刻从NTP服务器获取时间
  6. * 输入: retry_times_max  最大重试次数<255
  7. * 输出:校时状态
  8. *              0        SNTP_SUCCESS
  9. *              1  SNTP_TIMEOUT
  10. * 注意:调用该函数即开启自动同步功能,
  11. *                  同步周期默认1小时,可调用函数
  12. *                 SntpSyncCycleSet()设置同步周期。
  13. *                 如需通知同步结果,使用
  14. ****************************************/
  15. SNTP_STATUE SntpGetTimeImed(uint8_t retry_times_max)
  16. {
  17.         uint8_t retry=0;
  18.         /*初始化SNTP并校时*/
  19.         sntp_setoperatingmode(SNTP_OPMODE_POLL);
  20.         sntp_setservername(0, NTP_URL);
  21.         sntp_init();
  22.         /*设置时区并获取系统时间*/
  23.         setenv("TZ", "CST-8", 1);
  24.         tzset();
  25.         while (sntp_get_sync_status() != SNTP_SYNC_STATUS_COMPLETED && retry < retry_times_max) {
  26.                 retry++;;
  27.                 vTaskDelay(500 / portTICK_PERIOD_MS);
  28.         }
  29.         sntp_set_sync_status(SNTP_SYNC_STATUS_RESET);                //复位同步结果
  30.         /*输出校时结果*/
  31.         if(retry == retry_times_max)
  32.         {
  33.                 printf("SNTP Sync Time Out(Retry %d Times)\n" , retry_times_max);
  34.                 return SNTP_TIMEOUT;
  35.         }
  36.         else
  37.         {
  38.                 printf("SNTP Sync Successful(Retry %d Times)\n" , retry);
  39.                 return SNTP_SUCCESS;
  40.         }
  41. }
  42. /***************************************
  43. * 控制台输出当前时间
  44. * 输入:无
  45. * 输出:无
  46. **************************************/
  47. void SysTimeConsoleOutput(void)
  48. {
  49.         time_t now;
  50.         struct tm timeinfo;
  51.         char strftime_buf[64];
  52.         time(&now);                                                //获取系统时间s
  53.         localtime_r(&now, &timeinfo);        //将获取到的系统时间s转换为带有格式的timeinfo信息
  54.         strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
  55.         printf("System Time: %s\n" , strftime_buf);
  56. }
  57. /**************************************
  58. * SNTP同步周期设定
  59. * 输入:cycle                同步周期:分钟
  60. * 输出:无
  61. **************************************/
  62. void SntpSyncCycleSet(uint16_t cycle)
  63. {
  64.         sntp_set_sync_interval(cycle*60000);
  65. }
  66. /**************************************
  67. * 设置SNTP同步通知回调函数
  68. * 输入:callback        sntp_sync_time_cb_t样式的函数
  69. **************************************/
  70. void SntpNoticeCallbackSet(sntp_sync_time_cb_t callback)
  71. {
  72.         sntp_set_time_sync_notification_cb(callback);
  73. }
  74. /**************************************
  75. * SNTP通知用户回调函数
  76. **************************************/
  77. void SntpCallback(struct timeval *tv)
  78. {
  79.         if(sntp_get_sync_status() == SNTP_SYNC_STATUS_COMPLETED)
  80.         {
  81.                 printf("Sntp Auto Sync Finish\n");
  82.                 SysTimeConsoleOutput();
  83.                 updata_sign = 1;
  84.         }
  85.         else
  86.         {
  87.                 printf("Sntp Auto Sync Fail\n");
  88.         }
  89. }
复制代码
OLED驱动程序如下:
  1. /*******************************************************
  2. * SSD1315 API Source
  3. * build time:   2021/04/03
  4. * version:            1.0
  5. * author:                锋
  6. * note:
  7. * 1)本驱动仅适配了水平地址模式,因此用于显示的图像取模必须是
  8. *             数据水平、字节竖直、像素数据反序
  9. * 2)使用指南
  10. *              使用SSD1315_INIT初始化,清屏后即可正常显示
  11. ******************************************************/
  12. #include "SSD1315.h"
  13. #include "driver/i2c.h"
  14. #define I2CPORT                1        //I2C端口号
  15. /*SSD1315驱动配置结构体*/
  16. SSD1315_CONF_STRUCT dev =
  17. {
  18.                 .write = IIC_Write,
  19.                 .read  = IIC_Read,
  20.                 .adress = SSD1315_ADRESS_DC_L,
  21.                 .pump_level = CHARGE_PUMP_LEVEL_MEDIUM,
  22.                 .display_mode = INVERSE_DISPLAY
  23. };
  24. /*ESP32 I2C总线配置结构体*/
  25. i2c_config_t i2c =
  26. {
  27.                 .mode = I2C_MODE_MASTER,
  28.                 .sda_io_num = GPIO_NUM_32,
  29.                 .sda_pullup_en = true,
  30.                 .scl_io_num = GPIO_NUM_33,
  31.                 .scl_pullup_en = true,
  32.                 .master.clk_speed = 400000
  33. };
  34. /*
  35. * ESP32 IIC外设初始化函数
  36. */
  37. void Esp32I2cInit()
  38. {
  39.         i2c_param_config(I2CPORT, &i2c);                //配置IIC
  40.         i2c_driver_install(I2CPORT, I2C_MODE_MASTER, 0, 0, ESP_INTR_FLAG_LEVEL3);                //初始化驱动程序
  41.         SSD1315_Init(&dev);
  42. }
  43. /*
  44. * SSD1315初始化函数
  45. * &功能:初始化SSD1315并开启显示
  46. * &参数:*dev         包含配置信息的SSD1315_CONF_STRUCT结构体指针
  47. * &返回:函数的运行状态
  48. */
  49. SSD1315_STATUE SSD1315_Init(SSD1315_CONF_STRUCT* dev)
  50. {
  51.         uint8_t txbuffer[4];                //发送缓冲区
  52.         SSD1315_STATUE statue;
  53.         /*关闭显示*/
  54.         txbuffer[0] = 0xae;
  55.         statue = dev->write(dev->adress,COMMAND,txbuffer,1);
  56.         /*修改显示模式为水平地址模式*/
  57.         txbuffer[0] = 0x20;
  58.         txbuffer[1] = 0x00;
  59.         statue = dev->write(dev->adress,COMMAND,txbuffer,2);
  60.         /*设置显示模式*/
  61.         txbuffer[0] = dev->display_mode;
  62.         statue = dev->write(dev->adress,COMMAND,txbuffer,1);
  63.         /*设置充电泵电压*/
  64.         txbuffer[0] = 0x8d;
  65.         txbuffer[1] = dev->pump_level;
  66.         statue = dev->write(dev->adress,COMMAND,txbuffer,2);
  67.         /*开启显示*/
  68.         txbuffer[0] = 0xaf;
  69.         statue = dev->write(dev->adress,COMMAND,txbuffer,1);
  70.         return statue;
  71. }
  72. /*******************************基础显示部分*************************/
  73. /*
  74. * SSD1315显示内容写入函数
  75. * &参数:dev                        包含SSD1315配置信息的结构体指针
  76. *                    start_col                图像开始列
  77. *                    start_page        图像开始页,在枚举类型 PAGE_X 中选取(x = 1-8)
  78. *                    image_long        图像像素长
  79. *                    image_page        图像页长(图像宽/8)
  80. *                    pdata                        包含图像信息的数组指针
  81. */
  82. 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)
  83. {
  84.         uint8_t txbuffer[3];                //发送缓冲区
  85.         SSD1315_STATUE statue;
  86.         /*写入显示列地址*/
  87.         txbuffer[0] = 0x21;
  88.         txbuffer[1] = start_col-1;
  89.         txbuffer[2] = start_col + image_long -2;
  90.         statue = dev->write(dev->adress,COMMAND,txbuffer,3);
  91.         /*写入显示页地址*/
  92.         txbuffer[0] = 0x22;
  93.         txbuffer[1] = start_page;
  94.         txbuffer[2] = start_page + image_page -1;
  95.         statue = dev->write(dev->adress,COMMAND,txbuffer,3);
  96.         /*写入显示数据*/
  97.         statue = dev->write(dev->adress,DATA,pdata,image_long*image_page);
  98.         return statue;
  99. }
  100. /*
  101. * SSD1315清屏函数
  102. */
  103. SSD1315_STATUE SSD1315_Clear_Screen(SSD1315_CONF_STRUCT* dev)
  104. {
  105.         uint8_t statue,i;
  106.         uint8_t empty[8] = {0,0,0,0,0,0,0,0},txbuffer[3];
  107.         for(i = 1; i <= 128; i++)
  108.         {
  109.                 /*更新显示的列和页的地址指向*/
  110.                 txbuffer[0] = 0x21;
  111.                 txbuffer[1] = (i-1)%16*8;
  112.                 txbuffer[2] = i%16*8-1;
  113.                 statue = dev->write(dev->adress,COMMAND,txbuffer,3);
  114.                 txbuffer[0] = 0x22;
  115.                 txbuffer[1] = (i-1)/16;
  116.                 txbuffer[2] = (i-1)/16;
  117.                 statue = dev->write(dev->adress,COMMAND,txbuffer,3);
  118.                 /*擦除在该地址范围上的显示数据*/
  119.                 statue = dev->write(dev->adress,DATA,empty,8);
  120.         }
  121.         return statue;
  122. }
  123. /*******************************进阶显示部分*************************/
  124. /*
  125. * &显示淡出函数
  126. */
  127. /*
  128. * API适配部分
  129. */
  130. uint8_t IIC_Write(uint8_t adress , CORD_ENUM CorD , const uint8_t *pdata , int size)
  131. {
  132.         uint8_t statue;
  133.         i2c_cmd_handle_t handle;
  134.         /*******************************************************
  135.          * &注意:
  136.          * &在该函数内必须实现:
  137.          * 1)从机设备地址adress的传送
  138.          * 2)控制字CorD的传送
  139.          * 3)size个字节数据的传送
  140.          * 4)返回传送状态
  141.          ******************************************************/
  142.         handle = i2c_cmd_link_create();                                //创建命令链
  143.         i2c_master_start(handle);                                                //IIC开始标志
  144.         i2c_master_write_byte(handle, adress, true); //写地址
  145.         i2c_master_write_byte(handle, CorD, true);   //写数据类型
  146.         i2c_master_write(handle, pdata, size, true);  //写数据
  147.         i2c_master_stop(handle);                                                //终止标志
  148.         statue = i2c_master_cmd_begin(I2CPORT, handle, 100);        //开始IIC指令
  149.         i2c_cmd_link_delete(handle);
  150.         return statue;
  151. }
  152. uint8_t IIC_Read(uint8_t adress , CORD_ENUM CorD , const uint8_t *pdata , int size)
  153. {
  154.         uint8_t statue;
  155.         i2c_cmd_handle_t handle;
  156.         /*******************************************************
  157.          * &注意:
  158.          * &在该函数内必须实现:
  159.          * 1)从机设备地址adress的传送
  160.          * 2)控制字CorD的传送
  161.          * 3)size个字节数据的接收
  162.          * 4)返回传送状态
  163.          ******************************************************/
  164.         handle = i2c_cmd_link_create();                                                                       //创建命令链
  165.         i2c_master_start(handle);                                                                                           //IIC开始标志
  166.         i2c_master_write_byte(handle, adress|0x01, true);                                   //写地址
  167.         i2c_master_write_byte(handle, CorD, true);                                                      //写数据类型
  168.         i2c_master_read(handle, (uint8_t*)pdata, size, I2C_MASTER_LAST_NACK);         //读数据
  169.         i2c_master_stop(handle);                                                                                  //终止标志
  170.         statue = i2c_master_cmd_begin(I2CPORT, handle, 100);                                  //开始IIC指令
  171.         i2c_cmd_link_delete(handle);
  172.         return statue;
  173. }
复制代码
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail