Skip to content

Commit ce9c93f

Browse files
jmstriegeldeadprogram
authored andcommitted
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
1 parent db02cbb commit ce9c93f

File tree

3 files changed

+203
-37
lines changed

3 files changed

+203
-37
lines changed

examples/st7789/main.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,37 @@ import (
99
)
1010

1111
func main() {
12+
13+
// Example configuration for Adafruit Clue
14+
// machine.SPI1.Configure(machine.SPIConfig{
15+
// Frequency: 8000000,
16+
// SCK: machine.TFT_SCK,
17+
// SDO: machine.TFT_SDO,
18+
// SDI: machine.TFT_SDO,
19+
// Mode: 0,
20+
// })
21+
// display := st7789.New(machine.SPI1,
22+
// machine.TFT_RESET,
23+
// machine.TFT_DC,
24+
// machine.TFT_CS,
25+
// machine.TFT_LITE)
26+
1227
machine.SPI0.Configure(machine.SPIConfig{
1328
Frequency: 8000000,
14-
Mode: 3,
29+
Mode: 0,
30+
})
31+
display := st7789.New(machine.SPI0,
32+
machine.P6, // TFT_RESET
33+
machine.P7, // TFT_DC
34+
machine.P8, // TFT_CS
35+
machine.P9) // TFT_LITE
36+
37+
display.Configure(st7789.Config{
38+
Rotation: st7789.NO_ROTATION,
39+
RowOffset: 80,
40+
FrameRate: st7789.FRAMERATE_111,
41+
VSyncLines: st7789.MAX_VSYNC_SCANLINES,
1542
})
16-
display := st7789.New(machine.SPI0, machine.P6, machine.P7, machine.P8, machine.P9)
17-
display.Configure(st7789.Config{Rotation: st7789.NO_ROTATION})
1843

1944
width, height := display.Size()
2045

st7789/registers.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ const (
3333
RDID3 = 0xDC
3434
RDID4 = 0xDD
3535
FRMCTR1 = 0xB1
36+
RGBCTRL = 0xB1
3637
FRMCTR2 = 0xB2
38+
PORCTRL = 0xB2
3739
FRMCTR3 = 0xB3
3840
INVCTR = 0xB4
3941
DISSET5 = 0xB6
@@ -47,9 +49,45 @@ const (
4749
PWCTR6 = 0xFC
4850
GMCTRP1 = 0xE0
4951
GMCTRN1 = 0xE1
52+
GSCAN = 0x45
5053

5154
NO_ROTATION Rotation = 0
5255
ROTATION_90 Rotation = 1 // 90 degrees clock-wise rotation
5356
ROTATION_180 Rotation = 2
5457
ROTATION_270 Rotation = 3
58+
59+
// Allowable frame rate codes for FRCTRL2 (Identifier is in Hz)
60+
FRAMERATE_111 FrameRate = 0x01
61+
FRAMERATE_105 FrameRate = 0x02
62+
FRAMERATE_99 FrameRate = 0x03
63+
FRAMERATE_94 FrameRate = 0x04
64+
FRAMERATE_90 FrameRate = 0x05
65+
FRAMERATE_86 FrameRate = 0x06
66+
FRAMERATE_82 FrameRate = 0x07
67+
FRAMERATE_78 FrameRate = 0x08
68+
FRAMERATE_75 FrameRate = 0x09
69+
FRAMERATE_72 FrameRate = 0x0A
70+
FRAMERATE_69 FrameRate = 0x0B
71+
FRAMERATE_67 FrameRate = 0x0C
72+
FRAMERATE_64 FrameRate = 0x0D
73+
FRAMERATE_62 FrameRate = 0x0E
74+
FRAMERATE_60 FrameRate = 0x0F // 60 is default
75+
FRAMERATE_58 FrameRate = 0x10
76+
FRAMERATE_57 FrameRate = 0x11
77+
FRAMERATE_55 FrameRate = 0x12
78+
FRAMERATE_53 FrameRate = 0x13
79+
FRAMERATE_52 FrameRate = 0x14
80+
FRAMERATE_50 FrameRate = 0x15
81+
FRAMERATE_49 FrameRate = 0x16
82+
FRAMERATE_48 FrameRate = 0x17
83+
FRAMERATE_46 FrameRate = 0x18
84+
FRAMERATE_45 FrameRate = 0x19
85+
FRAMERATE_44 FrameRate = 0x1A
86+
FRAMERATE_43 FrameRate = 0x1B
87+
FRAMERATE_42 FrameRate = 0x1C
88+
FRAMERATE_41 FrameRate = 0x1D
89+
FRAMERATE_40 FrameRate = 0x1E
90+
FRAMERATE_39 FrameRate = 0x1F
91+
92+
MAX_VSYNC_SCANLINES = 254
5593
)

st7789/st7789.go

Lines changed: 137 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
// Package st7789 implements a driver for the ST7789 TFT displays, it comes in various screen sizes.
22
//
3-
// Datasheet: https://cdn-shop.adafruit.com/product-files/3787/3787_tft_QT154H2201__________20190228182902.pdf
3+
// Datasheets: https://cdn-shop.adafruit.com/product-files/3787/3787_tft_QT154H2201__________20190228182902.pdf
4+
// http://www.newhavendisplay.com/appnotes/datasheets/LCDs/ST7789V.pdf
45
//
56
package st7789 // import "tinygo.org/x/drivers/st7789"
67

78
import (
89
"image/color"
910
"machine"
11+
"math"
1012
"time"
1113

1214
"errors"
1315
)
1416

1517
type Rotation uint8
1618

19+
type FrameRate uint8
20+
1721
// Device wraps an SPI connection.
1822
type Device struct {
1923
bus machine.SPI
@@ -28,8 +32,10 @@ type Device struct {
2832
columnOffset int16
2933
rowOffset int16
3034
rotation Rotation
35+
frameRate FrameRate
3136
batchLength int32
3237
isBGR bool
38+
vSyncLines int16
3339
}
3440

3541
// Config is the configuration for the display
@@ -39,6 +45,8 @@ type Config struct {
3945
Rotation Rotation
4046
RowOffset int16
4147
ColumnOffset int16
48+
FrameRate FrameRate
49+
VSyncLines int16
4250
}
4351

4452
// New creates a new ST7789 connection. The SPI wire must already be configured.
@@ -73,60 +81,144 @@ func (d *Device) Configure(cfg Config) {
7381
d.rowOffsetCfg = cfg.RowOffset
7482
d.columnOffsetCfg = cfg.ColumnOffset
7583

84+
if cfg.FrameRate != 0 {
85+
d.frameRate = cfg.FrameRate
86+
} else {
87+
d.frameRate = FRAMERATE_60
88+
}
89+
90+
if cfg.VSyncLines >= 2 && cfg.VSyncLines <= 254 {
91+
d.vSyncLines = cfg.VSyncLines
92+
} else {
93+
d.vSyncLines = 16
94+
}
95+
7696
d.batchLength = int32(d.width)
7797
if d.height > d.width {
7898
d.batchLength = int32(d.height)
7999
}
80100
d.batchLength += d.batchLength & 1
81101

82-
// reset the device
102+
// Reset the device
83103
d.resetPin.High()
84104
time.Sleep(50 * time.Millisecond)
85105
d.resetPin.Low()
86106
time.Sleep(50 * time.Millisecond)
87107
d.resetPin.High()
88108
time.Sleep(50 * time.Millisecond)
89109

110+
// Common initialization
111+
d.Command(SWRESET) // Soft reset
112+
time.Sleep(150 * time.Millisecond) //
90113

114+
d.Command(SLPOUT) // Exit sleep mode
115+
time.Sleep(500 * time.Millisecond) //
91116

92-
// Common initialization
93-
d.Command(SWRESET) // Soft reset
94-
time.Sleep(150 * time.Millisecond)
117+
// Memory initialization
118+
d.Command(COLMOD) // Set color mode
119+
d.Data(0x55) // 16-bit color
120+
time.Sleep(10 * time.Millisecond) //
95121

96-
d.Command(SLPOUT) // Exit sleep mode
97-
time.Sleep(500 * time.Millisecond)
122+
d.SetRotation(d.rotation) // Memory orientation
98123

99-
d.Command(COLMOD) // Set color mode
100-
d.Data(0x55) // 16-bit color
101-
time.Sleep(10 * time.Millisecond)
124+
d.setWindow(0, 0, d.width, d.height) // Full draw window
125+
d.FillScreen(color.RGBA{0, 0, 0, 255}) // Clear screen
102126

103-
d.Command(MADCTL) // Memory orientation
104-
d.Data(0x08) // row/col bottom to top refresh
127+
// Framerate
128+
d.Command(FRCTRL2) // Frame rate for normal mode
129+
d.Data(uint8(d.frameRate)) // Default is 60Hz
105130

106-
d.Command(CASET) // Column addr range set
107-
d.Data(0x00)
108-
d.Data(0x00) // xstart (0) 2 bytes
109-
d.Data(240 >> 8)
110-
d.Data(240 & 0xFF) // xend (240) 2 bytes
131+
// Frame vertical sync and "porch"
132+
//
133+
// Front and back porch controls vertical scanline sync time before and after
134+
// a frame, where memory can be safely written without tearing.
135+
//
136+
fp := uint8(d.vSyncLines / 2) // Split the desired pause half and half
137+
bp := uint8(d.vSyncLines - int16(fp)) // between front and back porch.
111138

112-
d.Command(RASET) // Row addr range set
113-
d.Data(0x00)
114-
d.Data(0x00) // ystart (0) 2 bytes
115-
d.Data(320 >> 8)
116-
d.Data(320 & 0xFF) // yend (320) 2 bytes
139+
d.Command(PORCTRL)
140+
d.Data(bp) // Back porch 5bit (0x7F max 0x08 default)
141+
d.Data(fp) // Front porch 5bit (0x7F max 0x08 default)
142+
d.Data(0x00) // Seprarate porch (TODO: what is this?)
143+
d.Data(0x22) // Idle mode porch (4bit-back 4bit-front 0x22 default)
144+
d.Data(0x22) // Partial mode porch (4bit-back 4bit-front 0x22 default)
145+
146+
// Ready to display
147+
d.Command(INVON) // Inversion ON
148+
time.Sleep(10 * time.Millisecond) //
149+
150+
d.Command(NORON) // Normal mode ON
151+
time.Sleep(10 * time.Millisecond) //
152+
153+
d.Command(DISPON) // Screen ON
154+
time.Sleep(10 * time.Millisecond) //
155+
156+
d.blPin.High() // Backlight ON
157+
}
158+
159+
// Sync waits for the display to hit the next VSYNC pause
160+
func (d *Device) Sync() {
161+
d.SyncToScanLine(0)
162+
}
163+
164+
// SyncToScanLine waits for the display to hit a specific scanline
165+
//
166+
// A scanline value of 0 will forward to the beginning of the next VSYNC,
167+
// even if the display is currently in a VSYNC pause.
168+
//
169+
// Syncline values appear to increment once for every two vertical
170+
// lines on the display.
171+
//
172+
// NOTE: Use GetHighestScanLine and GetLowestScanLine to obtain the highest
173+
// and lowest useful values. Values are affected by front and back porch
174+
// vsync settings (derived from VSyncLines configuration option).
175+
//
176+
func (d *Device) SyncToScanLine(scanline uint16) {
177+
scan := d.GetScanLine()
117178

118-
d.Command(INVON) // Inversion ON
119-
time.Sleep(10 * time.Millisecond)
179+
// Sometimes GetScanLine returns erroneous 0 on first call after draw, so double check
180+
if scan == 0 {
181+
scan = d.GetScanLine()
182+
}
120183

121-
d.Command(NORON) // Normal mode ON
122-
time.Sleep(10 * time.Millisecond)
184+
if scanline == 0 {
185+
// we dont know where we are in an ongoing vsync so go around
186+
for scan < 1 {
187+
time.Sleep(1 * time.Millisecond)
188+
scan = d.GetScanLine()
189+
}
190+
for scan > 0 {
191+
scan = d.GetScanLine()
192+
}
193+
} else {
194+
// go around unless we're very close to the target
195+
for scan > scanline+4 {
196+
time.Sleep(1 * time.Millisecond)
197+
scan = d.GetScanLine()
198+
}
199+
for scan < scanline {
200+
scan = d.GetScanLine()
201+
}
202+
}
203+
}
123204

124-
d.Command(DISPON) // Screen ON
125-
time.Sleep(10 * time.Millisecond)
205+
// GetScanLine reads the current scanline value from the display
206+
func (d *Device) GetScanLine() uint16 {
207+
data := []uint8{0x00, 0x00}
208+
d.Rx(GSCAN, data)
209+
return uint16(data[0])<<8 + uint16(data[1])
210+
}
126211

127-
d.SetRotation(d.rotation)
212+
// GetHighestScanLine calculates the last scanline id in the frame before VSYNC pause
213+
func (d *Device) GetHighestScanLine() uint16 {
214+
// Last scanline id appears to be backporch/2 + 320/2
215+
return uint16(math.Ceil(float64(d.vSyncLines)/2)/2) + 160
216+
}
128217

129-
d.blPin.High()
218+
// GetLowestScanLine calculate the first scanline id to appear after VSYNC pause
219+
func (d *Device) GetLowestScanLine() uint16 {
220+
// First scanline id appears to be backporch/2 + 1
221+
return uint16(math.Ceil(float64(d.vSyncLines)/2)/2) + 1
130222
}
131223

132224
// 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) {
155247
d.Command(RAMWR)
156248
}
157249

158-
159-
160250
// FillRectangle fills a rectangle at a given coordinates with a color
161251
func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
162252
k, i := d.Size()
@@ -293,11 +383,24 @@ func (d *Device) Data(data uint8) {
293383
func (d *Device) Tx(data []byte, isCommand bool) {
294384
if isCommand {
295385
d.dcPin.Low()
296-
d.bus.Tx(data, nil)
297386
} else {
298387
d.dcPin.High()
299-
d.bus.Tx(data, nil)
300388
}
389+
d.csPin.Low()
390+
d.bus.Tx(data, nil)
391+
d.csPin.High()
392+
}
393+
394+
// Rx reads data from the display
395+
func (d *Device) Rx(command uint8, read_bytes []byte) {
396+
d.dcPin.Low()
397+
d.csPin.Low()
398+
d.bus.Transfer(command)
399+
d.dcPin.High()
400+
for i := range read_bytes {
401+
read_bytes[i], _ = d.bus.Transfer(0xFF)
402+
}
403+
d.csPin.High()
301404
}
302405

303406
// Size returns the current size of the display.

0 commit comments

Comments
 (0)