From b4319bdb4b5c5b23335e27bd306c19f48f339b02 Mon Sep 17 00:00:00 2001 From: FrankX Date: Thu, 9 Mar 2017 07:20:01 +0100 Subject: [PATCH] Add driver for XPT2046 touch controller (#1848) --- app/include/user_modules.h | 1 + app/modules/xpt2046.c | 217 +++++++++++++++++++++++++++++++++++++ docs/en/modules/xpt2046.md | 131 ++++++++++++++++++++++ mkdocs.yml | 1 + 4 files changed, 350 insertions(+) create mode 100644 app/modules/xpt2046.c create mode 100644 docs/en/modules/xpt2046.md diff --git a/app/include/user_modules.h b/app/include/user_modules.h index cf4f5d56..2d2c19ad 100644 --- a/app/include/user_modules.h +++ b/app/include/user_modules.h @@ -71,6 +71,7 @@ //#define LUA_USE_MODULES_WPS //#define LUA_USE_MODULES_WS2801 //#define LUA_USE_MODULES_WS2812 +//#define LUA_USE_MODULES_XPT2046 #endif /* LUA_CROSS_COMPILER */ #endif /* __USER_MODULES_H__ */ diff --git a/app/modules/xpt2046.c b/app/modules/xpt2046.c new file mode 100644 index 00000000..5d00f954 --- /dev/null +++ b/app/modules/xpt2046.c @@ -0,0 +1,217 @@ +// Module for xpt2046 +// by Starofall, F.J. Exoo +// used source code from: +// - https://github.com/spapadim/XPT2046/ +// - https://github.com/PaulStoffregen/XPT2046_Touchscreen/ + +#include "module.h" +#include "lauxlib.h" +#include "platform.h" + +// Hardware specific values +static const uint16_t CAL_MARGIN = 0; // Set to 0: up to the application +static const uint8_t CTRL_LO_DFR = 0b0011; +static const uint8_t CTRL_LO_SER = 0b0100; +static const uint8_t CTRL_HI_X = 0b1001 << 4; +static const uint8_t CTRL_HI_Y = 0b1101 << 4; +static const uint16_t ADC_MAX = 0x0fff; // 12 bits + +// Runtime variables +static uint16_t _width, _height; +static uint8_t _cs_pin, _irq_pin; +static int32_t _cal_dx, _cal_dy, _cal_dvi, _cal_dvj; +static uint16_t _cal_vi1, _cal_vj1; + +// Average pair with least distance between each +static int16_t besttwoavg( int16_t x , int16_t y , int16_t z ) { + int16_t da, db, dc; + int16_t reta = 0; + + if ( x > y ) da = x - y; else da = y - x; + if ( x > z ) db = x - z; else db = z - x; + if ( z > y ) dc = z - y; else dc = y - z; + + if ( da <= db && da <= dc ) reta = (x + y) >> 1; + else if ( db <= da && db <= dc ) reta = (x + z) >> 1; + else reta = (y + z) >> 1; + + return reta; +} + +// Checks if the irq_pin is down +static int isTouching() { + return (platform_gpio_read(_irq_pin) == 0); +} + +// transfer 16 bits from the touch display - returns the recived uint16_t +static uint16_t transfer16(uint16_t _data) { + union { uint16_t val; struct { uint8_t lsb; uint8_t msb; }; } t; + t.val = _data; + t.msb = platform_spi_send_recv(1, 8, t.msb); + t.lsb = platform_spi_send_recv(1, 8, t.lsb); + return t.val; +} + +// reads the value from the touch panel +static uint16_t _readLoop(uint8_t ctrl, uint8_t max_samples) { + uint16_t prev = 0xffff, cur = 0xffff; + uint8_t i = 0; + do { + prev = cur; + cur = platform_spi_send_recv(1, 8 , 0); + cur = (cur << 4) | (platform_spi_send_recv(1, 8 , ctrl) >> 4); // 16 clocks -> 12-bits (zero-padded at end) + } while ((prev != cur) && (++i < max_samples)); + return cur; +} + +// Returns the raw position information +static void getRaw(uint16_t *vi, uint16_t *vj) { + // Implementation based on TI Technical Note http://www.ti.com/lit/an/sbaa036/sbaa036.pdf + + // Disable interrupt: reading position generates false interrupt + ETS_GPIO_INTR_DISABLE(); + + platform_gpio_write(_cs_pin, PLATFORM_GPIO_LOW); + platform_spi_send_recv(1, 8 , CTRL_HI_X | CTRL_LO_DFR); // Send first control int + *vi = _readLoop(CTRL_HI_X | CTRL_LO_DFR, 255); + *vj = _readLoop(CTRL_HI_Y | CTRL_LO_DFR, 255); + + // Turn off ADC by issuing one more read (throwaway) + // This needs to be done, because PD=0b11 (needed for MODE_DFR) will disable PENIRQ + platform_spi_send_recv(1, 8 , 0); // Maintain 16-clocks/conversion; _readLoop always ends after issuing a control int + platform_spi_send_recv(1, 8 , CTRL_HI_Y | CTRL_LO_SER); + transfer16(0); // Flush last read, just to be sure + + platform_gpio_write(_cs_pin, PLATFORM_GPIO_HIGH); + + // Clear interrupt status + GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, BIT(pin_num[_irq_pin])); + // Enable interrupt again + ETS_GPIO_INTR_ENABLE(); +} + +// sets the calibration of the display +static void setCalibration (uint16_t vi1, uint16_t vj1, uint16_t vi2, uint16_t vj2) { + _cal_dx = _width - 2*CAL_MARGIN; + _cal_dy = _height - 2*CAL_MARGIN; + + _cal_vi1 = (int32_t)vi1; + _cal_vj1 = (int32_t)vj1; + _cal_dvi = (int32_t)vi2 - vi1; + _cal_dvj = (int32_t)vj2 - vj1; +} + +// returns the position on the screen by also applying the calibration +static void getPosition (uint16_t *x, uint16_t *y) { + if (isTouching() == 0) { + *x = *y = 0xffff; + return; + } + uint16_t vi, vj; + + getRaw(&vi, &vj); + + // Map to (un-rotated) display coordinates + *x = (uint16_t)(_cal_dx * (vj - _cal_vj1) / _cal_dvj + CAL_MARGIN); + if (*x > 0x7fff) *x = 0; + *y = (uint16_t)(_cal_dy * (vi - _cal_vi1) / _cal_dvi + CAL_MARGIN); + if (*y > 0x7fff) *y = 0; +} + + +// Lua: xpt2046.init(cspin, irqpin, height, width) +static int xpt2046_init( lua_State* L ) { + _cs_pin = luaL_checkinteger( L, 1 ); + _irq_pin = luaL_checkinteger( L, 2 ); + _height = luaL_checkinteger( L, 3 ); + _width = luaL_checkinteger( L, 4 ); + // set pins correct + platform_gpio_mode(_cs_pin, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT ); + + setCalibration( + /*vi1=*/((int32_t)CAL_MARGIN) * ADC_MAX / _width, + /*vj1=*/((int32_t)CAL_MARGIN) * ADC_MAX / _height, + /*vi2=*/((int32_t)_width - CAL_MARGIN) * ADC_MAX / _width, + /*vj2=*/((int32_t)_height - CAL_MARGIN) * ADC_MAX / _height + ); + + // assume spi was inited before with a clockDiv of >=16 + // as higher spi clock speed produced inaccurate results + + // do first powerdown + platform_gpio_write(_cs_pin, PLATFORM_GPIO_LOW); + + // Issue a throw-away read, with power-down enabled (PD{1,0} == 0b00) + // Otherwise, ADC is disabled + platform_spi_send_recv(1, 8, CTRL_HI_Y | CTRL_LO_SER); + transfer16(0); // Flush, just to be sure + + platform_gpio_write(_cs_pin, PLATFORM_GPIO_HIGH); + return 0; +} + +// Lua: xpt2046.isTouched() +static int xpt2046_isTouched( lua_State* L ) { + lua_pushboolean( L, isTouching()); + return 1; +} + +// Lua: xpt2046.setCalibration(a,b,c,d) +static int xpt2046_setCalibration( lua_State* L ) { + int32_t a = luaL_checkinteger( L, 1 ); + int32_t b = luaL_checkinteger( L, 2 ); + int32_t c = luaL_checkinteger( L, 3 ); + int32_t d = luaL_checkinteger( L, 4 ); + setCalibration(a,b,c,d); + return 0; +} + +// Lua: xpt2046.xpt2046_getRaw() +static int xpt2046_getRaw( lua_State* L ) { + uint16_t x, y; + getRaw(&x, &y); + lua_pushinteger( L, x); + lua_pushinteger( L, y); + return 2; +} + +// Lua: xpt2046.xpt2046_getPosition() +static int xpt2046_getPosition( lua_State* L ) { + uint16_t x, y; + getPosition(&x, &y); + lua_pushinteger( L, x); + lua_pushinteger( L, y); + return 2; +} + + +// Lua: xpt2046.xpt2046_getPositionAvg() +static int xpt2046_getPositionAvg( lua_State* L ) { + // Run three times + uint16_t x1, y1, x2, y2, x3, y3; + getPosition(&x1, &y1); + getPosition(&x2, &y2); + getPosition(&x3, &y3); + + // Average the two best results + int16_t x = besttwoavg(x1,x2,x3); + int16_t y = besttwoavg(y1,y2,y3); + + lua_pushinteger( L, x); + lua_pushinteger( L, y); + return 2; +} + +// Module function map +static const LUA_REG_TYPE xpt2046_map[] = { + { LSTRKEY( "isTouched"), LFUNCVAL(xpt2046_isTouched) }, + { LSTRKEY( "getRaw" ), LFUNCVAL(xpt2046_getRaw) }, + { LSTRKEY( "getPosition"), LFUNCVAL(xpt2046_getPosition)}, + { LSTRKEY( "getPositionAvg"), LFUNCVAL(xpt2046_getPositionAvg)}, + { LSTRKEY( "setCalibration"), LFUNCVAL(xpt2046_setCalibration)}, + { LSTRKEY( "init" ), LFUNCVAL(xpt2046_init) }, + { LNILKEY, LNILVAL } +}; + + +NODEMCU_MODULE(XPT2046, "xpt2046", xpt2046_map, NULL); diff --git a/docs/en/modules/xpt2046.md b/docs/en/modules/xpt2046.md new file mode 100644 index 00000000..7ee4dd7c --- /dev/null +++ b/docs/en/modules/xpt2046.md @@ -0,0 +1,131 @@ +# XPT2046 Module +| Since | Origin / Contributor | Maintainer | Source | +| :----- | :-------------------- | :---------- | :------ | +| 2017-03-09| [Starofall](https://github.com/nodemcu/nodemcu-firmware/pull/1242)/[Frank Exoo](https://github.com/FrankX0) | [Frank Exoo](https://github.com/FrankX0) | [xpt2046.c](../../../app/modules/xpt2046.c)| + +XPT2046 is a touch controller used by several cheap displays - often in combination with the ILI9341 display controller. +The module is built based on the libraries of [spapadim](https://github.com/spapadim/XPT2046/) and [PaulStoffregen](https://github.com/PaulStoffregen/XPT2046_Touchscreen). + + +## xpt2046.init() +Initiates the XPT2046 module to read touch values from the display. It is required to call [`spi.setup()`](spi.md#spisetup) before calling `xpt2046.init` (see example). +As the ucg lib also requires [`spi.setup()`](spi.md#spisetup) to be called before it is important to only call it once in total and to activate `spi.FULLDUPLEX`. +The `clock_div` used in [`spi.setup()`](spi.md#spisetup) should be 16 or higher, as lower values might produces inaccurate results. + +#### Syntax +`xpt2046.init(cs_pin, irq_pin, height, width)` + +#### Parameters +- `cs_pin` GPIO pin for cs +- `irq_pin` GPIO pin for irq +- `height` display height in pixel +- `width` display width in pixel + +#### Returns +`nil` + +#### Example +```lua +-- Setup spi with `clock_div` of 16 and spi.FULLDUPLEX +spi.setup(1, spi.MASTER, spi.CPOL_LOW, spi.CPHA_LOW, 8, 16,spi.FULLDUPLEX) +-- SETTING UP DISPLAY (using ucg module) +local disp = ucg.ili9341_18x240x320_hw_spi(8, 4, 0) +disp:begin(0) +-- SETTING UP TOUCH +xpt2046.init(2,1,320,240) +xpt2046.setCalibration(198, 1776, 1762, 273) +``` + + +## xpt2046.setCalibration() +Sets the calibration of the display. Calibration values can be optained by using [`xpt2046.getRaw()`](#xpt2046getraw) and read the values in the edges. + +#### Syntax +`xpt2046.setCalibration(x1, y1, x2, y2)` + +#### Parameters +- `x1` raw x value at top left +- `y1` raw y value at top left +- `x2` raw x value at bottom right +- `y2` raw y value at bottom right + +#### Returns +`nil` + + +## xpt2046.isTouched() +Checks if the touch panel is touched. + +#### Syntax +`xpt2046.isTouched()` + +#### Returns +`true` if the display is touched, else `false` + +#### Example +```lua +if(xpt2046.isTouched()) then + local x, y = xpt2046.getPosition() + print(x .. "-" .. y) +end +``` + + +## xpt2046.getPosition() +Returns the position the display is touched using the calibration values and given width and height. +Can be used in an interrupt pin callback to return the coordinates when the touch screen is touched. + +#### Syntax +`xpt2046.getPosition()` + +#### Returns +returns both the x and the y position. + +#### Example +```lua +-- Setup spi with `clock_div` of 16 and spi.FULLDUPLEX +spi.setup(1, spi.MASTER, spi.CPOL_LOW, spi.CPHA_LOW, 8, 16,spi.FULLDUPLEX) +-- SETTING UP TOUCH +cs_pin = 2 -- GPIO4 +irq_pin = 3 -- GPIO0 +height = 240 +width = 320 +xpt2046.init(cs_pin, irq_pin, width, height) +xpt2046.setCalibration(198, 1776, 1762, 273) +gpio.mode(irq_pin,gpio.INT,gpio.PULLUP) +gpio.trig(irq_pin, "down", function() + print(xpt2046.getPosition()) +end) +``` + + +## xpt2046.getPositionAvg() +To create better measurements this function reads the position three times and averages the two positions with the least distance. + +#### Syntax +`xpt2046.getPositionAvg()` + +#### Returns +returns both the x and the y position. + +#### Example +```lua +local x, y = xpt2046.getPositionAvg() +print(x .. "-" .. y) +``` + + +## xpt2046.getRaw() +Reads the raw value from the display. Useful for debugging and custom conversions. + +#### Syntax +`xpt2046.getRaw()` + +#### Returns +returns both the x and the y position as a raw value. + +#### Example +```lua +local rawX, rawY = xpt2046.getRaw() +print(rawX .. "-" .. rawY) +``` diff --git a/mkdocs.yml b/mkdocs.yml index d76220de..d2753d21 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -89,5 +89,6 @@ pages: - 'wps': 'en/modules/wps.md' - 'ws2801': 'en/modules/ws2801.md' - 'ws2812': 'en/modules/ws2812.md' + - 'xpt2046': 'en/modules/xpt2046.md' #- Deutsch: # - Home: 'de/index.md'