2024年11月6日 星期三

露天購買飛利浦 CR2032 鈕扣電池 3 組 (15 顆)

連續兩天晚上整理二哥讀大學時做實驗用的零件, 模組, IC 等, 回收非常多電子器材資產, 其中有一台小巧口袋式三用數位電錶, 電源是兩顆 CR2032 鈕扣電池, 這台小儀器換電池有點麻煩, 須拆下四角的小螺絲, 再艱難地取下鈕扣電池, 一量電壓為 0, 所以上露天一口氣買了 15 顆 : 






萊爾富取貨免運 144 元. 

MicroPython 學習筆記 : ESP32-WROVER 開發板測試

十月底再露天買了兩片 ESP32-WROVER 開發板, 但賣家卻寄錯, 收到的是 ESP32-WROOM, 向賣家反映後表示會補寄, 參考 : 


今天收到補寄的 ESP32-WROVER 後馬上燒錄韌體來確認看看有無 PSRAM : 

D:\ESP32>esptool --port COM6 flash_id   
esptool.py v4.6.2
Serial port COM6
Connecting.....
Detecting chip type... Unsupported detection protocol, switching and trying again...
Connecting....
Detecting chip type... ESP32
Chip is ESP32-D0WD-V3 (revision v3.1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: a8:42:e3:ae:72:64
Uploading stub...
Running stub...
Stub running...
Manufacturer: 68
Device: 4016
Detected flash size: 4MB   
Hard resetting via RTS pin...

抹除 Flash 記憶體 : 

D:\ESP32>esptool --chip esp32 --port COM6 erase_flash    
esptool.py v4.6.2
Serial port COM6
Connecting....
Chip is ESP32-D0WD-V3 (revision v3.1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: a8:42:e3:ae:72:64
Uploading stub...
Running stub...
Stub running...
Erasing flash (this may take a while)...
Chip erase completed successfully in 3.9s
Hard resetting via RTS pin...

燒錄 PSRAM 版韌體 : 

D:\ESP32>esptool --chip esp32 --port COM6 write_flash -z 0x1000 ESP32_GENERIC-SPIRAM-20240602-v1.23.0.bin    
esptool.py v4.6.2
Serial port COM6
Connecting......
Chip is ESP32-D0WD-V3 (revision v3.1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: a8:42:e3:ae:72:64
Uploading stub...
Running stub...
Stub running...
Configuring flash size...
Flash will be erased from 0x00001000 to 0x0018afff...
Compressed 1611216 bytes to 1055046...
Wrote 1611216 bytes (1055046 compressed) at 0x00001000 in 93.2 seconds (effective 138.3 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...

然後開啟 Thonny 連線開發板 :

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

Type "help()" for more information.

可見此為有 SPIRAM  (即 PSRAM) 存取功能的 MicroPython. 

用 gc.mem_free() 檢視 SRAM : 

>>> import gc   
>>> gc.mem_free()   
4174928  

這顯示 ESP32-WROVER 的堆積 (Heap) 記憶體空間超過 4MB, 一般的 ESP32-WROOM 則僅有 520KB 的 SRAM 可用而已, 這使得 WROVER 可應用在較耗記憶體的運算上. 

用 micropython.mem_info() 檢視記憶體使用情形 : 

>>> import micropython      
>>> micropython.mem_info()          
stack: 736 out of 15360    
GC: total: 56000, used: 16624, free: 39376, max new split: 4128768  
 No. of 1-blocks: 385, 2-blocks: 44, max blk sz: 32, max free sz: 2448

第一列顯示堆疊記憶體配置 15360 bytes, 目前已用掉 736 bytes. 堆疊主要用在函式呼叫與儲存區域變數. 第二列顯示堆積 (Heap) 記憶體配置與使用情形, total 為 MicroPython 管理的 Heap 記憶體容量為 56 KB, used 為已使用的堆積記憶體大小, free 是閒置可分配的空間. max new split 是 PSRAM (SPI RAM) 可用的最大外部記憶體空間. 可見 WROVER 有 4MB 的 PSRAM

上傳 config.py 設定檔與 xtools.py 函式庫後測試網路應用 (Line Notify 與 OpenAI API) :

# test.py
import config
import xtools

ip=xtools.connect_wifi(config.SSID, config.PASSWORD)
line_token=config.LINE_NOTIFY_TOKEN
openai_api_key=config.OPENAI_API_KEY
message='test'
image_url='https://cdn.pixabay.com/photo/2024/03/15/17/50/dogs-8635461_1280.jpg'
xtools.line_msg(line_token, message)
xtools.line_sticker(line_token, message, 1, 4)
xtools.line_image_url(line_token, message, image_url)
prompt='Who are you'
print(xtools.ask_gpt(prompt, openai_api_key))

結果正常值形 : 

>>> import config   
>>> import xtools   
>>> ip=xtools.connect_wifi(config.SSID, config.PASSWORD)    
Connecting to network...
network config: ('192.168.50.199', '255.255.255.0', '192.168.50.1', '192.168.50.1')
>>> line_token=config.LINE_NOTIFY_TOKEN   
>>> openai_api_key=config.OPENAI_API_KEY    
>>> message='test'    
>>> image_url='https://cdn.pixabay.com/photo/2024/03/15/17/50/dogs-8635461_1280.jpg'    
>>> xtools.line_msg(line_token, message)    
Message has been sent.
>>> xtools.line_sticker(line_token, message, 1, 4)     
'The sticker has been sent.'
>>> xtools.line_image_url(line_token, message, image_url)    
'The image URL has been sent.'
>>> prompt='Who are you'    
print(xtools.ask_gpt(prompt, openai_api_key))
I am an AI language model developed by OpenAI, designed to assist with a wide range of queries by providing information, answering questions, and engaging in conversation. How can I assist you today?

LINE Notify 結果如下 : 




掃描附近基地台 :

>>> xtools.scan_ssid()    
ASUS_RT_AC52-Plus 5c:27:d4:f3:82:22 -60dBm
7N8F-1 b4:f2:67:13:36:5a -85dBm
OPPO A55 c6:b1:51:54:e8:3f -86dBm
HOME bc:f6:85:ff:a0:e0 -94dBm
ASUS_RT_AC52 10:7c:61:9d:30:18 -96dBm

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 :


露天購買松下18650電池 3400mah x 10 (買十送二)

最近想動手製作太陽能物聯網信箱, 需要 18650 來打造電池組, 在露天找到下面 3400mAh 鋰電池, 因很久以前買了電池盒, 所以選購凸頭的 : 






全家取貨免運 450 元. 

2024年11月3日 星期日

2024 年第 44 周記事

週四康芮颱風來襲又放了一天颱風假, 但這回高雄沒甚麼災情, 反倒是台北較嚴重, 路樹倒一堆, 台北人終於理解為何邁邁上回為何要多放一天災後重建假了, 因為連要去上個班都要左閃右閃倒下橫在路面的樹, 甚至單向通行必須逆向行駛. 

週三颱風前一晚無風也無雨, 跟菁菁與水某去三多商圈新光三越好正點吃飯, 菁菁說我們以前來過, 有帶阿公一起來, 應該是某年的父親節, 但是我卻毫無印象, 真糟糕, 我該不會開始癡呆了吧? 因比預定時間早到, 就先逛一下, 隔壁上海湯包居然一個客人也無, 往上一樓逛, 發現 12 樓的瓦城也是, 指友好正點算是人多的, 感覺現在南高雄商圈人潮少很多. 

週六下午跟爸去請松和先生的媳婦看日子, 擇日幫阿蘭添罐至家族墓園, 她找到之前松和先生留下的紀錄簿, 上面有記載是坐西北方, 今年宜進行添罐, 排好之後下周才會知道哪一天. 前兩周周末下午都下雨, 今日則是好天氣, 下午我帶了前不久到貨的電動打草機去巡墓園, 想說把通往墓園路上的雜草打一打, 沒想到最近有人來打草, 整條小徑草都很短, 只好把墓園周圍剛冒出的雜草打一打就回家了. 

最近兩周芭樂結果甚少, 上週套袋僅 10 幾顆, 本周更少只有三顆, 看來都在發新芽沒開花, 下午拿了柴刀修剪往上長的枝葉, 這些幾乎都長芽不開花, 且太高難套袋. 傍晚在菜園找了兩處適合的地方挖洞, 將上回從燕巢買回來的珍珠芭樂苗種下去, 種了兩棵還有一棵不知要種哪, 因為原先預定要種芭樂的地方被小舅整理為菜圃, 可能要另尋處所了 (考慮馬路邊).

博客來買書 1 本 : 演算法圖解原理 x Python實作 x 創意應用王者歸來(四版)

今天在陳南宗大神臉書看到推介洪老師下面這本書 66 折好書 (今日限定) :






小七至興店取貨免運 514 元. 

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()     

結果如下 : 




露天購買 TEA5767 收音機模組 + 4 號轉 3 號電池轉換桶

今天在露天搜尋 "收音機模組" 沒想到還真的有這種模組 (TEA5767), 可用 I2C 介面與開發板連接, 價格約 200 元左右也不貴就買一個來玩玩, 同時附帶買4 號轉 3 號電池的轉換桶, 有了這個以後都用 4 號就可以了. 買這個也是想改裝, 連接到鋰電池包, 這樣就可以用剛買的 LiitoKalla 充電器充電與測內阻.  







萊爾富取貨免運 259 元. 

關於 TEA5767 模組資料參考 : 


也有 MicroPython 驅動 : 


2024年10月31日 星期四

康芮颱風假

今天因康芮颱風來襲放了一天颱風假, 但高雄這次還好, 比起上回山陀兒這次算是小巫了. 不過姊姊說台北風雨很大, 看新聞花東首當其衝, 風雨跟山陀兒襲擊高雄的慘狀差不多. 花了一個早上整理二哥大學時做專題完帶回來的零件箱, 清出好多寶貝, 包括一個數位電錶, 最近本想中將電子買一個的說, 這下就不用了. 還有一個迷你口袋型示波器, 一個 XBOX 遙控器, 以及麵包板與2電子零件與模組等. 

昨天下午菁菁的客人臨時請假, 駕訓班又因前一梯要考試不上課, 想要請我們去吃新光三越 11F 的好正點, 我原先以為是家附近自由路的好正點, 後來才搞清楚是前鎮的 (有吃到飽). 我上回評鑑有補休, 請了一個小時補休提前下班去楠梓載菁菁, 想說放在書包超過三年的 SOGO 禮券四千元順便帶去花掉, 但吃完要載菁菁回楠梓, 所以也沒時間到隔壁 SOGO. 

下午仔細看禮券上寫履約保證至 113 年 10 月 31 日, 那不就今天? 看外面也沒啥雨就邀水某做捷運去三多商圈的 SOGO 把禮券用掉. 結果興沖沖到 SOGO 門口卻看到一張告示牌 : 因應颱風, 本公司今日暫停營業, 哈! 白跑一趟, 只好搭捷運回家, 剛好捷運站內的 Mister Donut 有萬聖節買五送五活動, 就帶了十個甜甜圈回家免得空手而回. 仔細看禮券說明, 過了履約保證期應該還是可以使用.