mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-20 06:28:59 +00:00
@@ -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