mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +00:00
197 lines
4.6 KiB
Go
197 lines
4.6 KiB
Go
// Package adafruit4650 implements a driver for the Adafruit FeatherWing OLED - 128x64 OLED display.
|
|
// The display is backed itself by a SH1107 driver chip.
|
|
//
|
|
// Store: https://www.adafruit.com/product/4650
|
|
//
|
|
// Documentation: https://learn.adafruit.com/adafruit-128x64-oled-featherwing
|
|
package adafruit4650
|
|
|
|
import (
|
|
"image/color"
|
|
"time"
|
|
|
|
"tinygo.org/x/drivers"
|
|
)
|
|
|
|
const DefaultAddress = 0x3c
|
|
|
|
const (
|
|
commandSetLowColumn = 0x00
|
|
commandSetHighColumn = 0x10
|
|
commandSetPage = 0xb0
|
|
)
|
|
|
|
const (
|
|
width = 128
|
|
height = 64
|
|
)
|
|
|
|
// Device represents an Adafruit 4650 device
|
|
type Device struct {
|
|
bus drivers.I2C
|
|
Address uint8
|
|
buffer []byte
|
|
width int16
|
|
height int16
|
|
}
|
|
|
|
// New creates a new device, not configuring anything yet.
|
|
func New(bus drivers.I2C) Device {
|
|
return Device{
|
|
bus: bus,
|
|
Address: DefaultAddress,
|
|
width: width,
|
|
height: height,
|
|
}
|
|
}
|
|
|
|
// Configure initializes the display with default configuration
|
|
func (d *Device) Configure() error {
|
|
|
|
bufferSize := d.width * d.height / 8
|
|
d.buffer = make([]byte, bufferSize)
|
|
|
|
// This sequence is an amalgamation of the datasheet, official Arduino driver, CircuitPython driver and other drivers
|
|
initSequence := []byte{
|
|
0xae, // display off, sleep mode
|
|
//0xd5, 0x41, // set display clock divider (from original datasheet)
|
|
0xd5, 0x51, // set display clock divider (from Adafruit driver)
|
|
0xd9, 0x22, // pre-charge/dis-charge period mode: 2 DCLKs/2 DCLKs (POR)
|
|
0x20, // memory mode
|
|
0x81, 0x4f, // contrast setting = 0x4f
|
|
0xad, 0x8a, // set dc/dc pump
|
|
0xa0, // segment remap, flip-x
|
|
0xc0, // common output scan direction
|
|
0xdc, 0x00, // set display start line 0 (POR=0)
|
|
0xa8, 0x3f, // multiplex ratio, height - 1 = 0x3f
|
|
0xd3, 0x60, // set display offset mode = 0x60
|
|
0xdb, 0x35, // VCOM deselect level = 0.770 (POR)
|
|
0xa4, // entire display off, retain RAM, normal status (POR)
|
|
0xa6, // normal (not reversed) display
|
|
0xaf, // display on
|
|
}
|
|
|
|
err := d.writeCommands(initSequence)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// recommended in the datasheet, same in other drivers
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
return nil
|
|
}
|
|
|
|
// ClearDisplay clears the image buffer as well as the actual display
|
|
func (d *Device) ClearDisplay() error {
|
|
d.ClearBuffer()
|
|
return d.Display()
|
|
}
|
|
|
|
// ClearBuffer clears the buffer
|
|
func (d *Device) ClearBuffer() {
|
|
bzero(d.buffer)
|
|
}
|
|
|
|
// SetPixel modifies the internal buffer. Since this display has a bit-depth of 1 bit any non-zero
|
|
// color component will be treated as 'on', otherwise 'off'.
|
|
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
|
|
if x < 0 || x >= d.width || y < 0 || y >= d.height {
|
|
return
|
|
}
|
|
|
|
// RAM layout
|
|
// *-----> y
|
|
// |
|
|
// x| col0 col1 ... col63
|
|
// v p0 a0 b0 ..
|
|
// a1 b1 ..
|
|
// .. .. ..
|
|
// a7 b7 ..
|
|
// p1 a0 b0
|
|
// a1 b1
|
|
//
|
|
|
|
//flip y - so the display orientation matches the silk screen labeling etc.
|
|
y = d.height - y - 1
|
|
|
|
page := x / 8
|
|
bytesPerPage := d.height
|
|
byteIndex := y + bytesPerPage*page
|
|
bit := x % 8
|
|
if (c.R | c.G | c.B) != 0 {
|
|
d.buffer[byteIndex] |= 1 << uint8(bit)
|
|
} else {
|
|
d.buffer[byteIndex] &^= 1 << uint8(bit)
|
|
}
|
|
}
|
|
|
|
// Display sends the whole buffer to the screen
|
|
func (d *Device) Display() error {
|
|
|
|
bytesPerPage := d.height
|
|
|
|
pages := (d.width + 7) / 8
|
|
for page := int16(0); page < pages; page++ {
|
|
|
|
err := d.setRAMPosition(uint8(page), 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
offset := page * bytesPerPage
|
|
err = d.writeRAM(d.buffer[offset : offset+bytesPerPage])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// setRAMPosition updates the device's current page and column position
|
|
func (d *Device) setRAMPosition(page uint8, column uint8) error {
|
|
if page > 15 {
|
|
panic("page out of bounds")
|
|
}
|
|
if column > 127 {
|
|
panic("column out of bounds")
|
|
}
|
|
setPage := commandSetPage | (page & 0xF)
|
|
|
|
lo := column & 0xF
|
|
setLowColumn := commandSetLowColumn | lo
|
|
|
|
hi := (column >> 4) & 0x7
|
|
setHighColumn := commandSetHighColumn | hi
|
|
|
|
cmds := []byte{
|
|
setPage,
|
|
setLowColumn,
|
|
setHighColumn,
|
|
}
|
|
|
|
return d.writeCommands(cmds)
|
|
}
|
|
|
|
// Size returns the current size of the display.
|
|
func (d *Device) Size() (w, h int16) {
|
|
return d.width, d.height
|
|
}
|
|
|
|
func (d *Device) writeCommands(commands []byte) error {
|
|
onlyCommandsFollowing := byte(0x00)
|
|
return d.bus.Tx(uint16(d.Address), append([]byte{onlyCommandsFollowing}, commands...), nil)
|
|
}
|
|
|
|
func (d *Device) writeRAM(data []byte) error {
|
|
onlyRAMFollowing := byte(0x40)
|
|
return d.bus.Tx(uint16(d.Address), append([]byte{onlyRAMFollowing}, data...), nil)
|
|
}
|
|
|
|
func bzero(buf []byte) {
|
|
for i := range buf {
|
|
buf[i] = 0
|
|
}
|
|
}
|