|
@@ -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);
|