本篇是在 ESP32 開發板上重做 SSD1306 顯示器的測試紀錄, 使用的是 0.91 吋解析度 126*32 的 OLED 顯示器, 它在垂直方向可顯示 4 列文字, 每個字 8 px, 4*8=32px; 在水平方向可顯示 16 行字, 每個字 8 px, 16*8=128px :
之前的測試文章參考 :
MicroPython 官網 SSD1306 教學文章參考 :
另外下面這篇雖然是用 Arduino 語言, 但其中關於 I2C 介面的說明值得參考 :
總之, 使用 I2C 介面的好處是只需要 SCL 與 SDA 兩條通訊線, 以及 VCC 與 GND 電源線總共四條線即可, 比 SPI 所需的連接線少, 但缺點是速度比 SPI 慢.
ESP-WROOM-32 的 I2C 通訊腳分別在 GPIO21 (SDA) 與 GPIO22 (SCL), SDA 在右上方起算第 3 腳 (編號 36); 而 SCL 則為第 6 腳 (編號 33), 如下圖所示 :
Source : ESP32 Pinout
接線方式很簡單, 就是將 ESP32 的 SCL (GPIO22), SDA (GPIO21) 與 SSD1306 的 SCL, SDA 對接, 同時把 3.3V/5V VCC 與 GND 對接即可 :
實體接線如下圖 :
這樣就可以寫程式來測試了.
在最近買的 "創客自造者工作坊-Python 感測器大應用 (旗標)" 一書中說 ssd1306 函式庫是 MicroPython 內建模組, 但我在最新的 1.19.1 版中直接 import ssd1306 卻失敗 (無此模組) :
MicroPython v1.19.1 on 2022-06-18; ESP32 module with ESP32
Type "help()" for more information.
>>> import ssd1306
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: no module named 'ssd1306'
有可能之前 (書本用的是 v1.12) 有納入但後來移除了.
1. 上傳獨立的 ssd1306 模組 :
我找到 2017 年測試 ESP8266 時從 MicroPython 官網下載的 ssd1306.py 函式庫 (5 KB, 目前已找不到下載點), 上傳開發板測試發現還可以用, 我把它放在 GitHub 保存 :
也可以從作者 GitHub 下載最新版本上傳開發板 :
點 ssd1306.py 進去按 'RAW' 鈕, 再按滑鼠右鍵選 '另存新檔' 即可下載 :
這樣便可匯入 ssd1306 模組使用了, 先來檢視此模組內容 :
>>> import ssd1306
>>> dir(ssd1306)
['__class__', '__name__', 'const', '__file__', 'framebuf', 'time', 'SSD1306_I2C', 'SET_CONTRAST', 'SET_ENTIRE_ON', 'SET_NORM_INV', 'SET_DISP', 'SET_MEM_ADDR', 'SET_COL_ADDR', 'SET_PAGE_ADDR', 'SET_DISP_START_LINE', 'SET_SEG_REMAP', 'SET_MUX_RATIO', 'SET_COM_OUT_DIR', 'SET_DISP_OFFSET', 'SET_COM_PIN_CFG', 'SET_DISP_CLK_DIV', 'SET_PRECHARGE', 'SET_VCOM_DESEL', 'SET_CHARGE_PUMP', 'SSD1306', 'SSD1306_SPI']
>>> help(ssd1306)
object <module 'ssd1306' from 'ssd1306.py'> is of type module
SET_DISP -- 174
SET_MUX_RATIO -- 168
time -- <module 'utime'>
SET_VCOM_DESEL -- 219
__name__ -- ssd1306
framebuf -- <module 'framebuf'>
SET_DISP_OFFSET -- 211
SET_CHARGE_PUMP -- 141
SET_SEG_REMAP -- 160
SET_DISP_START_LINE -- 64
SSD1306 -- <class 'SSD1306'>
SET_COM_PIN_CFG -- 218
SSD1306_I2C -- <class 'SSD1306_I2C'>
SSD1306_SPI -- <class 'SSD1306_SPI'>
SET_DISP_CLK_DIV -- 213
__file__ -- ssd1306.py
const -- <function>
SET_MEM_ADDR -- 32
SET_ENTIRE_ON -- 164
SET_COM_OUT_DIR -- 192
SET_PRECHARGE -- 217
SET_NORM_INV -- 166
SET_CONTRAST -- 129
SET_PAGE_ADDR -- 34
SET_COL_ADDR -- 33
其中最重要的是 SSD1306_I2C 與 SSD1306_SPI 這兩個類別, 分別用來建立 I2C 與 SPI 之通訊物件, 此處會用到的是 SSD1306_I2C 類別. 以前要先呼叫 MicroPython 內建建構函式 I2C() 建立 I2C 物件, 然後將其傳給 ssd1306 的 SSD1306_I2C() 建構式, 不過 MicroPython 原先的內建類別 I2C 在新版韌體中已經被廢棄了, 執行時會出現 warning :
>>> import ssd1306
>>> from machine import Pin, I2C
>>> i2c=I2C(scl=Pin(22), sda=Pin(21))
>>> oled=ssd1306.SSD1306_I2C(128, 32, i2c)
Warning: I2C(-1, ...) is deprecated, use SoftI2C(...) instead
改用 SoftI2C 類別即可 :
>>> import ssd1306
>>> from machine import Pin, SoftI2C
>>> i2c=SoftI2C(scl=Pin(22), sda=Pin(21))
>>> type(i2c) # 建立 SoftI2C 物件
<class 'SoftI2C'>
>>> oled=ssd1306.SSD1306_I2C(128, 32, i2c) # 0.91 吋解析度是 128*32 px
>>> type(oled)
<class 'SSD1306_I2C'>
>>> dir(oled)
['__class__', '__init__', '__module__', '__qualname__', '__dict__', 'addr', 'blit', 'buffer', 'fill', 'fill_rect', 'hline', 'invert', 'line', 'pixel', 'rect', 'scroll', 'text', 'vline', 'width', 'height', 'external_vcc', 'pages', 'init_display', 'write_cmd', 'show', 'poweroff', 'poweron', 'contrast', 'write_data', 'i2c', 'temp', 'write_list']
>>> oled.text('Hello World', 0, 0, 1) # 在起始座標 (0, 0) 處填入字串 (col=1 點亮)
>>> oled.show()
此處因為使用 ESP32 預設的 I2C 接腳 GPIO22 (SCL) 與 GPIO21 (SDA), 所以在 SoftI2C() 中要用 此兩腳地 Pin 物件分別傳給 scl 與 sda 參數. SoftI2C 顧名思義應該可以指定任何 GPIO 腳做 I2C 通訊用, 這後續可以驗證看看.
呼叫 SSD1306_I2C() 後會建立一個 SSD1306_I2C 物件, 在顯示器上繪製文字或圖形就是用此物件的方法, 常用方法如下表 :
SSD1306_I2C 物件的方法 | 說明 |
fill(col) | 將顯示記憶體全部畫素填入 col=1 (亮) 或 0 (暗) |
pixel(x, y, col) | 在顯示記憶體指定畫素位置 (x, y) 填入 col=1 (亮) 或 0 (暗) |
text(string, x, y, col=1) | 在顯示記憶體指定畫素位置 (x, y) 起填入預設 col=1 之字串 string |
show() | 將顯示記憶體內容輸出於面板顯示內容 |
scroll(dx, dy) | 將顯示記憶體畫素內容向上下 (dy) 或向左右 (dx) 捲動 |
建立 SSD1306_I2C 物件 oled 後, 呼叫 text(msg, x, y [, col=1]) 將要顯示的字串傳入顯示器緩衝器中, 其中參數 x, y 為顯示的起始座標, 顯示器原點在左上角, 0.91 吋的 OLED 解析度 128*32 的座標軸如下, 顯示 ASCII 字元時每個字元解析度 8*8, 故可顯示 4 列 16 行共 64 個字元 :
但呼叫 text() 並不會顯示字串, 那只是將字串放進記憶體的顯示緩衝區而已, 必須呼叫 show() 方法才會真正輸出到顯示幕上, 結果如下 :
可見 'Hello World' 顯示在第一列起始的地方.
2. 用 upip 安裝 ssid1306 模組 :
首先利用自訂模組 xtools 的 connect_wifi_led() 先連上網路 :
MicroPython v1.19.1 on 2022-06-18; ESP32 module with ESP32
Type "help()" for more information.
>>> import xtools
>>> xtools.connect_wifi_led()
Connecting to network...
network config: ('192.168.2.115', '255.255.255.0', '192.168.2.1', '168.95.1.1')
'192.168.2.115'
然後匯入 upip, 呼叫 upip.install() 線上安裝 micropython-ssd1306 :
>>> import upip
>>> upip.install('micropython-ssd1306')
Installing to: /lib/
Warning: micropython.org SSL certificate is not validated
Installing micropython-ssd1306 0.3 from https://files.pythonhosted.org/packages/01/d0/0841d47772962c80af3ab178ef062ed2cd524cb99eb38463e669428402a8/micropython-ssd1306-0.3.tar.gz
安裝完就可匯入 ssd1306 了 :
>>> import ssd1306
>>> dir(ssd1306)
['__class__', '__name__', 'const', '__file__', 'framebuf', 'SET_CONTRAST', 'SET_ENTIRE_ON', 'SET_NORM_INV', 'SET_DISP', 'SET_MEM_ADDR', 'SET_COL_ADDR', 'SET_PAGE_ADDR', 'SET_DISP_START_LINE', 'SET_SEG_REMAP', 'SET_MUX_RATIO', 'SET_COM_OUT_DIR', 'SET_DISP_OFFSET', 'SET_COM_PIN_CFG', 'SET_DISP_CLK_DIV', 'SET_PRECHARGE', 'SET_VCOM_DESEL', 'SET_CHARGE_PUMP', 'SSD1306', 'SSD1306_I2C', 'SSD1306_SPI']
>>> dir(ssd1306)
['__class__', '__name__', 'const', '__file__', 'framebuf', 'SET_CONTRAST', 'SET_ENTIRE_ON', 'SET_NORM_INV', 'SET_DISP', 'SET_MEM_ADDR', 'SET_COL_ADDR', 'SET_PAGE_ADDR', 'SET_DISP_START_LINE', 'SET_SEG_REMAP', 'SET_MUX_RATIO', 'SET_COM_OUT_DIR', 'SET_DISP_OFFSET', 'SET_COM_PIN_CFG', 'SET_DISP_CLK_DIV', 'SET_PRECHARGE', 'SET_VCOM_DESEL', 'SET_CHARGE_PUMP', 'SSD1306', 'SSD1306_I2C', 'SSD1306_SPI']
可見內容與上面獨立的模組完全一樣.
用 upip 安裝後就如同內建一樣可直接 import 使用了.
下面的範例改編自以前寫的 "MicroPython on ESP8266 (十八) : SSD1306 液晶顯示器測試" 測試 4, 只是此處改用了 xtools 模組而已, 這使得程式碼更精簡. SSD1306 螢幕配置如下 :
測試 1 : 在 SSD1306 LED 上顯示 IP 與日期時間 [看原始碼]
import xtools
from xtools import pad_zero
import ssd1306
from machine import Pin, SoftI2C
import time
ip=xtools.connect_wifi_led()
i2c=SoftI2C(scl=Pin(22), sda=Pin(21))
oled=ssd1306.SSD1306_I2C(128, 32, i2c)
week={0:'Mon',1:'Tue',2:'Wed',3:'Thu',4:'Fri',5:'Sat',6:'Sun'}
n=0
while True:
utc_epoch=time.mktime(time.localtime())
Y,M,D,H,m,S,W,DY=time.localtime(utc_epoch + 28800)
YMD=f'{str(Y)}-{pad_zero(M)}-{pad_zero(D)}'
HmS=f'{pad_zero(H)}:{pad_zero(m)}:{pad_zero(S)}'
row1=f'@{ip}'
row2=f'{YMD} {week[W]}'
row3=HmS
oled.fill(0) # 先清除緩衝器舊內容
oled.text(row1, 0, 0, 1)
oled.text(row2, 0, 8, 1)
oled.text(row3, 0, 16, 1)
oled.show()
time.sleep(1)
此例假定已經呼叫 xtools.tw_now() 設定 RTC 與 NTP 伺服器同步過了, 所以 time.localtime() 會傳回 UTC 時間, 加上 28800 秒就是台灣時間了. 由於 SSD1306 一列只能顯示 16 個字元, xtools.tw_now() 傳回的日期時間為 19 個字元會超過, 所以此處匯入 xtools.pad_zero() 自行處理 日期 YMD 與時間 HmS 的格式, 將日期後面冠上星期縮寫, 與時間分成兩列顯示.
結果如下 :
由於無限迴圈用 time.sleep(1) 暫停 1 秒, 所以秒數會逐秒跳動.
下面範例改用其他 GPIO 腳作為 I2C 通訊接腳, 測試 SoftI2C() 是否如其名稱所示可將任何 GPIO 腳模擬成預設的 I2C 腳位 (在 ESP-WROOM-32 開發板是 scl=GPIO22, sda=GPIO21), 此處改用 scl=GPIO19 與 sda=GPIO18 (右上角屬下來第 8, 9 腳) :
程式碼與上面幾乎一樣, 只是改掉 SoftI2C() 裡參數 scl 與 sda 的設定而已 :
測試 2 : 改用任意 GPIO 腳當 scl 與 sda 腳測試 SoftI2C() [看原始碼]
import xtools
from xtools import pad_zero
import ssd1306
from machine import Pin, SoftI2C
import time
ip=xtools.connect_wifi_led()
i2c=SoftI2C(scl=Pin(19), sda=Pin(18)) # 改用 GPIO19, 18 當 scl, sda
oled=ssd1306.SSD1306_I2C(128, 32, i2c)
week={0:'Mon',1:'Tue',2:'Wed',3:'Thu',4:'Fri',5:'Sat',6:'Sun'}
n=0
while True:
utc_epoch=time.mktime(time.localtime())
Y,M,D,H,m,S,W,DY=time.localtime(utc_epoch + 28800)
YMD=f'{str(Y)}-{pad_zero(M)}-{pad_zero(D)}'
HmS=f'{pad_zero(H)}:{pad_zero(m)}:{pad_zero(S)}'
row1=f'@{ip}'
row2=f'{YMD} {week[W]}'
row3=HmS
oled.fill(0)
oled.text(row1, 0, 0, 1)
oled.text(row2, 0, 8, 1)
oled.text(row3, 0, 16, 1)
oled.show()
time.sleep(1)
結果如下 :
可見 machine.SoftI2C() 確實能模擬除預設的 GPIO22 與 GPIO21 之外的其他 GPIO 腳當 I2C 通訊腳之用.
參考 :
沒有留言 :
張貼留言