2022年12月16日 星期五

MicroPython 學習筆記 : SSD1306 OLED 顯示器測試 (一)

本篇是在 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 通訊腳之用. 

參考 :


沒有留言:

張貼留言