From dd6f498f5c4614a0cca3672e7da5c69b897582c8 Mon Sep 17 00:00:00 2001 From: gsexton Date: Fri, 28 Mar 2025 15:35:17 -0600 Subject: [PATCH] ssd1306: Add support for SH1106 and SH1107 (#104) * Add support for SH1106 Chip * Add support for SH1107 Chip * Expand example w/ Geometric drawing examples. * Cleanup code to use constants. --- ssd1306/doc.go | 24 +++- ssd1306/example_test.go | 77 +++++++++--- ssd1306/image1bit/image1bit.go | 14 +++ ssd1306/ssd1306.go | 207 ++++++++++++++++++++++++--------- ssd1306/ssd1306_test.go | 39 +++++-- 5 files changed, 277 insertions(+), 84 deletions(-) diff --git a/ssd1306/doc.go b/ssd1306/doc.go index 968a621..aafb7a9 100644 --- a/ssd1306/doc.go +++ b/ssd1306/doc.go @@ -2,17 +2,18 @@ // Use of this source code is governed under the Apache License, Version 2.0 // that can be found in the LICENSE file. -// Package ssd1306 controls a 128x64 monochrome OLED display via a SSD1306 -// controller. +// Package ssd1306 controls a monochrome OLED display via a SSD1306, SH1106, +// or SH1107 controller. The driver automatically detects the variant and +// adjusts accordingly. // // The driver does differential updates: it only sends modified pixels for the // smallest rectangle, to economize bus bandwidth. This is especially important // when using I²C as the bus default speed (often 100kHz) is slow enough to // saturate the bus at less than 10 frames per second. // -// The SSD1306 is a write-only device. It can be driven on either I²C or SPI -// with 4 wires. Changing between protocol is likely done through resistor -// soldering, for boards that support both. +// The device can be driven on either I²C or SPI with 4 wires. Changing +// between protocol is likely done through resistor soldering, for boards that +// support both. // // Some boards expose a RES / Reset pin. If present, it must be normally be // High. When set to Low (Ground), it enables the reset circuitry. It can be @@ -25,11 +26,22 @@ // # Datasheets // // Product page: +// +// # SSD1306 +// // http://www.solomon-systech.com/en/product/display-ic/oled-driver-controller/ssd1306/ // // https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf // // "DM-OLED096-624": https://drive.google.com/file/d/0B5lkVYnewKTGaEVENlYwbDkxSGM/view // -// "ssd1306": https://drive.google.com/file/d/0B5lkVYnewKTGYzhyWWp0clBMR1E/view +// # SH1106 +// +// https://cdn.velleman.eu/downloads/29/infosheets/sh1106_datasheet.pdf +// +// # SH1107 +// +// https://www.adafruit.com/product/5297 +// +// https://www.displayfuture.com/Display/datasheet/controller/SH1107.pdf package ssd1306 diff --git a/ssd1306/example_test.go b/ssd1306/example_test.go index cfc2060..c4a6a5e 100644 --- a/ssd1306/example_test.go +++ b/ssd1306/example_test.go @@ -5,8 +5,14 @@ package ssd1306_test import ( + "flag" + "fmt" "image" + "image/color" + "image/draw" "log" + "math" + "time" "periph.io/x/conn/v3/i2c/i2creg" "periph.io/x/devices/v3/ssd1306" @@ -19,35 +25,80 @@ func Example() { if _, err := host.Init(); err != nil { log.Fatal(err) } - + var width = flag.Int("width", 128, "Display Width") + var height = flag.Int("height", 64, "Display Height") + flag.Parse() // Use i2creg I²C bus registry to find the first available I²C bus. b, err := i2creg.Open("") if err != nil { log.Fatal(err) } defer b.Close() - - dev, err := ssd1306.NewI2C(b, &ssd1306.DefaultOpts) + opts := ssd1306.DefaultOpts + opts.W = *width + opts.H = *height + if opts.H == 32 { + opts.Sequential = true + } + dev, err := ssd1306.NewI2C(b, &opts) if err != nil { - log.Fatalf("failed to initialize ssd1306: %v", err) + log.Fatalf("failed to initialize display: %s", err.Error()) } + fmt.Printf("device=%s\n", dev.String()) - // Draw on it. img := image1bit.NewVerticalLSB(dev.Bounds()) // Note: this code is commented out so periph does not depend on: // "golang.org/x/image/font" // "golang.org/x/image/font/basicfont" // "golang.org/x/image/math/fixed" // - // f := basicfont.Face7x13 - // drawer := font.Drawer{ - // Dst: img, - // Src: &image.Uniform{image1bit.On}, - // Face: f, - // Dot: fixed.P(0, img.Bounds().Dy()-1-f.Descent), - // } - // drawer.DrawString("Hello from periph!") + // Draw on it. + /* + f := basicfont.Face7x13 + drawer := font.Drawer{ + Dst: img, + Src: &image.Uniform{image1bit.On}, + + Face: f, + Dot: fixed.P(0, img.Bounds().Dy()-1-f.Descent), + } + drawer.DrawString("Hello from periph!") + _ = dev.Draw(dev.Bounds(), img, image.Point{}) + time.Sleep(5 * time.Second) + */ + + white := color.RGBA{255, 255, 255, 255} + black := color.RGBA{0, 0, 0, 255} + colors := []color.RGBA{white, black} + + rectNum := 0 + // Draw some nested rectangles + for w, h := opts.W, opts.H; w > 0 && h > 0; w, h = w-4, h-4 { + rect := image.Rect(0, 0, w, h) + draw.Draw(img, rect.Add(image.Point{X: rectNum * 2, Y: rectNum * 2}), &image.Uniform{colors[rectNum%2]}, image.Point{}, draw.Src) + rectNum += 1 + } + if err := dev.Draw(dev.Bounds(), img, image.Point{}); err != nil { log.Fatal(err) } + time.Sleep(5 * time.Second) + + // Draw a Sine Wave + _ = dev.Invert(true) + img = image1bit.NewVerticalLSB(dev.Bounds()) + img.DrawHLine(0, opts.W, opts.H>>1-1, image1bit.On) + img.DrawVLine(0, opts.H, opts.W>>1-1, image1bit.On) + angle := float64(0) + angleStep := float64((4 * math.Pi) / float64(opts.W)) + scale := float64((opts.H >> 1) - 4) + for step := opts.W - 1; step >= 0; step -= 1 { + y := int(float64(math.Sin(angle)*scale)) + opts.H>>1 + img.SetBit(step, y, image1bit.On) + angle += angleStep + } + _ = dev.Draw(dev.Bounds(), img, image.Point{}) + time.Sleep(10 * time.Second) + + _ = dev.Halt() } diff --git a/ssd1306/image1bit/image1bit.go b/ssd1306/image1bit/image1bit.go index 8bda7f7..50dc370 100644 --- a/ssd1306/image1bit/image1bit.go +++ b/ssd1306/image1bit/image1bit.go @@ -143,6 +143,20 @@ func (i *VerticalLSB) SetBit(x, y int, b Bit) { } } +// Draw a horizontal line from start to end at ypos +func (i *VerticalLSB) DrawHLine(start, end, ypos int, b Bit) { + for x := start; x < end; x++ { + i.SetBit(x, ypos, b) + } +} + +// Draw a vertical line from start to end at xpos +func (i *VerticalLSB) DrawVLine(start, end, xpos int, b Bit) { + for y := start; y < end; y++ { + i.SetBit(xpos, y, b) + } +} + /* // SubImage returns an image representing the portion of the image p visible // through r. The returned value shares pixels with the original image. diff --git a/ssd1306/ssd1306.go b/ssd1306/ssd1306.go index 39fd2d2..987e7fd 100644 --- a/ssd1306/ssd1306.go +++ b/ssd1306/ssd1306.go @@ -4,13 +4,14 @@ package ssd1306 -// Some have SPI enabled; +// The SSD1306, SH1106, SH1107 are a family of OLED displays. Some have SPI enabled. +// // https://hallard.me/adafruit-oled-display-driver-for-pi/ +// // https://learn.adafruit.com/ssd1306-oled-displays-with-raspberry-pi-and-beaglebone-black?view=all import ( "bytes" - "errors" "fmt" "image" "image/color" @@ -25,22 +26,25 @@ import ( "periph.io/x/devices/v3/ssd1306/image1bit" ) +type variant string + const ( _CHARGEPUMP = 0x8D _COLUMNADDR = 0x21 _COMSCANDEC = 0xC8 _COMSCANINC = 0xC0 - _DISPLAYALLON = 0xA5 + _DC_DC_SETTING = 0xAD + _DEACTIVATE_SCROLL = 0x2E _DISPLAYALLON_RESUME = 0xA4 _DISPLAYOFF = 0xAE _DISPLAYON = 0xAF - _EXTERNALVCC = 0x1 _INVERTDISPLAY = 0xA7 _MEMORYMODE = 0x20 _NORMALDISPLAY = 0xA6 _PAGEADDR = 0x22 _PAGESTARTADDRESS = 0xB0 _SEGREMAP = 0xA0 + _SET_PAGE_ADDRESS = 0xB0 _SETCOMPINS = 0xDA _SETCONTRAST = 0x81 _SETDISPLAYCLOCKDIV = 0xD5 @@ -52,7 +56,13 @@ const ( _SETSEGMENTREMAP = 0xA1 _SETSTARTLINE = 0x40 _SETVCOMDETECT = 0xDB - _SWITCHCAPVCC = 0x2 + _SH1107_SETSTARTLINE = 0xDC +) + +const ( + _SSD1306 variant = "SSD1306" + _SH1106 variant = "SH1106" + _SH1107 variant = "SH1107" ) // FrameRate determines scrolling speed. @@ -91,6 +101,7 @@ var DefaultOpts = Opts{ MirrorHorizontal: false, Sequential: false, SwapTopBottom: false, + Addr: 0x3c, } // Opts defines the options for the device. @@ -103,7 +114,7 @@ type Opts struct { 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. + // missing on your display. Particularly on 32 pixel height displays. Sequential bool // MirrorVertical corresponds to the COM remap configuration in the OLED panel // hardware. Try toggling this if the display is flipped vertically. @@ -117,6 +128,8 @@ type Opts struct { // the OLED panel hardware. Try toggling this if the top and bottom halves of // your display are swapped. SwapTopBottom bool + // The I2C address of the display. + Addr uint16 } // NewSPI returns a Dev object that communicates over SPI to a SSD1306 display @@ -137,7 +150,7 @@ type Opts struct { // be reinstantiated. func NewSPI(p spi.Port, dc gpio.PinOut, opts *Opts) (*Dev, error) { if dc == gpio.INVALID { - return nil, errors.New("ssd1306: use nil for dc to use 3-wire mode, do not use gpio.INVALID") + return nil, fmt.Errorf("%s: use nil for dc to use 3-wire mode, do not use gpio.INVALID", _SSD1306) } bits := 8 if dc == nil { @@ -156,8 +169,11 @@ func NewSPI(p spi.Port, dc gpio.PinOut, opts *Opts) (*Dev, error) { // NewI2C returns a Dev object that communicates over I²C to a SSD1306 display // controller. func NewI2C(i i2c.Bus, opts *Opts) (*Dev, error) { + if opts.Addr == 0x00 { + opts.Addr = DefaultOpts.Addr + } // Maximum clock speed is 1/2.5µs = 400KHz. - return newDev(&i2c.Dev{Bus: i, Addr: 0x3C}, opts, false, nil) + return newDev(&i2c.Dev{Bus: i, Addr: opts.Addr}, opts, false, nil) } // Dev is an open handle to the display controller. @@ -184,13 +200,18 @@ type Dev struct { startCol, endCol int scrolled bool halted bool + // The display type. _SSD1306, _SH1106, _SH1107. + variant variant + // The SH1106 is a little funny. It's got 132 bytes wide of RAM, but 4 bytes + // are unused, so you have to offset writes by two to account for it. + startOffset byte } func (d *Dev) String() string { if d.spi { - return fmt.Sprintf("ssd1360.Dev{%s, %s, %s}", d.c, d.dc, d.rect.Max) + return fmt.Sprintf("%s.Dev{%s, %s, %s}", d.variant, d.c, d.dc, d.rect.Max) } - return fmt.Sprintf("ssd1360.Dev{%s, %s}", d.c, d.rect.Max) + return fmt.Sprintf("%s.Dev{%s, %s}", d.variant, d.c, d.rect.Max) } // ColorModel implements display.Drawer. @@ -234,7 +255,7 @@ func (d *Dev) Draw(r image.Rectangle, src image.Image, sp image.Point) error { // This function accepts the content of image1bit.VerticalLSB.Pix. func (d *Dev) Write(pixels []byte) (int, error) { if len(pixels) != len(d.buffer) { - return 0, fmt.Errorf("ssd1306: invalid pixel stream length; expected %d bytes, got %d bytes", len(d.buffer), len(pixels)) + return 0, fmt.Errorf("%s: invalid pixel stream length; expected %d bytes, got %d bytes", d.variant, len(d.buffer), len(pixels)) } // Write() skips d.next so it saves 1kb of RAM. if err := d.drawInternal(pixels); err != nil { @@ -288,16 +309,23 @@ func (d *Dev) StopScroll() error { // // Note: values other than 0xff do not seem useful... func (d *Dev) SetContrast(level byte) error { - return d.sendCommand([]byte{0x81, level}) + return d.sendCommand([]byte{_SETCONTRAST, level}) } // SetDisplayStartLine causes the display to start from startLine, effectively // scrolling the screen to that position. // -// startLine must be between 0 and 63. +// startLine must be between 0 and 63 (127 SH1107). func (d *Dev) SetDisplayStartLine(startLine byte) error { + if d.variant == _SH1107 { + if startLine > 0x7f { + return fmt.Errorf("%s: invalid startLine %d", d.variant, startLine) + } + return d.sendCommand([]byte{_SH1107_SETSTARTLINE, startLine}) + } + // SSDH1306 and SH1106 if startLine > 63 { - return fmt.Errorf("ssd1306: invalid startLine %d", startLine) + return fmt.Errorf("%s: invalid startLine %d", d.variant, startLine) } return d.sendCommand([]byte{_SETSTARTLINE | startLine}) } @@ -307,7 +335,7 @@ func (d *Dev) SetDisplayStartLine(startLine byte) error { // Sending any other command afterward reenables the display. func (d *Dev) Halt() error { d.halted = false - err := d.sendCommand([]byte{0xAE}) + err := d.sendCommand([]byte{_DISPLAYOFF}) if err == nil { d.halted = true } @@ -316,24 +344,16 @@ func (d *Dev) Halt() error { // Invert the display (black on white vs white on black). func (d *Dev) Invert(blackOnWhite bool) error { - b := []byte{0xA6} + b := []byte{_NORMALDISPLAY} if blackOnWhite { - b[0] = 0xA7 + b[0] = _INVERTDISPLAY } return d.sendCommand(b) } -// - // newDev is the common initialization code that is independent of the // communication protocol (I²C or SPI) being used. func newDev(c conn.Conn, opts *Opts, usingSPI bool, dc gpio.PinOut) (*Dev, error) { - if opts.W < 8 || opts.W > 128 || opts.W&7 != 0 { - return nil, fmt.Errorf("ssd1306: invalid width %d", opts.W) - } - if opts.H < 8 || opts.H > 64 || opts.H&7 != 0 { - return nil, fmt.Errorf("ssd1306: invalid height %d", opts.H) - } nbPages := opts.H / 8 pageSize := opts.W @@ -350,36 +370,76 @@ func newDev(c conn.Conn, opts *Opts, usingSPI bool, dc gpio.PinOut) (*Dev, error // Signal that the screen must be redrawn on first draw(). scrolled: true, } - if err := d.sendCommand(getInitCmd(opts)); err != nil { + + // Read the variant directly from the chip. + id, _ := d.readID() + id &= 0x0f + if id == 0x07 || id == 0x0f { + d.variant = _SH1107 + } else if id == 0x08 { + d.startOffset = 2 + d.variant = _SH1106 + } else { + d.variant = _SSD1306 + } + if err := d.sendCommand(getInitCmd(opts, d.variant)); err != nil { return nil, err } + + // validate W/H with rules for the specific variant. + if opts.W < 8 || opts.W > 128 || opts.W&7 != 0 { + return nil, fmt.Errorf("%s: invalid width %d", d.variant, opts.W) + } + + if d.variant == _SH1107 { + if opts.H < 8 || opts.H > 128 || opts.H&7 != 0 { + return nil, fmt.Errorf("%s: invalid height %d", d.variant, opts.H) + } + } else { + // SSD1306 and SH1106 + if opts.H < 8 || opts.H > 64 || opts.H&7 != 0 { + return nil, fmt.Errorf("%s: invalid height %d", d.variant, opts.H) + } + } + return d, nil } -func getInitCmd(opts *Opts) []byte { +func getInitCmd(opts *Opts, variant variant) []byte { + if variant == _SH1107 { + return getInitCmd1107(opts) + } + return getInitCmd1306(opts) +} + +func getInitCmd1306(opts *Opts) []byte { // Set COM output scan direction; C0 means normal; C8 means reversed - comScan := byte(0xC8) + comScan := byte(_COMSCANDEC) // See page 40. - columnAddr := byte(0xA1) + columnAddr := byte(_SETSEGMENTREMAP) + if opts.Rotated { // Change order both horizontally and vertically. - comScan = 0xC0 - columnAddr = byte(0xA0) + comScan = _COMSCANINC + columnAddr = byte(_SEGREMAP) } if opts.MirrorVertical { - comScan = byte(0xC0) + comScan = byte(_COMSCANINC) } + if opts.MirrorHorizontal { - columnAddr = byte(0xA0) + columnAddr = byte(_SEGREMAP) } // 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 // 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. @@ -389,25 +449,40 @@ func getInitCmd(opts *Opts) []byte { // Page 64 has the full recommended flow. // Page 28 lists all the commands. return []byte{ - 0xAE, // Display off - 0xD3, 0x00, // Set display offset; 0 - 0x40, // Start display start line; 0 - columnAddr, // Set segment remap; RESET is column 127. - comScan, // - 0xDA, hwLayout, // Set COM pins hardware configuration; see page 40 - 0x81, 0xFF, // Set max contrast - 0xA4, // Set display to use GDDRAM content - 0xA6, // Set normal display (0xA7 for inverted 0=lit, 1=dark) - 0xD5, freq, // Set osc frequency and divide ratio; power on reset value is 0x80. - 0x8D, 0x14, // Enable charge pump regulator; page 62 - 0xD9, 0xF1, // Set pre-charge period; from adafruit driver - 0xDB, 0x40, // Set Vcomh deselect level; page 32 - 0x2E, // Deactivate scroll - 0xA8, byte(opts.H - 1), // Set multiplex ratio (number of lines to display) - 0x20, 0x00, // Set memory addressing mode to horizontal - 0x21, 0, uint8(opts.W - 1), // Set column address (Width) - 0x22, 0, uint8(opts.H/8 - 1), // Set page address (Pages) - 0xAF, // Display on + _DISPLAYOFF, // Display off + _SETDISPLAYOFFSET, 0x00, // Set display offset; 0 + _SETSTARTLINE, // Start display start line; 0 + columnAddr, // Set segment remap; RESET is column 127. + comScan, // + _SETCOMPINS, hwLayout, // Set COM pins hardware configuration; see page 40 + _SETCONTRAST, 0xFF, // Set max contrast + _DISPLAYALLON_RESUME, // Set display to use GDDRAM content + _NORMALDISPLAY, // Set normal display (_INVERTDISPLAY for inverted 0=lit, 1=dark) + _SETDISPLAYCLOCKDIV, freq, // Set osc frequency and divide ratio; power on reset value is 0x80. + _CHARGEPUMP, 0x14, // Enable charge pump regulator; page 62 + _SETPRECHARGE, 0xF1, // Set pre-charge period; from adafruit driver + _SETVCOMDETECT, 0x40, // Set Vcomh deselect level; page 32 + _DEACTIVATE_SCROLL, // Deactivate scroll + _SETMULTIPLEX, byte(opts.H - 1), // Set multiplex ratio (number of lines to display) + _MEMORYMODE, 0x00, // Set memory addressing mode to horizontal + _COLUMNADDR, 0, uint8(opts.W - 1), // Set column address (Width) + _PAGEADDR, 0, uint8(opts.H/8 - 1), // Set page address (Pages) + _DISPLAYON, // Display on + } +} + +func getInitCmd1107(opts *Opts) []byte { + // From the adafruit driver... + return []byte{ + _DISPLAYOFF, // display off + _SETMULTIPLEX, byte(opts.H - 1), // set multiplex ratio (number of lines to display) + _MEMORYMODE, // set memory addressing mode to horizontal + _SET_PAGE_ADDRESS, // page address + _DC_DC_SETTING, 0x81, // DC/DC Converter + _SETDISPLAYCLOCKDIV, 0x50, // Display CLock + _SETVCOMDETECT, 0x35, // VCOM DSEL Level + _SETPRECHARGE, 0x22, // Discharge/PreCharge + _DISPLAYON, // display on } } @@ -491,7 +566,7 @@ func (d *Dev) drawInternal(next []byte) error { for page := d.startPage; page < d.endPage; page++ { err := d.sendCommand([]byte{ _PAGESTARTADDRESS | byte(page), - _SETLOWCOLUMN | (byte(d.startCol) & 0x0F), + _SETLOWCOLUMN | ((byte(d.startCol) & 0x0F) + d.startOffset), _SETHIGHCOLUMN | (byte(d.startCol) >> 4), }) if err != nil { @@ -526,13 +601,13 @@ func (d *Dev) sendData(c []byte) error { func (d *Dev) sendCommand(c []byte) error { if d.halted { // Transparently enable the display. - c = append([]byte{0xAF}, c...) + c = append([]byte{_DISPLAYON}, c...) d.halted = false } if d.spi { if d.dc == nil { // 3-wire SPI. - return errors.New("ssd1306: 3-wire SPI mode is not yet implemented") + return fmt.Errorf("%s: 3-wire SPI mode is not yet implemented", d.variant) } // 4-wire SPI. if err := d.dc.Out(gpio.Low); err != nil { @@ -543,6 +618,28 @@ func (d *Dev) sendCommand(c []byte) error { return d.c.Tx(append([]byte{i2cCmd}, c...), nil) } +// readID() reads the ID byte of the device. Piecing together the datasheet, +// the format is: +// +// Bits +// ---- +// 0 - 5 Device ID +// +// ID Values have been documented as: +// +// 0x03 SSD1306 128x32 +// 0x06 SSD1306 128x64 +// 0x07 or 0x0f: sh1107 +// 0x08: sh1106 +// +// 6 Display On/Off 0=on, 1 = off +// 7 BUSY +func (d *Dev) readID() (byte, error) { + r := make([]byte, 1) + err := d.c.Tx([]byte{0}, r) + return r[0], err +} + const ( i2cCmd = 0x00 // I²C transaction has stream of command bytes i2cData = 0x40 // I²C transaction has stream of data bytes diff --git a/ssd1306/ssd1306_test.go b/ssd1306/ssd1306_test.go index 0db41c3..17ee163 100644 --- a/ssd1306/ssd1306_test.go +++ b/ssd1306/ssd1306_test.go @@ -57,7 +57,7 @@ func TestI2C_String(t *testing.T) { if err != nil { t.Fatal(err) } - expected := "ssd1360.Dev{playback(60), (128,64)}" + expected := "SSD1306.Dev{playback(60), (128,64)}" if s := dev.String(); s != expected { t.Fatalf("%q != %q", expected, s) } @@ -77,6 +77,7 @@ func TestI2C_Draw_VerticalLSD_fast(t *testing.T) { bus := i2ctest.Playback{ Ops: []i2ctest.IO{ + {Addr: 0x3c, W: []byte{0}, R: []byte{0x06}}, // Startup initialization. {Addr: 0x3c, W: initCmdI2C()}, @@ -131,6 +132,7 @@ func TestI2C_Halt_Write(t *testing.T) { bus := i2ctest.Playback{ Ops: []i2ctest.IO{ // Startup initialization. + {Addr: 0x3c, W: []byte{0}, R: []byte{0x06}}, {Addr: 0x3c, W: initCmdI2C()}, // Halt() {Addr: 0x3c, W: []byte{0x0, 0xae}}, @@ -207,6 +209,7 @@ func TestI2C_Write_invalid_size(t *testing.T) { bus := i2ctest.Playback{ Ops: []i2ctest.IO{ // Startup initialization. + {Addr: 0x3c, W: []byte{0}, R: []byte{0x06}}, {Addr: 0x3c, W: initCmdI2C()}, }, } @@ -268,6 +271,7 @@ func TestI2C_DrawGray(t *testing.T) { bus := i2ctest.Playback{ Ops: []i2ctest.IO{ // Startup initialization. + {Addr: 0x3c, W: []byte{0}, R: []byte{0x06}}, {Addr: 0x3c, W: initCmdI2C()}, // Page 1 {Addr: 0x3c, W: []byte{0x00, 0xB0, 0x00, 0x10}}, @@ -314,6 +318,7 @@ func TestI2C_DrawGray(t *testing.T) { func TestI2C_Scroll(t *testing.T) { bus := i2ctest.Playback{ Ops: []i2ctest.IO{ + {Addr: 0x3c, W: []byte{0}, R: []byte{0x06}}, {Addr: 0x3c, W: initCmdI2C()}, // Scroll Left. {Addr: 0x3c, W: []byte{0x0, 0x27, 0x0, 0x0, 0x6, 0x7, 0x0, 0xff, 0x2f}}, @@ -353,6 +358,7 @@ func TestI2C_Scroll(t *testing.T) { func TestI2C_SetContrast(t *testing.T) { bus := i2ctest.Playback{ Ops: []i2ctest.IO{ + {Addr: 0x3c, W: []byte{0}, R: []byte{0x06}}, {Addr: 0x3c, W: initCmdI2C()}, {Addr: 0x3c, W: []byte{0x0, 0x81, 0x0}}, {Addr: 0x3c, W: []byte{0x0, 0x81, 0x7f}}, @@ -380,6 +386,7 @@ func TestI2C_SetContrast(t *testing.T) { func TestI2C_SetDisplayStartLine(t *testing.T) { bus := i2ctest.Playback{ Ops: []i2ctest.IO{ + {Addr: 0x3c, W: []byte{0}, R: []byte{0x06}}, {Addr: 0x3c, W: initCmdI2C()}, {Addr: 0x3c, W: []byte{0x0, 0x40}}, {Addr: 0x3c, W: []byte{0x0, 0x45}}, @@ -411,6 +418,7 @@ func TestI2C_SetDisplayStartLine(t *testing.T) { func TestI2C_Invert_Halt_resume(t *testing.T) { bus := i2ctest.Playback{ Ops: []i2ctest.IO{ + {Addr: 0x3c, W: []byte{0}, R: []byte{0x06}}, {Addr: 0x3c, W: initCmdI2C()}, // Invert(true) {Addr: 0x3c, W: []byte{0x0, 0xa7}}, @@ -441,6 +449,7 @@ func TestI2C_Invert_Halt_resume(t *testing.T) { func TestI2C_Halt(t *testing.T) { bus := i2ctest.Playback{ Ops: []i2ctest.IO{ + {Addr: 0x3c, W: []byte{0}, R: []byte{0x06}}, {Addr: 0x3c, W: initCmdI2C()}, // Halt() {Addr: 0x3c, W: []byte{0x0, 0xae}}, @@ -467,7 +476,9 @@ func TestI2C_Halt(t *testing.T) { // func TestNewSPI_fail(t *testing.T) { - if d, err := NewSPI(&spitest.Playback{}, nil, &Opts{H: 64}); d != nil || err == nil { + if d, err := NewSPI(&spitest.Playback{ + Playback: conntest.Playback{Ops: []conntest.IO{{W: []byte{0}, R: []byte{0x06}}}}, + }, nil, &Opts{H: 64}); d != nil || err == nil { t.Fatal(d, err) } if d, err := NewSPI(&configFail{}, nil, &Opts{W: 64, H: 64}); d != nil || err == nil { @@ -483,7 +494,7 @@ func TestNewSPI_fail(t *testing.T) { func TestSPI_3wire(t *testing.T) { // Not supported yet. - if dev, err := NewSPI(&spitest.Playback{}, nil, &DefaultOpts); dev != nil || err == nil { + if dev, err := NewSPI(&spitest.Playback{Playback: conntest.Playback{Ops: []conntest.IO{{W: []byte{0}, R: []byte{0x06}}}}}, nil, &DefaultOpts); dev != nil || err == nil { t.Fatal("SPI 3-wire is not supported") } } @@ -491,14 +502,17 @@ func TestSPI_3wire(t *testing.T) { func TestSPI_4wire_String(t *testing.T) { port := spitest.Playback{ Playback: conntest.Playback{ - Ops: []conntest.IO{{W: getInitCmd(&Opts{W: 128, H: 64, MirrorVertical: false, MirrorHorizontal: false})}}, + Ops: []conntest.IO{ + {W: []byte{0x00}, R: []byte{0x06}}, + {W: getInitCmd(&Opts{W: 128, H: 64, MirrorVertical: false, MirrorHorizontal: false}, _SSD1306)}, + }, }, } dev, err := NewSPI(&port, &gpiotest.Pin{N: "pin1", Num: 42}, &DefaultOpts) if err != nil { t.Fatal(err) } - expected := "ssd1360.Dev{playback, pin1(42), (128,64)}" + expected := "SSD1306.Dev{playback, pin1(42), (128,64)}" if s := dev.String(); s != expected { t.Fatalf("%q != %q", expected, s) } @@ -516,7 +530,8 @@ func TestSPI_4wire_Write_differential(t *testing.T) { port := spitest.Playback{ Playback: conntest.Playback{ Ops: []conntest.IO{ - {W: getInitCmd(&Opts{W: 128, H: 64, MirrorVertical: false, MirrorHorizontal: false})}, + {W: []byte{0}, R: []byte{0x06}}, + {W: getInitCmd(&Opts{W: 128, H: 64, MirrorVertical: false, MirrorHorizontal: false}, _SSD1306)}, // Page 1 {W: []byte{0xB0, 0x00, 0x10}}, @@ -573,7 +588,7 @@ func TestSPI_4wire_Write_differential_fail(t *testing.T) { port := spitest.Playback{ Playback: conntest.Playback{ Ops: []conntest.IO{ - {W: getInitCmd(&Opts{W: 128, H: 64, MirrorVertical: false, MirrorHorizontal: false})}, + {W: getInitCmd(&Opts{W: 128, H: 64, MirrorVertical: false, MirrorHorizontal: false}, _SSD1306)}, // Page 1 {W: []byte{0xB0, 0x00, 0x10}}, {W: buf1}, @@ -623,7 +638,10 @@ func TestSPI_4wire_Write_differential_fail(t *testing.T) { func TestSPI_4wire_gpio_fail(t *testing.T) { port := spitest.Playback{ Playback: conntest.Playback{ - Ops: []conntest.IO{{W: getInitCmd(&Opts{W: 128, H: 64, MirrorVertical: false, MirrorHorizontal: false})}}, + Ops: []conntest.IO{ + {W: []byte{0}, R: []byte{0x06}}, + {W: getInitCmd(&Opts{W: 128, H: 64, MirrorVertical: false, MirrorHorizontal: false}, _SSD1306)}, + }, }, } pin := &failPin{fail: false} @@ -656,7 +674,7 @@ func TestInitCmd(t *testing.T) { } for _, test := range tests { - got := getInitCmd(test.opts) + got := getInitCmd(test.opts, _SSD1306) if !bytes.Contains(got, test.wantSubslice) { t.Errorf("getInitCmd(%v) -> %v, want %v", test.opts, got, test.wantSubslice) } @@ -666,13 +684,14 @@ func TestInitCmd(t *testing.T) { // func initCmdI2C() []byte { - return append([]byte{0}, getInitCmd(&Opts{W: 128, H: 64, MirrorVertical: false, MirrorHorizontal: false})...) + return append([]byte{0}, getInitCmd(&Opts{W: 128, H: 64, MirrorVertical: false, MirrorHorizontal: false}, _SSD1306)...) } func getI2CPlayback() *i2ctest.Playback { return &i2ctest.Playback{ Ops: []i2ctest.IO{ // Startup initialization. + {Addr: 0x3c, W: []byte{0}, R: []byte{0x06}}, {Addr: 0x3c, W: initCmdI2C()}, }, }