mirror of https://github.com/kidoman/embd
Merge 6e08978999
into d3d8c0c5c6
This commit is contained in:
commit
f132ac7401
|
@ -0,0 +1,98 @@
|
|||
package ssd1306
|
||||
|
||||
import "errors"
|
||||
|
||||
// Buffer abstracts "drawing" into an 8-row page space for Display calls into an SSD1306.
|
||||
// A suitably-sized instance is created using NewBuffer on an SSD1306 instance.
|
||||
type Buffer interface {
|
||||
On(x, y int) error
|
||||
Off(x, y int) error
|
||||
Set(x, y int, on bool) error
|
||||
FillRect(x, y int, w, h int) error
|
||||
ClearRect(x, y int, w, h int) error
|
||||
Cells() []byte
|
||||
}
|
||||
|
||||
// bufferHoriz is a Buffer that operates in memory mode of SSD1306_MEMORYMODE_HORIZ
|
||||
type bufferHoriz struct {
|
||||
cells []byte
|
||||
width uint
|
||||
pages uint
|
||||
}
|
||||
|
||||
func newBuffer(width, pages uint, memoryMode int) Buffer {
|
||||
switch memoryMode {
|
||||
case SSD1306_MEMORYMODE_HORIZ:
|
||||
return newBufferHoriz(width, pages)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newBufferHoriz(width, pages uint) *bufferHoriz {
|
||||
return &bufferHoriz{
|
||||
width: width,
|
||||
pages: pages,
|
||||
cells: make([]byte, width*pages),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *bufferHoriz) Cells() []byte {
|
||||
return b.cells
|
||||
}
|
||||
|
||||
func (b *bufferHoriz) On(x, y int) error {
|
||||
return b.Set(x, y, true)
|
||||
}
|
||||
|
||||
func (b *bufferHoriz) Off(x, y int) error {
|
||||
return b.Set(x, y, false)
|
||||
}
|
||||
|
||||
func (b *bufferHoriz) Set(x, y int, on bool) error {
|
||||
if uint(x) > b.width {
|
||||
return errors.New("x cannot be greater than buffer width")
|
||||
}
|
||||
page := uint(y) >> 3
|
||||
if page > b.pages {
|
||||
return errors.New("y cannot be greater than buffer height")
|
||||
}
|
||||
|
||||
index := uint(page*b.width) + uint(x)
|
||||
cell := b.cells[index]
|
||||
|
||||
bit := byte(1) << (uint(y) & 0x7)
|
||||
if on {
|
||||
cell |= bit
|
||||
} else {
|
||||
cell &^= bit
|
||||
}
|
||||
|
||||
b.cells[index] = cell
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *bufferHoriz) FillRect(x, y int, w, h int) error {
|
||||
for xi := 0; xi < w; xi++ {
|
||||
for yi := 0; yi < h; yi++ {
|
||||
if err := b.Set(x+xi, y+yi, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *bufferHoriz) ClearRect(x, y int, w, h int) error {
|
||||
for xi := 0; xi < w; xi++ {
|
||||
for yi := 0; yi < h; yi++ {
|
||||
if err := b.Set(x+xi, y+yi, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
package ssd1306
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAllocation(t *testing.T) {
|
||||
buffer := newBuffer(16, 2, memoryMode)
|
||||
if buffer == nil {
|
||||
t.Fatal("buffer shouldn't be nil")
|
||||
}
|
||||
|
||||
if buffer.Cells() == nil {
|
||||
t.Fatal("cells shouldn't be nil")
|
||||
}
|
||||
|
||||
if len(buffer.Cells()) != 32 {
|
||||
t.Error("wrong cell count")
|
||||
}
|
||||
}
|
||||
|
||||
var onTests = []struct {
|
||||
name string
|
||||
x, y int
|
||||
expected []byte
|
||||
}{
|
||||
{name: "top left", x: 0, y: 0, expected: []byte{0x1, 0, 0, 0, 0, 0, 0, 0 /*page*/, 0, 0, 0, 0, 0, 0, 0, 0}},
|
||||
{name: "left top of page 2", x: 0, y: 8, expected: []byte{0, 0, 0, 0, 0, 0, 0, 0 /*page*/, 0x1, 0, 0, 0, 0, 0, 0, 0}},
|
||||
{name: "left row 2", x: 0, y: 1, expected: []byte{1 << 1, 0, 0, 0, 0, 0, 0, 0 /*page*/, 0, 0, 0, 0, 0, 0, 0, 0}},
|
||||
{name: "middle ish", x: 4, y: 3, expected: []byte{0, 0, 0, 0, 1 << 3, 0, 0, 0 /*page*/, 0, 0, 0, 0, 0, 0, 0, 0}},
|
||||
{name: "bottom ish", x: 6, y: 14, expected: []byte{0, 0, 0, 0, 0, 0, 0, 0 /*page*/, 0, 0, 0, 0, 0, 0, 1 << (14 - 8), 0}},
|
||||
}
|
||||
|
||||
func TestBufferHoriz_On(t *testing.T) {
|
||||
width := uint(8)
|
||||
pages := uint(2) // height = 16
|
||||
|
||||
for _, tt := range onTests {
|
||||
// Disabling testing Run use since this needs to build in go 1.6 also
|
||||
//t.Run(tt.name, func(t *testing.T) {
|
||||
buffer := newBufferHoriz(width, pages)
|
||||
|
||||
buffer.On(tt.x, tt.y)
|
||||
|
||||
if !reflect.DeepEqual(buffer.cells, tt.expected) {
|
||||
t.Errorf("%s: wrong cell content, saw %v", tt.name, buffer.cells)
|
||||
}
|
||||
//})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBufferHoriz_Set(t *testing.T) {
|
||||
width := uint(8)
|
||||
pages := uint(2) // height = 16
|
||||
|
||||
for _, tt := range onTests {
|
||||
// Disabling testing Run use since this needs to build in go 1.6 also
|
||||
//t.Run(tt.name, func(t *testing.T) {
|
||||
buffer := newBufferHoriz(width, pages)
|
||||
buffer.Set(tt.x, tt.y, true)
|
||||
|
||||
if !reflect.DeepEqual(buffer.cells, tt.expected) {
|
||||
t.Errorf("%s: wrong cell content, saw %v", tt.name, buffer.cells)
|
||||
}
|
||||
//})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBufferHoriz_FillRect(t *testing.T) {
|
||||
width := uint(8)
|
||||
pages := uint(2) // height = 16
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
x, y int
|
||||
width, height int
|
||||
expected []byte
|
||||
}{
|
||||
{name: "all", x: 0, y: 0, width: 8, height: 16, expected: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}},
|
||||
{name: "top half", x: 0, y: 0, width: 8, height: 8, expected: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0}},
|
||||
{name: "top left", x: 0, y: 0, width: 4, height: 8, expected: []byte{0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
|
||||
{name: "top quarter", x: 0, y: 0, width: int(width), height: 4, expected: []byte{0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0, 0, 0, 0, 0, 0, 0, 0}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
// Disabling testing Run use since this needs to build in go 1.6 also
|
||||
//t.Run(tt.name, func(t *testing.T) {
|
||||
buffer := newBufferHoriz(width, pages)
|
||||
|
||||
buffer.FillRect(tt.x, tt.y, tt.width, tt.height)
|
||||
|
||||
if !reflect.DeepEqual(buffer.cells, tt.expected) {
|
||||
t.Errorf("%s: wrong cell content, saw %v", tt.name, buffer.cells)
|
||||
}
|
||||
//})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBufferHoriz_Off(t *testing.T) {
|
||||
width := uint(8)
|
||||
pages := uint(2) // height = 16
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
x, y int
|
||||
expected []byte
|
||||
}{
|
||||
{name: "top left", x: 0, y: 0, expected: []byte{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}},
|
||||
{name: "bottom of upper page", x: 1, y: 7, expected: []byte{0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}},
|
||||
{name: "top of second page", x: 2, y: 8, expected: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
// Disabling testing Run use since this needs to build in go 1.6 also
|
||||
//t.Run(tt.name, func(t *testing.T) {
|
||||
buffer := newBufferHoriz(width, pages)
|
||||
|
||||
// assumes TestBufferHoriz_FillRect passes
|
||||
buffer.FillRect(0, 0, 8, 16)
|
||||
|
||||
buffer.Off(tt.x, tt.y)
|
||||
|
||||
if !reflect.DeepEqual(buffer.cells, tt.expected) {
|
||||
t.Errorf("%s: wrong cell content, saw %v", tt.name, buffer.cells)
|
||||
}
|
||||
|
||||
//})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
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)
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
package ssd1306
|
||||
|
||||
import (
|
||||
"github.com/kidoman/embd"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type mockSpiBus struct {
|
||||
chunks [][]byte
|
||||
}
|
||||
|
||||
func (s *mockSpiBus) Write(p []byte) (n int, err error) {
|
||||
s.chunks = append(s.chunks, p)
|
||||
return 0, nil
|
||||
}
|
||||
func (s *mockSpiBus) TransferAndReceiveData(dataBuffer []uint8) error { return nil }
|
||||
func (s *mockSpiBus) ReceiveData(len int) ([]uint8, error) { return nil, nil }
|
||||
func (s *mockSpiBus) TransferAndReceiveByte(data byte) (byte, error) { return 0, nil }
|
||||
func (s *mockSpiBus) ReceiveByte() (byte, error) { return 0, nil }
|
||||
func (s *mockSpiBus) Close() error { return nil }
|
||||
|
||||
type mockPin struct {
|
||||
values []int
|
||||
}
|
||||
|
||||
func (p *mockPin) Watch(edge embd.Edge, handler func(embd.DigitalPin)) error { return nil }
|
||||
func (p *mockPin) StopWatching() error { return nil }
|
||||
func (p *mockPin) N() int { return 0 }
|
||||
func (p *mockPin) Write(val int) error {
|
||||
p.values = append(p.values, val)
|
||||
return nil
|
||||
}
|
||||
func (p *mockPin) Read() (int, error) { return 0, nil }
|
||||
func (p *mockPin) TimePulse(state int) (time.Duration, error) { return 0, nil }
|
||||
func (p *mockPin) SetDirection(dir embd.Direction) error { return nil }
|
||||
func (p *mockPin) ActiveLow(b bool) error { return nil }
|
||||
func (p *mockPin) PullUp() error { return p.Write(1) }
|
||||
func (p *mockPin) PullDown() error { return p.Write(0) }
|
||||
func (p *mockPin) Close() error { return nil }
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
|
||||
spiBus := &mockSpiBus{}
|
||||
dcPin := &mockPin{}
|
||||
resetPin := &mockPin{}
|
||||
|
||||
controller, err := NewSPI(spiBus, dcPin, resetPin, 128, 64)
|
||||
if err != nil {
|
||||
t.Fatalf("Shouldn't be an error: %s", err)
|
||||
}
|
||||
if controller == nil {
|
||||
t.Fatal("controller shouldn't be nil")
|
||||
}
|
||||
|
||||
if len(resetPin.values) != 3 {
|
||||
t.Error("expected 3 touches to the reset pin")
|
||||
}
|
||||
|
||||
if len(dcPin.values) != 16 {
|
||||
t.Error("expected 16 touches to the dc pin")
|
||||
}
|
||||
|
||||
if len(spiBus.chunks) != 16 {
|
||||
t.Error("expected 16 commands during init")
|
||||
}
|
||||
i := 0
|
||||
|
||||
if spiBus.chunks[i][0] != SSD1306_DISPLAYOFF {
|
||||
t.Errorf("Wrong command in chunk %d", i)
|
||||
}
|
||||
i++
|
||||
|
||||
if spiBus.chunks[i][0] != SSD1306_SETDISPLAYCLOCKDIV {
|
||||
t.Errorf("Wrong command in chunk %d", i)
|
||||
}
|
||||
i++
|
||||
|
||||
if spiBus.chunks[i][0] != SSD1306_SETMULTIPLEX {
|
||||
t.Errorf("Wrong command in chunk %d", i)
|
||||
}
|
||||
i++
|
||||
|
||||
if spiBus.chunks[i][0] != SSD1306_SETDISPLAYOFFSET {
|
||||
t.Errorf("Wrong command in chunk %d", i)
|
||||
}
|
||||
i++
|
||||
|
||||
if spiBus.chunks[i][0] != SSD1306_SETSTARTLINE|0x0 {
|
||||
t.Errorf("Wrong command in chunk %d", i)
|
||||
}
|
||||
i++
|
||||
|
||||
if spiBus.chunks[i][0] != SSD1306_CHARGEPUMP {
|
||||
t.Errorf("Wrong command in chunk %d", i)
|
||||
}
|
||||
i++
|
||||
|
||||
if spiBus.chunks[i][0] != SSD1306_MEMORYMODE {
|
||||
t.Errorf("Wrong command in chunk %d", i)
|
||||
}
|
||||
i++
|
||||
|
||||
if spiBus.chunks[i][0] != SSD1306_SEGREMAP|0x1 {
|
||||
t.Errorf("Wrong command in chunk %d", i)
|
||||
}
|
||||
i++
|
||||
|
||||
if spiBus.chunks[i][0] != SSD1306_COMSCANDEC {
|
||||
t.Errorf("Wrong command in chunk %d", i)
|
||||
}
|
||||
i++
|
||||
|
||||
if spiBus.chunks[i][0] != SSD1306_SETCOMPINS {
|
||||
t.Errorf("Wrong command in chunk %d", i)
|
||||
}
|
||||
i++
|
||||
|
||||
if spiBus.chunks[i][0] != SSD1306_SETCONTRAST {
|
||||
t.Errorf("Wrong command in chunk %d", i)
|
||||
}
|
||||
i++
|
||||
|
||||
if spiBus.chunks[i][0] != SSD1306_SETPRECHARGE {
|
||||
t.Errorf("Wrong command in chunk %d", i)
|
||||
}
|
||||
i++
|
||||
|
||||
if spiBus.chunks[i][0] != SSD1306_SETVCOMDETECT {
|
||||
t.Errorf("Wrong command in chunk %d", i)
|
||||
}
|
||||
i++
|
||||
|
||||
if spiBus.chunks[i][0] != SSD1306_DISPLAYALLON_RESUME {
|
||||
t.Errorf("Wrong command in chunk %d", i)
|
||||
}
|
||||
i++
|
||||
|
||||
if spiBus.chunks[i][0] != SSD1306_NORMALDISPLAY {
|
||||
t.Errorf("Wrong command in chunk %d", i)
|
||||
}
|
||||
i++
|
||||
|
||||
if spiBus.chunks[i][0] != SSD1306_DISPLAYON {
|
||||
t.Errorf("Wrong command in chunk %d", i)
|
||||
}
|
||||
i++
|
||||
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
// +build ignore
|
||||
|
||||
// This sample runs on a monochrome 128x64 OLED graphic display using an SSD1306 controller,
|
||||
// such as https://www.adafruit.com/product/938.
|
||||
// It demonstrates the rectangular fill/clear and point on/off operations animated across the display.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/golang/glog"
|
||||
"github.com/kidoman/embd"
|
||||
"github.com/kidoman/embd/controller/ssd1306"
|
||||
_ "github.com/kidoman/embd/host/rpi" // This loads the RPi driver
|
||||
|
||||
"flag"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
glog.Info("Starting")
|
||||
|
||||
if err := embd.InitSPI(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer embd.CloseSPI()
|
||||
|
||||
spiBus := embd.NewSPIBus(embd.SPIMode0, 0, 1000000, 8, 0)
|
||||
defer spiBus.Close()
|
||||
|
||||
if err := embd.InitGPIO(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer embd.CloseGPIO()
|
||||
|
||||
dcPin := setupPin("GPIO_23")
|
||||
defer dcPin.Close()
|
||||
resetPin := setupPin("GPIO_24")
|
||||
defer resetPin.Close()
|
||||
|
||||
controller, err := ssd1306.NewSPI(spiBus, dcPin, resetPin, 128, 64)
|
||||
if err != nil {
|
||||
glog.Fatalf("Failed to start: %s", err)
|
||||
}
|
||||
defer controller.Close()
|
||||
|
||||
buffer := controller.NewBuffer()
|
||||
first := true
|
||||
chunkWidth := 32
|
||||
chunkHeight := 20
|
||||
zipDir := 1
|
||||
zipX := 0
|
||||
prevZipX := 0
|
||||
var prevX, prevY int
|
||||
|
||||
// Setup Control-C to gracefully stop
|
||||
done := make(chan os.Signal, 1)
|
||||
signal.Notify(done, os.Interrupt)
|
||||
|
||||
outer:
|
||||
for {
|
||||
|
||||
for y := 0; y+chunkHeight <= 64; y += chunkHeight {
|
||||
for x := 0; x+chunkWidth <= 128; x += chunkWidth {
|
||||
select {
|
||||
case <-done:
|
||||
break outer
|
||||
default:
|
||||
glog.Infof("x=%d, y=%d\n", x, y)
|
||||
if !first {
|
||||
buffer.ClearRect(prevX, prevY, chunkWidth, chunkHeight)
|
||||
} else {
|
||||
first = false
|
||||
}
|
||||
buffer.FillRect(x, y, chunkWidth, chunkHeight)
|
||||
prevX = x
|
||||
prevY = y
|
||||
|
||||
buffer.Off(prevZipX, 63)
|
||||
buffer.On(zipX, 63)
|
||||
prevZipX = zipX
|
||||
zipX += zipDir
|
||||
if zipX >= 128 {
|
||||
zipX = 127
|
||||
zipDir = -1
|
||||
} else if zipX < 0 {
|
||||
zipX = 0
|
||||
zipDir = 1
|
||||
}
|
||||
|
||||
controller.Display(buffer)
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func setupPin(key string) embd.DigitalPin {
|
||||
|
||||
p, err := embd.NewDigitalPin(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := p.SetDirection(embd.Out); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
Loading…
Reference in New Issue