Driver ST7735 for TFT color displays (#72)

* Driver ST7735 for TFT color displays
This commit is contained in:
Daniel Esteban
2019-08-03 11:13:15 +02:00
committed by Ron Evans
parent eb040dde9c
commit 45922f6524
6 changed files with 541 additions and 0 deletions
+1
View File
@@ -37,6 +37,7 @@ 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/st7735/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
+1
View File
@@ -77,6 +77,7 @@ 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 |
| [ST7735 TFT color display](https://www.crystalfontz.com/controllers/Sitronix/ST7735R/319/) | 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 |
+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)
}
+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))
}