Compare commits

...

7 Commits

Author SHA1 Message Date
Ron Evans b4dbac3a67 release: update CHANGELOG for v0.4.0 release
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-08-07 15:44:00 +02:00
Daniel Esteban bf077c8249 SSD1331 TFT color display (#77) 2019-08-05 17:54:23 +02:00
Daniel Esteban 4867abcbba added driver for ST7789 TFT color display (#76) 2019-08-05 14:52:39 +02:00
Daniel Esteban 45922f6524 Driver ST7735 for TFT color displays (#72)
* Driver ST7735 for TFT color displays
2019-08-03 11:13:15 +02:00
Daniel Esteban eb040dde9c Merge pull request #78 from conejoninja/typo
typo
2019-08-02 19:57:51 +02:00
Daniel Esteban 04bfa6fa70 typo 2019-08-02 19:49:19 +02:00
Ayke van Laethem 8453611d1f espat: update README with how to install dependencies 2019-07-24 15:48:19 -07:00
15 changed files with 1329 additions and 2 deletions
+10
View File
@@ -1,3 +1,13 @@
0.4.0
---
- **new devices**
- SSD1331 TFT color display
- ST7735 TFT color display
- ST7789 TFT color display
- **docs**
- espat
- complete list of dependencies for flashing NINA-W102 as used in Arduino Nano33 IoT board.
0.3.0
---
- **new devices**
+3
View File
@@ -37,6 +37,9 @@ smoke-test:
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/sht3x/main.go
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/ssd1306/i2c_128x32/main.go
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/ssd1306/spi_128x64/main.go
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/ssd1331/main.go
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/st7735/main.go
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/st7789/main.go
tinygo build -size short -o ./build/test.elf -target=circuitplay-express ./examples/thermistor/main.go
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/vl53l1x/main.go
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/waveshare-epd/epd2in13/main.go
+3
View File
@@ -77,6 +77,9 @@ func main() {
| [PCD8544 display](http://eia.udg.edu/~forest/PCD8544_1.pdf) | SPI |
| [SHT3x Digital Humidity Sensor](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/0_Datasheets/Humidity/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf) | I2C |
| [SSD1306 OLED display](https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf) | I2C / SPI |
| [SSD1331 TFT color display](https://www.crystalfontz.com/controllers/SolomonSystech/SSD1331/381/) | SPI |
| [ST7735 TFT color display](https://www.crystalfontz.com/controllers/Sitronix/ST7735R/319/) | SPI |
| [ST7789 TFT color display](https://cdn-shop.adafruit.com/product-files/3787/3787_tft_QT154H2201__________20190228182902.pdf) | SPI |
| [Thermistor](https://www.farnell.com/datasheets/33552.pdf) | ADC |
| [VL53L1X time-of-flight distance sensor](https://www.st.com/resource/en/datasheet/vl53l1x.pdf) | I2C |
| [Waveshare 2.13" e-paper display](https://www.waveshare.com/w/upload/e/e6/2.13inch_e-Paper_Datasheet.pdf) | SPI |
+3 -1
View File
@@ -16,7 +16,9 @@ https://github.com/hybridgroup/esp32-at
To flash this firmware on the Arduino Nano33 IoT you will need to follow the following procedure:
- Using the normal Arduino software, load the `SerialNINAPassthrough` sketch on to the board.
- Install _Arduino SAMD Boards_ from the Boards Manager.
- Install _WiFiNANO_ from the Library Manager.
- Using the normal Arduino software, load the `SerialNINAPassthrough` sketch on to the board (in File -> Examples -> WiFiNINA-> Tools).
- Flash the NINA 102 firmware using the `make flash` command in the https://github.com/hybridgroup/esp32-at repo.
You only need to do this one time, and then the correct ESP-AT firmware will be on the NINA chip, and you can just flash the Arduino Nano33 IoT board using TinyGo. We should be able to remove some of these step in a future release of this software.
+33
View File
@@ -0,0 +1,33 @@
package ssd1331
import (
"machine"
"image/color"
"tinygo.org/x/drivers/ssd1331"
)
func main() {
machine.SPI0.Configure(machine.SPIConfig{
Frequency: 8000000,
})
display := ssd1331.New(machine.SPI0, machine.P6, machine.P7, machine.P8)
display.Configure(ssd1331.Config{})
display.SetContrast(0x30, 0x20, 0x30)
width, height := display.Size()
white := color.RGBA{255, 255, 255, 255}
red := color.RGBA{255, 0, 0, 255}
blue := color.RGBA{0, 0, 255, 255}
green := color.RGBA{0, 255, 0, 255}
black := color.RGBA{0, 0, 0, 255}
display.FillScreen(black)
display.FillRectangle(0, 0, width/2, height/2, white)
display.FillRectangle(width/2, 0, width/2, height/2, red)
display.FillRectangle(0, height/2, width/2, height/2, green)
display.FillRectangle(width/2, height/2, width/2, height/2, blue)
display.FillRectangle(width/4, height/4, width/2, height/2, black)
}
+33
View File
@@ -0,0 +1,33 @@
package main
import (
"machine"
"image/color"
"tinygo.org/x/drivers/st7735"
)
func main() {
machine.SPI0.Configure(machine.SPIConfig{
Frequency: 8000000,
})
display := st7735.New(machine.SPI0, machine.P6, machine.P7, machine.P8, machine.P9)
display.Configure(st7735.Config{})
width, height := display.Size()
white := color.RGBA{255, 255, 255, 255}
red := color.RGBA{255, 0, 0, 255}
blue := color.RGBA{0, 0, 255, 255}
green := color.RGBA{0, 255, 0, 255}
black := color.RGBA{0, 0, 0, 255}
display.FillScreen(black)
display.FillRectangle(0, 0, width/2, height/2, white)
display.FillRectangle(width/2, 0, width/2, height/2, red)
display.FillRectangle(0, height/2, width/2, height/2, green)
display.FillRectangle(width/2, height/2, width/2, height/2, blue)
display.FillRectangle(width/4, height/4, width/2, height/2, black)
}
+34
View File
@@ -0,0 +1,34 @@
package main
import (
"machine"
"image/color"
"tinygo.org/x/drivers/st7789"
)
func main() {
machine.SPI0.Configure(machine.SPIConfig{
Frequency: 8000000,
Mode: 3,
})
display := st7789.New(machine.SPI0, machine.P6, machine.P7, machine.P8)
display.Configure(st7789.Config{Rotation: st7789.NO_ROTATION})
width, height := display.Size()
white := color.RGBA{255, 255, 255, 255}
red := color.RGBA{255, 0, 0, 255}
blue := color.RGBA{0, 0, 255, 255}
green := color.RGBA{0, 255, 0, 255}
black := color.RGBA{0, 0, 0, 255}
display.FillScreen(black)
display.FillRectangle(0, 0, width/2, height/2, white)
display.FillRectangle(width/2, 0, width/2, height/2, red)
display.FillRectangle(0, height/2, width/2, height/2, green)
display.FillRectangle(width/2, height/2, width/2, height/2, blue)
display.FillRectangle(width/4, height/4, width/2, height/2, black)
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Package lis3dh provides a driver for the HD44780 LCD controller.
// Package hd44780 provides a driver for the HD44780 LCD controller.
//
// Datasheet: https://www.sparkfun.com/datasheets/LCD/HD44780.pdf
//
+33
View File
@@ -0,0 +1,33 @@
package ssd1331
// Registers
const (
DRAWLINE = 0x21
DRAWRECT = 0x22
FILL = 0x26
SETCOLUMN = 0x15
SETROW = 0x75
CONTRASTA = 0x81
CONTRASTB = 0x82
CONTRASTC = 0x83
MASTERCURRENT = 0x87
SETREMAP = 0xA0
STARTLINE = 0xA1
DISPLAYOFFSET = 0xA2
NORMALDISPLAY = 0xA4
DISPLAYALLON = 0xA5
DISPLAYALLOFF = 0xA6
INVERTDISPLAY = 0xA7
SETMULTIPLEX = 0xA8
SETMASTER = 0xAD
DISPLAYOFF = 0xAE
DISPLAYON = 0xAF
POWERMODE = 0xB0
PRECHARGE = 0xB1
CLOCKDIV = 0xB3
PRECHARGEA = 0x8A
PRECHARGEB = 0x8B
PRECHARGEC = 0x8C
PRECHARGELEVEL = 0xBB
VCOMH = 0xBE
)
+286
View File
@@ -0,0 +1,286 @@
// Package ssd1331 implements a driver for the SSD1331 TFT color displays.
//
// Datasheet: https://www.crystalfontz.com/controllers/SolomonSystech/SSD1331/381/
//
package ssd1331 // import "tinygo.org/x/drivers/ssd1331"
import (
"image/color"
"machine"
"errors"
"time"
)
type Model uint8
type Rotation uint8
// Device wraps an SPI connection.
type Device struct {
bus machine.SPI
dcPin machine.Pin
resetPin machine.Pin
csPin machine.Pin
width int16
height int16
batchLength int16
isBGR bool
batchData []uint8
}
// Config is the configuration for the display
type Config struct {
Width int16
Height int16
}
// New creates a new ST7735 connection. The SPI wire must already be configured.
func New(bus machine.SPI, resetPin, dcPin, csPin machine.Pin) Device {
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
return Device{
bus: bus,
dcPin: dcPin,
resetPin: resetPin,
csPin: csPin,
}
}
// Configure initializes the display with default configuration
func (d *Device) Configure(cfg Config) {
if cfg.Width != 0 {
d.width = cfg.Width
} else {
d.width = 96
}
if cfg.Height != 0 {
d.height = cfg.Height
} else {
d.height = 64
}
d.batchLength = d.width
if d.height > d.width {
d.batchLength = d.height
}
d.batchLength += d.batchLength & 1
d.batchData = make([]uint8, d.batchLength*2)
// reset the device
d.resetPin.High()
time.Sleep(100 * time.Millisecond)
d.resetPin.Low()
time.Sleep(100 * time.Millisecond)
d.resetPin.High()
time.Sleep(200 * time.Millisecond)
// Initialization
d.Command(DISPLAYOFF)
d.Command(SETREMAP)
d.Command(0x72) // RGB
//d.Command(0x76) // BGR
d.Command(STARTLINE)
d.Command(0x0)
d.Command(DISPLAYOFFSET)
d.Command(0x0)
d.Command(NORMALDISPLAY)
d.Command(SETMULTIPLEX)
d.Command(0x3F)
d.Command(SETMASTER)
d.Command(0x8E)
d.Command(POWERMODE)
d.Command(0x0B)
d.Command(PRECHARGE)
d.Command(0x31)
d.Command(CLOCKDIV)
d.Command(0xF0)
d.Command(PRECHARGEA)
d.Command(0x64)
d.Command(PRECHARGEB)
d.Command(0x78)
d.Command(PRECHARGEC)
d.Command(0x64)
d.Command(PRECHARGELEVEL)
d.Command(0x3A)
d.Command(VCOMH)
d.Command(0x3E)
d.Command(MASTERCURRENT)
d.Command(0x06)
d.Command(CONTRASTA)
d.Command(0x91)
d.Command(CONTRASTB)
d.Command(0x50)
d.Command(CONTRASTC)
d.Command(0x7D)
d.Command(DISPLAYON)
}
// Display does nothing, there's no buffer as it might be too big for some boards
func (d *Device) Display() error {
return nil
}
// SetPixel sets a pixel in the screen
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
if x < 0 || y < 0 || x >= d.width || y >= d.height {
return
}
d.FillRectangle(x, y, 1, 1, c)
}
// setWindow prepares the screen to be modified at a given rectangle
func (d *Device) setWindow(x, y, w, h int16) {
/*d.Tx([]uint8{SETCOLUMN}, true)
d.Tx([]uint8{uint8(x), uint8(x + w - 1)}, false)
d.Tx([]uint8{SETROW}, true)
d.Tx([]uint8{uint8(y), uint8(y + h - 1)}, false)*/
d.Command(SETCOLUMN)
d.Command(uint8(x))
d.Command(uint8(x + w - 1))
d.Command(SETROW)
d.Command(uint8(y))
d.Command(uint8(y + h - 1))
}
// FillRectangle fills a rectangle at a given coordinates with a color
func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
x >= d.width || (x+width) > d.width || y >= d.height || (y+height) > d.height {
return errors.New("rectangle coordinates outside display area")
}
d.setWindow(x, y, width, height)
c565 := RGBATo565(c)
c1 := uint8(c565 >> 8)
c2 := uint8(c565)
var i int16
for i = 0; i < d.batchLength; i++ {
d.batchData[i*2] = c1
d.batchData[i*2+1] = c2
}
i = width * height
for i > 0 {
if i >= d.batchLength {
d.Tx(d.batchData, false)
} else {
d.Tx(d.batchData[:i*2], false)
}
i -= d.batchLength
}
return nil
}
// FillRectangle fills a rectangle at a given coordinates with a buffer
func (d *Device) FillRectangleWithBuffer(x, y, width, height int16, buffer []color.RGBA) error {
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
x >= d.width || (x+width) > d.width || y >= d.height || (y+height) > d.height {
return errors.New("rectangle coordinates outside display area")
}
k := width * height
l := int16(len(buffer))
if k != l {
return errors.New("buffer length does not match with rectangle size")
}
d.setWindow(x, y, width, height)
offset := int16(0)
for k > 0 {
for i := int16(0); i < d.batchLength; i++ {
if offset+i < l {
c565 := RGBATo565(buffer[offset+i])
c1 := uint8(c565 >> 8)
c2 := uint8(c565)
d.batchData[i*2] = c1
d.batchData[i*2+1] = c2
}
}
if k >= d.batchLength {
d.Tx(d.batchData, false)
} else {
d.Tx(d.batchData[:k*2], false)
}
k -= d.batchLength
offset += d.batchLength
}
return nil
}
// DrawFastVLine draws a vertical line faster than using SetPixel
func (d *Device) DrawFastVLine(x, y0, y1 int16, c color.RGBA) {
if y0 > y1 {
y0, y1 = y1, y0
}
d.FillRectangle(x, y0, 1, y1-y0+1, c)
}
// DrawFastHLine draws a horizontal line faster than using SetPixel
func (d *Device) DrawFastHLine(x0, x1, y int16, c color.RGBA) {
if x0 > x1 {
x0, x1 = x1, x0
}
d.FillRectangle(x0, y, x1-x0+1, y, c)
}
// FillScreen fills the screen with a given color
func (d *Device) FillScreen(c color.RGBA) {
d.FillRectangle(0, 0, d.width, d.height, c)
}
// SetContrast sets the three contrast values (A, B & C)
func (d *Device) SetContrast(contrastA, contrastB, contrastC uint8) {
d.Command(CONTRASTA)
d.Command(contrastA)
d.Command(CONTRASTB)
d.Command(contrastB)
d.Command(CONTRASTC)
d.Command(contrastC)
}
// Command sends a command to the display
func (d *Device) Command(command uint8) {
d.Tx([]byte{command}, true)
}
// Command sends a data to the display
func (d *Device) Data(data uint8) {
d.Tx([]byte{data}, false)
}
// Tx sends data to the display
func (d *Device) Tx(data []byte, isCommand bool) {
if isCommand {
d.csPin.High()
d.dcPin.Low()
d.csPin.Low()
d.bus.Tx(data, nil)
d.csPin.High()
} else {
d.csPin.High()
d.dcPin.High()
d.csPin.Low()
d.bus.Tx(data, nil)
d.csPin.High()
}
}
// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
return d.width, d.height
}
// IsBGR changes the color mode (RGB/BGR)
func (d *Device) IsBGR(bgr bool) {
d.isBGR = bgr
}
// RGBATo565 converts a color.RGBA to uint16 used in the display
func RGBATo565(c color.RGBA) uint16 {
r, g, b, _ := c.RGBA()
return uint16((r & 0xF800) +
((g & 0xFC00) >> 5) +
((b & 0xF800) >> 11))
}
+9
View File
@@ -0,0 +1,9 @@
# ST7735 driver
There are multiple devices using the ST7735 chip, and there are multiple versions ST7735B, ST7735R & ST7735S. Two apparently identical displays might have different configurations. The most common issues are:
* Colors are inverted (black is white and viceversa), invert the colors with display.InvertColors(true)
* Colors are not right (red is blue and viceversa, but green is ok), some displays uses BRG instead of RGB for defining colors, change the mode with display.IsBGR(true)
* There is noise/snow/confetti in the screen, probably rows and columns offsets are wrong, configure them with st7735.Config{RowOffset:XX, ColumnOffset:YY}
If nothing of the above works, your device may need a different boot-up process.
+57
View File
@@ -0,0 +1,57 @@
package st7735
// Registers
const (
NOP = 0x00
SWRESET = 0x01
RDDID = 0x04
RDDST = 0x09
SLPIN = 0x10
SLPOUT = 0x11
PTLON = 0x12
NORON = 0x13
INVOFF = 0x20
INVON = 0x21
DISPOFF = 0x28
DISPON = 0x29
CASET = 0x2A
RASET = 0x2B
RAMWR = 0x2C
RAMRD = 0x2E
PTLAR = 0x30
COLMOD = 0x3A
MADCTL = 0x36
MADCTL_MY = 0x80
MADCTL_MX = 0x40
MADCTL_MV = 0x20
MADCTL_ML = 0x10
MADCTL_RGB = 0x00
MADCTL_BGR = 0x08
MADCTL_MH = 0x04
RDID1 = 0xDA
RDID2 = 0xDB
RDID3 = 0xDC
RDID4 = 0xDD
FRMCTR1 = 0xB1
FRMCTR2 = 0xB2
FRMCTR3 = 0xB3
INVCTR = 0xB4
DISSET5 = 0xB6
PWCTR1 = 0xC0
PWCTR2 = 0xC1
PWCTR3 = 0xC2
PWCTR4 = 0xC3
PWCTR5 = 0xC4
VMCTR1 = 0xC5
PWCTR6 = 0xFC
GMCTRP1 = 0xE0
GMCTRN1 = 0xE1
GREENTAB Model = 0
MINI80x160 Model = 1
NO_ROTATION Rotation = 0
ROTATION_90 Rotation = 1 // 90 degrees clock-wise rotation
ROTATION_180 Rotation = 2
ROTATION_270 Rotation = 3
)
+440
View File
@@ -0,0 +1,440 @@
// Package st7735 implements a driver for the ST7735 TFT displays, it comes in various screen sizes.
//
// Datasheet: https://www.crystalfontz.com/controllers/Sitronix/ST7735R/319/
//
package st7735 // import "tinygo.org/x/drivers/st7735"
import (
"image/color"
"machine"
"time"
"errors"
)
type Model uint8
type Rotation uint8
// Device wraps an SPI connection.
type Device struct {
bus machine.SPI
dcPin machine.Pin
resetPin machine.Pin
csPin machine.Pin
blPin machine.Pin
width int16
height int16
columnOffset int16
rowOffset int16
rotation Rotation
batchLength int16
model Model
isBGR bool
batchData []uint8
}
// Config is the configuration for the display
type Config struct {
Width int16
Height int16
Rotation Rotation
Model Model
RowOffset int16
ColumnOffset int16
}
// New creates a new ST7735 connection. The SPI wire must already be configured.
func New(bus machine.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
blPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
return Device{
bus: bus,
dcPin: dcPin,
resetPin: resetPin,
csPin: csPin,
blPin: blPin,
}
}
// Configure initializes the display with default configuration
func (d *Device) Configure(cfg Config) {
d.model = cfg.Model
if cfg.Width != 0 {
d.width = cfg.Width
} else {
if d.model == MINI80x160 {
d.width = 80
} else {
d.width = 128
}
}
if cfg.Height != 0 {
d.height = cfg.Height
} else {
d.height = 160
}
d.rotation = cfg.Rotation
if cfg.RowOffset != 0 {
d.rowOffset = cfg.RowOffset
} else {
d.rowOffset = 1
}
if cfg.ColumnOffset != 0 {
d.columnOffset = cfg.ColumnOffset
} else {
if d.model == MINI80x160 {
d.columnOffset = 26
} else {
d.columnOffset = 2
}
}
d.batchLength = d.width
if d.height > d.width {
d.batchLength = d.height
}
d.batchLength += d.batchLength & 1
d.batchData = make([]uint8, d.batchLength*2)
// reset the device
d.resetPin.High()
time.Sleep(5 * time.Millisecond)
d.resetPin.Low()
time.Sleep(20 * time.Millisecond)
d.resetPin.High()
time.Sleep(150 * time.Millisecond)
// Common initialization
d.Command(SWRESET)
time.Sleep(150 * time.Millisecond)
d.Command(SLPOUT)
time.Sleep(500 * time.Millisecond)
d.Command(FRMCTR1)
d.Data(0x01)
d.Data(0x2C)
d.Data(0x2D)
d.Command(FRMCTR2)
d.Data(0x01)
d.Data(0x2C)
d.Data(0x2D)
d.Command(FRMCTR3)
d.Data(0x01)
d.Data(0x2C)
d.Data(0x2D)
d.Data(0x01)
d.Data(0x2C)
d.Data(0x2D)
d.Command(INVCTR)
d.Data(0x07)
d.Command(PWCTR1)
d.Data(0xA2)
d.Data(0x02)
d.Data(0x84)
d.Command(PWCTR2)
d.Data(0xC5)
d.Command(PWCTR3)
d.Data(0x0A)
d.Data(0x00)
d.Command(PWCTR4)
d.Data(0x8A)
d.Data(0x2A)
d.Command(PWCTR5)
d.Data(0x8A)
d.Data(0xEE)
d.Command(VMCTR1)
d.Data(0x0E)
d.Command(COLMOD)
d.Data(0x05)
if d.model == GREENTAB {
d.InvertColors(false)
d.Command(CASET)
d.Data(0x00)
d.Data(0x02)
d.Data(0x00)
d.Data(0x7F + 0x02)
d.Command(RASET)
d.Data(0x00)
d.Data(0x01)
d.Data(0x00)
d.Data(0x9F + 0x01)
} else if d.model == MINI80x160 {
d.isBGR = true
d.InvertColors(true)
d.Command(CASET)
d.Data(0x00)
d.Data(0x00)
d.Data(0x00)
d.Data(0x7F)
d.Command(RASET)
d.Data(0x00)
d.Data(0x00)
d.Data(0x00)
d.Data(0x9F)
}
// common color adjustment
d.Command(GMCTRP1)
d.Data(0x02)
d.Data(0x1C)
d.Data(0x07)
d.Data(0x12)
d.Data(0x37)
d.Data(0x32)
d.Data(0x29)
d.Data(0x2D)
d.Data(0x29)
d.Data(0x25)
d.Data(0x2B)
d.Data(0x39)
d.Data(0x00)
d.Data(0x01)
d.Data(0x03)
d.Data(0x10)
d.Command(GMCTRN1)
d.Data(0x03)
d.Data(0x1D)
d.Data(0x07)
d.Data(0x06)
d.Data(0x2E)
d.Data(0x2C)
d.Data(0x29)
d.Data(0x2D)
d.Data(0x2E)
d.Data(0x2E)
d.Data(0x37)
d.Data(0x3F)
d.Data(0x00)
d.Data(0x00)
d.Data(0x02)
d.Data(0x10)
d.Command(NORON)
time.Sleep(10 * time.Millisecond)
d.Command(DISPON)
time.Sleep(500 * time.Millisecond)
if cfg.Model == MINI80x160 {
d.Command(MADCTL)
d.Data(0xC0)
}
d.SetRotation(d.rotation)
d.blPin.High()
}
// Display does nothing, there's no buffer as it might be too big for some boards
func (d *Device) Display() error {
return nil
}
// SetPixel sets a pixel in the screen
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
w, h := d.Size()
if x < 0 || y < 0 || x >= w || y >= h {
return
}
d.FillRectangle(x, y, 1, 1, c)
}
// setWindow prepares the screen to be modified at a given rectangle
func (d *Device) setWindow(x, y, w, h int16) {
if d.rotation == NO_ROTATION || d.rotation == ROTATION_180 {
x += d.columnOffset
y += d.rowOffset
} else {
x += d.rowOffset
y += d.columnOffset
}
d.Tx([]uint8{CASET}, true)
d.Tx([]uint8{uint8(x >> 8), uint8(x), uint8((x + w - 1) >> 8), uint8(x + w - 1)}, false)
d.Tx([]uint8{RASET}, true)
d.Tx([]uint8{uint8(y >> 8), uint8(y), uint8((y + h - 1) >> 8), uint8(y + h - 1)}, false)
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()
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
x >= k || (x+width) > k || y >= i || (y+height) > i {
return errors.New("rectangle coordinates outside display area")
}
d.setWindow(x, y, width, height)
c565 := RGBATo565(c)
c1 := uint8(c565 >> 8)
c2 := uint8(c565)
for i = 0; i < d.batchLength; i++ {
d.batchData[i*2] = c1
d.batchData[i*2+1] = c2
}
i = width * height
for i > 0 {
if i >= d.batchLength {
d.Tx(d.batchData, false)
} else {
d.Tx(d.batchData[:i*2], false)
}
i -= d.batchLength
}
return nil
}
// FillRectangle fills a rectangle at a given coordinates with a buffer
func (d *Device) FillRectangleWithBuffer(x, y, width, height int16, buffer []color.RGBA) error {
k, l := d.Size()
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
x >= k || (x+width) > k || y >= l || (y+height) > l {
return errors.New("rectangle coordinates outside display area")
}
k = width * height
l = int16(len(buffer))
if k != l {
return errors.New("buffer length does not match with rectangle size")
}
d.setWindow(x, y, width, height)
offset := int16(0)
for k > 0 {
for i := int16(0); i < d.batchLength; i++ {
if offset+i < l {
c565 := RGBATo565(buffer[offset+i])
c1 := uint8(c565 >> 8)
c2 := uint8(c565)
d.batchData[i*2] = c1
d.batchData[i*2+1] = c2
}
}
if k >= d.batchLength {
d.Tx(d.batchData, false)
} else {
d.Tx(d.batchData[:k*2], false)
}
k -= d.batchLength
offset += d.batchLength
}
return nil
}
// DrawFastVLine draws a vertical line faster than using SetPixel
func (d *Device) DrawFastVLine(x, y0, y1 int16, c color.RGBA) {
if y0 > y1 {
y0, y1 = y1, y0
}
d.FillRectangle(x, y0, 1, y1-y0+1, c)
}
// DrawFastHLine draws a horizontal line faster than using SetPixel
func (d *Device) DrawFastHLine(x0, x1, y int16, c color.RGBA) {
if x0 > x1 {
x0, x1 = x1, x0
}
d.FillRectangle(x0, y, x1-x0+1, y, c)
}
// FillScreen fills the screen with a given color
func (d *Device) FillScreen(c color.RGBA) {
if d.rotation == NO_ROTATION || d.rotation == ROTATION_180 {
d.FillRectangle(0, 0, d.width, d.height, c)
} else {
d.FillRectangle(0, 0, d.height, d.width, c)
}
}
// SetRotation changes the rotation of the device (clock-wise)
func (d *Device) SetRotation(rotation Rotation) {
madctl := uint8(0)
switch rotation % 4 {
case 0:
madctl = MADCTL_MX | MADCTL_MY
break
case 1:
madctl = MADCTL_MY | MADCTL_MV
break
case 2:
break
case 3:
madctl = MADCTL_MX | MADCTL_MV
break
}
if d.isBGR {
madctl |= MADCTL_BGR
}
d.Command(MADCTL)
d.Data(madctl)
}
// Command sends a command to the display
func (d *Device) Command(command uint8) {
d.Tx([]byte{command}, true)
}
// Command sends a data to the display
func (d *Device) Data(data uint8) {
d.Tx([]byte{data}, false)
}
// Tx sends data to the display
func (d *Device) Tx(data []byte, isCommand bool) {
if isCommand {
d.csPin.High()
d.dcPin.Low()
d.csPin.Low()
d.bus.Tx(data, nil)
d.csPin.High()
} else {
d.csPin.High()
d.dcPin.High()
d.csPin.Low()
d.bus.Tx(data, nil)
d.csPin.High()
}
}
// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
if d.rotation == NO_ROTATION || d.rotation == ROTATION_180 {
return d.width, d.height
}
return d.height, d.width
}
// EnableBacklight enables or disables the backlight
func (d *Device) EnableBacklight(enable bool) {
if enable {
d.blPin.High()
} else {
d.blPin.Low()
}
}
// InverColors inverts the colors of the screen
func (d *Device) InvertColors(invert bool) {
if invert {
d.Command(INVON)
} else {
d.Command(INVOFF)
}
}
// IsBGR changes the color mode (RGB/BGR)
func (d *Device) IsBGR(bgr bool) {
d.isBGR = bgr
}
// RGBATo565 converts a color.RGBA to uint16 used in the display
func RGBATo565(c color.RGBA) uint16 {
r, g, b, _ := c.RGBA()
return uint16((r & 0xF800) +
((g & 0xFC00) >> 5) +
((b & 0xF800) >> 11))
}
+54
View File
@@ -0,0 +1,54 @@
package st7789
// Registers
const (
NOP = 0x00
SWRESET = 0x01
RDDID = 0x04
RDDST = 0x09
SLPIN = 0x10
SLPOUT = 0x11
PTLON = 0x12
NORON = 0x13
INVOFF = 0x20
INVON = 0x21
DISPOFF = 0x28
DISPON = 0x29
CASET = 0x2A
RASET = 0x2B
RAMWR = 0x2C
RAMRD = 0x2E
PTLAR = 0x30
COLMOD = 0x3A
MADCTL = 0x36
MADCTL_MY = 0x80
MADCTL_MX = 0x40
MADCTL_MV = 0x20
MADCTL_ML = 0x10
MADCTL_RGB = 0x00
MADCTL_BGR = 0x08
MADCTL_MH = 0x04
RDID1 = 0xDA
RDID2 = 0xDB
RDID3 = 0xDC
RDID4 = 0xDD
FRMCTR1 = 0xB1
FRMCTR2 = 0xB2
FRMCTR3 = 0xB3
INVCTR = 0xB4
DISSET5 = 0xB6
PWCTR1 = 0xC0
PWCTR2 = 0xC1
PWCTR3 = 0xC2
PWCTR4 = 0xC3
PWCTR5 = 0xC4
VMCTR1 = 0xC5
PWCTR6 = 0xFC
GMCTRP1 = 0xE0
GMCTRN1 = 0xE1
NO_ROTATION Rotation = 0
ROTATION_90 Rotation = 1 // 90 degrees clock-wise rotation
ROTATION_180 Rotation = 2
ROTATION_270 Rotation = 3
)
+330
View File
@@ -0,0 +1,330 @@
// 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
//
package st7789 // import "tinygo.org/x/drivers/st7789"
import (
"image/color"
"machine"
"time"
"errors"
)
type Rotation uint8
// Device wraps an SPI connection.
type Device struct {
bus machine.SPI
dcPin machine.Pin
resetPin machine.Pin
blPin machine.Pin
width int16
height int16
columnOffsetCfg int16
rowOffsetCfg int16
columnOffset int16
rowOffset int16
rotation Rotation
batchLength int32
isBGR bool
}
// Config is the configuration for the display
type Config struct {
Width int16
Height int16
Rotation Rotation
RowOffset int16
ColumnOffset int16
}
// New creates a new ST7789 connection. The SPI wire must already be configured.
func New(bus machine.SPI, resetPin, dcPin, blPin machine.Pin) Device {
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
blPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
return Device{
bus: bus,
dcPin: dcPin,
resetPin: resetPin,
blPin: blPin,
}
}
// Configure initializes the display with default configuration
func (d *Device) Configure(cfg Config) {
if cfg.Width != 0 {
d.width = cfg.Width
} else {
d.width = 240
}
if cfg.Height != 0 {
d.height = cfg.Height
} else {
d.height = 240
}
d.rotation = cfg.Rotation
if cfg.RowOffset != 0 {
d.rowOffsetCfg = cfg.RowOffset
} else {
d.rowOffsetCfg = 80
}
if cfg.ColumnOffset != 0 {
d.columnOffsetCfg = cfg.ColumnOffset
}
d.batchLength = int32(d.width)
if d.height > d.width {
d.batchLength = int32(d.height)
}
d.batchLength += d.batchLength & 1
// reset the device
d.resetPin.High()
time.Sleep(5 * time.Millisecond)
d.resetPin.Low()
time.Sleep(20 * time.Millisecond)
d.resetPin.High()
time.Sleep(150 * time.Millisecond)
// Common initialization
d.Command(SWRESET)
time.Sleep(150 * time.Millisecond)
d.Command(SLPOUT)
time.Sleep(500 * time.Millisecond)
d.Command(COLMOD)
d.Data(0x55)
time.Sleep(10 * time.Millisecond)
d.SetRotation(d.rotation)
d.Command(CASET)
d.Data(0x00)
d.Data(uint8(d.columnOffset))
d.Data((240 + uint8(d.columnOffset)) >> 8)
d.Data(((240 + uint8(d.columnOffset)) >> 8) & 0xFF)
d.Command(RASET)
d.Data(0x00)
d.Data(uint8(d.rowOffset))
d.Data((240 + uint8(d.rowOffset)) >> 8)
d.Data(((240 + uint8(d.rowOffset)) >> 8) & 0xFF)
d.InvertColors(true)
d.Command(NORON)
time.Sleep(10 * time.Millisecond)
d.Command(DISPON)
time.Sleep(500 * time.Millisecond)
d.blPin.High()
}
// Display does nothing, there's no buffer as it might be too big for some boards
func (d *Device) Display() error {
return nil
}
// SetPixel sets a pixel in the screen
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
if x < 0 || y < 0 ||
(((d.rotation == NO_ROTATION || d.rotation == ROTATION_180) && (x >= d.width || y >= d.height)) ||
((d.rotation == ROTATION_90 || d.rotation == ROTATION_270) && (x >= d.height || y >= d.width))) {
return
}
d.FillRectangle(x, y, 1, 1, c)
}
// setWindow prepares the screen to be modified at a given rectangle
func (d *Device) setWindow(x, y, w, h int16) {
x += d.columnOffset
y += d.rowOffset
d.Tx([]uint8{CASET}, true)
d.Tx([]uint8{uint8(x << 8), uint8(x), uint8((x + w - 1) >> 8), uint8(x + w - 1)}, false)
d.Tx([]uint8{RASET}, true)
d.Tx([]uint8{uint8(y >> 8), uint8(y), uint8((y + h - 1) >> 8), uint8(y + h - 1)}, false)
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()
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
x >= k || (x+width) > k || y >= i || (y+height) > i {
return errors.New("rectangle coordinates outside display area")
}
d.setWindow(x, y, width, height)
c565 := RGBATo565(c)
c1 := uint8(c565 >> 8)
c2 := uint8(c565)
data := make([]uint8, d.batchLength*2)
for i := int32(0); i < d.batchLength; i++ {
data[i*2] = c1
data[i*2+1] = c2
}
j := int32(width) * int32(height)
for j > 0 {
if j >= d.batchLength {
d.Tx(data, false)
} else {
d.Tx(data[:j*2], false)
}
j -= d.batchLength
}
return nil
}
// FillRectangle fills a rectangle at a given coordinates with a buffer
func (d *Device) FillRectangleWithBuffer(x, y, width, height int16, buffer []color.RGBA) error {
i, j := d.Size()
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
x >= i || (x+width) > i || y >= j || (y+height) > j {
return errors.New("rectangle coordinates outside display area")
}
if int32(width)*int32(height) != int32(len(buffer)) {
return errors.New("buffer length does not match with rectangle size")
}
d.setWindow(x, y, width, height)
k := int32(width) * int32(height)
data := make([]uint8, d.batchLength*2)
offset := int32(0)
for k > 0 {
for i := int32(0); i < d.batchLength; i++ {
c565 := RGBATo565(buffer[offset+i])
c1 := uint8(c565 >> 8)
c2 := uint8(c565)
data[i*2] = c1
data[i*2+1] = c2
}
if k >= d.batchLength {
d.Tx(data, false)
} else {
d.Tx(data[:k*2], false)
}
k -= d.batchLength
offset += d.batchLength
}
return nil
}
// DrawFastVLine draws a vertical line faster than using SetPixel
func (d *Device) DrawFastVLine(x, y0, y1 int16, c color.RGBA) {
if y0 > y1 {
y0, y1 = y1, y0
}
d.FillRectangle(x, y0, 1, y1-y0+1, c)
}
// DrawFastHLine draws a horizontal line faster than using SetPixel
func (d *Device) DrawFastHLine(x0, x1, y int16, c color.RGBA) {
if x0 > x1 {
x0, x1 = x1, x0
}
d.FillRectangle(x0, y, x1-x0+1, y, c)
}
// FillScreen fills the screen with a given color
func (d *Device) FillScreen(c color.RGBA) {
if d.rotation == NO_ROTATION || d.rotation == ROTATION_180 {
d.FillRectangle(0, 0, d.width, d.height, c)
} else {
d.FillRectangle(0, 0, d.height, d.width, c)
}
}
// SetRotation changes the rotation of the device (clock-wise)
func (d *Device) SetRotation(rotation Rotation) {
madctl := uint8(0)
switch rotation % 4 {
case 0:
madctl = MADCTL_MX | MADCTL_MY
d.rowOffset = d.rowOffsetCfg
d.columnOffset = d.columnOffsetCfg
break
case 1:
madctl = MADCTL_MY | MADCTL_MV
d.rowOffset = d.columnOffsetCfg
d.columnOffset = d.rowOffsetCfg
break
case 2:
d.rowOffset = 0
d.columnOffset = 0
break
case 3:
madctl = MADCTL_MX | MADCTL_MV
d.rowOffset = 0
d.columnOffset = 0
break
}
if d.isBGR {
madctl |= MADCTL_BGR
}
d.Command(MADCTL)
d.Data(madctl)
}
// Command sends a command to the display
func (d *Device) Command(command uint8) {
d.Tx([]byte{command}, true)
}
// Command sends a data to the display
func (d *Device) Data(data uint8) {
d.Tx([]byte{data}, false)
}
// Tx sends data to the display
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)
}
}
// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
if d.rotation == NO_ROTATION || d.rotation == ROTATION_180 {
return d.width, d.height
}
return d.height, d.width
}
// EnableBacklight enables or disables the backlight
func (d *Device) EnableBacklight(enable bool) {
if enable {
d.blPin.High()
} else {
d.blPin.Low()
}
}
// InverColors inverts the colors of the screen
func (d *Device) InvertColors(invert bool) {
if invert {
d.Command(INVON)
} else {
d.Command(INVOFF)
}
}
// IsBGR changes the color mode (RGB/BGR)
func (d *Device) IsBGR(bgr bool) {
d.isBGR = bgr
}
// RGBATo565 converts a color.RGBA to uint16 used in the display
func RGBATo565(c color.RGBA) uint16 {
r, g, b, _ := c.RGBA()
return uint16((r & 0xF800) +
((g & 0xFC00) >> 5) +
((b & 0xF800) >> 11))
}