forked from nodemcu/nodemcu-firmware
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add driver for XPT2046 touch controller (nodemcu#1848)
- Loading branch information
1 parent
466c03d
commit b4319bd
Showing
4 changed files
with
350 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters