Add driver for XPT2046 touch controller (#1848)

This commit is contained in:
FrankX 2017-03-09 07:20:01 +01:00 committed by Marcel Stör
parent 466c03d90f
commit b4319bdb4b
4 changed files with 350 additions and 0 deletions

View File

@ -71,6 +71,7 @@
//#define LUA_USE_MODULES_WPS //#define LUA_USE_MODULES_WPS
//#define LUA_USE_MODULES_WS2801 //#define LUA_USE_MODULES_WS2801
//#define LUA_USE_MODULES_WS2812 //#define LUA_USE_MODULES_WS2812
//#define LUA_USE_MODULES_XPT2046
#endif /* LUA_CROSS_COMPILER */ #endif /* LUA_CROSS_COMPILER */
#endif /* __USER_MODULES_H__ */ #endif /* __USER_MODULES_H__ */

217
app/modules/xpt2046.c Normal file
View File

@ -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
docs/en/modules/xpt2046.md Normal file
View File

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

View File

@ -89,5 +89,6 @@ pages:
- 'wps': 'en/modules/wps.md' - 'wps': 'en/modules/wps.md'
- 'ws2801': 'en/modules/ws2801.md' - 'ws2801': 'en/modules/ws2801.md'
- 'ws2812': 'en/modules/ws2812.md' - 'ws2812': 'en/modules/ws2812.md'
- 'xpt2046': 'en/modules/xpt2046.md'
#- Deutsch: #- Deutsch:
# - Home: 'de/index.md' # - Home: 'de/index.md'