OLED显示屏在消费类产品中非常流行,使用有机发光二极管技术,每个像素只在需要时自发光,而非传统的背光方式。下面演示用MCUSH/Python控制128x64显示模块。
这款显示器由SSD1306芯片驱动,有传统的I2C和兼容SPI两种控制方式。I2C方式仅需要将SDA/SCL连接至PA0/PA1。
SPI方式需要MOSI/SCK/CS连接至PA1/PA2/PA3,连接RST(模块复位)至PA4,连接DC(数据命令模式切换)至PA5。
和上次控制MAX7219点阵模块流程类似:先要初始化OLED控制对象,然后用该对象创建虚拟画布,这样导入了画点、画线、贴图和文字等接口。
根据数据手册,整屏64行按8行一组分成8个Pages,纵向8个像素作为一个字节整体控制,于是后续的缓存策略都是如此:8行,每行128字节。
先将两种控制方式共用的API提取出来,准备一个基础类:
class Ssd1306_Common():
current_page = None
current_x = None
def cmd( self, b ):
raise NotImplementedError
def dat( self, b ):
raise NotImplementedError
def fill( self, pattern ):
# 初始化时整屏填充固定模式
for p in range((self.height-1)/8+1):
self.cmd(0xb0+p)
self.cmd(0x01)
self.cmd(0x10)
for x in range(self.width):
self.dat(pattern)
def write_mem( self, page, x, mem ):
# 运行中写入指定位置缓存数据
# 加了点优化:当顺序位置写入时,不用重复发送位置坐标,提高效率
if page != self.current_page:
self.cmd(0xb0+page)
self.current_page = page
if x != self.current_x:
self.cmd(((x&0xf0)>>4)|0x10)
self.cmd((x&0x0f)|0x00)
self.current_x = x
self.dat( mem )
self.current_x += 1
def init(self):
# 寄存器初始化,参考数据手册
self.cmd(0xae) # turn off oled panel
self.cmd(0x00) # set low column address
self.cmd(0x10) # set high column address
self.cmd(0x40) # set start line address Set Mapping RAM Display Start Line (0x00~0x3F)
self.cmd(0x81) # set contrast control register
self.cmd(0xcf) # Set SEG Output Current Brightness
self.cmd(0xa1) # Set SEG/Column Mapping
self.cmd(0xc8) # Set COM/Row Scan Direction
self.cmd(0xa6) # set normal display
self.cmd(0xa8) # set multiplex ratio(1 to 64)
self.cmd(0x3f) # 1/64 duty
self.cmd(0xd3) # set display offset Shift Mapping RAM Counter (0x00~0x3F)
self.cmd(0x00) # not offset
self.cmd(0xd5) # set display clock divide ratio/oscillator frequency
self.cmd(0x80) # set divide ratio, Set Clock as 100 Frames/Sec
self.cmd(0xd9) # set pre-charge period
self.cmd(0xf1) # Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
self.cmd(0xda) # set com pins hardware configuration
self.cmd(0x12) #
self.cmd(0xdb) # set vcomh
self.cmd(0x40) # Set VCOM Deselect Level
self.cmd(0x20) # Set Page Addressing Mode (0x00/0x01/0x02)
self.cmd(0x02) #
self.cmd(0x8d) # set Charge Pump enable/disable
self.cmd(0x14) # set(0x10) disable
self.cmd(0xa4) # Disable Entire Display On (0xa4/0xa5)
self.cmd(0xa6) # Disable Inverse Display On (0xa6/a7)
self.cmd(0xaf) # turn on oled panel
其中两个函数cmd/dat分别用来发送命令指令和数据指令,根据SPI/I2C两种方式来实现。
I2C模式下的继承方式:
class Ssd1306_I2C(Ssd1306_Common):
def __init__( self, controller, scl=None, sda=None ):
self.controller = controller
self.controller.i2c_init( 0x3C, scl, sda, delay=5 )
self.init()
def cmd( self, b ):
self.controller.i2c( [0x00, b] ) # 引导符0x00表示后续是命令
def dat( self, b ):
self.controller.i2c( [0x40, b] ) # 引导符0x40表示后续是数据
class OLED128x64_I2C(Ssd1306_I2C):
width, height = 128, 64
SPI模式下的继承方式:
class Ssd1306_SPI(Ssd1306_Common):
def __init__( self, controller, rst=None, dc=None, sdo=None, sdi=None, sck=None, cs=None ):
self.controller = controller
self.controller.spi_init( sdo=sdo, sdi=sdi, sck=sck, cs=cs, delay=5 )
self.rst_pin = rst
self.dc_pin = dc
self.dc = None # 缓存当前DC脚状态,在后续发送cmd/dat时不用发送无效指令,提高效率
self.controller.pinOutputHigh( [rst, dc] )
self.reset()
self.init()
def reset( self ):
self.controller.pinClr( self.rst_pin )
time.sleep(0.1)
self.controller.pinSet( self.rst_pin )
def cmd( self, d ):
if self.dc != 0:
self.controller.pinClr( self.dc_pin )
self.dc = 0
self.controller.spi( [d] )
def dat( self, d ):
if self.dc != 1:
self.controller.pinSet( self.dc_pin )
self.dc = 1
self.controller.spi( [d] )
class OLED128x64_SPI(Ssd1306_SPI):
width, height = 128, 64
至此可以创建OLED128x64_XXX对象,调用写缓存write_mem函数来控制显示内容了,但这还不够,需要和Canvas画布类结合起来,调用更高阶的接口。
看看MCUSH库提供的Canvas类(位于misc/Canvas.py)的接口:
class Canvas():
def __init__( self, display ):
self.display = display
self.width = display.width
self.height = display.height
def flush( self, force=False ):
# flush screen
raise NotImplementedError
def setPixel( self, x, y, color=None, flush=True ):
# set pixel
raise NotImplementedError
def clrScr( self, flush=True ):
...
def drawVLine( self, x, y0, y1, color=None, flush=True ):
...
def drawHLine( self, y, x0, x1, color=None, flush=True ):
...
def drawRectangle( self, x0, y0, x1, y1, color=None, fill=True, flush=True ):
...
def drawLine( self, x0, y0, x1, y1, color=None, flush=True ):
...
def drawBitmap( self, x, y, bitmap, mode='normal', flush=True ):
...
def drawString( self, x, y, string, mode='normal', font=None, flush=True ):
...
必须实现flush和setPixel两个基础函数,才能调用Canvas提供的其它2D图像接口:
import Canvas as _Canvas
class Canvas(_Canvas.Canvas):
def __init__( self, display ):
self.display = display
self.width = display.width
self.height = display.height
self.pages = (display.height-1)/8+1
self.buffer = [ [0] * self.width for i in range(self.pages) ] # 缓存区
self.display.fill( 0 )
self.dirty = [0] * self.pages # 标注各个Page/X的8个像素是否“脏”,需要重绘
def flush( self, force=False ):
for p in range(self.pages):
for x in range(self.width):
if force or (self.dirty[p] & (1<
self.display.write_mem( p, x, self.buffer[p][x] )
self.dirty[p] &= ~(1<
def setPixel( self, x, y, color=None, flush=True ):
# check if it's out of range
if x < 0 or x >= self.width:
return
if y < 0 or y >= self.height:
return
page = y/8
byte_mask = 1 << (y%8)
byte_index = x
if color is None:
color = 1
if color:
self.buffer[page][byte_index] |= byte_mask
else:
self.buffer[page][byte_index] &= ~byte_mask
self.dirty[page] |= 1<
if flush:
self.flush()
所有基础环境准备就绪,下面编写调用脚本:
import time
from mcush import *
s = Mcush.Mcush('/dev/ttyACM0')
# Ssd1306模块已经集成了上文的基础环境
oled = Ssd1306.OLED128x64_SPI(s, rst='0.4', dc='0.5') # SPI接口的OLED
#oled = Ssd1306.OLED128x64_I2C(s) # I2C接口的OLED
cv = Ssd1306.Canvas(oled)
# 绘制边框
cv.drawRectangle( 0, 0, cv.width-1, cv.height-1, color=1, fill=False )
# 居中显示两行文字
line1 = 'Hardware control'
line2 = 'with Python'
w1, h = cv.getStringRenderSize(line1)
w2, h = cv.getStringRenderSize(line2)
cv.drawString( (cv.width-w1)/2, (cv.height-h)/2-2, line1 )
cv.drawString( (cv.width-w2)/2, (cv.height+h)/2+2, line2 )
# 循环显示转动图标
while True:
for i in range(7):
x, y = 10, 10 # 图标左上角位置
cv.drawRectangle( x, y, x+7, y+7, color=0, fill=True, flush=False )
cv.drawLine( x+i, y, x+7-i, y+7, flush=False )
cv.drawLine( x+i+1, y, x+7-i-1, y+7, flush=False )
cv.drawLine( x+7, y+i, x+0, y+7-i, flush=False )
cv.drawLine( x+7, y+i+1, x+0, y+7-i-1, flush=False )
cv.flush()
#time.sleep(0.1)
运行效果如下:用SPI/I2C控制OLED12864显示模块https://www.zhihu.com/video/1167188182536949760