# main.py — 1.3吋 OLED + DHT22 顯示(I2C)
# 支援 SSD1306 / SH1106(建議 1.3" 用 SH1106)
# 連線:
# OLED I2C:VCC→3V3, GND→GND, SCL→GPIO22, SDA→GPIO21, I2C位址多為0x3C
# DHT22:VCC→3V3, GND→GND, DATA→GPIO4(建議外接10kΩ上拉到3V3)
# 注意:DHT22 每次量測請間隔 ≥ 2 秒,否則容易讀取失敗。
import time # 延遲用
from machine import Pin, I2C # 腳位 / I2C
import dht # DHT 驅動
from oled_drivers import SSD1306_I2C, SH1106_I2C # 顯示器驅動(本專案自帶)
# === 你可以在這裡切換顯示器控制器 ===
# 選項:"SH1106"(1.3" 常見,推薦) or "SSD1306"
CONTROLLER = "SH1106"
# === I2C 與腳位設定 ===
I2C_SCL = 22
I2C_SDA = 21
OLED_ADDR = 0x3C # 大多數模組都用 0x3C,若不確定可改用 I2C 掃描(見下)
OLED_WIDTH = 128
OLED_HEIGHT = 64
# === DHT22 腳位 ===
DHT_PIN = 4
# === 指示用 LED 腳位(可選,沒有就改成不使用) ===
HINT_LED_PIN = 2
hint_led = Pin(HINT_LED_PIN, Pin.OUT) # 板載 LED 常見在 GPIO2
# === 建立 I2C 物件(400kHz)===
i2c = I2C(0, scl=Pin(I2C_SCL), sda=Pin(I2C_SDA), freq=400_000)
# === (可選)掃描 I2C 裝置,列印除錯資訊 ===
try:
devs = i2c.scan()
print("I2C 掃描到位址清單:", [hex(d) for d in devs])
except Exception as e:
print("I2C 掃描失敗:", e)
# === 依控制器建立 OLED 物件 ===
if CONTROLLER.upper() == "SH1106":
# x_offset = 2 是常見 1.3" 的對齊值(若畫面左右偏,可調 0~4 試試)
oled = SH1106_I2C(OLED_WIDTH, OLED_HEIGHT, i2c, addr=OLED_ADDR, external_vcc=False, x_offset=2)
else:
oled = SSD1306_I2C(OLED_WIDTH, OLED_HEIGHT, i2c, addr=OLED_ADDR, external_vcc=False)
# === 建立 DHT22 物件(內建上拉)===
dht_sensor = dht.DHT22(Pin(DHT_PIN, Pin.IN, Pin.PULL_UP))
# === UI 小工具 ===
def clear():
"""清空畫面"""
oled.fill(0)
def draw_center(text, y):
"""以 8x8 基本字型估計寬度做置中(英文/數字效果最好)"""
x = max(0, (oled.width - len(text) * 8) // 2)
oled.text(text, x, y, 1)
def draw_text2x(text, x, y):
"""
簡單 2× 放大字體(只針對 8x8 內建字體,數字/英文清晰)
原理:逐像素放大繪製
"""
# 用 framebuf 內建字先畫到小畫布,再放大抄到大畫面
w, h = len(text) * 8, 8
buf = bytearray((w * h) // 8)
fb = framebuf.FrameBuffer(buf, w, h, framebuf.MONO_VLSB)
fb.fill(0)
fb.text(text, 0, 0, 1)
# 放大抄寫
for yy in range(h):
for xx in range(w):
col = fb.pixel(xx, yy)
# 2x2 填滿
if col:
oled.pixel(x + 2*xx, y + 2*yy, 1)
oled.pixel(x + 2*xx + 1, y + 2*yy, 1)
oled.pixel(x + 2*xx, y + 2*yy + 1, 1)
oled.pixel(x + 2*xx + 1, y + 2*yy + 1, 1)
def boot_screen():
"""開機畫面(1 秒)"""
clear()
draw_center("ESP32 + 1.3\" OLED", 0)
draw_center("DHT22 SENSOR", 16)
draw_center("Initializing...", 40)
oled.show()
time.sleep(1)
def read_dht22_once():
"""讀一次 DHT22,成功回傳 (t, h),失敗回傳 (None, None)"""
try:
dht_sensor.measure() # 觸發量測(須間隔 >= 2 秒)
t = dht_sensor.temperature()
h = dht_sensor.humidity()
return (t, h)
except Exception as e:
# 可在這裡 print(e) 做進一步除錯
return (None, None)
def draw_readings(t, h):
"""把讀值畫到 1.3吋螢幕(用較大字讓可讀性更好)"""
clear()
draw_center("DHT22 READINGS", 0)
if t is None or h is None:
draw_center("READ ERROR", 24)
draw_center("Retrying...", 40)
else:
# 用 2× 放大字顯示(更適合 1.3 吋)
temp = "T:{:.1f}C".format(t)
humi = "H:{:.1f}%".format(h)
# 估算置中位置(每字寬約 8×2=16px)
tx = max(0, (oled.width - len(temp)*16)//2)
hx = max(0, (oled.width - len(humi)*16)//2)
# 放大字函式需要 framebuf(從 machine import framebuf 前先引入)
draw_text2x(temp, tx, 18)
draw_text2x(humi, hx, 38)
oled.show()
def main():
# 逃生倒數:3 秒內按 Ctrl+C 可以進 REPL,不會自動跑
for i in range(3, 0, -1):
print("按 Ctrl+C 取消自動執行…", i)
time.sleep(1)
boot_screen()
INTERVAL = 2.0 # DHT22 建議至少 2 秒取樣一次
TH_T = 25.0 # 指示 LED 門檻(溫度)
TH_H = 50.0 # 指示 LED 門檻(濕度)
while True:
t, h = read_dht22_once()
draw_readings(t, h)
# 依門檻控制 LED(你可自行調門檻或改為蜂鳴器等)
if (t is not None and h is not None) and (t >= TH_T and h >= TH_H):
hint_led.value(1)
else:
hint_led.value(0)
time.sleep(INTERVAL)
# --- 程式進入點 ---
if __name__ == "__main__":
# 需要用到 framebuf 的 2× 字函式,這裡延後匯入避免循環
import framebuf
main()