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:

  1. ESP32 development board.
  2. Gorillacell ESP32 shield.
  3. 4-pin female-female Dupont jumper wires.
  4. 1.3 OLED display.
 

PINOUT:

  1. GND – for the ground pin.
  2. VCC – for the supply voltage.
  3. SDA – for the i2c serial data pin.
  4. SCL – for the i2c serial clock pin.
 

HARDWARE INSTRUCTION:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. Next, connect the ESP32 to the computer by attaching a micro USB cable. The demo circuit should be ready by now.
 

SOFTWARE INSTRUCTION:

  1. 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.pyYou may also copy it below under the SOURCE CODE SECTION for your convenience.
  2. 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.
  3. Click MicroPython device and save it as sh1106.py.
  4. 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.
  5. 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.
  6. Please feel free to try each example given or you may play along with the video tutorial. 
  7. 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:

Leave a Reply

Your email address will not be published. Required fields are marked *