diff --git a/examples/xpt2046/main.go b/examples/xpt2046/gpio/main.go similarity index 100% rename from examples/xpt2046/main.go rename to examples/xpt2046/gpio/main.go diff --git a/examples/xpt2046/spi/main.go b/examples/xpt2046/spi/main.go new file mode 100644 index 000000000..b914e4dba --- /dev/null +++ b/examples/xpt2046/spi/main.go @@ -0,0 +1,114 @@ +package main + +import ( + "image/color" + "machine" + "strconv" + "time" + "tinygo.org/x/drivers" + "tinygo.org/x/drivers/st7789" + // "tinygo.org/x/drivers/xpt2046" + "github.com/gregoster/tinygo-drivers/xpt2046" + "tinygo.org/x/tinyfont" + "tinygo.org/x/tinyfont/freemono" +) + +func initSPI() drivers.SPI { + machine.SPI1.Configure(machine.SPIConfig{ + Frequency: 3000000, + Mode: 0, + }) + + return machine.SPI1 + +} + +func initDisplay(bus drivers.SPI) (drivers.Displayer, st7789.Device) { + + // https://www.waveshare.com/wiki/Pico-ResTouch-LCD-2.8 + display := st7789.New(bus, + machine.GPIO15, // TFT_RESET - reset pin + machine.GPIO8, // TFT_DC - Data/Command + machine.GPIO9, // TFT_CS - Chip Select + machine.GPIO13) // TFT_LITE - Backlite pin + + display.Configure(st7789.Config{ + Rotation: st7789.ROTATION_270, + RowOffset: 0, + FrameRate: st7789.FRAMERATE_60, + VSyncLines: st7789.MAX_VSYNC_SCANLINES, + Width: 240, + Height: 320, + }) + return &display, display +} + +func initTouch(bus drivers.SPI) xpt2046.Device { + // clk := machine.GPIO10 // TP_CLK + cs := machine.GPIO16 // TP_CS + // din := machine.GPIO11 // MOSI + // dout := machine.GPIO12 // MISO + irq := machine.GPIO17 // TP_IRQ + + touchScreen := xpt2046.New(bus, cs, irq) + + touchScreen.Configure(&xpt2046.Config{ + Precision: 10, //Maximum number of samples for a single ReadTouchPoint to improve accuracy. + }) + return touchScreen + +} + +func main() { + + SPI := initSPI() + + touchScreen := initTouch(SPI) + + display, _ := initDisplay(SPI) + 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} + // yellow := color.RGBA{255,255,0,255} + + tinyfont.WriteLine(display, &freemono.Regular9pt7b, 30, + 80, strconv.Itoa(int(width))+"x"+strconv.Itoa(int(height)), green) + + prev := "" + + // tinyfont.WriteLine(display, &freemono.Regular9pt7b, 0, 160, "HERE!", red) + // tinyfont.WriteLine(display, &freemono.Regular9pt7b, 0, 200, strconv.Itoa(int(machine.SPI1.GetBaudRate())), red) + + for { + + //Wait for a touch + for !touchScreen.Touched() { + time.Sleep(50 * time.Millisecond) + } + + if prev != "" { + tinyfont.WriteLine(display, &freemono.Regular9pt7b, 0, 180, prev, black) + } + + touch := touchScreen.ReadTouchPoint() + + //X and Y are 16 bit with 12 bit resolution and need to be scaled for the display size + //Z is 24 bit and is typically > 2000 for a touch + + //Example of scaling for a 240x320 display + + prev = strconv.Itoa(int((touch.X*240)>>16)) + "x" + strconv.Itoa(int(touch.Y*320)>>16) + tinyfont.WriteLine(display, &freemono.Regular9pt7b, 0, 180, prev, blue) + + //Wait for touch to end + for touchScreen.Touched() { + time.Sleep(50 * time.Millisecond) + } + } + +} diff --git a/go.mod b/go.mod index 0c281c758..515912d4c 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,5 @@ -module tinygo.org/x/drivers +// module tinygo.org/x/drivers +module github.com/gregoster/tinygo-drivers go 1.15 @@ -7,6 +8,7 @@ require ( github.com/frankban/quicktest v1.10.2 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 golang.org/x/net v0.0.0-20210614182718-04defd469f4e + tinygo.org/x/drivers v0.19.0 tinygo.org/x/tinyfont v0.3.0 tinygo.org/x/tinyterm v0.1.0 ) diff --git a/go.sum b/go.sum index 48c76086d..3e219a4ef 100644 --- a/go.sum +++ b/go.sum @@ -29,6 +29,7 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T tinygo.org/x/drivers v0.14.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= tinygo.org/x/drivers v0.15.1/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= tinygo.org/x/drivers v0.16.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= +tinygo.org/x/drivers v0.19.0 h1:x/TIC8SFWeGViJvcYO1ATjgwSNF7hHN2ouAyjMUXI2Q= tinygo.org/x/drivers v0.19.0/go.mod h1:uJD/l1qWzxzLx+vcxaW0eY464N5RAgFi1zTVzASFdqI= tinygo.org/x/tinyfont v0.2.1/go.mod h1:eLqnYSrFRjt5STxWaMeOWJTzrKhXqpWw7nU3bPfKOAM= tinygo.org/x/tinyfont v0.3.0 h1:HIRLQoI3oc+2CMhPcfv+Ig88EcTImE/5npjqOnMD4lM= diff --git a/xpt2046/xpt2046.go b/xpt2046/xpt2046.go index d1ef26902..7d79e6582 100644 --- a/xpt2046/xpt2046.go +++ b/xpt2046/xpt2046.go @@ -1,4 +1,5 @@ -// Package xpt2046 implements a driver for the XPT2046 resistive touch controller as packaged on the TFT_320QVT board +// Package xpt2046 implements a driver for the XPT2046 resistive touch controller +// as packaged on the TFT_320QVT board or a Waveshare Pico-ResTouch-LCD-2.8 board. // // Datasheet: http://grobotronics.com/images/datasheets/xpt2046-datasheet.pdf package xpt2046 @@ -6,11 +7,16 @@ package xpt2046 import ( "machine" "time" - + "tinygo.org/x/drivers" "tinygo.org/x/drivers/touch" ) +// This driver supports both GPIO and SPI interfaces for a given touch controller, +// but not at the same time. GPIO works fine unless t_clk, t_din, and t_dout +// are shared with another device and that device is expecting to use SPI over +// those pins. type Device struct { + bus drivers.SPI t_clk machine.Pin t_cs machine.Pin t_din machine.Pin @@ -20,12 +26,16 @@ type Device struct { precision uint8 } +// Simple configuration -- Precision indicates the number of samples +// averaged to produce X, Y, and Z (pressure) coordinates. type Config struct { Precision uint8 } +// Create a new GPIO-based device. SPI bus not used in this setup. func New(t_clk, t_cs, t_din, t_dout, t_irq machine.Pin) Device { return Device{ + bus: nil, precision: 10, t_clk: t_clk, t_cs: t_cs, @@ -35,6 +45,19 @@ func New(t_clk, t_cs, t_din, t_dout, t_irq machine.Pin) Device { } } +// Create a new SPI-based device. GPIO not available for this instance +// when SPI is used. +func NewSPI(bus drivers.SPI, t_cs, t_irq machine.Pin) Device { + return Device{ + bus: bus, + precision: 10, + t_cs: t_cs, + t_irq: t_irq, + } +} + +// Configure a GPIO-based device. Sets up the Precision of the device +// and initializes the GPIO pins. func (d *Device) Configure(config *Config) error { if config.Precision == 0 { @@ -59,10 +82,55 @@ func (d *Device) Configure(config *Config) error { return nil } +// Configure a SPI-based device. Sets up the Precision of the device. +// Also initializes t_cs and t_irq. All of the other pins in Device are +// used by SPI. +func (d *Device) ConfigureSPI(config *Config) error { + + if config.Precision == 0 { + d.precision = 10 + } else { + d.precision = config.Precision + } + + d.t_cs.Configure(machine.PinConfig{Mode: machine.PinOutput}) + d.t_irq.Configure(machine.PinConfig{Mode: machine.PinInput}) + + d.t_cs.High() + + //S = 1 --> Required Control bit + //A2-A0 = 000 --> nothing + //MODE = 0 --> 12 bit conversion + //SER/DFR = 0 --> Differential preferred for pressure + //PD1-PD0 = 10 --> Powerdown and enable PEN_IRQ + d.tx([]byte{0x80}) // make sure PD1 is cleared on start + return nil +} + +// tx and rx pulled from st7789, modified for xps2046 + +// tx sends data to the touchpad. +func (d *Device) tx(data []byte) { + d.t_cs.Low() + d.bus.Tx(data, nil) + d.t_cs.High() +} + +// rx reads data from the touchpad. +func (d *Device) rx(command uint8, data []byte) { + cmd := make([]byte, len(data)) + cmd[0] = command + d.t_cs.Low() + d.bus.Tx(cmd, data) + d.t_cs.High() +} + +// Very short sleep for GPIO pulsing. func busSleep() { time.Sleep(5 * time.Nanosecond) } +// Pulse the given pin p high and then low. func pulseHigh(p machine.Pin) { p.High() busSleep() @@ -70,6 +138,7 @@ func pulseHigh(p machine.Pin) { busSleep() } +// Write a command to the touchscreen using GPIO. func (d *Device) writeCommand(data uint8) { for count := uint8(0); count < 8; count++ { @@ -80,6 +149,7 @@ func (d *Device) writeCommand(data uint8) { } +// Read whatever data is waiting on t_dout using GPIO. func (d *Device) readData() uint16 { data := uint16(0) @@ -99,22 +169,40 @@ func (d *Device) readData() uint16 { return data } +// Read the X, Y, and Z (pressure) coordinates of the point currently being +// touched on the screen. Works for both GPIO and SPI by calling the associated +// raw read routines. The device is queried at most d.precision times, with +// the resulting touch.Point having the average values for each of X, Y, and Z. func (d *Device) ReadTouchPoint() touch.Point { tx := uint32(0) ty := uint32(0) tz := uint32(0) + rx := int32(0) + ry := int32(0) + rz := int32(0) sampleCount := uint8(0) - d.t_cs.Low() + if d.bus == nil { + d.t_cs.Low() + } for ; sampleCount < d.precision && d.Touched(); sampleCount++ { - rx, ry, rz := d.readRaw() + if d.bus == nil { + rx, ry, rz = d.readRaw() + } else { + rx, ry, rz = d.readRawSPI() + } tx += uint32(rx) ty += uint32(ry) tz += uint32(rz) + if d.bus != nil { + time.Sleep(200 * time.Microsecond) + } + } + if d.bus == nil { + d.t_cs.High() } - d.t_cs.High() if sampleCount > 0 { x := int(tx / uint32(sampleCount)) @@ -134,11 +222,13 @@ func (d *Device) ReadTouchPoint() touch.Point { } } +// Touched() is true if the touch device senses a touch at the current moment. func (d *Device) Touched() bool { avail := !d.t_irq.Get() return avail } +// Read the current X, Y, and Z values using the GPIO interface. func (d *Device) readRaw() (int32, int32, int32) { d.t_cs.Low() @@ -186,3 +276,54 @@ func (d *Device) readRaw() (int32, int32, int32) { //Scale X&Y to 16 bit for consistency across touch drivers return int32(tx) << 4, int32(4096-ty) << 4, tz } + +// Read the current X, Y, and Z values using the SPI interface. +func (d *Device) readRawSPI() (int32, int32, int32) { + + data := make([]byte, 4) + + //S = 1 --> Required Control bit + //A2-A0 = 101 --> X-Position + //MODE = 0 --> 12 bit conversion + //SER/DFR = 0 --> Differential preferred for X,Y position + //PD1-PD0 = 00 --> Powerdown and enable PEN_IRQ + + d.rx(0xD0, data) + tx := int32((uint16(data[1])<<8 | uint16(data[2])) >> 3) // 7 bits come from data[1], remaining 5 from top of data[2] + + //S = 1 --> Required Control bit + //A2-A0 = 001 --> Y-Position + //MODE = 0 --> 12 bit conversion + //SER/DFR = 0 --> Differential preferred for X,Y position + //PD1-PD0 = 00 --> Powerdown and enable PEN_IRQ + + d.rx(0x90, data) + ty := int32((uint16(data[1])<<8 | uint16(data[2])) >> 3) // 7 bits come from data[0], remaining 5 from top of data[1] + + //S = 1 --> Required Control bit + //A2-A0 = 011 --> Z1-position (pressure) + //MODE = 0 --> 12 bit conversion + //SER/DFR = 0 --> Differential preferred for pressure + //PD1-PD0 = 00 --> Powerdown and enable PEN_IRQ + + d.rx(0xB0, data) + tz1 := int32((uint16(data[1])<<8 | uint16(data[2])) >> 3) // 7 bits come from data[0], remaining 5 from top of data[1] + + //S = 1 --> Required Control bit + //A2-A0 = 100 --> Z2-position (pressure) + //MODE = 0 --> 12 bit conversion + //SER/DFR = 0 --> Differential preferred for pressure + //PD1-PD0 = 00 --> Powerdown and enable PEN_IRQ + + d.rx(0xC0, data) + tz2 := int32((uint16(data[1])<<8 | uint16(data[2])) >> 3) // 7 bits come from data[0], remaining 5 from top of data[1] + + tz := int32(0) + if tz1 != 0 { + //Touch pressure is proportional to the ratio of z2 to z1 and the x position. + tz = int32(tx) * ((tz2 << 12) / (tz1 << 12)) + } + + //Scale X&Y to 16 bit for consistency across touch drivers + return int32(tx) << 4, int32(4096-ty) << 4, tz +}