mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-17 13:23:26 +00:00
support for SSD1306 displays using I2C or SPI, several screen sizes (#49)
supported too
This commit is contained in:
committed by
Ron Evans
parent
ba4de1efb8
commit
ad2b76be8b
@@ -32,6 +32,8 @@ jobs:
|
|||||||
- run: tinygo build -size short -o test.elf -target=itsybitsy-m0 ./examples/mpu6050/main.go
|
- run: tinygo build -size short -o test.elf -target=itsybitsy-m0 ./examples/mpu6050/main.go
|
||||||
- run: tinygo build -size short -o test.elf -target=microbit ./examples/pcd8544/setbuffer/main.go
|
- run: tinygo build -size short -o test.elf -target=microbit ./examples/pcd8544/setbuffer/main.go
|
||||||
- run: tinygo build -size short -o test.elf -target=microbit ./examples/pcd8544/setpixel/main.go
|
- run: tinygo build -size short -o test.elf -target=microbit ./examples/pcd8544/setpixel/main.go
|
||||||
|
- run: tinygo build -size short -o test.elf -target=microbit ./examples/ssd1306/i2c_128x32/main.go
|
||||||
|
- run: tinygo build -size short -o test.elf -target=microbit ./examples/ssd1306/spi_128x64/main.go
|
||||||
- run: tinygo build -size short -o test.elf -target=circuitplay-express ./examples/thermistor/main.go
|
- run: tinygo build -size short -o test.elf -target=circuitplay-express ./examples/thermistor/main.go
|
||||||
- run: tinygo build -size short -o test.elf -target=itsybitsy-m0 ./examples/vl53l1x/main.go
|
- run: tinygo build -size short -o test.elf -target=itsybitsy-m0 ./examples/vl53l1x/main.go
|
||||||
- run: tinygo build -size short -o test.elf -target=circuitplay-express ./examples/ws2812/main.go
|
- run: tinygo build -size short -o test.elf -target=circuitplay-express ./examples/ws2812/main.go
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ func main() {
|
|||||||
| [MMA8653 accelerometer](https://www.nxp.com/docs/en/data-sheet/MMA8653FC.pdf) | I2C |
|
| [MMA8653 accelerometer](https://www.nxp.com/docs/en/data-sheet/MMA8653FC.pdf) | I2C |
|
||||||
| [MPU6050 accelerometer/gyroscope](https://store.invensense.com/datasheets/invensense/MPU-6050_DataSheet_V3%204.pdf) | I2C |
|
| [MPU6050 accelerometer/gyroscope](https://store.invensense.com/datasheets/invensense/MPU-6050_DataSheet_V3%204.pdf) | I2C |
|
||||||
| [PCD8544 display](http://eia.udg.edu/~forest/PCD8544_1.pdf) | SPI |
|
| [PCD8544 display](http://eia.udg.edu/~forest/PCD8544_1.pdf) | SPI |
|
||||||
|
| [SSD1306 OLED display](https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf) | I2C / SPI |
|
||||||
| [Thermistor](https://www.farnell.com/datasheets/33552.pdf) | ADC |
|
| [Thermistor](https://www.farnell.com/datasheets/33552.pdf) | ADC |
|
||||||
| [VL53L1X time-of-flight distance sensor](https://www.st.com/resource/en/datasheet/vl53l1x.pdf) | I2C |
|
| [VL53L1X time-of-flight distance sensor](https://www.st.com/resource/en/datasheet/vl53l1x.pdf) | I2C |
|
||||||
| [WS2812 RGB LED](https://cdn-shop.adafruit.com/datasheets/WS2812.pdf) | GPIO |
|
| [WS2812 RGB LED](https://cdn-shop.adafruit.com/datasheets/WS2812.pdf) | GPIO |
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package i2c_128x32
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
|
||||||
|
"image/color"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tinygo-org/drivers/ssd1306"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
machine.I2C0.Configure(machine.I2CConfig{
|
||||||
|
Frequency: machine.TWI_FREQ_400KHZ,
|
||||||
|
})
|
||||||
|
|
||||||
|
display := ssd1306.NewI2C(machine.I2C0)
|
||||||
|
display.Configure(ssd1306.Config{
|
||||||
|
Address: ssd1306.Address_128_32,
|
||||||
|
Width: 128,
|
||||||
|
Height: 32,
|
||||||
|
})
|
||||||
|
|
||||||
|
display.ClearDisplay()
|
||||||
|
|
||||||
|
x := int16(0)
|
||||||
|
y := int16(0)
|
||||||
|
deltaX := int16(1)
|
||||||
|
deltaY := int16(1)
|
||||||
|
for {
|
||||||
|
pixel := display.GetPixel(x, y)
|
||||||
|
c := color.RGBA{255, 255, 255, 255}
|
||||||
|
if pixel {
|
||||||
|
c = color.RGBA{0, 0, 0, 255}
|
||||||
|
}
|
||||||
|
display.SetPixel(x, y, c)
|
||||||
|
display.Display()
|
||||||
|
|
||||||
|
x += deltaX
|
||||||
|
y += deltaY
|
||||||
|
|
||||||
|
if x == 0 || x == 127 {
|
||||||
|
deltaX = -deltaX
|
||||||
|
}
|
||||||
|
|
||||||
|
if y == 0 || y == 31 {
|
||||||
|
deltaY = -deltaY
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package i2c_128x64
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tinygo-org/drivers/ssd1306"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
machine.SPI0.Configure(machine.SPIConfig{
|
||||||
|
Frequency: 8000000,
|
||||||
|
})
|
||||||
|
display := ssd1306.NewSPI(machine.SPI0, machine.P8, machine.P7, machine.P9)
|
||||||
|
display.Configure(ssd1306.Config{
|
||||||
|
Width: 128,
|
||||||
|
Height: 64,
|
||||||
|
})
|
||||||
|
|
||||||
|
display.ClearDisplay()
|
||||||
|
|
||||||
|
x := int16(64)
|
||||||
|
y := int16(32)
|
||||||
|
deltaX := int16(1)
|
||||||
|
deltaY := int16(1)
|
||||||
|
for {
|
||||||
|
pixel := display.GetPixel(x, y)
|
||||||
|
c := color.RGBA{255, 255, 255, 255}
|
||||||
|
if pixel {
|
||||||
|
c = color.RGBA{0, 0, 0, 255}
|
||||||
|
}
|
||||||
|
display.SetPixel(x, y, c)
|
||||||
|
display.Display()
|
||||||
|
|
||||||
|
x += deltaX
|
||||||
|
y += deltaY
|
||||||
|
|
||||||
|
if x == 0 || x == 127 {
|
||||||
|
deltaX = -deltaX
|
||||||
|
}
|
||||||
|
|
||||||
|
if y == 0 || y == 63 {
|
||||||
|
deltaY = -deltaY
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package ssd1306
|
||||||
|
|
||||||
|
// Registers
|
||||||
|
const (
|
||||||
|
Address = 0x3D
|
||||||
|
Address_128_32 = 0x3C
|
||||||
|
|
||||||
|
SETCONTRAST = 0x81
|
||||||
|
DISPLAYALLON_RESUME = 0xA4
|
||||||
|
DISPLAYALLON = 0xA5
|
||||||
|
NORMALDISPLAY = 0xA6
|
||||||
|
INVERTDISPLAY = 0xA7
|
||||||
|
DISPLAYOFF = 0xAE
|
||||||
|
DISPLAYON = 0xAF
|
||||||
|
SETDISPLAYOFFSET = 0xD3
|
||||||
|
SETCOMPINS = 0xDA
|
||||||
|
SETVCOMDETECT = 0xDB
|
||||||
|
SETDISPLAYCLOCKDIV = 0xD5
|
||||||
|
SETPRECHARGE = 0xD9
|
||||||
|
SETMULTIPLEX = 0xA8
|
||||||
|
SETLOWCOLUMN = 0x00
|
||||||
|
SETHIGHCOLUMN = 0x10
|
||||||
|
SETSTARTLINE = 0x40
|
||||||
|
MEMORYMODE = 0x20
|
||||||
|
COLUMNADDR = 0x21
|
||||||
|
PAGEADDR = 0x22
|
||||||
|
COMSCANINC = 0xC0
|
||||||
|
COMSCANDEC = 0xC8
|
||||||
|
SEGREMAP = 0xA0
|
||||||
|
CHARGEPUMP = 0x8D
|
||||||
|
ACTIVATE_SCROLL = 0x2F
|
||||||
|
DEACTIVATE_SCROLL = 0x2E
|
||||||
|
SET_VERTICAL_SCROLL_AREA = 0xA3
|
||||||
|
RIGHT_HORIZONTAL_SCROLL = 0x26
|
||||||
|
LEFT_HORIZONTAL_SCROLL = 0x27
|
||||||
|
VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL = 0x29
|
||||||
|
VERTICAL_AND_LEFT_HORIZONTAL_SCROLL = 0x2A
|
||||||
|
|
||||||
|
EXTERNALVCC VccMode = 0x1
|
||||||
|
SWITCHCAPVCC VccMode = 0x2
|
||||||
|
)
|
||||||
@@ -0,0 +1,302 @@
|
|||||||
|
// Package ssd1306 implements a driver for the SSD1306 led matrix controller, it comes in various colors and screen sizes.
|
||||||
|
//
|
||||||
|
// Datasheet: https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf
|
||||||
|
package ssd1306
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"image/color"
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Device wraps an SPI connection.
|
||||||
|
type Device struct {
|
||||||
|
bus Buser
|
||||||
|
buffer []byte
|
||||||
|
width int16
|
||||||
|
height int16
|
||||||
|
bufferSize int16
|
||||||
|
vccState VccMode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config is the configuration for the display
|
||||||
|
type Config struct {
|
||||||
|
Width int16
|
||||||
|
Height int16
|
||||||
|
VccState VccMode
|
||||||
|
Address uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type I2CBus struct {
|
||||||
|
wire machine.I2C
|
||||||
|
Address uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type SPIBus struct {
|
||||||
|
wire machine.SPI
|
||||||
|
dcPin machine.GPIO
|
||||||
|
resetPin machine.GPIO
|
||||||
|
csPin machine.GPIO
|
||||||
|
}
|
||||||
|
|
||||||
|
type Buser interface {
|
||||||
|
configure()
|
||||||
|
tx(data []byte, isCommand bool)
|
||||||
|
setAddress(address uint16)
|
||||||
|
}
|
||||||
|
|
||||||
|
type VccMode uint8
|
||||||
|
|
||||||
|
// NewI2C creates a new SSD1306 connection. The I2C wire must already be configured.
|
||||||
|
func NewI2C(bus machine.I2C) Device {
|
||||||
|
return Device{
|
||||||
|
bus: &I2CBus{
|
||||||
|
wire: bus,
|
||||||
|
Address: Address,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSPI creates a new SSD1306 connection. The SPI wire must already be configured.
|
||||||
|
func NewSPI(bus machine.SPI, dcPin uint8, resetPin uint8, csPin uint8) Device {
|
||||||
|
pinDc := machine.GPIO{dcPin}
|
||||||
|
pinDc.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||||
|
pinReset := machine.GPIO{resetPin}
|
||||||
|
pinReset.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||||
|
pinCs := machine.GPIO{csPin}
|
||||||
|
pinCs.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||||
|
return Device{
|
||||||
|
bus: &SPIBus{
|
||||||
|
wire: bus,
|
||||||
|
dcPin: pinDc,
|
||||||
|
resetPin: pinReset,
|
||||||
|
csPin: pinCs,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure initializes the display with default configuration
|
||||||
|
func (d *Device) Configure(cfg Config) {
|
||||||
|
if cfg.Width != 0 {
|
||||||
|
d.width = cfg.Width
|
||||||
|
} else {
|
||||||
|
d.width = 128
|
||||||
|
}
|
||||||
|
if cfg.Height != 0 {
|
||||||
|
d.height = cfg.Height
|
||||||
|
} else {
|
||||||
|
d.height = 64
|
||||||
|
}
|
||||||
|
if cfg.Address != 0 {
|
||||||
|
d.bus.setAddress(cfg.Address)
|
||||||
|
}
|
||||||
|
if cfg.VccState != 0 {
|
||||||
|
d.vccState = cfg.VccState
|
||||||
|
} else {
|
||||||
|
d.vccState = SWITCHCAPVCC
|
||||||
|
}
|
||||||
|
d.bufferSize = d.width * d.height / 8
|
||||||
|
d.buffer = make([]byte, d.bufferSize)
|
||||||
|
|
||||||
|
d.bus.configure()
|
||||||
|
|
||||||
|
time.Sleep(100 * time.Nanosecond)
|
||||||
|
d.Command(DISPLAYOFF)
|
||||||
|
d.Command(SETDISPLAYCLOCKDIV)
|
||||||
|
d.Command(0x80)
|
||||||
|
d.Command(SETMULTIPLEX)
|
||||||
|
d.Command(uint8(d.height - 1))
|
||||||
|
d.Command(SETDISPLAYOFFSET)
|
||||||
|
d.Command(0x0)
|
||||||
|
d.Command(SETSTARTLINE | 0x0)
|
||||||
|
d.Command(CHARGEPUMP)
|
||||||
|
if d.vccState == EXTERNALVCC {
|
||||||
|
d.Command(0x10)
|
||||||
|
} else {
|
||||||
|
d.Command(0x14)
|
||||||
|
}
|
||||||
|
d.Command(MEMORYMODE)
|
||||||
|
d.Command(0x00)
|
||||||
|
d.Command(SEGREMAP | 0x1)
|
||||||
|
d.Command(COMSCANDEC)
|
||||||
|
|
||||||
|
if (d.width == 128 && d.height == 64) || (d.width == 64 && d.height == 48) { // 128x64 or 64x48
|
||||||
|
d.Command(SETCOMPINS)
|
||||||
|
d.Command(0x12)
|
||||||
|
d.Command(SETCONTRAST)
|
||||||
|
if d.vccState == EXTERNALVCC {
|
||||||
|
d.Command(0x9F)
|
||||||
|
} else {
|
||||||
|
d.Command(0xCF)
|
||||||
|
}
|
||||||
|
} else if d.width == 128 && d.height == 32 { // 128x32
|
||||||
|
d.Command(SETCOMPINS)
|
||||||
|
d.Command(0x02)
|
||||||
|
d.Command(SETCONTRAST)
|
||||||
|
d.Command(0x8F)
|
||||||
|
} else if d.width == 96 && d.height == 16 { // 96x16
|
||||||
|
d.Command(SETCOMPINS)
|
||||||
|
d.Command(0x2)
|
||||||
|
d.Command(SETCONTRAST)
|
||||||
|
if d.vccState == EXTERNALVCC {
|
||||||
|
d.Command(0x10)
|
||||||
|
} else {
|
||||||
|
d.Command(0xAF)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// fail silently, it might work
|
||||||
|
println("there's no configuration for this display's size")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Command(SETPRECHARGE)
|
||||||
|
if d.vccState == EXTERNALVCC {
|
||||||
|
d.Command(0x22)
|
||||||
|
} else {
|
||||||
|
d.Command(0xF1)
|
||||||
|
}
|
||||||
|
d.Command(SETVCOMDETECT)
|
||||||
|
d.Command(0x40)
|
||||||
|
d.Command(DISPLAYALLON_RESUME)
|
||||||
|
d.Command(NORMALDISPLAY)
|
||||||
|
d.Command(DEACTIVATE_SCROLL)
|
||||||
|
d.Command(DISPLAYON)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearBuffer clears the image buffer
|
||||||
|
func (d *Device) ClearBuffer() {
|
||||||
|
for i := int16(0); i < d.bufferSize; i++ {
|
||||||
|
d.buffer[i] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearDisplay clears the image buffer and clear the display
|
||||||
|
func (d *Device) ClearDisplay() {
|
||||||
|
d.ClearBuffer()
|
||||||
|
d.Display()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display sends the whole buffer to the screen
|
||||||
|
func (d *Device) Display() error {
|
||||||
|
// In the 128x64 (SPI) screen resetting to 0x0 after 128 times corrupt the buffer
|
||||||
|
// Since we're printing the whole buffer, avoid resetting it
|
||||||
|
if d.width != 128 || d.height != 64 {
|
||||||
|
d.Command(COLUMNADDR)
|
||||||
|
d.Command(0)
|
||||||
|
d.Command(uint8(d.width - 1))
|
||||||
|
d.Command(PAGEADDR)
|
||||||
|
d.Command(0)
|
||||||
|
d.Command(uint8(d.height/8) - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Tx(d.buffer, false)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPixel enables or disables a pixel in the buffer
|
||||||
|
// color.RGBA{0, 0, 0, 255} is consider transparent, anything else
|
||||||
|
// with enable a pixel on the screen
|
||||||
|
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
|
||||||
|
if x < 0 || x >= d.width || y < 0 || y >= d.height {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
byteIndex := x + (y/8)*d.width
|
||||||
|
if c.R != 0 || c.G != 0 || c.B != 0 {
|
||||||
|
d.buffer[byteIndex] |= 1 << uint8(y%8)
|
||||||
|
} else {
|
||||||
|
d.buffer[byteIndex] &^= 1 << uint8(y%8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPixel returns if the specified pixel is on (true) or off (false)
|
||||||
|
func (d *Device) GetPixel(x int16, y int16) bool {
|
||||||
|
if x < 0 || x >= d.width || y < 0 || y >= d.height {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
byteIndex := x + (y/8)*d.width
|
||||||
|
return (d.buffer[byteIndex] >> uint8(y%8) & 0x1) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBuffer changes the whole buffer at once
|
||||||
|
func (d *Device) SetBuffer(buffer []byte) error {
|
||||||
|
if int16(len(buffer)) != d.bufferSize {
|
||||||
|
//return ErrBuffer
|
||||||
|
return errors.New("Wrong size buffer")
|
||||||
|
}
|
||||||
|
for i := int16(0); i < d.bufferSize; i++ {
|
||||||
|
d.buffer[i] = buffer[i]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command sends a command to the display
|
||||||
|
func (d *Device) Command(command uint8) {
|
||||||
|
d.bus.tx([]byte{command}, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setAddress sets the address to the I2C bus
|
||||||
|
func (b *I2CBus) setAddress(address uint16) {
|
||||||
|
b.Address = address
|
||||||
|
}
|
||||||
|
|
||||||
|
// setAddress does nothing, but it's required to avoid reflection
|
||||||
|
func (b *SPIBus) setAddress(address uint16) {
|
||||||
|
// do nothing
|
||||||
|
println("trying to Configure an address on a SPI device")
|
||||||
|
}
|
||||||
|
|
||||||
|
// configure does nothing, but it's required to avoid reflection
|
||||||
|
func (b *I2CBus) configure() {}
|
||||||
|
|
||||||
|
// configure configures some pins with the SPI bus
|
||||||
|
func (b *SPIBus) configure() {
|
||||||
|
b.csPin.Low()
|
||||||
|
b.dcPin.Low()
|
||||||
|
b.resetPin.Low()
|
||||||
|
|
||||||
|
b.resetPin.High()
|
||||||
|
time.Sleep(1 * time.Millisecond)
|
||||||
|
b.resetPin.Low()
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
b.resetPin.High()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tx sends data to the display
|
||||||
|
func (d *Device) Tx(data []byte, isCommand bool) {
|
||||||
|
d.bus.tx(data, isCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tx sends data to the display (I2CBus implementation)
|
||||||
|
func (b *I2CBus) tx(data []byte, isCommand bool) {
|
||||||
|
if isCommand {
|
||||||
|
b.wire.WriteRegister(uint8(b.Address), 0x00, data)
|
||||||
|
} else {
|
||||||
|
b.wire.WriteRegister(uint8(b.Address), 0x40, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tx sends data to the display (SPIBus implementation)
|
||||||
|
func (b *SPIBus) tx(data []byte, isCommand bool) {
|
||||||
|
if isCommand {
|
||||||
|
b.csPin.High()
|
||||||
|
time.Sleep(1 * time.Millisecond)
|
||||||
|
b.dcPin.Low()
|
||||||
|
b.csPin.Low()
|
||||||
|
|
||||||
|
b.wire.Tx(data, nil)
|
||||||
|
b.csPin.High()
|
||||||
|
} else {
|
||||||
|
b.csPin.High()
|
||||||
|
time.Sleep(1 * time.Millisecond)
|
||||||
|
b.dcPin.High()
|
||||||
|
b.csPin.Low()
|
||||||
|
|
||||||
|
b.wire.Tx(data, nil)
|
||||||
|
b.csPin.High()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the current size of the display.
|
||||||
|
func (d *Device) Size() (w, h int16) {
|
||||||
|
return d.width, d.height
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user