#include <Wire.h>
#include <U8g2lib.h>
#include <stdio.h>
// =====================================================
// STM32L031K6 / NUCLEO-L031K6 I2C 腳位
// 板子預設焊橋:
// PB7 -> Nucleo D4 -> OLED SDA
// PB6 -> Nucleo D5 -> OLED SCL
// OLED 請使用 3.3V 供電
// =====================================================
#define OLED_SDA_PIN PB7
#define OLED_SCL_PIN PB6
// SSD1315 使用 SSD1306 相容驅動
// _1_ 分頁模式,可減少 STM32L031K6 SRAM 使用量
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(
U8G2_R0,
U8X8_PIN_NONE
);
// 每個畫面停留時間
const uint32_t DISPLAY_TIME_MS = 500;
// =====================================================
// 低電量圖示
// 數值 <= 10 時,直接顯示這個原始圖案
// =====================================================
static const unsigned char image_battery_0_bits[] U8X8_PROGMEM = {
0x00,0x00,0x00,0xf0,0xff,0x7f,0x08,0x00,
0x80,0x08,0x02,0x82,0x0e,0x04,0x81,0x01,
0x88,0x80,0x01,0x50,0x80,0x01,0x20,0x80,
0x01,0x50,0x80,0x01,0x88,0x80,0x0e,0x04,
0x81,0x08,0x02,0x82,0x08,0x00,0x80,0xf0,
0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00
};
// =====================================================
// 畫右上角電池
// 電池凸點在左側,電量從右向左增加
// =====================================================
void drawBattery(uint8_t value) {
const uint8_t x = 104;
const uint8_t y = 2;
if (value > 100) {
value = 100;
}
// 0~10:顯示低電量圖案
if (value <= 10) {
u8g2.drawXBMP(
x,
y,
24,
16,
image_battery_0_bits
);
return;
}
const uint8_t bodyX = x + 4;
const uint8_t bodyY = y + 1;
const uint8_t bodyWidth = 19;
const uint8_t bodyHeight = 13;
// 電池主體外框
u8g2.drawFrame(
bodyX,
bodyY,
bodyWidth,
bodyHeight
);
// 左側電池凸點
u8g2.drawBox(
x + 1,
y + 4,
3,
7
);
const uint8_t fillAreaX = bodyX + 2;
const uint8_t fillAreaY = bodyY + 2;
const uint8_t maxFillWidth = bodyWidth - 4;
const uint8_t fillHeight = bodyHeight - 4;
uint8_t fillWidth =
((uint16_t)maxFillWidth * value) / 100;
if (fillWidth < 1) {
fillWidth = 1;
}
// 電量從右側往左側增加
const uint8_t fillX =
fillAreaX + maxFillWidth - fillWidth;
u8g2.drawBox(
fillX,
fillAreaY,
fillWidth,
fillHeight
);
}
// =====================================================
// 顯示中央數字
// =====================================================
void drawValue(uint8_t value) {
char valueText[4];
// 使用標準格式化函式,避免不同 MCU 的 itoa 相容性差異
snprintf(valueText, sizeof(valueText), "%u", (unsigned int)value);
u8g2.setFont(u8g2_font_logisoso38_tn);
const int16_t textWidth = u8g2.getStrWidth(valueText);
const int16_t textX = (128 - textWidth) / 2;
u8g2.drawStr(
textX,
62,
valueText
);
}
// =====================================================
// 畫無限大符號的一條路徑
// =====================================================
void drawInfinityPath(int8_t yOffset) {
// 左半部
u8g2.drawLine(64, 46 + yOffset, 55, 36 + yOffset);
u8g2.drawLine(55, 36 + yOffset, 45, 31 + yOffset);
u8g2.drawLine(45, 31 + yOffset, 36, 34 + yOffset);
u8g2.drawLine(36, 34 + yOffset, 30, 41 + yOffset);
u8g2.drawLine(30, 41 + yOffset, 28, 46 + yOffset);
u8g2.drawLine(28, 46 + yOffset, 30, 51 + yOffset);
u8g2.drawLine(30, 51 + yOffset, 36, 58 + yOffset);
u8g2.drawLine(36, 58 + yOffset, 45, 61 + yOffset);
u8g2.drawLine(45, 61 + yOffset, 55, 56 + yOffset);
u8g2.drawLine(55, 56 + yOffset, 64, 46 + yOffset);
// 右半部
u8g2.drawLine(64, 46 + yOffset, 73, 36 + yOffset);
u8g2.drawLine(73, 36 + yOffset, 83, 31 + yOffset);
u8g2.drawLine(83, 31 + yOffset, 92, 34 + yOffset);
u8g2.drawLine(92, 34 + yOffset, 98, 41 + yOffset);
u8g2.drawLine(98, 41 + yOffset, 100, 46 + yOffset);
u8g2.drawLine(100, 46 + yOffset, 98, 51 + yOffset);
u8g2.drawLine(98, 51 + yOffset, 92, 58 + yOffset);
u8g2.drawLine(92, 58 + yOffset, 83, 61 + yOffset);
u8g2.drawLine(83, 61 + yOffset, 73, 56 + yOffset);
u8g2.drawLine(73, 56 + yOffset, 64, 46 + yOffset);
}
// =====================================================
// 畫較粗的無限大符號
// =====================================================
void drawInfinity() {
drawInfinityPath(-1);
drawInfinityPath(0);
drawInfinityPath(1);
}
// =====================================================
// 畫完整畫面
// =====================================================
void drawScreen(uint8_t value, bool showInfinity) {
u8g2.setFontMode(1);
u8g2.setBitmapMode(1);
if (showInfinity) {
drawBattery(100);
drawInfinity();
}
else {
drawBattery(value);
drawValue(value);
}
}
// =====================================================
// 初始化
// =====================================================
void setup() {
// STM32duino 必須在 Wire.begin() 之前指定 I2C 腳位。
// u8g2.begin() 內部會啟動 Wire。
Wire.setSDA(OLED_SDA_PIN);
Wire.setSCL(OLED_SCL_PIN);
// Wokwi 的 SSD1306 預設位址為 0x3C
u8g2.setI2CAddress(0x3C << 1);
u8g2.begin();
}
// =====================================================
// 主程式
// =====================================================
void loop() {
static uint8_t value = 0;
static bool showInfinity = false;
u8g2.firstPage();
do {
drawScreen(value, showInfinity);
}
while (u8g2.nextPage());
delay(DISPLAY_TIME_MS);
if (showInfinity) {
showInfinity = false;
value = 0;
}
else if (value >= 100) {
showInfinity = true;
}
else {
value += 5;
}
}