1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/drivers/stmpe811/stmpe811.c
Gunar Schorcht ebe51e7f51 drivers/stmpe811: introduce coordinate conversion
To obtain coordinates from the touch panel that correspond to the display coordinates, it is often necessary to convert the coordinates from the touch display by swapping and mirroring. For the sake of simplicity, possible rotations are additionally defined.
2023-09-02 17:01:46 +02:00

472 lines
13 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, &reg_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);
reg = STMPE811_SPI_CFG_AUTO_INCR;
_write_reg(dev, STMPE811_SPI_CFG, reg);
_read_reg(dev, STMPE811_SPI_CFG, &reg);
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);
reg &= ~STMPE811_SYS_CTRL2_TSC_OFF;
ret += _write_reg(dev, STMPE811_SYS_CTRL2, reg);
ret += _read_reg(dev, STMPE811_TSC_CTRL, &reg);
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;
/* maximum values in device coordinates */
uint16_t tmp_xmax;
uint16_t tmp_ymax;
if (dev->params.xyconv & STMPE811_SWAP_XY) {
tmp_xmax = dev->params.ymax;
tmp_ymax = dev->params.xmax;
}
else {
tmp_xmax = dev->params.xmax;
tmp_ymax = dev->params.ymax;
}
/* clamp y position */
if (tmp_y > tmp_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 > tmp_xmax) {
tmp_x = dev->prev_x;
}
dev->prev_x = tmp_x;
dev->prev_y = tmp_y;
/* conversion to screen coordinates */
if (dev->params.xyconv & STMPE811_SWAP_XY) {
position->x = tmp_y;
position->y = tmp_x;
}
else {
position->x = tmp_x;
position->y = tmp_y;
}
if (dev->params.xyconv & STMPE811_MIRROR_X) {
assert(position->x <= dev->params.xmax);
position->x = dev->params.xmax - position->x;
}
if (dev->params.xyconv & STMPE811_MIRROR_Y) {
assert(position->y <= dev->params.ymax);
position->y = dev->params.ymax - position->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;
}