Files
deadprogram 1bb1b621c6 all: correct go fmt
Signed-off-by: deadprogram <ron@hybridgroup.com>
2022-09-25 13:23:16 +02:00

568 lines
11 KiB
Go

// 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()
}