427 lines
12 KiB
Go
427 lines
12 KiB
Go
package spi
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"image"
|
|
"time"
|
|
|
|
"gobot.io/x/gobot"
|
|
"gobot.io/x/gobot/drivers/gpio"
|
|
)
|
|
|
|
const (
|
|
// default values
|
|
ssd1306Width = 128
|
|
ssd1306Height = 64
|
|
ssd1306DcPin = "16" // for raspberry pi
|
|
ssd1306RstPin = "18" // for raspberry pi
|
|
ssd1306ExternalVcc = false
|
|
ssd1306SetStartLine = 0x40
|
|
// fundamental commands
|
|
ssd1306SetContrast = 0x81
|
|
ssd1306DisplayOnResumeToRAM = 0xA4
|
|
ssd1306DisplayOnResume = 0xA5
|
|
ssd1306SetDisplayNormal = 0xA6
|
|
ssd1306SetDisplayInverse = 0xA7
|
|
ssd1306SetDisplayOff = 0xAE
|
|
ssd1306SetDisplayOn = 0xAF
|
|
// scrolling commands
|
|
ssd1306RightHorizontalScroll = 0x26
|
|
ssd1306LeftHorizontalScroll = 0x27
|
|
ssd1306VerticalAndRightHorizontalScroll = 0x29
|
|
ssd1306VerticalAndLeftHorizontalScroll = 0x2A
|
|
ssd1306DeactivateScroll = 0x2E
|
|
ssd1306ActivateScroll = 0x2F
|
|
ssd1306SetVerticalScrollArea = 0xA3
|
|
// addressing settings commands
|
|
ssd1306SetMemoryAddressingMode = 0x20
|
|
ssd1306ColumnAddr = 0x21
|
|
ssd1306PageAddr = 0x22
|
|
// hardware configuration commands
|
|
ssd1306SetSegmentRemap0 = 0xA0
|
|
ssd1306SetSegmentRemap127 = 0xA1
|
|
ssd1306SetMultiplexRatio = 0xA8
|
|
ssd1306ComScanInc = 0xC0
|
|
ssd1306ComScanDec = 0xC8
|
|
ssd1306SetDisplayOffset = 0xD3
|
|
ssd1306SetComPins = 0xDA
|
|
// timing and driving scheme commands
|
|
ssd1306SetDisplayClock = 0xD5
|
|
ssd1306SetPrechargePeriod = 0xD9
|
|
ssd1306SetVComDeselectLevel = 0xDB
|
|
ssd1306NOOP = 0xE3
|
|
// charge pump command
|
|
ssd1306ChargePumpSetting = 0x8D
|
|
)
|
|
|
|
// ssd1306Init configures the ssd1306 based on the options passed in when the driver was created
|
|
func (s *SSD1306Driver) ssd1306Init() {
|
|
s.command(ssd1306SetDisplayOff)
|
|
s.command(ssd1306SetDisplayClock)
|
|
if s.DisplayHeight == 16 {
|
|
s.command(0x60)
|
|
} else {
|
|
s.command(0x80)
|
|
}
|
|
s.command(ssd1306SetMultiplexRatio)
|
|
s.command(uint8(s.DisplayHeight) - 1)
|
|
s.command(ssd1306SetDisplayOffset)
|
|
s.command(0x0)
|
|
s.command(ssd1306SetStartLine)
|
|
s.command(0x0)
|
|
s.command(ssd1306ChargePumpSetting)
|
|
if s.ExternalVcc {
|
|
s.command(0x10)
|
|
} else {
|
|
s.command(0x14)
|
|
}
|
|
s.command(ssd1306SetMemoryAddressingMode)
|
|
s.command(0x00)
|
|
s.command(ssd1306SetSegmentRemap0)
|
|
s.command(0x01)
|
|
s.command(ssd1306ComScanInc)
|
|
s.command(ssd1306SetComPins)
|
|
if s.DisplayHeight == 64 {
|
|
s.command(0x12)
|
|
} else {
|
|
s.command(0x02)
|
|
}
|
|
s.command(ssd1306SetContrast)
|
|
if s.DisplayHeight == 64 {
|
|
if s.ExternalVcc {
|
|
s.command(0x9F)
|
|
} else {
|
|
s.command(0xCF)
|
|
}
|
|
} else {
|
|
s.command(0x8F)
|
|
}
|
|
s.command(ssd1306SetPrechargePeriod)
|
|
if s.ExternalVcc {
|
|
s.command(0x22)
|
|
} else {
|
|
s.command(0xF1)
|
|
}
|
|
s.command(ssd1306SetVComDeselectLevel)
|
|
s.command(0x40)
|
|
s.command(ssd1306DisplayOnResumeToRAM)
|
|
s.command(ssd1306SetDisplayNormal)
|
|
s.command(ssd1306DeactivateScroll)
|
|
s.command(ssd1306SetDisplayOn)
|
|
}
|
|
|
|
// DisplayBuffer represents the display buffer intermediate memory
|
|
type DisplayBuffer struct {
|
|
width, height, pageSize int
|
|
buffer []byte
|
|
}
|
|
|
|
// NewDisplayBuffer creates a new DisplayBuffer
|
|
func NewDisplayBuffer(width, height, pageSize int) *DisplayBuffer {
|
|
s := &DisplayBuffer{
|
|
width: width,
|
|
height: height,
|
|
pageSize: pageSize,
|
|
}
|
|
s.buffer = make([]byte, s.Size())
|
|
return s
|
|
}
|
|
|
|
// Size returns the memory size of the display buffer
|
|
func (d *DisplayBuffer) Size() int {
|
|
return (d.width * d.height) / d.pageSize
|
|
}
|
|
|
|
// Clear the contents of the display buffer
|
|
func (d *DisplayBuffer) Clear() {
|
|
d.buffer = make([]byte, d.Size())
|
|
}
|
|
|
|
// SetPixel sets the x, y pixel with c color
|
|
func (d *DisplayBuffer) SetPixel(x, y, c int) {
|
|
idx := x + (y/d.pageSize)*d.width
|
|
bit := uint(y) % uint(d.pageSize)
|
|
if c == 0 {
|
|
d.buffer[idx] &= ^(1 << bit)
|
|
} else {
|
|
d.buffer[idx] |= (1 << bit)
|
|
}
|
|
}
|
|
|
|
// Set sets the display buffer with the given buffer
|
|
func (d *DisplayBuffer) Set(buf []byte) {
|
|
d.buffer = buf
|
|
}
|
|
|
|
// SSD1306Driver is a Gobot Driver for a SSD1306 Display
|
|
type SSD1306Driver struct {
|
|
name string
|
|
connector Connector
|
|
connection Connection
|
|
dcDriver *gpio.DirectPinDriver
|
|
rstDriver *gpio.DirectPinDriver
|
|
pageSize int
|
|
DisplayWidth int
|
|
DisplayHeight int
|
|
DCPin string
|
|
RSTPin string
|
|
ExternalVcc bool
|
|
buffer *DisplayBuffer
|
|
Config
|
|
gobot.Commander
|
|
}
|
|
|
|
// NewSSD1306Driver creates a new SSD1306Driver.
|
|
//
|
|
// Params:
|
|
// conn Connector - the Adaptor to use with this Driver
|
|
//
|
|
// Optional params:
|
|
// spi.WithBus(int): bus to use with this driver
|
|
// spi.WithChip(int): chip to use with this driver
|
|
// spi.WithMode(int): mode to use with this driver
|
|
// spi.WithBits(int): number of bits to use with this driver
|
|
// spi.WithSpeed(int64): speed in Hz to use with this driver
|
|
// spi.WithDisplayWidth(int): width of display (defaults to 128)
|
|
// spi.WithDisplayHeight(int): height of display (defaults to 64)
|
|
// spi.WithDCPin(string): gpio pin number connected to dc pin on display (defaults to "16")
|
|
// spi.WithRstPin(string): gpio pin number connected to rst pin on display (defaults to "18")
|
|
// spi.WithExternalVCC(bool): set to true if using external vcc (defaults to false)
|
|
//
|
|
func NewSSD1306Driver(a gobot.Adaptor, options ...func(Config)) *SSD1306Driver {
|
|
// cast adaptor to spi connector since we also need the adaptor for gpio
|
|
b, ok := a.(Connector)
|
|
if !ok {
|
|
panic("unable to get gobot connector for ssd1306")
|
|
}
|
|
s := &SSD1306Driver{
|
|
name: gobot.DefaultName("SSD1306"),
|
|
Commander: gobot.NewCommander(),
|
|
connector: b,
|
|
DisplayWidth: ssd1306Width,
|
|
DisplayHeight: ssd1306Height,
|
|
DCPin: ssd1306DcPin,
|
|
RSTPin: ssd1306RstPin,
|
|
ExternalVcc: ssd1306ExternalVcc,
|
|
Config: NewConfig(),
|
|
}
|
|
for _, option := range options {
|
|
option(s)
|
|
}
|
|
s.dcDriver = gpio.NewDirectPinDriver(a, s.DCPin)
|
|
s.rstDriver = gpio.NewDirectPinDriver(a, s.RSTPin)
|
|
s.pageSize = s.DisplayHeight / 8
|
|
s.buffer = NewDisplayBuffer(s.DisplayWidth, s.DisplayHeight, s.pageSize)
|
|
s.AddCommand("Display", func(params map[string]interface{}) interface{} {
|
|
err := s.Display()
|
|
return map[string]interface{}{"err": err}
|
|
})
|
|
s.AddCommand("On", func(params map[string]interface{}) interface{} {
|
|
err := s.On()
|
|
return map[string]interface{}{"err": err}
|
|
})
|
|
s.AddCommand("Off", func(params map[string]interface{}) interface{} {
|
|
err := s.Off()
|
|
return map[string]interface{}{"err": err}
|
|
})
|
|
s.AddCommand("Clear", func(params map[string]interface{}) interface{} {
|
|
err := s.Clear()
|
|
return map[string]interface{}{"err": err}
|
|
})
|
|
s.AddCommand("SetContrast", func(params map[string]interface{}) interface{} {
|
|
contrast := byte(params["contrast"].(byte))
|
|
err := s.SetContrast(contrast)
|
|
return map[string]interface{}{"err": err}
|
|
})
|
|
s.AddCommand("Set", func(params map[string]interface{}) interface{} {
|
|
x := int(params["x"].(int))
|
|
y := int(params["y"].(int))
|
|
c := int(params["c"].(int))
|
|
s.Set(x, y, c)
|
|
return nil
|
|
})
|
|
return s
|
|
}
|
|
|
|
// Name returns the Name for the Driver
|
|
func (s *SSD1306Driver) Name() string { return s.name }
|
|
|
|
// SetName sets the Name for the Driver
|
|
func (s *SSD1306Driver) SetName(n string) { s.name = n }
|
|
|
|
// Connection returns the connection for the Driver
|
|
func (s *SSD1306Driver) Connection() gobot.Connection { return s.connector.(gobot.Connection) }
|
|
|
|
// Start sets up the needed connection, and initialized the device.
|
|
func (s *SSD1306Driver) Start() (err error) {
|
|
bus := s.GetBusOrDefault(s.connector.GetSpiDefaultBus())
|
|
chip := s.GetChipOrDefault(s.connector.GetSpiDefaultChip())
|
|
mode := s.GetModeOrDefault(s.connector.GetSpiDefaultMode())
|
|
bits := s.GetBitsOrDefault(s.connector.GetSpiDefaultBits())
|
|
maxSpeed := s.GetSpeedOrDefault(s.connector.GetSpiDefaultMaxSpeed())
|
|
|
|
s.connection, err = s.connector.GetSpiConnection(bus, chip, mode, bits, maxSpeed)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.ssd1306Init()
|
|
return
|
|
}
|
|
|
|
// Halt returns true if device is halted successfully.
|
|
func (s *SSD1306Driver) Halt() (err error) {
|
|
s.Reset()
|
|
s.Off()
|
|
return nil
|
|
}
|
|
|
|
// WithDisplayWidth option sets the SSD1306Driver DisplayWidth option.
|
|
func WithDisplayWidth(val int) func(Config) {
|
|
return func(c Config) {
|
|
d, ok := c.(*SSD1306Driver)
|
|
if ok {
|
|
d.DisplayWidth = val
|
|
} else {
|
|
panic("unable to set display width for ssd1306")
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithDisplayHeight option sets the SSD1306Driver DisplayHeight option.
|
|
func WithDisplayHeight(val int) func(Config) {
|
|
return func(c Config) {
|
|
d, ok := c.(*SSD1306Driver)
|
|
if ok {
|
|
d.DisplayHeight = val
|
|
} else {
|
|
panic("unable to set display height for ssd1306")
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithDCPin option sets the SSD1306Driver DC Pin option.
|
|
func WithDCPin(val string) func(Config) {
|
|
return func(c Config) {
|
|
d, ok := c.(*SSD1306Driver)
|
|
if ok {
|
|
d.DCPin = val
|
|
} else {
|
|
panic("unable to set dc pin for ssd1306")
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithRstPin option sets the SSD1306Driver RST pin option.
|
|
func WithRstPin(val string) func(Config) {
|
|
return func(c Config) {
|
|
d, ok := c.(*SSD1306Driver)
|
|
if ok {
|
|
d.RSTPin = val
|
|
} else {
|
|
panic("unable to set rst pin for ssd1306")
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithExternalVCC option sets the SSD1306Driver external vcc option.
|
|
func WithExternalVCC(val bool) func(Config) {
|
|
return func(c Config) {
|
|
d, ok := c.(*SSD1306Driver)
|
|
if ok {
|
|
d.ExternalVcc = val
|
|
} else {
|
|
panic("unable to set rst pin for ssd1306")
|
|
}
|
|
}
|
|
}
|
|
|
|
// On turns on the display.
|
|
func (s *SSD1306Driver) On() (err error) {
|
|
return s.command(ssd1306SetDisplayOn)
|
|
}
|
|
|
|
// Off turns off the display.
|
|
func (s *SSD1306Driver) Off() (err error) {
|
|
return s.command(ssd1306SetDisplayOff)
|
|
}
|
|
|
|
// Clear clears the display buffer.
|
|
func (s *SSD1306Driver) Clear() (err error) {
|
|
s.buffer.Clear()
|
|
return nil
|
|
}
|
|
|
|
// Set sets a pixel in the display buffer.
|
|
func (s *SSD1306Driver) Set(x, y, c int) {
|
|
s.buffer.SetPixel(x, y, c)
|
|
}
|
|
|
|
// Reset re-initializes the device to a clean state.
|
|
func (s *SSD1306Driver) Reset() (err error) {
|
|
s.rstDriver.DigitalWrite(1)
|
|
time.Sleep(10 * time.Millisecond)
|
|
s.rstDriver.DigitalWrite(0)
|
|
time.Sleep(10 * time.Millisecond)
|
|
s.rstDriver.DigitalWrite(1)
|
|
return nil
|
|
}
|
|
|
|
// SetBufferAndDisplay sets the display buffer with the given buffer and displays the image.
|
|
func (s *SSD1306Driver) SetBufferAndDisplay(buf []byte) (err error) {
|
|
s.buffer.Set(buf)
|
|
return s.Display()
|
|
}
|
|
|
|
// SetContrast sets the display contrast (0-255).
|
|
func (s *SSD1306Driver) SetContrast(contrast byte) (err error) {
|
|
if contrast < 0 || contrast > 255 {
|
|
return fmt.Errorf("contrast value must be between 0-255")
|
|
}
|
|
if err = s.command(ssd1306SetContrast); err != nil {
|
|
return err
|
|
}
|
|
return s.command(contrast)
|
|
}
|
|
|
|
// Display sends the memory buffer to the display.
|
|
func (s *SSD1306Driver) Display() (err error) {
|
|
s.command(ssd1306ColumnAddr)
|
|
s.command(0)
|
|
s.command(uint8(s.DisplayWidth) - 1)
|
|
s.command(ssd1306PageAddr)
|
|
s.command(0)
|
|
s.command(uint8(s.pageSize) - 1)
|
|
if err = s.dcDriver.DigitalWrite(1); err != nil {
|
|
return err
|
|
}
|
|
return s.connection.Tx(append([]byte{0x40}, s.buffer.buffer...), nil)
|
|
}
|
|
|
|
// ShowImage takes a standard Go image and shows it on the display in monochrome.
|
|
func (s *SSD1306Driver) ShowImage(img image.Image) (err error) {
|
|
if img.Bounds().Dx() != s.DisplayWidth || img.Bounds().Dy() != s.DisplayHeight {
|
|
return errors.New("Image must match the display width and height")
|
|
}
|
|
|
|
s.Clear()
|
|
for y, w, h := 0, img.Bounds().Dx(), img.Bounds().Dy(); y < h; y++ {
|
|
for x := 0; x < w; x++ {
|
|
c := img.At(x, y)
|
|
if r, g, b, _ := c.RGBA(); r > 0 || g > 0 || b > 0 {
|
|
s.Set(x, y, 1)
|
|
}
|
|
}
|
|
}
|
|
return s.Display()
|
|
}
|
|
|
|
// command sends a unique command
|
|
func (s *SSD1306Driver) command(b byte) (err error) {
|
|
if err = s.dcDriver.DigitalWrite(0); err != nil {
|
|
return err
|
|
}
|
|
err = s.connection.Tx([]byte{b}, nil)
|
|
return err
|
|
}
|