embd/controller/ssd1306/ssd1306.go

237 lines
5.8 KiB
Go

/*
Package ssd1306 allows controlling an SSD1306 OLED controller.
This currently supports only write-only operations and a SPI connection to the controller.
Resources
This library is based on these prior implementations:
https://github.com/adafruit/Adafruit_Python_SSD1306/blob/master/Adafruit_SSD1306/SSD1306.py
https://github.com/kakaryan/i2cssd1306/blob/master/ssd1306.go
Datasheet
https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf
*/
package ssd1306
import (
"github.com/golang/glog"
"github.com/kidoman/embd"
"time"
)
const (
SSD1306_I2C_ADDRESS = 0x3C
SSD1306_SETCONTRAST = 0x81
SSD1306_DISPLAYALLON_RESUME = 0xA4
SSD1306_DISPLAYALLON = 0xA5
SSD1306_NORMALDISPLAY = 0xA6
SSD1306_INVERTDISPLAY = 0xA7
SSD1306_DISPLAYOFF = 0xAE
SSD1306_DISPLAYON = 0xAF
SSD1306_SETDISPLAYOFFSET = 0xD3
SSD1306_SETCOMPINS = 0xDA
SSD1306_SETVCOMDETECT = 0xDB
SSD1306_SETDISPLAYCLOCKDIV = 0xD5
SSD1306_SETPRECHARGE = 0xD9
SSD1306_SETMULTIPLEX = 0xA8
SSD1306_SETLOWCOLUMN = 0x00
SSD1306_SETHIGHCOLUMN = 0x10
SSD1306_SETSTARTLINE = 0x40
SSD1306_MEMORYMODE = 0x20
SSD1306_MEMORYMODE_HORIZ = 0x00
SSD1306_COLUMNADDR = 0x21
SSD1306_PAGEADDR = 0x22
SSD1306_COMSCANINC = 0xC0
SSD1306_COMSCANDEC = 0xC8
SSD1306_SEGREMAP = 0xA0
SSD1306_CHARGEPUMP = 0x8D
SSD1306_EXTERNALVCC = 0x1
SSD1306_SWITCHCAPVCC = 0x2
SSD1306_ACTIVATE_SCROLL = 0x2F
SSD1306_DEACTIVATE_SCROLL = 0x2E
SSD1306_SET_VERTICAL_SCROLL_AREA = 0xA3
SSD1306_RIGHT_HORIZONTAL_SCROLL = 0x26
SSD1306_LEFT_HORIZONTAL_SCROLL = 0x27
SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL = 0x29
SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL = 0x2A
)
const (
memoryMode = SSD1306_MEMORYMODE_HORIZ
)
// SSD1306 represents an instance of an SSD1306 OLED controller.
type SSD1306 struct {
spiBus embd.SPIBus
dcPin embd.DigitalPin
resetPin embd.DigitalPin
vccState byte
width uint
height uint
pages uint
}
// NewSPI creates a new SSD1306 controller connected via the given SPIBus.
// The GPIO digital output pins that are connected to DC and Rst must also be provided.
// Finally the width x height of the OLED must be given where the width is usually 128 and height is either 32 or 64.
func NewSPI(spiBus embd.SPIBus, dcPin, resetPin embd.DigitalPin, width, height uint) (*SSD1306, error) {
controller := &SSD1306{
spiBus: spiBus,
dcPin: dcPin,
resetPin: resetPin,
vccState: SSD1306_SWITCHCAPVCC,
width: width,
height: height,
pages: height / 8,
}
err := controller.reset()
if err != nil {
glog.Errorf("ssd1306: failed to reset: %s", err)
return nil, err
}
err = controller.init()
if err != nil {
glog.Errorf("ssd1306: failed to init: %s", err)
return nil, err
}
return controller, nil
}
func (c *SSD1306) reset() error {
if err := c.resetPin.Write(embd.High); err != nil {
return err
}
time.Sleep(1 * time.Millisecond)
if err := c.resetPin.Write(embd.Low); err != nil {
return err
}
time.Sleep(10 * time.Millisecond)
if err := c.resetPin.Write(embd.High); err != nil {
return err
}
return nil
}
func (c *SSD1306) init() error {
if err := c.command(SSD1306_DISPLAYOFF); err != nil {
return err
}
if err := c.command(SSD1306_SETDISPLAYCLOCKDIV, 0x80); err != nil {
return err
}
if err := c.command(SSD1306_SETMULTIPLEX, 0x3F); err != nil {
return err
}
if err := c.command(SSD1306_SETDISPLAYOFFSET, 0x0); err != nil {
return err
}
if err := c.command(SSD1306_SETSTARTLINE | 0x0); err != nil {
return err
}
if c.vccState == SSD1306_EXTERNALVCC {
if err := c.command(SSD1306_CHARGEPUMP, 0x10); err != nil {
return err
}
} else {
if err := c.command(SSD1306_CHARGEPUMP, 0x14); err != nil {
return err
}
}
if err := c.command(SSD1306_MEMORYMODE, memoryMode); err != nil {
return err
}
if err := c.command(SSD1306_SEGREMAP | 0x1); err != nil {
return err
}
if err := c.command(SSD1306_COMSCANDEC); err != nil {
return err
}
if err := c.command(SSD1306_SETCOMPINS, 0x12); err != nil {
return err
}
if c.vccState == SSD1306_EXTERNALVCC {
if err := c.command(SSD1306_SETCONTRAST, 0x9F); err != nil {
return err
}
} else {
if err := c.command(SSD1306_SETCONTRAST, 0xCF); err != nil {
return err
}
}
if c.vccState == SSD1306_EXTERNALVCC {
if err := c.command(SSD1306_SETPRECHARGE, 0x22); err != nil {
return err
}
} else {
if err := c.command(SSD1306_SETPRECHARGE, 0xF1); err != nil {
return err
}
}
if err := c.command(SSD1306_SETVCOMDETECT, 0x40); err != nil {
return err
}
if err := c.command(SSD1306_DISPLAYALLON_RESUME); err != nil {
return err
}
if err := c.command(SSD1306_NORMALDISPLAY); err != nil {
return err
}
if err := c.command(SSD1306_DISPLAYON); err != nil {
return err
}
return nil
}
func (c *SSD1306) command(cmd ...byte) error {
c.dcPin.Write(embd.Low)
_, err := c.spiBus.Write(cmd)
return err
}
func (c *SSD1306) data(d ...byte) error {
c.dcPin.Write(embd.High)
_, err := c.spiBus.Write(d)
return err
}
// Display sends the given buffer to the controller to "rendered"
func (c *SSD1306) Display(buf Buffer) error {
if err := c.command(SSD1306_COLUMNADDR, 0, byte(c.width-1)); err != nil {
return err
}
if err := c.command(SSD1306_PAGEADDR, 0, byte(c.pages-1)); err != nil {
return err
}
return c.data(buf.Cells()...)
}
// Close turns the display off
func (c *SSD1306) Close() error {
if err := c.command(SSD1306_DISPLAYOFF); err != nil {
return err
}
return nil
}
// NewBuffer creates a buffer that is suitably configured to be used in Display calls.
func (c *SSD1306) NewBuffer() Buffer {
return newBuffer(c.width, c.pages, memoryMode)
}