From 8fe00338c016c66756270b97ebef743a0f0846c2 Mon Sep 17 00:00:00 2001 From: Denis Luchkin-Zhou Date: Thu, 28 May 2020 18:40:05 -0700 Subject: [PATCH] Made column differential work for SSD1306 (#444) --- devices/ssd1306/ssd1306.go | 95 +++++++++++++------ devices/ssd1306/ssd1306_test.go | 161 ++++++++++++++++++++++++++++---- 2 files changed, 207 insertions(+), 49 deletions(-) diff --git a/devices/ssd1306/ssd1306.go b/devices/ssd1306/ssd1306.go index d703ade..d8fbd36 100644 --- a/devices/ssd1306/ssd1306.go +++ b/devices/ssd1306/ssd1306.go @@ -25,6 +25,36 @@ import ( "periph.io/x/periph/devices/ssd1306/image1bit" ) +const ( + _CHARGEPUMP = 0x8D + _COLUMNADDR = 0x21 + _COMSCANDEC = 0xC8 + _COMSCANINC = 0xC0 + _DISPLAYALLON = 0xA5 + _DISPLAYALLON_RESUME = 0xA4 + _DISPLAYOFF = 0xAE + _DISPLAYON = 0xAF + _EXTERNALVCC = 0x1 + _INVERTDISPLAY = 0xA7 + _MEMORYMODE = 0x20 + _NORMALDISPLAY = 0xA6 + _PAGEADDR = 0x22 + _PAGESTARTADDRESS = 0xB0 + _SEGREMAP = 0xA0 + _SETCOMPINS = 0xDA + _SETCONTRAST = 0x81 + _SETDISPLAYCLOCKDIV = 0xD5 + _SETDISPLAYOFFSET = 0xD3 + _SETHIGHCOLUMN = 0x10 + _SETLOWCOLUMN = 0x00 + _SETMULTIPLEX = 0xA8 + _SETPRECHARGE = 0xD9 + _SETSEGMENTREMAP = 0xA1 + _SETSTARTLINE = 0x40 + _SETVCOMDETECT = 0xDB + _SWITCHCAPVCC = 0x2 +) + // FrameRate determines scrolling speed. type FrameRate byte @@ -387,30 +417,28 @@ func (d *Dev) calculateSubset(next []byte) (int, int, int, int, bool) { // Early exit, the image is exactly the same. return 0, 0, 0, 0, true } - // TODO(maruel): This currently corrupts the screen. Likely a small error - // in the way the commands are sent. - /* - // Left. - for ; startCol < endCol; startCol++ { - for i := startPage; i < endPage; i++ { - x := i*pageSize + startCol - if d.buffer[x] != next[x] { - goto breakLeft - } - } + + // Left. + for ; startCol < endCol; startCol++ { + for i := startPage; i < endPage; i++ { + x := i*pageSize + startCol + if d.buffer[x] != next[x] { + goto breakLeft } - breakLeft: - // Right. - for ; endCol > startCol; endCol-- { - for i := startPage; i < endPage; i++ { - x := i*pageSize + endCol - 1 - if d.buffer[x] != next[x] { - goto breakRight - } - } + } + } + breakLeft: + + // Right. + for ; endCol > startCol; endCol-- { + for i := startPage; i < endPage; i++ { + x := i*pageSize + endCol - 1 + if d.buffer[x] != next[x] { + goto breakRight } - breakRight: - */ + } + } + breakRight: } return startPage, endPage, startCol, endCol, false } @@ -428,18 +456,25 @@ func (d *Dev) drawInternal(next []byte) error { d.endPage = endPage d.startCol = startCol d.endCol = endCol - cmd := []byte{ - 0x21, uint8(d.startCol), uint8(d.endCol - 1), // Set column address (Width) - 0x22, uint8(d.startPage), uint8(d.endPage - 1), // Set page address (Pages) + } + + pageSize := d.rect.Dx() + for page := d.startPage; page < d.endPage; page++ { + err := d.sendCommand([]byte{ + _PAGESTARTADDRESS | byte(page), + _SETLOWCOLUMN | (byte(d.startCol) & 0x0F), + _SETHIGHCOLUMN | (byte(d.startCol) >> 4), + }) + if err != nil { + return err } - if err := d.sendCommand(cmd); err != nil { + pageStart := page * pageSize + err = d.sendData(d.buffer[pageStart+d.startCol : pageStart+d.endCol]) + if err != nil { return err } } - - // Write the subset of the data as needed. - pageSize := d.rect.Dx() - return d.sendData(d.buffer[startPage*pageSize+startCol : (endPage-1)*pageSize+endCol]) + return nil } func (d *Dev) sendData(c []byte) error { diff --git a/devices/ssd1306/ssd1306_test.go b/devices/ssd1306/ssd1306_test.go index f1a952c..5511145 100644 --- a/devices/ssd1306/ssd1306_test.go +++ b/devices/ssd1306/ssd1306_test.go @@ -68,15 +68,42 @@ func TestI2C_String(t *testing.T) { func TestI2C_Draw_VerticalLSD_fast(t *testing.T) { // Exercise the fast path. - buf := make([]byte, 1025) + buf := make([]byte, 129) buf[0] = i2cData buf[23] = 1 + + emptyBuf := make([]byte, 129) + emptyBuf[0] = i2cData + bus := i2ctest.Playback{ Ops: []i2ctest.IO{ // Startup initialization. {Addr: 0x3c, W: initCmdI2C()}, - // Actual draw buffer. + + // Page 1 + {Addr: 0x3c, W: []byte{0x00, 0xB0, 0x00, 0x10}}, {Addr: 0x3c, W: buf}, + // Page 2 + {Addr: 0x3c, W: []byte{0x00, 0xB1, 0x00, 0x10}}, + {Addr: 0x3c, W: emptyBuf}, + // Page 3 + {Addr: 0x3c, W: []byte{0x00, 0xB2, 0x00, 0x10}}, + {Addr: 0x3c, W: emptyBuf}, + // Page 4 + {Addr: 0x3c, W: []byte{0x00, 0xB3, 0x00, 0x10}}, + {Addr: 0x3c, W: emptyBuf}, + // Page 5 + {Addr: 0x3c, W: []byte{0x00, 0xB4, 0x00, 0x10}}, + {Addr: 0x3c, W: emptyBuf}, + // Page 6 + {Addr: 0x3c, W: []byte{0x00, 0xB5, 0x00, 0x10}}, + {Addr: 0x3c, W: emptyBuf}, + // Page 7 + {Addr: 0x3c, W: []byte{0x00, 0xB6, 0x00, 0x10}}, + {Addr: 0x3c, W: emptyBuf}, + // Page 8 + {Addr: 0x3c, W: []byte{0x00, 0xB7, 0x00, 0x10}}, + {Addr: 0x3c, W: emptyBuf}, }, } dev, err := NewI2C(&bus, &DefaultOpts) @@ -95,19 +122,43 @@ func TestI2C_Draw_VerticalLSD_fast(t *testing.T) { func TestI2C_Halt_Write(t *testing.T) { // Exercise the fast path. - buf := make([]byte, 1025) + buf := make([]byte, 129) buf[0] = i2cData buf[23] = 1 + + emptyBuf := make([]byte, 129) + emptyBuf[0] = i2cData bus := i2ctest.Playback{ Ops: []i2ctest.IO{ // Startup initialization. {Addr: 0x3c, W: initCmdI2C()}, // Halt() {Addr: 0x3c, W: []byte{0x0, 0xae}}, - // transparent resume - {Addr: 0x3c, W: []byte{0x0, 0xaf}}, - // Actual draw buffer. + // transparent resume & page 1 setup + {Addr: 0x3c, W: []byte{0x0, 0xaf, 0xB0, 0x00, 0x10}}, + // Page 1 {Addr: 0x3c, W: buf}, + // Page 2 + {Addr: 0x3c, W: []byte{0x00, 0xB1, 0x00, 0x10}}, + {Addr: 0x3c, W: emptyBuf}, + // Page 3 + {Addr: 0x3c, W: []byte{0x00, 0xB2, 0x00, 0x10}}, + {Addr: 0x3c, W: emptyBuf}, + // Page 4 + {Addr: 0x3c, W: []byte{0x00, 0xB3, 0x00, 0x10}}, + {Addr: 0x3c, W: emptyBuf}, + // Page 5 + {Addr: 0x3c, W: []byte{0x00, 0xB4, 0x00, 0x10}}, + {Addr: 0x3c, W: emptyBuf}, + // Page 6 + {Addr: 0x3c, W: []byte{0x00, 0xB5, 0x00, 0x10}}, + {Addr: 0x3c, W: emptyBuf}, + // Page 7 + {Addr: 0x3c, W: []byte{0x00, 0xB6, 0x00, 0x10}}, + {Addr: 0x3c, W: emptyBuf}, + // Page 8 + {Addr: 0x3c, W: []byte{0x00, 0xB7, 0x00, 0x10}}, + {Addr: 0x3c, W: emptyBuf}, }, } dev, err := NewI2C(&bus, &DefaultOpts) @@ -212,12 +263,36 @@ func TestI2C_Draw_fail(t *testing.T) { } func TestI2C_DrawGray(t *testing.T) { + buf := append([]byte{i2cData}, grayCheckboard()...) + bus := i2ctest.Playback{ Ops: []i2ctest.IO{ // Startup initialization. {Addr: 0x3c, W: initCmdI2C()}, - // Actual draw buffer. - {Addr: 0x3c, W: append([]byte{i2cData}, grayCheckboard()...)}, + // Page 1 + {Addr: 0x3c, W: []byte{0x00, 0xB0, 0x00, 0x10}}, + {Addr: 0x3c, W: buf}, + // Page 2 + {Addr: 0x3c, W: []byte{0x00, 0xB1, 0x00, 0x10}}, + {Addr: 0x3c, W: buf}, + // Page 3 + {Addr: 0x3c, W: []byte{0x00, 0xB2, 0x00, 0x10}}, + {Addr: 0x3c, W: buf}, + // Page 4 + {Addr: 0x3c, W: []byte{0x00, 0xB3, 0x00, 0x10}}, + {Addr: 0x3c, W: buf}, + // Page 5 + {Addr: 0x3c, W: []byte{0x00, 0xB4, 0x00, 0x10}}, + {Addr: 0x3c, W: buf}, + // Page 6 + {Addr: 0x3c, W: []byte{0x00, 0xB5, 0x00, 0x10}}, + {Addr: 0x3c, W: buf}, + // Page 7 + {Addr: 0x3c, W: []byte{0x00, 0xB6, 0x00, 0x10}}, + {Addr: 0x3c, W: buf}, + // Page 8 + {Addr: 0x3c, W: []byte{0x00, 0xB7, 0x00, 0x10}}, + {Addr: 0x3c, W: buf}, }, } dev, err := NewI2C(&bus, &DefaultOpts) @@ -402,8 +477,8 @@ func TestSPI_4wire_String(t *testing.T) { } func TestSPI_4wire_Write_differential(t *testing.T) { - buf1 := make([]byte, 1024) - buf1[130] = 1 + buf1 := make([]byte, 128) + buf1[29] = 1 buf2 := make([]byte, 128) buf2[130-128] = 1 buf2[131-128] = 2 @@ -411,10 +486,35 @@ func TestSPI_4wire_Write_differential(t *testing.T) { Playback: conntest.Playback{ Ops: []conntest.IO{ {W: getInitCmd(&Opts{W: 128, H: 64, Rotated: false})}, + + // Page 1 + {W: []byte{0xB0, 0x00, 0x10}}, {W: buf1}, - // Reset to write only to the first page. - {W: []byte{0x21, 0x0, 0x7f, 0x22, 0x1, 0x1}}, - {W: buf2}, + // Page 2 + {W: []byte{0xB1, 0x00, 0x10}}, + {W: make([]byte, 128)}, + // Page 3 + {W: []byte{0xB2, 0x00, 0x10}}, + {W: make([]byte, 128)}, + // Page 4 + {W: []byte{0xB3, 0x00, 0x10}}, + {W: make([]byte, 128)}, + // Page 5 + {W: []byte{0xB4, 0x00, 0x10}}, + {W: make([]byte, 128)}, + // Page 6 + {W: []byte{0xB5, 0x00, 0x10}}, + {W: make([]byte, 128)}, + // Page 7 + {W: []byte{0xB6, 0x00, 0x10}}, + {W: make([]byte, 128)}, + // Page 8 + {W: []byte{0xB7, 0x00, 0x10}}, + {W: make([]byte, 128)}, + + // Only write to column 3 of page 1 + {W: []byte{0xB1, 0x03, 0x10}}, + {W: []byte{0x02}}, }, }, } @@ -423,7 +523,7 @@ func TestSPI_4wire_Write_differential(t *testing.T) { t.Fatal(err) } pix := make([]byte, 1024) - pix[130] = 1 + pix[29] = 1 if n, err := dev.Write(pix); n != len(pix) || err != nil { t.Fatal(n, err) } @@ -437,13 +537,36 @@ func TestSPI_4wire_Write_differential(t *testing.T) { } func TestSPI_4wire_Write_differential_fail(t *testing.T) { - buf1 := make([]byte, 1024) - buf1[130] = 1 + buf1 := make([]byte, 128) + buf1[29] = 1 port := spitest.Playback{ Playback: conntest.Playback{ Ops: []conntest.IO{ {W: getInitCmd(&Opts{W: 128, H: 64, Rotated: false})}, + // Page 1 + {W: []byte{0xB0, 0x00, 0x10}}, {W: buf1}, + // Page 2 + {W: []byte{0xB1, 0x00, 0x10}}, + {W: make([]byte, 128)}, + // Page 3 + {W: []byte{0xB2, 0x00, 0x10}}, + {W: make([]byte, 128)}, + // Page 4 + {W: []byte{0xB3, 0x00, 0x10}}, + {W: make([]byte, 128)}, + // Page 5 + {W: []byte{0xB4, 0x00, 0x10}}, + {W: make([]byte, 128)}, + // Page 6 + {W: []byte{0xB5, 0x00, 0x10}}, + {W: make([]byte, 128)}, + // Page 7 + {W: []byte{0xB6, 0x00, 0x10}}, + {W: make([]byte, 128)}, + // Page 8 + {W: []byte{0xB7, 0x00, 0x10}}, + {W: make([]byte, 128)}, }, DontPanic: true, }, @@ -453,11 +576,11 @@ func TestSPI_4wire_Write_differential_fail(t *testing.T) { t.Fatal(err) } pix := make([]byte, 1024) - pix[130] = 1 + pix[29] = 1 if n, err := dev.Write(pix); n != len(pix) || err != nil { t.Fatal(n, err) } - pix[131] = 2 + pix[29] = 2 if n, err := dev.Write(pix); n != 0 || !conntest.IsErr(err) { t.Fatalf("expected conntest error: %v", err) } @@ -529,7 +652,7 @@ func getI2CPlayback() *i2ctest.Playback { } func grayCheckboard() []byte { - buf := make([]byte, 1024) + buf := make([]byte, 128) for i := range buf { if i&1 == 0 { buf[i] = 0xaa