diff --git a/devices/ssd1306/example_test.go b/devices/ssd1306/example_test.go index 427fab7..3f86bd7 100644 --- a/devices/ssd1306/example_test.go +++ b/devices/ssd1306/example_test.go @@ -27,7 +27,7 @@ func Example() { } defer b.Close() - dev, err := ssd1306.NewI2C(b, 128, 64, false) + dev, err := ssd1306.NewI2C(b, &ssd1306.DefaultOpts) if err != nil { log.Fatalf("failed to initialize ssd1306: %v", err) } diff --git a/devices/ssd1306/ssd1306.go b/devices/ssd1306/ssd1306.go index 46d8b49..cfc3167 100644 --- a/devices/ssd1306/ssd1306.go +++ b/devices/ssd1306/ssd1306.go @@ -77,11 +77,24 @@ const ( UpLeft Orientation = 0x2A ) +// DefaultOpts is the recommended default options. +var DefaultOpts = Opts{ + W: 128, + H: 64, + Rotated: false, +} + +// Opts defines the options for the device. +type Opts struct { + W int + H int + // Rotated determines if the display is rotated by 180°. + Rotated bool +} + // NewSPI returns a Dev object that communicates over SPI to a SSD1306 display // controller. // -// If rotated is true, turns the display by 180° -// // The SSD1306 can operate at up to 3.3Mhz, which is much higher than I²C. This // permits higher refresh rates. // @@ -95,7 +108,7 @@ const ( // The RES (reset) pin can be used outside of this driver but is not supported // natively. In case of external reset via the RES pin, this device drive must // be reinstantiated. -func NewSPI(p spi.Port, dc gpio.PinOut, w, h int, rotated bool) (*Dev, error) { +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") } @@ -110,16 +123,14 @@ func NewSPI(p spi.Port, dc gpio.PinOut, w, h int, rotated bool) (*Dev, error) { if err != nil { return nil, err } - return newDev(c, w, h, rotated, true, dc) + return newDev(c, opts, true, dc) } // NewI2C returns a Dev object that communicates over I²C to a SSD1306 display // controller. -// -// If rotated, turns the display by 180° -func NewI2C(i i2c.Bus, w, h int, rotated bool) (*Dev, error) { +func NewI2C(i i2c.Bus, opts *Opts) (*Dev, error) { // Maximum clock speed is 1/2.5µs = 400KHz. - return newDev(&i2c.Dev{Bus: i, Addr: 0x3C}, w, h, rotated, false, nil) + return newDev(&i2c.Dev{Bus: i, Addr: 0x3C}, opts, false, nil) } // Dev is an open handle to the display controller. @@ -286,30 +297,30 @@ func (d *Dev) Invert(blackOnWhite bool) error { // newDev is the common initialization code that is independent of the // communication protocol (I²C or SPI) being used. -func newDev(c conn.Conn, w, h int, rotated, usingSPI bool, dc gpio.PinOut) (*Dev, error) { - if w < 8 || w > 128 || w&7 != 0 { - return nil, fmt.Errorf("ssd1306: invalid width %d", w) +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 h < 8 || h > 64 || h&7 != 0 { - return nil, fmt.Errorf("ssd1306: invalid height %d", h) + if opts.H < 8 || opts.H > 64 || opts.H&7 != 0 { + return nil, fmt.Errorf("ssd1306: invalid height %d", opts.H) } - nbPages := h / 8 - pageSize := w + nbPages := opts.H / 8 + pageSize := opts.W d := &Dev{ c: c, spi: usingSPI, dc: dc, - rect: image.Rect(0, 0, int(w), int(h)), + rect: image.Rect(0, 0, opts.W, opts.H), buffer: make([]byte, nbPages*pageSize), startPage: 0, endPage: nbPages, startCol: 0, - endCol: w, + endCol: opts.W, // Signal that the screen must be redrawn on first draw(). scrolled: true, } - if err := d.sendCommand(getInitCmd(w, h, rotated)); err != nil { + if err := d.sendCommand(getInitCmd(opts.W, opts.H, opts.Rotated)); err != nil { return nil, err } return d, nil diff --git a/devices/ssd1306/ssd1306_test.go b/devices/ssd1306/ssd1306_test.go index aa772f7..dbdc2db 100644 --- a/devices/ssd1306/ssd1306_test.go +++ b/devices/ssd1306/ssd1306_test.go @@ -21,13 +21,13 @@ import ( func TestNewI2C_fail(t *testing.T) { bus := i2ctest.Playback{DontPanic: true} - if d, err := NewI2C(&bus, 0, 64, false); d != nil || err == nil { + if d, err := NewI2C(&bus, &Opts{H: 64}); d != nil || err == nil { t.Fatal(d, err) } - if d, err := NewI2C(&bus, 64, 0, false); d != nil || err == nil { + if d, err := NewI2C(&bus, &Opts{W: 64}); d != nil || err == nil { t.Fatal(d, err) } - if d, err := NewI2C(&bus, 64, 64, true); d != nil || err == nil { + if d, err := NewI2C(&bus, &Opts{W: 64, H: 64, Rotated: true}); d != nil || err == nil { t.Fatal(d, err) } if err := bus.Close(); err != nil { @@ -37,7 +37,7 @@ func TestNewI2C_fail(t *testing.T) { func TestI2C_ColorModel(t *testing.T) { bus := getI2CPlayback() - dev, err := NewI2C(bus, 128, 64, false) + dev, err := NewI2C(bus, &DefaultOpts) if err != nil { t.Fatal(err) } @@ -51,7 +51,7 @@ func TestI2C_ColorModel(t *testing.T) { func TestI2C_String(t *testing.T) { bus := getI2CPlayback() - dev, err := NewI2C(bus, 128, 64, false) + dev, err := NewI2C(bus, &DefaultOpts) if err != nil { t.Fatal(err) } @@ -77,7 +77,7 @@ func TestI2C_Draw_VerticalLSD_fast(t *testing.T) { {Addr: 0x3c, W: buf}, }, } - dev, err := NewI2C(&bus, 128, 64, false) + dev, err := NewI2C(&bus, &DefaultOpts) if err != nil { t.Fatal(err) } @@ -109,7 +109,7 @@ func TestI2C_Halt_Write(t *testing.T) { {Addr: 0x3c, W: buf}, }, } - dev, err := NewI2C(&bus, 128, 64, false) + dev, err := NewI2C(&bus, &DefaultOpts) if err != nil { t.Fatal(err) } @@ -136,7 +136,7 @@ func TestI2C_Halt_resume_fail(t *testing.T) { }, DontPanic: true, } - dev, err := NewI2C(&bus, 128, 64, false) + dev, err := NewI2C(&bus, &DefaultOpts) if err != nil { t.Fatal(err) } @@ -158,7 +158,7 @@ func TestI2C_Write_invalid_size(t *testing.T) { {Addr: 0x3c, W: initCmdI2C()}, }, } - dev, err := NewI2C(&bus, 128, 64, false) + dev, err := NewI2C(&bus, &DefaultOpts) if err != nil { t.Fatal(err) } @@ -178,7 +178,7 @@ func TestI2C_Write_fail(t *testing.T) { }, DontPanic: true, } - dev, err := NewI2C(&bus, 128, 64, false) + dev, err := NewI2C(&bus, &DefaultOpts) if err != nil { t.Fatal(err) } @@ -198,7 +198,7 @@ func TestI2C_Draw_fail(t *testing.T) { }, DontPanic: true, } - dev, err := NewI2C(&bus, 128, 64, false) + dev, err := NewI2C(&bus, &DefaultOpts) if err != nil { t.Fatal(err) } @@ -220,7 +220,7 @@ func TestI2C_DrawGray(t *testing.T) { {Addr: 0x3c, W: append([]byte{i2cData}, grayCheckboard()...)}, }, } - dev, err := NewI2C(&bus, 128, 64, false) + dev, err := NewI2C(&bus, &DefaultOpts) if err != nil { t.Fatal(err) } @@ -250,7 +250,7 @@ func TestI2C_Scroll(t *testing.T) { {Addr: 0x3c, W: []byte{0x0, 0x2e}}, }, } - dev, err := NewI2C(&bus, 128, 64, false) + dev, err := NewI2C(&bus, &DefaultOpts) if err != nil { t.Fatal(err) } @@ -286,7 +286,7 @@ func TestI2C_SetContrast(t *testing.T) { {Addr: 0x3c, W: []byte{0x0, 0x81, 0xff}}, }, } - dev, err := NewI2C(&bus, 128, 64, false) + dev, err := NewI2C(&bus, &DefaultOpts) if err != nil { t.Fatal(err) } @@ -316,7 +316,7 @@ func TestI2C_Invert_Halt_resume(t *testing.T) { {Addr: 0x3c, W: []byte{0x0, 0xaf, 0xa6}}, }, } - dev, err := NewI2C(&bus, 128, 64, false) + dev, err := NewI2C(&bus, &DefaultOpts) if err != nil { t.Fatal(err) } @@ -345,7 +345,7 @@ func TestI2C_Halt(t *testing.T) { }, DontPanic: true, } - dev, err := NewI2C(&bus, 128, 64, false) + dev, err := NewI2C(&bus, &DefaultOpts) if err != nil { t.Fatal(err) } @@ -363,23 +363,23 @@ func TestI2C_Halt(t *testing.T) { // func TestNewSPI_fail(t *testing.T) { - if d, err := NewSPI(&spitest.Playback{}, nil, 0, 64, false); d != nil || err == nil { + if d, err := NewSPI(&spitest.Playback{}, nil, &Opts{H: 64}); d != nil || err == nil { t.Fatal(d, err) } - if d, err := NewSPI(&configFail{}, nil, 64, 64, false); d != nil || err == nil { + if d, err := NewSPI(&configFail{}, nil, &Opts{W: 64, H: 64}); d != nil || err == nil { t.Fatal(d, err) } - if d, err := NewSPI(&spitest.Playback{}, gpio.INVALID, 64, 64, false); d != nil || err == nil { + if d, err := NewSPI(&spitest.Playback{}, gpio.INVALID, &DefaultOpts); d != nil || err == nil { t.Fatal(d, err) } - if d, err := NewSPI(&spitest.Playback{}, &failPin{fail: true}, 64, 64, false); d != nil || err == nil { + if d, err := NewSPI(&spitest.Playback{}, &failPin{fail: true}, &DefaultOpts); d != nil || err == nil { t.Fatal(d, err) } } func TestSPI_3wire(t *testing.T) { // Not supported yet. - if dev, err := NewSPI(&spitest.Playback{}, nil, 128, 64, false); dev != nil || err == nil { + if dev, err := NewSPI(&spitest.Playback{}, nil, &DefaultOpts); dev != nil || err == nil { t.Fatal("SPI 3-wire is not supported") } } @@ -390,7 +390,7 @@ func TestSPI_4wire_String(t *testing.T) { Ops: []conntest.IO{{W: getInitCmd(128, 64, false)}}, }, } - dev, err := NewSPI(&port, &gpiotest.Pin{N: "pin1", Num: 42}, 128, 64, false) + dev, err := NewSPI(&port, &gpiotest.Pin{N: "pin1", Num: 42}, &DefaultOpts) if err != nil { t.Fatal(err) } @@ -420,7 +420,7 @@ func TestSPI_4wire_Write_differential(t *testing.T) { }, }, } - dev, err := NewSPI(&port, &gpiotest.Pin{N: "pin1", Num: 42}, 128, 64, false) + dev, err := NewSPI(&port, &gpiotest.Pin{N: "pin1", Num: 42}, &DefaultOpts) if err != nil { t.Fatal(err) } @@ -450,7 +450,7 @@ func TestSPI_4wire_Write_differential_fail(t *testing.T) { DontPanic: true, }, } - dev, err := NewSPI(&port, &gpiotest.Pin{N: "pin1", Num: 42}, 128, 64, false) + dev, err := NewSPI(&port, &gpiotest.Pin{N: "pin1", Num: 42}, &DefaultOpts) if err != nil { t.Fatal(err) } @@ -475,7 +475,7 @@ func TestSPI_4wire_gpio_fail(t *testing.T) { }, } pin := &failPin{fail: false} - dev, err := NewSPI(&port, pin, 128, 64, false) + dev, err := NewSPI(&port, pin, &DefaultOpts) if err != nil { t.Fatal(err) } diff --git a/devices/ssd1306/ssd1306smoketest/ssd1306smoketest.go b/devices/ssd1306/ssd1306smoketest/ssd1306smoketest.go index 2eab1b9..96152d6 100644 --- a/devices/ssd1306/ssd1306smoketest/ssd1306smoketest.go +++ b/devices/ssd1306/ssd1306smoketest/ssd1306smoketest.go @@ -94,13 +94,14 @@ func (s *SmokeTest) Run(f *flag.FlagSet, args []string) (err error) { if len(*dcName) != 0 { dc = gpioreg.ByName(*dcName) } + opts := ssd1306.Opts{W: *w, H: *h, Rotated: *rotated} if !*record { - return s.run(i2cBus, spiPort, dc, *w, *h, *rotated) + return s.run(i2cBus, spiPort, dc, &opts) } i2cRecorder := i2ctest.Record{Bus: i2cBus} spiRecorder := spitest.Record{Port: spiPort} - err = s.run(&i2cRecorder, &spiRecorder, dc, *w, *h, *rotated) + err = s.run(&i2cRecorder, &spiRecorder, dc, &opts) if len(i2cRecorder.Ops) != 0 { fmt.Printf("I²C recorder Addr: 0x%02X\n", i2cRecorder.Ops[0].Addr) } else { @@ -151,16 +152,16 @@ func (s *SmokeTest) Run(f *flag.FlagSet, args []string) (err error) { return err } -func (s *SmokeTest) run(i2cBus i2c.Bus, spiPort spi.PortCloser, dc gpio.PinOut, w, h int, rotated bool) (err error) { +func (s *SmokeTest) run(i2cBus i2c.Bus, spiPort spi.PortCloser, dc gpio.PinOut, opts *ssd1306.Opts) (err error) { s.timings = make([]time.Duration, 2) start := time.Now() - i2cDev, err2 := ssd1306.NewI2C(i2cBus, w, h, rotated) + i2cDev, err2 := ssd1306.NewI2C(i2cBus, opts) s.timings[0] = time.Since(start) if err2 != nil { return err2 } start = time.Now() - spiDev, err2 := ssd1306.NewSPI(spiPort, dc, w, h, rotated) + spiDev, err2 := ssd1306.NewSPI(spiPort, dc, opts) s.timings[1] = time.Since(start) if err2 != nil { return err2 @@ -181,8 +182,8 @@ func (s *SmokeTest) run(i2cBus i2c.Bus, spiPort spi.PortCloser, dc gpio.PinOut, // Right format, right size imgBunny1bitLarge := image1bit.NewVerticalLSB(i2cDev.Bounds()) center := imgBunny1bit.Bounds() - draw.Src.Draw(imgBunny1bitLarge, center.Add(image.Point{X: (w - center.Dx()) / 2}), imgBunny1bit, image.Point{}) - imgClear := make([]byte, w*h/8) + draw.Src.Draw(imgBunny1bitLarge, center.Add(image.Point{X: (opts.W - center.Dx()) / 2}), imgBunny1bit, image.Point{}) + imgClear := make([]byte, opts.W*opts.H/8) for i, d := range s.devices { start := time.Now() @@ -332,7 +333,7 @@ func (s *SmokeTest) run(i2cBus i2c.Bus, spiPort spi.PortCloser, dc gpio.PinOut, } s.step("Restore") - imgStripes := broadStripes(w, h) + imgStripes := broadStripes(opts.W, opts.H) for i, d := range s.devices { start := time.Now() if _, err := d.Write(imgStripes); err != nil { @@ -378,7 +379,7 @@ func (s *SmokeTest) run(i2cBus i2c.Bus, spiPort spi.PortCloser, dc gpio.PinOut, } s.printStr("Clear (redundant)") - imgPattern := binaryPattern(w, h) + imgPattern := binaryPattern(opts.W, opts.H) for i, d := range s.devices { start := time.Now() if _, err := d.Write(imgPattern); err != nil { @@ -388,7 +389,7 @@ func (s *SmokeTest) run(i2cBus i2c.Bus, spiPort spi.PortCloser, dc gpio.PinOut, } s.step("Fill display with binary 0..255 pattern") - imgPattern[w+h/2] ^= 0x10 + imgPattern[opts.W+opts.H/2] ^= 0x10 for i, d := range s.devices { start := time.Now() if _, err := d.Write(imgPattern); err != nil {