2016-02-10 14:23:03 +00:00
# -*- coding: utf-8 -*-
"""
MicroPython SSD1306 I2C driver
"""
__author__ = " Markus Birth "
__copyright__ = " Copyright 2016, Markus Birth "
__credits__ = [ " Markus Birth " ]
__license__ = " MIT "
__version__ = " 1.0 "
__maintainer__ = " Markus Birth "
__email__ = " markus@birth-online.de "
__status__ = " Production "
# Datasheet: https://www.adafruit.com/datasheets/SSD1306.pdf
# Inspiration from:
# - https://github.com/khenderick/micropython-drivers/tree/master/ssd1306
#
# PINOUT
# WiPy/pyBoard display function
#
# 3V3 or any Pin => VCC 3.3V logic voltage (0=off, 1=on)
# SDA => SDM data
# SCL => SCL clock
# GND => GND
#
# WiPy (on Exp board, SD and User-LED jumper have to be removed!)
# PWR = directly from 3V3 pin of the WiPy
try :
import pyb as machine
except :
# WiPy
import machine
import struct
import time
class SSD1306 :
ADDRESSING_HORIZ = 0x00
ADDRESSING_VERT = 0x01
ADDRESSING_PAGE = 0x02
POWER_UP = 0xaf
POWER_DOWN = 0xae
DISPLAY_BLANK = 0xae
DISPLAY_ALL = 0xa5
DISPLAY_NORMAL = [ 0xaf , 0xa4 , 0xa6 ]
DISPLAY_INVERSE = 0xa7
DC_CMD = 0x80
DC_DATA = 0x40
def __init__ ( self , i2c , pwr = None , devid = 0x3c ) :
self . width = 128
self . height = 64
self . devid = devid
self . power = self . POWER_DOWN
self . addressing = self . ADDRESSING_HORIZ
self . display_mode = self . DISPLAY_NORMAL
# init the I2C bus and pins
i2c . init ( i2c . MASTER , baudrate = 400000 ) # 400 kHz
if pwr :
if " OUT_PP " in dir ( pwr ) :
# pyBoard style
pwr . init ( pwr . OUT_PP , pwr_PULL_NONE )
else :
# WiPy style
pwr . init ( pwr . OUT , None )
self . i2c = i2c
self . pwr = pwr
2016-02-28 22:42:45 +00:00
self . osc_freq = 8
self . clock_div = 1
2016-02-10 14:23:03 +00:00
2016-02-10 14:55:34 +00:00
self . power_on ( ) # enable power to the display
self . set_power ( self . POWER_DOWN ) # set display to sleep mode
2016-02-28 22:42:45 +00:00
self . set_osc_freq ( 8 , False ) # set oscillator freq., but don't send to LCD yet
self . set_clock_div ( 1 ) # set clock div and send osc_freq+clock_div to LCD
self . set_mux_ratio ( self . height ) # set multiplex ratio to 64 (default), for 32px: 32
2016-02-29 00:18:06 +00:00
self . set_disp_offset ( 0 ) # set display offset to 0
self . set_disp_start_line ( 0 ) # set display start line to 0
2016-03-22 16:52:00 +00:00
self . set_chargepump_enabled ( True ) # chargepump on (ext. VCC: off)
2016-02-10 14:55:34 +00:00
self . set_addressing ( self . ADDRESSING_HORIZ )
2016-03-22 16:52:00 +00:00
self . set_segment_remap_enabled ( False )
self . set_com_output_scan_dir_remap_enabled ( False )
2016-02-10 14:55:34 +00:00
self . command ( [ 0xda , 0x12 ] ) # com pins (for 32px: 0x02)
self . set_contrast ( 255 )
self . command ( [ 0xd9 , 0xf1 ] ) # precharge (ext. VCC: 0x22 = RESET)
self . command ( [ 0xdb , 0x40 ] ) # Vcom deselect
self . set_display ( DISPLAY_NORMAL ) # enables and sets disp to show RAM contents, not inversed
2016-02-10 14:23:03 +00:00
self . clear ( )
2016-02-28 22:42:45 +00:00
def set_power ( self , power ) :
2016-02-10 14:23:03 +00:00
""" Sets the power mode of the LCD controller """
assert power in [ self . POWER_UP , self . POWER_DOWN ] , " Power must be POWER_UP or POWER_DOWN. "
self . power = power
self . command ( power )
2016-03-22 16:52:00 +00:00
def set_com_output_scan_dir_remap_enabled ( self , status ) :
""" Enables or disables COM output scan direction remapping. """
assert isinstance ( status , bool ) , " Status must be True or False. "
self . command ( ( 0xc8 if status else 0xc0 ) )
def set_segment_remap_enabled ( self , status ) :
""" Enables or disables segment remapping. """
assert isinstance ( status , bool ) , " Status must be True or False. "
self . command ( ( 0xa1 if status else 0xa0 ) )
def set_chargepump_enabled ( self , status ) :
""" Enables or disables the charge pump. """
assert isinstance ( status , bool ) , " Status must be True or False. "
self . command ( [ 0x8d , ( 0x14 if status else 0x10 ) ] )
2016-02-28 22:42:45 +00:00
def _set_oscfreqclockdiv ( self ) :
""" Sets the oscillator frequency and clock divider value """
value = ( self . osc_freq << 4 ) | ( self . clock_div - 1 )
self . command ( [ 0xd5 , value ] )
def set_osc_freq ( self , osc_freq , set = True ) :
""" Stores and sets the oscillator frequency """
assert 0 < = osc_freq < 16 , " Oscillator frequency must be between 0 and 15. "
self . osc_freq = osc_freq
if set :
self . _set_oscfreqclockdiv ( )
def set_clock_div ( self , clock_div , set = True ) :
""" Stores and sets clock divider """
assert 0 < clock_div < = 16 , " Clock divider must be between 1 and 16. "
self . clock_div = clock_div
if set :
self . _set_oscfreqclockdiv ( )
def set_mux_ratio ( self , mux_ratio ) :
""" Sets the multiplex ratio. """
assert 16 < = mux_ratio < = 64 , " Mux ratio must be between 16 and 64. "
self . command ( [ 0xa8 , ( mux_ratio - 1 ) ] )
2016-02-29 00:18:06 +00:00
def set_disp_offset ( self , offset ) :
""" Sets the display offset (vertical shift). """
assert 0 < = offset < 63 , " Offset must be between 0 and 63. "
self . command ( [ 0xd3 , offset ] )
def set_disp_start_line ( self , start_line ) :
""" Sets the display RAM start line register. """
assert 0 < = start_line < 63 , " Start line must be between 0 and 63. "
self . command ( 0x40 | start_line )
2016-02-10 14:23:03 +00:00
def set_adressing ( self , addr ) :
""" Sets the adressing mode """
assert addr in [ self . ADDRESSING_HORIZ , self . ADDRESSING_VERT , self . ADDRESSING_PAGE ] , " Addressing must be ADDRESSING_HORIZ, ADDRESSING_VERT or ADDRESSING_PAGE. "
self . addressing = addr
self . command ( [ 0x20 , addr ] )
def set_display ( self , display_mode ) :
""" Sets display mode (blank, black, normal, inverse) """
assert display_mode in [ self . DISPLAY_BLANK , self . DISPLAY_ALL , self . DISPLAY_NORMAL , self . DISPLAY_INVERSE ] , " Mode must be one of DISPLAY_BLANK, DISPLAY_ALL, DISPLAY_NORMAL or DISPLAY_INVERSE. "
self . display_mode = display_mode
self . command ( [ display_mode ] )
def set_contrast ( self , value ) :
""" set OLED contrast """
2016-02-10 14:55:34 +00:00
assert 0x00 < = value < = 0xff , " Contrast value must be between 0 and 255 "
2016-02-10 14:23:03 +00:00
self . command ( [ 0x81 , value ] )
def position ( self , x , y ) :
""" set cursor to page y, column x """
assert 0 < = x < self . width , " x must be between 0 and 127 "
assert 0 < = y < self . height / / 8 , " y must be between 0 and 7 "
self . command ( [ 0x20 , 0x00 , x , 0x21 , 0x00 , y ] )
def clear ( self ) :
""" clear screen """
self . position ( 0 , 0 )
self . data ( [ 0 ] * ( self . height * self . width / / 8 ) )
self . position ( 0 , 0 )
def sleep_ms ( self , mseconds ) :
try :
time . sleep_ms ( mseconds )
except AttributeError :
machine . delay ( mseconds )
def sleep_us ( self , useconds ) :
try :
time . sleep_us ( useconds )
except AttributeError :
machine . udelay ( useconds )
def power_on ( self ) :
if self . pwr :
self . pwr . value ( 1 )
self . reset ( )
def reset ( self ) :
""" issue reset impulse to reset the display """
self . rst . value ( 0 ) # RST on
self . sleep_us ( 100 ) # reset impulse has to be >100 ns and <100 ms
self . rst . value ( 1 ) # RST off
# Defaults after reset:
# 1. Display is OFF
# 2. 128 x 64 Display Mode
# 3. Normal segment and display data column address and row address mapping (SEG0 mapped to
# address 00h and COM0 mapped to address 00h)
# 4. Shift register data clear in serial interface
# 5. Display start line is set at display RAM address 0
# 6. Column address counter is set at 0
# 7. Normal scan direction of the COM outputs
# 8. Contrast control register is set at 7Fh
# 9. Normal display mode (Equivalent to A4h command)
self . power = self . POWER_DOWN
self . addressing = self . ADDRESSING_HORIZ
self . display_mode = self . DISPLAY_NORMAL
def power_off ( self ) :
self . clear ( )
self . set_power ( self . POWER_DOWN )
self . sleep_ms ( 10 )
if self . pwr :
self . pwr . value ( 0 ) # turn off power
def command ( self , arr ) :
""" send bytes in command mode """
self . bitmap ( arr , self . DC_CMD )
def data ( self , arr ) :
""" send bytes in data mode """
self . bitmap ( arr , self . DC_DATA )
def bitmap ( self , arr , dc ) :
arr = [ dc ] + arr
buf = struct . pack ( ' B ' * len ( arr ) , * arr )
self . i2c . send ( buf , addr = self . devid , timeout = 5000 )