fix ssd1306 i2c commands (#107)

* fix ssd1306 i2c commands
* verified that scrolling works
* added comment that ssd1306 doesn't support SPI yet
pull/1/head
Thorsten von Eicken 9 years ago committed by GitHub
parent a76f663ae2
commit adc0251b5a

@ -9,9 +9,24 @@
// Changing between protocol is likely done through resistor soldering, for // Changing between protocol is likely done through resistor soldering, for
// boards that support both. // boards that support both.
// //
// Datasheet // Known issue
//
// The SPI version of this driver is not functional. To interface with the ssd1306
// in 3-wire SPI mode each byte must be transmitted using 9 bits where the 9th bit
// discriminates between command & data. To interface using 4-wire SPI a separate
// gpio is needed to drive a c/d input. Neither of these two mechanisms have been
// implemented yet.
// For more info, see
// https://drive.google.com/file/d/0B5lkVYnewKTGYzhyWWp0clBMR1E/view
// pages 17-18 (8.1.3, 8.1.4).
//
// Datasheets
// //
// https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf // 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
package ssd1306 package ssd1306
// Some have SPI enabled; // Some have SPI enabled;
@ -104,8 +119,6 @@ func newDev(dev io.Writer, w, h int, rotated bool) (*Dev, error) {
} }
d := &Dev{w: dev, W: w, H: h} d := &Dev{w: dev, W: w, H: h}
contrast := byte(0x7F) // (default value)
// 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.
@ -115,38 +128,38 @@ func newDev(dev io.Writer, w, h int, rotated bool) (*Dev, error) {
comScan = 0xC0 comScan = 0xC0
columnAddr = byte(0xA0) columnAddr = byte(0xA0)
} }
// Initialize the device by fully reseting all values. // Initialize the device by fully resetting all values.
// https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf // https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf
// Page 64 has the full recommended flow. // Page 64 has the full recommended flow.
// Page 28 lists all the commands. // Page 28 lists all the commands.
// BUG(maruel): This flow may not recover a controller in a complete // Some values come from the DM-OLED096 datasheet p15.
// corrupted state. Figure out a more resilient startup init code.
init := []byte{ init := []byte{
i2cCmd,
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, 0x12, // Set COM pins hardware configuration; see page 40
0x81, contrast, // Set contrast control 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 reversed bitness i.e. bit set is black) (?) 0xA6, // Set normal display (0xA7 for inverted 0=lit, 1=dark)
0xD5, 0x40, // Set osc frequency and divide ratio; power on reset value is 0x3F. 0xD5, 0x80, // Set osc frequency and divide ratio; power on reset value is 0x3F.
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
// Not sure 0xDB, 0x40, // Set Vcomh deselect level; page 32
0xD9, 0xF1, // Set pre-charge period. 0x20, 0x00, // Set memory addressing mode to horizontal
//0xDB, 0x40, // Set Vcomh deselect level; page 32 0xB0, // Set page start address
0x20, 0x00, // Set memory addressing mode to horizontal (can be page, horizontal or vertical)
0x2E, // Deactivate scroll 0x2E, // Deactivate scroll
0x00 | 0x00, // Set column offset (lower nibble) 0x00, // Set column offset (lower nibble)
0x10 | 0x00, // Set column offset (higher nibble) 0x10, // Set column offset (higher nibble)
0xA8, byte(d.H - 1), // Set multiplex ratio (number of lines to display) 0xA8, byte(d.H - 1), // Set multiplex ratio (number of lines to display)
0xAF, // Display on 0xAF, // Display on
} }
if _, err := d.w.Write(init); err != nil { if _, err := d.w.Write(init); err != nil {
return nil, err return nil, err
} }
return d, nil return d, nil
} }
@ -226,66 +239,88 @@ func (d *Dev) Write(pixels []byte) (int, error) {
return 0, errors.New("ssd1306: invalid pixel stream") return 0, errors.New("ssd1306: invalid pixel stream")
} }
// Run as 2 big transactions to reduce downtime on the bus. Doing with one // Run as 2 big transactions to reduce downtime on the bus.
// transaction doesn't work (?) // First tx is commands, second is data.
// The following commands should not be needed, but then if the ssd1306 gets out of sync
// for some reason the display ends up messed-up. Given the small overhead compared to
// sending all the data might as well reset things a bit.
hdr := []byte{ hdr := []byte{
0x21, 0x00, byte(d.W - 1), // Set column address (Width) i2cCmd,
0x22, 0x00, byte(d.H/8 - 1), // Set page address (Pages) 0xB0, // Set page start addr just in case
0x00, 0x10, // Set column start addr, lower & upper nibble
0x20, 0x00, // Ensure addressing mode is horizontal
0x21, 0x00, byte(d.W - 1), // Set start/end column
0x22, 0x00, byte(d.H/8 - 1), // Set start/end page
} }
if _, err := d.w.Write(hdr); err != nil { if _, err := d.w.Write(hdr); err != nil {
return 0, err return 0, err
} }
if _, err := d.w.Write(append([]byte{0x40}, pixels...)); err != nil {
// Write the data.
if _, err := d.w.Write(append([]byte{i2cData}, pixels...)); err != nil {
return 0, err return 0, err
} }
return len(pixels), nil return len(pixels), nil
} }
// Scroll scrolls the entire. // Scroll scrolls the entire screen.
func (d *Dev) Scroll(o Orientation, rate FrameRate) error { func (d *Dev) Scroll(o Orientation, rate FrameRate) error {
// TODO(maruel): Allow to specify page. // TODO(maruel): Allow to specify page.
// TODO(maruel): Allow to specify offset. // TODO(maruel): Allow to specify offset.
if o == Left || o == Right { if o == Left || o == Right {
// page 28 // page 28
// STOP, <op>, dummy, <start page>, <rate>, <end page>, <dummy>, <dummy>, <ENABLE> // STOP, <op>, dummy, <start page>, <rate>, <end page>, <dummy>, <dummy>, <ENABLE>
_, err := d.w.Write([]byte{0x2E, byte(o), 0x00, 0x00, byte(rate), 0x07, 0x00, 0xFF, 0x2F}) _, err := d.w.Write([]byte{i2cCmd, 0x2E, byte(o), 0x00, 0x00, byte(rate), 0x07, 0x00, 0xFF, 0x2F})
return err return err
} }
// page 29 // page 29
// STOP, <op>, dummy, <start page>, <rate>, <end page>, <offset>, <ENABLE> // STOP, <op>, dummy, <start page>, <rate>, <end page>, <offset>, <ENABLE>
// page 30: 0xA3 permits to set rows for scroll area. // page 30: 0xA3 permits to set rows for scroll area.
_, err := d.w.Write([]byte{0x2E, byte(o), 0x00, 0x00, byte(rate), 0x07, 0x01, 0x2F}) _, err := d.w.Write([]byte{i2cCmd, 0x2E, byte(o), 0x00, 0x00, byte(rate), 0x07, 0x01, 0x2F})
return err return err
} }
// StopScroll stops any scrolling previously set. // StopScroll stops any scrolling previously set.
// //
// It will only take effect after redrawing the ram. // It will only take effect after redrawing the ram.
//
// BUG(maruel): Doesn't work.
func (d *Dev) StopScroll() error { func (d *Dev) StopScroll() error {
_, err := d.w.Write([]byte{0x2E}) _, err := d.w.Write([]byte{i2cCmd, 0x2E})
return err return err
} }
// SetContrast changes the screen contrast. // SetContrast changes the screen contrast.
// //
// BUG(maruel): Doesn't work. // Note: values other than 0xff do not seem useful...
func (d *Dev) SetContrast(level byte) error { func (d *Dev) SetContrast(level byte) error {
_, err := d.w.Write([]byte{0x81, level}) _, err := d.w.Write([]byte{i2cCmd, 0x81, level})
return err return err
} }
// Enable or disable the display. // Enable or disable the display.
//
// BUG(maruel): Doesn't work.
func (d *Dev) Enable(on bool) error { func (d *Dev) Enable(on bool) error {
b := byte(0xAE) b := byte(0xAE)
if on { if on {
b = 0xAF b = 0xAF
} }
_, err := d.w.Write([]byte{b}) _, err := d.w.Write([]byte{i2cCmd, b})
return err return err
} }
// Invert the display (black on white vs white on black).
func (d *Dev) Invert(blackOnWhite bool) error {
b := byte(0xA6)
if blackOnWhite {
b = 0xA7
}
_, err := d.w.Write([]byte{i2cCmd, b})
return err
}
const (
i2cCmd = 0x00 // i2c transaction has stream of command bytes
i2cData = 0x40 // i2c transaction has stream of data bytes
)
var _ devices.Display = &Dev{} var _ devices.Display = &Dev{}

@ -27,7 +27,7 @@ func TestDrawGray(t *testing.T) {
// Startup initialization. // Startup initialization.
{Addr: 0x3c, Write: initline}, {Addr: 0x3c, Write: initline},
// Preparation to draw. // Preparation to draw.
{Addr: 0x3c, Write: []byte{0x21, 0x0, 0x7f, 0x22, 0x0, 0x7}}, {Addr: 0x3c, Write: prelude},
// Actual draw buffer. // Actual draw buffer.
{Addr: 0x3c, Write: grayCheckboardWrite}, {Addr: 0x3c, Write: grayCheckboardWrite},
}, },
@ -49,7 +49,7 @@ func TestDraw1D(t *testing.T) {
bus := i2ctest.Playback{ bus := i2ctest.Playback{
Ops: []i2ctest.IO{ Ops: []i2ctest.IO{
{Addr: 0x3c, Write: initline}, {Addr: 0x3c, Write: initline},
{Addr: 0x3c, Write: []byte{0x21, 0x0, 0x7f, 0x22, 0x0, 0x7}}, {Addr: 0x3c, Write: prelude},
{Addr: 0x3c, Write: grayCheckboardWrite}, {Addr: 0x3c, Write: grayCheckboardWrite},
}, },
} }
@ -119,8 +119,14 @@ func makeGrayCheckboard(r image.Rectangle) image.Image {
} }
var initline = []byte{ var initline = []byte{
0xae, 0xd3, 0x00, 0x40, 0xa1, 0xc8, 0xda, 0x12, 0x81, 0x7f, 0xa4, 0xa6, 0xd5, 0x00,
0x40, 0x8d, 0x14, 0xd9, 0xf1, 0x20, 0x00, 0x2e, 0x00, 0x10, 0xa8, 0x3f, 0xaf, 0xae, 0xd3, 0x00, 0x40, 0xa1, 0xc8, 0xda, 0x12, 0x81, 0xff, 0xa4, 0xa6, 0xd5,
0x80, 0x8d, 0x14, 0xd9, 0xf1, 0xdb, 0x40, 0x20, 0x00, 0xb0, 0x2e, 0x00, 0x10,
0xa8, 0x3f, 0xaf,
}
var prelude = []byte{
0x0, 0xb0, 0x0, 0x10, 0x20, 0x0, 0x21, 0x0, 0x7f, 0x22, 0x0, 0x7,
} }
var grayCheckboardWrite = []byte{ var grayCheckboardWrite = []byte{

Loading…
Cancel
Save