Compare commits

..

4 Commits

Author SHA1 Message Date
deadprogram 85e123eeb2 st7789: work in progress on display refactor
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-12-20 10:29:55 +01:00
deadprogram 51561d4eab st7789: add Bus interface to accomodate both SPI and parallel connections to display
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-12-20 10:29:55 +01:00
deadprogram b561dc36f2 examples: switch st7789 example to use the Adafruit Clue since that device actually comes with one.
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-12-20 10:29:55 +01:00
deadprogram 85b24b3418 st7789: remove dependency on the machine package by using internal/pin instead
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-12-20 10:29:55 +01:00
11 changed files with 222 additions and 301 deletions
-91
View File
@@ -1,91 +0,0 @@
package main
// Example program for the ST7735 display (Waveshare 1.44" LCD HAT) using the rpio driver on a Raspberry Pi.
// Build command:
// GOOS=linux GOARCH=arm64 go build -o rpio-example ./examples/rpio/
import (
"fmt"
"image/color"
"time"
go_rpio "github.com/stianeikeland/go-rpio/v4"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/rpio"
"tinygo.org/x/drivers/st7735"
)
var (
black = color.RGBA{0, 0, 0, 255}
colors = [...]color.RGBA{
{255, 0, 0, 255}, // red
{0, 255, 0, 255}, // green
{0, 0, 255, 255}, // blue
{255, 255, 0, 255}, // yellow
}
)
func main() {
if err := go_rpio.Open(); err != nil {
fmt.Println("Error opening GPIO:", err)
return
}
defer go_rpio.Close()
// Initialize SPI and pins
spi := rpio.NewSPI()
resetPin := rpio.NewPin(27)
dcPin := rpio.NewPin(25)
csPin := rpio.NewPin(8)
blPin := rpio.NewPin(24)
rotatePin := rpio.NewPin(20) // middle button on the HAT
rotatePin.Pin.PullUp() // enable pull-up resistor, our wrapper sets only input mode automatically
// Initialize display
device := st7735.New(spi, resetPin, dcPin, csPin, blPin)
device.Configure(st7735.Config{
Width: 128,
Height: 128,
Model: st7735.GREENTAB,
RowOffset: 3,
ColumnOffset: 2,
})
device.InvertColors(false)
device.EnableBacklight(true)
device.IsBGR(true) // no effect w/o rotation!
device.SetRotation(st7735.NO_ROTATION)
width, height := device.Size()
// Clear display
device.FillScreen(black)
// Draw rectangles in a loop, clockwise rotation of colors
pos := 0
for {
device.FillRectangle(0, 0, width/2, height/2, colors[(pos+0)%len(colors)]) // top left
device.FillRectangle(0, height/2, width/2, height/2, colors[(pos+1)%len(colors)]) // bottom left
device.FillRectangle(width/2, height/2, width/2, height/2, colors[(pos+2)%len(colors)]) // bottom right
device.FillRectangle(width/2, 0, width/2, height/2, colors[(pos+3)%len(colors)]) // top right
device.FillRectangle(0, 0, 10, 10, black) // rotation marker
pos++
timeout := time.Now().Add(1 * time.Second)
for time.Now().Before(timeout) {
// check button pressed
if !rotatePin.Get() {
currentRotation := device.Rotation()
newRotation := (currentRotation + 1) % 4
device.SetRotation(drivers.Rotation(newRotation))
// wait for button release
for !rotatePin.Get() {
time.Sleep(10 * time.Millisecond)
}
break
}
time.Sleep(50 * time.Millisecond)
}
}
}
+4 -4
View File
@@ -29,10 +29,10 @@ func main() {
Mode: 0, Mode: 0,
}) })
display := st7789.New(machine.SPI0, display := st7789.New(machine.SPI0,
machine.P6, // TFT_RESET machine.TFT_RESET, // TFT_RESET
machine.P7, // TFT_DC machine.TFT_DC, // TFT_DC
machine.P8, // TFT_CS machine.TFT_CS, // TFT_CS
machine.P9) // TFT_LITE machine.TFT_LITE) // TFT_LITE
display.Configure(st7789.Config{ display.Configure(st7789.Config{
Rotation: st7789.NO_ROTATION, Rotation: st7789.NO_ROTATION,
-1
View File
@@ -12,7 +12,6 @@ require (
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/orsinium-labs/tinymath v1.1.0 github.com/orsinium-labs/tinymath v1.1.0
github.com/soypat/natiu-mqtt v0.5.1 github.com/soypat/natiu-mqtt v0.5.1
github.com/stianeikeland/go-rpio/v4 v4.6.0
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d
golang.org/x/net v0.33.0 golang.org/x/net v0.33.0
tinygo.org/x/tinyfont v0.3.0 tinygo.org/x/tinyfont v0.3.0
-2
View File
@@ -17,8 +17,6 @@ github.com/orsinium-labs/tinymath v1.1.0 h1:KomdsyLHB7vE3f1nRAJF2dyf1m/gnM2HxfTe
github.com/orsinium-labs/tinymath v1.1.0/go.mod h1:WPXX6ei3KSXG7JfA03a+ekCYaY9SWN4I+JRl2p6ck+A= github.com/orsinium-labs/tinymath v1.1.0/go.mod h1:WPXX6ei3KSXG7JfA03a+ekCYaY9SWN4I+JRl2p6ck+A=
github.com/soypat/natiu-mqtt v0.5.1 h1:rwaDmlvjzD2+3MCOjMZc4QEkDkNwDzbct2TJbpz+TPc= github.com/soypat/natiu-mqtt v0.5.1 h1:rwaDmlvjzD2+3MCOjMZc4QEkDkNwDzbct2TJbpz+TPc=
github.com/soypat/natiu-mqtt v0.5.1/go.mod h1:xEta+cwop9izVCW7xOx2W+ct9PRMqr0gNVkvBPnQTc4= github.com/soypat/natiu-mqtt v0.5.1/go.mod h1:xEta+cwop9izVCW7xOx2W+ct9PRMqr0gNVkvBPnQTc4=
github.com/stianeikeland/go-rpio/v4 v4.6.0 h1:eAJgtw3jTtvn/CqwbC82ntcS+dtzUTgo5qlZKe677EY=
github.com/stianeikeland/go-rpio/v4 v4.6.0/go.mod h1:A3GvHxC1Om5zaId+HqB3HKqx4K/AqeckxB7qRjxMK7o=
github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0= golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
-37
View File
@@ -1,37 +0,0 @@
// Implements internal.pin.[Input,Output] interfaces for the Raspberry Pi GPIO pins.
// Depends on the go-rpio library.
// Configures pin modes automatically when Get or Set methods are called.
package rpio // import "tinygo.org/x/drivers/rpio"
import go_rpio "github.com/stianeikeland/go-rpio/v4"
type Pin struct {
modeSet bool
Mode go_rpio.Mode
Pin go_rpio.Pin
}
func NewPin(pinNumber int) *Pin {
return &Pin{Pin: go_rpio.Pin(pinNumber)}
}
func (p *Pin) Get() bool {
if !p.modeSet || p.Mode != go_rpio.Input {
p.Pin.Input()
p.Mode = go_rpio.Input
}
return p.Pin.Read() == go_rpio.High
}
func (p *Pin) Set(high bool) {
if !p.modeSet || p.Mode != go_rpio.Output {
p.Pin.Output()
p.Mode = go_rpio.Output
}
state := go_rpio.Low
if high {
state = go_rpio.High
}
p.Pin.Write(state)
}
-33
View File
@@ -1,33 +0,0 @@
// Implemenmts drivers.SPI interface for the Raspberry Pi.
// Depends on the go-rpio library.
package rpio // import "tinygo.org/x/drivers/rpio"
import go_rpio "github.com/stianeikeland/go-rpio/v4"
type SPI struct {
}
func NewSPI() (s *SPI) {
if err := go_rpio.SpiBegin(go_rpio.Spi0); err != nil {
panic(err)
}
go_rpio.SpiSpeed(25_000_000) // 25 MHz
go_rpio.SpiChipSelect(0)
return &SPI{}
}
func (s *SPI) Tx(w, r []byte) error {
data := make([]byte, len(w))
copy(data, w)
go_rpio.SpiExchange(data)
copy(r, data)
return nil
}
func (s *SPI) Transfer(b byte) (byte, error) {
w := []byte{b}
r := make([]byte, len(w))
err := s.Tx(w, r)
return r[0], err
}
+1 -1
View File
@@ -74,7 +74,7 @@ tinygo build -size short -o ./build/test.hex -target=xiao-rp2040 ./examples/ssd1
tinygo build -size short -o ./build/test.hex -target=thumby ./examples/ssd1306/ tinygo build -size short -o ./build/test.hex -target=thumby ./examples/ssd1306/
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1331/main.go tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1331/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/st7735/main.go tinygo build -size short -o ./build/test.hex -target=microbit ./examples/st7735/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/st7789/main.go tinygo build -size short -o ./build/test.hex -target=clue ./examples/st7789/main.go
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/thermistor/main.go tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/thermistor/main.go
tinygo build -size short -o ./build/test.hex -target=circuitplay-bluefruit ./examples/tone tinygo build -size short -o ./build/test.hex -target=circuitplay-bluefruit ./examples/tone
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/tm1637/main.go tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/tm1637/main.go
+16 -19
View File
@@ -5,13 +5,12 @@ package st7735 // import "tinygo.org/x/drivers/st7735"
import ( import (
"image/color" "image/color"
"machine"
"time" "time"
"errors" "errors"
"tinygo.org/x/drivers" "tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
"tinygo.org/x/drivers/pixel" "tinygo.org/x/drivers/pixel"
) )
@@ -40,10 +39,10 @@ type Device = DeviceOf[pixel.RGB565BE]
// formats. // formats.
type DeviceOf[T Color] struct { type DeviceOf[T Color] struct {
bus drivers.SPI bus drivers.SPI
dcPin pin.OutputFunc dcPin machine.Pin
resetPin pin.OutputFunc resetPin machine.Pin
csPin pin.OutputFunc csPin machine.Pin
blPin pin.OutputFunc blPin machine.Pin
width int16 width int16
height int16 height int16
columnOffset int16 columnOffset int16
@@ -66,25 +65,23 @@ type Config struct {
} }
// New creates a new ST7735 connection. The SPI wire must already be configured. // New creates a new ST7735 connection. The SPI wire must already be configured.
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin pin.Output) Device { func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
return NewOf[pixel.RGB565BE](bus, resetPin, dcPin, csPin, blPin) return NewOf[pixel.RGB565BE](bus, resetPin, dcPin, csPin, blPin)
} }
// NewOf creates a new ST7735 connection with a particular pixel format. The SPI // NewOf creates a new ST7735 connection with a particular pixel format. The SPI
// wire must already be configured. // wire must already be configured.
func NewOf[T Color](bus drivers.SPI, resetPin, dcPin, csPin, blPin pin.Output) DeviceOf[T] { func NewOf[T Color](bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) DeviceOf[T] {
// IMPORTANT: pin configuration should really be done outside of this driver, dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
// but for backwards compatibility with existing code, we do it here. resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
legacy.ConfigurePinOut(dcPin) csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
legacy.ConfigurePinOut(resetPin) blPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
legacy.ConfigurePinOut(csPin)
legacy.ConfigurePinOut(blPin)
return DeviceOf[T]{ return DeviceOf[T]{
bus: bus, bus: bus,
dcPin: dcPin.Set, dcPin: dcPin,
resetPin: resetPin.Set, resetPin: resetPin,
csPin: csPin.Set, csPin: csPin,
blPin: blPin.Set, blPin: blPin,
} }
} }
@@ -426,7 +423,7 @@ func (d *DeviceOf[T]) Data(data uint8) {
// Tx sends data to the display // Tx sends data to the display
func (d *DeviceOf[T]) Tx(data []byte, isCommand bool) { func (d *DeviceOf[T]) Tx(data []byte, isCommand bool) {
d.dcPin(!isCommand) d.dcPin.Set(!isCommand)
d.bus.Tx(data, nil) d.bus.Tx(data, nil)
} }
+8
View File
@@ -0,0 +1,8 @@
package st7789
// Bus is the interface that wraps the basic Tx and Transfer methods
// for communication buses like SPI or Parallel.
type Bus interface {
Tx(w, r []byte) error
Transfer(w byte) (byte, error)
}
+57 -50
View File
@@ -4,56 +4,63 @@ import "tinygo.org/x/drivers"
// Registers // Registers
const ( const (
NOP = 0x00 NOP = 0x00
SWRESET = 0x01 SWRESET = 0x01
RDDID = 0x04 RDDID = 0x04
RDDST = 0x09 RDDST = 0x09
SLPIN = 0x10 SLPIN = 0x10
SLPOUT = 0x11 SLPOUT = 0x11
PTLON = 0x12 PTLON = 0x12
NORON = 0x13 NORON = 0x13
INVOFF = 0x20 INVOFF = 0x20
INVON = 0x21 INVON = 0x21
DISPOFF = 0x28 DISPOFF = 0x28
DISPON = 0x29 DISPON = 0x29
CASET = 0x2A CASET = 0x2A
RASET = 0x2B RASET = 0x2B
RAMWR = 0x2C RAMWR = 0x2C
RAMRD = 0x2E RAMRD = 0x2E
PTLAR = 0x30 PTLAR = 0x30
COLMOD = 0x3A VSCRDEF = 0x33
MADCTL = 0x36 TEOFF = 0x34
MADCTL_MY = 0x80 TEON = 0x35
MADCTL_MX = 0x40 VSCRSADD = 0x37
MADCTL_MV = 0x20 COLMOD = 0x3A
MADCTL_ML = 0x10 MADCTL = 0x36
MADCTL_RGB = 0x00 GSCAN = 0x45
MADCTL_BGR = 0x08 PWCTRL1 = 0xD0
MADCTL_MH = 0x04 RDID1 = 0xDA
RDID1 = 0xDA RDID2 = 0xDB
RDID2 = 0xDB RDID3 = 0xDC
RDID3 = 0xDC RDID4 = 0xDD
RDID4 = 0xDD RAMCTRL = 0xB0
FRMCTR1 = 0xB1 FRMCTR1 = 0xB1
RGBCTRL = 0xB1 RGBCTRL = 0xB1
FRMCTR2 = 0xB2 FRMCTR2 = 0xB2
PORCTRL = 0xB2 PORCTRL = 0xB2
FRMCTR3 = 0xB3 FRMCTR3 = 0xB3
INVCTR = 0xB4 INVCTR = 0xB4
DISSET5 = 0xB6 DISSET5 = 0xB6
PWCTR1 = 0xC0 GCTRL = 0xB7
PWCTR2 = 0xC1 VCOMS = 0xBB
PWCTR3 = 0xC2 LCMCTRL = 0xC0
PWCTR4 = 0xC3 PWCTR2 = 0xC1
PWCTR5 = 0xC4 VDVVRHEN = 0xC2
VMCTR1 = 0xC5 VRHS = 0xC3
FRCTRL2 = 0xC6 VDVS = 0xC4
PWCTR6 = 0xFC VMCTR1 = 0xC5
GMCTRP1 = 0xE0 FRCTRL2 = 0xC6
GMCTRN1 = 0xE1 PVGAMCTRL = 0xE0
GSCAN = 0x45 NVGAMCTRL = 0xE1
VSCRDEF = 0x33 PWCTR6 = 0xFC
VSCRSADD = 0x37
MADCTL_RGB = 0x00
MADCTL_ROWORDER = 0x80
MADCTL_COLORDER = 0x40
MADCTL_SWAPXY = 0x20
MADCTL_SCANORDER = 0x10
MADCTL_BGR = 0x08
MADCTL_MH = 0x04
ColorRGB444 ColorFormat = 0b011 ColorRGB444 ColorFormat = 0b011
ColorRGB565 ColorFormat = 0b101 ColorRGB565 ColorFormat = 0b101
+136 -63
View File
@@ -7,13 +7,14 @@ package st7789 // import "tinygo.org/x/drivers/st7789"
import ( import (
"image/color" "image/color"
"machine"
"math" "math"
"time" "time"
"errors" "errors"
"tinygo.org/x/drivers" "tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
"tinygo.org/x/drivers/pixel" "tinygo.org/x/drivers/pixel"
) )
@@ -45,11 +46,11 @@ type Device = DeviceOf[pixel.RGB565BE]
// DeviceOf is a generic version of Device. It supports multiple different pixel // DeviceOf is a generic version of Device. It supports multiple different pixel
// formats. // formats.
type DeviceOf[T Color] struct { type DeviceOf[T Color] struct {
bus drivers.SPI bus Bus
dcPin machine.Pin dcPin pin.OutputFunc
resetPin machine.Pin resetPin pin.OutputFunc
csPin machine.Pin csPin pin.OutputFunc
blPin machine.Pin blPin pin.OutputFunc
width int16 width int16
height int16 height int16
columnOffsetCfg int16 columnOffsetCfg int16
@@ -68,13 +69,15 @@ type DeviceOf[T Color] struct {
// Config is the configuration for the display // Config is the configuration for the display
type Config struct { type Config struct {
Width int16 Width int16
Height int16 Height int16
Rotation drivers.Rotation Rotation drivers.Rotation
RowOffset int16 RowOffset int16
ColumnOffset int16 ColumnOffset int16
FrameRate FrameRate FrameRate FrameRate
VSyncLines int16 VSyncLines int16
IdleModePorch byte
PartialModePorch byte
// Gamma control. Look in the LCD panel datasheet or provided example code // Gamma control. Look in the LCD panel datasheet or provided example code
// to find these values. If not set, the defaults will be used. // to find these values. If not set, the defaults will be used.
@@ -83,23 +86,27 @@ type Config struct {
} }
// New creates a new ST7789 connection. The SPI wire must already be configured. // 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 { func New(bus Bus, resetPin, dcPin, csPin, blPin pin.Output) Device {
return NewOf[pixel.RGB565BE](bus, resetPin, dcPin, csPin, blPin) return NewOf[pixel.RGB565BE](bus, resetPin, dcPin, csPin, blPin)
} }
// NewOf creates a new ST7789 connection with a particular pixel format. The SPI // NewOf creates a new ST7789 connection with a particular pixel format. The SPI
// wire must already be configured. // wire must already be configured.
func NewOf[T Color](bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) DeviceOf[T] { func NewOf[T Color](bus Bus, resetPin, dcPin, csPin, blPin pin.Output) DeviceOf[T] {
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) // IMPORTANT: pin configuration should really be done outside of this
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) // driver, but for backwards compatibility with existing code, we do it
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) // here.
blPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) legacy.ConfigurePinOut(dcPin)
legacy.ConfigurePinOut(resetPin)
legacy.ConfigurePinOut(csPin)
legacy.ConfigurePinOut(blPin)
return DeviceOf[T]{ return DeviceOf[T]{
bus: bus, bus: bus,
dcPin: dcPin, dcPin: dcPin.Set,
resetPin: resetPin, resetPin: resetPin.Set,
csPin: csPin, csPin: csPin.Set,
blPin: blPin, blPin: blPin.Set,
} }
} }
@@ -129,7 +136,7 @@ func (d *DeviceOf[T]) Configure(cfg Config) {
if cfg.VSyncLines >= 2 && cfg.VSyncLines <= 254 { if cfg.VSyncLines >= 2 && cfg.VSyncLines <= 254 {
d.vSyncLines = cfg.VSyncLines d.vSyncLines = cfg.VSyncLines
} else { } else {
d.vSyncLines = 16 d.vSyncLines = 0 // Default: no VSYNC pause
} }
d.batchLength = int32(d.width) d.batchLength = int32(d.width)
@@ -139,21 +146,18 @@ func (d *DeviceOf[T]) Configure(cfg Config) {
d.batchLength += d.batchLength & 1 d.batchLength += d.batchLength & 1
// Reset the device // Reset the device
d.resetPin.High() d.Reset()
time.Sleep(50 * time.Millisecond)
d.resetPin.Low()
time.Sleep(50 * time.Millisecond)
d.resetPin.High()
time.Sleep(50 * time.Millisecond)
// Common initialization // Common initialization
d.startWrite() d.startWrite()
d.sendCommand(SWRESET, nil) // Soft reset d.sendCommand(SWRESET, nil) // Soft reset
d.endWrite() d.endWrite()
time.Sleep(150 * time.Millisecond) //
d.startWrite()
d.sendCommand(SLPOUT, nil) // Exit sleep mode time.Sleep(150 * time.Millisecond) //
d.startWrite()
// enable frame sync signal if used
d.sendCommand(TEON, nil)
// Memory initialization // Memory initialization
var zeroColor T var zeroColor T
@@ -164,54 +168,121 @@ func (d *DeviceOf[T]) Configure(cfg Config) {
// Use default RGB565 color format. // Use default RGB565 color format.
d.setColorFormat(ColorRGB565) // 16 bits per pixel d.setColorFormat(ColorRGB565) // 16 bits per pixel
} }
time.Sleep(10 * time.Millisecond)
d.setRotation(d.rotation) // Memory orientation
d.setWindow(0, 0, d.width, d.height) // Full draw window
d.fillScreen(color.RGBA{0, 0, 0, 255}) // Clear screen
// Framerate
d.sendCommand(FRCTRL2, []byte{byte(d.frameRate)}) // Frame rate for normal mode (default 60Hz)
// Frame vertical sync and "porch" // Frame vertical sync and "porch"
// //
// Front and back porch controls vertical scanline sync time before and after // Front and back porch controls vertical scanline sync time before and after
// a frame, where memory can be safely written without tearing. // a frame, where memory can be safely written without tearing.
// //
fp := uint8(d.vSyncLines / 2) // Split the desired pause half and half // TODO: is this correct? fp := uint8(d.vSyncLines / 2) // Split the desired pause half and half
bp := uint8(d.vSyncLines - int16(fp)) // between front and back porch. fp := byte(0x0c)
// TODO: is this correct? bp := uint8(d.vSyncLines - int16(fp)) // between front and back porch.
bp := byte(0x0c)
if cfg.IdleModePorch == 0 {
cfg.IdleModePorch = 0x22 // Default value
}
if cfg.PartialModePorch == 0 {
cfg.PartialModePorch = 0x22 // Default value
}
d.sendCommand(PORCTRL, []byte{ d.sendCommand(PORCTRL, []byte{
bp, // Back porch 5bit (0x7F max 0x08 default) bp, // Back porch 5bit (0x7F max 0x08 default)
fp, // Front porch 5bit (0x7F max 0x08 default) fp, // Front porch 5bit (0x7F max 0x08 default)
0x00, // Seprarate porch (TODO: what is this?) 0x00, // Separate porch (TODO: what is this?)
0x22, // Idle mode porch (4bit-back 4bit-front 0x22 default) cfg.IdleModePorch, // Idle mode porch (4bit-back 4bit-front 0x22 default)
0x22, // Partial mode porch (4bit-back 4bit-front 0x22 default) cfg.PartialModePorch, // Partial mode porch (4bit-back 4bit-front 0x22 default)
}) })
// Ready to display // LCM control, power on sequence
d.sendCommand(INVON, nil) // Inversion ON d.sendCommand(LCMCTRL, []byte{0x2C})
time.Sleep(10 * time.Millisecond) //
// VDV and VRH Command Enable - 0x01 power on sequence
d.sendCommand(VDVVRHEN, []byte{0x01})
// VRH Set - 0x12 5.3+( vcom+vcom offset+vdv)
d.sendCommand(VRHS, []byte{0x12})
// VDV Set - 0x20 0V
d.sendCommand(VDVS, []byte{0x20})
// PWCTRL1 Power control 1 - power on sequence
d.sendCommand(PWCTRL1, []byte{0xA4, 0xA1})
// Framerate
d.sendCommand(FRCTRL2, []byte{byte(d.frameRate)}) // Frame rate for normal mode (default 60Hz)
// As noted in https://github.com/pimoroni/pimoroni-pico/issues/1040
// this is required to avoid a weird light grey banding issue with low brightness green.
// The banding is not visible without tweaking gamma settings (GMCTRP1 & GMCTRN1) but
// it makes sense to fix it anyway.
d.sendCommand(RAMCTRL, []byte{0x00, 0xC0})
// Set gamma tables, if configured. // Set gamma tables, if configured.
if len(cfg.PVGAMCTRL) == 14 { if len(cfg.PVGAMCTRL) == 14 {
d.sendCommand(GMCTRP1, cfg.PVGAMCTRL) // PVGAMCTRL: Positive Voltage Gamma Control d.sendCommand(PVGAMCTRL, cfg.PVGAMCTRL) // PVGAMCTRL: Positive Voltage Gamma Control
} }
if len(cfg.NVGAMCTRL) == 14 { if len(cfg.NVGAMCTRL) == 14 {
d.sendCommand(GMCTRN1, cfg.NVGAMCTRL) // NVGAMCTRL: Negative Voltage Gamma Control d.sendCommand(NVGAMCTRL, cfg.NVGAMCTRL) // NVGAMCTRL: Negative Voltage Gamma Control
} }
d.sendCommand(NORON, nil) // Normal mode ON switch {
time.Sleep(10 * time.Millisecond) // case d.width == 240 && d.height == 240:
// command(reg::GCTRL, 1, "\x14");
// Gate Control - power on sequence for 240x240
d.sendCommand(GCTRL, []byte{0x35})
d.sendCommand(DISPON, nil) // Screen ON // command(reg::VCOMS, 1, "\x37");
time.Sleep(10 * time.Millisecond) // // VCOM Setting
d.sendCommand(VCOMS, []byte{0x37})
// command(reg::GMCTRP1, 14, "\xD0\x04\x0D\x11\x13\x2B\x3F\x54\x4C\x18\x0D\x0B\x1F\x23");
d.sendCommand(PVGAMCTRL, []byte{0xD0, 0x04, 0x0D, 0x11, 0x13, 0x2B, 0x3F, 0x54, 0x4C, 0x18, 0x0D, 0x0B, 0x1F, 0x23})
// command(reg::GMCTRN1, 14, "\xD0\x04\x0C\x11\x13\x2C\x3F\x44\x51\x2F\x1F\x1F\x20\x23");
d.sendCommand(NVGAMCTRL, []byte{0xD0, 0x04, 0x0C, 0x11, 0x13, 0x2C, 0x3F, 0x44, 0x51, 0x2F, 0x1F, 0x1F, 0x20, 0x23})
case d.width == 320 && d.height == 240:
// Gate Control - power on sequence for 320x240
d.sendCommand(GCTRL, []byte{0x35})
// VCOM Setting - 0.875V
d.sendCommand(VCOMS, []byte{0x1f})
// command(reg::GMCTRP1, 14, "\xD0\x08\x11\x08\x0C\x15\x39\x33\x50\x36\x13\x14\x29\x2D");
d.sendCommand(PVGAMCTRL, []byte{0xD0, 0x08, 0x11, 0x08, 0x0C, 0x15, 0x39, 0x33, 0x50, 0x36, 0x13, 0x14, 0x29, 0x2D})
// command(reg::GMCTRN1, 14, "\xD0\x08\x10\x08\x06\x06\x39\x44\x51\x0B\x16\x14\x2F\x31");
d.sendCommand(NVGAMCTRL, []byte{0xD0, 0x08, 0x10, 0x08, 0x06, 0x06, 0x39, 0x44, 0x51, 0x0B, 0x16, 0x14, 0x2F, 0x31})
}
// Ready to display
d.sendCommand(INVON, nil) // Inversion ON
d.sendCommand(SLPOUT, nil) // Exit sleep mode
d.sendCommand(DISPON, nil) // Screen ON
d.endWrite() d.endWrite()
time.Sleep(100 * time.Millisecond)
d.startWrite()
d.setRotation(d.rotation) // Memory orientation
d.setWindow(0, 0, d.width, d.height) // Full draw window
d.fillScreen(color.RGBA{0, 0, 0, 255}) // Clear screen
d.endWrite()
time.Sleep(50 * time.Millisecond)
d.blPin.High() // Backlight ON d.blPin.High() // Backlight ON
} }
// Reset performs a hardware reset of the display.
func (d *DeviceOf[T]) Reset() {
d.resetPin.High()
time.Sleep(50 * time.Millisecond)
d.resetPin.Low()
time.Sleep(50 * time.Millisecond)
d.resetPin.High()
time.Sleep(50 * time.Millisecond)
}
// Send a command with data to the display. It does not change the chip select // Send a command with data to the display. It does not change the chip select
// pin (it must be low when calling). The DC pin is left high after return, // pin (it must be low when calling). The DC pin is left high after return,
// meaning that data can be sent right away. // meaning that data can be sent right away.
@@ -229,7 +300,7 @@ func (d *DeviceOf[T]) sendCommand(command uint8, data []byte) error {
// startWrite must be called at the beginning of all exported methods to set the // startWrite must be called at the beginning of all exported methods to set the
// chip select pin low. // chip select pin low.
func (d *DeviceOf[T]) startWrite() { func (d *DeviceOf[T]) startWrite() {
if d.csPin != machine.NoPin { if d.csPin != nil {
d.csPin.Low() d.csPin.Low()
} }
} }
@@ -237,7 +308,7 @@ func (d *DeviceOf[T]) startWrite() {
// endWrite must be called at the end of all exported methods to set the chip // endWrite must be called at the end of all exported methods to set the chip
// select pin high. // select pin high.
func (d *DeviceOf[T]) endWrite() { func (d *DeviceOf[T]) endWrite() {
if d.csPin != machine.NoPin { if d.csPin != nil {
d.csPin.High() d.csPin.High()
} }
} }
@@ -509,18 +580,20 @@ func (d *DeviceOf[T]) setRotation(rotation Rotation) error {
madctl := uint8(0) madctl := uint8(0)
switch rotation % 4 { switch rotation % 4 {
case drivers.Rotation0: case drivers.Rotation0:
madctl = MADCTL_COLORDER
madctl |= MADCTL_SWAPXY | MADCTL_SCANORDER
d.rowOffset = 0 d.rowOffset = 0
d.columnOffset = 0 d.columnOffset = 0
case drivers.Rotation90: case drivers.Rotation90:
madctl = MADCTL_MX | MADCTL_MV madctl = MADCTL_COLORDER | MADCTL_SWAPXY
d.rowOffset = 0 d.rowOffset = 0
d.columnOffset = 0 d.columnOffset = 0
case drivers.Rotation180: case drivers.Rotation180:
madctl = MADCTL_MX | MADCTL_MY madctl = MADCTL_COLORDER | MADCTL_ROWORDER
d.rowOffset = d.rowOffsetCfg d.rowOffset = d.rowOffsetCfg
d.columnOffset = d.columnOffsetCfg d.columnOffset = d.columnOffsetCfg
case drivers.Rotation270: case drivers.Rotation270:
madctl = MADCTL_MY | MADCTL_MV madctl = MADCTL_ROWORDER | MADCTL_SWAPXY
d.rowOffset = d.columnOffsetCfg d.rowOffset = d.columnOffsetCfg
d.columnOffset = d.rowOffsetCfg d.columnOffset = d.rowOffsetCfg
} }