Skip to content

Commit

Permalink
Add SPI support for SSD1306 and SH1106
Browse files Browse the repository at this point in the history
  • Loading branch information
vickash committed Sep 7, 2024
1 parent a116a14 commit 4986d4e
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 58 deletions.
4 changes: 2 additions & 2 deletions HARDWARE.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@ Polling and reading follow a call and response pattern.
| Name | Status | Interface | Component Class | Notes |
| :--------------- | :------: | :-------- | :--------------- |------ |
| HD44780 LCD | :green_heart: | Digital Out, Output Register | `Display::HD44780` |
| SSD1306 OLED | :yellow_heart: | I2C | `Display::SSD1306` | 1 font, some graphics
| SH1106 OLED | :yellow_heart: | I2C | `Display::SH1106` | Similar to SSD1306
| SSD1306 OLED | :yellow_heart: | I2C or SPI | `Display::SSD1306` | 1 font, some graphics
| SH1106 OLED | :yellow_heart: | I2C or SPI | `Display::SH1106` | Works same as SSD1306
| ST7565R (128x64 Mono) | :heart: | SPI | `Display::ST7565R` |
| ST7735S (160x128 RGB) | :heart: | SPI | `Display::ST7735S` |
| ILI9341 (240x320 RGB) | :heart: | SPI | `Display::ILI9341` |
Expand Down
36 changes: 16 additions & 20 deletions examples/display/sh1106.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,27 @@

board = Denko::Board.new(Denko::Connection::Serial.new)

# The SH1106 OLED connects to either an I2C or SPI bus, depending on the model you have.
# Bus setup exampels in order:
# I2C Hardware
# I2C Bit-Bang
# SPI Hardware
# SPI Bit-Bang
#
# Default pins for the I2C0 (first) interface on most chips:
#
# ATmega 328p: SDA = 'A4' SCL = 'A5' - Arduino Uno, Nano
# ATmega 32u4: SDA = 2 SCL = 3 - Arduino Leonardo, Pro Micro
# ATmega1280 / 2560: SDA = 20 SCL = 21 - Arduino Mega
# SAM3X8E: SDA = 20 SCL = 21 - Arduino Due
# SAMD21G18: SDA = 20 SCL = 21 - Arduino Zero, M0, M0 Pro
# ESP8266: SDA = 4 SCL = 5
# ESP32: SDA = 21 SCL = 22
# RP2040: SDA = 4 SCL = 5 - Raspberry Pi Pico (W)
#
# Only give the SDA pin of the I2C bus. SCL (clock) pin must be
# connected for it to work, but we don't need to control it.
#

# Board's hardware I2C interface on predetermined pins.
bus = Denko::I2C::Bus.new(board: board, pin: :SDA)
# Bit-banged I2C on any pins.
# bus = Denko::I2C::BitBang.new(board: board, pins: {scl: 8, sda: 9})
# bus = Denko::I2C::BitBang.new(board: board, pins: {scl: 4, sda: 5})
# bus = Denko::SPI::Bus.new(board: board)
# bus = Denko::SPI::BitBang.new(board: board, pins: {clock: 13, output: 11})

oled = Denko::Display::SH1106.new(bus: bus, rotate: true)
canvas = oled.canvas
# I2C OLED, connected to I2C SDA and SCL only. Default I2C address of 0x3C.
oled = Denko::Display::SH1106.new(bus: bus, address: 0x3C, rotate: true)

# SPI OLED, connected to SPI CLK and MOSI pins.
# select and dc pins must be given. reset is optional (can be pulled high instead).
# oled = Denko::Display::SH1106.new(bus: bus, pins: {select: 10, dc: 7, reset: 8}, rotate: true)

# Draw some text on the OLED's canvas (a Ruby memory buffer).
canvas = oled.canvas
canvas.text_cursor = [27,60]
canvas.print("Hello World!")

Expand Down
36 changes: 16 additions & 20 deletions examples/display/ssd1306.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,27 @@

board = Denko::Board.new(Denko::Connection::Serial.new)

# The SSD1306 OLED connects to either an I2C or SPI bus, depending on the model you have.
# Bus setup exampels in order:
# I2C Hardware
# I2C Bit-Bang
# SPI Hardware
# SPI Bit-Bang
#
# Default pins for the I2C0 (first) interface on most chips:
#
# ATmega 328p: SDA = 'A4' SCL = 'A5' - Arduino Uno, Nano
# ATmega 32u4: SDA = 2 SCL = 3 - Arduino Leonardo, Pro Micro
# ATmega1280 / 2560: SDA = 20 SCL = 21 - Arduino Mega
# SAM3X8E: SDA = 20 SCL = 21 - Arduino Due
# SAMD21G18: SDA = 20 SCL = 21 - Arduino Zero, M0, M0 Pro
# ESP8266: SDA = 4 SCL = 5
# ESP32: SDA = 21 SCL = 22
# RP2040: SDA = 4 SCL = 5 - Raspberry Pi Pico (W)
#
# Only give the SDA pin of the I2C bus. SCL (clock) pin must be
# connected for it to work, but we don't need to control it.
#

# Board's hardware I2C interface on predetermined pins.
bus = Denko::I2C::Bus.new(board: board, pin: :SDA)
# Bit-banged I2C on any pins.
# bus = Denko::I2C::BitBang.new(board: board, pins: {scl: 8, sda: 9})
# bus = Denko::I2C::BitBang.new(board: board, pins: {scl: 4, sda: 5})
# bus = Denko::SPI::Bus.new(board: board)
# bus = Denko::SPI::BitBang.new(board: board, pins: {clock: 13, output: 11})

oled = Denko::Display::SSD1306.new(bus: bus, rotate: true)
canvas = oled.canvas
# I2C OLED, connected to I2C SDA and SCL only. Default I2C address of 0x3C.
oled = Denko::Display::SSD1306.new(bus: bus, address: 0x3C, rotate: true)

# SPI OLED, connected to SPI CLK and MOSI pins.
# select and dc pins must be given. reset is optional (can be pulled high instead).
# oled = Denko::Display::SSD1306.new(bus: bus, pins: {select: 10, dc: 7, reset: 8}, rotate: true)

# Draw some text on the OLED's canvas (a Ruby memory buffer).
canvas = oled.canvas
canvas.text_cursor = [27,60]
canvas.print("Hello World!")

Expand Down
75 changes: 59 additions & 16 deletions lib/denko/display/ssd1306.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
module Denko
module Display
class SSD1306
include I2C::Peripheral
include Behaviors::BusPeripheral

# Fundamental Commands
# Single byte (no need to OR with anything)
Expand Down Expand Up @@ -65,12 +65,6 @@ class SSD1306
WIDTHS = [64,96,128]
HEIGHTS = [16,32,48,64]

def before_initialize(options={})
@i2c_address = 0x3C
@i2c_frequency = 400000
super(options)
end

def after_initialize(options={})
super(options)

Expand All @@ -79,8 +73,8 @@ def after_initialize(options={})
@rows = options[:rows] || options[:height] || 64

# Validate known sizes.
raise ArgumentError, "error in SSD1306 width: #{@columns}. Must be in: #{WIDTHS.inspect}" unless WIDTHS.include?(@columns)
raise ArgumentError, "error in SSD1306 height: #{@rows}. Must be in: #{HEIGHTS.inspect}" unless HEIGHTS.include?(@rows)
raise ArgumentError, "error in #{self.class} width: #{@columns}. Must be in: #{WIDTHS.inspect}" unless WIDTHS.include?(@columns)
raise ArgumentError, "error in #{self.class} height: #{@rows}. Must be in: #{HEIGHTS.inspect}" unless HEIGHTS.include?(@rows)

# Everything except 96x16 size uses clock 0x80.
clock = 0x80
Expand All @@ -94,7 +88,8 @@ def after_initialize(options={})
seg_remap = options[:rotate] ? 0x01 : 0x00
com_direction = options[:rotate] ? 0x08 : 0x00

# Startup sequence
# Startup sequence (SPI doesn't work properly if this isn't sent twice.)
2.times do
command [
MULTIPLEX_RATIO, @rows - 1,
DISPLAY_OFFSET, 0x00,
Expand All @@ -111,6 +106,7 @@ def after_initialize(options={})
ADDRESSING_MODE, self.class::ADDRESSING_MODE_DEFAULT,
DISPLAY_ON
]
end

# Create a new blank canvas and draw it.
self.canvas = Canvas.new(@columns, @rows)
Expand Down Expand Up @@ -169,14 +165,61 @@ def draw_partial(buffer, x_min, x_max, p_min, p_max)
end
end

# Commands are I2C messages prefixed with 0x00.
def command(bytes)
i2c_write([0x00] + bytes)
def i2c_setup
singleton_class.include(I2C::Peripheral)

define_singleton_method(:before_initialize) do |options|
@i2c_address = 0x3C
@i2c_frequency = 400000
super(options)
end

# Commands are I2C messages prefixed with 0x00.
define_singleton_method(:command) do |bytes|
i2c_write([0x00] + bytes)
end

# Data are I2C messages prefixed with 0x40.
define_singleton_method(:data) do |bytes|
i2c_write([0x40] + bytes)
end
end

# Data are I2C messages prefixed with 0x40.
def data(bytes)
i2c_write([0x40] + bytes)
def spi_setup
singleton_class.include(SPI::Peripheral::MultiPin)

define_singleton_method(:initialize_pins) do |options|
super(options)
proxy_pin :dc, DigitalIO::Output, board: bus.board
proxy_pin :reset, DigitalIO::Output, board: bus.board, optional: true
reset.high if reset
end

# Commands are SPI bytes written while DC pin low.
define_singleton_method(:command) do |bytes|
dc.low
spi_write(bytes)
end

# Data are SPI SPI bytes written while DC pin high.
define_singleton_method(:data) do |bytes|
dc.high
spi_write(bytes)
end
end

def initialize(options={})
bus = options[:bus] || options[:board]

if bus.class.ancestors.include?(Denko::SPI::Bus) || bus.class.ancestors.include?(Denko::SPI::BitBang)
spi_setup
elsif bus.class.ancestors.include?(Denko::I2C::Bus) || bus.class.ancestors.include?(Denko::I2C::BitBang)
i2c_setup
else
raise ArgumentError, "#{self.class} must be connected to either an I2C or SPI bus"
end

super(options)
end
end
end
Expand Down

0 comments on commit 4986d4e

Please sign in to comment.