本篇教學將深入探討 U8g2 程式庫的強大功能,帶您學習如何設計更豐富的資訊介面、使用圖形化元素,甚至顯示部分中文。我們將透過一系列螺旋式範例,從最基礎的文字顯示開始,逐步疊加功能,最終打造一個專業且具視覺吸引力的溫濕度儀表板。
本教學文件採用螺旋式上升的結構設計,您可以依照下圖的路徑,從基礎概念開始,逐步完成實作,並深入理解其核心原理。
U8g2 不僅僅是個顯示文字的工具,它是一個功能完整的圖形函式庫。理解其核心功能是創造優質介面的關鍵。
U8g2 的強大之處在於其豐富的繪圖 API。以下是一些最常用的函數:
| 類別 | 函數 | 功能說明 |
|---|---|---|
| 基礎控制 | u8g2.begin() |
初始化顯示器。 |
u8g2.firstPage() |
開始一個頁面繪圖循環。 | |
u8g2.nextPage() |
繼續繪製下一個頁面,直到完成。 | |
| 文字處理 | u8g2.setFont(font_name) |
設定要使用的字型。這是改變外觀最重要的指令。 |
u8g2.setCursor(x, y) |
設定下一次 print 指令的起始座標。 |
|
u8g2.print(value) |
在當前游標位置印出文字、數字或變數。 | |
u8g2.drawStr(x, y, "text") |
在指定座標繪製一個字串。 | |
| 圖形繪製 | u8g2.drawBox(x, y, w, h) |
畫一個實心矩形。 |
u8g2.drawFrame(x, y, w, h) |
畫一個空心矩形。 | |
u8g2.drawRFrame(x, y, w, h, r) |
畫一個圓角的空心矩形。 | |
u8g2.drawGlyph(x, y, char_code) |
繪製特定字型中的單一圖形或字元。 |
在開始繪製畫面之前,最重要的一步是理解 OLED 螢幕的座標系統。對於一塊 128x64 解析度的螢幕:
- 原點 (0,0):位於螢幕的左上角。
- X 軸:從左到右,範圍是 0 到 127。
- Y 軸:從上到下,範圍是 0 到 63。
所有 draw... 或 setCursor 相關的函數,其 (x, y) 座標都遵循這個規則。
(0,0)
+-----------------------------------------------------> X (0 to 127)
|
|
|
|
|
|
V Y (0 to 63)
例如,u8g2.drawPixel(64, 32) 會在螢幕的正中央畫一個點。
在進入複雜的專案前,讓我們先透過幾個簡單的練習來熟悉這些繪圖函數。
U8g2 程式庫驅動 SSD1306 I2C 128x64 OLED 顯示器。程式需在螢幕四周繪製一個距離邊緣 1 像素的邊框,並在螢幕正中央繪製一個 20x20 像素的實心方塊。請確保程式碼是完整的 Arduino Sketch,包含 setup() 和 loop() 函數。"程式碼範例:
```cpp
#include
#include
#include
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
void setup(void) {
u8g2.begin();
}
void loop(void) {
u8g2.firstPage();
do {
// 繪製外框 (x, y, width, height)
u8g2.drawFrame(1, 1, 126, 62);
// 繪製中央實心方塊
// 中央 x 座標: (128 / 2) - (20 / 2) = 54
// 中央 y 座標: (64 / 2) - (20 / 2) = 22
u8g2.drawBox(54, 22, 20, 20);
} while (u8g2.nextPage());
delay(1000); // 延遲一秒,觀察效果
}
```
U8g2 程式庫驅動 SSD1306 I2C 128x64 OLED 顯示器。程式需在螢幕的 (10, 30) 座標位置,使用 u8g2_font_ncenB10_tr 字型顯示文字 'Hello U8g2!'。然後,在文字下方 2 像素處,繪製一條從 (10, 32) 到 (90, 32) 的水平線作為底線。請確保程式碼是完整的 Arduino Sketch,包含 setup() 和 loop() 函數。"程式碼範例:
```cpp
#include
#include
#include
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
void setup(void) {
u8g2.begin();
}
void loop(void) {
u8g2.firstPage();
do {
// 設定字型
u8g2.setFont(u8g2_font_ncenB10_tr);
// 繪製文字
u8g2.drawStr(10, 30, "Hello U8g2!");
// 繪製底線 (x1, y1, x2, y2)
u8g2.drawLine(10, 32, 90, 32);
} while (u8g2.nextPage());
delay(1000);
}
```
在所有 U8g2 程式碼中,最關鍵的一行就是建構式。它告訴程式庫你正在使用哪種硬體。讓我們來拆解它:
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
U8G2_ : U8g2 程式庫的固定前綴。SSD1306 : OLED 的驅動晶片型號。如果你的螢幕是 SH1106,這裡就要跟著改。128X64 : 螢幕的解析度(寬 x 高)。常見的還有 128x32。NONAME : 品牌或變體。NONAME 適用於大多數通用型模組。_F_ : 緩衝模式 (Buffer Mode)。_F_ 代表 Full Buffer,會佔用 (128*64)/8 = 1024 bytes 的 RAM,效能最好。其他選項如 _1_ (128 bytes RAM)、_2_ (256 bytes RAM) 則更節省記憶體,但繪圖速度稍慢。對於 Arduino Uno,_F_ 模式已佔用其一半 RAM,但通常仍是可行的。HW_I2C : 通訊介面。HW_I2C 表示使用硬體 I2C 介面,這是最常見的方式。u8g2 : 你為這個顯示器物件取的名字,可以自訂。(U8G2_R0, ...) : 參數。U8G2_R0 代表螢幕不旋轉。你可以改成 U8G2_R1 (旋轉90度)、U8G2_R2 (180度) 或 U8G2_R3 (270度)。U8X8_PIN_NONE : Reset 腳位。如果你的 OLED 模組沒有用到 RST 腳,就設為此項。U8g2 內建了數百種字型。最好的方法是參考 U8g2 官方字型圖庫 來預覽所有字型的外觀。
如何使用中文?
Arduino Uno 的記憶體有限,無法容納完整的 CJK (中日韓) 字庫。因此,U8g2 採取了收錄部分常用字的策略。您需要找到名稱包含 chinese 的字型,並在程式碼中啟用 UTF-8 編碼。
u8g2.enableUTF8Print();
注意:使用中文字型會佔用大量的 Flash 儲存空間,可能會讓您的專案無法上傳至 Arduino Uno。在資源有限的開發板上,使用圖示或英文是更穩妥的選擇。
接下來,我們將透過五個由簡入繁的範例,一步步建構出最終的儀表板。每個範例都包含顯示規劃、給 AI 的指令範例以及完整程式碼。
在 128x64 的 OLED 螢幕上,上半部顯示溫度,下半部顯示濕度。
- 第一行 (y=20): Temp: XX.X C
- 第二行 (y=50): Humi: XX.X %
ASCII 模擬圖:
+--------------------------+
| |
| Temp: 25.5 C |
| |
| |
| Humi: 60.1 % |
| |
| |
+--------------------------+
"請為 Arduino Uno 撰寫一支完整的程式碼,使用
U8g2程式庫驅動SSD1306 I2C 128x64 OLED顯示器。程式需連接DHT11 感測器(DATA 腳位連接到 Arduino 的Pin 2)。在 OLED 螢幕上,請使用u8g2_font_ncenB10_tr字型,在 y=20 處顯示溫度資訊,格式為 'Temp: XX.X C';在 y=50 處顯示濕度資訊,格式為 'Humi: XX.X %'。程式應使用非阻塞延遲 (millis()) 每 2 秒更新一次 DHT11 的數據並顯示在螢幕上。"
#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
#include "DHT.h"
#define DHTPIN 2
#define DHTTYPE DHT11
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
DHT dht(DHTPIN, DHTTYPE);
unsigned long previousMillis = 0;
const long interval = 2000;
float humidity, temp_c;
void setup(void) {
dht.begin();
u8g2.begin();
}
void loop(void) {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
humidity = dht.readHumidity();
temp_c = dht.readTemperature();
}
u8g2.firstPage();
do {
u8g2.setFont(u8g2_font_ncenB10_tr); // 選擇一個大小為 10 的粗體字型
// 顯示溫度
u8g2.setCursor(0, 20);
u8g2.print("Temp: ");
u8g2.print(temp_c, 1);
u8g2.print(" C");
// 顯示濕度
u8g2.setCursor(0, 50);
u8g2.print("Humi: ");
u8g2.print(humidity, 1);
u8g2.print(" %");
} while (u8g2.nextPage());
}
在 128x64 的 OLED 螢幕上,於溫度和濕度文字左側分別加入溫度計和水滴圖示。
- 溫度計圖示 (x=5, y=22),文字 (x=30, y=20): Temp: XX.X C
- 水滴圖示 (x=5, y=52),文字 (x=30, y=50): Humi: XX.X %
ASCII 模擬圖:
+--------------------------+
| |
| [T] Temp: 25.5 C |
| |
| |
| [D] Humi: 60.1 % |
| |
| |
+--------------------------+
([T]: 溫度計圖示, [D]: 水滴圖示)
"請為 Arduino Uno 撰寫一支完整的程式碼,使用
U8g2程式庫驅動SSD1306 I2C 128x64 OLED顯示器。程式需連接DHT11 感測器(DATA 腳位連接到 Arduino 的Pin 2)。在 OLED 螢幕上,請在溫度數值左側加入一個溫度計圖示,在濕度數值左側加入一個水滴圖示。請使用u8g2_font_open_iconic_weather_2x_t圖示字型,其中 'A' 為溫度計,'E' 為水滴。文字部分請使用u8g2_font_ncenB10_tr字型。確保圖示與文字排版對齊,溫度計圖示位於 (5, 22),溫度文字從 (30, 20) 開始;水滴圖示位於 (5, 52),濕度文字從 (30, 50) 開始。程式應使用非阻塞延遲 (millis()) 每 2 秒更新一次 DHT11 的數據並顯示在螢幕上。"
#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
#include "DHT.h"
#define DHTPIN 2
#define DHTTYPE DHT11
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
DHT dht(DHTPIN, DHTTYPE);
unsigned long previousMillis = 0;
const long interval = 2000;
float humidity, temp_c;
void setup(void) {
dht.begin();
u8g2.begin();
}
void loop(void) {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
humidity = dht.readHumidity();
temp_c = dht.readTemperature();
}
u8g2.firstPage();
do {
// --- 繪製溫度 ---
u8g2.setFont(u8g2_font_open_iconic_weather_2x_t); // 設定 2x 大小的圖示字型
u8g2.drawGlyph(5, 22, 'A'); // 繪製溫度計圖示
u8g2.setFont(u8g2_font_ncenB10_tr); // 切換回文字字型
u8g2.setCursor(30, 20);
u8g2.print("Temp: ");
u8g2.print(temp_c, 1);
u8g2.print(" C");
// --- 繪製濕度 ---
u8g2.setFont(u8g2_font_open_iconic_weather_2x_t);
u8g2.drawGlyph(5, 52, 'E'); // 繪製水滴圖示
u8g2.setFont(u8g2_font_ncenB10_tr);
u8g2.setCursor(30, 50);
u8g2.print("Humi: ");
u8g2.print(humidity, 1);
u8g2.print(" %");
} while (u8g2.nextPage());
}
在 128x64 的 OLED 螢幕上,將標籤 "Temp:" 和 "Humi:" 替換為中文 "溫度:" 和 "濕度:"。
- 第一行 (y=20): 溫度: XX.X C
- 第二行 (y=50): 濕度: XX.X %
ASCII 模擬圖:
+--------------------------+
| |
| 溫度: 25.5 C |
| |
| |
| 濕度: 60.1 % |
| |
| |
+--------------------------+
"請為 Arduino Uno 撰寫一支完整的程式碼,使用
U8g2程式庫驅動SSD1306 I2C 128x64 OLED顯示器。程式需連接DHT11 感測器(DATA 腳位連接到 Arduino 的Pin 2)。在 OLED 螢幕上,請將溫度標籤 'Temp:' 改為 '溫度:',濕度標籤 'Humi:' 改為 '濕度:'。請使用u8g2_font_wqy12_t_chinese2字型顯示中文,並啟用 UTF-8 支援。溫度資訊顯示在 y=20 處,濕度資訊顯示在 y=50 處。程式應使用非阻塞延遲 (millis()) 每 2 秒更新一次 DHT11 的數據並顯示在螢幕上。請在程式碼註解中提醒使用者在 Arduino Uno 上使用中文字型可能導致儲存空間不足的問題。"
#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
#include "DHT.h"
#define DHTPIN 2
#define DHTTYPE DHT11
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
DHT dht(DHTPIN, DHTTYPE);
unsigned long previousMillis = 0;
const long interval = 2000;
float humidity, temp_c;
void setup(void) {
dht.begin();
u8g2.begin();
// 注意:使用中文字型會大幅增加程式碼大小,可能不適用於 Flash 空間小的開發板
u8g2.enableUTF8Print(); // 啟用 UTF-8 編碼以支援中文
}
void loop(void) {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
humidity = dht.readHumidity();
temp_c = dht.readTemperature();
}
u8g2.firstPage();
do {
u8g2.setFont(u8g2_font_wqy12_t_chinese2); // 選擇一個包含常用字的 12px 中文字型
// 顯示溫度
u8g2.setCursor(0, 20);
u8g2.print("溫度: ");
u8g2.print(temp_c, 1);
u8g2.print(" C");
// 顯示濕度
u8g2.setCursor(0, 50);
u8g2.print("濕度: ");
u8g2.print(humidity, 1);
u8g2.print(" %");
} while (u8g2.nextPage());
}
在 128x64 的 OLED 螢幕上,上半部顯示溫度,下半部顯示濕度數值,並在濕度數值下方增加一個水平長條圖。
- 第一行 (y=20): Temp: XX.X C
- 第二行 (y=45): Humi: XX.X %
- 濕度長條圖: 位於 (0, 55) 處,外框寬度 102 像素,高度 8 像素。
ASCII 模擬圖:
+--------------------------+
| |
| Temp: 25.5 C |
| |
| Humi: 60.1 % |
| [=========== ] |
| |
| |
+--------------------------+
"請為 Arduino Uno 撰寫一支完整的程式碼,使用
U8g2程式庫驅動SSD1306 I2C 128x64 OLED顯示器。程式需連接DHT11 感測器(DATA 腳位連接到 Arduino 的Pin 2)。在 OLED 螢幕上,請使用u8g2_font_ncenB10_tr字型,在 y=20 處顯示溫度資訊,格式為 'Temp: XX.X C';在 y=45 處顯示濕度資訊,格式為 'Humi: XX.X %'。此外,在濕度數值下方 (從 (0, 55) 座標開始),加入一個水平長條圖來視覺化呈現濕度百分比 (0-100%)。長條圖的外框寬度為 102 像素,高度為 8 像素,實心長條的寬度應根據濕度值 (0-100%) 映射到 0-100 像素。程式應使用非阻塞延遲 (millis()) 每 2 秒更新一次 DHT11 的數據並顯示在螢幕上。"
#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
#include "DHT.h"
#define DHTPIN 2
#define DHTTYPE DHT11
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
DHT dht(DHTPIN, DHTTYPE);
unsigned long previousMillis = 0;
const long interval = 2000;
float humidity, temp_c;
void setup(void) {
dht.begin();
u8g2.begin();
}
void loop(void) {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
humidity = dht.readHumidity();
temp_c = dht.readTemperature();
}
u8g2.firstPage();
do {
u8g2.setFont(u8g2_font_ncenB10_tr);
// 顯示溫度
u8g2.setCursor(0, 20);
u8g2.print("Temp: ");
u8g2.print(temp_c, 1);
u8g2.print(" C");
// 顯示濕度文字
u8g2.setCursor(0, 45);
u8g2.print("Humi: ");
u8g2.print(humidity, 1);
u8g2.print(" %");
// 繪製濕度長條圖
int barWidth = map(humidity, 0, 100, 0, 100); // 將 0-100% 映射到 0-100 像素寬度
u8g2.drawFrame(0, 55, 102, 8); // 繪製長條圖外框 (x, y, width, height)
u8g2.drawBox(1, 56, barWidth, 6); // 根據濕度繪製實心長條 (x, y, width, height)
} while (u8g2.nextPage());
}
設計一個專業的儀表板介面,將 128x64 的 OLED 螢幕畫面分為上下兩區,結合圖示與更美觀的字型,並優化排版。
- 溫度區:左側為大型溫度計圖示,右側為標籤與數值。
- 濕度區:左側為大型水滴圖示,右側為標籤與數值。
ASCII 模擬圖:
+--------------------------+
| / \ Temp: |
| | o | 25.5 C |
| \ / |
|--------------------------|
| |
| / \ Humi: |
| ( o ) 60.1 % |
| \ / |
+--------------------------+
"請為 Arduino Uno 撰寫一支完整的程式碼,使用
U8g2程式庫驅動SSD1306 I2C 128x64 OLED顯示器。程式需連接DHT11 感測器(DATA 腳位連接到 Arduino 的Pin 2)。請設計一個專業的溫濕度儀表板介面,畫面分為上下兩區:
1. 溫度區:左側在 (5, 30) 處顯示一個大型溫度計圖示 (使用u8g2_font_open_iconic_weather_4x_t字型,字元 'A')。右側在 (40, 20) 處顯示標籤 'Temp:',在 (40, 40) 處顯示溫度數值 (XX.X),並在 (95, 40) 處顯示 '°C' 單位。數值和標籤請使用u8g2_font_logisoso16_tr字型,'°' 符號請使用drawGlyph(x, y, 176)繪製。
2. 濕度區:左側在 (5, 62) 處顯示一個大型水滴圖示 (使用u8g2_font_open_iconic_weather_4x_t字型,字元 'E')。右側在 (40, 60) 處顯示標籤 'Humi:',在 (40, 80) 處顯示濕度數值 (XX.X),並在 (95, 80) 處顯示 '%' 單位。數值和標籤請使用u8g2_font_logisoso16_tr字型。
程式應使用非阻塞延遲 (millis()) 每 2 秒更新一次 DHT11 的數據並顯示在螢幕上。"
#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
#include "DHT.h"
#define DHTPIN 2
#define DHTTYPE DHT11
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
DHT dht(DHTPIN, DHTTYPE);
unsigned long previousMillis = 0;
const long interval = 2000;
float humidity, temp_c;
void setup(void) {
dht.begin();
u8g2.begin();
}
void loop(void) {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
humidity = h;
temp_c = t;
}
}
u8g2.firstPage();
do {
// --- 繪製溫度區 ---
u8g2.setFont(u8g2_font_open_iconic_weather_4x_t);
u8g2.drawGlyph(5, 30, 'A'); // 溫度計圖示
u8g2.setFont(u8g2_font_logisoso16_tr); // 使用較大的藝術字體
char tempString[10];
dtostrf(temp_c, 4, 1, tempString);
u8g2.drawStr(40, 20, "Temp:");
u8g2.drawStr(40, 40, tempString);
u8g2.drawGlyph(95, 40, 176); // '°' 符號
u8g2.drawStr(105, 40, "C");
// --- 繪製濕度區 ---
u8g2.setFont(u8g2_font_open_iconic_weather_4x_t);
u8g2.drawGlyph(5, 62, 'E'); // 水滴圖示
u8g2.setFont(u8g2_font_logisoso16_tr);
char humiString[10];
dtostrf(humidity, 4, 1, humiString);
u8g2.drawStr(40, 60, "Humi:");
u8g2.drawStr(40, 80, humiString); // 此處 y 座標調整以配合字體
u8g2.drawStr(95, 80, "%");
} while (u8g2.nextPage());
}
透過以上五個螺旋式範例,我們從最簡單的文字顯示開始,逐步加入了圖示、中文、視覺化圖表,最終完成了一個精美的儀表板。這個過程不僅讓您學會了 U8g2 的各種功能,更重要的是,它展示了一種迭代開發的思維模式:從一個最小可行性產品 (MVP) 開始,逐步增加功能,直到達成最終目標。
現在,您已經具備了使用 Arduino 和 OLED 顯示器創造各種客製化資訊面板的能力。下一步,您可以嘗試結合這些範例的元素,例如,創造一個帶有中文標籤和長條圖的介面,將您的創客專案提升到新的水平。
u8g2.firstPage() / nextPage() 迴圈?您可能已經注意到,所有 U8g2 的繪圖指令都必須放在一個 do-while 迴圈中。這並非偶然,而是 U8g2 設計的核心,也是它能在像 Arduino Uno 這樣記憶體極小的微控制器上高效運作的秘密。
問題:有限的記憶體
一台 Arduino Uno 只有 2KB (2048 bytes) 的 SRAM (靜態隨機存取記憶體)。如果要一次性在記憶體中準備好整個 128x64 畫面的資料,需要 (128 * 64) / 8 = 1024 bytes 的空間。這就佔掉了整台裝置一半的記憶體!剩餘的空間非常小,很容易導致程式不穩定或崩潰。
解決方案:Page Buffer 機制
為了解決這個問題,U8g2 採用了巧妙的 Page Buffer (頁面緩衝) 機制。它不在 Arduino 的記憶體中建立一個完整的畫面緩衝區,而是只建立一個小得多的「頁面」緩衝區(例如 128 或 256 bytes)。
而 u8g2.firstPage() 和 u8g2.nextPage() 就是這個機制的執行者。整個繪圖過程如下:
1. u8g2.firstPage():準備繪製第一個頁面。
2. do { ... }:您放在迴圈中的所有繪圖程式碼會被執行一次。U8g2 會計算哪些繪圖指令的結果落在目前這個小小的頁面範圍內,並將其存入緩-衝區。
3. while (u8g2.nextPage()):將目前這個頁面的緩衝區內容傳送到 OLED 螢幕上,然後準備下一個頁面,並再次回到 do 開頭,重新執行一次所有的繪圖程式碼。
4. 這個過程會不斷重複,直到整個螢幕的所有頁面都被繪製完成為止。
您可以把它想像成用一個小小的「郵票」去蓋滿一張大海報。您每次只在郵票大小的範圍內作畫,蓋完一格後,再移動到下一格,重新作畫,直到蓋滿整張海報。
結論:
U8g2 透過「犧牲 CPU 運算時間,來換取極低的記憶體佔用」。它透過重複執行繪圖指令,來避免一次性佔用大量 RAM。這就是為什麼我們能在一台小小的 Arduino Uno 上,流暢地驅動圖形化螢幕,實現複雜介面的關鍵所在。