ssd1306: add options to customize the hardware pin layout (#282)

Fixes #166
pull/1/head
David Sansome 8 years ago committed by M-A
parent 97f5f4fe5d
commit 5d01024987

@ -54,9 +54,11 @@ const (
// DefaultOpts is the recommended default options. // DefaultOpts is the recommended default options.
var DefaultOpts = Opts{ var DefaultOpts = Opts{
W: 128, W: 128,
H: 64, H: 64,
Rotated: false, Rotated: false,
Sequential: false,
SwapTopBottom: false,
} }
// Opts defines the options for the device. // Opts defines the options for the device.
@ -65,6 +67,14 @@ type Opts struct {
H int H int
// Rotated determines if the display is rotated by 180°. // Rotated determines if the display is rotated by 180°.
Rotated bool Rotated bool
// Sequential corresponds to the Sequential/Alternative COM pin configuration
// in the OLED panel hardware. Try toggling this if half the rows appear to be
// missing on your display.
Sequential bool
// SwapTopBottom corresponds to the Left/Right remap COM pin configuration in
// the OLED panel hardware. Try toggling this if the top and bottom halves of
// your display are swapped.
SwapTopBottom bool
} }
// NewSPI returns a Dev object that communicates over SPI to a SSD1306 display // NewSPI returns a Dev object that communicates over SPI to a SSD1306 display
@ -287,22 +297,30 @@ func newDev(c conn.Conn, opts *Opts, usingSPI bool, dc gpio.PinOut) (*Dev, error
// Signal that the screen must be redrawn on first draw(). // Signal that the screen must be redrawn on first draw().
scrolled: true, scrolled: true,
} }
if err := d.sendCommand(getInitCmd(opts.W, opts.H, opts.Rotated)); err != nil { if err := d.sendCommand(getInitCmd(opts)); err != nil {
return nil, err return nil, err
} }
return d, nil return d, nil
} }
func getInitCmd(w, h int, rotated bool) []byte { func getInitCmd(opts *Opts) []byte {
// Set COM output scan direction; C0 means normal; C8 means reversed // Set COM output scan direction; C0 means normal; C8 means reversed
comScan := byte(0xC8) comScan := byte(0xC8)
// See page 40. // See page 40.
columnAddr := byte(0xA1) columnAddr := byte(0xA1)
if rotated { if opts.Rotated {
// Change order both horizontally and vertically. // Change order both horizontally and vertically.
comScan = 0xC0 comScan = 0xC0
columnAddr = byte(0xA0) columnAddr = byte(0xA0)
} }
// See page 40.
hwLayout := byte(0x02)
if !opts.Sequential {
hwLayout |= 0x10
}
if opts.SwapTopBottom {
hwLayout |= 0x20
}
// Set the max frequency. The problem with I²C is that it creates visible // Set the max frequency. The problem with I²C is that it creates visible
// tear down. On SPI at high speed this is not visible. Page 23 pictures how // tear down. On SPI at high speed this is not visible. Page 23 pictures how
// to avoid tear down. For now default to max frequency. // to avoid tear down. For now default to max frequency.
@ -314,10 +332,10 @@ func getInitCmd(w, h int, rotated bool) []byte {
return []byte{ return []byte{
0xAE, // Display off 0xAE, // Display off
0xD3, 0x00, // Set display offset; 0 0xD3, 0x00, // Set display offset; 0
0x40, // Start display start line; 0 0x40, // Start display start line; 0
columnAddr, // Set segment remap; RESET is column 127. columnAddr, // Set segment remap; RESET is column 127.
comScan, // comScan, //
0xDA, 0x12, // Set COM pins hardware configuration; see page 40 0xDA, hwLayout, // Set COM pins hardware configuration; see page 40
0x81, 0xFF, // Set max contrast 0x81, 0xFF, // Set max contrast
0xA4, // Set display to use GDDRAM content 0xA4, // Set display to use GDDRAM content
0xA6, // Set normal display (0xA7 for inverted 0=lit, 1=dark) 0xA6, // Set normal display (0xA7 for inverted 0=lit, 1=dark)
@ -325,11 +343,11 @@ func getInitCmd(w, h int, rotated bool) []byte {
0x8D, 0x14, // Enable charge pump regulator; page 62 0x8D, 0x14, // Enable charge pump regulator; page 62
0xD9, 0xF1, // Set pre-charge period; from adafruit driver 0xD9, 0xF1, // Set pre-charge period; from adafruit driver
0xDB, 0x40, // Set Vcomh deselect level; page 32 0xDB, 0x40, // Set Vcomh deselect level; page 32
0x2E, // Deactivate scroll 0x2E, // Deactivate scroll
0xA8, byte(h - 1), // Set multiplex ratio (number of lines to display) 0xA8, byte(opts.H - 1), // Set multiplex ratio (number of lines to display)
0x20, 0x00, // Set memory addressing mode to horizontal 0x20, 0x00, // Set memory addressing mode to horizontal
0x21, 0, uint8(w - 1), // Set column address (Width) 0x21, 0, uint8(opts.W - 1), // Set column address (Width)
0x22, 0, uint8(h/8 - 1), // Set page address (Pages) 0x22, 0, uint8(opts.H/8 - 1), // Set page address (Pages)
0xAF, // Display on 0xAF, // Display on
} }
} }

@ -5,6 +5,7 @@
package ssd1306 package ssd1306
import ( import (
"bytes"
"errors" "errors"
"image" "image"
"image/color" "image/color"
@ -384,7 +385,7 @@ func TestSPI_3wire(t *testing.T) {
func TestSPI_4wire_String(t *testing.T) { func TestSPI_4wire_String(t *testing.T) {
port := spitest.Playback{ port := spitest.Playback{
Playback: conntest.Playback{ Playback: conntest.Playback{
Ops: []conntest.IO{{W: getInitCmd(128, 64, false)}}, Ops: []conntest.IO{{W: getInitCmd(&Opts{W: 128, H: 64, Rotated: false})}},
}, },
} }
dev, err := NewSPI(&port, &gpiotest.Pin{N: "pin1", Num: 42}, &DefaultOpts) dev, err := NewSPI(&port, &gpiotest.Pin{N: "pin1", Num: 42}, &DefaultOpts)
@ -409,7 +410,7 @@ func TestSPI_4wire_Write_differential(t *testing.T) {
port := spitest.Playback{ port := spitest.Playback{
Playback: conntest.Playback{ Playback: conntest.Playback{
Ops: []conntest.IO{ Ops: []conntest.IO{
{W: getInitCmd(128, 64, false)}, {W: getInitCmd(&Opts{W: 128, H: 64, Rotated: false})},
{W: buf1}, {W: buf1},
// Reset to write only to the first page. // Reset to write only to the first page.
{W: []byte{0x21, 0x0, 0x7f, 0x22, 0x1, 0x1}}, {W: []byte{0x21, 0x0, 0x7f, 0x22, 0x1, 0x1}},
@ -441,7 +442,7 @@ func TestSPI_4wire_Write_differential_fail(t *testing.T) {
port := spitest.Playback{ port := spitest.Playback{
Playback: conntest.Playback{ Playback: conntest.Playback{
Ops: []conntest.IO{ Ops: []conntest.IO{
{W: getInitCmd(128, 64, false)}, {W: getInitCmd(&Opts{W: 128, H: 64, Rotated: false})},
{W: buf1}, {W: buf1},
}, },
DontPanic: true, DontPanic: true,
@ -468,7 +469,7 @@ func TestSPI_4wire_Write_differential_fail(t *testing.T) {
func TestSPI_4wire_gpio_fail(t *testing.T) { func TestSPI_4wire_gpio_fail(t *testing.T) {
port := spitest.Playback{ port := spitest.Playback{
Playback: conntest.Playback{ Playback: conntest.Playback{
Ops: []conntest.IO{{W: getInitCmd(128, 64, false)}}, Ops: []conntest.IO{{W: getInitCmd(&Opts{W: 128, H: 64, Rotated: false})}},
}, },
} }
pin := &failPin{fail: false} pin := &failPin{fail: false}
@ -489,10 +490,29 @@ func TestSPI_4wire_gpio_fail(t *testing.T) {
} }
} }
func TestInitCmd(t *testing.T) {
tests := []struct {
opts *Opts
wantSubslice []byte
}{
{opts: &Opts{W: 128, H: 64}, wantSubslice: []byte{0xDA, 0x12}},
{opts: &Opts{W: 128, H: 64, Sequential: true}, wantSubslice: []byte{0xDA, 0x02}},
{opts: &Opts{W: 128, H: 64, SwapTopBottom: true}, wantSubslice: []byte{0xDA, 0x32}},
{opts: &Opts{W: 128, H: 64, Sequential: true, SwapTopBottom: true}, wantSubslice: []byte{0xDA, 0x22}},
}
for _, test := range tests {
got := getInitCmd(test.opts)
if !bytes.Contains(got, test.wantSubslice) {
t.Errorf("getInitCmd(%v) -> %v, want %v", test.opts, got, test.wantSubslice)
}
}
}
// //
func initCmdI2C() []byte { func initCmdI2C() []byte {
return append([]byte{0}, getInitCmd(128, 64, false)...) return append([]byte{0}, getInitCmd(&Opts{W: 128, H: 64, Rotated: false})...)
} }
var preludeI2C = []byte{ var preludeI2C = []byte{

Loading…
Cancel
Save