星期四, 11月 29, 2018

使用 I2C SSD1306 0.96" OLED

手邊有一款 OLED 顯示器,尺寸大小僅 0.96",採用 SSD1306 晶片 (詳見技術文件) 做驅動控制,可顯示單色 128x64 點陣圖形。它不需要額外背光電源線路且以 I2C (IIC) 做資料傳輸,所以線路連結較曾經使用過的 LCD1602 或 LCD12864 要來得簡潔,在微控制器裝置輕巧的要求下是可考慮的選擇。

以 Raspberry Pi 的使用 (Python) 為例,可參考 Interfacing SSD1306 OLED Display with Raspberry Pi 的內容,該例引用了 Adafruit_SSD1306 程式模組。一般上,參照網路上的現有資源也就足夠,但本文興趣在如何綜合這些資源簡易的自訂一個驅動程式。



SSD1306 I2C bus data format
檢視 SSD1306 技術文件,了解經 I2C 介面傳送資料的格式如附圖。傳送命令資料時,需要在每個資料位元組前置入一個數值為 0x80 的控制位元組 (control byte);然而將圖形顯示資料寫入 GDDRAM (Graphic Display Data RAM) 時,可僅於資料集前置一個數值為 0x40 的控制位元組。
查看命令表 (Command Table) 決定晶片的初始設定命令集,初步先定義一個 class 如下:
# SSD1306 class
class SSD1306:
    DEV_ADDR = 0x3c
    CMDSET_INIT = (0xae,      #display off
                   0x20,0,    #horizontal addressing mode
                   0x40,      #display start line from 0
                   0xa0,      #column address 0 mapped to seg0
                   0xa8,0x3f, #set multiplex ratio to 64
                   0xc0,      #scan from com0
                   0xd3,0,    #vertical shift by com0
                   0xda,0x12, #alternative com pin configuration
                   0xd5,0x80, #display clock divide ratio, oscillator frequency
                   0xd9,0xf1, #pre-charge period
                   0xdb,0x30, #Vcomh deselect level
                   0x81,0x7f, #contrast control
                   0xa4,      #output follows ram content
                   0xa6,      #normal display
                   0x8d,0x14, #enable charge pump
                   0xaf)      #display on
    def __init__(self):
        self.dev = i2c(self.DEV_ADDR, 1)
        self.command(self.CMDSET_INIT)
    def command(self, cmdset):
        d = []
        for c in cmdset:
            d.extend((0x80,c))
        self.dev.write(d)

若想使用 smbus 模組來傳送 I2C 介面資料會遇到 32 bytes 限制的問題,可參考 smbus i2c read/write more than 32 bytes 內所提及的方法。
處理圖形顯示資料可引用 PIL 模組的諸多功能:
from PIL import Image,ImageDraw,ImageFont
在初始時中添加相關的物件:
#
        self.img = Image.new('1',(128,64))
        self.draw = ImageDraw.Draw(self.img)
        self.font = ImageFont.load_default()
        self.tfont = ImageFont.truetype('/usr/share/fonts/truetype/wqy/wqy-microhei.ttc',16)

實作有關顯示功能的程式:
#
    def display(self, on=None):
        if on:
            self.command([0xaf])
            return
        elif on is False:
            self.command([0xae])
            return
        gd = [0x40]
        gd.extend([0] *(128*8))
        p = self.img.load()
        for pg in range(8):
            for col in range(128):
                b = 0
                for i in range(8):
                    b = b >> 1
                    b |= 0 if p[(col, pg*8 +i)] == 0 else 0x80
                gd[pg*128 +col+1] = b
        self.command([0x21,0,127,0x22,0,7])
        self.dev.write(gd)
    def clear(self):
        self.draw.rectangle((0,0,127,63),outline=0,fill=0)
        self.display()
    def text(self,pos,msg,ttc=False):
        self.draw.text(pos,msg,font=self.tfont if ttc else self.font, fill=255)
    def close(self):
        self.display(on=False)
        self.dev.close()

嘗試做捲動功能:
#
    def start_scroll(self,dir,pg_start,pg_end,rows=None):
        cmd = [0x29] if rows else [0x26]
        cmd[0] += dir
        cmd.extend([0,pg_start,0,pg_end,rows,0x2f] if rows else [0,pg_start,0,pg_end,0,0xff,0x2f])
        self.stop_scroll()
        self.command(cmd)
    def stop_scroll(self):
        self.command([0x2e])
        self.display()

此時,可以來一個簡易的測試:
#
    disp = SSD1306()
    time.sleep(3)
    disp.draw.rectangle((0,0,127,63),outline=1,fill=0)
    disp.text((8,8),'SSD1306 Testing...')
    disp.display()
    time.sleep(5)
    disp.draw.rectangle((0,0,127,63),outline=0,fill=0)
    disp.text((8,0),u'三山半落青天外',ttc=True)
    disp.text((8,16),u'二水中分白鷺洲',ttc=True)
    disp.text((8,32),u'總為浮雲能蔽日',ttc=True)
    disp.text((8,48),u'長安不見使人愁',ttc=True)
    disp.display()
    time.sleep(5)
    disp.start_scroll(0, 0, 7)
    time.sleep(10)
    disp.start_scroll(1, 0, 7, 1)
    time.sleep(10)
    disp.stop_scroll()
    time.sleep(10)
    disp.close()

測試的結果:

沒有留言:

張貼留言