Skip to content

lupyuen/cst816s-nuttx

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Touch Panel Calibration for Pine64 PineDio Stack BL604 RISC-V Board

Hynitron CST816S Touch Controller Driver for Apache NuttX RTOS

(Used by PineDio Stack BL604)

Read the article...

Watch the demo...

Install Driver

If you're using NuttX on PineDio Stack, there's no need to install the driver...

Otherwise to add this repo to your NuttX project...

pushd nuttx/nuttx/drivers/input
git submodule add https://github.com/lupyuen/cst816s-nuttx cst816s
ln -s cst816s/cst816s.c .
popd

pushd nuttx/nuttx/include/nuttx/input
ln -s ../../../drivers/input/cst816s/cst816s.h .
popd

Next update the Makefile and Kconfig...

Then update the NuttX Build Config...

## TODO: Change this to the path of our "incubator-nuttx" folder
cd nuttx/nuttx

## Preserve the Build Config
cp .config ../config

## Erase the Build Config and Kconfig files
make distclean

## For BL602: Configure the build for BL602
./tools/configure.sh bl602evb:nsh

## For PineDio Stack BL604: Configure the build for BL604
./tools/configure.sh bl602evb:pinedio

## For ESP32: Configure the build for ESP32.
## TODO: Change "esp32-devkitc" to our ESP32 board.
./tools/configure.sh esp32-devkitc:nsh

## Restore the Build Config
cp ../config .config

## Edit the Build Config
make menuconfig 

In menuconfig, enable the Hynitron CST816S Driver under "Device Drivers → Input Device Support".

Enable I2C Warnings because of the I2C Workaround for CST816S...

  • Click "Build Setup" → "Debug Options"

  • Check the boxes for the following...

    • Enable Warnings Output
    • I2C Warnings Output
  • (Optional) To enable logging for the CST816S Driver, check the boxes for...

    • Enable Error Output
    • Enable Informational Debug Output
    • Enable Debug Assertions
    • Input Device Error Output
    • Input Device Warnings Output
    • Input Device Informational Output
  • Note that "Enable Informational Debug Output" must be unchecked for the LoRaWAN Test App lorawan_test to work (because the LoRaWAN Timers are time-sensitive)

Edit the function bl602_i2c_transfer and apply this workaround patch...

Edit the function bl602_bringup or esp32_bringup in this file...

## For BL602:
nuttx/boards/risc-v/bl602/bl602evb/src/bl602_bringup.c

## For ESP32: Change "esp32-devkitc" to our ESP32 board 
nuttx/boards/xtensa/esp32/esp32-devkitc/src/esp32_bringup.c

And call cst816s_register to load our driver:

https://github.com/lupyuen/incubator-nuttx/blob/touch/boards/risc-v/bl602/bl602evb/src/bl602_bringup.c#L826-L843

#ifdef CONFIG_INPUT_CST816S
/* I2C Address of CST816S Touch Controller */

#define CST816S_DEVICE_ADDRESS 0x15
#include <nuttx/input/cst816s.h>
#endif /* CONFIG_INPUT_CST816S */
...
#ifdef CONFIG_INPUT_CST816S
int bl602_bringup(void)
{
  ...
  /* Init I2C bus for CST816S */

  struct i2c_master_s *cst816s_i2c_bus = bl602_i2cbus_initialize(0);
  if (!cst816s_i2c_bus)
    {
      _err("ERROR: Failed to get I2C%d interface\n", 0);
    }

  /* Register the CST816S driver */

  ret = cst816s_register("/dev/input0", cst816s_i2c_bus, CST816S_DEVICE_ADDRESS);
  if (ret < 0)
    {
      _err("ERROR: Failed to register CST816S\n");
    }
#endif /* CONFIG_INPUT_CST816S */

Here's how we created the CST816S Driver for NuttX on PineDio Stack BL604...

Cypress MBR3108

NuttX Driver for Cypress MBR3108 Touch Controller looks structurally similar to PineDio Stack's CST816S ... So we copy-n-paste into our CST816S Driver

I2C Scan

PineDio Stack's Touch Panel is a peculiar I2C Device ... It won't respond to I2C Scan unless we tap the screen and wake it up!

GPIO Interrupt

PineDio Stack's Touch Panel triggers a GPIO Interrupt when we tap the screen ... Here's how we handle the GPIO Interrupt

int cst816s_register(FAR const char *devpath,
                     FAR struct i2c_master_s *i2c_dev,
                     uint8_t i2c_devaddr)
{
  ...
  /* Prepare interrupt line and handler. */

  ret = bl602_irq_attach(BOARD_TOUCH_INT, cst816s_isr_handler, priv);
  if (ret < 0)
    {
      kmm_free(priv);
      ierr("Attach interrupt failed\n");
      return ret;
    }

  ret = bl602_irq_enable(false);
  if (ret < 0)
    {
      kmm_free(priv);
      ierr("Disable interrupt failed\n");
      return ret;
    }

(Source)

bl602_irq_attach is defined below...

//  Attach Interrupt Handler to GPIO Interrupt for Touch Controller
//  Based on https://github.com/lupyuen/incubator-nuttx/blob/touch/boards/risc-v/bl602/bl602evb/src/bl602_gpio.c#L477-L505
static int bl602_irq_attach(gpio_pinset_t pinset, FAR isr_handler *callback, FAR void *arg)
{
  int ret = 0;
  uint8_t gpio_pin = (pinset & GPIO_PIN_MASK) >> GPIO_PIN_SHIFT;
  FAR struct bl602_gpint_dev_s *dev = NULL;  //  TODO

  DEBUGASSERT(callback != NULL);

  /* Configure the pin that will be used as interrupt input */

  #warning Check GLB_GPIO_INT_TRIG_NEG_PULSE  ////  TODO
  bl602_expander_set_intmod(gpio_pin, 1, GLB_GPIO_INT_TRIG_NEG_PULSE);
  ret = bl602_configgpio(pinset);
  if (ret < 0)
    {
      gpioerr("Failed to configure GPIO pin %d\n", gpio_pin);
      return ret;
    }

  /* Make sure the interrupt is disabled */

  bl602_expander_pinset = pinset;
  bl602_expander_callback = callback;
  bl602_expander_arg = arg;
  bl602_expander_intmask(gpio_pin, 1);

  irq_attach(BL602_IRQ_GPIO_INT0, bl602_expander_interrupt, dev);
  bl602_expander_intmask(gpio_pin, 0);

  gpioinfo("Attach %p\n", callback);

  return 0;
}

(Source)

Note that we're calling bl602_expander to handle interrupts. There doesn't seem to be a way to do this with the current BL602 GPIO Driver (bl602evb/bl602_gpio.c).

We are building bl602_expander here...

To test interrupts we uncomment #define TEST_CST816S_INTERRUPT...

int cst816s_register(FAR const char *devpath,
                     FAR struct i2c_master_s *i2c_dev,
                     uint8_t i2c_devaddr)
{
...
//  Uncomment this to test interrupts (tap the screen)
#define TEST_CST816S_INTERRUPT
#ifdef TEST_CST816S_INTERRUPT
#warning Testing CST816S interrupt
  bl602_irq_enable(true);
#endif /* TEST_CST816S_INTERRUPT */

(Source)

There's bug with BL602 GPIO Interrupts that we have fixed for our driver...

apache/nuttx#5810 (comment)

Test GPIO Interrupt

Tapping the screen on PineDio Stack ... Correctly triggers a GPIO Interrupt 🎉

gpio_pin_register: Registering /dev/gpio0
gpio_pin_register: Registering /dev/gpio1
gpint_enable: Disable the interrupt
gpio_pin_register: Registering /dev/gpio2
bl602_gpio_set_intmod: ****gpio_pin=115, int_ctlmod=1, int_trgmod=0
spi_test_driver_register: devpath=/dev/spitest0, spidev=0
cst816s_register:
bl602_expander_set_intmod: ****gpio_pin=9, int_ctlmod=1, int_trgmod=0
bl602_irq_attach: Attach 0x2305e9de
bl602_irq_enable: Disable interrupt
cst816s_register: Driver registered
bl602_irq_enable: Enable interrupt

NuttShell (NSH) NuttX-10.2.0-RC0
nsh> bl602_expander_interrupt: Interrupt! callback=0x2305e9de, arg=0x42020a60
bl602_expander_interrupt: Call callback=0x2305e9de, arg=0x42020a60
cst816s_poll_notify:
bl602_expander_interrupt: Interrupt! callback=0x2305e9de, arg=0x42020a60
bl602_expander_interrupt: Call callback=0x2305e9de, arg=0x42020a60
cst816s_poll_notify:
bl602_expander_interrupt: Interrupt! callback=0x2305e9de, arg=0x42020a60
bl602_expander_interrupt: Call callback=0x2305e9de, arg=0x42020a60
cst816s_poll_notify:
bl602_expander_interrupt: Interrupt! callback=0x2305e9de, arg=0x42020a60
bl602_expander_interrupt: Call callback=0x2305e9de, arg=0x42020a60
cst816s_poll_notify:

LVGL Test App lvgltest fails to open /dev/input0, but that's OK because we haven't implemented the I2C part.

nsh> ls /dev
/dev:
 console
 gpio0
 gpio1
 gpio2
 i2c0
 input0
 lcd0
 null
 spi0
 spitest0
 timer0
 urandom
 zero

nsh> lvgltest
tp_init: Opening /dev/input0
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
cst816s_probe_device: family_id: 0x34, device_id: 0x00aa, device_rev: 35
cst816s_probe_device: Probe failed, dev-id mismatch!
cst816s_probe_device:   Expected: family_id: 0x9a, device_id: 0x0a03, device_rev: 1
tp_init: open /dev/input0 failed: 6
Terminating!
bl602_expander_interrupt: Interrupt! callback=0x2305e9e8, arg=0
bl602_expander_interrupt: Call callback=0x2305e9e8, arg=0
bl602_expander_interrupt: Interrupt! callback=0x2305e9e8, arg=0
bl602_expander_interrupt: Call callback=0x2305e9e8, arg=0
bl602_expander_interrupt: Interrupt! callback=0x2305e9e8, arg=0
bl602_expander_interrupt: Call callback=0x2305e9e8, arg=0

Touch Data

Apache NuttX RTOS has a standard data format for Touch Panels ... Let's implement this for PineDio Stack

/* This structure contains information about a single touch point.
 * Positional units are device specific.
 */

struct touch_point_s
{
  uint8_t  id;        /* Unique identifies contact; Same in all reports for the contact */
  uint8_t  flags;     /* See TOUCH_* definitions above */
  int16_t  x;         /* X coordinate of the touch point (uncalibrated) */
  int16_t  y;         /* Y coordinate of the touch point (uncalibrated) */
  int16_t  h;         /* Height of touch point (uncalibrated) */
  int16_t  w;         /* Width of touch point (uncalibrated) */
  uint16_t gesture;   /* Gesture of touchscreen contact */
  uint16_t pressure;  /* Touch pressure */
  uint64_t timestamp; /* Touch event time stamp, in microseconds */
};

/* The typical touchscreen driver is a read-only, input character device
 * driver.the driver write() method is not supported and any attempt to
 * open the driver in any mode other than read-only will fail.
 *
 * Data read from the touchscreen device consists only of touch events and
 * touch sample data.  This is reflected by struct touch_sample_s.  This
 * structure is returned by either the driver read method.
 *
 * On some devices, multiple touchpoints may be supported. So this top level
 * data structure is a struct touch_sample_s that "contains" a set of touch
 * points.  Each touch point is managed individually using an ID that
 * identifies a touch from first contact until the end of the contact.
 */

struct touch_sample_s
{
  int npoints;                   /* The number of touch points in point[] */
  struct touch_point_s point[1]; /* Actual dimension is npoints */
};

(Source)

Read Touch Data

Here's how we read the Touched Coordinates in our driver...

static int cst816s_get_touch_data(FAR struct cst816s_dev_s *dev, FAR void *buf)
{
  iinfo("\n"); ////
  struct touch_sample_s data;
  uint8_t readbuf[7];
  int ret;

  /* Read the raw touch data. */

  ret = cst816s_i2c_read(dev, CST816S_REG_TOUCHDATA, readbuf, sizeof(readbuf));
  if (ret < 0)
    {
      iinfo("Read touch data failed\n");
      return ret;
    }

  /* Interpret the raw touch data. */

  uint8_t id = readbuf[5] >> 4;
  uint8_t touchpoints = readbuf[2] & 0x0f;
  uint8_t xhigh = readbuf[3] & 0x0f;
  uint8_t xlow  = readbuf[4];
  uint8_t yhigh = readbuf[5] & 0x0f;
  uint8_t ylow  = readbuf[6];
  uint8_t event = readbuf[3] >> 6;  /* 0 = Touch Down, 1 = Touch Up, 2 = Contact */
  uint16_t x  = (xhigh  << 8) | xlow;
  uint16_t y  = (yhigh  << 8) | ylow;

  /* If touch coordinates are invalid, return the last valid coordinates. */

  bool valid = true;
  if (x >= 240 || y >= 240)
    {
      iwarn("Invalid touch data: id=%d, touch=%d, x=%d, y=%d\n", id, touchpoints, x, y);
      if (last_event == 0xff)  /* Quit if we have no last valid coordinates. */
        {
          ierr("Can't return touch data: id=%d, touch=%d, x=%d, y=%d\n", id, touchpoints, x, y);
          return -EINVAL;
        }
      valid = false;
      id = last_id;
      x  = last_x;
      y  = last_y;
    }

  /* Remember the last valid touch data. */

  last_event = event;
  last_id    = id;
  last_x     = x;
  last_y     = y;

  /* Set the touch data fields. */

  memset(&data, 0, sizeof(data));
  data.npoints     = 1;
  data.point[0].id = id;
  data.point[0].x  = x;
  data.point[0].y  = y;

  /* Set the touch flags. */

  if (event == 0)  /* Touch Down */
    {
      iinfo("DOWN: id=%d, touch=%d, x=%d, y=%d\n", id, touchpoints, x, y);
      if (valid)  /* Touch coordinates were valid. */
        {
          data.point[0].flags  = TOUCH_DOWN | TOUCH_ID_VALID | TOUCH_POS_VALID;
        }
      else  /* Touch coordinates were invalid. */
        {
          data.point[0].flags  = TOUCH_DOWN | TOUCH_ID_VALID;
        }
    }
  else if (event == 1)  /* Touch Up */
    {
      iinfo("UP: id=%d, touch=%d, x=%d, y=%d\n", id, touchpoints, x, y);
      if (valid)  /* Touch coordinates were valid. */
        {
          data.point[0].flags  = TOUCH_UP | TOUCH_ID_VALID | TOUCH_POS_VALID;
        }
      else  /* Touch coordinates were invalid. */
        {
          data.point[0].flags  = TOUCH_UP | TOUCH_ID_VALID;
        }
    }
  else  /* Reject Contact */
    {
      iinfo("CONTACT: id=%d, touch=%d, x=%d, y=%d\n", id, touchpoints, x, y);
      return -EINVAL;
    }

  /* Return the touch data. */

  memcpy(buf, &data, sizeof(data));

  iinfo("  id:      %d\n",   data.point[0].id);
  iinfo("  flags:   %02x\n", data.point[0].flags);
  iinfo("  x:       %d\n",   data.point[0].x);
  iinfo("  y:       %d\n",   data.point[0].y);

  return sizeof(data);
}

(Source)

Note that our NuttX Driver for PineDio Stack's Touch Panel returns 4 possible states: Touch Down vs Touch Up, Valid vs Invalid.

We got this code thanks to JF's CST816S driver for the Self-Test Firmware...

And from our previous work on PineTime, which also uses CST816S...

Test Touch Data

NuttX Driver for PineDio Stack Touch Panel responds correctly to touch! 🎉

PineDio Stack Touch Screen feels laggy on Apache #NuttX RTOS right now ... 2 things we can fix: 1️⃣ Increase SPI Frequency 2️⃣ Switch to SPI DMA eventually

(UPDATE: We have bumped up the SPI Frequency to max 40 MHz, still feels laggy)

Here's the detailed log...

gpio_pin_register: Registering /dev/gpio0
gpio_pin_register: Registering /dev/gpio1
gpint_enable: Disable the interrupt
gpio_pin_register: Registering /dev/gpio2
bl602_gpio_set_intmod: ****gpio_pin=115, int_ctlmod=1, int_trgmod=0
spi_test_driver_register: devpath=/dev/spitest0, spidev=0
cst816s_register: path=/dev/input0, addr=21
bl602_expander_set_intmod: gpio_pin=9, int_ctlmod=1, int_trgmod=0
bl602_irq_attach: Attach 0x2305e596
bl602_irq_enable: Disable interrupt
cst816s_register: Driver registered
bl602_irq_enable: Enable interrupt

NuttShell (NSH) NuttX-10.2.0-RC0
nsh> lvgltest
tp_init: Opening /dev/input0
cst816s_open:

bl602_expander_interrupt: Interrupt! callback=0x2305e596, arg=0x42020a70
bl602_expander_interrupt: Call callback=0x2305e596, arg=0x42020a70
cst816s_poll_notify:

cst816s_get_touch_data:
cst816s_i2c_read:
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500
bl602_i2c_recvdata: count=3, temp=0x1700de
ransfer success
cst816s_get_touch_data: DOWN: id=0,touch=0, x=222, y=23
cst816s_get_touch_data:   id:      0
cst816s_get_touch_data:   flags:   19
cst816s_get_touch_data:   x:       222
cst816s_get_touch_data:   y:       23

cst816s_get_touch_data:
cst816s_i2c_read:
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500
bl602_i2c_recvdata: count=3, temp=0x1700de
ransfer success
cst816s_get_touch_data: DOWN: id=0, ouch=0, x=222, y=23
cst816s_get_touch_data:   id:      0
cst816s_get_touch_data:   flags:   19
cst816s_get_touch_data:   x:       222
cst816s_get_touch_data:   y:       23

cst816s_get_touch_data:
cst816s_i2c_read:
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500
bl602_i2c_recvdata: count=3, temp=0x1700de
ransfer success
cst816s_get_touch_data: DOWN: id=0, touch=0, x=222, y=23
cst816s_get_touch_data:   id:      0
cst816s_get_touch_data:   flags:   19
cst816s_get_touch_data:   x:       222
cst816s_get_touch_data:   y:       23

cst816s_get_touch_data:
cst816s_i2c_read:
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
cst816s_get_touch_data: Invalid touch data: id=9, touch=2, x=639, y=1688
cst816s_get_touch_data: UP: id=0, touch=2, x=222, y=23
cst816s_get_touch_data:   id:      0
cst816s_get_touch_data:   flags:   0c
cst816s_get_touch_data:   x:       222
cst816s_get_touch_data:   y:       23
bl602_expander_interrupt: Interrupt! callback=0x2305e596, arg=0x42020a70
bl602_expander_interrupt: Call callback=0x2305e596, arg=0x42020a70
cst816s_poll_notify:

cst816s_get_touch_data:
cst816s_i2c_read:
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500
bl602_i2c_recvdata: count=3, temp=0xd900db
ransfer success
cst816s_get_touch_data: DOWN: id=0, touch=0, x=219, y=217
cst816s_get_touch_data:   id:      0
cst816s_get_touch_data:   flags:   19
cst816s_get_touch_data:   x:       219
cst816s_get_touch_data:   y:       217

cst816s_get_touch_data:
cst816s_i2c_read:
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500
bl602_i2c_recvdata: count=3, temp=0xd900db
ransfer success
cst816s_get_touch_data: DOWN: id=0, touch=0, x=219, y=217
cst816s_get_touch_data:   id:      0
cst816s_get_touch_data:   flags:  19
cst816s_get_touch_data:   x:       219
cst816s_get_touch_data:   y:       217

cst816s_get_touch_data:
cst816s_i2c_read:
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
cst816s_get_touch_data: Invalid touch data: id=4, touch=2, x=636, y=3330
cst816s_get_touch_data: UP: id=0, touch=2, x=219, y=217
cst816s_get_touch_data:   id:      0
cst816s_get_touch_data:   flags:   0c
cst816s_get_touch_data:   x:       219
cst816s_get_touch_data:   y:       217
bl602_expander_interrupt: Interrupt! callback=0x2305e596, arg=0x42020a70
bl602_expander_interrupt: Call callback=0x2305e596, arg=0x42020a70
cst816s_poll_notify:

cst816s_get_touch_data:
cst816s_i2c_read:
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500
bl602_i2c_recvdata: count=3, temp=0xdb0022
ransfer success
cst816s_get_touch_data: DOWN: id=0, touch=0, x=34, y=219
cst816s_get_touch_data:   id:      0
cst816s_get_touch_data:   flags:   19
cst816s_get_touch_data:   x:       34
cst816s_get_touch_data:   y:       219

cst816s_get_touch_data:
cst816s_i2c_read:
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500
bl602_i2c_recvdata: count=3, temp=0xdb0022
ransfer success
cst816s_get_touch_data: DOWN: id=0, touch=0, x=34, y=219
cst816s_get_touch_data:   id:      0
cst816s_get_touch_data:   flags:   19
cst816s_get_touch_data:   x:       34
cst816s_get_touch_data:   y:       219

cst816s_get_touch_data:
cst816s_i2c_read:
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
cst816s_get_touch_data: Invalid touch data: id=4, touch=2, x=636, y=3330
cst816s_get_touch_data: UP: id=0, touch=2, x=34, y=219
cst816s_get_touch_data:   id:      0
cst816s_get_touch_data:   flags:   0c
cst816s_get_touch_data:   x:       34
cst816s_get_touch_data:   y:       219
bl602_expander_interrupt: Interrupt! callback=0x2305e596, arg=0x42020a70
bl602_expander_interrupt: Call callback=0x2305e596, arg=0x42020a70
cst816s_poll_notify:

cst816s_get_touch_data:
cst816s_i2c_read:
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500
bl602_i2c_recvdata: count=3, temp=0x180018
ransfer success
cst816s_get_touch_data: DOWN: id=0, touch=0, x=24, y=24
cst816s_get_touch_data:   id:      0
cst816s_get_touch_data:   flags:   19
cst816s_get_touch_data:   x:       24
cst816s_get_touch_data:   y:       24

cst816s_get_touch_data:
cst816s_i2c_read:
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500
bl602_i2c_recvdata: count=3, temp=0x180018
ransfer success
cst816s_get_touch_data: DOWN: id=0, touch=0, x=24, y=24
cst816s_get_touch_data:   id:      0
cst816s_get_touch_data:   flags:   19
cst816s_get_touch_data:   x:       24
cst816s_get_touch_data:   y:       24

cst816s_get_touch_data:
cst816s_i2c_read:
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
cst816s_get_touch_data: Invalid touch data: id=4, touch=2, x=636, y=3330
cst816s_get_touch_data: UP: id=0, touch=2, x=24, y=24
cst816s_get_touch_data:   id:      0
cst816s_get_touch_data:   flags:   0c
cst816s_get_touch_data:   x:       24
cst816s_get_touch_data:   y:       24
bl602_expander_interrupt: Interrupt! callback=0x2305e596, arg=0x42020a70
bl602_expander_interrupt: Call callback=0x2305e596, arg=0x42020a70
cst816s_poll_notify:

cst816s_get_touch_data:
cst816s_i2c_read:
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500
bl602_i2c_recvdata: count=3, temp=0x8d0076
ransfer success
cst816s_get_touch_data: DOWN: id=0, touch=0, x=118, y=141
cst816s_get_touch_data:   id:      0
cst816s_get_touch_data:   flags:   19
cst816s_get_touch_data:   x:       118
cst816s_get_touch_data:   y:       141

cst816s_get_touch_data:
cst816s_i2c_read:
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500
bl602_i2c_recvdata: count=3, temp=0x8d0076
ransfer success
cst816s_get_touch_data: DOWN: id=0, touch=0, x=118, y=141
cst816s_get_touch_data:   id:      0
cst816s_get_touch_data:   flags:   19
cst816s_get_touch_data:   x:       118
cst816s_get_touch_data:   y:       141

cst816s_get_touch_data:
cst816s_i2c_read:
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
cst816s_get_touch_data: Invalid touch data: id=4, touch=2, x=636, y=3330
cst816s_get_touch_data: UP: id=0, touch=2, x=118, y=141
cst816s_get_touch_data:   id:      0
cst816s_get_touch_data:   flags:   0c
cst816s_get_touch_data:   x:       118
cst816s_get_touch_data:   y:       141

tp_cal result
offset x:23, y:24
range x:194, y:198
invert x/y:1, x:0, y:1

Let's break down the log...

Enable GPIO Interrupt

At NuttX Startup, we register the CST816S Driver as /dev/input0 and enable the GPIO interrupt...

gpio_pin_register: Registering /dev/gpio0
gpio_pin_register: Registering /dev/gpio1
gpint_enable: Disable the interrupt
gpio_pin_register: Registering /dev/gpio2
bl602_gpio_set_intmod: ****gpio_pin=115, int_ctlmod=1, int_trgmod=0
spi_test_driver_register: devpath=/dev/spitest0, spidev=0
cst816s_register: path=/dev/input0, addr=21
bl602_expander_set_intmod: gpio_pin=9, int_ctlmod=1, int_trgmod=0
bl602_irq_attach: Attach 0x2305e596
bl602_irq_enable: Disable interrupt
cst816s_register: Driver registered
bl602_irq_enable: Enable interrupt

NuttShell (NSH) NuttX-10.2.0-RC0
nsh>

Start LVGL App

We run the LVGL Test App lvgltest...

nsh> lvgltest
tp_init: Opening /dev/input0
cst816s_open:

Which calls cst816s_open() to open our CST816S Driver.

The app begins the Touchscreen Calibration process.

Read Touch Data

The LVGL Test App calls cst816s_read() repeatedly on the CST816S Driver to get Touch Data...

bool tp_read(struct _lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
{
  ...
  /* Read one sample */

  nbytes = read(fd, &sample, sizeof(struct touch_sample_s));

(Source)

Since the screen hasn't been touched and we have no Touch Data yet, our driver returns an error -EINVAL...

static ssize_t cst816s_read(FAR struct file *filep, FAR char *buffer,
                            size_t buflen)
{
  ...
  int ret = -EINVAL;

  /* Read the touch data, only if screen has been touched or if we're waiting for touch up */
  if ((priv->int_pending || last_event == 0) && buflen >= outlen)
    {
      ret = cst816s_get_touch_data(priv, buffer);
    }

(Source)

int_pending becomes true when a GPIO Interrupt gets triggered later.

last_event becomes 0 when we get a Touch Down event later.

Why do we check int_pending?

To reduce contention on the I2C Bus, we only read the Touch Data over I2C when the screen has been touched. We'll see this in a while.

(But the LVGL Test App really shouldn't call read() repeatedly. It ought to call poll() and block until Touch Data is available)

Why do we we check last_event?

The Touch Controller triggers a GPIO Interrupt only upon Touch Down, not on Touch Up.

So after Touch Down, we allow cst816s_read() to call cst816s_get_touch_data() to fetch the Touch Data repeatedly, until we see the Touch Up Event. We'll see this in a while.

Trigger GPIO Interrupt

We touch the screen and trigger a GPIO Interrupt...

bl602_expander_interrupt: Interrupt! callback=0x2305e596, arg=0x42020a70
bl602_expander_interrupt: Call callback=0x2305e596, arg=0x42020a70
cst816s_poll_notify:

The Interrupt Handler in our driver sets int_pending to true...

static int cst816s_isr_handler(int _irq, FAR void *_context, FAR void *arg)
{
  FAR struct cst816s_dev_s *priv = (FAR struct cst816s_dev_s *)arg;
  irqstate_t flags;

  DEBUGASSERT(priv != NULL);

  flags = enter_critical_section();
  priv->int_pending = true;
  leave_critical_section(flags);

  cst816s_poll_notify(priv);
  return 0;
}

(Source)

And calls cst816s_poll_notify() to unblock all poll() callers and notify them that Touch Data is available.

(But LVGL Test App doesn't poll() our driver, so this doesn't effect anything)

Touch Down Event

Remember that the LVGL Test App keeps calling cst816s_read() repeatedly to get Touch Data.

Now that int_pending is true, our driver proceeds to call cst816s_get_touch_data() and fetch the Touch Data over I2C...

cst816s_get_touch_data:
cst816s_i2c_read:
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500
bl602_i2c_recvdata: count=3, temp=0x1700de
ransfer success
cst816s_get_touch_data: DOWN: id=0,touch=0, x=222, y=23
cst816s_get_touch_data:   id:      0
cst816s_get_touch_data:   flags:   19
cst816s_get_touch_data:   x:       222
cst816s_get_touch_data:   y:       23

The Touch Data that was read from CST816S over I2C...

cst816s_get_touch_data: DOWN: id=0,touch=0, x=222, y=23

Gets returned directly to the LVGL Test App as a Touch Down Event...

cst816s_get_touch_data:   id:      0
cst816s_get_touch_data:   flags:   19
cst816s_get_touch_data:   x:       222
cst816s_get_touch_data:   y:       23

cst816s_get_touch_data() sets last_event to 0 because it's a Touch Down Event.

cst816s_read() sets int_pending to false.

Touch Down Event Again

LVGL Test App is still calling cst816s_read() repeatedly to get Touch Data.

Now that last_event is 0 (Touch Down), our driver proceeds to call cst816s_get_touch_data() and fetch the Touch Data over I2C...

cst816s_get_touch_data:
cst816s_i2c_read:
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500
bl602_i2c_recvdata: count=3, temp=0x1700de
ransfer success
cst816s_get_touch_data: DOWN: id=0, ouch=0, x=222, y=23
cst816s_get_touch_data:   id:      0
cst816s_get_touch_data:   flags:   19
cst816s_get_touch_data:   x:       222
cst816s_get_touch_data:   y:       23

cst816s_get_touch_data:
cst816s_i2c_read:
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500
bl602_i2c_recvdata: count=3, temp=0x1700de
ransfer success
cst816s_get_touch_data: DOWN: id=0, touch=0, x=222, y=23
cst816s_get_touch_data:   id:      0
cst816s_get_touch_data:   flags:   19
cst816s_get_touch_data:   x:       222
cst816s_get_touch_data:   y:       23

This happens twice because we haven't received a Touch Up Event.

Touch Up Event

When our finger is no longer touching the screen, cst816s_get_touch_data() receives a Touch Up Event...

cst816s_get_touch_data:
cst816s_i2c_read:
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
cst816s_get_touch_data: Invalid touch data: id=9, touch=2, x=639, y=1688
cst816s_get_touch_data: UP: id=0, touch=2, x=222, y=23
cst816s_get_touch_data:   id:      0
cst816s_get_touch_data:   flags:   0c
cst816s_get_touch_data:   x:       222
cst816s_get_touch_data:   y:       23

For Touch Up Events the Touch Coordinates are invalid...

cst816s_get_touch_data: Invalid touch data: id=9, touch=2, x=639, y=1688

The driver patches the Touch Coordinates with the data from the last Touch Down Event...

cst816s_get_touch_data: UP: id=0, touch=2, x=222, y=23
cst816s_get_touch_data:   id:      0
cst816s_get_touch_data:   flags:   0c
cst816s_get_touch_data:   x:       222
cst816s_get_touch_data:   y:       23

And returns the valid coordinates to the LVGL Test App. The patching is done here...

static int cst816s_get_touch_data(FAR struct cst816s_dev_s *dev, FAR void *buf) {
...
  /* If touch coordinates are invalid, return the last valid coordinates. */

  bool valid = true;
  if (x >= 240 || y >= 240)
    {
      iwarn("Invalid touch data: id=%d, touch=%d, x=%d, y=%d\n", id, touchpoints, x, y);
      if (last_event == 0xff)  /* Quit if we have no last valid coordinates. */
        {
          ierr("Can't return touch data: id=%d, touch=%d, x=%d, y=%d\n", id, touchpoints, x, y);
          return -EINVAL;
        }
      valid = false;
      id = last_id;
      x  = last_x;
      y  = last_y;
    }

  /* Remember the last valid touch data. */

  last_event = event;
  last_id    = id;
  last_x     = x;
  last_y     = y;

  /* Set the touch data fields. */

  memset(&data, 0, sizeof(data));
  data.npoints     = 1;
  data.point[0].id = id;
  data.point[0].x  = x;
  data.point[0].y  = y;

(Source)

last_event is now set to 1 (Touch Up).

cst816s_read() will no longer call cst816s_get_touch_data() to fetch the Touch Data, until the screen is touched again.

Screen Calibration Result

When we have touched the 4 screen corners, the LVGL Test App displays the Screen Calibration result...

tp_cal result
offset x:23, y:24
range x:194, y:198
invert x/y:1, x:0, y:1

Which will be used to tweak the Touch Coordinates in the apps.

Screen Is Sideways

According to the Touch Data from the LVGL Test App, our screen is rotated sideways...

  • Top Left: x=181, y=12

  • Top Right: x=230, y=212

  • Bottom Left: x=9, y=10

  • Bottom Right: x=19, y=202

So be careful when mapping the touch coordinates.

We can rotate the display in the ST7789 Driver. But first we need to agree which way is "up"...

https://twitter.com/MisterTechBlog/status/1514438646568415232

I2C Logging

cst816s_get_touch_data() won't return any valid Touch Data unless we enable I2C Logging. Could be an I2C Timing Issue or Race Condition.

With I2C Logging Enabled: We get the Touch Down Event (with valid Touch Data)...

nsh> lvgltest
tp_init: Opening /dev/input0
cst816s_open:

bl602_expander_interrupt: Interrupt! callback=0x2305e596, arg=0x42020a70
bl602_expander_interrupt: Call callback=0x2305e596, arg=0x42020a70
cst816s_poll_notify:

cst816s_get_touch_data:
cst816s_i2c_read:
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c transfer success
bl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0
bl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500
bl602_i2c_recvdata: count=3, temp=0x1700de
Transfer success
cst816s_get_touch_data: DOWN: id=0,touch=0, x=222, y=23
cst816s_get_touch_data:   id:      0
cst816s_get_touch_data:   flags:   19
cst816s_get_touch_data:   x:       222
cst816s_get_touch_data:   y:       23

With I2C Logging Disabled: We only get the Touch Up Event (with invalid Touch Data)...

nsh> lvgltest
tp_init: Opening /dev/input0
cst816s_open:

bl602_expander_interrupt: Interrupt! callback=0x2305e55e, arg=0x42020a70
bl602_expander_interrupt: Call callback=0x2305e55e, arg=0x42020a70
cst816s_poll_notify:

cst816s_get_touch_data:
cst816s_i2c_read:
cst816s_get_touch_data: Invalid touch data: id=9, touch=2, x=639, y=1688
cst816s_get_touch_data: Can't return touch data: id=9, touch=2, x=639, y=1688

bl602_expander_interrupt: Interrupt! callback=0x2305e55e, arg=0x42020a70
bl602_expander_interrupt: Call callback=0x2305e55e, arg=0x42020a70
cst816s_poll_notify:

cst816s_get_touch_data:
cst816s_i2c_read:
cst816s_get_touch_data: Invalid touch data: id=9, touch=2, x=639, y=1688
cst816s_get_touch_data: Can't return touch data: id=9, touch=2, x=639, y=1688

This happens before and after we have reduced the number of I2C Transfers (by checking GPIO Interrupts via int_pending).

The workaround is to call i2cwarn() in the BL602 I2C Driver to force this specific log to be printed...

static int bl602_i2c_transfer(struct i2c_master_s *dev,
                              struct i2c_msg_s *   msgs,
                              int                      count) {
      ...
      if (priv->i2cstate == EV_I2C_END_INT)
        {
          i2cinfo("i2c transfer success\n");
#ifdef CONFIG_INPUT_CST816S
          /* Workaround for CST816S. See https://github.com/lupyuen/cst816s-nuttx#i2c-logging */

          i2cwarn("i2c transfer success\n");
#endif /* CONFIG_INPUT_CST816S */
        }

(Source)

After patching the workaround, we get the Touch Down Event (with valid Touch Data)...

nsh> lvgltest
tp_init: Opening /dev/input0
cst816s_open:

bl602_expander_interrupt: Interrupt! callback=0x2305e55e, arg=0x42020a70
bl602_expander_interrupt: Call callback=0x2305e55e, arg=0x42020a70
cst816s_poll_notify:

cst816s_get_touch_data:
cst816s_i2c_read:
bl602_i2c_transfer: i2c transfer success
bl602_i2c_transfer: i2c transfer success
cst816s_get_touch_data: DOWN: id=0, touch=0, x=200, y=26
cst816s_get_touch_data:   id:      0
cst816s_get_touch_data:   flags:   19
cst816s_get_touch_data:   x:       200
cst816s_get_touch_data:   y:       26

LoRaWAN Test App lorawan_test also tested OK with the patch.

TODO: Investigate the internals of the BL602 I2C Driver. Look for I2C Timing Issues or Race Conditions.

TODO: Probe the I2C Bus with a Logic Analyser. Watch for I2C Hardware issues.

TODO: Why must we disable logging? Eventually we must disable CONFIG_DEBUG_INFO (Informational Debug Output) because the LoRaWAN Test App lorawan_test fails when CONFIG_DEBUG_INFO is enabled (due to LoRaWAN Timers)

TODO: LoRaWAN Test App, LoRaWAN Library, SX1262 Library, NimBLE Porting Layer, SPI Test Driver should have their own flags for logging

TODO: Move CST816S Interrupt Handler to BL602 GPIO Expander

TODO: Implement SPI DMA on NuttX so that the touchscreen feels less laggy

TODO: Add a button and a message box to the LVGL Test App lvgltest to demo the touchscreen

Releases

No releases published

Packages

No packages published

Languages