顯示具有 物聯網 標籤的文章。 顯示所有文章
顯示具有 物聯網 標籤的文章。 顯示所有文章

2026年3月6日 星期五

好站 : 米羅文創學院的 ESP32-CAM 教學

今天在整理 Chrome 書籤時發現之前找到的 ESP32-CAM 教學, 這是線上教學平台米羅文創釋出的公開文章 (第 3~5 篇為受保護文章, 須購買課程才能閱讀), 內容豐富鉅細靡遺值得參考 : 


雖然我主要使用 MicroPython 開發, 但韌體不支援的部分就必須改用 Arduino IDE 的 C 語言來寫了. 所幸現在有 AI, 這部分的障礙低很多了. 

2026年1月12日 星期一

ESP32 帶觸控螢幕開發板 CYD (ESP32-2432S028R)

今天在臉書看到這塊 ESP32 開發板令我驚艷, 組合了 ESP32-WROOM-32 開發板與 2.8 吋 TFT LCD (320x240 解析度), 以及 RGB LED, 光敏電阻 (LDR), 蜂鳴器, Micro SD 卡槽等感測器與周邊, 價格大約 600~700 元左右, 除了使用 C 語言開發, 也可以用 MicroPython (螢幕效能會較慢), 先記下來備忘, 參考資料如下 : 


2025年12月14日 星期日

蝦皮購買 Tuya 小圓智能插座 5 個 (增購)

我在 2022 年底曾在蝦皮買過 Tuya 小圓智能插座, 目前剩下 2 個將用罄, 向此賣家再買 5 個 20A 無計量 3 孔 2 插 :






自由路智取店取付. 

2025年12月2日 星期二

WiFi 無線網路信號強度測量

最近打算著手建造鄉下老家門口安裝智慧信箱監控器, 週日下午先進行了 WiFi 網路的信號強度測試, 紀錄如下. 

目前老家有兩個無線網路, 一個光世代升 300 MB 時購置的 WiFi 6 ASUS AX3000 無線基地台, 另一個是很久以前的 Edimax WiFi 基地台 (仍保留繼續使用). 此外手中還有 Toto 與穿牆王這兩塊 WiFi 延伸器. 我本來打算用穿牆王插在前門插座上, 將 AX3000 訊號延伸至大門口, 測試結果雖然穿牆王確實強度比距離較遠的 AX3000 高了 10 幾 dBm, 但速率卻遠遠比不上 AX3000. 


測試一 : 在穿牆王 WiFi 延伸器附近測量





強度 -33dBm, 上下載速率 10 幾 MB. ASUS WiFi 6 基地台 2.4G 強度比它少了近 30 dBm, 約為 -62 dBm. 


測試二 : 在大門口測量穿牆王 WiFi 延伸器信號




在距離延伸器約 10 公尺的大門口, 信號約衰減了 20 dBm, 來到 -53dBm, 速率還是差不多 10 幾 MB. ASUS WiFi 6 基地台 5G 強度還有 -67 dBM, 而 2.4G 強度則衰減了 9 dBm, 約 -71 dBm. 


測試三 : 在大門口測量 ASUS WiFi  6 基地台信號

最後切換連線改到 ASUS WiFi 6 基地台, 此處 5G 信號還有 -65dBm, 而 2.4G 信號較弱, 為 -70dBM, 實際連線為較強的 5G : 




測試速率下載居然有 170MB, 上傳有 60MB : 




結論當然是拔掉穿牆王, 直接連線 ASUS WiFi 6 啦!

2025年10月24日 星期五

安裝前門路旁 CW300 攝影機

八月時曾到裕誠路小米店買了一組 CW300 攝影機, 打算安裝在鄉下老家前門靠路邊的牆上原 CRV 攝影機下方, 但帶回鄉下後一直沒時間用. 

上周去小漢買了一條約 20 尺的電線與插座插頭, 趁今日光復節連假首日, 把電鑽鐵鎚尖嘴鉗等工具備妥, 先在窗戶塑膠接合處鑽一個洞, 把房間的電引出來, 沿著牆面鋪線, 然後在牆上鑽兩個洞, 將基座鎖緊後安裝攝影鏡頭, 最後送電在手機設定米家 App, 順利添加了此新裝置 : 

 



前門路燈晚上很亮, 攝影效果不錯. 

2025年9月3日 星期三

好站 : 小霸王 MQTT 伺服器

這是我今年初上小霸王的 ESP32-CAM 線上課程時, 尤博介紹他自家好用的免費 MQTT 伺服器, 不但網頁介面簡單清爽, 測試發現速度也很快 :





最近在規劃鄉下老家大門口的太陽能智慧信箱, 屆時會用到 MQTT, 可以先使用小霸王的 MQTT 服務, 我現在已擁有自己的主機, 以後也可以模仿小霸王在 Mapleboard 上打造自己的 MQTT (如果有那個賊時間的話哈哈). 

2025年4月24日 星期四

momo 買書 3 本加購 64GB TF 卡

最近在學 Canva, 從母校借來的 "剛剛好的 Canva 設計教本" 被預約還回去了, 這本寫得很好值得買下來, 剛好 momo 有滿額再打 86 折活動, 所以又多買了兩本, 並加購一張 64GB TF 卡 (momo 加購商品價格都比較優惠) :





全部原價 2748 元, 優惠後 2030 元, 用掉 136 元 momo 幣, 實付 1894 元 (69 折).

2025年4月23日 星期三

MicroPython 學習筆記 : PCB 天線與 IPEX 天線訊號強度比較

去年 11 月初為了用具有 SPI RAM 的開發板做測試, 第一次買 ESP32 WROVER, 它有兩種款式, 一是使用 PCB 天線的 ESP32-WROVER-E, 另一款是使用外接 IPEX 天線的 ESP32-WROVER-IE, 當時先各買一片, 想說比較看看訊號到底差別多大, 但一直沒動手, 趁這兩天拿板子出來測試推播訊息到 Telegram, 順便做訊號強度測試. 




測試方法是利用 esptools 模組的 scan_ssid() 函式, 它會掃描周邊的 WiFi 熱點, 傳回它們的 SSID, MAC, 以及信號強度, 兩塊板子均位於相同位置進行測試. 

首先測試外接 IPEX 天線的 ESP32-WROVER-IE 開發板, 將 config.py 與 esptools.py 上傳開發板, 呼叫 connect_wifi() 連線最近的基地台後呼叫 scan_ssid() : 

>>> import config  
>>> import esptools  
>>> ssid=config.SSID   
>>> password=config.PASSWORD   
>>> ip=esptools.connect_wifi(ssid, password)  
network config: ('192.168.50.199', '255.255.255.0', '192.168.50.1', '192.168.50.1')
>>> esptools.scan_ssid() 
ASUS_RT_AC52-Plus 5c:27:d4:f3:82:22 -52dBm
>>> esptools.scan_ssid()  
ASUS_RT_AC52-Plus 5c:27:d4:f3:82:22 -51dBm
>>> esptools.scan_ssid()
ASUS_RT_AC52-Plus 5c:27:d4:f3:82:22 -51dBm
>>> esptools.scan_ssid()  
ASUS_RT_AC52-Plus 5c:27:d4:f3:82:22 -49dBm

掃描 4 次發現其信號強度約 -49 至 -52 dBm 左右. 

接下來測試 ESP32-WROVER-E 開發板 :

>>> import config  
>>> import esptools  
>>> ssid=config.SSID   
>>> password=config.PASSWORD   
>>> ip=esptools.connect_wifi(ssid, password)  
network config: ('192.168.50.199', '255.255.255.0', '192.168.50.1', '192.168.50.1')
>>> esptools.scan_ssid() 
ASUS_RT_AC52-Plus 5c:27:d4:f3:82:22 -78dBm
>>> esptools.scan_ssid()  
ASUS_RT_AC52-Plus 5c:27:d4:f3:82:22 -78dBm
>>> esptools.scan_ssid()
ASUS_RT_AC52-Plus 5c:27:d4:f3:82:22 -78dBm
>>> esptools.scan_ssid()  
ASUS_RT_AC52-Plus 5c:27:d4:f3:82:22 -78dBm

可見 PCB 天線偵測到的 WiFi 信號強度確實比 IPEX 弱, 大約低了 20dBm 左右. 如果開發板離 WiFi 基地台較遠, 可以改用外接天線的 ESP32-WROVER-IE 開發板, 板子價格差不多, 只是要多一支 IPEX 天線的成本 (約 30 元左右). 

參考 :


2025年3月12日 星期三

OmniXRI TinyML 小學堂 (2025) 【第0講】& 【第1講】

今天晚上參加了歐尼克斯許哲豪老師的 TinyML 直播講座 : 





此課程連續 12 周會在每周三 20:00~21:00 直播, 簡報等相關資料可在 GitHub 下載 : 


聽完才發現, 原來還有那麼多我沒聽過沒碰過的 AIoT 開發板啊! 

2024年12月4日 星期三

參加徐爸的 HMI 分享會

今天在滑臉書時發現慧手科技徐爸晚上八點要辦 HMI 分享會, 之前有上過徐爸收費課程 "ESP32 也有 AI 腦" 覺得很棒, 所以分享會當然要參加啦. 結束時大伙敲碗 Part 2, 沒想到睡前就看到 Part 2 日期也出來了, 訂於下周三 (12/11) 同一時間 : 

HMI觸控螢幕編輯軟體(請務必先下載安裝) : 


分享會議室:


書籍購買連結:


套件包購買連結:


#需自備型號為TJC8048X550_011CS的5吋HMI觸控螢幕與ESP32S
#還有32G以下的microSD與讀卡機
#一樣超過20人有興趣就開課囉

2024年11月12日 星期二

好站 : 台大演講網-智慧農業

今天找到台大演講網兩年前舉辦的一場農業物聯網演講錄影 (講者為陽明交大陳文亮教授) :





農業是物聯網應用很廣的場域, 例如農作物生長監控, 病蟲害防治, 以及防盜等等. 

2024年11月4日 星期一

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

在前一篇測試中已經完成 SSD1306 指針時鐘的改寫, 後續我想在此基礎上添加顯示秒針與日期資訊, 由於篇幅太長, 所以紀錄在此篇. 

本系列之前文章參考 :


1. 加上秒針 : 

如果要加上秒針, 則原本的分針長度拿來給秒針用, 所以時針與分針的長度要相對縮短一些才行, 修改後的程式碼如下 :

# ssd1306_clock_3.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()
    # 繪製時針, 分針與秒針    
    prev_s_angle=None   # 紀錄前一秒針的角度
    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()  # 繪製時針

        # 計算並繪製分針 (縮短至 20 像素) 
        m_angle=dt[5] * 360 / 60
        gfx.line(31, 31,
                 31 + math.trunc(20 * math.sin(math.radians(m_angle))),  # 修改分針長度為 20
                 31 - math.trunc(20 * math.cos(math.radians(m_angle))), 1)
        oled.show()  # 繪製分針
        # 計算並繪製秒針 (使用原來的分針長度 25)
        s_angle=dt[6] * 360 / 60            
        if prev_s_angle is not None:  # 清除舊的秒針線條
            gfx.line(31, 31,
                 31 + math.trunc(25 * math.sin(math.radians(prev_s_angle))),
                 31 - math.trunc(25 * math.cos(math.radians(prev_s_angle))), 0) 
        gfx.line(31, 31,
                 31 + math.trunc(25 * math.sin(math.radians(s_angle))),  # 秒針長度設為 25
                 31 - math.trunc(25 * math.cos(math.radians(s_angle))), 1)   
        oled.show()  # 繪製新的秒針
        prev_s_angle=s_angle  # 更新前一秒針的角度
        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) # 填滿 0 清空錶面
            gfx.circle(31, 31, 1, 1)  # 重繪時鐘圓心
            gfx.circle(31, 31, 29, 1) # 重繪時鐘外框
            xtools.tw_now()  # RTC 時鐘同步 NTP 
else:
    print('無法連線 WiFi 網路!')

黃底色部分即添加的顯示秒針功能, 由於 while 迴圈每秒跑一圈, 秒針每秒要重繪, 在畫新的秒針前必須先將舊的秒針線條清除, 否則秒針線條會一直不斷地在錶面累積, 這裡用 prev_s_angle 來記錄前一個秒針的角度 (分針與時針變動較緩慢不須每秒重繪, 迴圈末尾在 59 秒要跳下一分鐘時會重繪錶面, 時針與分針是在跳分時重繪的).

結果如下 : 




可見秒針每秒都會隨秒數跳動而旋轉. 


2. 在右半邊上半部顯示日期時間文字 : 

接下來我想在螢幕右半邊的上半部以文字顯示日期, 星期, 與時間等四列資訊. 右半部寬度 64px. 每個字元 8px, 因此每列只能顯示 8 個字元, 規劃如下 :

2024
Nov.4
Monday
10:16:38

以下是從 RTC 時鐘取得的日期時間 tuple : 

>>> from machine import RTC      
>>> rtc=RTC()    
>>> dt=rtc.datetime()    
>>> dt    
(2024, 11, 4, 0, 11, 33, 24, 909053)     # (年, 月, 日, 星期, 時, 分, 秒, 微秒)
   
前三個元素是日期, 第四個是星期 (0=Monday), 第 5~7 個元素是時間, 我們可以從 datetime() 傳回的元組來製作要顯示的日期時間資訊 . 

首先來處理月份, 先定義一個 months 串列 : 

>>> months=['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May.', 'Jun.', 'Jul.', 'Aug.', 'Sep.', 'Oct.', 'Nov.', 'Dec.']    
製作月份日期字串 : 

>>> md=f'{months[dt[1]-1]}{dt[2]}'     
>>> md   
'Nov.4'   

製作年份字串 : 

>>> year=str(dt[0])     # 轉成字串
>>> year   
'2024'

定義星期串列來製作星期字串 :

>>> days=['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']     
>>> day=days[dt[3]]     
>>> day       
'Monday'

利用格式化字串製作時分秒字串 :  

>>> hms='%02d:%02d:%02d' % (dt[4], dt[5], dt[6])    
>>> hms     
'11:39:27'

依據上面測試修改程式碼如下 :

# ssd1306_clock_4.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()
    # 繪製時針, 分針與秒針    
    prev_s_angle=None   # 紀錄前一秒針的角度
    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()  # 繪製時針

        # 計算並繪製分針 (縮短至 20 像素) 
        m_angle=dt[5] * 360 / 60
        gfx.line(31, 31,
                 31 + math.trunc(20 * math.sin(math.radians(m_angle))),  # 修改分針長度為 20
                 31 - math.trunc(20 * math.cos(math.radians(m_angle))), 1)
        oled.show()  # 繪製分針
        # 計算並繪製秒針 (使用原來的分針長度 25)
        s_angle=dt[6] * 360 / 60            
        if prev_s_angle is not None:  # 清除舊的秒針線條
            gfx.line(31, 31,
                 31 + math.trunc(25 * math.sin(math.radians(prev_s_angle))),
                 31 - math.trunc(25 * math.cos(math.radians(prev_s_angle))), 0)
        gfx.line(31, 31,
                 31 + math.trunc(25 * math.sin(math.radians(s_angle))),  # 秒針長度設為 25
                 31 - math.trunc(25 * math.cos(math.radians(s_angle))), 1)
        oled.show()  # 繪製新的秒針
        prev_s_angle=s_angle  # 更新前一秒針的角度
        if dt[6] == 59: # 59 秒時清空錶面重繪時鐘
            gfx.fill_circle(31, 31, 25, 0) # 填滿 0 清空錶面
            gfx.circle(31, 31, 1, 1)  # 重繪時鐘圓心
            gfx.circle(31, 31, 29, 1) # 重繪時鐘外框
            xtools.tw_now()  # RTC 時鐘同步 NTP
        # 在右半部顯示日期時間資訊
        months=['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May.', 'Jun.',
                'Jul.', 'Aug.', 'Sep.', 'Oct.', 'Nov.', 'Dec.']
        days=['Monday', 'Tuesday', 'Wednesday', 'Thursday',
              'Friday', 'Saturday', 'Sunday']
        # 在螢幕右半邊上半部四列顯示日期時間資訊
        gfx.fill_rect(64, 0, 64, 64, 0)  # 清除右半邊的顯示區域
        # 更新日期與時間顯示
        oled.text(str(dt[0]), 64, 0, 1)  # 年
        oled.text(f'{months[dt[1] - 1]}{dt[2]}', 64, 8, 1)  # 月.日
        oled.text(days[dt[3]], 64, 16, 1)  # 星期
        oled.text('%02d:%02d:%02d' % (dt[4], dt[5], dt[6]), 64, 24, 1)  # 時分秒
        oled.show()
else:
    print('無法連線 WiFi 網路!')

黃底色部分為此次添加的程式碼. 注意, 由時日期時間資訊中已包含跳動的秒數, 所以原本在時鐘右下角的秒數顯示已被去除. 結果如下 :




3. 在螢幕右下半部顯示氣象資訊 : 

上面的範例程式只使用了螢幕的右上半部, 我們可以在右下半部顯示從 OpenWeatherMap 取得的氣象資料, 關於 OpenWeatherMap 的 API 用法參考 :


在之前的測試中我們已經將查詢 OpenWeatherMap API 的方法寫成如下的爬蟲函式 :

def get_weather(country, city, api_key):
    url=f'https://api.openweathermap.org/data/2.5/weather?q={city},{country}&units=metric&lang=zh_tw&appid={api_key}'
    try:
        res=urequests.get(url)
        data=ujson.loads(res.text)
        if data['cod']==200:    # 注意是數值
            ret={'geocode': data['id'],
                 'icon': data['weather'][0]['icon'],
                 'temperature': data['main']['temp'],
                 'pressure': data['main']['pressure'],
                 'humidity': data['main']['humidity']}
            return ret
        else:
            print(f'code:{data['cod']}')
            return None
    except Exception as e:
        print(e)
        return None 

使用 OpenWeatherMap API 前須先申請 API Key, 然後把它寫入 config.py 裡的一個變數中 (例如 WEATHER_API_KEY) 以備程式取用. 

呼叫 get_weather() 前需要先匯入 ujson 與 urequests 模組 :

>>> import ujson   
>>> import urequests   

傳入 API key, 城市, 與國家代號會傳回一個字典, 我們要將其中的 temperature, humidity, 與 temperature 三個鍵的值顯示在螢幕的右下半部. 

>>> weather_api_key=config.WEATHER_API_KEY      
>>> city='Kaohsiung'       
>>> country='TW'   
>>> data=get_weather(country, city, weather_api_key)      
>>> data   
{'icon': '03d', 'temperature': 28.94, 'geocode': 1673820, 'pressure': 1013, 'humidity': 71}

修改上面的程式碼, 在秒數為 59 秒時呼叫 get_weather() 取得氣象資料:

        if dt[6] == 59: # 59 秒時清空錶面重繪時鐘
            gfx.fill_circle(31, 31, 25, 0) # 填滿 0 清空錶面
            gfx.circle(31, 31, 1, 1)  # 重繪時鐘圓心
            gfx.circle(31, 31, 29, 1) # 重繪時鐘外框
            xtools.tw_now()  # RTC 時鐘同步 NTP
            data=get_weather('TW', 'Kaohsiung', config.WEATHER_API_KEY)  # 查詢氣象資料

 然後顯示在 (64, 32) 開始的右下邊螢幕 :

        if data:
            degree=[0x00, 0x06, 0x09, 0x09, 0x06, 0x00, 0x00, 0x00]  # 溫度符號點陣
            buf=bytearray(degree)
            fb=framebuf.FrameBuffer(buf, 8, 8, framebuf.MONO_VLSB)
            oled.text(str(data['temperature']), 64, 32, 1)  # 溫度
            oled.framebuf.blit(fb, 104, 32) # degree symbol
            oled.text(str(data['humidity']) + '%', 64, 40, 1)  # 濕度
            oled.text(str(data['pressure']) + 'mPh', 64, 48, 1)  # 氣壓
            oled.text('Kaohsiung', 64, 56, 1)  # 濕度

注意, 此處使用 framebuf 來顯示右上角小圈圈的溫度度數符號, 做法參考 : 


完整程式碼如下 :

# ssd1306_clock_5.py
import config   
import xtools
from gfx import GFX   
from machine import I2C, Pin, RTC  
import ssd1306
import time
import math
import urequests  
import ujson  
import framebuf   

def get_weather(country, city, api_key):
    url=f'https://api.openweathermap.org/data/2.5/weather?q={city},{country}&units=metric&lang=zh_tw&appid={api_key}'
    try:
        res=urequests.get(url)
        data=ujson.loads(res.text)
        if data['cod']==200:    # 注意是數值
            ret={'geocode': data['id'],
                 'icon': data['weather'][0]['icon'],
                 'temperature': data['main']['temp'],
                 'pressure': data['main']['pressure'],
                 'humidity': data['main']['humidity']}
            return ret
        else:
            print(f'code:{data['cod']}')
            return None
    except Exception as e:
        print(e)
        return None 

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()
    # 繪製時針, 分針與秒針    
    prev_s_angle=None   # 紀錄前一秒針的角度
    data=None  # 氣象資料
    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()  # 繪製時針

        # 計算並繪製分針 (縮短至 20 像素) 
        m_angle=dt[5] * 360 / 60
        gfx.line(31, 31,
                 31 + math.trunc(20 * math.sin(math.radians(m_angle))),  # 修改分針長度為 20
                 31 - math.trunc(20 * math.cos(math.radians(m_angle))), 1)
        oled.show()  # 繪製分針
        # 計算並繪製秒針 (使用原來的分針長度 25)
        s_angle=dt[6] * 360 / 60            
        if prev_s_angle is not None:  # 清除舊的秒針線條
            gfx.line(31, 31,
                 31 + math.trunc(25 * math.sin(math.radians(prev_s_angle))),
                 31 - math.trunc(25 * math.cos(math.radians(prev_s_angle))), 0)
        gfx.line(31, 31,
                 31 + math.trunc(25 * math.sin(math.radians(s_angle))),  # 秒針長度設為 25
                 31 - math.trunc(25 * math.cos(math.radians(s_angle))), 1)
        oled.show()  # 繪製新的秒針
        prev_s_angle=s_angle  # 更新前一秒針的角度
        if dt[6] == 59: # 59 秒時清空錶面重繪時鐘
            gfx.fill_circle(31, 31, 25, 0) # 填滿 0 清空錶面
            gfx.circle(31, 31, 1, 1)  # 重繪時鐘圓心
            gfx.circle(31, 31, 29, 1) # 重繪時鐘外框
            xtools.tw_now()  # RTC 時鐘同步 NTP
            data=get_weather('TW', 'Kaohsiung', config.WEATHER_API_KEY)  # 查詢氣象資料
        # 在右半部顯示日期時間資訊
        months=['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May.', 'Jun.',
                'Jul.', 'Aug.', 'Sep.', 'Oct.', 'Nov.', 'Dec.']
        days=['Monday', 'Tuesday', 'Wednesday', 'Thursday',
              'Friday', 'Saturday', 'Sunday']
        # 在螢幕右半邊上半部四列顯示日期時間資訊
        gfx.fill_rect(64, 0, 64, 64, 0)  # 清除右半邊的顯示區域
        # 更新日期與時間顯示
        oled.text(str(dt[0]), 64, 0, 1)  # 年
        oled.text(f'{months[dt[1] - 1]}{dt[2]}', 64, 8, 1)  # 月.日
        oled.text(days[dt[3]], 64, 16, 1)  # 星期
        oled.text('%02d:%02d:%02d' % (dt[4], dt[5], dt[6]), 64, 24, 1)  # 時分秒
        if data:  
            degree=[0x00, 0x06, 0x09, 0x09, 0x06, 0x00, 0x00, 0x00] # 溫度符號點陣
            buf=bytearray(degree)   
            fb=framebuf.FrameBuffer(buf, 8, 8, framebuf.MONO_VLSB)    
            oled.text(str(data['temperature']), 64, 32, 1)  # 溫度
            oled.framebuf.blit(fb, 104, 32) # degree symbol
            oled.text(str(data['humidity']) + '%', 64, 40, 1)  # 濕度
            oled.text(str(data['pressure']) + 'mPh', 64, 48, 1)  # 氣壓
            oled.text('Kaohsiung', 64, 56, 1)  # 濕度
        oled.show()
else:
    print('無法連線 WiFi 網路!')

黃底色部分為新增的部分, 結果如下 : 




大功告成! 

最終範例與所需模組已打包上傳 GitHub :


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 網路!')

2024年11月2日 星期六

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

今天在網路上搜尋 MicroPython 指針時鐘資料時找到下面這篇 : 


範例程式使用了 GFX 這個模組來繪圖, 查詢網路發現原來這也是 Adafruit 的作品, GFX 旨在為各種尺寸的 OLED 及 LCD 營幕提供一個通用繪圖函式庫, 其原始碼使用 C 語言寫成, 參考 :


GFX 的 MicroPython 驅動程式 gfx.py 可從 GitHub 下載 :


GFX 運作原理參考下面這兩篇 :


以下測試使用 128*64 解析度的 SSD1306 OLED 顯示器, 搭配燒錄 MicroPython v1.23.0 韌體的 LOLIN D32 開發板 (ESP32). SSD1306 透過 I2C 介面與 ESP32 開發板的 GPIO21 (SDA) 與 GPIO22 (SCL) 連接, 這是 ESP32 預設的 I2C 接腳, 但也可以用 SoftI2C 模組將一般 GPIO 腳模擬成 I2C 的 SCL 與 SDA 接腳, 參考 :


本測試使用的 Adafruit SSD1306 驅動程式可從 GitHub 下載 :


首先將下載的 ssd1306.py 與 gfx.py 上傳到開發板 : 

MicroPython v1.23.0 on 2024-06-02; Generic ESP32 module with ESP32
Type "help()" for more information.

用 dir() 檢視 gfx 模組內容 :

>>> import gfx     
>>> dir(gfx)      
['__class__', '__name__', '__dict__', '__file__', 'GFX']   

其中 GFX 類別就是用來建立繪圖物件的類別, 為了方便通常直接從 gfx 匯入 GFX 即可 :

>>> from gfx import GFX   

先匯入模組 : 

>>> from machine import I2C, Pin      
>>> import ssd1306   

建立 SSD1306_I2C 物件 :

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

這樣就可以呼叫 GFX 類別的建構式 GFX() 來建立 GFX 繪圖物件了, 呼叫時必須傳入螢幕寬度, 高度, 以及繪製像素的函式, 參數結構如下 :

GFX(width, height, pixel_function)   
 
因為 GFX 是一個通用的繪圖函式庫, 不是專用於特定螢幕 (可用於 SSD1306 或 ILI9341 等螢幕), 因此必須傳入螢幕尺寸與繪製像素的函式, 以 SSD1306 而言, 這些參數都可在 SSD1306_I2C 物件中取得, 檢視 oled 物件 : 

>>> dir(oled)     
['__class__', '__init__', '__module__', '__qualname__', '__dict__', 'addr', 'buffer', 'fill', 'framebuf', 'invert', 'pixel', 'scroll', 'text', 'width', 'height', 'external_vcc', 'pages', 'poweron', 'init_display', 'write_cmd', 'show', 'poweroff', 'contrast', 'write_framebuf', 'i2c', 'temp']

其中 width 與 height 屬性就是螢幕尺寸; 而 pixel() 就是像素繪製函式 : 

>>> oled.width   
128
>>> oled.height   
64
>>> oled.pixel      
<bound_method>

只要將此三個參數傳入 GFX() 即可建立 GFX 物件 : 

>>> gfx=GFX(oled.width, oled.height, oled.pixel)     
>>> type(gfx)     
<class 'GFX'>   

檢視 GFX 物件內容 :

>>> dir(gfx)    
['__class__', '__init__', '__module__', '__qualname__', '__dict__', 'fill_rect', 'hline', 'line', 'rect', 'vline', 'width', 'height', '_pixel', '_slow_hline', '_slow_vline', 'circle', 'fill_circle', 'triangle', 'fill_triangle']

其中 width 與 height 為傳入隻螢幕尺寸 :

>>> gfx.width   
128
>>> gfx.height      
64

GFX 物件提供許多繪圖函式, 摘要說明如下表 :


 GFX 物件的方法 說明
 line(x0, y0, x1, y1, color)  在座標 (x0, y0) 與 (x1, y1) 間畫一直線, color=0 (暗)/1 (亮)
 hline(x, y, w, color) 從座標 (x, y) 開始向右畫一段長 w 的水平線, color=0 (暗)/1 (亮)
 vline(x, y, h, color) 從座標 (x, y) 開始向下畫一段高 h 的水平線, color=0 (暗)/1 (亮)
 rect(x, y, w, h, color) 從座標 (x, y) 開始繪製寬 w 高 h 的矩形, color=0 (暗)/1 (亮)
 fill_rect(x, y, w, h, color) 從座標 (x, y) 開始繪製寬 w 高 h 的填滿矩形, color=0 (暗)/1 (亮)
 triangle(x0, y0, x1, y1, x2, y2, color) 連接座標 (x0, y0), (x1, y1), (x2, y2) 三點繪製三角形, color=0 (暗)/1 (亮)
 fill_triangle(x0, y0, x1, y1, x2, y2, color) 連接座標 (x0, y0), (x1, y1), (x2, y2) 三點繪製填滿三角形, color=0 (暗)/1 (亮)
 circle(x0, y0, r, color) 以 (x0, y0) 為圓心, r 為半徑繪製圓形, color=0 (暗)/1 (亮)
 fill_circle(x0, y0, r, color) 以 (x0, y0) 為圓心, r 為半徑繪製填滿圓形, color=0 (暗)/1 (亮)


這些繪圖方法都是利用呼叫 GFX() 時傳入的第三參數, 即顯示器的像素繪製函式 oled.pixel() 將圖描出來的, 不同的螢幕有各自的像素繪製函式, 因此 GFX 是一個可用於各型螢幕的通用函式庫. 


1. 繪製直線 :   

GFX 物件的 line(), hline(), vline() 可用來繪製直線, 先清除螢幕 : 

>>> oled.fill(0)      # 填滿暗點
>>> oled.show()     

呼叫 line() 方法繪製直線, 先畫一條左上角到右下角的對角線 : 

>>> gfx.line(0, 0, gfx.width-1, gfx.height-1, 1)  # 從左上角到右下角的對角線
>>> oled.show()    

再畫一條右上角到左下角的對角線 : 

>>> gfx.line(gfx.width-1, 0, 0, gfx.height-1, 1)   # 從右上角到左下角的對角線
>>> oled.show()    

結果如下 : 




接下來測試 vline() 與 hline(), 先清螢幕 :

>>> oled.fill(0)      # 填滿暗點
>>> oled.show()   

先畫一條起點為 (0, 31) 寬度為 128 的水平線  (即左邊中央往右) : 

>>> gfx.hline(0, 31, 128, 1)     
>>> oled.show()   

然後畫一條起點為 (63, 0) 高度為 63 的垂直線 (即最上面中央往下) : 

>>> gfx.vline(63, 0, 64, 1)    
>>> oled.show()     

結果如下 :




當然這也可以用 line() 方法來畫, 只是要傳入直線兩端座標較麻煩 : 

>>> gfx.line(0, 31, 127, 31, 1)   
>>> gfx.line(63, 0, 63, 63, 1)   
>>> oled.show()    

結果與上面是一樣的. 


2. 繪製矩形 :   

GFX 物件的 rect() 與 fill_rect()  可用來繪製矩形, 先清除螢幕 : 

>>> oled.fill(0)      # 填滿暗點
>>> oled.show()     

繪製一個沿螢幕邊框的方框矩形 : 

>>> gfx.rect(0, 0, 128, 64, 1)    
>>> oled.show()   

結果是一個有 1px 寬度的方框 :




如果要讓框邊加粗 1 個 px, 可以在此矩形內畫一個內縮 1px 的矩形, 這時左上角座標要用 (1, 1), 尺寸因兩邊各縮 1px 變成 128-2=126 與 64-2=62 : 

>>> gfx.rect(1, 1, 126, 62, 1)      
>>> oled.show()     

結果如下 :

 


下面測試 fill_rect() 效果, 先清除螢幕 : 

>>> oled.fill(0)      # 填滿暗點
>>> oled.show()      

然後在螢幕四周畫一個 1px 邊框 : 

>>> gfx.rect(0, 0, 128, 64, 1)     
>>> oled.show()     

接著往內縮一個 px 用 fill_rect() 畫一個填滿內部的矩形, 因邊框佔據 1px, 內縮 1px, 故左上角座標是 (2, 2), 尺寸要減 4 : 

>>> gfx.fill_rect(2, 2, 124, 60, 1)    
>>> oled.show()  

結果如下 : 




由於填滿效果會不斷繪製畫素, 故照相時會因掃描頻率較低產生 aliasing 現象, 實際顯示效果是白邊框內有一黑框, 裡面才是白色填滿. 


3. 繪製三角形 :   

GFX 物件的 triangle() 與 fill_triangle()  繪製三角形, 先清除螢幕 : 

>>> oled.fill(0)      # 填滿暗點
>>> oled.show()     

用 triangle() 在螢幕繪製一個等腰三角形 :

>>> gfx.triangle(0, 0, 127, 0, 63, 64, 1)   
>>> oled.show()      

結果如下 : 




用 fill_triangle() 在螢幕繪製一個填滿的等腰三角形 :

>>> oled.fill(0)      # 填滿暗點清除螢幕
>>> oled.show()     
>>> gfx.fill_triangle(0, 0, 127, 0, 63, 64, 1)    
>>> oled.show()    

結果如下 : 




4. 繪製圓形 :   


GFX 物件的 triangle() 與 fill_triangle()  繪製三角形, 先清除螢幕 : 

>>> oled.fill(0)      # 填滿暗點清除螢幕
>>> oled.show()     

呼叫 circle() 在螢幕左半邊畫一個圓形, 圓心為 (31, 31); 再呼叫 fill_circle() 在右半邊畫一個填滿的圓形, 圓心為 (96, 31), 半徑都是 31 : 

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

結果如下 : 




2024年10月27日 星期日

MicroPython 學習筆記 : 使用 SoftI2C 與 SSD1306 通訊

在前面所做的 SSD1306 實驗中都使用 ESP32 預設的硬體 I2C 腳 : GPIO21 (SDA) 與 GPIO22 (SCL), 其實也可以用 machine.SoftI2C 類別將任何其他的 GPIO 腳模擬為軟體 I2C 腳. 


本實驗使用 LOLIN D32 開發板, 使用 GPIO4 模擬 SDA 腳, GPIO5 模擬 SCL 腳. 下列程式改自 "MicroPython 學習筆記 : 用 SSD1306 顯示氣象資訊 (一)" 最後面的 weather_ssd1306.py :

# weather_ssd1306_softi2c.py
import config   
import xtools
import urequests   
import ujson
from machine import SoftI2C, Pin   
import ssd1306
import time

def get_weather(country, city, api_key):
    url=f'https://api.openweathermap.org/data/2.5/weather?q={city},{country}&units=metric&lang=zh_tw&appid={api_key}'
    try:
        res=urequests.get(url)
        data=ujson.loads(res.text)
        if data['cod']==200:    # 注意是數值
            ret={'geocode': data['id'],
                 'icon': data['weather'][0]['icon'],
                 'temperature': data['main']['temp'],
                 'pressure': data['main']['pressure'],
                 'humidity': data['main']['humidity']}
            return ret
        else:
            return None
    except Exception as e:
        return None

def show_weather(oled, data):
    oled.fill(0)  # (填滿 0 熄滅畫素)
    oled.text(data['city'], 0, 0, 1)  
    oled.text(data['time'], 0, 8, 1)  
    oled.text(f"Temperature:{data['temperature']}", 0, 16, 1)    
    oled.text(f"Humidity:{data['humidity']}", 0, 24, 1)    
    oled.text(f"Pressure:{data['pressure']}", 0, 32, 1)    
    oled.show()

def main():
    ip=xtools.connect_wifi(config.SSID, config.PASSWORD)
    if ip:
        # 設定氣象爬蟲參數
        weather_api_key=config.WEATHER_API_KEY
        city='Kaohsiung'   
        country='TW'
        # 建立 I2C 與 SSD1306_I2C 物件
        i2c=SoftI2C(scl=Pin(5), sda=Pin(4)) # ESP32 I2C
        oled=ssd1306.SSD1306_I2C(128, 64, i2c)
        while True:  # 每分鐘抓一次更新 OLED 顯示器
            data=get_weather(country, city, weather_api_key)
            data['time']=xtools.tw_now()  # 新增或更新欄位
            data['city']=city # 新增或更新欄位
            show_weather(oled, data)
            time.sleep(60)
    else:
        print('無法連線 WiFi')

if __name__ == '__main__':
  main()

僅修改了黃底部分, 主要是把 I2C 換成 SoftI2C 類別, 且 GPIO 腳更換為 GPIO4 與 GPIO5 而已,  結果如下 : 




可見結果是一樣的.