mirror of
https://github.com/hybridgroup/gobot.git
synced 2025-04-29 13:49:14 +08:00
428 lines
12 KiB
Go
428 lines
12 KiB
Go
package i2c
|
|
|
|
import (
|
|
"fmt"
|
|
"image"
|
|
|
|
"gobot.io/x/gobot"
|
|
)
|
|
|
|
// register addresses for the ssd1306
|
|
const (
|
|
// default values
|
|
ssd1306Width = 128
|
|
ssd1306Height = 64
|
|
ssd1306ExternalVCC = false
|
|
ssd1306SetStartLine = 0x40
|
|
ssd1306I2CAddress = 0x3c
|
|
// fundamental commands
|
|
ssd1306SetComOutput0 = 0xC0
|
|
ssd1306SetComOutput1 = 0xC1
|
|
ssd1306SetComOutput2 = 0xC2
|
|
ssd1306SetComOutput3 = 0xC3
|
|
ssd1306SetComOutput4 = 0xC4
|
|
ssd1306SetComOutput5 = 0xC5
|
|
ssd1306SetComOutput6 = 0xC6
|
|
ssd1306SetComOutput7 = 0xC7
|
|
ssd1306SetComOutput8 = 0xC8
|
|
ssd1306SetContrast = 0x81
|
|
// scrolling commands
|
|
ssd1306ContinuousHScrollRight = 0x26
|
|
ssd1306ContinuousHScrollLeft = 0x27
|
|
ssd1306ContinuousVHScrollRight = 0x29
|
|
ssd1306ContinuousVHScrollLeft = 0x2A
|
|
ssd1306StopScroll = 0x2E
|
|
ssd1306StartScroll = 0x2F
|
|
// adressing settings commands
|
|
ssd1306SetMemoryAddressingMode = 0x20
|
|
ssd1306ColumnAddr = 0x21
|
|
ssd1306PageAddr = 0x22
|
|
// hardware configuration commands
|
|
ssd1306SetSegmentRemap0 = 0xA0
|
|
ssd1306SetSegmentRemap127 = 0xA1
|
|
ssd1306DisplayOnResumeToRAM = 0xA4
|
|
ssd1306SetDisplayNormal = 0xA6
|
|
ssd1306SetDisplayInverse = 0xA7
|
|
ssd1306SetDisplayOff = 0xAE
|
|
ssd1306SetDisplayOn = 0xAF
|
|
// timing and driving scheme commands
|
|
ssd1306SetDisplayClock = 0xD5
|
|
ssd1306SetPrechargePeriod = 0xD9
|
|
ssd1306SetVComDeselectLevel = 0xDB
|
|
ssd1306SetMultiplexRatio = 0xA8
|
|
ssd1306SetComPins = 0xDA
|
|
ssd1306SetDisplayOffset = 0xD3
|
|
// charge pump command
|
|
ssd1306ChargePumpSetting = 0x8D
|
|
)
|
|
|
|
// SSD1306Init contains the initialization settings for the ssd1306 display.
|
|
type SSD1306Init struct {
|
|
displayClock byte
|
|
multiplexRatio byte
|
|
displayOffset byte
|
|
startLine byte
|
|
chargePumpSetting byte
|
|
memoryAddressingMode byte
|
|
comPins byte
|
|
contrast byte
|
|
prechargePeriod byte
|
|
vComDeselectLevel byte
|
|
}
|
|
|
|
// GetSequence returns the initialization sequence for the ssd1306 display.
|
|
func (i *SSD1306Init) GetSequence() []byte {
|
|
return []byte{
|
|
ssd1306SetDisplayNormal,
|
|
ssd1306SetDisplayOff,
|
|
ssd1306SetDisplayClock, i.displayClock,
|
|
ssd1306SetMultiplexRatio, i.multiplexRatio,
|
|
ssd1306SetDisplayOffset, i.displayOffset,
|
|
ssd1306SetStartLine | i.startLine,
|
|
ssd1306ChargePumpSetting, i.chargePumpSetting,
|
|
ssd1306SetMemoryAddressingMode, i.memoryAddressingMode,
|
|
ssd1306SetSegmentRemap0,
|
|
ssd1306SetComOutput0,
|
|
ssd1306SetComPins, i.comPins,
|
|
ssd1306SetContrast, i.contrast,
|
|
ssd1306SetPrechargePeriod, i.prechargePeriod,
|
|
ssd1306SetVComDeselectLevel, i.vComDeselectLevel,
|
|
ssd1306DisplayOnResumeToRAM,
|
|
ssd1306SetDisplayNormal,
|
|
}
|
|
}
|
|
|
|
// 128x64 init sequence
|
|
var ssd1306Init128x64 = &SSD1306Init{
|
|
displayClock: 0x80,
|
|
multiplexRatio: 0x3F,
|
|
displayOffset: 0x00,
|
|
startLine: 0x00,
|
|
chargePumpSetting: 0x14, // 0x10 if external vcc is set
|
|
memoryAddressingMode: 0x00,
|
|
comPins: 0x12,
|
|
contrast: 0xCF, // 0x9F if external vcc is set
|
|
prechargePeriod: 0xF1, // 0x22 if external vcc is set
|
|
vComDeselectLevel: 0x40,
|
|
}
|
|
|
|
// 128x32 init sequence
|
|
var ssd1306Init128x32 = &SSD1306Init{
|
|
displayClock: 0x80,
|
|
multiplexRatio: 0x1F,
|
|
displayOffset: 0x00,
|
|
startLine: 0x00,
|
|
chargePumpSetting: 0x14, // 0x10 if external vcc is set
|
|
memoryAddressingMode: 0x00,
|
|
comPins: 0x02,
|
|
contrast: 0x8F, // 0x9F if external vcc is set
|
|
prechargePeriod: 0xF1, // 0x22 if external vcc is set
|
|
vComDeselectLevel: 0x40,
|
|
}
|
|
|
|
// 96x16 init sequence
|
|
var ssd1306Init96x16 = &SSD1306Init{
|
|
displayClock: 0x60,
|
|
multiplexRatio: 0x0F,
|
|
displayOffset: 0x00,
|
|
startLine: 0x00,
|
|
chargePumpSetting: 0x14, // 0x10 if external vcc is set
|
|
memoryAddressingMode: 0x00,
|
|
comPins: 0x02,
|
|
contrast: 0x8F, // 0x9F if external vcc is set
|
|
prechargePeriod: 0xF1, // 0x22 if external vcc is set
|
|
vComDeselectLevel: 0x40,
|
|
}
|
|
|
|
// 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 {
|
|
d := &DisplayBuffer{
|
|
width: width,
|
|
height: height,
|
|
pageSize: pageSize,
|
|
}
|
|
d.buffer = make([]byte, d.Size())
|
|
return d
|
|
}
|
|
|
|
// 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
|
|
Config
|
|
gobot.Commander
|
|
initSequence *SSD1306Init
|
|
displayWidth int
|
|
displayHeight int
|
|
externalVCC bool
|
|
pageSize int
|
|
buffer *DisplayBuffer
|
|
}
|
|
|
|
// NewSSD1306Driver creates a new SSD1306Driver.
|
|
//
|
|
// Params:
|
|
// conn Connector - the Adaptor to use with this Driver
|
|
//
|
|
// Optional params:
|
|
// WithBus(int): bus to use with this driver
|
|
// WithAddress(int): address to use with this driver
|
|
// WithSSD1306DisplayWidth(int): width of display (defaults to 128)
|
|
// WithSSD1306DisplayHeight(int): height of display (defaults to 64)
|
|
// WithSSD1306ExternalVCC: set true when using an external OLED supply (defaults to false)
|
|
//
|
|
func NewSSD1306Driver(a Connector, options ...func(Config)) *SSD1306Driver {
|
|
s := &SSD1306Driver{
|
|
name: gobot.DefaultName("SSD1306"),
|
|
Commander: gobot.NewCommander(),
|
|
connector: a,
|
|
Config: NewConfig(),
|
|
displayHeight: ssd1306Height,
|
|
displayWidth: ssd1306Width,
|
|
externalVCC: ssd1306ExternalVCC,
|
|
}
|
|
// set options
|
|
for _, option := range options {
|
|
option(s)
|
|
}
|
|
// set page size
|
|
s.pageSize = 8
|
|
// set display buffer
|
|
s.buffer = NewDisplayBuffer(s.displayWidth, s.displayHeight, s.pageSize)
|
|
// add commands
|
|
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{} {
|
|
s.Clear()
|
|
return map[string]interface{}{}
|
|
})
|
|
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 starts the Driver up, and writes start command
|
|
func (s *SSD1306Driver) Start() (err error) {
|
|
// check device size for supported resolutions
|
|
switch {
|
|
case s.displayWidth == 128 && s.displayHeight == 64:
|
|
s.initSequence = ssd1306Init128x64
|
|
case s.displayWidth == 128 && s.displayHeight == 32:
|
|
s.initSequence = ssd1306Init128x32
|
|
case s.displayWidth == 96 && s.displayHeight == 16:
|
|
s.initSequence = ssd1306Init96x16
|
|
default:
|
|
return fmt.Errorf("%dx%d resolution is unsupported, supported resolutions: 128x64, 128x32, 96x16", s.displayWidth, s.displayHeight)
|
|
}
|
|
// check for external vcc
|
|
if s.externalVCC {
|
|
s.initSequence.chargePumpSetting = 0x10
|
|
s.initSequence.contrast = 0x9F
|
|
s.initSequence.prechargePeriod = 0x22
|
|
}
|
|
bus := s.GetBusOrDefault(s.connector.GetDefaultBus())
|
|
address := s.GetAddressOrDefault(ssd1306I2CAddress)
|
|
s.connection, err = s.connector.GetConnection(address, bus)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = s.Init(); err != nil {
|
|
return err
|
|
}
|
|
if err = s.On(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Halt returns true if device is halted successfully
|
|
func (s *SSD1306Driver) Halt() (err error) { return nil }
|
|
|
|
// WithSSD1306DisplayWidth option sets the SSD1306Driver DisplayWidth option.
|
|
func WithSSD1306DisplayWidth(val int) func(Config) {
|
|
return func(c Config) {
|
|
d, ok := c.(*SSD1306Driver)
|
|
if ok {
|
|
d.displayWidth = val
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithSSD1306DisplayHeight option sets the SSD1306Driver DisplayHeight option.
|
|
func WithSSD1306DisplayHeight(val int) func(Config) {
|
|
return func(c Config) {
|
|
d, ok := c.(*SSD1306Driver)
|
|
if ok {
|
|
d.displayHeight = val
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithSSD1306ExternalVCC option sets the SSD1306Driver ExternalVCC option.
|
|
func WithSSD1306ExternalVCC(val bool) func(Config) {
|
|
return func(c Config) {
|
|
d, ok := c.(*SSD1306Driver)
|
|
if ok {
|
|
d.externalVCC = val
|
|
}
|
|
}
|
|
}
|
|
|
|
// Init initializes the ssd1306 display.
|
|
func (s *SSD1306Driver) Init() (err error) {
|
|
// turn off screen
|
|
if err = s.Off(); err != nil {
|
|
return err
|
|
}
|
|
// run through initialization commands
|
|
if err = s.commands(s.initSequence.GetSequence()); err != nil {
|
|
return err
|
|
}
|
|
if err = s.commands([]byte{ssd1306ColumnAddr, 0, byte(s.buffer.width) - 1}); err != nil {
|
|
return err
|
|
}
|
|
if err = s.commands([]byte{ssd1306PageAddr, 0, (byte(s.buffer.height / s.pageSize)) - 1}); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// 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() {
|
|
s.buffer.Clear()
|
|
}
|
|
|
|
// Set sets a pixel in the buffer.
|
|
func (s *SSD1306Driver) Set(x, y, c int) {
|
|
s.buffer.SetPixel(x, y, c)
|
|
}
|
|
|
|
// Reset clears display.
|
|
func (s *SSD1306Driver) Reset() (err error) {
|
|
if err = s.Off(); err != nil {
|
|
return err
|
|
}
|
|
s.Clear()
|
|
if err = s.On(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetContrast sets the display contrast.
|
|
func (s *SSD1306Driver) SetContrast(contrast byte) (err error) {
|
|
err = s.commands([]byte{ssd1306SetContrast, contrast})
|
|
return
|
|
}
|
|
|
|
// Display sends the memory buffer to the display.
|
|
func (s *SSD1306Driver) Display() (err error) {
|
|
_, err = s.connection.Write(append([]byte{0x40}, s.buffer.buffer...))
|
|
return err
|
|
}
|
|
|
|
// ShowImage takes a standard Go image and displays it in monochrome.
|
|
func (s *SSD1306Driver) ShowImage(img image.Image) (err error) {
|
|
if img.Bounds().Dx() != s.displayWidth || img.Bounds().Dy() != s.displayHeight {
|
|
return fmt.Errorf("image must match display width and height: %dx%d", s.displayWidth, s.displayHeight)
|
|
}
|
|
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 command to the ssd1306
|
|
func (s *SSD1306Driver) command(b byte) (err error) {
|
|
_, err = s.connection.Write([]byte{0x80, b})
|
|
return err
|
|
}
|
|
|
|
// commands sends a command sequence to the ssd1306
|
|
func (s *SSD1306Driver) commands(commands []byte) (err error) {
|
|
var command []byte
|
|
for _, d := range commands {
|
|
command = append(command, []byte{0x80, d}...)
|
|
}
|
|
_, err = s.connection.Write(command)
|
|
return err
|
|
}
|