Преглед изворни кода

Add driver for XPT2046 touch controller (#1848)

FrankX пре 7 година
родитељ
комит
b4319bdb4b
4 измењених фајлова са 350 додато и 0 уклоњено
  1. 1 0
      app/include/user_modules.h
  2. 217 0
      app/modules/xpt2046.c
  3. 131 0
      docs/en/modules/xpt2046.md
  4. 1 0
      mkdocs.yml

+ 1 - 0
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__ */

+ 217 - 0
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);

+ 131 - 0
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)
+```

+ 1 - 0
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'