011 – MicroPython TechNotes: 1.3 OLED Display

Previously, we learned on how to use the 0.96 inch OLED display with MicroPython language and we use an external driver library named ssd1306.py. Now in this tutorial, we will look at the identical but a little bigger display which is the 1.3 inch OLED display and it uses an SH1106 driver chip. It has a monochrome display resolution which is 128×64 pixels.
BILL OF MATERIALS:
- ESP32 development board.
- Gorillacell ESP32 shield.
- 4-pin female-female Dupont jumper wires.
- 1.3 OLED display.
PINOUT:
- GND – for the ground pin.
- VCC – for the supply voltage.
- SDA – for the i2c serial data pin.
- SCL – for the i2c serial clock pin.
HARDWARE INSTRUCTION:
- First, attach the ESP32 development board at the top of Gorillacell ESP32 shield and make sure that both USB port are on the same side.
- Next, attach the dupont wire to the 1.3 OLED display by following the color coding which is black for the ground, red for the VCC, yellow for the SDA, and white for the SCL pin.
- Next, attach the other side of the dupont wire to the ESP32 shield by matching the colors of the wires to the colors of the pin headers which is black to black, red to red, yellow and the following colors to yellow pin headers. For this experiment, I choose GPIO 21 for the SDA pin and GPIO 22 for the SCL pin.
- Next, power the ESP32 shield by attaching an external power supply with a type-C USB cable and make sure that the power switch is slide to ON state.
- Next, connect the ESP32 to the computer by attaching a micro USB cable. The demo circuit should be ready by now.
SOFTWARE INSTRUCTION:
- Now to make our life easier, we will use an ready-made driver library for the 1.3 OLED display which is the SH106 by Robert Hammelrath and originally developed by Radomir Dopieralski:
https://github.com/robert-hh/SH1106/blob/master/sh1106.py. You may also copy it below under the SOURCE CODE SECTION for your convenience. - Now copy and save the sh1106.py to Thonny Python IDE and save it to MicroPython’s root directory by clicking the File menu and select Save As.
- Click MicroPython device and save it as sh1106.py.
- Repeat #2 and #3 to copy the following files from Peter Hinch font-to-py library for the custom font: https://github.com/peterhinch/micropython-font-to-py especially for the writer_minimal.py as a module for displaying custom font and the freesans20.py as the custom font. You may also copy it below under the SOURCE CODE SECTION for your convenience.
- Now check if you successfully save the file on ESP32 by clicking the View menu and select File. The newly save file should be seen under the MicroPython device.
- Please feel free to try each example given or you may play along with the video tutorial.
- Also, try to modify the example according to your liking and most of all, enjoy learning.
VIDEO DEMONSTRATION:
CALL TO ACTION:
For any concern, write your message in the comment section.
You might also like to support my journey on Youtube by Subscribing. Click this to Subscribe to TechToTinker.
Thank you and have a good days ahead.
See you,
– George Bantique | tech.to.tinker@gmail.com
SOURCE CODE:
1. Example # 1, for exploring basic functions you can do with the OLED display:
# More details can be found in TechToTinker.blogspot.com
# George Bantique | tech.to.tinker@gmail.com
from machine import Pin, I2C
from sh1106 import SH1106_I2C
i2c = I2C(scl=Pin(22), sda=Pin(21), freq=400000)
oled = SH1106_I2C(128, 64, i2c, None, addr=0x3C)
oled.sleep(False)
# The following codes should be tested using the REPL.
# #1. To print a string:
# oled.text('Hello world', 0, 0)
# #2. To display all the commands in queue:
# oled.show()
# #3. Now to clear the oled display:
# oled.fill(0)
# oled.show()
# #4. You may also use the invert function to invert the display.
# oled.invert(1)
# #5.To display a single pixel.
# oled.pixel(10,20,1)
# oled.show()
# #6. To display a horizontal line
# oled.hline(30,40,10,1)
# oled.show()
# #7. To display a vertical line
# oled.vline(30,45,5,1)
# oled.show()
# #8. While hline and vline is quite useful, there is another function that is more flexible to use which is the line function.
# oled.line(0,50,10,50,1)
# oled.show()
# #9.We may also be able to print a rectangle.
# oled.rect(10,60,10,5,1)
# oled.show()
# #10. Or we may also print a filled rectangle:
# oled.fill_rect(10,70,10,5,1)
# oled.show()
2. Example # 2, display date and time using the ESP32 internal RTC module:
# More details can be found in TechToTinker.blogspot.com
# George Bantique | tech.to.tinker@gmail.com
from machine import Pin
from machine import I2C
from machine import RTC
from time import sleep_ms
from machine import Pin, I2C
from sh1106 import SH1106_I2C
i2c = I2C(scl=Pin(22), sda=Pin(21), freq=400000)
oled = SH1106_I2C(128, 64, i2c, None, addr=0x3C)
oled.sleep(False)
rtc = RTC()
rtc.datetime((2021, 2, 6, 6, 19, 44, 0, 0))
# rtc.datetime((YYYY, MM, DD, WD, HH, MM, SS, MS))
# WD 1 = Monday
# WD 7 = Sunday
isPoint = True
while True:
t = rtc.datetime()
oled.fill(0)
oled.text('** 1.3 OLED **', 4, 0)
oled.text('Date: {}-{:02d}-{:02d}' .format(t[0],t[1],t[2]), 0, 25)
if isPoint:
colon = ':'
else:
colon = ' '
oled.text('Time: {:02d}{}{:02d}' .format(t[4], colon, t[5]), 0, 40)
oled.show()
sleep_ms(500)
isPoint = not isPoint
3. Example # 3, for exploring the basics of custom font:
# More details can be found in TechToTinker.blogspot.com
# George Bantique | tech.to.tinker@gmail.com
from machine import Pin, I2C
from sh1106 import SH1106_I2C
import freesans20
from writer_minimal import Writer
i2c = I2C(scl=Pin(22), sda=Pin(21), freq=400000)
oled = SH1106_I2C(128,64,i2c,None,addr=0x3C)
# create a writer instance
font_writer = Writer(oled, freesans20)
# set writing position
font_writer.set_textpos(0, 0)
# write some text!
font_writer.printstring("hello")
oled.show()
4. Example # 4, applying custom font to display date and time using the ESP32 internal RTC module:
# More details can be found in TechToTinker.blogspot.com
# George Bantique | tech.to.tinker@gmail.com
from machine import Pin, I2C
from sh1106 import SH1106_I2C
import freesans20
from writer_minimal import Writer
from machine import RTC
from time import sleep_ms
i2c = I2C(scl=Pin(22), sda=Pin(21), freq=400000)
oled = SH1106_I2C(128, 64, i2c, None, addr=0x3C)
font_writer = Writer(oled, freesans20)
rtc = RTC()
rtc.datetime((2021, 2, 10, 3, 11, 30, 0, 0))
# rtc.datetime((YYYY, MM, DD, WD, HH, MM, SS, MS))
# WD 1 = Monday
# WD 7 = Sunday
isPoint = True
while True:
t = rtc.datetime()
oled.fill(0)
oled.text('** 1.3 OLED **', 10, 0)
# Display the date
font_writer.set_textpos(20, 15)
font_writer.printstring('{}-{:02d}-{:02d}' .format(t[0],t[1],t[2]))
if isPoint:
colon = ':'
else:
colon = ' '
# Display the time
font_writer.set_textpos(40, 40)
font_writer.printstring('{:02d}{}{:02d}' .format(t[4], colon, t[5]))
oled.show()
sleep_ms(500)
isPoint = not isPoint
5. sh1106.py OLED driver library:
# Modified and copy some initializations from ssd1306
# to prevent inverted display and the breathing like
# effect on the display. I don't know the reason but it works
# Original from: https://github.com/robert-hh/SH1106/blob/master/sh1106.py
#
# MicroPython SH1106 OLED driver, I2C and SPI interfaces
#
# The MIT License (MIT)
#
# Copyright (c) 2016 Radomir Dopieralski (@deshipu), 2017 Robert Hammelrath (@robert-hh)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Sample code sections
# ------------ SPI ------------------
# Pin Map SPI
# - 3v - xxxxxx - Vcc
# - G - xxxxxx - Gnd
# - D7 - GPIO 13 - Din / MOSI fixed
# - D5 - GPIO 14 - Clk / Sck fixed
# - D8 - GPIO 4 - CS (optional, if the only connected device)
# - D2 - GPIO 5 - D/C
# - D1 - GPIO 2 - Res
#
# for CS, D/C and Res other ports may be chosen.
#
# from machine import Pin, SPI
# import sh1106
# spi = SPI(1, baudrate=1000000)
# display = sh1106.SH1106_SPI(128, 64, spi, Pin(5), Pin(2), Pin(4))
# display.sleep(False)
# display.fill(0)
# display.text('Testing 1', 0, 0, 1)
# display.show()
#
# --------------- I2C ------------------
#
# Pin Map I2C
# - 3v - xxxxxx - Vcc
# - G - xxxxxx - Gnd
# - D2 - GPIO 5 - SCK / SCL
# - D1 - GPIO 4 - DIN / SDA
# - D0 - GPIO 16 - Res
# - G - xxxxxx CS
# - G - xxxxxx D/C
#
# Pin's for I2C can be set almost arbitrary
#
# from machine import Pin, I2C
# import sh1106
#
# i2c = I2C(scl=Pin(5), sda=Pin(4), freq=400000)
# display = sh1106.SH1106_I2C(128, 64, i2c, Pin(16), 0x3c)
# display.sleep(False)
# display.fill(0)
# display.text('Testing 1', 0, 0, 1)
# display.show()
from micropython import const
import utime as time
import framebuf
# Copy some definitions from ssd1306
# a few register definitions
_SET_CONTRAST = const(0x81)
_SET_SCAN_DIR = const(0xc0)
_SET_SEG_REMAP = const(0xa0)
_SET_DISP = const(0xae)
_LOW_COLUMN_ADDRESS = const(0x00)
_HIGH_COLUMN_ADDRESS = const(0x10)
_SET_PAGE_ADDRESS = const(0xB0)
SET_CONTRAST = const(0x81)
SET_ENTIRE_ON = const(0xa4)
SET_NORM_INV = const(0xa6)
SET_DISP = const(0xae)
SET_MEM_ADDR = const(0x20)
SET_COL_ADDR = const(0x21)
SET_PAGE_ADDR = const(0x22)
SET_DISP_START_LINE = const(0x40)
SET_SEG_REMAP = const(0xa0)
SET_MUX_RATIO = const(0xa8)
SET_COM_OUT_DIR = const(0xc0)
SET_DISP_OFFSET = const(0xd3)
SET_COM_PIN_CFG = const(0xda)
SET_DISP_CLK_DIV = const(0xd5)
SET_PRECHARGE = const(0xd9)
SET_VCOM_DESEL = const(0xdb)
SET_CHARGE_PUMP = const(0x8d)
class SH1106:
def __init__(self, width, height, external_vcc):
self.width = width
self.height = height
self.external_vcc = external_vcc
self.pages = self.height // 8
self.buffer = bytearray(self.pages * self.width)
fb = framebuf.FrameBuffer(self.buffer, self.width, self.height, framebuf.MVLSB)
self.framebuf = fb
# set shortcuts for the methods of framebuf
self.fill = fb.fill
self.fill_rect = fb.fill_rect
self.hline = fb.hline
self.vline = fb.vline
self.line = fb.line
self.rect = fb.rect
self.pixel = fb.pixel
self.scroll = fb.scroll
self.text = fb.text
self.blit = fb.blit
self.poweron()
self.init_display()
# Comment out and copy the init_display from ssd1306
# def init_display(self):
# self.reset()
# self.fill(0)
# self.poweron()
# self.show()
def init_display(self):
for cmd in (
SET_DISP | 0x00, # off
# address setting
SET_MEM_ADDR, 0x00, # horizontal
# resolution and layout
SET_DISP_START_LINE | 0x00,
SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0
SET_MUX_RATIO, self.height - 1,
SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0
SET_DISP_OFFSET, 0x00,
SET_COM_PIN_CFG, 0x02 if self.height == 32 else 0x12,
# timing and driving scheme
SET_DISP_CLK_DIV, 0x80,
SET_PRECHARGE, 0x22 if self.external_vcc else 0xf1,
SET_VCOM_DESEL, 0x30, # 0.83*Vcc
# display
SET_CONTRAST, 0xff, # maximum
SET_ENTIRE_ON, # output follows RAM contents
SET_NORM_INV, # not inverted
# charge pump
SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14,
SET_DISP | 0x01): # on
self.write_cmd(cmd)
self.fill(0)
self.show()
def poweroff(self):
self.write_cmd(_SET_DISP | 0x00)
def poweron(self):
self.write_cmd(_SET_DISP | 0x01)
def rotate(self, flag, update = True):
if flag:
self.write_cmd(_SET_SEG_REMAP | 0x01) # mirror display vertically
self.write_cmd(_SET_SCAN_DIR | 0x08) # mirror display horizontically
else:
self.write_cmd(_SET_SEG_REMAP | 0x00) #
self.write_cmd(_SET_SCAN_DIR | 0x00) #
if update:
self.show()
def sleep(self, value):
self.write_cmd(_SET_DISP | (not value))
def contrast(self, contrast):
self.write_cmd(_SET_CONTRAST)
self.write_cmd(contrast)
def invert(self, invert):
self.write_cmd(_SET_NORM_INV | (invert & 1))
def show(self):
for page in range(self.height // 8):
self.write_cmd(_SET_PAGE_ADDRESS | page)
self.write_cmd(_LOW_COLUMN_ADDRESS | 2)
self.write_cmd(_HIGH_COLUMN_ADDRESS | 0)
self.write_data(self.buffer[
self.width * page:self.width * page + self.width
])
def reset(self, res):
if res is not None:
res(1)
time.sleep_ms(1)
res(0)
time.sleep_ms(20)
res(1)
time.sleep_ms(20)
class SH1106_I2C(SH1106):
def __init__(self, width, height, i2c, res=None, addr=0x3c, external_vcc=False):
self.i2c = i2c
self.addr = addr
self.res = res
self.temp = bytearray(2)
if hasattr(self.i2c, "start"):
self.write_data = self.sw_write_data
else:
self.write_data = self.hw_write_data
if res is not None:
res.init(res.OUT, value=1)
super().__init__(width, height, external_vcc)
def write_cmd(self, cmd):
self.temp[0] = 0x80 # Co=1, D/C#=0
self.temp[1] = cmd
self.i2c.writeto(self.addr, self.temp)
def hw_write_data(self, buf):
self.i2c.writeto(self.addr, b'x40'+buf)
def sw_write_data(self, buf):
self.temp[0] = self.addr << 1
self.temp[1] = 0x40 # Co=0, D/C#=1
self.i2c.start()
self.i2c.write(self.temp)
self.i2c.write(buf)
self.i2c.stop()
def reset(self):
super().reset(self.res)
class SH1106_SPI(SH1106):
def __init__(self, width, height, spi, dc, res=None, cs=None, external_vcc=False):
self.rate = 10 * 1000 * 1000
dc.init(dc.OUT, value=0)
if res is not None:
res.init(res.OUT, value=0)
if cs is not None:
cs.init(cs.OUT, value=1)
self.spi = spi
self.dc = dc
self.res = res
self.cs = cs
super().__init__(width, height, external_vcc)
def write_cmd(self, cmd):
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
if self.cs is not None:
self.cs(1)
self.dc(0)
self.cs(0)
self.spi.write(bytearray([cmd]))
self.cs(1)
else:
self.dc(0)
self.spi.write(bytearray([cmd]))
def write_data(self, buf):
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
if self.cs is not None:
self.cs(1)
self.dc(1)
self.cs(0)
self.spi.write(buf)
self.cs(1)
else:
self.dc(1)
self.spi.write(buf)
def reset(self):
super().reset(self.res)
6. Custom font writer utility, writer minimal.py:
# writer_minimal.py Implements the Writer class.
# Minimal version for SSD1306
# V0.22 Peter Hinch 3rd Jan 2018: Supports new SSD1306 driver.
# The MIT License (MIT)
#
# Copyright (c) 2016 Peter Hinch
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# A Writer supports rendering text to a Display instance in a given font.
# Multiple Writer instances may be created, each rendering a font to the
# same Display object.
import framebuf
class Writer():
text_row = 0 # attributes common to all Writer instances
text_col = 0
row_clip = False # Clip or scroll when screen full
col_clip = False # Clip or new line when row is full
@classmethod
def set_textpos(cls, row, col):
cls.text_row = row
cls.text_col = col
@classmethod
def set_clip(cls, row_clip, col_clip):
cls.row_clip = row_clip
cls.col_clip = col_clip
def __init__(self, device, font, verbose=True):
self.device = device
self.font = font
# Allow to work with any font mapping
if font.hmap():
self.map = framebuf.MONO_HMSB if font.reverse() else framebuf.MONO_HLSB
else:
raise ValueError('Font must be horizontally mapped.')
if verbose:
print('Orientation: {} Reversal: {}'.format('horiz' if font.hmap() else 'vert', font.reverse()))
self.screenwidth = device.width # In pixels
self.screenheight = device.height
def _newline(self):
height = self.font.height()
Writer.text_row += height
Writer.text_col = 0
margin = self.screenheight - (Writer.text_row + height)
if margin < 0:
if not Writer.row_clip:
self.device.scroll(0, margin)
Writer.text_row += margin
def printstring(self, string):
for char in string:
self._printchar(char)
# Method using blitting. Efficient rendering for monochrome displays.
# Tested on SSD1306. Invert is for black-on-white rendering.
def _printchar(self, char, invert=False):
if char == 'n':
self._newline()
return
glyph, char_height, char_width = self.font.get_ch(char)
if Writer.text_row + char_height > self.screenheight:
if Writer.row_clip:
return
self._newline()
if Writer.text_col + char_width > self.screenwidth:
if Writer.col_clip:
return
else:
self._newline()
buf = bytearray(glyph)
if invert:
for i, v in enumerate(buf):
buf[i] = 0xFF & ~ v
fbc = framebuf.FrameBuffer(buf, char_width, char_height, self.map)
self.device.blit(fbc, Writer.text_col, Writer.text_row)
Writer.text_col += char_width
7. Custom font, freesans20.py:
# Code generated by font-to-py.py.
# Font: FreeSans.ttf
version = '0.25'
def height():
return 20
def max_width():
return 20
def hmap():
return True
def reverse():
return False
def monospaced():
return False
def min_ch():
return 32
def max_ch():
return 126
_font =
b'x0bx00x00x00x3cx00x7ex00xc7x00xc3x00x03x00x03x00'
b'x06x00x0cx00x08x00x18x00x18x00x00x00x00x00x18x00'
b'x18x00x00x00x00x00x00x00x00x00x05x00x00x00x00x00'
b'x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00'
b'x07x00x00xc0xc0xc0xc0xc0xc0xc0xc0xc0xc0xc0x00x00'
b'xc0xc0x00x00x00x00x07x00x00x00xd8xd8xd8xd8x90x00'
b'x00x00x00x00x00x00x00x00x00x00x00x00x0bx00x00x00'
b'x00x00x0cxc0x08x80x08x80x7fxe0x7fxe0x19x80x11x00'
b'x11x00xffxc0xffxc0x33x00x33x00x22x00x22x00x00x00'
b'x00x00x00x00x00x00x0bx00x08x00x3ex00x7fx80xe9xc0'
b'xc8xc0xc8xc0xc8x00xe8x00x7cx00x1fx80x09xc0x08xc0'
b'xc8xc0xe9xc0x7fx80x3ex00x08x00x00x00x00x00x00x00'
b'x12x00x00x00x00x00x00x00x38x10x00x7cx10x00xc6x20'
b'x00xc6x20x00xc6x40x00x7cxc0x00x38x80x00x01x1ex00'
b'x01x3fx00x02x73x80x02x61x80x04x73x80x04x3fx00x08'
b'x1ex00x00x00x00x00x00x00x00x00x00x00x00x00x0dx00'
b'x00x00x00x00x0ex00x1fx00x31x80x31x80x31x80x1fx00'
b'x1cx00x76x60xe3x60xc1xc0xc0xc0xe1xc0x7fx60x3ex30'
b'x00x00x00x00x00x00x00x00x04x00x00x00xc0xc0xc0xc0'
b'x80x00x00x00x00x00x00x00x00x00x00x00x00x00x07x00'
b'x00x10x10x20x20x60x40xc0xc0xc0xc0xc0xc0xc0x40x60'
b'x20x30x10x18x07x00x00x40x40x20x20x30x10x18x18x18'
b'x18x18x18x18x10x30x20x60x40xc0x08x00x00x20x20xf8'
b'x20x50x50x00x00x00x00x00x00x00x00x00x00x00x00x00'
b'x0cx00x00x00x00x00x00x00x00x00x00x00x00x00x00x00'
b'x18x00x18x00x18x00xffx00xffx00x18x00x18x00x18x00'
b'x18x00x00x00x00x00x00x00x00x00x06x00x00x00x00x00'
b'x00x00x00x00x00x00x00x00x00x00xc0xc0x40x40x80x00'
b'x07x00x00x00x00x00x00x00x00x00x00xf8xf8x00x00x00'
b'x00x00x00x00x00x00x05x00x00x00x00x00x00x00x00x00'
b'x00x00x00x00x00x00xc0xc0x00x00x00x00x06x00x00x04'
b'x0cx08x08x18x10x10x30x20x20x60x40x40xc0x80x00x00'
b'x00x00x0bx00x00x00x00x00x3ex00x7fx00x63x00xe3x80'
b'xc1x80xc1x80xc1x80xc1x80xc1x80xc1x80xe3x80x63x00'
b'x7fx00x3ex00x00x00x00x00x00x00x00x00x0bx00x00x00'
b'x00x00x10x00x30x00xf0x00xf0x00x30x00x30x00x30x00'
b'x30x00x30x00x30x00x30x00x30x00x30x00x30x00x00x00'
b'x00x00x00x00x00x00x0bx00x00x00x00x00x3ex00x7fx00'
b'xe3x80xc1x80x01x80x01x80x03x00x0ex00x1cx00x30x00'
b'x60x00xc0x00xffx80xffx80x00x00x00x00x00x00x00x00'
b'x0bx00x00x00x00x00x3ex00x7fx00xe3x80xc1x80x01x80'
b'x0fx00x0fx00x03x80x01x80x01x80xc1x80xe3x80x7fx00'
b'x3ex00x00x00x00x00x00x00x00x00x0bx00x00x00x00x00'
b'x06x00x06x00x0ex00x1ex00x16x00x26x00x46x00x46x00'
b'x86x00xffx00xffx00x06x00x06x00x06x00x00x00x00x00'
b'x00x00x00x00x0bx00x00x00x00x00x7fx00x7fx00x60x00'
b'x60x00xdex00xffx00xe3x80x01x80x01x80x01x80x01x80'
b'xc3x00x7fx00x3ex00x00x00x00x00x00x00x00x00x0bx00'
b'x00x00x00x00x1ex00x3fx00x63x00x61x80xc0x00xdex00'
b'xffx00xe3x80xc1x80xc1x80xc1x80x63x80x7fx00x3ex00'
b'x00x00x00x00x00x00x00x00x0bx00x00x00x00x00xffx80'
b'xffx80x01x00x03x00x02x00x06x00x04x00x0cx00x08x00'
b'x18x00x18x00x10x00x30x00x30x00x00x00x00x00x00x00'
b'x00x00x0bx00x00x00x00x00x1cx00x3ex00x63x00x63x00'
b'x63x00x3ex00x3ex00x63x00xc1x80xc1x80xc1x80x63x00'
b'x7fx00x1cx00x00x00x00x00x00x00x00x00x0bx00x00x00'
b'x00x00x3ex00x7fx00xe3x00xc1x80xc1x80xc1x80xe3x80'
b'x7fx80x3dx80x01x80x03x00xe3x00x7ex00x3cx00x00x00'
b'x00x00x00x00x00x00x05x00x00x00x00x00x00xc0xc0x00'
b'x00x00x00x00x00x00xc0xc0x00x00x00x00x05x00x00x00'
b'x00x00x00x00xc0xc0x00x00x00x00x00x00xc0xc0x40x40'
b'x80x00x0cx00x00x00x00x00x00x00x00x00x00x00x00x00'
b'x00x40x01xc0x07x00x3cx00xe0x00xe0x00x78x00x0fx00'
b'x03xc0x00x40x00x00x00x00x00x00x00x00x0cx00x00x00'
b'x00x00x00x00x00x00x00x00x00x00x00x00x00x00xffxc0'
b'xffxc0x00x00x00x00xffxc0xffxc0x00x00x00x00x00x00'
b'x00x00x00x00x00x00x0cx00x00x00x00x00x00x00x00x00'
b'x00x00x00x00x00x00xe0x00x78x00x0ex00x03xc0x01xc0'
b'x07x00x3cx00xf0x00x80x00x00x00x00x00x00x00x00x00'
b'x0bx00x00x00x3cx00x7ex00xc7x00xc3x00x03x00x03x00'
b'x06x00x0cx00x08x00x18x00x18x00x00x00x00x00x18x00'
b'x18x00x00x00x00x00x00x00x00x00x14x00x00x00x00x03'
b'xf0x00x0fxfcx00x1ex0fx00x38x03x80x71xe1x80x63xe9'
b'xc0x67x18xc0xcex18xc0xccx18xc0xccx10xc0xccx31x80'
b'xcex73x80x67xffx00x63x9ex00x30x00x00x3cx00x00x0f'
b'xf8x00x03xf0x00x00x00x00x0dx00x00x00x07x00x07x00'
b'x07x80x0dx80x0dx80x08xc0x18xc0x18xc0x10x60x3fxe0'
b'x3fxe0x30x30x60x30x60x38xc0x18x00x00x00x00x00x00'
b'x00x00x0dx00x00x00xffx00xffx80xc1xc0xc0xc0xc0xc0'
b'xc1xc0xffx00xffx80xc0xc0xc0x60xc0x60xc0x60xc0xe0'
b'xffxc0xffx80x00x00x00x00x00x00x00x00x0ex00x00x00'
b'x0fx80x3fxe0x70x60x60x30xe0x00xc0x00xc0x00xc0x00'
b'xc0x00xc0x00xe0x30x60x70x70x60x3fxe0x0fx80x00x00'
b'x00x00x00x00x00x00x0ex00x00x00xffx00xffx80xc1xc0'
b'xc0xc0xc0x60xc0x60xc0x60xc0x60xc0x60xc0x60xc0x60'
b'xc0xc0xc1xc0xffx80xffx00x00x00x00x00x00x00x00x00'
b'x0dx00x00x00xffxc0xffxc0xc0x00xc0x00xc0x00xc0x00'
b'xffx80xffx80xc0x00xc0x00xc0x00xc0x00xc0x00xffxc0'
b'xffxc0x00x00x00x00x00x00x00x00x0cx00x00x00xffx80'
b'xffx80xc0x00xc0x00xc0x00xc0x00xffx00xffx00xc0x00'
b'xc0x00xc0x00xc0x00xc0x00xc0x00xc0x00x00x00x00x00'
b'x00x00x00x00x0fx00x00x00x0fxc0x3fxf0x38x30x60x18'
b'x60x00xc0x00xc0x00xc1xf8xc1xf8xc0x18xe0x18x60x38'
b'x78x78x3fxd8x0fx88x00x00x00x00x00x00x00x00x0ex00'
b'x00x00xc0x60xc0x60xc0x60xc0x60xc0x60xc0x60xffxe0'
b'xffxe0xc0x60xc0x60xc0x60xc0x60xc0x60xc0x60xc0x60'
b'x00x00x00x00x00x00x00x00x06x00x00xc0xc0xc0xc0xc0'
b'xc0xc0xc0xc0xc0xc0xc0xc0xc0xc0x00x00x00x00x0bx00'
b'x00x00x03x00x03x00x03x00x03x00x03x00x03x00x03x00'
b'x03x00x03x00x03x00xc3x00xc3x00xe7x00x7ex00x3cx00'
b'x00x00x00x00x00x00x00x00x0dx00x00x00xc0x60xc0xc0'
b'xc1x80xc3x00xc6x00xccx00xdcx00xf6x00xe6x00xc3x00'
b'xc1x80xc1x80xc0xc0xc0x60xc0x60x00x00x00x00x00x00'
b'x00x00x0bx00x00x00xc0x00xc0x00xc0x00xc0x00xc0x00'
b'xc0x00xc0x00xc0x00xc0x00xc0x00xc0x00xc0x00xc0x00'
b'xffx80xffx80x00x00x00x00x00x00x00x00x11x00x00x00'
b'x00xe0x1cx00xe0x1cx00xf0x3cx00xf0x3cx00xd0x2cx00'
b'xd8x6cx00xd8x6cx00xc8x4cx00xccxccx00xccxccx00xc4'
b'x8cx00xc6x8cx00xc7x8cx00xc3x0cx00xc3x0cx00x00x00'
b'x00x00x00x00x00x00x00x00x00x00x0fx00x00x00xe0x60'
b'xe0x60xf0x60xf0x60xd8x60xd8x60xccx60xc4x60xc6x60'
b'xc2x60xc3x60xc1xe0xc1xe0xc0xe0xc0xe0x00x00x00x00'
b'x00x00x00x00x10x00x00x00x0fxc0x1fxe0x38x70x60x18'
b'x60x1cxc0x0cxc0x0cxc0x0cxc0x0cxc0x0cx60x1cx60x18'
b'x38x70x1fxe0x0fxc0x00x00x00x00x00x00x00x00x0dx00'
b'x00x00xffx00xffx80xc1xc0xc0xc0xc0xc0xc0xc0xc1xc0'
b'xffx80xffx00xc0x00xc0x00xc0x00xc0x00xc0x00xc0x00'
b'x00x00x00x00x00x00x00x00x10x00x00x00x0fxc0x1fxe0'
b'x38x70x60x18x60x1cxc0x0cxc0x0cxc0x0cxc0x0cxc0x0c'
b'x60x18x60xd8x38x70x1fxf8x0fx98x00x08x00x00x00x00'
b'x00x00x0ex00x00x00xffx80xffxc0xc0xe0xc0x60xc0x60'
b'xc0x60xc0xc0xffx80xffxc0xc0xe0xc0x60xc0x60xc0x60'
b'xc0x60xc0x70x00x00x00x00x00x00x00x00x0dx00x00x00'
b'x1fx80x7fxe0xe0x70xc0x30xc0x00xe0x00x78x00x3fx80'
b'x03xe0x00x70xc0x30xc0x30x70x60x7fxe0x1fx80x00x00'
b'x00x00x00x00x00x00x0dx00x00x00xffxc0xffxc0x0cx00'
b'x0cx00x0cx00x0cx00x0cx00x0cx00x0cx00x0cx00x0cx00'
b'x0cx00x0cx00x0cx00x0cx00x00x00x00x00x00x00x00x00'
b'x0ex00x00x00xc0x60xc0x60xc0x60xc0x60xc0x60xc0x60'
b'xc0x60xc0x60xc0x60xc0x60xc0x60xc0x60x60xc0x7fxc0'
b'x1fx00x00x00x00x00x00x00x00x00x0dx00x00x00xc0x30'
b'x60x30x60x30x20x20x30x60x30x60x10x40x18xc0x18xc0'
b'x08x80x0dx80x0dx80x07x00x07x00x07x00x00x00x00x00'
b'x00x00x00x00x13x00x00x00x00xc0xc0xc0x60xe0xc0x60'
b'xe0xc0x61xe0xc0x61xb1x80x31xb1x80x31xb1x80x33x11'
b'x80x33x19x00x13x1bx00x1fx1bx00x1ex0bx00x1ex0ex00'
b'x0ex0ex00x0cx06x00x00x00x00x00x00x00x00x00x00x00'
b'x00x00x0dx00x00x00x60x30x30x70x30x60x18xc0x0cxc0'
b'x0dx80x07x00x07x00x07x00x0dx80x18xc0x18xe0x30x60'
b'x70x30x60x38x00x00x00x00x00x00x00x00x0ex00x00x00'
b'x60x18x70x38x30x30x18x60x18x60x0cxc0x0fxc0x07x80'
b'x03x00x03x00x03x00x03x00x03x00x03x00x03x00x00x00'
b'x00x00x00x00x00x00x0cx00x00x00xffxe0xffxe0x00xc0'
b'x01x80x03x80x03x00x06x00x0cx00x1cx00x38x00x30x00'
b'x60x00xc0x00xffxe0xffxe0x00x00x00x00x00x00x00x00'
b'x06x00x00xe0xe0xc0xc0xc0xc0xc0xc0xc0xc0xc0xc0xc0'
b'xc0xc0xc0xc0xe0xe0x06x00x00x80xc0x40x40x60x20x20'
b'x30x10x10x18x08x08x0cx04x00x00x00x00x06x00x00xe0'
b'xe0x60x60x60x60x60x60x60x60x60x60x60x60x60x60x60'
b'xe0xe0x09x00x00x00x00x00x18x00x38x00x28x00x2cx00'
b'x64x00x46x00xc2x00x82x00x00x00x00x00x00x00x00x00'
b'x00x00x00x00x00x00x00x00x00x00x00x00x0cx00x00x00'
b'x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00'
b'x00x00x00x00x00x00x00x00x00x00x00x00x00x00xffxf0'
b'x00x00x00x00x00x00x05x00x00xc0x60x30x00x00x00x00'
b'x00x00x00x00x00x00x00x00x00x00x00x00x0bx00x00x00'
b'x00x00x00x00x00x00x00x00x3ex00xffx80xc1x80x01x80'
b'x01x80x3fx80xf1x80xc1x80xc3x80xffxc0x78xc0x00x00'
b'x00x00x00x00x00x00x0bx00x00x00xc0x00xc0x00xc0x00'
b'xc0x00xdfx00xffx80xe1x80xc0xc0xc0xc0xc0xc0xc0xc0'
b'xc0xc0xe1x80xffx80xdex00x00x00x00x00x00x00x00x00'
b'x0ax00x00x00x00x00x00x00x00x00x00x00x1ex00x7fx00'
b'x61x80xc0x00xc0x00xc0x00xc0x00xc1x80x63x80x7fx00'
b'x3ex00x00x00x00x00x00x00x00x00x0bx00x00x00x01x80'
b'x01x80x01x80x01x80x3dx80x7fx80x63x80xc1x80xc1x80'
b'xc1x80xc1x80xc1x80x63x80x7fx80x3dx80x00x00x00x00'
b'x00x00x00x00x0bx00x00x00x00x00x00x00x00x00x00x00'
b'x3ex00x7fx00x63x00xc1x80xffx80xffx80xc0x00xc0x00'
b'x63x80x7fx00x3ex00x00x00x00x00x00x00x00x00x06x00'
b'x00x30x70x60x60xf0xf0x60x60x60x60x60x60x60x60x60'
b'x00x00x00x00x0bx00x00x00x00x00x00x00x00x00x00x00'
b'x3dx80x7fx80x63x80xc1x80xc1x80xc1x80xc1x80xc1x80'
b'x63x80x7fx80x3dx80x01x80xc3x80x7fx00x3ex00x0bx00'
b'x00x00xc0x00xc0x00xc0x00xc0x00xdfx00xdfx80xe3x80'
b'xc1x80xc1x80xc1x80xc1x80xc1x80xc1x80xc1x80xc1x80'
b'x00x00x00x00x00x00x00x00x04x00x00xc0xc0x00x00xc0'
b'xc0xc0xc0xc0xc0xc0xc0xc0xc0xc0x00x00x00x00x05x00'
b'x00x30x30x00x00x30x30x30x30x30x30x30x30x30x30x30'
b'x30x30xf0xe0x0ax00x00x00xc0x00xc0x00xc0x00xc0x00'
b'xc3x00xc6x00xccx00xd8x00xf8x00xecx00xcex00xc6x00'
b'xc3x00xc3x00xc1x80x00x00x00x00x00x00x00x00x04x00'
b'x00xc0xc0xc0xc0xc0xc0xc0xc0xc0xc0xc0xc0xc0xc0xc0'
b'x00x00x00x00x10x00x00x00x00x00x00x00x00x00x00x00'
b'xdex78xfexfcxe3x8cxc3x0cxc3x0cxc3x0cxc3x0cxc3x0c'
b'xc3x0cxc3x0cxc3x0cx00x00x00x00x00x00x00x00x0bx00'
b'x00x00x00x00x00x00x00x00x00x00xcfx00xdfx80xe3x80'
b'xc1x80xc1x80xc1x80xc1x80xc1x80xc1x80xc1x80xc1x80'
b'x00x00x00x00x00x00x00x00x0bx00x00x00x00x00x00x00'
b'x00x00x00x00x3ex00x7fx00x63x00xc1x80xc1x80xc1x80'
b'xc1x80xc1x80x63x00x7fx00x3ex00x00x00x00x00x00x00'
b'x00x00x0bx00x00x00x00x00x00x00x00x00x00x00xdex00'
b'xffx80xe1x80xc0xc0xc0xc0xc0xc0xc0xc0xc0xc0xe1x80'
b'xffx80xdex00xc0x00xc0x00xc0x00x00x00x0bx00x00x00'
b'x00x00x00x00x00x00x00x00x3dx80x7fx80x63x80xc1x80'
b'xc1x80xc1x80xc1x80xc1x80x63x80x7fx80x3dx80x01x80'
b'x01x80x01x80x00x00x07x00x00x00x00x00x00xd8xf8xe0'
b'xc0xc0xc0xc0xc0xc0xc0xc0x00x00x00x00x0ax00x00x00'
b'x00x00x00x00x00x00x00x00x3cx00x7fx00xc3x00xc0x00'
b'xf0x00x7ex00x0fx00x03x00xc3x00xfex00x7cx00x00x00'
b'x00x00x00x00x00x00x06x00x00x00x00x60x60xf0xf0x60'
b'x60x60x60x60x60x60x70x70x00x00x00x00x0bx00x00x00'
b'x00x00x00x00x00x00x00x00xc1x80xc1x80xc1x80xc1x80'
b'xc1x80xc1x80xc1x80xc1x80xe3x80xfdx80x79x80x00x00'
b'x00x00x00x00x00x00x0ax00x00x00x00x00x00x00x00x00'
b'x00x00xc0xc0x61x80x61x80x61x00x23x00x33x00x32x00'
b'x16x00x1ex00x1cx00x0cx00x00x00x00x00x00x00x00x00'
b'x0ex00x00x00x00x00x00x00x00x00x00x00xc3x0cxc3x8c'
b'x63x8cx67x88x66x98x24xd8x34xd0x3cxd0x3cx70x18x70'
b'x18x60x00x00x00x00x00x00x00x00x0ax00x00x00x00x00'
b'x00x00x00x00x00x00x61x80x63x00x33x00x1ex00x1cx00'
b'x0cx00x1cx00x16x00x33x00x63x00x41x80x00x00x00x00'
b'x00x00x00x00x0ax00x00x00x00x00x00x00x00x00x00x00'
b'xc0x80x41x80x61x80x61x00x23x00x33x00x32x00x16x00'
b'x1cx00x1cx00x0cx00x08x00x18x00x78x00x70x00x0ax00'
b'x00x00x00x00x00x00x00x00x00x00xffx00xffx00x06x00'
b'x06x00x0cx00x18x00x30x00x60x00xc0x00xffx00xffx00'
b'x00x00x00x00x00x00x00x00x07x00x00x18x38x30x30x30'
b'x30x30x30x70xc0x70x30x30x30x30x30x30x38x18x05x00'
b'x00xc0xc0xc0xc0xc0xc0xc0xc0xc0xc0xc0xc0xc0xc0xc0'
b'xc0xc0xc0xc0x07x00x00xc0xe0x60x60x60x60x60x60x70'
b'x18x70x60x60x60x60x60x60xe0xc0x0ax00x00x00x00x00'
b'x00x00x00x00x00x00x00x00x00x00x60x00xf1x00x9fx00'
b'x06x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00'
b'x00x00x00x00'
_index =
b'x00x00x2ax00x2ax00x40x00x40x00x56x00x56x00x6cx00'
b'x6cx00x96x00x96x00xc0x00xc0x00xfex00xfex00x28x01'
b'x28x01x3ex01x3ex01x54x01x54x01x6ax01x6ax01x80x01'
b'x80x01xaax01xaax01xc0x01xc0x01xd6x01xd6x01xecx01'
b'xecx01x02x02x02x02x2cx02x2cx02x56x02x56x02x80x02'
b'x80x02xaax02xaax02xd4x02xd4x02xfex02xfex02x28x03'
b'x28x03x52x03x52x03x7cx03x7cx03xa6x03xa6x03xbcx03'
b'xbcx03xd2x03xd2x03xfcx03xfcx03x26x04x26x04x50x04'
b'x50x04x7ax04x7ax04xb8x04xb8x04xe2x04xe2x04x0cx05'
b'x0cx05x36x05x36x05x60x05x60x05x8ax05x8ax05xb4x05'
b'xb4x05xdex05xdex05x08x06x08x06x1ex06x1ex06x48x06'
b'x48x06x72x06x72x06x9cx06x9cx06xdax06xdax06x04x07'
b'x04x07x2ex07x2ex07x58x07x58x07x82x07x82x07xacx07'
b'xacx07xd6x07xd6x07x00x08x00x08x2ax08x2ax08x54x08'
b'x54x08x92x08x92x08xbcx08xbcx08xe6x08xe6x08x10x09'
b'x10x09x26x09x26x09x3cx09x3cx09x52x09x52x09x7cx09'
b'x7cx09xa6x09xa6x09xbcx09xbcx09xe6x09xe6x09x10x0a'
b'x10x0ax3ax0ax3ax0ax64x0ax64x0ax8ex0ax8ex0axa4x0a'
b'xa4x0axcex0axcex0axf8x0axf8x0ax0ex0bx0ex0bx24x0b'
b'x24x0bx4ex0bx4ex0bx64x0bx64x0bx8ex0bx8ex0bxb8x0b'
b'xb8x0bxe2x0bxe2x0bx0cx0cx0cx0cx36x0cx36x0cx4cx0c'
b'x4cx0cx76x0cx76x0cx8cx0cx8cx0cxb6x0cxb6x0cxe0x0c'
b'xe0x0cx0ax0dx0ax0dx34x0dx34x0dx5ex0dx5ex0dx88x0d'
b'x88x0dx9ex0dx9ex0dxb4x0dxb4x0dxcax0dxcax0dxf4x0d'
_mvfont = memoryview(_font)
def get_ch(ch):
ordch = ord(ch)
ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 32
idx_offs = 4 * (ordch - 32)
offset = int.from_bytes(_index[idx_offs : idx_offs + 2], 'little')
next_offs = int.from_bytes(_index[idx_offs + 2 : idx_offs + 4], 'little')
width = int.from_bytes(_font[offset:offset + 2], 'little')
return _mvfont[offset + 2:next_offs], 20, width
REFERENCES AND CREDITS:
1. SH106 by Robert Hammelrath and Radomir Dopieralski:
2. Custom font library by Peter Hinch:
3. Purchase a copy of the Gorillacell ESP32 kit: