2024年11月3日 星期日

MicroPython 學習筆記 : SSD1306 指針時鐘 (一)

測試完 GFX 函式庫就可以來改寫下面這篇文章所展示的 SSD1306 指針時鐘了 : 

Micropython NTP指針時鐘 OLED SSD1306

關於 GFX 函式庫用法參考前一篇測試 :

MicroPython 學習筆記 : 使用 Adafruit 的 GFX 函式庫繪圖 

本測試需要用到 SSD1306 驅動程式 ssd1306.py 與 GFX 模組 gfx.py, 可在 GitHub 下載 :


另外連線 WiFi 與查詢 NTP 伺服器會用到 xtools.py 函式庫, 下載網址如下 :


連線 WiFi 的 SSID 與密碼放在 config.py 中的 SSID 與 PASSWORD 變數中 :

# config.py
SSID="YOUR SSID"
PASSWORD="YOUR PASSWORD"

將 config.py, xtools.py, ssd1306.py, 與 gfx.py 這四個模組上傳到開發板, 匯入 config 與 xtools 後呼叫 xtools.connect_wifi() 連上網路 :

MicroPython v1.23.0 on 2024-06-02; Generic ESP32 module with ESP32

Type "help()" for more information.

>>> import config   
>>> import xtools   
>>> ip=xtools.connect_wifi(led=5)      
Connecting to network...
network config: ('192.168.50.156', '255.255.255.0', '192.168.50.1', '192.168.50.1')

>>> from machine import RTC   
>>> dt=RTC().datetime()     
>>> dt    
(2024, 11, 3, 6, 7, 33, 9, 988798)

元組的索引 4, 5, 6 分別為時分秒, 就是顯示指針時鐘所需的時間資料. 

接下來匯入 machine.PIN 與 machine.I2C 類別來建立 I2C 通訊, 匯入 ssd1306 模組與 gfx.GFX 類別來繪圖, 匯入 time 用來延遲時間 : 

>>> from gfx import GFX    
>>> from machine import I2C, Pin      
>>> import ssd1306 
>>> import time   

然後建立 SSD1306_I2C 與 GFX 物件 : 

>>> i2c=I2C(0, scl=Pin(22), sda=Pin(21))    # ESP32 I2C    
>>> oled=ssd1306.SSD1306_I2C(128, 64, i2c)    # 0.96 吋解析度 128*64
>>> gfx=GFX(oled.width, oled.height, oled.pixel)        

首先指針時鐘的圓心 : 

>>> gfx.circle(31, 31, 1, 1)      
>>> oled.show()     

接著繪製外框, 半徑取 29px : 

>>> gfx.circle(31, 31, 29, 1)      
>>> oled.show()     

結果如下 : 




然後繪製小時刻度 :

>>> for i in range(1,13):
    angle=i * 30
    gfx.line(31+math.trunc(27*math.sin(math.radians(angle))),
        31-math.trunc(27*math.cos(math.radians(angle))),
        31+math.trunc(29*math.sin(math.radians(angle))),
        31-math.trunc(29*math.cos(math.radians(angle))), 1)
oled.show()

結果如下 :




最後是用一個無限迴圈每隔約 1 秒鐘讀取 RTC 時鐘, 取得目前時間的時分秒資料, 然後繪製時針與分針線條 :

>>> while True: 
    dt=RTC().datetime() 
    time.sleep_ms(950)  # 延遲 0.95 秒
    gfx.circle(31, 31, 29, 1)  # 繪製時鐘外框
    h_angle=dt[4] % 12 * 30  # 計算時針角度
    gfx.line(31, 31,
        31+math.trunc(15*math.sin(math.radians(h_angle))),
        31-math.trunc(15*math.cos(math.radians(h_angle))), 1)
    oled.show()  # 繪製時針
    m_angle=dt[5] * 360 / 60  # 計算分針角度
    gfx.line(31, 31,
        31+math.trunc(25*math.sin(math.radians(m_angle))),
        31-math.trunc(25*math.cos(math.radians(m_angle))), 1)
    oled.show()  # 繪製分針
    gfx.fill_rect(53, 56, 17, 10, 0)  # 繪製填滿的長方形框
    oled.text(str(dt[6]), 53, 56, 1)  # 繪製秒數 (文字)
    oled.show()
    if dt[6] == 59: # 59 秒時
        gfx.fill_circle(31, 31, 25, 0) # 填充圓
        gfx.circle(31, 31, 1, 1)  # 繪製時鐘圓心
        gfx.circle(31, 31, 29, 1) # 繪製時鐘外框
        xtools.tw_now()  # 同步 NTP

我在原始程式末尾添加了一行 tw_now(), 此函式會查詢 NTP 伺服器, 成功的話會更新 RTC 時鐘, 從而保證 RTC 時間的準確, 如果沒有同步, 時間一久 RTC 時鐘會漂移而不準. 結果如下 : 




完整程式碼如下 : 

# ssd1306_clock_1.py
import config   
import xtools
from gfx import GFX   
from machine import I2C, Pin, RTC  
import ssd1306
import time
import math

ip=xtools.connect_wifi(led=5) 
if ip:
    # 建立 SSD1306_I2C 與 GFX 物件
    i2c=I2C(0, scl=Pin(22), sda=Pin(21)) # ESP32 I2C   
    oled=ssd1306.SSD1306_I2C(128, 64, i2c) # 0.96 吋解析度 128*64
    gfx=GFX(oled.width, oled.height, oled.pixel)
    oled.fill(0)  # 清除螢幕
    oled.show()
    # 繪製圓心與圓形外框
    gfx.circle(31, 31, 1, 1)
    oled.show()
    gfx.circle(31, 31, 29, 1)
    oled.show()
    # 繪製小時刻度
    for i in range(1,13):
        angle=i * 30
        gfx.line(31+math.trunc(27*math.sin(math.radians(angle))),
            31-math.trunc(27*math.cos(math.radians(angle))),
            31+math.trunc(29*math.sin(math.radians(angle))),
            31-math.trunc(29*math.cos(math.radians(angle))), 1)
    oled.show()
    # 繪製時針與分針
    while True:
        dt=RTC().datetime() 
        time.sleep_ms(950)  # 延遲 0.95 秒
        gfx.circle(31, 31, 29, 1)  # 繪製時鐘外框
        h_angle=dt[4] % 12 * 30  # 計算時針角度
        gfx.line(31, 31,
            31+math.trunc(15*math.sin(math.radians(h_angle))),
            31-math.trunc(15*math.cos(math.radians(h_angle))), 1)
        oled.show()  # 繪製時針
        m_angle=dt[5] * 360 / 60  # 計算分針角度
        gfx.line(31, 31,
            31+math.trunc(25*math.sin(math.radians(m_angle))),
            31-math.trunc(25*math.cos(math.radians(m_angle))), 1)
        oled.show()  # 繪製分針
        gfx.fill_rect(53, 56, 17, 10, 0)  # 繪製填滿的長方形框
        oled.text(str(dt[6]), 53, 56, 1)  # 繪製秒數 (文字)
        oled.show()
        if dt[6] == 59: # 59 秒時
            gfx.fill_circle(31, 31, 25, 0) # 填充圓
            gfx.circle(31, 31, 1, 1)  # 繪製時鐘圓心
            gfx.circle(31, 31, 29, 1) # 繪製時鐘外框
            xtools.tw_now()  # 同步 NTP
else:
    print('無法連線 WiFi 網路!')

但我觀察發現, 時針的位置似乎沒有伴隨分針做適當的移動, 例如已經 12:45 了, 時針還是指到 12 點, 正確位置應該是較靠近 1 點才對, 它要到 13:00 才會突然從 12 點突然指向 13 點, 我詢問 ChatGPT 得到的修改建議是將 h_angle 變數改成如下 :

h_angle=(dt[4] % 12 + dt[5] / 60) * 30  # 計算時針角度   

加入目前的分鐘數 dt[5] / 60 作為調整項, 這樣時針的位置就正確了. 另外, 12 個小時刻度都是相同, 我想將 3, 6, 9, 12 這 4 個小時刻度改成 2x2 px 的方點, ChatGPT 建議將刻度迴圈修改為如下 : 

    # 繪製小時刻度
    for i in range(1, 13):
        angle=i * 30
        x=31+math.trunc(27*math.sin(math.radians(angle)))
        y=31-math.trunc(27*math.cos(math.radians(angle)))
        # 判斷是否為 3, 6, 9 或 12 點的刻度
        if i in [3, 6, 9, 12]:
            gfx.fill_rect(x-1, y-1, 2, 2, 1)  # 繪製 2x2 像素的粗圓點
        else:
            gfx.line(x, y,
                31+math.trunc(29 * math.sin(math.radians(angle))),
                31-math.trunc(29 * math.cos(math.radians(angle))), 1)
        oled.show()

黃底色部分就是 3, 6, 9, 12 這 4 個小時刻度改用 fill_rect() 繪製 2x2 矩形, 結果如下 :




這樣子小時刻度辨識度就比較高了. 完整程式碼如下 :

# ssd1306_clock_2.py
import config   
import xtools
from gfx import GFX   
from machine import I2C, Pin, RTC  
import ssd1306
import time
import math

ip=xtools.connect_wifi(led=5) 
if ip:
    # 建立 SSD1306_I2C 與 GFX 物件
    i2c=I2C(0, scl=Pin(22), sda=Pin(21)) # ESP32 I2C   
    oled=ssd1306.SSD1306_I2C(128, 64, i2c) # 0.96 吋解析度 128*64
    gfx=GFX(oled.width, oled.height, oled.pixel)
    oled.fill(0)  # 清除螢幕
    oled.show()
    # 繪製圓心與圓形外框
    gfx.circle(31, 31, 1, 1)
    oled.show()
    gfx.circle(31, 31, 29, 1)
    oled.show()
    # 繪製小時刻度
    for i in range(1, 13):
        angle=i * 30
        x=31+math.trunc(27*math.sin(math.radians(angle)))
        y=31-math.trunc(27*math.cos(math.radians(angle)))
        # 判斷是否為 3, 6, 9 或 12 點的刻度
        if i in [3, 6, 9, 12]:
            gfx.fill_rect(x-1, y-1, 2, 2, 1)  # 繪製 2x2 像素的粗圓點
        else:
            gfx.line(x, y,
                31+math.trunc(29 * math.sin(math.radians(angle))),
                31-math.trunc(29 * math.cos(math.radians(angle))), 1)
        oled.show()
    # 繪製時針與分針
    while True:
        dt=RTC().datetime() 
        time.sleep_ms(950)  # 延遲 0.95 秒
        gfx.circle(31, 31, 29, 1)  # 繪製時鐘外框
        h_angle=(dt[4] % 12 + dt[5] / 60) * 30  # 計算時針角度
        gfx.line(31, 31,
            31+math.trunc(15*math.sin(math.radians(h_angle))),
            31-math.trunc(15*math.cos(math.radians(h_angle))), 1)
        oled.show()  # 繪製時針
        m_angle=dt[5] * 360 / 60  # 計算分針角度
        gfx.line(31, 31,
            31+math.trunc(25*math.sin(math.radians(m_angle))),
            31-math.trunc(25*math.cos(math.radians(m_angle))), 1)
        oled.show()  # 繪製分針
        gfx.fill_rect(53, 56, 17, 10, 0)  # 繪製填滿的長方形框
        oled.text(str(dt[6]), 53, 56, 1)  # 繪製秒數 (文字)
        oled.show()
        if dt[6] == 59: # 59 秒時
            gfx.fill_circle(31, 31, 25, 0) # 填充圓
            gfx.circle(31, 31, 1, 1)  # 繪製時鐘圓心
            gfx.circle(31, 31, 29, 1) # 繪製時鐘外框
            xtools.tw_now()  # 同步 NTP
else:
    print('無法連線 WiFi 網路!')

沒有留言 :