From d43263f7647c2f3c57da048e0100bdc03242a518 Mon Sep 17 00:00:00 2001 From: BCG Date: Tue, 28 Jan 2020 12:55:31 -0500 Subject: [PATCH] Adding driver for four-wire resistive touchscreen (#118) * resistive: Adding driver for four-wire resistive touchscreen, as used on the Adafruit PyPortal. --- Makefile | 4 + README.md | 1 + examples/touch/resistive/fourwire/main.go | 80 ++++++++ .../resistive/pyportal_touchpaint/main.go | 185 ++++++++++++++++++ touch/point.go | 17 ++ touch/resistive/fourwire.go | 124 ++++++++++++ 6 files changed, 411 insertions(+) create mode 100644 examples/touch/resistive/fourwire/main.go create mode 100644 examples/touch/resistive/pyportal_touchpaint/main.go create mode 100644 touch/point.go create mode 100644 touch/resistive/fourwire.go diff --git a/Makefile b/Makefile index cf13ea3..6145c5d 100644 --- a/Makefile +++ b/Makefile @@ -85,6 +85,10 @@ smoke-test: @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/thermistor/main.go @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/touch/resistive/fourwire/main.go + @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/touch/resistive/pyportal_touchpaint/main.go + @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/vl53l1x/main.go @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd2in13/main.go diff --git a/README.md b/README.md index 0696433..349c3e8 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ The following 37 devices are supported. | [MMA8653 accelerometer](https://www.nxp.com/docs/en/data-sheet/MMA8653FC.pdf) | I2C | | [MPU6050 accelerometer/gyroscope](https://store.invensense.com/datasheets/invensense/MPU-6050_DataSheet_V3%204.pdf) | I2C | | [PCD8544 display](http://eia.udg.edu/~forest/PCD8544_1.pdf) | SPI | +| [Resistive Touchscreen (4-wire)](http://ww1.microchip.com/downloads/en/Appnotes/doc8091.pdf) | GPIO | | [Shift register](https://en.wikipedia.org/wiki/Shift_register#Parallel-in_serial-out_\(PISO\)) | GPIO | | [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 | diff --git a/examples/touch/resistive/fourwire/main.go b/examples/touch/resistive/fourwire/main.go new file mode 100644 index 0000000..894e2b2 --- /dev/null +++ b/examples/touch/resistive/fourwire/main.go @@ -0,0 +1,80 @@ +// demo of 4-wire touchscreen as described in app note: +// http://ww1.microchip.com/downloads/en/Appnotes/doc8091.pdf +package main + +import ( + "machine" + "math" + + "tinygo.org/x/drivers/touch" + "tinygo.org/x/drivers/touch/resistive" +) + +var ( + resistiveTouch = new(resistive.FourWire) +) + +const ( + Xmin = 750 + Xmax = 325 + Ymin = 840 + Ymax = 240 +) + +func main() { + + // configure touchscreen + machine.InitADC() + resistiveTouch.Configure(&resistive.FourWireConfig{ + YP: machine.TOUCH_YD, // y+ + YM: machine.TOUCH_YU, // y- + XP: machine.TOUCH_XR, // x+ + XM: machine.TOUCH_XL, // x- + }) + + last := touch.Point{} + + // loop and poll for touches, including performing debouncing + debounce := 0 + for { + + point := resistiveTouch.ReadTouchPoint() + touch := touch.Point{} + if point.Z>>6 > 100 { + touch.X = mapval(point.X>>6, Xmin, Xmax, 0, 240) + touch.Y = mapval(point.Y>>6, Ymin, Ymax, 0, 320) + touch.Z = point.Z >> 6 / 100 + } else { + touch.X = 0 + touch.Y = 0 + touch.Z = 0 + } + + if last.Z != touch.Z { + debounce = 0 + last = touch + } else if math.Abs(float64(touch.X-last.X)) > 4 || + math.Abs(float64(touch.Y-last.Y)) > 4 { + debounce = 0 + last = touch + } else if debounce > 1 { + debounce = 0 + HandleTouch(last) + } else if touch.Z > 0 { + debounce++ + } else { + last = touch + debounce = 0 + } + + } +} + +// based on Arduino's "map" function +func mapval(x int, inMin int, inMax int, outMin int, outMax int) int { + return (x-inMin)*(outMax-outMin)/(inMax-inMin) + outMin +} + +func HandleTouch(touch touch.Point) { + println("touch point:", touch.X, touch.Y, touch.Z) +} diff --git a/examples/touch/resistive/pyportal_touchpaint/main.go b/examples/touch/resistive/pyportal_touchpaint/main.go new file mode 100644 index 0000000..517d3c9 --- /dev/null +++ b/examples/touch/resistive/pyportal_touchpaint/main.go @@ -0,0 +1,185 @@ +package main + +import ( + "image/color" + "machine" + "math" + + "tinygo.org/x/drivers/ili9341" + "tinygo.org/x/drivers/touch" + "tinygo.org/x/drivers/touch/resistive" +) + +var ( + resistiveTouch = &resistive.FourWire{} + + display = ili9341.NewParallel( + machine.LCD_DATA0, + machine.TFT_WR, + machine.TFT_DC, + machine.TFT_CS, + machine.TFT_RESET, + machine.TFT_RD, + ) + + white = color.RGBA{255, 255, 255, 255} + black = color.RGBA{0, 0, 0, 255} + red = color.RGBA{255, 0, 0, 255} + green = color.RGBA{0, 255, 0, 255} + blue = color.RGBA{0, 0, 255, 255} + magenta = color.RGBA{255, 0, 255, 255} + yellow = color.RGBA{255, 255, 0, 255} + cyan = color.RGBA{0, 255, 255, 255} + + oldColor color.RGBA + currentColor color.RGBA +) + +const ( + penRadius = 3 + boxSize = 30 + + Xmin = 750 + Xmax = 325 + Ymin = 840 + Ymax = 240 +) + +func main() { + + // configure backlight + machine.TFT_BACKLIGHT.Configure(machine.PinConfig{machine.PinOutput}) + + // configure touchscreen + machine.InitADC() + resistiveTouch.Configure(&resistive.FourWireConfig{ + YP: machine.TOUCH_YD, + YM: machine.TOUCH_YU, + XP: machine.TOUCH_XR, + XM: machine.TOUCH_XL, + }) + + // configure display + display.Configure(ili9341.Config{}) + + // fill the background and activate the backlight + width, height := display.Size() + display.FillRectangle(0, 0, width, height, black) + machine.TFT_BACKLIGHT.High() + + // make color selection boxes + display.FillRectangle(0, 0, boxSize, boxSize, red) + display.FillRectangle(boxSize, 0, boxSize, boxSize, yellow) + display.FillRectangle(boxSize*2, 0, boxSize, boxSize, green) + display.FillRectangle(boxSize*3, 0, boxSize, boxSize, cyan) + display.FillRectangle(boxSize*4, 0, boxSize, boxSize, blue) + display.FillRectangle(boxSize*5, 0, boxSize, boxSize, magenta) + display.FillRectangle(boxSize*6, 0, boxSize, boxSize, black) + display.FillRectangle(boxSize*7, 0, boxSize, boxSize, white) + + // set the initial color to red and draw a box to highlight it + oldColor = red + currentColor = red + display.DrawRectangle(0, 0, boxSize, boxSize, white) + + last := touch.Point{} + + // loop and poll for touches, including performing debouncing + debounce := 0 + for { + + point := resistiveTouch.ReadTouchPoint() + touch := touch.Point{} + if point.Z>>6 > 100 { + rawX := mapval(point.X>>6, Xmin, Xmax, 0, 240) + rawY := mapval(point.Y>>6, Ymin, Ymax, 0, 320) + touch.X = rawX + touch.Y = rawY + touch.Z = 1 + } else { + touch.X = 0 + touch.Y = 0 + touch.Z = 0 + } + + if last.Z != touch.Z { + debounce = 0 + last = touch + } else if math.Abs(float64(touch.X-last.X)) > 4 || + math.Abs(float64(touch.Y-last.Y)) > 4 { + debounce = 0 + last = touch + } else if debounce > 1 { + debounce = 0 + HandleTouch(last) + } else if touch.Z > 0 { + debounce++ + } else { + last = touch + debounce = 0 + } + + } +} + +// based on Arduino's "map" function +func mapval(x int, inMin int, inMax int, outMin int, outMax int) int { + return (x-inMin)*(outMax-outMin)/(inMax-inMin) + outMin +} + +func HandleTouch(touch touch.Point) { + + if int16(touch.Y) < boxSize { + oldColor = currentColor + x := int16(touch.X) + switch { + case x < boxSize: + currentColor = red + case x < boxSize*2: + currentColor = yellow + case x < boxSize*3: + currentColor = green + case x < boxSize*4: + currentColor = cyan + case x < boxSize*5: + currentColor = blue + case x < boxSize*6: + currentColor = magenta + case x < boxSize*7: + currentColor = black + case x < boxSize*8: + currentColor = white + } + + if oldColor == currentColor { + return + } + + display.DrawRectangle((x/boxSize)*boxSize, 0, boxSize, boxSize, white) + switch oldColor { + case red: + x = 0 + case yellow: + x = boxSize + case green: + x = boxSize * 2 + case cyan: + x = boxSize * 3 + case blue: + x = boxSize * 4 + case magenta: + x = boxSize * 5 + case black: + x = boxSize * 6 + case white: + x = boxSize * 7 + } + display.FillRectangle(int16(x), 0, boxSize, boxSize, oldColor) + + } + + if (int16(touch.Y) - penRadius) > boxSize { + display.FillRectangle( + int16(touch.X), int16(touch.Y), penRadius*2, penRadius*2, currentColor) + } +} diff --git a/touch/point.go b/touch/point.go new file mode 100644 index 0000000..c5f84ef --- /dev/null +++ b/touch/point.go @@ -0,0 +1,17 @@ +package touch + +// Pointer is a device that is capable of reading a single touch point +type Pointer interface { + ReadTouchPoint() Point +} + +// Point represents the result of reading a single touch point from a screen. +// X and Y are the horizontal and vertical coordinates of the touch, while Z +// represents the touch pressure. In general, client code will want to inspect +// the value of Z to see if it is above some threshold to determine if a touch +// is detected at all. +type Point struct { + X int + Y int + Z int +} diff --git a/touch/resistive/fourwire.go b/touch/resistive/fourwire.go new file mode 100644 index 0000000..9f467d1 --- /dev/null +++ b/touch/resistive/fourwire.go @@ -0,0 +1,124 @@ +package resistive + +import ( + "machine" + + "tinygo.org/x/drivers/touch" +) + +// FourWire represents a resistive touchscreen with a four-wire interface as +// described in http://ww1.microchip.com/downloads/en/Appnotes/doc8091.pdf +type FourWire struct { + yp machine.ADC + ym machine.ADC + xp machine.ADC + xm machine.ADC + + readSamples int +} + +// FourWireConfig is passed to the Configure method. All of the pins must be +// specified for this to be a valid configuration. ReadSamples is optional, and +// if not set with default to 2. +type FourWireConfig struct { + + // Y+ pin, must be capable of analog reads + YP machine.Pin + + // Y- pin, must be capable of analog reads + YM machine.Pin + + // X+ pin, must be capable of analog reads + XP machine.Pin + + // X- pin, must be capable of analog reads + XM machine.Pin + + // If set, each call to ReadTouchPoint() will sample the X, Y, and Z values + // and average them. This can help smooth out spurious readings, for example + // ones that result from the capacitance of a TFT under the touchscreen + ReadSamples int +} + +// Configure should be called once before starting to read the device +func (res *FourWire) Configure(config *FourWireConfig) error { + + res.yp = machine.ADC{Pin: config.YP} + res.ym = machine.ADC{Pin: config.YM} + res.xp = machine.ADC{Pin: config.XP} + res.xm = machine.ADC{Pin: config.XM} + + if config.ReadSamples < 1 { + res.readSamples = 2 + } else { + res.readSamples = config.ReadSamples + } + + return nil +} + +// ReadTouchPoint reads a single touch.Point from the device. If the device +// was configured with ReadSamples > 1, each value will be sampled that many +// times and averaged to smooth over spurious results of the analog reads. +func (res *FourWire) ReadTouchPoint() (p touch.Point) { + p.X = int(sample(res.ReadX, res.readSamples)) + p.Y = int(sample(res.ReadY, res.readSamples)) + p.Z = int(sample(res.ReadZ, res.readSamples)) + return +} + +// sample the results of the provided function and average the results +func sample(fn func() uint16, numSamples int) (v uint16) { + sum := 0 + for n := 0; n < numSamples; n++ { + sum += int(fn()) + } + return uint16(sum / numSamples) +} + +// ReadX reads the "raw" X-value on a 16-bit scale without multiple sampling +func (res *FourWire) ReadX() uint16 { + res.ym.Pin.Configure(machine.PinConfig{Mode: machine.PinInputPulldown}) + + res.xp.Pin.Configure(machine.PinConfig{Mode: machine.PinOutput}) + res.xp.Pin.High() + + res.xm.Pin.Configure(machine.PinConfig{Mode: machine.PinOutput}) + res.xm.Pin.Low() + + res.yp.Configure() + + return 0xFFFF - res.yp.Get() +} + +// ReadY reads the "raw" Y-value on a 16-bit scale without multiple sampling +func (res *FourWire) ReadY() uint16 { + res.xm.Pin.Configure(machine.PinConfig{Mode: machine.PinInputPulldown}) + + res.yp.Pin.Configure(machine.PinConfig{Mode: machine.PinOutput}) + res.yp.Pin.High() + + res.ym.Pin.Configure(machine.PinConfig{Mode: machine.PinOutput}) + res.ym.Pin.Low() + + res.xp.Configure() + + return 0xFFFF - res.xp.Get() +} + +// ReadZ reads the "raw" Z-value on a 16-bit scale without multiple sampling +func (res *FourWire) ReadZ() uint16 { + res.xp.Pin.Configure(machine.PinConfig{Mode: machine.PinOutput}) + res.xp.Pin.Low() + + res.ym.Pin.Configure(machine.PinConfig{Mode: machine.PinOutput}) + res.ym.Pin.High() + + res.xm.Configure() + res.yp.Configure() + + z1 := res.xm.Get() + z2 := res.yp.Get() + + return 0xFFFF - (z2 - z1) +}