mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-19 06:03:57 +00:00
@@ -49,6 +49,8 @@ smoke-test:
|
|||||||
@md5sum ./build/test.hex
|
@md5sum ./build/test.hex
|
||||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/hub75/main.go
|
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/hub75/main.go
|
||||||
@md5sum ./build/test.hex
|
@md5sum ./build/test.hex
|
||||||
|
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341/basic/main.go
|
||||||
|
@md5sum ./build/test.hex
|
||||||
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/lis3dh/main.go
|
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/lis3dh/main.go
|
||||||
@md5sum ./build/test.hex
|
@md5sum ./build/test.hex
|
||||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/lsm6ds3/main.go
|
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/lsm6ds3/main.go
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ The following 37 devices are supported.
|
|||||||
| [ESP8266/ESP32 AT Command set for WiFi/TCP/UDP](https://github.com/espressif/esp32-at) | UART |
|
| [ESP8266/ESP32 AT Command set for WiFi/TCP/UDP](https://github.com/espressif/esp32-at) | UART |
|
||||||
| [GPS module](https://www.u-blox.com/en/product/neo-6-series) | I2C/UART |
|
| [GPS module](https://www.u-blox.com/en/product/neo-6-series) | I2C/UART |
|
||||||
| [HUB75 RGB led matrix](https://cdn-learn.adafruit.com/downloads/pdf/32x16-32x32-rgb-led-matrix.pdf) | SPI |
|
| [HUB75 RGB led matrix](https://cdn-learn.adafruit.com/downloads/pdf/32x16-32x32-rgb-led-matrix.pdf) | SPI |
|
||||||
|
| [ILI9341 TFT color display](https://cdn-shop.adafruit.com/datasheets/ILI9341.pdf) | SPI |
|
||||||
| [L293x motor driver](https://www.ti.com/lit/ds/symlink/l293d.pdf) | GPIO/PWM |
|
| [L293x motor driver](https://www.ti.com/lit/ds/symlink/l293d.pdf) | GPIO/PWM |
|
||||||
| [L9110x motor driver](https://www.elecrow.com/download/datasheet-l9110.pdf) | GPIO/PWM |
|
| [L9110x motor driver](https://www.elecrow.com/download/datasheet-l9110.pdf) | GPIO/PWM |
|
||||||
| [LIS3DH accelerometer](https://www.st.com/resource/en/datasheet/lis3dh.pdf) | I2C |
|
| [LIS3DH accelerometer](https://www.st.com/resource/en/datasheet/lis3dh.pdf) | I2C |
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/ili9341"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
display = ili9341.NewParallel(
|
||||||
|
machine.LCD_DATA0,
|
||||||
|
machine.TFT_WR,
|
||||||
|
machine.TFT_DC,
|
||||||
|
machine.TFT_CS,
|
||||||
|
machine.TFT_RESET,
|
||||||
|
machine.TFT_RD,
|
||||||
|
)
|
||||||
|
|
||||||
|
black = color.RGBA{0, 0, 0, 255}
|
||||||
|
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}
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
machine.TFT_BACKLIGHT.Configure(machine.PinConfig{machine.PinOutput})
|
||||||
|
|
||||||
|
display.Configure(ili9341.Config{})
|
||||||
|
width, height := display.Size()
|
||||||
|
|
||||||
|
display.FillScreen(black)
|
||||||
|
machine.TFT_BACKLIGHT.High()
|
||||||
|
|
||||||
|
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)
|
||||||
|
for {
|
||||||
|
time.Sleep(time.Hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,234 @@
|
|||||||
|
// Port of Adafruit's "pyportal_boing" demo found here:
|
||||||
|
// https://github.com/adafruit/Adafruit_ILI9341/blob/master/examples/pyportal_boing
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/examples/ili9341/pyportal_boing/graphics"
|
||||||
|
"tinygo.org/x/drivers/ili9341"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
BGCOLOR = 0xAD75
|
||||||
|
GRIDCOLOR = 0xA815
|
||||||
|
BGSHADOW = 0x5285
|
||||||
|
GRIDSHADOW = 0x600C
|
||||||
|
RED = 0xF800
|
||||||
|
WHITE = 0xFFFF
|
||||||
|
|
||||||
|
YBOTTOM = 123 // Ball Y coord at bottom
|
||||||
|
YBOUNCE = -3.5 // Upward velocity on ball bounce
|
||||||
|
|
||||||
|
_debug = false
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
display = ili9341.NewParallel(
|
||||||
|
machine.LCD_DATA0,
|
||||||
|
machine.TFT_WR,
|
||||||
|
machine.TFT_DC,
|
||||||
|
machine.TFT_CS,
|
||||||
|
machine.TFT_RESET,
|
||||||
|
machine.TFT_RD,
|
||||||
|
)
|
||||||
|
|
||||||
|
frameBuffer = [(graphics.BALLHEIGHT + 8) * (graphics.BALLWIDTH + 8)]uint16{}
|
||||||
|
|
||||||
|
startTime int64
|
||||||
|
frame int64
|
||||||
|
|
||||||
|
// Ball coordinates are stored floating-point because screen refresh
|
||||||
|
// is so quick, whole-pixel movements are just too fast!
|
||||||
|
ballx float32
|
||||||
|
bally float32
|
||||||
|
ballvx float32
|
||||||
|
ballvy float32
|
||||||
|
ballframe float32
|
||||||
|
balloldx float32
|
||||||
|
balloldy float32
|
||||||
|
|
||||||
|
// Color table for ball rotation effect
|
||||||
|
palette [16]uint16
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// configure backlight
|
||||||
|
machine.TFT_BACKLIGHT.Configure(machine.PinConfig{machine.PinOutput})
|
||||||
|
|
||||||
|
// configure display
|
||||||
|
display.Configure(ili9341.Config{})
|
||||||
|
print("width, height == ")
|
||||||
|
width, height := display.Size()
|
||||||
|
println(width, height)
|
||||||
|
|
||||||
|
machine.TFT_BACKLIGHT.High()
|
||||||
|
|
||||||
|
display.SetRotation(ili9341.Rotation270)
|
||||||
|
DrawBackground()
|
||||||
|
|
||||||
|
startTime = time.Now().UnixNano()
|
||||||
|
frame = 0
|
||||||
|
|
||||||
|
ballx = 20.0
|
||||||
|
bally = YBOTTOM // Current ball position
|
||||||
|
ballvx = 0.8
|
||||||
|
ballvy = YBOUNCE // Ball velocity
|
||||||
|
ballframe = 3 // Ball animation frame #
|
||||||
|
balloldx = ballx
|
||||||
|
balloldy = bally // Prior ball position
|
||||||
|
|
||||||
|
for {
|
||||||
|
|
||||||
|
balloldx = ballx // Save prior position
|
||||||
|
balloldy = bally
|
||||||
|
ballx += ballvx // Update position
|
||||||
|
bally += ballvy
|
||||||
|
ballvy += 0.06 // Update Y velocity
|
||||||
|
if (ballx <= 15) || (ballx >= graphics.SCREENWIDTH-graphics.BALLWIDTH) {
|
||||||
|
ballvx *= -1 // Left/right bounce
|
||||||
|
}
|
||||||
|
if bally >= YBOTTOM { // Hit ground?
|
||||||
|
bally = YBOTTOM // Clip and
|
||||||
|
ballvy = YBOUNCE // bounce up
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine screen area to update. This is the bounds of the ball's
|
||||||
|
// prior and current positions, so the old ball is fully erased and new
|
||||||
|
// ball is fully drawn.
|
||||||
|
var minx, miny, maxx, maxy, width, height int16
|
||||||
|
|
||||||
|
// Determine bounds of prior and new positions
|
||||||
|
minx = int16(ballx)
|
||||||
|
if int16(balloldx) < minx {
|
||||||
|
minx = int16(balloldx)
|
||||||
|
}
|
||||||
|
miny = int16(bally)
|
||||||
|
if int16(balloldy) < miny {
|
||||||
|
miny = int16(balloldy)
|
||||||
|
}
|
||||||
|
maxx = int16(ballx + graphics.BALLWIDTH - 1)
|
||||||
|
if int16(balloldx+graphics.BALLWIDTH-1) > maxx {
|
||||||
|
maxx = int16(balloldx + graphics.BALLWIDTH - 1)
|
||||||
|
}
|
||||||
|
maxy = int16(bally + graphics.BALLHEIGHT - 1)
|
||||||
|
if int16(balloldy+graphics.BALLHEIGHT-1) > maxy {
|
||||||
|
maxy = int16(balloldy + graphics.BALLHEIGHT - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
width = maxx - minx + 1
|
||||||
|
height = maxy - miny + 1
|
||||||
|
|
||||||
|
// Ball animation frame # is incremented opposite the ball's X velocity
|
||||||
|
ballframe -= ballvx * 0.5
|
||||||
|
if ballframe < 0 {
|
||||||
|
ballframe += 14 // Constrain from 0 to 13
|
||||||
|
} else if ballframe >= 14 {
|
||||||
|
ballframe -= 14
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set 7 palette entries to white, 7 to red, based on frame number.
|
||||||
|
// This makes the ball spin
|
||||||
|
for i := 0; i < 14; i++ {
|
||||||
|
if (int(ballframe)+i)%14 < 7 {
|
||||||
|
palette[i+2] = WHITE
|
||||||
|
} else {
|
||||||
|
palette[i+2] = RED
|
||||||
|
} // Palette entries 0 and 1 aren't used (clear and shadow, respectively)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only the changed rectangle is drawn into the 'renderbuf' array...
|
||||||
|
var c uint16 //, *destPtr;
|
||||||
|
bx := minx - int16(ballx) // X relative to ball bitmap (can be negative)
|
||||||
|
by := miny - int16(bally) // Y relative to ball bitmap (can be negative)
|
||||||
|
bgx := minx // X relative to background bitmap (>= 0)
|
||||||
|
bgy := miny // Y relative to background bitmap (>= 0)
|
||||||
|
var bx1, bgx1 int16 // Loop counters and working vars
|
||||||
|
var p uint8 // 'packed' value of 2 ball pixels
|
||||||
|
var bufIdx int8 = 0
|
||||||
|
|
||||||
|
//tft.setAddrWindow(minx, miny, width, height)
|
||||||
|
|
||||||
|
for y := 0; y < int(height); y++ { // For each row...
|
||||||
|
//destPtr = &renderbuf[bufIdx][0];
|
||||||
|
bx1 = bx // Need to keep the original bx and bgx values,
|
||||||
|
bgx1 = bgx // so copies of them are made here (and changed in loop below)
|
||||||
|
for x := 0; x < int(width); x++ {
|
||||||
|
var bgidx = int(bgy)*(graphics.SCREENWIDTH/8) + int(bgx1/8)
|
||||||
|
if (bx1 >= 0) && (bx1 < graphics.BALLWIDTH) && // Is current pixel row/column
|
||||||
|
(by >= 0) && (by < graphics.BALLHEIGHT) { // inside the ball bitmap area?
|
||||||
|
// Yes, do ball compositing math...
|
||||||
|
p = graphics.Ball[int(by*(graphics.BALLWIDTH/2))+int(bx1/2)] // Get packed value (2 pixels)
|
||||||
|
if (bx1 & 1) != 0 {
|
||||||
|
c = uint16(p & 0xF)
|
||||||
|
} else {
|
||||||
|
c = uint16(p >> 4)
|
||||||
|
} // Unpack high or low nybble
|
||||||
|
if c == 0 { // Outside ball - just draw grid
|
||||||
|
if graphics.Background[bgidx]&(0x80>>(bgx1&7)) != 0 {
|
||||||
|
c = GRIDCOLOR
|
||||||
|
} else {
|
||||||
|
c = BGCOLOR
|
||||||
|
}
|
||||||
|
} else if c > 1 { // In ball area...
|
||||||
|
c = palette[c]
|
||||||
|
} else { // In shadow area...
|
||||||
|
if graphics.Background[bgidx]&(0x80>>(bgx1&7)) != 0 {
|
||||||
|
c = GRIDSHADOW
|
||||||
|
} else {
|
||||||
|
c = BGSHADOW
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // Outside ball bitmap, just draw background bitmap...
|
||||||
|
if graphics.Background[bgidx]&(0x80>>(bgx1&7)) != 0 {
|
||||||
|
c = GRIDCOLOR
|
||||||
|
} else {
|
||||||
|
c = BGCOLOR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frameBuffer[y*int(width)+x] = c
|
||||||
|
bx1++ // Increment bitmap position counters (X axis)
|
||||||
|
bgx1++
|
||||||
|
}
|
||||||
|
//tft.dmaWait(); // Wait for prior line to complete
|
||||||
|
//tft.writePixels(&renderbuf[bufIdx][0], width, false); // Non-blocking write
|
||||||
|
bufIdx = 1 - bufIdx
|
||||||
|
by++ // Increment bitmap position counters (Y axis)
|
||||||
|
bgy++
|
||||||
|
}
|
||||||
|
|
||||||
|
display.DrawRGBBitmap(minx, miny, frameBuffer[:width*height], width, height)
|
||||||
|
|
||||||
|
// Show approximate frame rate
|
||||||
|
frame++
|
||||||
|
if frame&255 == 0 { // Every 256 frames...
|
||||||
|
elapsed := (time.Now().UnixNano() - startTime) / int64(time.Second)
|
||||||
|
if elapsed > 0 {
|
||||||
|
println(frame/elapsed, " fps")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DrawBackground() {
|
||||||
|
w, h := display.Size()
|
||||||
|
byteWidth := (w + 7) / 8 // Bitmap scanline pad = whole byte
|
||||||
|
var b uint8
|
||||||
|
for j := int16(0); j < h; j++ {
|
||||||
|
for k := int16(0); k < w; k++ {
|
||||||
|
if k&7 > 0 {
|
||||||
|
b <<= 1
|
||||||
|
} else {
|
||||||
|
b = graphics.Background[j*byteWidth+k/8]
|
||||||
|
}
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
frameBuffer[k] = BGCOLOR
|
||||||
|
} else {
|
||||||
|
frameBuffer[k] = GRIDCOLOR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
display.DrawRGBBitmap(0, j, frameBuffer[0:w], w, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
TinyGo driver for TFT displays using ILI9341 driver chips.
|
||||||
|
|
||||||
|
These displays support 8-bit parallel, 16-bit parallel, or SPI interfaces.
|
||||||
|
|
||||||
|
Examples of such displays include:
|
||||||
|
|
||||||
|
* [Adafruit PyPortal
|
||||||
|
](https://www.adafruit.com/product/4116)
|
||||||
|
* [Adafruit 2.8" Touch Shield V2 (SPI)](http://www.adafruit.com/products/1651)
|
||||||
|
* [Adafruit 2.4" TFT LCD with Touchscreen Breakout w/MicroSD Socket](https://www.adafruit.com/product/2478)
|
||||||
|
* [2.8" TFT LCD with Touchscreen Breakout Board w/MicroSD Socket](https://www.adafruit.com/product/1770)
|
||||||
|
* [2.2" 18-bit color TFT LCD display with microSD card breakout](https://www.adafruit.com/product/1770)
|
||||||
|
* [TFT FeatherWing - 2.4" 320x240 Touchscreen For All Feathers](https://www.adafruit.com/product/3315)
|
||||||
|
|
||||||
|
Currently this driver only supports an 8-bit parallel interface using ATSAMD51
|
||||||
|
(this is the default configuration on PyPortal). It should be relatively
|
||||||
|
straightforward to implement a more generic SPI-based interface as well.
|
||||||
|
Please see `parallel_atsamd51.go` for an example of what needs to be
|
||||||
|
implemented if you are interested in contributing.
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
package ili9341
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"image/color"
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const _debug = false
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Width int16
|
||||||
|
Height int16
|
||||||
|
Rotation Rotation
|
||||||
|
}
|
||||||
|
|
||||||
|
type Device struct {
|
||||||
|
width int16
|
||||||
|
height int16
|
||||||
|
rotation Rotation
|
||||||
|
driver driver
|
||||||
|
|
||||||
|
dc machine.Pin
|
||||||
|
cs machine.Pin
|
||||||
|
rst machine.Pin
|
||||||
|
rd machine.Pin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) Configure(config Config) {
|
||||||
|
|
||||||
|
if config.Width == 0 {
|
||||||
|
config.Width = TFTWIDTH
|
||||||
|
}
|
||||||
|
if config.Height == 0 {
|
||||||
|
config.Height = TFTHEIGHT
|
||||||
|
}
|
||||||
|
d.width = config.Width
|
||||||
|
d.height = config.Height
|
||||||
|
|
||||||
|
output := machine.PinConfig{machine.PinOutput}
|
||||||
|
|
||||||
|
// configure chip select if there is one
|
||||||
|
if d.cs != machine.NoPin {
|
||||||
|
d.cs.Configure(output)
|
||||||
|
d.cs.High() // deselect
|
||||||
|
}
|
||||||
|
|
||||||
|
d.dc.Configure(output)
|
||||||
|
d.dc.High() // data mode
|
||||||
|
|
||||||
|
// driver-specific configuration
|
||||||
|
d.driver.configure(&config)
|
||||||
|
|
||||||
|
if d.rd != machine.NoPin {
|
||||||
|
d.rd.Configure(output)
|
||||||
|
d.rd.High()
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset the display
|
||||||
|
if d.rst != machine.NoPin {
|
||||||
|
// configure hardware reset if there is one
|
||||||
|
d.rst.Configure(output)
|
||||||
|
d.rst.High()
|
||||||
|
delay(100)
|
||||||
|
d.rst.Low()
|
||||||
|
delay(100)
|
||||||
|
d.rst.High()
|
||||||
|
delay(200)
|
||||||
|
} else {
|
||||||
|
// if no hardware reset, send software reset
|
||||||
|
d.sendCommand(SWRESET, nil)
|
||||||
|
delay(150)
|
||||||
|
}
|
||||||
|
|
||||||
|
initCmd := []byte{
|
||||||
|
0xEF, 3, 0x03, 0x80, 0x02,
|
||||||
|
0xCF, 3, 0x00, 0xC1, 0x30,
|
||||||
|
0xED, 4, 0x64, 0x03, 0x12, 0x81,
|
||||||
|
0xE8, 3, 0x85, 0x00, 0x78,
|
||||||
|
0xCB, 5, 0x39, 0x2C, 0x00, 0x34, 0x02,
|
||||||
|
0xF7, 1, 0x20,
|
||||||
|
0xEA, 2, 0x00, 0x00,
|
||||||
|
PWCTR1, 1, 0x23, // Power control VRH[5:0]
|
||||||
|
PWCTR2, 1, 0x10, // Power control SAP[2:0];BT[3:0]
|
||||||
|
VMCTR1, 2, 0x3e, 0x28, // VCM control
|
||||||
|
VMCTR2, 1, 0x86, // VCM control2
|
||||||
|
MADCTL, 1, 0x48, // Memory Access Control
|
||||||
|
VSCRSADD, 1, 0x00, // Vertical scroll zero
|
||||||
|
PIXFMT, 1, 0x55,
|
||||||
|
FRMCTR1, 2, 0x00, 0x18,
|
||||||
|
DFUNCTR, 3, 0x08, 0x82, 0x27, // Display Function Control
|
||||||
|
0xF2, 1, 0x00, // 3Gamma Function Disable
|
||||||
|
GAMMASET, 1, 0x01, // Gamma curve selected
|
||||||
|
GMCTRP1, 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma
|
||||||
|
0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00,
|
||||||
|
GMCTRN1, 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma
|
||||||
|
0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F,
|
||||||
|
SLPOUT, 0x80, // Exit Sleep
|
||||||
|
DISPON, 0x80, // Display on
|
||||||
|
0x00, // End of list
|
||||||
|
}
|
||||||
|
for i, c := 0, len(initCmd); i < c; {
|
||||||
|
cmd := initCmd[i]
|
||||||
|
if cmd == 0x00 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
x := initCmd[i+1]
|
||||||
|
numArgs := int(x & 0x7F)
|
||||||
|
d.sendCommand(cmd, initCmd[i+2:i+2+numArgs])
|
||||||
|
if x&0x80 > 0 {
|
||||||
|
delay(150)
|
||||||
|
}
|
||||||
|
i += numArgs + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the current size of the display.
|
||||||
|
func (d *Device) Size() (x, y int16) {
|
||||||
|
if d.rotation == 1 || d.rotation == 3 {
|
||||||
|
return d.height, d.width
|
||||||
|
}
|
||||||
|
return d.width, d.height
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPixel modifies the internal buffer.
|
||||||
|
func (d *Device) SetPixel(x, y int16, c color.RGBA) {
|
||||||
|
d.setWindow(x, y, 1, 1)
|
||||||
|
c565 := RGBATo565(c)
|
||||||
|
d.startWrite()
|
||||||
|
d.driver.write16(c565)
|
||||||
|
d.endWrite()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display sends the buffer (if any) to the screen.
|
||||||
|
func (d *Device) Display() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) DrawRGBBitmap(x, y int16, data []uint16, w, h int16) error {
|
||||||
|
k, i := d.Size()
|
||||||
|
if x < 0 || y < 0 || w <= 0 || h <= 0 ||
|
||||||
|
x >= k || (x+w) > k || y >= i || (y+h) > i {
|
||||||
|
return errors.New("rectangle coordinates outside display area")
|
||||||
|
}
|
||||||
|
d.setWindow(x, y, w, h)
|
||||||
|
d.startWrite()
|
||||||
|
d.driver.write16sl(data)
|
||||||
|
d.endWrite()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
d.startWrite()
|
||||||
|
d.driver.write16n(c565, int(width)*int(height))
|
||||||
|
d.endWrite()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrawRectangle fills a rectangle at a given coordinates with a color
|
||||||
|
func (d *Device) DrawRectangle(x, y, w, h int16, c color.RGBA) error {
|
||||||
|
if err := d.DrawFastHLine(x, x+w-1, y, c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := d.DrawFastHLine(x, x+w-1, y+h-1, c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := d.DrawFastVLine(x, y, y+h-1, c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := d.DrawFastVLine(x+w-1, y, y+h-1, c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrawFastVLine draws a vertical line faster than using SetPixel
|
||||||
|
func (d *Device) DrawFastVLine(x, y0, y1 int16, c color.RGBA) error {
|
||||||
|
if y0 > y1 {
|
||||||
|
y0, y1 = y1, y0
|
||||||
|
}
|
||||||
|
return 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) error {
|
||||||
|
if x0 > x1 {
|
||||||
|
x0, x1 = x1, x0
|
||||||
|
}
|
||||||
|
return d.FillRectangle(x0, y, x1-x0+1, 1, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FillScreen fills the screen with a given color
|
||||||
|
func (d *Device) FillScreen(c color.RGBA) {
|
||||||
|
if d.rotation == Rotation0 || d.rotation == Rotation180 {
|
||||||
|
d.FillRectangle(0, 0, d.width, d.height, c)
|
||||||
|
} else {
|
||||||
|
d.FillRectangle(0, 0, d.height, d.width, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) GetRotation() Rotation {
|
||||||
|
return d.rotation
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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_BGR
|
||||||
|
case 1:
|
||||||
|
madctl = MADCTL_MV | MADCTL_BGR
|
||||||
|
case 2:
|
||||||
|
madctl = MADCTL_MY | MADCTL_BGR
|
||||||
|
case 3:
|
||||||
|
madctl = MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR
|
||||||
|
}
|
||||||
|
d.sendCommand(MADCTL, []uint8{madctl})
|
||||||
|
d.rotation = rotation
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.sendCommand(CASET, []uint8{
|
||||||
|
uint8(x << 8), uint8(x), uint8((x + w - 1) >> 8), uint8(x + w - 1),
|
||||||
|
})
|
||||||
|
d.sendCommand(PASET, []uint8{
|
||||||
|
uint8(y >> 8), uint8(y), uint8((y + h - 1) >> 8), uint8(y + h - 1),
|
||||||
|
})
|
||||||
|
d.sendCommand(RAMWR, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:inline
|
||||||
|
func (d *Device) startWrite() {
|
||||||
|
if d.cs != machine.NoPin {
|
||||||
|
d.cs.Low()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:inline
|
||||||
|
func (d *Device) endWrite() {
|
||||||
|
if d.cs != machine.NoPin {
|
||||||
|
d.cs.High()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) sendCommand(cmd byte, data []byte) {
|
||||||
|
d.startWrite()
|
||||||
|
d.dc.Low()
|
||||||
|
d.driver.write8(cmd)
|
||||||
|
d.dc.High()
|
||||||
|
for _, b := range data {
|
||||||
|
d.driver.write8(b)
|
||||||
|
}
|
||||||
|
d.endWrite()
|
||||||
|
}
|
||||||
|
|
||||||
|
type driver interface {
|
||||||
|
configure(config *Config)
|
||||||
|
write8(b byte)
|
||||||
|
write16(data uint16)
|
||||||
|
write16n(data uint16, n int)
|
||||||
|
write16sl(data []uint16)
|
||||||
|
}
|
||||||
|
|
||||||
|
func delay(m int) {
|
||||||
|
t := time.Now().UnixNano() + int64(time.Duration(m*1000)*time.Microsecond)
|
||||||
|
for time.Now().UnixNano() < t {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,87 @@
|
|||||||
|
// +build atsamd51
|
||||||
|
|
||||||
|
package ili9341
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
"runtime/volatile"
|
||||||
|
)
|
||||||
|
|
||||||
|
type parallelDriver struct {
|
||||||
|
d0 machine.Pin
|
||||||
|
wr machine.Pin
|
||||||
|
|
||||||
|
setPort *uint32
|
||||||
|
setMask uint32
|
||||||
|
|
||||||
|
clrPort *uint32
|
||||||
|
clrMask uint32
|
||||||
|
|
||||||
|
wrPortSet *uint32
|
||||||
|
wrMaskSet uint32
|
||||||
|
|
||||||
|
wrPortClr *uint32
|
||||||
|
wrMaskClr uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewParallel(d0, wr, dc, cs, rst, rd machine.Pin) *Device {
|
||||||
|
return &Device{
|
||||||
|
dc: dc,
|
||||||
|
cs: cs,
|
||||||
|
rd: rd,
|
||||||
|
rst: rst,
|
||||||
|
driver: ¶llelDriver{
|
||||||
|
d0: d0,
|
||||||
|
wr: wr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pd *parallelDriver) configure(config *Config) {
|
||||||
|
output := machine.PinConfig{machine.PinOutput}
|
||||||
|
for pin := pd.d0; pin < pd.d0+8; pin++ {
|
||||||
|
pin.Configure(output)
|
||||||
|
pin.Low()
|
||||||
|
}
|
||||||
|
pd.wr.Configure(output)
|
||||||
|
pd.wr.High()
|
||||||
|
|
||||||
|
pd.setPort, _ = pd.d0.PortMaskSet()
|
||||||
|
pd.setMask = uint32(pd.d0) & 0x1f
|
||||||
|
|
||||||
|
pd.clrPort, _ = (pd.d0).PortMaskClear()
|
||||||
|
pd.clrMask = 0xFF << uint32(pd.d0)
|
||||||
|
|
||||||
|
pd.wrPortSet, pd.wrMaskSet = pd.wr.PortMaskSet()
|
||||||
|
pd.wrPortClr, pd.wrMaskClr = pd.wr.PortMaskClear()
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:inline
|
||||||
|
func (pd *parallelDriver) write8(b byte) {
|
||||||
|
volatile.StoreUint32(pd.clrPort, pd.clrMask)
|
||||||
|
volatile.StoreUint32(pd.setPort, uint32(b)<<pd.setMask)
|
||||||
|
volatile.StoreUint32(pd.wrPortClr, pd.wrMaskClr)
|
||||||
|
volatile.StoreUint32(pd.wrPortSet, pd.wrMaskSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:inline
|
||||||
|
func (pd *parallelDriver) write16(data uint16) {
|
||||||
|
pd.write8(byte(data >> 8))
|
||||||
|
pd.write8(byte(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:inline
|
||||||
|
func (pd *parallelDriver) write16n(data uint16, n int) {
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
pd.write8(byte(data >> 8))
|
||||||
|
pd.write8(byte(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:inline
|
||||||
|
func (pd *parallelDriver) write16sl(data []uint16) {
|
||||||
|
for i, c := 0, len(data); i < c; i++ {
|
||||||
|
pd.write8(byte(data[i] >> 8))
|
||||||
|
pd.write8(byte(data[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package ili9341
|
||||||
|
|
||||||
|
type Rotation uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
|
||||||
|
// register constants based on source:
|
||||||
|
// https://github.com/adafruit/Adafruit_ILI9341/blob/master/Adafruit_ILI9341.h
|
||||||
|
|
||||||
|
TFTWIDTH = 240 ///< ILI9341 max TFT width
|
||||||
|
TFTHEIGHT = 320 ///< ILI9341 max TFT height
|
||||||
|
|
||||||
|
NOP = 0x00 ///< No-op register
|
||||||
|
SWRESET = 0x01 ///< Software reset register
|
||||||
|
RDDID = 0x04 ///< Read display identification information
|
||||||
|
RDDST = 0x09 ///< Read Display Status
|
||||||
|
|
||||||
|
SLPIN = 0x10 ///< Enter Sleep Mode
|
||||||
|
SLPOUT = 0x11 ///< Sleep Out
|
||||||
|
PTLON = 0x12 ///< Partial Mode ON
|
||||||
|
NORON = 0x13 ///< Normal Display Mode ON
|
||||||
|
|
||||||
|
RDMODE = 0x0A ///< Read Display Power Mode
|
||||||
|
RDMADCTL = 0x0B ///< Read Display MADCTL
|
||||||
|
RDPIXFMT = 0x0C ///< Read Display Pixel Format
|
||||||
|
RDIMGFMT = 0x0D ///< Read Display Image Format
|
||||||
|
RDSELFDIAG = 0x0F ///< Read Display Self-Diagnostic Result
|
||||||
|
|
||||||
|
INVOFF = 0x20 ///< Display Inversion OFF
|
||||||
|
INVON = 0x21 ///< Display Inversion ON
|
||||||
|
GAMMASET = 0x26 ///< Gamma Set
|
||||||
|
DISPOFF = 0x28 ///< Display OFF
|
||||||
|
DISPON = 0x29 ///< Display ON
|
||||||
|
|
||||||
|
CASET = 0x2A ///< Column Address Set
|
||||||
|
PASET = 0x2B ///< Page Address Set
|
||||||
|
RAMWR = 0x2C ///< Memory Write
|
||||||
|
RAMRD = 0x2E ///< Memory Read
|
||||||
|
|
||||||
|
PTLAR = 0x30 ///< Partial Area
|
||||||
|
VSCRDEF = 0x33 ///< Vertical Scrolling Definition
|
||||||
|
MADCTL = 0x36 ///< Memory Access Control
|
||||||
|
VSCRSADD = 0x37 ///< Vertical Scrolling Start Address
|
||||||
|
PIXFMT = 0x3A ///< COLMOD: Pixel Format Set
|
||||||
|
|
||||||
|
FRMCTR1 = 0xB1 ///< Frame Rate Control (In Normal Mode/Full Colors)
|
||||||
|
FRMCTR2 = 0xB2 ///< Frame Rate Control (In Idle Mode/8 colors)
|
||||||
|
FRMCTR3 = 0xB3 ///< Frame Rate control (In Partial Mode/Full Colors)
|
||||||
|
INVCTR = 0xB4 ///< Display Inversion Control
|
||||||
|
DFUNCTR = 0xB6 ///< Display Function Control
|
||||||
|
|
||||||
|
PWCTR1 = 0xC0 ///< Power Control 1
|
||||||
|
PWCTR2 = 0xC1 ///< Power Control 2
|
||||||
|
PWCTR3 = 0xC2 ///< Power Control 3
|
||||||
|
PWCTR4 = 0xC3 ///< Power Control 4
|
||||||
|
PWCTR5 = 0xC4 ///< Power Control 5
|
||||||
|
VMCTR1 = 0xC5 ///< VCOM Control 1
|
||||||
|
VMCTR2 = 0xC7 ///< VCOM Control 2
|
||||||
|
|
||||||
|
RDID1 = 0xDA ///< Read ID 1
|
||||||
|
RDID2 = 0xDB ///< Read ID 2
|
||||||
|
RDID3 = 0xDC ///< Read ID 3
|
||||||
|
RDID4 = 0xDD ///< Read ID 4
|
||||||
|
|
||||||
|
GMCTRP1 = 0xE0 ///< Positive Gamma Correction
|
||||||
|
GMCTRN1 = 0xE1 ///< Negative Gamma Correction
|
||||||
|
//PWCTR6 0xFC
|
||||||
|
|
||||||
|
MADCTL_MY = 0x80 ///< Bottom to top
|
||||||
|
MADCTL_MX = 0x40 ///< Right to left
|
||||||
|
MADCTL_MV = 0x20 ///< Reverse Mode
|
||||||
|
MADCTL_ML = 0x10 ///< LCD refresh Bottom to top
|
||||||
|
MADCTL_RGB = 0x00 ///< Red-Green-Blue pixel order
|
||||||
|
MADCTL_BGR = 0x08 ///< Blue-Green-Red pixel order
|
||||||
|
MADCTL_MH = 0x04 ///< LCD refresh right to left
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Rotation0 Rotation = 0
|
||||||
|
Rotation90 Rotation = 1 // 90 degrees clock-wise rotation
|
||||||
|
Rotation180 Rotation = 2
|
||||||
|
Rotation270 Rotation = 3
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user