Skip to content

Instantly share code, notes, and snippets.

@smittytone
Last active July 10, 2019 16:46
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save smittytone/20ed962d9f06072bd1c0 to your computer and use it in GitHub Desktop.
Save smittytone/20ed962d9f06072bd1c0 to your computer and use it in GitHub Desktop.
Electric Imp Squirrel class for SSD1306 with alternative, proportionally spaced character set, and 128 x 32 and 128 x 64 OLED support
class SSD1306_OLED {
// Squirrel Class for Solomon SSD1306 OLED controller chip
// [http://www.adafruit.com/datasheets/SSD1306.pdf]
// As used on the Adafruit SSD1306 I2C breakout board
// [http://www.adafruit.com/products/931]
// Bus: I2C
// Code by Tony Smith (@smittytone) June 2014
// Version 1.0.1
static HIGH = 1;
static LOW = 0;
static SSD1306_SETCONTRAST = "\x81";
static SSD1306_DISPLAYALLON_RESUME = "\xA4";
static SSD1306_DISPLAYALLON = "\xA5";
static SSD1306_NORMALDISPLAY = "\xA6";
static SSD1306_INVERTDISPLAY = "\xA7";
static SSD1306_DISPLAYOFF = "\xAE";
static SSD1306_DISPLAYON = "\xAF";
static SSD1306_SETDISPLAYOFFSET = "\xD3";
static SSD1306_SETCOMPINS = "\xDA";
static SSD1306_SETVCOMDETECT = "\xDB";
static SSD1306_SETDISPLAYCLOCKDIV = "\xD5";
static SSD1306_SETPRECHARGE = "\xD9";
static SSD1306_SETMULTIPLEX = "\xA8";
static SSD1306_SETLOWCOLUMN = "\x00";
static SSD1306_SETHIGHCOLUMN = "\x10";
static SSD1306_SETSTARTLINE = "\x40";
static SSD1306_MEMORYMODE = "\x20";
static SSD1306_COLUMNADDR = "\x21";
static SSD1306_PAGEADDR = "\x22";
static SSD1306_COMSCANINC = "\xC0";
static SSD1306_COMSCANDEC = "\xC8";
static SSD1306_SEGREMAP = "\xA1";
static SSD1306_CHARGEPUMP = "\x8D";
static SSD1306_EXTERNALVCC = "\x01";
static SSD1306_SWITCHCAPVCC = "\x02";
// Scrolling statics
static SSD1306_ACTIVATE_SCROLL = 0x2F;
static SSD1306_DEACTIVATE_SCROLL = 0x2E;
static SSD1306_SET_VERTICAL_SCROLL_AREA = 0xA3;
static SSD1306_RIGHT_HORIZONTAL_SCROLL = 0x26;
static SSD1306_LEFT_HORIZONTAL_SCROLL = 0x27;
static SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL = 0x29;
static SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL = 0x2A;
// Constants for the alphanumeric character set
// Each character is an proportionally spaced bitmap rendered
// as 8-bit values
static alpha_count = 96;
static charset = [
[0x00, 0x00], // space - Ascii 32
[0xfa], // !
[0xe0, 0xc0, 0x00, 0xe0, 0xc0], // "
[0x24, 0x7e, 0x24, 0x7e, 0x24], // #
[0x24, 0xd4, 0x56, 0x48], // $
[0xc6, 0xc8, 0x10, 0x26, 0xc6], // %
[0x6c, 0x92, 0x6a, 0x04, 0x0a], // &
[0xc0], // '
[0x7c, 0x82], // (
[0x82, 0x7c], // )
[0x10, 0x7c, 0x38, 0x7c, 0x10], // *
[0x10, 0x10, 0x7c, 0x10, 0x10], // +
[0x06, 0x07], // ,
[0x10, 0x10, 0x10, 0x10, 0x10], // -
[0x06, 0x06], // .
[0x04, 0x08, 0x10, 0x20, 0x40], // /
[0x7c, 0x8a, 0x92, 0xa2, 0x7c], // 0 - Ascii 48
[0x42, 0xfe, 0x02], // 1
[0x46, 0x8a, 0x92, 0x92, 0x62], // 2
[0x44, 0x92, 0x92, 0x92, 0x6c], // 3
[0x18, 0x28, 0x48, 0xfe, 0x08], // 4
[0xf4, 0x92, 0x92, 0x92, 0x8c], // 5
[0x3c, 0x52, 0x92, 0x92, 0x8c], // 6
[0x80, 0x8e, 0x90, 0xa0, 0xc0], // 7
[0x6c, 0x92, 0x92, 0x92, 0x6c], // 8
[0x60, 0x92, 0x92, 0x94, 0x78], // 9
[0x36, 0x36], // : - Ascii 58
[0x36, 0x37], // ;
[0x10, 0x28, 0x44, 0x82], // <
[0x24, 0x24, 0x24, 0x24, 0x24], // =
[0x82, 0x44, 0x28, 0x10], // >
[0x60, 0x80, 0x9a, 0x90, 0x60], // ?
[0x7c, 0x82, 0xba, 0xaa, 0x78], // @
[0x7e, 0x90, 0x90, 0x90, 0x7e], // A - Ascii 65
[0xfe, 0x92, 0x92, 0x92, 0x6c], // B
[0x7c, 0x82, 0x82, 0x82, 0x44], // C
[0xfe, 0x82, 0x82, 0x82, 0x7c], // D
[0xfe, 0x92, 0x92, 0x92, 0x82], // E
[0xfe, 0x90, 0x90, 0x90, 0x80], // F
[0x7c, 0x82, 0x92, 0x92, 0x5c], // G
[0xfe, 0x10, 0x10, 0x10, 0xfe], // H
[0x82, 0xfe, 0x82], // I
[0x0c, 0x02, 0x02, 0x02, 0xfc], // J
[0xfe, 0x10, 0x28, 0x44, 0x82], // K
[0xfe, 0x02, 0x02, 0x02, 0x02], // L
[0xfe, 0x40, 0x20, 0x40, 0xfe], // M
[0xfe, 0x40, 0x20, 0x10, 0xfe], // N
[0x7c, 0x82, 0x82, 0x82, 0x7c], // O
[0xfe, 0x90, 0x90, 0x90, 0x60], // P
[0x7c, 0x82, 0x92, 0x8c, 0x7a], // Q
[0xfe, 0x90, 0x90, 0x98, 0x66], // R
[0x64, 0x92, 0x92, 0x92, 0x4c], // S
[0x80, 0x80, 0xfe, 0x80, 0x80], // T
[0xfc, 0x02, 0x02, 0x02, 0xfc], // U
[0xf8, 0x04, 0x02, 0x04, 0xf8], // V
[0xfc, 0x02, 0x3c, 0x02, 0xfc], // W
[0xc6, 0x28, 0x10, 0x28, 0xc6], // X
[0xe0, 0x10, 0x0e, 0x10, 0xe0], // Y
[0x86, 0x8a, 0x92, 0xa2, 0xc2], // Z - Ascii 90
[0xfe, 0x82, 0x82], // [
[0x40, 0x20, 0x10, 0x08, 0x04], // \
[0x82, 0x82, 0xfe], // ]
[0x20, 0x40, 0x80, 0x40, 0x20], // ^
[0x02, 0x02, 0x02, 0x02, 0x02], // _
[0xc0, 0xe0], // '
[0x04, 0x2a, 0x2a, 0x2a, 0x1e], // a - Ascii 97
[0xfe, 0x22, 0x22, 0x22, 0x1c], // b
[0x1c, 0x22, 0x22, 0x22], // c
[0x1c, 0x22, 0x22, 0x22, 0xfc], // d
[0x1c, 0x2a, 0x2a, 0x2a, 0x10], // e
[0x10, 0x7e, 0x90, 0x90, 0x80], // f
[0x18, 0x25, 0x25, 0x25, 0x3e], // g
[0xfe, 0x20, 0x20, 0x20, 0x1e], // h
[0xbe, 0x02], // i
[0x02, 0x01, 0x01, 0x21, 0xbe], // j
[0xfe, 0x08, 0x14, 0x22], // k
[0xfe, 0x02], // l
[0x3e, 0x20, 0x18, 0x20, 0x1e], // m
[0x3e, 0x20, 0x20, 0x20, 0x1e], // n
[0x1c, 0x22, 0x22, 0x22, 0x1c], // o
[0x3f, 0x22, 0x22, 0x22, 0x1c], // p
[0x1c, 0x22, 0x22, 0x22, 0x3f], // q
[0x22, 0x1e, 0x22, 0x20, 0x10], // r
[0x12, 0x2a, 0x2a, 0x2a, 0x04], // s
[0x20, 0x7c, 0x22, 0x22, 0x04], // t
[0x3c, 0x02, 0x02, 0x3e], // u
[0x38, 0x04, 0x02, 0x04, 0x38], // v
[0x3c, 0x06, 0x0c, 0x06, 0x3c], // w
[0x22, 0x14, 0x08, 0x14, 0x22], // x
[0x39, 0x05, 0x06, 0x3c], // y
[0x26, 0x2a, 0x2a, 0x32], // z - Ascii 122
[0x10, 0x7c, 0x82, 0x82], // {
[0xee], // |
[0x82, 0x82, 0x7c, 0x10], // }
[0x40, 0x80, 0x40, 0x80], // ~
[0x60, 0x90, 0x90, 0x60], // Degrees sign - Ascii 127
]
// Zero instance variables ahead of constructor function
_i2c_address = 0
_i2c = null
_rst = null
_buffer = null
_gbuffer = null
_pixel_cursor_x = 0
_pixel_cursor_y = 0
_text_cursor_x = 0
_text_cursor_y = 0
_oled_width = 0
_oled_height = 0
constructor(imp_i2c_bus, address, imp_rst_pin, pixel_width = 128, pixel_height = 32)
{
// Parameters:
// 1. The chosen imp I2C bus object
// 2. The OLED's 7-bit I2C address as an integer
// 3. The chosen imp pin object to control the Reset line
// 4. OLED pixel width
// 5. OLED pixel height
_i2c = imp_i2c_bus
_i2c_address = address
_i2c.configure(CLOCK_SPEED_400_KHZ)
_rst = imp_rst_pin
_rst.configure(DIGITAL_OUT)
_oled_width = pixel_width
_oled_height = pixel_height
_buffer = array((_oled_width * _oled_height / 8), 0)
_gbuffer = blob(_oled_width * _oled_height)
}
function init()
{
// Toggle the RST pin over 1ms + 10ms
_rst.write(HIGH)
imp.sleep(0.001)
_rst.write(LOW)
imp.sleep(0.01)
_rst.write(HIGH)
// Write the display settings
_i2c.write(_i2c_address, "\x00" + SSD1306_DISPLAYOFF)
_i2c.write(_i2c_address, "\x00" + SSD1306_SETDISPLAYCLOCKDIV + "\x80")
_i2c.write(_i2c_address, "\x00" + SSD1306_SETMULTIPLEX + "\x1F")
_i2c.write(_i2c_address, "\x00" + SSD1306_SETDISPLAYOFFSET + "\x00")
_i2c.write(_i2c_address, "\x00" + SSD1306_SETSTARTLINE)
_i2c.write(_i2c_address, "\x00" + SSD1306_CHARGEPUMP + "\x14")
_i2c.write(_i2c_address, "\x00" + SSD1306_MEMORYMODE + "\x00") // Horizontal addressing mode
_i2c.write(_i2c_address, "\x00" + SSD1306_SEGREMAP)
_i2c.write(_i2c_address, "\x00" + SSD1306_COMSCANDEC)
_i2c.write(_i2c_address, "\x00" + SSD1306_SETCOMPINS + "\x02")
_i2c.write(_i2c_address, "\x00" + SSD1306_SETCONTRAST + "\x8F")
_i2c.write(_i2c_address, "\x00" + SSD1306_SETPRECHARGE + "\xF1")
_i2c.write(_i2c_address, "\x00" + SSD1306_SETVCOMDETECT + "\x40")
_i2c.write(_i2c_address, "\x00" + SSD1306_DISPLAYALLON_RESUME)
_i2c.write(_i2c_address, "\x00" + SSD1306_NORMALDISPLAY)
_i2c.write(_i2c_address, "\x00" + SSD1306_DISPLAYON)
clear_display()
}
function set_gfx_cursor(x, y)
{
if (x < 0 || x > 127 || y < 0 || y > 31) return
_pixel_cursor_x = x
_pixel_cursor_y = y
_i2c.write(_i2c_address, "\x00" + SSD1306_COLUMNADDR)
_i2c.write(_i2c_address, "\x00" + _pixel_cursor_x.tochar())
_i2c.write(_i2c_address, "\x00" + (_oled_width - 1).tochar())
_i2c.write(_i2c_address, "\x00" + SSD1306_PAGEADDR)
_i2c.write(_i2c_address, "\x00" + _pixel_cursor_y.tochar())
_i2c.write(_i2c_address, "\x00" + (_oled_height - 1).tochar())
}
function set_text_cursor(row, col)
{
if (row < 0 || row > 3 || col < 0 || col > 15) return
_text_cursor_x = col
_text_cursor_y = row
local a = _text_cursor_x * 8
local b = _text_cursor_y
local c = "\x03"
if (_oled_height == 64) c = "\x07"
_i2c.write(_i2c_address, "\x00" + SSD1306_COLUMNADDR + a.tochar() + (_oled_width - 1).tochar())
_i2c.write(_i2c_address, "\x00" + SSD1306_PAGEADDR + b.tochar() + c)
}
function display()
{
// Writes the display buffer to screen as a single I2C string
local data_string = ""
for (local i = 0; i < (_oled_width * _oled_height / 8) ; i++)
{
data_string = data_string + _buffer[i].tochar()
}
_i2c.write(_i2c_address, "\x40" + data_string)
}
function clear_buffer()
{
for (local i = 0 ; i < 512 ; i++)
{
_buffer[i] = 0x00;
}
}
function clear_display()
{
// Wipes the display buffer by writing 0x00 to all array elements
clear_buffer()
set_gfx_cursor(0, 0)
display()
}
function inverse(setting)
{
if (setting)
{
_i2c.write(_i2c_address, "\x00" + SSD1306_INVERTDISPLAY)
}
else
{
_i2c.write(_i2c_address, "\x00" + SSD1306_NORMALDISPLAY)
}
}
function print_text(string_to_print)
{
// Display the input string on the display
// Parameter:
// 1. String to print
local data_string = ""
foreach (index, character in string_to_print)
{
// Read in each character in the source string, determine its
// Ascii value and use this to find the character's bitmap array
// in the charset.
local ascii = string_to_print[index] - 32
local glyph = charset[ascii]
for (local j = 0 ; j < glyph.len() ; j++)
{
local c = flip(glyph[j])
data_string = data_string + c.tochar()
}
data_string = data_string + "\x00"
}
_i2c.write(_i2c_address, "\x40" + data_string)
}
function print_icon(icon_array)
{
// Display a custom 8 x 8 character icon on the display
// Parameter:
// 1. Array of eight 8-bit values forming the icon bitmap
local data_string = ""
local icon = rotate_matrix(icon_array)
for (local i = 0 ; i < 8 ; i++)
{
data_string = data_string + icon[i].tochar()
}
_i2c.write(_i2c_address, "\x40" + data_string)
}
function print_char(ascii_value, x, y)
{
// Display a specific character at a set position
// Parameters:
// 1. Integer Ascii code of the character to print
// 2. Integer row value (0-3)
// 3. Integer column value (0-15)
set_text_cursor(y, x)
local data_string = ""
local glyph = charset[ascii_value - 32]
for (local j = 0 ; j < glyph.len() ; j++)
{
local a = flip(glyph[j])
data_string = data_string + a.tochar()
}
_i2c.write(_i2c_address, "\x40" + data_string)
}
function rotate_matrix(input_matrix)
{
// Rotate the character matrix through 90 degrees anti-clockwise
// Used if the OLED matrix pins are connected directly to a breadboard
local i = 0
local j = 0
local a = 0
local line_value = 0
local output_matrix = [0,0,0,0,0,0,0,0]
for (i = 0 ; i < 8 ; i++)
{
line_value = input_matrix[i]
for (j = 7 ; j > -1 ; j--)
{
a = (line_value & math.pow(2, j).tointeger())
if (a > 0)
{
output_matrix[7-j] = output_matrix[7-j] + math.pow(2, i).tointeger()
}
}
}
return output_matrix
}
function print_bitmap(pixel_array)
{
// Displays a preformatted 128 x 32 or 128 x 64 bitmap image stored in a byte array
// Note each byte consists of a row-high set of *vertical* pixels
// Parameter:
// 1. Array of 512 or 1024 8-bit values
set_text_cursor(0, 0)
local data_string = ""
foreach (byte in pixel_array)
{
data_string = data_string + byte.tochar()
}
_i2c.write(_i2c_address, "\x40" + data_string)
}
function flip(value)
{
local a = 0
local b = 0
for (local i = 0 ; i < 8 ; i++)
{
a = value & math.pow(2, i).tointeger()
if (a > 0)
{
b = b + math.pow(2, 7 - i).tointeger()
}
}
return b
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment