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.
pull/109/head
gsexton 1 year ago committed by GitHub
parent 9a7f7ac5da
commit dd6f498f5c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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

@ -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()
}

@ -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.

@ -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

@ -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()},
},
}

Loading…
Cancel
Save