mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2025-01-18 12:52:44 +01:00
8017249bab
19885: drivers/stmpe811: changes for interrupt-driven touch handling and gesture recognition r=aabadie a=gschorcht ### Contribution description This PR contains changes needed for the purely interrupt-driven handling of the touch position as `touch_dev` device, which is a prerequisite for the realization of gesture recognition. (PR #19884). The interrupt-driven approach of `touch_dev` devices (PR #19882) and the gesture recognition (PR #19884) need continuous reporting of the touch position as long as there is at least one touch. Since the driver so far only uses the _Touch Detect_ interrupt, only the position at the beginning of a touch is available. All further positions must be polled. Therefore, the changes in this PR additionally enable the _FIFO Threshold_ interrupt when the `touch_dev` module is enabled. However, since the _Touch Detect_ interrupt does not work reliably when the _FIFO Threshold_ interrupt is enabled and the FIFO Threshold is 1, the FIFO Threshold is set to 2 by default when the `touch_dev` module is enabled. Furthermore, the FIFO queue has to be reset after reading one touch position. Otherwise new touch positions are processed with a delay if the rate of calling the function to read the FIFO is slower than the rate at which the FIFO is filled. The reason for this is that with each call of this function only the oldest touch position is read value by value from the FIFO. Gestures can't be implemented with such a behavior. ### Testing procedure 1. `tests/drivers/stmpe811` should work as before (only a single position is shown): ``` BOARD=stm32f429i-disco make -j8 -C tests/drivers/stmpe811 flash ``` ``` main(): This is RIOT! (Version: 2023.10-devel-120-g848d1- drivers/stmpe811_touch_dev_gestures) STMPE811 test application +------------Initializing------------+ Initialization successful Pressed! X: 135, Y:131 Released! ``` 2. `tests/drivers/stmpe811` should work on top of PR #19882 with continuous outputs of touch positions: ``` BOARD=stm32f429i-disco make -j8 -C tests/drivers/touch_dev flash ``` ``` main(): This is RIOT! (Version: 2023.10-devel-121-g38d3db-drivers/stmpe811_touch_dev_gestures) Event: pressed! X: 131, Y:145 X: 133, Y:141 X: 135, Y:138 X: 138, Y:133 X: 141, Y:128 X: 146, Y:122 X: 151, Y:117 Event: released! ``` 3. `tests/driver/touch_dev_gestures` of PR #19884 should work on top of this PR: ``` BOARD=stm32f429i-disco make -j8 -C tests/drivers/touch_dev_gestures flash ``` ``` main(): This is RIOT! (Version: 2023.10-devel-128-g05f690-drivers/touch_dev_gestures_work) Swipe right Swipe left Swipe down Swipe up Single Tap X: 246, Y:148 Single Tap X: 256, Y:139 Double Tap X: 247, Y:136 Pressed X: 246, Y:131 Moving X: 248, Y:132 Moving X: 250, Y:133 Moving X: 258, Y:135 Moving X: 270, Y:136 Moving X: 285, Y:132 Moving X: 300, Y:126 Moving X: 309, Y:122 Moving X: 310, Y:119 Released X: 310, Y:119 ``` ### Issues/PRs references Co-authored-by: Gunar Schorcht <gunar@schorcht.net>
443 lines
12 KiB
C
443 lines
12 KiB
C
/*
|
|
* Copyright (C) 2019 Inria
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU Lesser
|
|
* General Public License v2.1. See the file LICENSE in the top level
|
|
* directory for more details.
|
|
*/
|
|
|
|
/**
|
|
* @ingroup drivers_stmpe811
|
|
* @{
|
|
*
|
|
* @file
|
|
* @brief Device driver implementation for the STMPE811 touchscreen controller.
|
|
*
|
|
* @author Alexandre Abadie <alexandre.abadie@inria.fr>
|
|
*
|
|
* @}
|
|
*/
|
|
|
|
#include <inttypes.h>
|
|
|
|
#include "ztimer.h"
|
|
#if IS_USED(MODULE_STMPE811_SPI)
|
|
#include "periph/spi.h"
|
|
#else
|
|
#include "periph/i2c.h"
|
|
#endif
|
|
#include "periph/gpio.h"
|
|
|
|
#include "stmpe811.h"
|
|
#include "stmpe811_constants.h"
|
|
#include "stmpe811_params.h"
|
|
|
|
#define ENABLE_DEBUG 0
|
|
#include "debug.h"
|
|
|
|
#if IS_USED(MODULE_STMPE811_SPI)
|
|
#define BUS (dev->params.spi)
|
|
#define CS (dev->params.cs)
|
|
#define CLK (dev->params.clk)
|
|
#define MODE (dev->params.mode)
|
|
#define WRITE_MASK (0x7F)
|
|
#else
|
|
#define BUS (dev->params.i2c)
|
|
#define ADDR (dev->params.addr)
|
|
#endif
|
|
|
|
#ifndef STMPE811_FIFO_THRESHOLD_ENABLED
|
|
#define STMPE811_FIFO_THRESHOLD_ENABLED (0)
|
|
#endif
|
|
|
|
/* The driver only works reliably with FIFO threshold interrupts if the FIFO
|
|
* threshold is at least 2. The reason is that all interrupts are cleared when
|
|
* the status is checked in `stmpe811_read_touch_state` but the FIFO Threshold
|
|
* interrupt is asserted again immediately since the FIFO is not read so that
|
|
* a new interrupt is pending. On the other hand, the Touch Detected interrupt
|
|
* does not work reliably for the release event if only the Touch Detected
|
|
* interrupt in `stmpe811_read_touch_state` is cleared. The workaround is
|
|
* to set the FIFO threshold to at least 2 to introduce a small delay between
|
|
* the Touch Detect interrupt on a touch and the first FIFO threshold
|
|
* interrupt. */
|
|
#ifndef STMPE811_FIFO_THRESHOLD
|
|
#define STMPE811_FIFO_THRESHOLD (STMPE811_FIFO_THRESHOLD_ENABLED ? 2 : 1)
|
|
#endif
|
|
|
|
#if IS_USED(MODULE_STMPE811_SPI) /* using SPI mode */
|
|
static inline void _acquire(const stmpe811_t *dev)
|
|
{
|
|
spi_acquire(BUS, CS, MODE, CLK);
|
|
}
|
|
|
|
static inline void _release(const stmpe811_t *dev)
|
|
{
|
|
spi_release(BUS);
|
|
}
|
|
|
|
static int _read_reg(const stmpe811_t *dev, uint8_t reg, uint8_t *data)
|
|
{
|
|
*data = spi_transfer_reg(BUS, CS, reg | 0x80, 0x00);
|
|
return 0;
|
|
}
|
|
|
|
static int _write_reg(const stmpe811_t *dev, uint8_t reg, uint8_t data)
|
|
{
|
|
(void)spi_transfer_reg(BUS, CS, (reg & WRITE_MASK), data);
|
|
return 0;
|
|
}
|
|
|
|
static int _read_burst(const stmpe811_t *dev, uint8_t reg, void *buf, size_t len)
|
|
{
|
|
uint8_t reg_read = reg | 0x80;
|
|
|
|
/* since SPI is in auto-increment mode subsequent reads will ignore the
|
|
content of the reg_read buffer, and itself can't overflow since it matches
|
|
the amount of data per register */
|
|
spi_transfer_regs(BUS, CS, reg_read, ®_read, buf, len);
|
|
return 0;
|
|
}
|
|
|
|
#else /* using I2C mode */
|
|
|
|
static inline void _acquire(const stmpe811_t *dev)
|
|
{
|
|
i2c_acquire(BUS);
|
|
}
|
|
|
|
static inline void _release(const stmpe811_t *dev)
|
|
{
|
|
i2c_release(BUS);
|
|
}
|
|
|
|
static int _read_reg(const stmpe811_t *dev, uint8_t reg, uint8_t *data)
|
|
{
|
|
if (i2c_read_reg(BUS, ADDR, reg, data, 0) != 0) {
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int _write_reg(const stmpe811_t *dev, uint8_t reg, uint8_t data)
|
|
{
|
|
if (i2c_write_reg(BUS, ADDR, reg, data, 0) != 0) {
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int _read_burst(const stmpe811_t *dev, uint8_t reg, void *buf, size_t len)
|
|
{
|
|
if (i2c_read_regs(BUS, ADDR, reg, buf, len, 0) != 0) {
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif /* bus mode selection */
|
|
|
|
static int _soft_reset(const stmpe811_t *dev)
|
|
{
|
|
if (_write_reg(dev, STMPE811_SYS_CTRL1, STMPE811_SYS_CTRL1_SOFT_RESET ) < 0) {
|
|
DEBUG("[stmpe811] soft reset: cannot write soft reset bit to SYS_CTRL1 register\n");
|
|
return -EPROTO;
|
|
}
|
|
ztimer_sleep(ZTIMER_MSEC, 10);
|
|
|
|
if (_write_reg(dev, STMPE811_SYS_CTRL1, 0) < 0) {
|
|
DEBUG("[stmpe811] soft reset: cannot clear SYS_CTRL1 register\n");
|
|
return -EPROTO;
|
|
}
|
|
ztimer_sleep(ZTIMER_MSEC, 2);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void _reset_fifo(const stmpe811_t *dev)
|
|
{
|
|
_write_reg(dev, STMPE811_FIFO_CTRL_STA, STMPE811_FIFO_CTRL_STA_RESET);
|
|
_write_reg(dev, STMPE811_FIFO_CTRL_STA, 0);
|
|
}
|
|
|
|
static void _clear_interrupt_status(const stmpe811_t *dev)
|
|
{
|
|
_write_reg(dev, STMPE811_INT_STA, 0xff);
|
|
}
|
|
|
|
#if IS_USED(MODULE_STMPE811_SPI)
|
|
static int _stmpe811_check_mode(stmpe811_t *dev)
|
|
{
|
|
/* can iterate directly through the enum since they might not be
|
|
monotonically incrementing */
|
|
uint8_t modes[] = { SPI_MODE_0, SPI_MODE_1, SPI_MODE_2, SPI_MODE_3};
|
|
uint8_t reg;
|
|
|
|
for (uint8_t i = 0; i < sizeof(modes); i++) {
|
|
DEBUG("[stmpe811] init: set spi mode to 0x%02x ... ", modes[i]);
|
|
dev->params.mode = modes[i];
|
|
/* acquire */
|
|
_acquire(dev);
|
|
/* configure auto increment SPI */
|
|
_read_reg(dev, STMPE811_SPI_CFG, ®);
|
|
reg = STMPE811_SPI_CFG_AUTO_INCR;
|
|
_write_reg(dev, STMPE811_SPI_CFG, reg);
|
|
_read_reg(dev, STMPE811_SPI_CFG, ®);
|
|
if (reg & STMPE811_SPI_CFG_AUTO_INCR) {
|
|
DEBUG("success\n");
|
|
_release(dev);
|
|
return 0;
|
|
}
|
|
DEBUG("failed\n");
|
|
_release(dev);
|
|
}
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
int stmpe811_init(stmpe811_t *dev, const stmpe811_params_t *params, stmpe811_event_cb_t cb,
|
|
void *arg)
|
|
{
|
|
dev->params = *params;
|
|
dev->prev_x = 0;
|
|
dev->prev_y = 0;
|
|
|
|
int ret = 0;
|
|
uint8_t reg;
|
|
|
|
#if IS_USED(MODULE_STMPE811_SPI)
|
|
/* configure the chip-select pin */
|
|
if (spi_init_cs(BUS, CS) != SPI_OK) {
|
|
DEBUG("[stmpe811] error: unable to configure chip the select pin\n");
|
|
return -EIO;
|
|
}
|
|
|
|
/* check mode configuration */
|
|
if (_stmpe811_check_mode(dev) != 0) {
|
|
DEBUG("[stmpe811] error: couldn't setup SPI\n");
|
|
return -EIO;
|
|
}
|
|
#endif
|
|
|
|
/* acquire bus */
|
|
_acquire(dev);
|
|
|
|
uint16_t device_id;
|
|
if (_read_burst(dev, STMPE811_CHIP_ID, &device_id, 2) < 0) {
|
|
DEBUG("[stmpe811] init: cannot read CHIP_ID register\n");
|
|
_release(dev);
|
|
return -EPROTO;
|
|
}
|
|
|
|
device_id = (device_id << 8) | (device_id >> 8);
|
|
if (device_id != STMPE811_CHIP_ID_VALUE) {
|
|
DEBUG("[stmpe811] init: invalid device id (actual: 0x%04X, expected: 0x%04X)\n",
|
|
device_id, STMPE811_CHIP_ID_VALUE);
|
|
_release(dev);
|
|
return -ENODEV;
|
|
}
|
|
|
|
DEBUG("[stmpe811] init: valid device\n");
|
|
|
|
ret = _soft_reset(dev);
|
|
if (ret != 0) {
|
|
DEBUG("[stmpe811] init: reset failed\n");
|
|
_release(dev);
|
|
return -EIO;
|
|
}
|
|
|
|
DEBUG("[stmpe811] init: soft reset done\n");
|
|
|
|
/* Initialization sequence */
|
|
|
|
/* disable temperature sensor and GPIO */
|
|
ret =
|
|
_write_reg(dev, STMPE811_SYS_CTRL2,
|
|
(STMPE811_SYS_CTRL2_TS_OFF | STMPE811_SYS_CTRL2_GPIO_OFF));
|
|
|
|
/* set to 80 cycles and adc resolution to 12 bit*/
|
|
reg =
|
|
((uint8_t)(STMPE811_ADC_CTRL1_SAMPLE_TIME_80 << STMPE811_ADC_CTRL1_SAMPLE_TIME_POS) |
|
|
STMPE811_ADC_CTRL1_MOD_12B);
|
|
|
|
ret += _write_reg(dev, STMPE811_ADC_CTRL1, reg);
|
|
|
|
/* set adc clock speed to 3.25 MHz */
|
|
ret += _write_reg(dev, STMPE811_ADC_CTRL2, STMPE811_ADC_CTRL2_FREQ_3_25MHZ);
|
|
|
|
/* set GPIO AF to function as ts/adc */
|
|
ret += _write_reg(dev, STMPE811_GPIO_ALT_FUNCTION, 0x00);
|
|
|
|
/* set touchscreen configuration */
|
|
reg = ((uint8_t)(STMPE811_TSC_CFG_AVE_CTRL_4 << STMPE811_TSC_CFG_AVE_CTRL_POS) |
|
|
(uint8_t)(STMPE811_TSC_CFG_TOUCH_DET_DELAY_500US <<
|
|
STMPE811_TSC_CFG_TOUCH_DET_DELAY_POS) |
|
|
(STMPE811_TSC_CFG_SETTLING_500US));
|
|
ret += _write_reg(dev, STMPE811_TSC_CFG, reg);
|
|
|
|
/* set fifo threshold */
|
|
ret += _write_reg(dev, STMPE811_FIFO_TH, STMPE811_FIFO_THRESHOLD);
|
|
|
|
/* reset fifo */
|
|
_reset_fifo(dev);
|
|
|
|
/* set fractional part to 7, whole part to 1 */
|
|
ret += _write_reg(dev, STMPE811_TSC_FRACTION_Z, STMPE811_TSC_FRACTION_Z_7_1);
|
|
|
|
/* set current limit value to 50 mA */
|
|
ret += _write_reg(dev, STMPE811_TSC_I_DRIVE, STMPE811_TSC_I_DRIVE_50MA);
|
|
|
|
/* enable touchscreen clock */
|
|
ret += _read_reg(dev, STMPE811_SYS_CTRL2, ®);
|
|
reg &= ~STMPE811_SYS_CTRL2_TSC_OFF;
|
|
ret += _write_reg(dev, STMPE811_SYS_CTRL2, reg);
|
|
|
|
ret += _read_reg(dev, STMPE811_TSC_CTRL, ®);
|
|
reg |= STMPE811_TSC_CTRL_EN;
|
|
ret += _write_reg(dev, STMPE811_TSC_CTRL, reg);
|
|
|
|
/* clear interrupt status */
|
|
_clear_interrupt_status(dev);
|
|
|
|
if (gpio_is_valid(dev->params.int_pin)) {
|
|
DEBUG("[stmpe811] init: configuring touchscreen interrupt\n");
|
|
if (cb) {
|
|
gpio_init_int(dev->params.int_pin, GPIO_IN, GPIO_FALLING, cb, arg);
|
|
}
|
|
|
|
/* Enable touchscreen interrupt */
|
|
ret += _write_reg(dev, STMPE811_INT_EN,
|
|
STMPE811_INT_EN_TOUCH_DET |
|
|
(STMPE811_FIFO_THRESHOLD_ENABLED ? STMPE811_INT_EN_FIFO_TH : 0));
|
|
|
|
/* Enable global interrupt */
|
|
ret += _write_reg(dev, STMPE811_INT_CTRL,
|
|
STMPE811_INT_CTRL_GLOBAL_INT | STMPE811_INT_CTRL_INT_TYPE);
|
|
}
|
|
|
|
if (ret < 0) {
|
|
_release(dev);
|
|
DEBUG("[stmpe811] init: initialization sequence failed\n");
|
|
return -EPROTO;
|
|
}
|
|
|
|
/* Release I2C device */
|
|
_release(dev);
|
|
|
|
DEBUG("[stmpe811] initialization successful\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
int stmpe811_read_touch_position(stmpe811_t *dev, stmpe811_touch_position_t *position)
|
|
{
|
|
uint16_t tmp_x, tmp_y;
|
|
|
|
/* Acquire device bus */
|
|
_acquire(dev);
|
|
|
|
/* Ensure there's a least one position measured in the FIFO */
|
|
uint8_t fifo_size = 0;
|
|
|
|
do {
|
|
_read_reg(dev, STMPE811_FIFO_SIZE, &fifo_size);
|
|
} while (!fifo_size);
|
|
|
|
uint8_t xyz[4];
|
|
uint32_t xyz_ul;
|
|
|
|
#if IS_USED(MODULE_STMPE811_SPI)
|
|
for (uint8_t i = 0; i < sizeof(xyz); i++) {
|
|
if (_read_reg(dev, STMPE811_TSC_DATA_NON_INC, &xyz[i]) < 0) {
|
|
DEBUG("[stmpe811] position: cannot read position\n");
|
|
_release(dev);
|
|
return -EPROTO;
|
|
}
|
|
}
|
|
#else
|
|
if (_read_burst(dev, STMPE811_TSC_DATA_NON_INC, xyz, sizeof(xyz)) < 0) {
|
|
DEBUG("[stmpe811] position: cannot read position\n");
|
|
_release(dev);
|
|
return -EPROTO;
|
|
}
|
|
#endif
|
|
|
|
/* Reset the FIFO, otherwise new touch data will be processed with a delay
|
|
* if the rate of calling this function to read the FIFO is slower than
|
|
* the rate at which the FIFO is filled. The reason for this is that with
|
|
* each call of this function only the oldest touch data is read
|
|
* value by value from the FIFO. Gestures, for example, can't be
|
|
* implemented with such a behavior. */
|
|
_reset_fifo(dev);
|
|
|
|
/* Release device bus */
|
|
_release(dev);
|
|
|
|
xyz_ul = ((uint32_t)xyz[0] << 24) | ((uint32_t)xyz[1] << 16) | \
|
|
((uint32_t)xyz[2] << 8) | (xyz[3] << 0);
|
|
|
|
tmp_x = (xyz_ul >> 20) & 0xfff;
|
|
tmp_y = (xyz_ul >> 8) & 0xfff;
|
|
|
|
/* Y value first correction */
|
|
tmp_y -= 360;
|
|
|
|
/* Y value second correction */
|
|
tmp_y /= 11;
|
|
|
|
/* clamp y position */
|
|
if (tmp_y > dev->params.ymax) {
|
|
tmp_y = dev->prev_y;
|
|
}
|
|
|
|
/* X value first correction */
|
|
if (tmp_x <= 3000) {
|
|
tmp_x = 3870 - tmp_x;
|
|
}
|
|
else {
|
|
tmp_x = 3800 - tmp_x;
|
|
}
|
|
|
|
/* X value second correction */
|
|
tmp_x /= 15;
|
|
|
|
/* clamp x position */
|
|
if (tmp_x > dev->params.xmax) {
|
|
tmp_x = dev->prev_x;
|
|
}
|
|
|
|
dev->prev_x = tmp_x;
|
|
dev->prev_y = tmp_y;
|
|
position->x = tmp_x;
|
|
position->y = tmp_y;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int stmpe811_read_touch_state(const stmpe811_t *dev, stmpe811_touch_state_t *state)
|
|
{
|
|
uint8_t val;
|
|
|
|
/* Acquire device bus */
|
|
_acquire(dev);
|
|
|
|
if (_read_reg(dev, STMPE811_TSC_CTRL, &val) < 0) {
|
|
DEBUG("[stmpe811] position: cannot read touch state\n");
|
|
_release(dev);
|
|
return -EPROTO;
|
|
}
|
|
|
|
_clear_interrupt_status(dev);
|
|
|
|
if ((val & STMPE811_TSC_CTRL_STA)) {
|
|
*state = STMPE811_TOUCH_STATE_PRESSED;
|
|
}
|
|
else {
|
|
_reset_fifo(dev);
|
|
*state = STMPE811_TOUCH_STATE_RELEASED;
|
|
}
|
|
|
|
/* Release I2C device */
|
|
_release(dev);
|
|
|
|
return 0;
|
|
}
|