Add GC9A01 Driver (#452)

* Added driver for gc9a01 round display and example code
This commit is contained in:
Lucas Matte
2022-09-18 08:22:07 -05:00
committed by GitHub
parent 7133c2e9db
commit 4061adf6db
5 changed files with 675 additions and 0 deletions
+2
View File
@@ -53,6 +53,8 @@ smoke-test:
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/flash/console/qspi
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/gc9a01/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/gps/i2c/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/gps/uart/main.go
+1
View File
@@ -140,6 +140,7 @@ The following 83 devices are supported.
| [Waveshare 2.13" e-paper display](https://www.waveshare.com/w/upload/e/e6/2.13inch_e-Paper_Datasheet.pdf) | SPI |
| [Waveshare 2.9" e-paper display (V1)](https://www.waveshare.com/w/upload/e/e6/2.9inch_e-Paper_Datasheet.pdf) | SPI |
| [Waveshare 4.2" e-paper B/W display](https://www.waveshare.com/w/upload/6/6a/4.2inch-e-paper-specification.pdf) | SPI |
| [Waveshare GC9A01 TFT round display](https://www.waveshare.com/w/upload/5/5e/GC9A01A.pdf) | SPI |
| [WS2812 RGB LED](https://cdn-shop.adafruit.com/datasheets/WS2812.pdf) | GPIO |
| [XPT2046 touch controller](http://grobotronics.com/images/datasheets/xpt2046-datasheet.pdf) | GPIO |
| [Semtech SX126x Lora](https://www.semtech.com/products/wireless-rf/lora-transceiv-ers/sx1261) | SPI |
+39
View File
@@ -0,0 +1,39 @@
package main
import (
"machine"
"time"
"image/color"
"tinygo.org/x/drivers/gc9a01"
)
func main() {
machine.SPI0.Configure(machine.SPIConfig{
Frequency: 80000000,
})
display := gc9a01.New(machine.SPI0, machine.P6, machine.P7, machine.P8, machine.P9)
display.Configure(gc9a01.Config{Orientation: gc9a01.HORIZONTAL, Width: 240, Height: 240})
width, height := display.Size()
white := color.RGBA{255, 255, 255, 255}
red := color.RGBA{255, 0, 0, 255}
blue := color.RGBA{0, 0, 255, 255}
green := color.RGBA{0, 255, 0, 255}
black := color.RGBA{0, 0, 0, 255}
display.FillScreen(black)
display.FillRectangle(0, 0, width/2, height/2, white)
display.FillRectangle(width/2, 0, width/2, height/2, red)
display.FillRectangle(0, height/2, width/2, height/2, green)
display.FillRectangle(width/2, height/2, width/2, height/2, blue)
display.FillRectangle(width/4, height/4, width/2, height/2, black)
for {
time.Sleep(time.Hour)
}
}
+568
View File
@@ -0,0 +1,568 @@
// Package gc9a01 implements a driver for the gc9a01 LCD round display
//
// Datasheet: https://www.waveshare.com/w/upload/5/5e/GC9A01A.pdf
//
package gc9a01 // import "tinygo.org/x/drivers/gc9a01"
import (
"image/color"
"machine"
"time"
"errors"
"tinygo.org/x/drivers"
)
// Rotation controls the rotation used by the display.
type Orientation uint8
// FrameRate controls the frame rate used by the display.
type FrameRate uint8
// Device wraps an SPI connection.
type Device struct {
bus drivers.SPI
dcPin machine.Pin
resetPin machine.Pin
csPin machine.Pin
blPin machine.Pin
width int16
height int16
columnOffsetCfg int16
rowOffsetCfg int16
columnOffset int16
rowOffset int16
frameRate FrameRate
isBGR bool
vSyncLines int16
orientation Orientation
batchLength int16
batchData []uint8
}
// Config is the configuration for the display
type Config struct {
Orientation Orientation
RowOffset int16
ColumnOffset int16
FrameRate FrameRate
VSyncLines int16
Width int16
Height int16
}
// New creates a new ST7789 connection. The SPI wire must already be configured.
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
blPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
return Device{
bus: bus,
resetPin: resetPin,
dcPin: dcPin,
csPin: csPin,
blPin: blPin,
}
}
// Reset the Device
func (d *Device) Reset() {
d.resetPin.High()
time.Sleep(100 * time.Millisecond)
d.resetPin.Low()
time.Sleep(100 * time.Millisecond)
d.resetPin.High()
time.Sleep(100 * time.Millisecond)
}
// Sets Device configuration for screen orientation using the initialzation values
func (d *Device) SetDeviceOrientation() {
var MemoryAccessReg uint8
//Get GRAM and LCD width and height
if d.orientation == HORIZONTAL {
MemoryAccessReg = 0xc8
} else {
MemoryAccessReg = 0x68
}
// Set the read / write scan direction of the frame memory
d.Command(MADCTR)
//0x08 set RGB
d.Command(MemoryAccessReg)
}
// setWindow prepares the screen to be modified at a given rectangle
func (d *Device) setWindow(x, y, w, h int16) {
if d.orientation == HORIZONTAL {
x += d.columnOffset
y += d.rowOffset
} else {
x += d.rowOffset
y += d.columnOffset
}
d.Tx([]uint8{CASET}, true)
d.Tx([]uint8{uint8(x >> 8), uint8(x), uint8((x + w - 1) >> 8), uint8(x + w - 1)}, false)
d.Tx([]uint8{RASET}, true)
d.Tx([]uint8{uint8(y >> 8), uint8(y), uint8((y + h - 1) >> 8), uint8(y + h - 1)}, false)
d.Command(RAMWR)
}
// FillScreen fills the screen with a given color
func (d *Device) FillScreen(c color.RGBA) {
d.FillRectangle(0, 0, d.height, d.width, c)
}
// FillRectangle fills a rectangle at a given coordinates with a color
func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
k, j := d.Size()
var i int32
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
x >= k || (x+width) > k || y >= j || (y+height) > j {
return errors.New("rectangle coordinates outside display area")
}
d.setWindow(x, y, width, height)
c565 := RGBATo565(c)
c1 := uint8(c565 >> 8)
c2 := uint8(c565)
for i = 0; i < int32(d.batchLength); i++ {
d.batchData[i*2] = c1
d.batchData[i*2+1] = c2
}
i = int32(width) * int32(height)
batchLength := int32(d.batchLength)
for i > 0 {
if i >= batchLength {
d.Tx(d.batchData, false)
} else {
d.Tx(d.batchData[:i*2], false)
}
i -= batchLength
}
return nil
}
// Display sends the whole buffer to the screen
func (d *Device) Display() error {
return nil
}
// FillRectangleWithBuffer fills buffer with a rectangle at a given coordinates.
func (d *Device) FillRectangleWithBuffer(x, y, width, height int16, buffer []color.RGBA) error {
h, w := d.Size()
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
x >= h || (x+width) > h || y >= w || (y+height) > w {
return errors.New("rectangle coordinates outside display area")
}
k := int32(width) * int32(height)
l := int32(len(buffer))
if k != l {
return errors.New("buffer length does not match with rectangle size")
}
d.setWindow(x, y, width, height)
offset := int32(0)
batchLength := int32(d.batchLength)
for k > 0 {
for i := int32(0); i < batchLength; i++ {
if offset+i < l {
c565 := RGBATo565(buffer[offset+i])
c1 := uint8(c565 >> 8)
c2 := uint8(c565)
d.batchData[i*2] = c1
d.batchData[i*2+1] = c2
}
}
if k >= batchLength {
d.Tx(d.batchData, false)
} else {
d.Tx(d.batchData[:k*2], false)
}
k -= batchLength
offset += batchLength
}
return nil
}
// DrawFastVLine draws a vertical line faster than using SetPixel
func (d *Device) DrawFastVLine(x, y0, y1 int16, c color.RGBA) {
if y0 > y1 {
y0, y1 = y1, y0
}
d.FillRectangle(x, y0, 1, y1-y0+1, c)
}
// DrawFastHLine draws a horizontal line faster than using SetPixel
func (d *Device) DrawFastHLine(x0, x1, y int16, c color.RGBA) {
if x0 > x1 {
x0, x1 = x1, x0
}
d.FillRectangle(x0, y, x1-x0+1, 1, c)
}
// SetPixel sets a pixel in the screen
func (d *Device) SetPixel(x, y int16, c color.RGBA) {
w, h := d.Size()
if x < 0 || y < 0 || x >= w || y >= h {
return
}
d.FillRectangle(x, y, 1, 1, c)
}
// Command sends a command to the display.
func (d *Device) Command(command uint8) {
d.Tx([]byte{command}, true)
}
// Data sends data to the display.
func (d *Device) Data(data uint8) {
d.Tx([]byte{data}, false)
}
// Tx sends data to the display
func (d *Device) Tx(data []byte, isCommand bool) {
d.dcPin.Set(!isCommand)
d.bus.Tx(data, nil)
}
// Rx reads data from the display
func (d *Device) Rx(command uint8, data []byte) {
d.dcPin.Low()
d.csPin.Low()
d.bus.Transfer(command)
d.dcPin.High()
for i := range data {
data[i], _ = d.bus.Transfer(0xFF)
}
d.csPin.High()
}
// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
return d.height, d.width
}
// EnableBacklight enables or disables the backlight
func (d *Device) EnableBacklight(enable bool) {
if enable {
d.blPin.High()
} else {
d.blPin.Low()
}
}
// InvertColors inverts the colors of the screen
func (d *Device) InvertColors(invert bool) {
if invert {
d.Command(INVON)
} else {
d.Command(INVOFF)
}
}
// IsBGR changes the color mode (RGB/BGR)
func (d *Device) IsBGR(bgr bool) {
d.isBGR = bgr
}
// SetScrollArea sets an area to scroll with fixed top and bottom parts of the display.
func (d *Device) SetScrollArea(topFixedArea, bottomFixedArea int16) {
d.Command(VSCRDEF)
d.Tx([]uint8{
uint8(topFixedArea >> 8), uint8(topFixedArea),
uint8(d.height - topFixedArea - bottomFixedArea>>8), uint8(d.height - topFixedArea - bottomFixedArea),
uint8(bottomFixedArea >> 8), uint8(bottomFixedArea)},
false)
}
// SetScroll sets the vertical scroll address of the display.
func (d *Device) SetScroll(line int16) {
d.Command(VSCRSADD)
d.Tx([]uint8{uint8(line >> 8), uint8(line)}, false)
}
// StopScroll returns the display to its normal state.
func (d *Device) StopScroll() {
d.Command(NORON)
}
// RGBATo565 converts a color.RGBA to uint16 used in the display
func RGBATo565(c color.RGBA) uint16 {
r, g, b, _ := c.RGBA()
return uint16((r & 0xF800) +
((g & 0xFC00) >> 5) +
((b & 0xF800) >> 11))
}
// Configure initializes the display with default configuration
func (d *Device) Configure(cfg Config) {
if cfg.Width != 0 {
d.width = cfg.Width
} else {
d.width = 240
}
if cfg.Height != 0 {
d.height = cfg.Height
} else {
d.height = 240
}
d.orientation = cfg.Orientation
d.rowOffsetCfg = cfg.RowOffset
d.columnOffsetCfg = cfg.ColumnOffset
d.batchLength = d.width
if cfg.VSyncLines >= 2 && cfg.VSyncLines <= 254 {
d.vSyncLines = cfg.VSyncLines
} else {
d.vSyncLines = 16
}
d.batchLength += d.batchLength & 1
d.batchData = make([]uint8, d.batchLength*2)
// Reset the device
d.Reset()
// Set Device Attributes
d.SetDeviceOrientation()
// Common initialization
d.Command(0xEF)
d.Command(0xEB)
d.Data(0x14)
d.Command(INTEN1)
d.Command(0xEF)
d.Command(0xEB)
d.Data(0x14)
d.Command(0x84)
d.Data(0x40)
d.Command(0x85)
d.Data(0xFF)
d.Command(0x86)
d.Data(0xFF)
d.Command(0x87)
d.Data(0xFF)
d.Command(0x88)
d.Data(0x0A)
d.Command(0x89)
d.Data(0x21)
d.Command(0x8A)
d.Data(0x00)
d.Command(0x8B)
d.Data(0x80)
d.Command(0x8C)
d.Data(0x01)
d.Command(0x8D)
d.Data(0x01)
d.Command(0x8E)
d.Data(0xFF)
d.Command(0x8F)
d.Data(0xFF)
d.Command(DISFNCTL)
d.Data(0x00)
d.Data(0x20)
d.Command(MADCTR)
d.Data(0x08) //Set as vertical screen
d.Command(COLMOD)
d.Data(0x05)
d.Command(0x90)
d.Data(0x08)
d.Data(0x08)
d.Data(0x08)
d.Data(0x08)
d.Command(0xBD)
d.Data(0x06)
d.Command(0xBC)
d.Data(0x00)
d.Command(0xFF)
d.Data(0x60)
d.Data(0x01)
d.Data(0x04)
d.Command(PWCTR3)
d.Data(0x13)
d.Command(PWCTR4)
d.Data(0x13)
d.Command(0xC9)
d.Data(0x22)
d.Command(0xBE)
d.Data(0x11)
d.Command(0xE1)
d.Data(0x10)
d.Data(0x0E)
d.Command(0xDF)
d.Data(0x21)
d.Data(0x0c)
d.Data(0x02)
d.Command(GMSET1)
d.Data(0x45)
d.Data(0x09)
d.Data(0x08)
d.Data(0x08)
d.Data(0x26)
d.Data(0x2A)
d.Command(GMSET2)
d.Data(0x43)
d.Data(0x70)
d.Data(0x72)
d.Data(0x36)
d.Data(0x37)
d.Data(0x6F)
d.Command(GMSET3)
d.Data(0x45)
d.Data(0x09)
d.Data(0x08)
d.Data(0x08)
d.Data(0x26)
d.Data(0x2A)
d.Command(GMSET4)
d.Data(0x43)
d.Data(0x70)
d.Data(0x72)
d.Data(0x36)
d.Data(0x37)
d.Data(0x6F)
d.Command(0xED)
d.Data(0x1B)
d.Data(0x0B)
d.Command(0xAE)
d.Data(0x77)
d.Command(0xCD)
d.Data(0x63)
d.Command(0x70)
d.Data(0x07)
d.Data(0x07)
d.Data(0x04)
d.Data(0x0E)
d.Data(0x0F)
d.Data(0x09)
d.Data(0x07)
d.Data(0x08)
d.Data(0x03)
d.Command(FRMCTL)
d.Data(0x34)
d.Command(0x62)
d.Data(0x18)
d.Data(0x0D)
d.Data(0x71)
d.Data(0xED)
d.Data(0x70)
d.Data(0x70)
d.Data(0x18)
d.Data(0x0F)
d.Data(0x71)
d.Data(0xEF)
d.Data(0x70)
d.Data(0x70)
d.Command(0x63)
d.Data(0x18)
d.Data(0x11)
d.Data(0x71)
d.Data(0xF1)
d.Data(0x70)
d.Data(0x70)
d.Data(0x18)
d.Data(0x13)
d.Data(0x71)
d.Data(0xF3)
d.Data(0x70)
d.Data(0x70)
d.Command(0x64)
d.Data(0x28)
d.Data(0x29)
d.Data(0xF1)
d.Data(0x01)
d.Data(0xF1)
d.Data(0x00)
d.Data(0x07)
d.Command(0x66)
d.Data(0x3C)
d.Data(0x00)
d.Data(0xCD)
d.Data(0x67)
d.Data(0x45)
d.Data(0x45)
d.Data(0x10)
d.Data(0x00)
d.Data(0x00)
d.Data(0x00)
d.Command(0x67)
d.Data(0x00)
d.Data(0x3C)
d.Data(0x00)
d.Data(0x00)
d.Data(0x00)
d.Data(0x01)
d.Data(0x54)
d.Data(0x10)
d.Data(0x32)
d.Data(0x98)
d.Command(0x74)
d.Data(0x10)
d.Data(0x85)
d.Data(0x80)
d.Data(0x00)
d.Data(0x00)
d.Data(0x4E)
d.Data(0x00)
d.Command(0x98)
d.Data(0x3e)
d.Data(0x07)
d.Command(TEON)
d.Command(0x21)
d.Command(SLPOUT)
time.Sleep(120 * time.Millisecond)
d.Command(DISPON)
time.Sleep(20 * time.Millisecond)
d.blPin.High()
}
+65
View File
@@ -0,0 +1,65 @@
package gc9a01
// Registers
const (
NOP = 0x00
SWRESET = 0x01
RDDIDIF = 0x04
RDDST = 0x09
SLPIN = 0x10
SLPOUT = 0x11
PTLON = 0x12
NORON = 0x13
INVOFF = 0x20
INVON = 0x21
DISPOFF = 0x28
DISPON = 0x29
CASET = 0x2A
RASET = 0x2B
RAMWR = 0x2C
PTLAR = 0x30
VSCRDEF = 0x33
TEOFF = 0x34
TEON = 0x35
MADCTR = 0x36
VSCRSADD = 0x37
IDMOFF = 0x38
IDMON = 0x39
COLMOD = 0x3A
RMEMCON = 0x3C
STTRSCL = 0x44
GTSCL = 0x45
WRDISBV = 0x51
WRCTRLD = 0x51
MADCTL_MY = 0x80
MADCTL_MX = 0x40
MADCTL_MV = 0x20
MADCTL_ML = 0x10
MADCTL_RGB = 0x00
MADCTL_BGR = 0x08
MADCTL_MH = 0x04
RDID1 = 0xDA
RDID2 = 0xDB
RDID3 = 0xDC
RGBICTR = 0xB0
BLPCHCTRL = 0xB5
DISFNCTL = 0xB6
TECTL = 0xBA
INTCTL = 0xBA
FRMCTL = 0xE8
SPICTL = 0xE9
PWCTR1 = 0xC1
PWCTR2 = 0xC2
PWCTR3 = 0xC3
PWCTR4 = 0xC4
PWCTR7 = 0xC7
INTEN1 = 0xFE
INTEN2 = 0xEF
GMSET1 = 0xF0
GMSET2 = 0xF1
GMSET3 = 0xF2
GMSET4 = 0xF3
HORIZONTAL Orientation = 0
VERTICAL Orientation = 1
)