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//
56package st7789 // import "tinygo.org/x/drivers/st7789"
67
78import (
89 "image/color"
910 "machine"
11+ "math"
1012 "time"
1113
1214 "errors"
1315)
1416
1517type Rotation uint8
1618
19+ type FrameRate uint8
20+
1721// Device wraps an SPI connection.
1822type 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
161251func (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) {
293383func (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