mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-27 11:08:41 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b4dbac3a67 | |||
| bf077c8249 | |||
| 4867abcbba | |||
| 45922f6524 | |||
| eb040dde9c | |||
| 04bfa6fa70 | |||
| 8453611d1f |
@@ -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**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
//
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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
|
||||
)
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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))
|
||||
}
|
||||
Reference in New Issue
Block a user