mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +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() {
|
||||
|
||||
// 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{
|
||||
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()
|
||||
|
||||
|
||||
@@ -33,7 +33,9 @@ const (
|
||||
RDID3 = 0xDC
|
||||
RDID4 = 0xDD
|
||||
FRMCTR1 = 0xB1
|
||||
RGBCTRL = 0xB1
|
||||
FRMCTR2 = 0xB2
|
||||
PORCTRL = 0xB2
|
||||
FRMCTR3 = 0xB3
|
||||
INVCTR = 0xB4
|
||||
DISSET5 = 0xB6
|
||||
@@ -47,9 +49,45 @@ const (
|
||||
PWCTR6 = 0xFC
|
||||
GMCTRP1 = 0xE0
|
||||
GMCTRN1 = 0xE1
|
||||
GSCAN = 0x45
|
||||
|
||||
NO_ROTATION Rotation = 0
|
||||
ROTATION_90 Rotation = 1 // 90 degrees clock-wise rotation
|
||||
ROTATION_180 Rotation = 2
|
||||
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.
|
||||
//
|
||||
// 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"
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"errors"
|
||||
@@ -14,6 +16,8 @@ import (
|
||||
|
||||
type Rotation uint8
|
||||
|
||||
type FrameRate uint8
|
||||
|
||||
// Device wraps an SPI connection.
|
||||
type Device struct {
|
||||
bus machine.SPI
|
||||
@@ -28,8 +32,10 @@ type Device struct {
|
||||
columnOffset int16
|
||||
rowOffset int16
|
||||
rotation Rotation
|
||||
frameRate FrameRate
|
||||
batchLength int32
|
||||
isBGR bool
|
||||
vSyncLines int16
|
||||
}
|
||||
|
||||
// Config is the configuration for the display
|
||||
@@ -39,6 +45,8 @@ type Config struct {
|
||||
Rotation Rotation
|
||||
RowOffset int16
|
||||
ColumnOffset int16
|
||||
FrameRate FrameRate
|
||||
VSyncLines int16
|
||||
}
|
||||
|
||||
// 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.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)
|
||||
if d.height > d.width {
|
||||
d.batchLength = int32(d.height)
|
||||
}
|
||||
d.batchLength += d.batchLength & 1
|
||||
|
||||
// reset the device
|
||||
// Reset the device
|
||||
d.resetPin.High()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
d.resetPin.Low()
|
||||
@@ -87,46 +107,118 @@ func (d *Device) Configure(cfg Config) {
|
||||
d.resetPin.High()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
|
||||
|
||||
// Common initialization
|
||||
d.Command(SWRESET) // Soft reset
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
d.Command(SWRESET) // Soft reset
|
||||
time.Sleep(150 * time.Millisecond) //
|
||||
|
||||
d.Command(SLPOUT) // Exit sleep mode
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
d.Command(SLPOUT) // Exit sleep mode
|
||||
time.Sleep(500 * time.Millisecond) //
|
||||
|
||||
d.Command(COLMOD) // Set color mode
|
||||
d.Data(0x55) // 16-bit color
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
// Memory initialization
|
||||
d.Command(COLMOD) // Set color mode
|
||||
d.Data(0x55) // 16-bit color
|
||||
time.Sleep(10 * time.Millisecond) //
|
||||
|
||||
d.Command(MADCTL) // Memory orientation
|
||||
d.Data(0x08) // row/col bottom to top refresh
|
||||
d.SetRotation(d.rotation) // Memory orientation
|
||||
|
||||
d.Command(CASET) // Column addr range set
|
||||
d.Data(0x00)
|
||||
d.Data(0x00) // xstart (0) 2 bytes
|
||||
d.Data(240 >> 8)
|
||||
d.Data(240 & 0xFF) // xend (240) 2 bytes
|
||||
d.setWindow(0, 0, d.width, d.height) // Full draw window
|
||||
d.FillScreen(color.RGBA{0, 0, 0, 255}) // Clear screen
|
||||
|
||||
d.Command(RASET) // Row addr range set
|
||||
d.Data(0x00)
|
||||
d.Data(0x00) // ystart (0) 2 bytes
|
||||
d.Data(320 >> 8)
|
||||
d.Data(320 & 0xFF) // yend (320) 2 bytes
|
||||
// Framerate
|
||||
d.Command(FRCTRL2) // Frame rate for normal mode
|
||||
d.Data(uint8(d.frameRate)) // Default is 60Hz
|
||||
|
||||
d.Command(INVON) // Inversion ON
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
// Frame vertical sync and "porch"
|
||||
//
|
||||
// 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
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
d.Command(PORCTRL)
|
||||
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
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
// Ready to display
|
||||
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
|
||||
@@ -155,8 +247,6 @@ func (d *Device) setWindow(x, y, w, h int16) {
|
||||
d.Command(RAMWR)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 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, i := d.Size()
|
||||
@@ -293,11 +383,24 @@ func (d *Device) Data(data uint8) {
|
||||
func (d *Device) Tx(data []byte, isCommand bool) {
|
||||
if isCommand {
|
||||
d.dcPin.Low()
|
||||
d.bus.Tx(data, nil)
|
||||
} else {
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user