mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-16 04:43:25 +00:00
Add support for setting framerate, vsync pause, and reading scanline data.
- FrameRate option sets device framerates from 39-111Hz - VSyncLines option adjusts device vsync pause using st7789 "porch control" feature - Added Rx(cmd, bytes[]) to support retrieving scanline timing data over SPI - Added Sync functions to support syncronization of animation to vertical scanline timing - Minor adjustments to Configure to clear screen memory before display is visible
This commit is contained in:
committed by
Ron Evans
parent
db02cbb8a4
commit
ce9c93f228
+28
-3
@@ -9,12 +9,37 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
|
// Example configuration for Adafruit Clue
|
||||||
|
// machine.SPI1.Configure(machine.SPIConfig{
|
||||||
|
// Frequency: 8000000,
|
||||||
|
// SCK: machine.TFT_SCK,
|
||||||
|
// SDO: machine.TFT_SDO,
|
||||||
|
// SDI: machine.TFT_SDO,
|
||||||
|
// Mode: 0,
|
||||||
|
// })
|
||||||
|
// display := st7789.New(machine.SPI1,
|
||||||
|
// machine.TFT_RESET,
|
||||||
|
// machine.TFT_DC,
|
||||||
|
// machine.TFT_CS,
|
||||||
|
// machine.TFT_LITE)
|
||||||
|
|
||||||
machine.SPI0.Configure(machine.SPIConfig{
|
machine.SPI0.Configure(machine.SPIConfig{
|
||||||
Frequency: 8000000,
|
Frequency: 8000000,
|
||||||
Mode: 3,
|
Mode: 0,
|
||||||
|
})
|
||||||
|
display := st7789.New(machine.SPI0,
|
||||||
|
machine.P6, // TFT_RESET
|
||||||
|
machine.P7, // TFT_DC
|
||||||
|
machine.P8, // TFT_CS
|
||||||
|
machine.P9) // TFT_LITE
|
||||||
|
|
||||||
|
display.Configure(st7789.Config{
|
||||||
|
Rotation: st7789.NO_ROTATION,
|
||||||
|
RowOffset: 80,
|
||||||
|
FrameRate: st7789.FRAMERATE_111,
|
||||||
|
VSyncLines: st7789.MAX_VSYNC_SCANLINES,
|
||||||
})
|
})
|
||||||
display := st7789.New(machine.SPI0, machine.P6, machine.P7, machine.P8, machine.P9)
|
|
||||||
display.Configure(st7789.Config{Rotation: st7789.NO_ROTATION})
|
|
||||||
|
|
||||||
width, height := display.Size()
|
width, height := display.Size()
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ const (
|
|||||||
RDID3 = 0xDC
|
RDID3 = 0xDC
|
||||||
RDID4 = 0xDD
|
RDID4 = 0xDD
|
||||||
FRMCTR1 = 0xB1
|
FRMCTR1 = 0xB1
|
||||||
|
RGBCTRL = 0xB1
|
||||||
FRMCTR2 = 0xB2
|
FRMCTR2 = 0xB2
|
||||||
|
PORCTRL = 0xB2
|
||||||
FRMCTR3 = 0xB3
|
FRMCTR3 = 0xB3
|
||||||
INVCTR = 0xB4
|
INVCTR = 0xB4
|
||||||
DISSET5 = 0xB6
|
DISSET5 = 0xB6
|
||||||
@@ -47,9 +49,45 @@ const (
|
|||||||
PWCTR6 = 0xFC
|
PWCTR6 = 0xFC
|
||||||
GMCTRP1 = 0xE0
|
GMCTRP1 = 0xE0
|
||||||
GMCTRN1 = 0xE1
|
GMCTRN1 = 0xE1
|
||||||
|
GSCAN = 0x45
|
||||||
|
|
||||||
NO_ROTATION Rotation = 0
|
NO_ROTATION Rotation = 0
|
||||||
ROTATION_90 Rotation = 1 // 90 degrees clock-wise rotation
|
ROTATION_90 Rotation = 1 // 90 degrees clock-wise rotation
|
||||||
ROTATION_180 Rotation = 2
|
ROTATION_180 Rotation = 2
|
||||||
ROTATION_270 Rotation = 3
|
ROTATION_270 Rotation = 3
|
||||||
|
|
||||||
|
// Allowable frame rate codes for FRCTRL2 (Identifier is in Hz)
|
||||||
|
FRAMERATE_111 FrameRate = 0x01
|
||||||
|
FRAMERATE_105 FrameRate = 0x02
|
||||||
|
FRAMERATE_99 FrameRate = 0x03
|
||||||
|
FRAMERATE_94 FrameRate = 0x04
|
||||||
|
FRAMERATE_90 FrameRate = 0x05
|
||||||
|
FRAMERATE_86 FrameRate = 0x06
|
||||||
|
FRAMERATE_82 FrameRate = 0x07
|
||||||
|
FRAMERATE_78 FrameRate = 0x08
|
||||||
|
FRAMERATE_75 FrameRate = 0x09
|
||||||
|
FRAMERATE_72 FrameRate = 0x0A
|
||||||
|
FRAMERATE_69 FrameRate = 0x0B
|
||||||
|
FRAMERATE_67 FrameRate = 0x0C
|
||||||
|
FRAMERATE_64 FrameRate = 0x0D
|
||||||
|
FRAMERATE_62 FrameRate = 0x0E
|
||||||
|
FRAMERATE_60 FrameRate = 0x0F // 60 is default
|
||||||
|
FRAMERATE_58 FrameRate = 0x10
|
||||||
|
FRAMERATE_57 FrameRate = 0x11
|
||||||
|
FRAMERATE_55 FrameRate = 0x12
|
||||||
|
FRAMERATE_53 FrameRate = 0x13
|
||||||
|
FRAMERATE_52 FrameRate = 0x14
|
||||||
|
FRAMERATE_50 FrameRate = 0x15
|
||||||
|
FRAMERATE_49 FrameRate = 0x16
|
||||||
|
FRAMERATE_48 FrameRate = 0x17
|
||||||
|
FRAMERATE_46 FrameRate = 0x18
|
||||||
|
FRAMERATE_45 FrameRate = 0x19
|
||||||
|
FRAMERATE_44 FrameRate = 0x1A
|
||||||
|
FRAMERATE_43 FrameRate = 0x1B
|
||||||
|
FRAMERATE_42 FrameRate = 0x1C
|
||||||
|
FRAMERATE_41 FrameRate = 0x1D
|
||||||
|
FRAMERATE_40 FrameRate = 0x1E
|
||||||
|
FRAMERATE_39 FrameRate = 0x1F
|
||||||
|
|
||||||
|
MAX_VSYNC_SCANLINES = 254
|
||||||
)
|
)
|
||||||
|
|||||||
+138
-35
@@ -1,12 +1,14 @@
|
|||||||
// Package st7789 implements a driver for the ST7789 TFT displays, it comes in various screen sizes.
|
// Package st7789 implements a driver for the ST7789 TFT displays, it comes in various screen sizes.
|
||||||
//
|
//
|
||||||
// Datasheet: https://cdn-shop.adafruit.com/product-files/3787/3787_tft_QT154H2201__________20190228182902.pdf
|
// Datasheets: https://cdn-shop.adafruit.com/product-files/3787/3787_tft_QT154H2201__________20190228182902.pdf
|
||||||
|
// http://www.newhavendisplay.com/appnotes/datasheets/LCDs/ST7789V.pdf
|
||||||
//
|
//
|
||||||
package st7789 // import "tinygo.org/x/drivers/st7789"
|
package st7789 // import "tinygo.org/x/drivers/st7789"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image/color"
|
"image/color"
|
||||||
"machine"
|
"machine"
|
||||||
|
"math"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"errors"
|
"errors"
|
||||||
@@ -14,6 +16,8 @@ import (
|
|||||||
|
|
||||||
type Rotation uint8
|
type Rotation uint8
|
||||||
|
|
||||||
|
type FrameRate uint8
|
||||||
|
|
||||||
// Device wraps an SPI connection.
|
// Device wraps an SPI connection.
|
||||||
type Device struct {
|
type Device struct {
|
||||||
bus machine.SPI
|
bus machine.SPI
|
||||||
@@ -28,8 +32,10 @@ type Device struct {
|
|||||||
columnOffset int16
|
columnOffset int16
|
||||||
rowOffset int16
|
rowOffset int16
|
||||||
rotation Rotation
|
rotation Rotation
|
||||||
|
frameRate FrameRate
|
||||||
batchLength int32
|
batchLength int32
|
||||||
isBGR bool
|
isBGR bool
|
||||||
|
vSyncLines int16
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config is the configuration for the display
|
// Config is the configuration for the display
|
||||||
@@ -39,6 +45,8 @@ type Config struct {
|
|||||||
Rotation Rotation
|
Rotation Rotation
|
||||||
RowOffset int16
|
RowOffset int16
|
||||||
ColumnOffset int16
|
ColumnOffset int16
|
||||||
|
FrameRate FrameRate
|
||||||
|
VSyncLines int16
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
@@ -73,13 +81,25 @@ func (d *Device) Configure(cfg Config) {
|
|||||||
d.rowOffsetCfg = cfg.RowOffset
|
d.rowOffsetCfg = cfg.RowOffset
|
||||||
d.columnOffsetCfg = cfg.ColumnOffset
|
d.columnOffsetCfg = cfg.ColumnOffset
|
||||||
|
|
||||||
|
if cfg.FrameRate != 0 {
|
||||||
|
d.frameRate = cfg.FrameRate
|
||||||
|
} else {
|
||||||
|
d.frameRate = FRAMERATE_60
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.VSyncLines >= 2 && cfg.VSyncLines <= 254 {
|
||||||
|
d.vSyncLines = cfg.VSyncLines
|
||||||
|
} else {
|
||||||
|
d.vSyncLines = 16
|
||||||
|
}
|
||||||
|
|
||||||
d.batchLength = int32(d.width)
|
d.batchLength = int32(d.width)
|
||||||
if d.height > d.width {
|
if d.height > d.width {
|
||||||
d.batchLength = int32(d.height)
|
d.batchLength = int32(d.height)
|
||||||
}
|
}
|
||||||
d.batchLength += d.batchLength & 1
|
d.batchLength += d.batchLength & 1
|
||||||
|
|
||||||
// reset the device
|
// Reset the device
|
||||||
d.resetPin.High()
|
d.resetPin.High()
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
d.resetPin.Low()
|
d.resetPin.Low()
|
||||||
@@ -87,46 +107,118 @@ func (d *Device) Configure(cfg Config) {
|
|||||||
d.resetPin.High()
|
d.resetPin.High()
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Common initialization
|
// Common initialization
|
||||||
d.Command(SWRESET) // Soft reset
|
d.Command(SWRESET) // Soft reset
|
||||||
time.Sleep(150 * time.Millisecond)
|
time.Sleep(150 * time.Millisecond) //
|
||||||
|
|
||||||
d.Command(SLPOUT) // Exit sleep mode
|
d.Command(SLPOUT) // Exit sleep mode
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond) //
|
||||||
|
|
||||||
d.Command(COLMOD) // Set color mode
|
// Memory initialization
|
||||||
d.Data(0x55) // 16-bit color
|
d.Command(COLMOD) // Set color mode
|
||||||
time.Sleep(10 * time.Millisecond)
|
d.Data(0x55) // 16-bit color
|
||||||
|
time.Sleep(10 * time.Millisecond) //
|
||||||
|
|
||||||
d.Command(MADCTL) // Memory orientation
|
d.SetRotation(d.rotation) // Memory orientation
|
||||||
d.Data(0x08) // row/col bottom to top refresh
|
|
||||||
|
|
||||||
d.Command(CASET) // Column addr range set
|
d.setWindow(0, 0, d.width, d.height) // Full draw window
|
||||||
d.Data(0x00)
|
d.FillScreen(color.RGBA{0, 0, 0, 255}) // Clear screen
|
||||||
d.Data(0x00) // xstart (0) 2 bytes
|
|
||||||
d.Data(240 >> 8)
|
|
||||||
d.Data(240 & 0xFF) // xend (240) 2 bytes
|
|
||||||
|
|
||||||
d.Command(RASET) // Row addr range set
|
// Framerate
|
||||||
d.Data(0x00)
|
d.Command(FRCTRL2) // Frame rate for normal mode
|
||||||
d.Data(0x00) // ystart (0) 2 bytes
|
d.Data(uint8(d.frameRate)) // Default is 60Hz
|
||||||
d.Data(320 >> 8)
|
|
||||||
d.Data(320 & 0xFF) // yend (320) 2 bytes
|
|
||||||
|
|
||||||
d.Command(INVON) // Inversion ON
|
// Frame vertical sync and "porch"
|
||||||
time.Sleep(10 * time.Millisecond)
|
//
|
||||||
|
// Front and back porch controls vertical scanline sync time before and after
|
||||||
|
// a frame, where memory can be safely written without tearing.
|
||||||
|
//
|
||||||
|
fp := uint8(d.vSyncLines / 2) // Split the desired pause half and half
|
||||||
|
bp := uint8(d.vSyncLines - int16(fp)) // between front and back porch.
|
||||||
|
|
||||||
d.Command(NORON) // Normal mode ON
|
d.Command(PORCTRL)
|
||||||
time.Sleep(10 * time.Millisecond)
|
d.Data(bp) // Back porch 5bit (0x7F max 0x08 default)
|
||||||
|
d.Data(fp) // Front porch 5bit (0x7F max 0x08 default)
|
||||||
|
d.Data(0x00) // Seprarate porch (TODO: what is this?)
|
||||||
|
d.Data(0x22) // Idle mode porch (4bit-back 4bit-front 0x22 default)
|
||||||
|
d.Data(0x22) // Partial mode porch (4bit-back 4bit-front 0x22 default)
|
||||||
|
|
||||||
d.Command(DISPON) // Screen ON
|
// Ready to display
|
||||||
time.Sleep(10 * time.Millisecond)
|
d.Command(INVON) // Inversion ON
|
||||||
|
time.Sleep(10 * time.Millisecond) //
|
||||||
|
|
||||||
d.SetRotation(d.rotation)
|
d.Command(NORON) // Normal mode ON
|
||||||
|
time.Sleep(10 * time.Millisecond) //
|
||||||
|
|
||||||
d.blPin.High()
|
d.Command(DISPON) // Screen ON
|
||||||
|
time.Sleep(10 * time.Millisecond) //
|
||||||
|
|
||||||
|
d.blPin.High() // Backlight ON
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync waits for the display to hit the next VSYNC pause
|
||||||
|
func (d *Device) Sync() {
|
||||||
|
d.SyncToScanLine(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncToScanLine waits for the display to hit a specific scanline
|
||||||
|
//
|
||||||
|
// A scanline value of 0 will forward to the beginning of the next VSYNC,
|
||||||
|
// even if the display is currently in a VSYNC pause.
|
||||||
|
//
|
||||||
|
// Syncline values appear to increment once for every two vertical
|
||||||
|
// lines on the display.
|
||||||
|
//
|
||||||
|
// NOTE: Use GetHighestScanLine and GetLowestScanLine to obtain the highest
|
||||||
|
// and lowest useful values. Values are affected by front and back porch
|
||||||
|
// vsync settings (derived from VSyncLines configuration option).
|
||||||
|
//
|
||||||
|
func (d *Device) SyncToScanLine(scanline uint16) {
|
||||||
|
scan := d.GetScanLine()
|
||||||
|
|
||||||
|
// Sometimes GetScanLine returns erroneous 0 on first call after draw, so double check
|
||||||
|
if scan == 0 {
|
||||||
|
scan = d.GetScanLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
if scanline == 0 {
|
||||||
|
// we dont know where we are in an ongoing vsync so go around
|
||||||
|
for scan < 1 {
|
||||||
|
time.Sleep(1 * time.Millisecond)
|
||||||
|
scan = d.GetScanLine()
|
||||||
|
}
|
||||||
|
for scan > 0 {
|
||||||
|
scan = d.GetScanLine()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// go around unless we're very close to the target
|
||||||
|
for scan > scanline+4 {
|
||||||
|
time.Sleep(1 * time.Millisecond)
|
||||||
|
scan = d.GetScanLine()
|
||||||
|
}
|
||||||
|
for scan < scanline {
|
||||||
|
scan = d.GetScanLine()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetScanLine reads the current scanline value from the display
|
||||||
|
func (d *Device) GetScanLine() uint16 {
|
||||||
|
data := []uint8{0x00, 0x00}
|
||||||
|
d.Rx(GSCAN, data)
|
||||||
|
return uint16(data[0])<<8 + uint16(data[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHighestScanLine calculates the last scanline id in the frame before VSYNC pause
|
||||||
|
func (d *Device) GetHighestScanLine() uint16 {
|
||||||
|
// Last scanline id appears to be backporch/2 + 320/2
|
||||||
|
return uint16(math.Ceil(float64(d.vSyncLines)/2)/2) + 160
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLowestScanLine calculate the first scanline id to appear after VSYNC pause
|
||||||
|
func (d *Device) GetLowestScanLine() uint16 {
|
||||||
|
// First scanline id appears to be backporch/2 + 1
|
||||||
|
return uint16(math.Ceil(float64(d.vSyncLines)/2)/2) + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display does nothing, there's no buffer as it might be too big for some boards
|
// Display does nothing, there's no buffer as it might be too big for some boards
|
||||||
@@ -155,8 +247,6 @@ func (d *Device) setWindow(x, y, w, h int16) {
|
|||||||
d.Command(RAMWR)
|
d.Command(RAMWR)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// FillRectangle fills a rectangle at a given coordinates with a color
|
// FillRectangle fills a rectangle at a given coordinates with a color
|
||||||
func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
|
func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
|
||||||
k, i := d.Size()
|
k, i := d.Size()
|
||||||
@@ -293,11 +383,24 @@ func (d *Device) Data(data uint8) {
|
|||||||
func (d *Device) Tx(data []byte, isCommand bool) {
|
func (d *Device) Tx(data []byte, isCommand bool) {
|
||||||
if isCommand {
|
if isCommand {
|
||||||
d.dcPin.Low()
|
d.dcPin.Low()
|
||||||
d.bus.Tx(data, nil)
|
|
||||||
} else {
|
} else {
|
||||||
d.dcPin.High()
|
d.dcPin.High()
|
||||||
d.bus.Tx(data, nil)
|
|
||||||
}
|
}
|
||||||
|
d.csPin.Low()
|
||||||
|
d.bus.Tx(data, nil)
|
||||||
|
d.csPin.High()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rx reads data from the display
|
||||||
|
func (d *Device) Rx(command uint8, read_bytes []byte) {
|
||||||
|
d.dcPin.Low()
|
||||||
|
d.csPin.Low()
|
||||||
|
d.bus.Transfer(command)
|
||||||
|
d.dcPin.High()
|
||||||
|
for i := range read_bytes {
|
||||||
|
read_bytes[i], _ = d.bus.Transfer(0xFF)
|
||||||
|
}
|
||||||
|
d.csPin.High()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size returns the current size of the display.
|
// Size returns the current size of the display.
|
||||||
|
|||||||
Reference in New Issue
Block a user