Skip to content

实时显示 更进一步 又快又准

简介

FireBeetle 2 ESP32-S3可以使用Arduino IDE、ESP-IDF、MicroPython进行编程,C语言、python都可以轻松的操纵硬件。我选择以vscode为主,插件platformio,需要安装python3.
Purple Pi开发板通过J12排针提供了丰富的GPIO接口,包括UART、SPI、I2C、GPIO等(还包括DC5V、DC3.3V、GND)。

引脚概述

  • GPIO:常规引脚
  • Analog:模拟输入引脚
  • ADC:模数转换
  • TOUCH:触摸引脚
  • SPI:SPI接口
  • I2C:I2C接口
  • UART:UART接口
  • USB:USB接口
  • JTAG:调试接口
  • 3V3:3.3V稳压电源输出
  • VCC:电源输入/输出
  • 输入:5V DC输入为FireBeetle供电(无法为锂电池充电)
  • 输出:5V-USB 供电时输出USB电压,3.7V-锂电池供电时输出锂电池电压
  • GND:公共地引脚

引脚示意图

引脚示意图

引脚关系图

boardextend
IO1/SDASSD1306-SDA
IO2/SCLSSD1306-SCK
IO7/D5DS1302-SCK
IO38/D3DS1302-DO
IO3/D2DS1302-RST

如下图:

为什么要设计?

在SSD1306上获取实时是指在SSD1306液晶显示屏上显示实时的时间或其它信息。为了实现这一功能,需要通过一个实时时钟(RTC)模块来获取实时的时间,并将时间信息传递给SSD1306液晶显示屏进行显示。

DS1302是一种常用的实时时钟芯片,它具有掉电不丢失时间的特性,即使在掉电的情况下,它仍能保存当前的时间信息。因此,将DS1302与SSD1306结合使用,可以实现在掉电或烧录后仍然能够获取实时时间,并在液晶显示屏上显示。

这样的设计可以使设备具有更好的用户体验,即使在掉电或重新烧录程序的情况下,用户也能看到实时的时间信息,而不需要重新设置时间。这对于一些需要精确时间信息的应用场景是非常有用的,比如钟表、计时器等。

基于01SHOWTIME开源改进一些内容

源代码如下:

cpp


// CONNECTIONS:
// DS1302 CLK/SCLK --> 7
// DS1302 DAT/IO --> 38
// DS1302 RST/CE --> 3
// DS1302 VCC --> 3.3v - 5v
// DS1302 GND --> GND
// SSD1306 SCK --> IO2/SCL
// SSD1306 SDA --> IO1/SDA
// SSD1306 GND --> GND
// SSD1306 VDD --> 3.3V

#include "OLEDDisplayUi.h"
#include "SSD1306Wire.h"
#include "images.h"
#include <RtcDS1302.h>
#include <TimeLib.h>
#include <Wire.h>

void printDateTime(const RtcDateTime &dt);
#define countof(a) (sizeof(a) / sizeof(a[0]))

ThreeWire myWire(38, 7, 3);          // IO, SCLK, CE
SSD1306Wire display(0x3c, SDA, SCL); // IO1 IO2
RtcDS1302<ThreeWire> Rtc(myWire);
OLEDDisplayUi ui(&display);

int screenW = 128;
int screenH = 64;
int clockCenterX = screenW / 2;
int clockCenterY = ((screenH - 16) / 2) + 16; // top yellow part is 16 px height
int clockRadius = 23;

// utility function for digital clock display: prints leading 0
String twoDigits(int digits)
{
    if (digits < 10)
    {
        String i = '0' + String(digits);
        return i;
    }
    else
    {
        return String(digits);
    }
}

void clockOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
{
}

void analogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
    //  ui.disableIndicator();

    // Draw the clock face
    //  display->drawCircle(clockCenterX + x, clockCenterY + y, clockRadius);
    display->drawCircle(clockCenterX + x, clockCenterY + y, 2);
    //
    // hour ticks
    for (int z = 0; z < 360; z = z + 30)
    {
        // Begin at 0° and stop at 360°
        float angle = z;
        angle = (angle / 57.29577951); // Convert degrees to radians
        int x2 = (clockCenterX + (sin(angle) * clockRadius));
        int y2 = (clockCenterY - (cos(angle) * clockRadius));
        int x3 = (clockCenterX + (sin(angle) * (clockRadius - (clockRadius / 8))));
        int y3 = (clockCenterY - (cos(angle) * (clockRadius - (clockRadius / 8))));
        display->drawLine(x2 + x, y2 + y, x3 + x, y3 + y);
    }

    // display second hand
    float angle = second() * 6;
    angle = (angle / 57.29577951); // Convert degrees to radians
    int x3 = (clockCenterX + (sin(angle) * (clockRadius - (clockRadius / 5))));
    int y3 = (clockCenterY - (cos(angle) * (clockRadius - (clockRadius / 5))));
    display->drawLine(clockCenterX + x, clockCenterY + y, x3 + x, y3 + y);
    //
    // display minute hand
    angle = minute() * 6;
    angle = (angle / 57.29577951); // Convert degrees to radians
    x3 = (clockCenterX + (sin(angle) * (clockRadius - (clockRadius / 4))));
    y3 = (clockCenterY - (cos(angle) * (clockRadius - (clockRadius / 4))));
    display->drawLine(clockCenterX + x, clockCenterY + y, x3 + x, y3 + y);
    //
    // display hour hand
    angle = hour() * 30 + int((minute() / 12) * 6);
    angle = (angle / 57.29577951); // Convert degrees to radians
    x3 = (clockCenterX + (sin(angle) * (clockRadius - (clockRadius / 2))));
    y3 = (clockCenterY - (cos(angle) * (clockRadius - (clockRadius / 2))));
    display->drawLine(clockCenterX + x, clockCenterY + y, x3 + x, y3 + y);
}

void digitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
    String timenow = String(hour()) + ":" + twoDigits(minute()) + ":" + twoDigits(second());
    display->setTextAlignment(TEXT_ALIGN_CENTER);
    display->setFont(ArialMT_Plain_24);
    display->drawString(clockCenterX + x, clockCenterY + y, timenow);
}

void printDateTime(const RtcDateTime &dt)
{
    char datestring[26];

    snprintf_P(datestring,
               countof(datestring),
               PSTR("%02u/%02u/%04u %02u:%02u:%02u"),
               dt.Month(),
               dt.Day(),
               dt.Year(),
               dt.Hour(),
               dt.Minute(),
               dt.Second());
    Serial.print(datestring);
}

void clock_init()
{
    Rtc.Begin();
    RtcDateTime compiled = RtcDateTime(__DATE__, __TIME__);
    printDateTime(compiled);
    Serial.println();

    if (!Rtc.IsDateTimeValid())
    {
        // Common Causes:
        //    1) first time you ran and the device wasn't running yet
        //    2) the battery on the device is low or even missing

        Serial.println("RTC lost confidence in the DateTime!");
        Rtc.SetDateTime(compiled);
    }

    if (Rtc.GetIsWriteProtected())
    {
        Serial.println("RTC was write protected, enabling writing now");
        Rtc.SetIsWriteProtected(false);
    }

    if (!Rtc.GetIsRunning())
    {
        Serial.println("RTC was not actively running, starting now");
        Rtc.SetIsRunning(true);
    }

    RtcDateTime now = Rtc.GetDateTime();
    if (now < compiled)
    {
        Serial.println("RTC is older than compile time!  (Updating DateTime)");
        Rtc.SetDateTime(compiled);
    }
    else if (now > compiled)
    {
        Serial.println("RTC is newer than compile time. (this is expected)");
    }
    else if (now == compiled)
    {
        Serial.println("RTC is the same as compile time! (not expected but all is fine)");
    }
}

// This array keeps function pointers to all frames
// frames are the single views that slide in
FrameCallback frames[] = {analogClockFrame, digitalClockFrame};

// how many frames are there?
int frameCount = 2;

// Overlays are statically drawn on top of a frame eg. a clock
OverlayCallback overlays[] = {clockOverlay};
int overlaysCount = 1;

void setup()
{
    Serial.begin(115200);

    RtcDateTime now = Rtc.GetDateTime();

    Serial.print("compiled: ");
    Serial.print(__DATE__);
    Serial.println(__TIME__);

    // clock function
    clock_init(); // init time and date
    Serial.println("Init time and date~");

    ui.setTargetFPS(60);
    // Customize the active and inactive symbol
    ui.setActiveSymbol(activeSymbol);
    ui.setInactiveSymbol(inactiveSymbol);

    // You can change this to
    // TOP, LEFT, BOTTOM, RIGHT
    ui.setIndicatorPosition(TOP);

    // Defines where the first frame is located in the bar.
    ui.setIndicatorDirection(LEFT_RIGHT);

    // You can change the transition that is used
    // SLIDE_LEFT, SLIDE_RIGHT, SLIDE_UP, SLIDE_DOWN
    ui.setFrameAnimation(SLIDE_LEFT);

    // Add frames
    ui.setFrames(frames, frameCount);

    // Add overlays
    ui.setOverlays(overlays, overlaysCount);

    // Initialising the UI will init the display too.
    ui.init();

    display.flipScreenVertically();
    // 从ds1302获取实时
    setTime(now.Hour(), now.Minute(), now.Second(), now.Day(), now.Month(), now.Year());
}

void loop()
{
    int remainingTimeBudget = ui.update();
    if (remainingTimeBudget > 0)
    {
        delay(remainingTimeBudget);
    }
}