xpt2046.c 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. // Module for xpt2046
  2. // by Starofall, F.J. Exoo
  3. // used source code from:
  4. // - https://github.com/spapadim/XPT2046/
  5. // - https://github.com/PaulStoffregen/XPT2046_Touchscreen/
  6. #include "module.h"
  7. #include "lauxlib.h"
  8. #include "platform.h"
  9. // Hardware specific values
  10. static const uint16_t CAL_MARGIN = 0; // Set to 0: up to the application
  11. static const uint8_t CTRL_LO_DFR = 0b0011;
  12. static const uint8_t CTRL_LO_SER = 0b0100;
  13. static const uint8_t CTRL_HI_X = 0b1001 << 4;
  14. static const uint8_t CTRL_HI_Y = 0b1101 << 4;
  15. static const uint16_t ADC_MAX = 0x0fff; // 12 bits
  16. // Runtime variables
  17. static uint16_t _width, _height;
  18. static uint8_t _cs_pin, _irq_pin;
  19. static int32_t _cal_dx, _cal_dy, _cal_dvi, _cal_dvj;
  20. static uint16_t _cal_vi1, _cal_vj1;
  21. // Average pair with least distance between each
  22. static int16_t besttwoavg( int16_t x , int16_t y , int16_t z ) {
  23. int16_t da, db, dc;
  24. int16_t reta = 0;
  25. if ( x > y ) da = x - y; else da = y - x;
  26. if ( x > z ) db = x - z; else db = z - x;
  27. if ( z > y ) dc = z - y; else dc = y - z;
  28. if ( da <= db && da <= dc ) reta = (x + y) >> 1;
  29. else if ( db <= da && db <= dc ) reta = (x + z) >> 1;
  30. else reta = (y + z) >> 1;
  31. return reta;
  32. }
  33. // Checks if the irq_pin is down
  34. static int isTouching() {
  35. return (platform_gpio_read(_irq_pin) == 0);
  36. }
  37. // transfer 16 bits from the touch display - returns the recived uint16_t
  38. static uint16_t transfer16(uint16_t _data) {
  39. union { uint16_t val; struct { uint8_t lsb; uint8_t msb; }; } t;
  40. t.val = _data;
  41. t.msb = platform_spi_send_recv(1, 8, t.msb);
  42. t.lsb = platform_spi_send_recv(1, 8, t.lsb);
  43. return t.val;
  44. }
  45. // reads the value from the touch panel
  46. static uint16_t _readLoop(uint8_t ctrl, uint8_t max_samples) {
  47. uint16_t prev = 0xffff, cur = 0xffff;
  48. uint8_t i = 0;
  49. do {
  50. prev = cur;
  51. cur = platform_spi_send_recv(1, 8 , 0);
  52. cur = (cur << 4) | (platform_spi_send_recv(1, 8 , ctrl) >> 4); // 16 clocks -> 12-bits (zero-padded at end)
  53. } while ((prev != cur) && (++i < max_samples));
  54. return cur;
  55. }
  56. // Returns the raw position information
  57. static void getRaw(uint16_t *vi, uint16_t *vj) {
  58. // Implementation based on TI Technical Note http://www.ti.com/lit/an/sbaa036/sbaa036.pdf
  59. // Disable interrupt: reading position generates false interrupt
  60. ETS_GPIO_INTR_DISABLE();
  61. platform_gpio_write(_cs_pin, PLATFORM_GPIO_LOW);
  62. platform_spi_send_recv(1, 8 , CTRL_HI_X | CTRL_LO_DFR); // Send first control int
  63. *vi = _readLoop(CTRL_HI_X | CTRL_LO_DFR, 255);
  64. *vj = _readLoop(CTRL_HI_Y | CTRL_LO_DFR, 255);
  65. // Turn off ADC by issuing one more read (throwaway)
  66. // This needs to be done, because PD=0b11 (needed for MODE_DFR) will disable PENIRQ
  67. platform_spi_send_recv(1, 8 , 0); // Maintain 16-clocks/conversion; _readLoop always ends after issuing a control int
  68. platform_spi_send_recv(1, 8 , CTRL_HI_Y | CTRL_LO_SER);
  69. transfer16(0); // Flush last read, just to be sure
  70. platform_gpio_write(_cs_pin, PLATFORM_GPIO_HIGH);
  71. // Clear interrupt status
  72. GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, BIT(pin_num[_irq_pin]));
  73. // Enable interrupt again
  74. ETS_GPIO_INTR_ENABLE();
  75. }
  76. // sets the calibration of the display
  77. static void setCalibration (uint16_t vi1, uint16_t vj1, uint16_t vi2, uint16_t vj2) {
  78. _cal_dx = _width - 2*CAL_MARGIN;
  79. _cal_dy = _height - 2*CAL_MARGIN;
  80. _cal_vi1 = (int32_t)vi1;
  81. _cal_vj1 = (int32_t)vj1;
  82. _cal_dvi = (int32_t)vi2 - vi1;
  83. _cal_dvj = (int32_t)vj2 - vj1;
  84. }
  85. // returns the position on the screen by also applying the calibration
  86. static void getPosition (uint16_t *x, uint16_t *y) {
  87. if (isTouching() == 0) {
  88. *x = *y = 0xffff;
  89. return;
  90. }
  91. uint16_t vi, vj;
  92. getRaw(&vi, &vj);
  93. // Map to (un-rotated) display coordinates
  94. *x = (uint16_t)(_cal_dx * (vj - _cal_vj1) / _cal_dvj + CAL_MARGIN);
  95. if (*x > 0x7fff) *x = 0;
  96. *y = (uint16_t)(_cal_dy * (vi - _cal_vi1) / _cal_dvi + CAL_MARGIN);
  97. if (*y > 0x7fff) *y = 0;
  98. }
  99. // Lua: xpt2046.init(cspin, irqpin, height, width)
  100. static int xpt2046_init( lua_State* L ) {
  101. _cs_pin = luaL_checkinteger( L, 1 );
  102. _irq_pin = luaL_checkinteger( L, 2 );
  103. _height = luaL_checkinteger( L, 3 );
  104. _width = luaL_checkinteger( L, 4 );
  105. // set pins correct
  106. platform_gpio_mode(_cs_pin, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT );
  107. setCalibration(
  108. /*vi1=*/((int32_t)CAL_MARGIN) * ADC_MAX / _width,
  109. /*vj1=*/((int32_t)CAL_MARGIN) * ADC_MAX / _height,
  110. /*vi2=*/((int32_t)_width - CAL_MARGIN) * ADC_MAX / _width,
  111. /*vj2=*/((int32_t)_height - CAL_MARGIN) * ADC_MAX / _height
  112. );
  113. // assume spi was inited before with a clockDiv of >=16
  114. // as higher spi clock speed produced inaccurate results
  115. // do first powerdown
  116. platform_gpio_write(_cs_pin, PLATFORM_GPIO_LOW);
  117. // Issue a throw-away read, with power-down enabled (PD{1,0} == 0b00)
  118. // Otherwise, ADC is disabled
  119. platform_spi_send_recv(1, 8, CTRL_HI_Y | CTRL_LO_SER);
  120. transfer16(0); // Flush, just to be sure
  121. platform_gpio_write(_cs_pin, PLATFORM_GPIO_HIGH);
  122. return 0;
  123. }
  124. // Lua: xpt2046.isTouched()
  125. static int xpt2046_isTouched( lua_State* L ) {
  126. lua_pushboolean( L, isTouching());
  127. return 1;
  128. }
  129. // Lua: xpt2046.setCalibration(a,b,c,d)
  130. static int xpt2046_setCalibration( lua_State* L ) {
  131. int32_t a = luaL_checkinteger( L, 1 );
  132. int32_t b = luaL_checkinteger( L, 2 );
  133. int32_t c = luaL_checkinteger( L, 3 );
  134. int32_t d = luaL_checkinteger( L, 4 );
  135. setCalibration(a,b,c,d);
  136. return 0;
  137. }
  138. // Lua: xpt2046.xpt2046_getRaw()
  139. static int xpt2046_getRaw( lua_State* L ) {
  140. uint16_t x, y;
  141. getRaw(&x, &y);
  142. lua_pushinteger( L, x);
  143. lua_pushinteger( L, y);
  144. return 2;
  145. }
  146. // Lua: xpt2046.xpt2046_getPosition()
  147. static int xpt2046_getPosition( lua_State* L ) {
  148. uint16_t x, y;
  149. getPosition(&x, &y);
  150. lua_pushinteger( L, x);
  151. lua_pushinteger( L, y);
  152. return 2;
  153. }
  154. // Lua: xpt2046.xpt2046_getPositionAvg()
  155. static int xpt2046_getPositionAvg( lua_State* L ) {
  156. // Run three times
  157. uint16_t x1, y1, x2, y2, x3, y3;
  158. getPosition(&x1, &y1);
  159. getPosition(&x2, &y2);
  160. getPosition(&x3, &y3);
  161. // Average the two best results
  162. int16_t x = besttwoavg(x1,x2,x3);
  163. int16_t y = besttwoavg(y1,y2,y3);
  164. lua_pushinteger( L, x);
  165. lua_pushinteger( L, y);
  166. return 2;
  167. }
  168. // Module function map
  169. static const LUA_REG_TYPE xpt2046_map[] = {
  170. { LSTRKEY( "isTouched"), LFUNCVAL(xpt2046_isTouched) },
  171. { LSTRKEY( "getRaw" ), LFUNCVAL(xpt2046_getRaw) },
  172. { LSTRKEY( "getPosition"), LFUNCVAL(xpt2046_getPosition)},
  173. { LSTRKEY( "getPositionAvg"), LFUNCVAL(xpt2046_getPositionAvg)},
  174. { LSTRKEY( "setCalibration"), LFUNCVAL(xpt2046_setCalibration)},
  175. { LSTRKEY( "init" ), LFUNCVAL(xpt2046_init) },
  176. { LNILKEY, LNILVAL }
  177. };
  178. NODEMCU_MODULE(XPT2046, "xpt2046", xpt2046_map, NULL);