{"id":20316147,"url":"https://github.com/lupyuen/cst816s-nuttx","last_synced_at":"2025-04-11T17:40:43.413Z","repository":{"id":41856978,"uuid":"481001694","full_name":"lupyuen/cst816s-nuttx","owner":"lupyuen","description":"Hynitron CST816S Touch Controller Driver for Apache NuttX RTOS","archived":false,"fork":false,"pushed_at":"2022-10-11T13:13:46.000Z","size":111,"stargazers_count":11,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-25T13:39:46.575Z","etag":null,"topics":["bl602","bl604","cst816s","i2c","nuttx","pinecone","pinedio","riscv32"],"latest_commit_sha":null,"homepage":"https://lupyuen.github.io/articles/touch","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lupyuen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":["lupyuen"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":["paypal.me/lupyuen"]}},"created_at":"2022-04-12T23:14:24.000Z","updated_at":"2024-04-03T21:01:21.000Z","dependencies_parsed_at":"2023-01-19T21:03:44.067Z","dependency_job_id":null,"html_url":"https://github.com/lupyuen/cst816s-nuttx","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lupyuen%2Fcst816s-nuttx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lupyuen%2Fcst816s-nuttx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lupyuen%2Fcst816s-nuttx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lupyuen%2Fcst816s-nuttx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lupyuen","download_url":"https://codeload.github.com/lupyuen/cst816s-nuttx/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248451348,"owners_count":21105854,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bl602","bl604","cst816s","i2c","nuttx","pinecone","pinedio","riscv32"],"created_at":"2024-11-14T18:24:37.461Z","updated_at":"2025-04-11T17:40:43.393Z","avatar_url":"https://github.com/lupyuen.png","language":"C","funding_links":["https://github.com/sponsors/lupyuen","paypal.me/lupyuen"],"categories":[],"sub_categories":[],"readme":"![Touch Panel Calibration for Pine64 PineDio Stack BL604 RISC-V Board](https://lupyuen.github.io/images/touch-title.jpg)\n\n# Hynitron CST816S Touch Controller Driver for Apache NuttX RTOS\n\n[(Used by PineDio Stack BL604)](https://lupyuen.github.io/articles/pinedio2)\n\nRead the article...\n\n-   [\"NuttX Touch Panel Driver for PineDio Stack BL604\"](https://lupyuen.github.io/articles/touch)\n\nWatch the demo...\n\n-   [PineDio Stack Demo on YouTube](https://www.youtube.com/shorts/2Nzjrlp5lcE)\n\n# Install Driver\n\nIf you're using NuttX on PineDio Stack, there's no need to install the driver...\n\n-   [lupyuen/incubator-nuttx (pinedio branch)](https://github.com/lupyuen/incubator-nuttx/tree/pinedio)\n\n-   [lupyuen/incubator-nuttx-apps (pinedio branch)](https://github.com/lupyuen/incubator-nuttx-apps/tree/pinedio)\n\nOtherwise to add this repo to your NuttX project...\n\n```bash\npushd nuttx/nuttx/drivers/input\ngit submodule add https://github.com/lupyuen/cst816s-nuttx cst816s\nln -s cst816s/cst816s.c .\npopd\n\npushd nuttx/nuttx/include/nuttx/input\nln -s ../../../drivers/input/cst816s/cst816s.h .\npopd\n```\n\nNext update the Makefile and Kconfig...\n\n-   [See the modified Makefile and Kconfig](https://github.com/lupyuen/incubator-nuttx/commit/5dbf67df8f36cdba2eb0034dac0ff8ed0f8e73e1)\n\nThen update the NuttX Build Config...\n\n```bash\n## TODO: Change this to the path of our \"incubator-nuttx\" folder\ncd nuttx/nuttx\n\n## Preserve the Build Config\ncp .config ../config\n\n## Erase the Build Config and Kconfig files\nmake distclean\n\n## For BL602: Configure the build for BL602\n./tools/configure.sh bl602evb:nsh\n\n## For PineDio Stack BL604: Configure the build for BL604\n./tools/configure.sh bl602evb:pinedio\n\n## For ESP32: Configure the build for ESP32.\n## TODO: Change \"esp32-devkitc\" to our ESP32 board.\n./tools/configure.sh esp32-devkitc:nsh\n\n## Restore the Build Config\ncp ../config .config\n\n## Edit the Build Config\nmake menuconfig \n```\n\nIn menuconfig, enable the Hynitron CST816S Driver under \"Device Drivers → Input Device Support\".\n\nEnable I2C Warnings because of the [I2C Workaround for CST816S](https://github.com/lupyuen/cst816s-nuttx#i2c-logging)...\n\n-   Click \"Build Setup\" → \"Debug Options\"\n\n-   Check the boxes for the following...\n    -   Enable Warnings Output\n    -   I2C Warnings Output\n\n-   (Optional) To enable logging for the CST816S Driver, check the boxes for...\n    -   Enable Error Output\n    -   Enable Informational Debug Output\n    -   Enable Debug Assertions\n    -   Input Device Error Output\n    -   Input Device Warnings Output\n    -   Input Device Informational Output\n\n-   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)\n\nEdit the function [`bl602_i2c_transfer`](https://github.com/lupyuen/incubator-nuttx/blob/touch/arch/risc-v/src/bl602/bl602_i2c.c#L671-L773) and apply this workaround patch...\n\n-   [\"I2C Logging\"](https://github.com/lupyuen/cst816s-nuttx#i2c-logging)\n\nEdit the function `bl602_bringup` or `esp32_bringup` in this file...\n\n```text\n## For BL602:\nnuttx/boards/risc-v/bl602/bl602evb/src/bl602_bringup.c\n\n## For ESP32: Change \"esp32-devkitc\" to our ESP32 board \nnuttx/boards/xtensa/esp32/esp32-devkitc/src/esp32_bringup.c\n```\n\nAnd call `cst816s_register` to load our driver:\n\nhttps://github.com/lupyuen/incubator-nuttx/blob/touch/boards/risc-v/bl602/bl602evb/src/bl602_bringup.c#L826-L843\n\n```c\n#ifdef CONFIG_INPUT_CST816S\n/* I2C Address of CST816S Touch Controller */\n\n#define CST816S_DEVICE_ADDRESS 0x15\n#include \u003cnuttx/input/cst816s.h\u003e\n#endif /* CONFIG_INPUT_CST816S */\n...\n#ifdef CONFIG_INPUT_CST816S\nint bl602_bringup(void)\n{\n  ...\n  /* Init I2C bus for CST816S */\n\n  struct i2c_master_s *cst816s_i2c_bus = bl602_i2cbus_initialize(0);\n  if (!cst816s_i2c_bus)\n    {\n      _err(\"ERROR: Failed to get I2C%d interface\\n\", 0);\n    }\n\n  /* Register the CST816S driver */\n\n  ret = cst816s_register(\"/dev/input0\", cst816s_i2c_bus, CST816S_DEVICE_ADDRESS);\n  if (ret \u003c 0)\n    {\n      _err(\"ERROR: Failed to register CST816S\\n\");\n    }\n#endif /* CONFIG_INPUT_CST816S */\n```\n\nHere's how we created the CST816S Driver for NuttX on PineDio Stack BL604...\n\n# Cypress MBR3108\n\nNuttX Driver for Cypress MBR3108 Touch Controller looks structurally similar to PineDio Stack's CST816S ... So we copy-n-paste into our CST816S Driver\n\n-   [NuttX Driver for Cypress MBR3108](https://github.com/lupyuen/incubator-nuttx/blob/master/drivers/input/cypress_mbr3108.c)\n\n# I2C Scan\n\nPineDio 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!\n\n-   [\"Building a Rust Driver for PineTime’s Touch Controller\"](https://lupyuen.github.io/articles/building-a-rust-driver-for-pinetimes-touch-controller)\n\n# GPIO Interrupt\n\nPineDio Stack's Touch Panel triggers a GPIO Interrupt when we tap the screen ... Here's how we handle the GPIO Interrupt\n\n```c\nint cst816s_register(FAR const char *devpath,\n                     FAR struct i2c_master_s *i2c_dev,\n                     uint8_t i2c_devaddr)\n{\n  ...\n  /* Prepare interrupt line and handler. */\n\n  ret = bl602_irq_attach(BOARD_TOUCH_INT, cst816s_isr_handler, priv);\n  if (ret \u003c 0)\n    {\n      kmm_free(priv);\n      ierr(\"Attach interrupt failed\\n\");\n      return ret;\n    }\n\n  ret = bl602_irq_enable(false);\n  if (ret \u003c 0)\n    {\n      kmm_free(priv);\n      ierr(\"Disable interrupt failed\\n\");\n      return ret;\n    }\n```\n\n[(Source)](https://github.com/lupyuen/cst816s-nuttx/blob/main/cst816s.c#L593-L661)\n\n`bl602_irq_attach` is defined below...\n\n```c\n//  Attach Interrupt Handler to GPIO Interrupt for Touch Controller\n//  Based on https://github.com/lupyuen/incubator-nuttx/blob/touch/boards/risc-v/bl602/bl602evb/src/bl602_gpio.c#L477-L505\nstatic int bl602_irq_attach(gpio_pinset_t pinset, FAR isr_handler *callback, FAR void *arg)\n{\n  int ret = 0;\n  uint8_t gpio_pin = (pinset \u0026 GPIO_PIN_MASK) \u003e\u003e GPIO_PIN_SHIFT;\n  FAR struct bl602_gpint_dev_s *dev = NULL;  //  TODO\n\n  DEBUGASSERT(callback != NULL);\n\n  /* Configure the pin that will be used as interrupt input */\n\n  #warning Check GLB_GPIO_INT_TRIG_NEG_PULSE  ////  TODO\n  bl602_expander_set_intmod(gpio_pin, 1, GLB_GPIO_INT_TRIG_NEG_PULSE);\n  ret = bl602_configgpio(pinset);\n  if (ret \u003c 0)\n    {\n      gpioerr(\"Failed to configure GPIO pin %d\\n\", gpio_pin);\n      return ret;\n    }\n\n  /* Make sure the interrupt is disabled */\n\n  bl602_expander_pinset = pinset;\n  bl602_expander_callback = callback;\n  bl602_expander_arg = arg;\n  bl602_expander_intmask(gpio_pin, 1);\n\n  irq_attach(BL602_IRQ_GPIO_INT0, bl602_expander_interrupt, dev);\n  bl602_expander_intmask(gpio_pin, 0);\n\n  gpioinfo(\"Attach %p\\n\", callback);\n\n  return 0;\n}\n```\n\n[(Source)](https://github.com/lupyuen/cst816s-nuttx/blob/main/cst816s.c#L686-L727)\n\nNote 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`).\n\nWe are building `bl602_expander` here...\n\n-   [lupyuen/bl602_expander](https://github.com/lupyuen/bl602_expander)\n\nTo test interrupts we uncomment `#define TEST_CST816S_INTERRUPT`...\n\n```c\nint cst816s_register(FAR const char *devpath,\n                     FAR struct i2c_master_s *i2c_dev,\n                     uint8_t i2c_devaddr)\n{\n...\n//  Uncomment this to test interrupts (tap the screen)\n#define TEST_CST816S_INTERRUPT\n#ifdef TEST_CST816S_INTERRUPT\n#warning Testing CST816S interrupt\n  bl602_irq_enable(true);\n#endif /* TEST_CST816S_INTERRUPT */\n```\n\n[(Source)](https://github.com/lupyuen/cst816s-nuttx/blob/main/cst816s.c#L593-L661)\n\nThere's bug with BL602 GPIO Interrupts that we have fixed for our driver...\n\nhttps://github.com/apache/incubator-nuttx/issues/5810#issuecomment-1098633538\n\n# Test GPIO Interrupt\n\nTapping the screen on PineDio Stack ... Correctly triggers a GPIO Interrupt 🎉\n\n```text\ngpio_pin_register: Registering /dev/gpio0\ngpio_pin_register: Registering /dev/gpio1\ngpint_enable: Disable the interrupt\ngpio_pin_register: Registering /dev/gpio2\nbl602_gpio_set_intmod: ****gpio_pin=115, int_ctlmod=1, int_trgmod=0\nspi_test_driver_register: devpath=/dev/spitest0, spidev=0\ncst816s_register:\nbl602_expander_set_intmod: ****gpio_pin=9, int_ctlmod=1, int_trgmod=0\nbl602_irq_attach: Attach 0x2305e9de\nbl602_irq_enable: Disable interrupt\ncst816s_register: Driver registered\nbl602_irq_enable: Enable interrupt\n\nNuttShell (NSH) NuttX-10.2.0-RC0\nnsh\u003e bl602_expander_interrupt: Interrupt! callback=0x2305e9de, arg=0x42020a60\nbl602_expander_interrupt: Call callback=0x2305e9de, arg=0x42020a60\ncst816s_poll_notify:\nbl602_expander_interrupt: Interrupt! callback=0x2305e9de, arg=0x42020a60\nbl602_expander_interrupt: Call callback=0x2305e9de, arg=0x42020a60\ncst816s_poll_notify:\nbl602_expander_interrupt: Interrupt! callback=0x2305e9de, arg=0x42020a60\nbl602_expander_interrupt: Call callback=0x2305e9de, arg=0x42020a60\ncst816s_poll_notify:\nbl602_expander_interrupt: Interrupt! callback=0x2305e9de, arg=0x42020a60\nbl602_expander_interrupt: Call callback=0x2305e9de, arg=0x42020a60\ncst816s_poll_notify:\n```\n\nLVGL Test App `lvgltest` fails to open `/dev/input0`, but that's OK because we haven't implemented the I2C part.\n\n```text\nnsh\u003e ls /dev\n/dev:\n console\n gpio0\n gpio1\n gpio2\n i2c0\n input0\n lcd0\n null\n spi0\n spitest0\n timer0\n urandom\n zero\n\nnsh\u003e lvgltest\ntp_init: Opening /dev/input0\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\ncst816s_probe_device: family_id: 0x34, device_id: 0x00aa, device_rev: 35\ncst816s_probe_device: Probe failed, dev-id mismatch!\ncst816s_probe_device:   Expected: family_id: 0x9a, device_id: 0x0a03, device_rev: 1\ntp_init: open /dev/input0 failed: 6\nTerminating!\nbl602_expander_interrupt: Interrupt! callback=0x2305e9e8, arg=0\nbl602_expander_interrupt: Call callback=0x2305e9e8, arg=0\nbl602_expander_interrupt: Interrupt! callback=0x2305e9e8, arg=0\nbl602_expander_interrupt: Call callback=0x2305e9e8, arg=0\nbl602_expander_interrupt: Interrupt! callback=0x2305e9e8, arg=0\nbl602_expander_interrupt: Call callback=0x2305e9e8, arg=0\n```\n\n# Touch Data\n\nApache NuttX RTOS has a standard data format for Touch Panels ... Let's implement this for PineDio Stack\n\n```c\n/* This structure contains information about a single touch point.\n * Positional units are device specific.\n */\n\nstruct touch_point_s\n{\n  uint8_t  id;        /* Unique identifies contact; Same in all reports for the contact */\n  uint8_t  flags;     /* See TOUCH_* definitions above */\n  int16_t  x;         /* X coordinate of the touch point (uncalibrated) */\n  int16_t  y;         /* Y coordinate of the touch point (uncalibrated) */\n  int16_t  h;         /* Height of touch point (uncalibrated) */\n  int16_t  w;         /* Width of touch point (uncalibrated) */\n  uint16_t gesture;   /* Gesture of touchscreen contact */\n  uint16_t pressure;  /* Touch pressure */\n  uint64_t timestamp; /* Touch event time stamp, in microseconds */\n};\n\n/* The typical touchscreen driver is a read-only, input character device\n * driver.the driver write() method is not supported and any attempt to\n * open the driver in any mode other than read-only will fail.\n *\n * Data read from the touchscreen device consists only of touch events and\n * touch sample data.  This is reflected by struct touch_sample_s.  This\n * structure is returned by either the driver read method.\n *\n * On some devices, multiple touchpoints may be supported. So this top level\n * data structure is a struct touch_sample_s that \"contains\" a set of touch\n * points.  Each touch point is managed individually using an ID that\n * identifies a touch from first contact until the end of the contact.\n */\n\nstruct touch_sample_s\n{\n  int npoints;                   /* The number of touch points in point[] */\n  struct touch_point_s point[1]; /* Actual dimension is npoints */\n};\n```\n\n[(Source)](https://github.com/lupyuen/incubator-nuttx/blob/touch/include/nuttx/input/touchscreen.h#L113-L148)\n\n# Read Touch Data\n\nHere's how we read the Touched Coordinates in our driver...\n\n```c\nstatic int cst816s_get_touch_data(FAR struct cst816s_dev_s *dev, FAR void *buf)\n{\n  iinfo(\"\\n\"); ////\n  struct touch_sample_s data;\n  uint8_t readbuf[7];\n  int ret;\n\n  /* Read the raw touch data. */\n\n  ret = cst816s_i2c_read(dev, CST816S_REG_TOUCHDATA, readbuf, sizeof(readbuf));\n  if (ret \u003c 0)\n    {\n      iinfo(\"Read touch data failed\\n\");\n      return ret;\n    }\n\n  /* Interpret the raw touch data. */\n\n  uint8_t id = readbuf[5] \u003e\u003e 4;\n  uint8_t touchpoints = readbuf[2] \u0026 0x0f;\n  uint8_t xhigh = readbuf[3] \u0026 0x0f;\n  uint8_t xlow  = readbuf[4];\n  uint8_t yhigh = readbuf[5] \u0026 0x0f;\n  uint8_t ylow  = readbuf[6];\n  uint8_t event = readbuf[3] \u003e\u003e 6;  /* 0 = Touch Down, 1 = Touch Up, 2 = Contact */\n  uint16_t x  = (xhigh  \u003c\u003c 8) | xlow;\n  uint16_t y  = (yhigh  \u003c\u003c 8) | ylow;\n\n  /* If touch coordinates are invalid, return the last valid coordinates. */\n\n  bool valid = true;\n  if (x \u003e= 240 || y \u003e= 240)\n    {\n      iwarn(\"Invalid touch data: id=%d, touch=%d, x=%d, y=%d\\n\", id, touchpoints, x, y);\n      if (last_event == 0xff)  /* Quit if we have no last valid coordinates. */\n        {\n          ierr(\"Can't return touch data: id=%d, touch=%d, x=%d, y=%d\\n\", id, touchpoints, x, y);\n          return -EINVAL;\n        }\n      valid = false;\n      id = last_id;\n      x  = last_x;\n      y  = last_y;\n    }\n\n  /* Remember the last valid touch data. */\n\n  last_event = event;\n  last_id    = id;\n  last_x     = x;\n  last_y     = y;\n\n  /* Set the touch data fields. */\n\n  memset(\u0026data, 0, sizeof(data));\n  data.npoints     = 1;\n  data.point[0].id = id;\n  data.point[0].x  = x;\n  data.point[0].y  = y;\n\n  /* Set the touch flags. */\n\n  if (event == 0)  /* Touch Down */\n    {\n      iinfo(\"DOWN: id=%d, touch=%d, x=%d, y=%d\\n\", id, touchpoints, x, y);\n      if (valid)  /* Touch coordinates were valid. */\n        {\n          data.point[0].flags  = TOUCH_DOWN | TOUCH_ID_VALID | TOUCH_POS_VALID;\n        }\n      else  /* Touch coordinates were invalid. */\n        {\n          data.point[0].flags  = TOUCH_DOWN | TOUCH_ID_VALID;\n        }\n    }\n  else if (event == 1)  /* Touch Up */\n    {\n      iinfo(\"UP: id=%d, touch=%d, x=%d, y=%d\\n\", id, touchpoints, x, y);\n      if (valid)  /* Touch coordinates were valid. */\n        {\n          data.point[0].flags  = TOUCH_UP | TOUCH_ID_VALID | TOUCH_POS_VALID;\n        }\n      else  /* Touch coordinates were invalid. */\n        {\n          data.point[0].flags  = TOUCH_UP | TOUCH_ID_VALID;\n        }\n    }\n  else  /* Reject Contact */\n    {\n      iinfo(\"CONTACT: id=%d, touch=%d, x=%d, y=%d\\n\", id, touchpoints, x, y);\n      return -EINVAL;\n    }\n\n  /* Return the touch data. */\n\n  memcpy(buf, \u0026data, sizeof(data));\n\n  iinfo(\"  id:      %d\\n\",   data.point[0].id);\n  iinfo(\"  flags:   %02x\\n\", data.point[0].flags);\n  iinfo(\"  x:       %d\\n\",   data.point[0].x);\n  iinfo(\"  y:       %d\\n\",   data.point[0].y);\n\n  return sizeof(data);\n}\n```\n\n[(Source)](https://github.com/lupyuen/cst816s-nuttx/blob/main/cst816s.c#L213-L302)\n\nNote that our NuttX Driver for PineDio Stack's Touch Panel returns 4 possible states: Touch Down vs Touch Up, Valid vs Invalid.\n\nWe got this code thanks to JF's CST816S driver for the Self-Test Firmware...\n\n-   [pinedio-stack-selftest/drivers/cst816s.c](https://codeberg.org/JF002/pinedio-stack-selftest/src/branch/master/drivers/cst816s.c)\n\nAnd from our previous work on PineTime, which also uses CST816S...\n\n-   [\"Building a Rust Driver for PineTime’s Touch Controller\"](https://lupyuen.github.io/articles/building-a-rust-driver-for-pinetimes-touch-controller)\n\n-   [CST816S Driver in Rust](https://github.com/lupyuen/stm32bluepill-mynewt-sensor/blob/pinetime/rust/app/src/touch_sensor.rs)\n\n-   [Hynitron Reference Driver](https://github.com/lupyuen/hynitron_i2c_cst0xxse/blob/master/cst0xx_core.c#L407-L466)\n\n# Test Touch Data\n\nNuttX Driver for PineDio Stack Touch Panel responds correctly to touch! 🎉\n\nPineDio 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\n\n-   [Watch the demo on YouTube](https://www.youtube.com/shorts/2Nzjrlp5lcE)\n\n[(UPDATE: We have bumped up the SPI Frequency to max 40 MHz, still feels laggy)](https://github.com/lupyuen/incubator-nuttx/blob/touch/boards/risc-v/bl602/bl602evb/configs/pinedio/defconfig#L580)\n\nHere's the detailed log...\n\n```text\ngpio_pin_register: Registering /dev/gpio0\ngpio_pin_register: Registering /dev/gpio1\ngpint_enable: Disable the interrupt\ngpio_pin_register: Registering /dev/gpio2\nbl602_gpio_set_intmod: ****gpio_pin=115, int_ctlmod=1, int_trgmod=0\nspi_test_driver_register: devpath=/dev/spitest0, spidev=0\ncst816s_register: path=/dev/input0, addr=21\nbl602_expander_set_intmod: gpio_pin=9, int_ctlmod=1, int_trgmod=0\nbl602_irq_attach: Attach 0x2305e596\nbl602_irq_enable: Disable interrupt\ncst816s_register: Driver registered\nbl602_irq_enable: Enable interrupt\n\nNuttShell (NSH) NuttX-10.2.0-RC0\nnsh\u003e lvgltest\ntp_init: Opening /dev/input0\ncst816s_open:\n\nbl602_expander_interrupt: Interrupt! callback=0x2305e596, arg=0x42020a70\nbl602_expander_interrupt: Call callback=0x2305e596, arg=0x42020a70\ncst816s_poll_notify:\n\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500\nbl602_i2c_recvdata: count=3, temp=0x1700de\nransfer success\ncst816s_get_touch_data: DOWN: id=0,touch=0, x=222, y=23\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       222\ncst816s_get_touch_data:   y:       23\n\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500\nbl602_i2c_recvdata: count=3, temp=0x1700de\nransfer success\ncst816s_get_touch_data: DOWN: id=0, ouch=0, x=222, y=23\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       222\ncst816s_get_touch_data:   y:       23\n\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500\nbl602_i2c_recvdata: count=3, temp=0x1700de\nransfer success\ncst816s_get_touch_data: DOWN: id=0, touch=0, x=222, y=23\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       222\ncst816s_get_touch_data:   y:       23\n\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\ncst816s_get_touch_data: Invalid touch data: id=9, touch=2, x=639, y=1688\ncst816s_get_touch_data: UP: id=0, touch=2, x=222, y=23\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   0c\ncst816s_get_touch_data:   x:       222\ncst816s_get_touch_data:   y:       23\nbl602_expander_interrupt: Interrupt! callback=0x2305e596, arg=0x42020a70\nbl602_expander_interrupt: Call callback=0x2305e596, arg=0x42020a70\ncst816s_poll_notify:\n\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500\nbl602_i2c_recvdata: count=3, temp=0xd900db\nransfer success\ncst816s_get_touch_data: DOWN: id=0, touch=0, x=219, y=217\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       219\ncst816s_get_touch_data:   y:       217\n\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500\nbl602_i2c_recvdata: count=3, temp=0xd900db\nransfer success\ncst816s_get_touch_data: DOWN: id=0, touch=0, x=219, y=217\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:  19\ncst816s_get_touch_data:   x:       219\ncst816s_get_touch_data:   y:       217\n\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\ncst816s_get_touch_data: Invalid touch data: id=4, touch=2, x=636, y=3330\ncst816s_get_touch_data: UP: id=0, touch=2, x=219, y=217\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   0c\ncst816s_get_touch_data:   x:       219\ncst816s_get_touch_data:   y:       217\nbl602_expander_interrupt: Interrupt! callback=0x2305e596, arg=0x42020a70\nbl602_expander_interrupt: Call callback=0x2305e596, arg=0x42020a70\ncst816s_poll_notify:\n\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500\nbl602_i2c_recvdata: count=3, temp=0xdb0022\nransfer success\ncst816s_get_touch_data: DOWN: id=0, touch=0, x=34, y=219\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       34\ncst816s_get_touch_data:   y:       219\n\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500\nbl602_i2c_recvdata: count=3, temp=0xdb0022\nransfer success\ncst816s_get_touch_data: DOWN: id=0, touch=0, x=34, y=219\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       34\ncst816s_get_touch_data:   y:       219\n\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\ncst816s_get_touch_data: Invalid touch data: id=4, touch=2, x=636, y=3330\ncst816s_get_touch_data: UP: id=0, touch=2, x=34, y=219\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   0c\ncst816s_get_touch_data:   x:       34\ncst816s_get_touch_data:   y:       219\nbl602_expander_interrupt: Interrupt! callback=0x2305e596, arg=0x42020a70\nbl602_expander_interrupt: Call callback=0x2305e596, arg=0x42020a70\ncst816s_poll_notify:\n\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500\nbl602_i2c_recvdata: count=3, temp=0x180018\nransfer success\ncst816s_get_touch_data: DOWN: id=0, touch=0, x=24, y=24\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       24\ncst816s_get_touch_data:   y:       24\n\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500\nbl602_i2c_recvdata: count=3, temp=0x180018\nransfer success\ncst816s_get_touch_data: DOWN: id=0, touch=0, x=24, y=24\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       24\ncst816s_get_touch_data:   y:       24\n\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\ncst816s_get_touch_data: Invalid touch data: id=4, touch=2, x=636, y=3330\ncst816s_get_touch_data: UP: id=0, touch=2, x=24, y=24\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   0c\ncst816s_get_touch_data:   x:       24\ncst816s_get_touch_data:   y:       24\nbl602_expander_interrupt: Interrupt! callback=0x2305e596, arg=0x42020a70\nbl602_expander_interrupt: Call callback=0x2305e596, arg=0x42020a70\ncst816s_poll_notify:\n\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500\nbl602_i2c_recvdata: count=3, temp=0x8d0076\nransfer success\ncst816s_get_touch_data: DOWN: id=0, touch=0, x=118, y=141\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       118\ncst816s_get_touch_data:   y:       141\n\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500\nbl602_i2c_recvdata: count=3, temp=0x8d0076\nransfer success\ncst816s_get_touch_data: DOWN: id=0, touch=0, x=118, y=141\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       118\ncst816s_get_touch_data:   y:       141\n\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\ncst816s_get_touch_data: Invalid touch data: id=4, touch=2, x=636, y=3330\ncst816s_get_touch_data: UP: id=0, touch=2, x=118, y=141\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   0c\ncst816s_get_touch_data:   x:       118\ncst816s_get_touch_data:   y:       141\n\ntp_cal result\noffset x:23, y:24\nrange x:194, y:198\ninvert x/y:1, x:0, y:1\n```\n\nLet's break down the log...\n\n## Enable GPIO Interrupt\n\nAt NuttX Startup, we register the CST816S Driver as `/dev/input0` and enable the GPIO interrupt...\n\n```text\ngpio_pin_register: Registering /dev/gpio0\ngpio_pin_register: Registering /dev/gpio1\ngpint_enable: Disable the interrupt\ngpio_pin_register: Registering /dev/gpio2\nbl602_gpio_set_intmod: ****gpio_pin=115, int_ctlmod=1, int_trgmod=0\nspi_test_driver_register: devpath=/dev/spitest0, spidev=0\ncst816s_register: path=/dev/input0, addr=21\nbl602_expander_set_intmod: gpio_pin=9, int_ctlmod=1, int_trgmod=0\nbl602_irq_attach: Attach 0x2305e596\nbl602_irq_enable: Disable interrupt\ncst816s_register: Driver registered\nbl602_irq_enable: Enable interrupt\n\nNuttShell (NSH) NuttX-10.2.0-RC0\nnsh\u003e\n```\n\n## Start LVGL App\n\nWe run the LVGL Test App `lvgltest`...\n\n```text\nnsh\u003e lvgltest\ntp_init: Opening /dev/input0\ncst816s_open:\n```\n\nWhich calls [`cst816s_open()`](https://github.com/lupyuen/cst816s-nuttx/blob/main/cst816s.c#L384-L420) to open our CST816S Driver.\n\nThe app begins the Touchscreen Calibration process.\n\n## Read Touch Data\n\nThe LVGL Test App calls [`cst816s_read()`](https://github.com/lupyuen/cst816s-nuttx/blob/main/cst816s.c#L328-L382) repeatedly on the CST816S Driver to get Touch Data...\n\n```c\nbool tp_read(struct _lv_indev_drv_t *indev_drv, lv_indev_data_t *data)\n{\n  ...\n  /* Read one sample */\n\n  nbytes = read(fd, \u0026sample, sizeof(struct touch_sample_s));\n```\n\n[(Source)](https://github.com/lupyuen/lvgltest-nuttx/blob/main/tp.c#L115-L132)\n\nSince the screen hasn't been touched and we have no Touch Data yet, our driver returns an error `-EINVAL`...\n\n```c\nstatic ssize_t cst816s_read(FAR struct file *filep, FAR char *buffer,\n                            size_t buflen)\n{\n  ...\n  int ret = -EINVAL;\n\n  /* Read the touch data, only if screen has been touched or if we're waiting for touch up */\n  if ((priv-\u003eint_pending || last_event == 0) \u0026\u0026 buflen \u003e= outlen)\n    {\n      ret = cst816s_get_touch_data(priv, buffer);\n    }\n```\n\n[(Source)](https://github.com/lupyuen/cst816s-nuttx/blob/main/cst816s.c#L336-L370)\n\n`int_pending` becomes true when a GPIO Interrupt gets triggered later.\n\n`last_event` becomes 0 when we get a Touch Down event later.\n\n_Why do we check `int_pending`?_\n\nTo 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.\n\n(But the LVGL Test App really shouldn't call `read()` repeatedly. It ought to call `poll()` and block until Touch Data is available)\n\n_Why do we we check `last_event`?_\n\nThe Touch Controller triggers a GPIO Interrupt only upon Touch Down, not on Touch Up.\n\nSo after Touch Down, we allow  [`cst816s_read()`](https://github.com/lupyuen/cst816s-nuttx/blob/main/cst816s.c#L328-L382) 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.\n\n## Trigger GPIO Interrupt\n\nWe touch the screen and trigger a GPIO Interrupt...\n\n```text\nbl602_expander_interrupt: Interrupt! callback=0x2305e596, arg=0x42020a70\nbl602_expander_interrupt: Call callback=0x2305e596, arg=0x42020a70\ncst816s_poll_notify:\n```\n\nThe Interrupt Handler in our driver sets `int_pending` to true...\n\n```c\nstatic int cst816s_isr_handler(int _irq, FAR void *_context, FAR void *arg)\n{\n  FAR struct cst816s_dev_s *priv = (FAR struct cst816s_dev_s *)arg;\n  irqstate_t flags;\n\n  DEBUGASSERT(priv != NULL);\n\n  flags = enter_critical_section();\n  priv-\u003eint_pending = true;\n  leave_critical_section(flags);\n\n  cst816s_poll_notify(priv);\n  return 0;\n}\n```\n\n[(Source)](https://github.com/lupyuen/cst816s-nuttx/blob/main/cst816s.c#L598-L611)\n\nAnd calls [`cst816s_poll_notify()`](https://github.com/lupyuen/cst816s-nuttx/blob/main/cst816s.c#L472-L498) to unblock all `poll()` callers and notify them that Touch Data is available.\n\n(But LVGL Test App doesn't `poll()` our driver, so this doesn't effect anything)\n\n## Touch Down Event\n\nRemember that the LVGL Test App keeps calling [`cst816s_read()`](https://github.com/lupyuen/cst816s-nuttx/blob/main/cst816s.c#L328-L382) repeatedly to get Touch Data.\n\nNow that `int_pending` is true, our driver proceeds to call [`cst816s_get_touch_data()`](https://github.com/lupyuen/cst816s-nuttx/blob/main/cst816s.c#L222-L326) and fetch the Touch Data over I2C...\n\n```text\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500\nbl602_i2c_recvdata: count=3, temp=0x1700de\nransfer success\ncst816s_get_touch_data: DOWN: id=0,touch=0, x=222, y=23\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       222\ncst816s_get_touch_data:   y:       23\n```\n\nThe Touch Data that was read from CST816S over I2C...\n\n```text\ncst816s_get_touch_data: DOWN: id=0,touch=0, x=222, y=23\n```\n\nGets returned directly to the LVGL Test App as a Touch Down Event...\n\n```text\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       222\ncst816s_get_touch_data:   y:       23\n```\n\n[`cst816s_get_touch_data()`](https://github.com/lupyuen/cst816s-nuttx/blob/main/cst816s.c#L222-L326) sets `last_event` to 0 because it's a Touch Down Event.\n\n[`cst816s_read()`](https://github.com/lupyuen/cst816s-nuttx/blob/main/cst816s.c#L372-L382) sets `int_pending` to false.\n\n## Touch Down Event Again\n\nLVGL Test App is still calling [`cst816s_read()`](https://github.com/lupyuen/cst816s-nuttx/blob/main/cst816s.c#L328-L382) repeatedly to get Touch Data.\n\nNow that `last_event` is 0 (Touch Down), our driver proceeds to call [`cst816s_get_touch_data()`](https://github.com/lupyuen/cst816s-nuttx/blob/main/cst816s.c#L222-L326) and fetch the Touch Data over I2C...\n\n```text\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500\nbl602_i2c_recvdata: count=3, temp=0x1700de\nransfer success\ncst816s_get_touch_data: DOWN: id=0, ouch=0, x=222, y=23\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       222\ncst816s_get_touch_data:   y:       23\n\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500\nbl602_i2c_recvdata: count=3, temp=0x1700de\nransfer success\ncst816s_get_touch_data: DOWN: id=0, touch=0, x=222, y=23\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       222\ncst816s_get_touch_data:   y:       23\n```\n\nThis happens twice because we haven't received a Touch Up Event.\n\n## Touch Up Event\n\nWhen our finger is no longer touching the screen, [`cst816s_get_touch_data()`](https://github.com/lupyuen/cst816s-nuttx/blob/main/cst816s.c#L222-L326) receives a Touch Up Event...\n\n```text\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\ncst816s_get_touch_data: Invalid touch data: id=9, touch=2, x=639, y=1688\ncst816s_get_touch_data: UP: id=0, touch=2, x=222, y=23\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   0c\ncst816s_get_touch_data:   x:       222\ncst816s_get_touch_data:   y:       23\n```\n\nFor Touch Up Events the Touch Coordinates are invalid...\n\n```text\ncst816s_get_touch_data: Invalid touch data: id=9, touch=2, x=639, y=1688\n```\n\nThe driver patches the Touch Coordinates with the data from the last Touch Down Event...\n\n```text\ncst816s_get_touch_data: UP: id=0, touch=2, x=222, y=23\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   0c\ncst816s_get_touch_data:   x:       222\ncst816s_get_touch_data:   y:       23\n```\n\nAnd returns the valid coordinates to the LVGL Test App. The patching is done here...\n\n```c\nstatic int cst816s_get_touch_data(FAR struct cst816s_dev_s *dev, FAR void *buf) {\n...\n  /* If touch coordinates are invalid, return the last valid coordinates. */\n\n  bool valid = true;\n  if (x \u003e= 240 || y \u003e= 240)\n    {\n      iwarn(\"Invalid touch data: id=%d, touch=%d, x=%d, y=%d\\n\", id, touchpoints, x, y);\n      if (last_event == 0xff)  /* Quit if we have no last valid coordinates. */\n        {\n          ierr(\"Can't return touch data: id=%d, touch=%d, x=%d, y=%d\\n\", id, touchpoints, x, y);\n          return -EINVAL;\n        }\n      valid = false;\n      id = last_id;\n      x  = last_x;\n      y  = last_y;\n    }\n\n  /* Remember the last valid touch data. */\n\n  last_event = event;\n  last_id    = id;\n  last_x     = x;\n  last_y     = y;\n\n  /* Set the touch data fields. */\n\n  memset(\u0026data, 0, sizeof(data));\n  data.npoints     = 1;\n  data.point[0].id = id;\n  data.point[0].x  = x;\n  data.point[0].y  = y;\n```\n\n[(Source)](https://github.com/lupyuen/cst816s-nuttx/blob/main/cst816s.c#L258-L282)\n\n`last_event` is now set to 1 (Touch Up). \n\n[`cst816s_read()`](https://github.com/lupyuen/cst816s-nuttx/blob/main/cst816s.c#L328-L382) will no longer call [`cst816s_get_touch_data()`](https://github.com/lupyuen/cst816s-nuttx/blob/main/cst816s.c#L222-L326) to fetch the Touch Data, until the screen is touched again.\n\n## Screen Calibration Result\n\nWhen we have touched the 4 screen corners, the LVGL Test App displays the Screen Calibration result...\n\n```text\ntp_cal result\noffset x:23, y:24\nrange x:194, y:198\ninvert x/y:1, x:0, y:1\n```\n\nWhich will be used to tweak the Touch Coordinates in the apps.\n\n# Screen Is Sideways\n\nAccording to the Touch Data from the LVGL Test App, our screen is rotated sideways...\n\n-   Top Left: x=181, y=12\n\n-   Top Right: x=230, y=212\n\n-   Bottom Left: x=9, y=10\n\n-   Bottom Right: x=19, y=202\n\nSo be careful when mapping the touch coordinates.\n\nWe can rotate the display in the ST7789 Driver. But first we need to agree which way is \"up\"...\n\nhttps://twitter.com/MisterTechBlog/status/1514438646568415232\n\n# I2C Logging\n\n[`cst816s_get_touch_data()`](https://github.com/lupyuen/cst816s-nuttx/blob/main/cst816s.c#L222-L326) won't return any valid Touch Data unless we enable I2C Logging. Could be an I2C Timing Issue or Race Condition.\n\nWith I2C Logging Enabled: We get the Touch Down Event (with valid Touch Data)...\n\n```text\nnsh\u003e lvgltest\ntp_init: Opening /dev/input0\ncst816s_open:\n\nbl602_expander_interrupt: Interrupt! callback=0x2305e596, arg=0x42020a70\nbl602_expander_interrupt: Call callback=0x2305e596, arg=0x42020a70\ncst816s_poll_notify:\n\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: subflag=0, subaddr=0x0, sublen=0\nbl602_i2c_transfer: i2c tbl602_i2c_recvdata: count=7, temp=0x500\nbl602_i2c_recvdata: count=3, temp=0x1700de\nTransfer success\ncst816s_get_touch_data: DOWN: id=0,touch=0, x=222, y=23\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       222\ncst816s_get_touch_data:   y:       23\n```\n\nWith I2C Logging Disabled: We only get the Touch Up Event (with invalid Touch Data)...\n\n```text\nnsh\u003e lvgltest\ntp_init: Opening /dev/input0\ncst816s_open:\n\nbl602_expander_interrupt: Interrupt! callback=0x2305e55e, arg=0x42020a70\nbl602_expander_interrupt: Call callback=0x2305e55e, arg=0x42020a70\ncst816s_poll_notify:\n\ncst816s_get_touch_data:\ncst816s_i2c_read:\ncst816s_get_touch_data: Invalid touch data: id=9, touch=2, x=639, y=1688\ncst816s_get_touch_data: Can't return touch data: id=9, touch=2, x=639, y=1688\n\nbl602_expander_interrupt: Interrupt! callback=0x2305e55e, arg=0x42020a70\nbl602_expander_interrupt: Call callback=0x2305e55e, arg=0x42020a70\ncst816s_poll_notify:\n\ncst816s_get_touch_data:\ncst816s_i2c_read:\ncst816s_get_touch_data: Invalid touch data: id=9, touch=2, x=639, y=1688\ncst816s_get_touch_data: Can't return touch data: id=9, touch=2, x=639, y=1688\n```\n\nThis happens before and after we have reduced the number of I2C Transfers (by checking GPIO Interrupts via `int_pending`).\n\nThe workaround is to call `i2cwarn()` in the [BL602 I2C Driver](https://github.com/lupyuen/incubator-nuttx/blob/touch/arch/risc-v/src/bl602/bl602_i2c.c) to force this specific log to be printed...\n\n```c\nstatic int bl602_i2c_transfer(struct i2c_master_s *dev,\n                              struct i2c_msg_s *   msgs,\n                              int                      count) {\n      ...\n      if (priv-\u003ei2cstate == EV_I2C_END_INT)\n        {\n          i2cinfo(\"i2c transfer success\\n\");\n#ifdef CONFIG_INPUT_CST816S\n          /* Workaround for CST816S. See https://github.com/lupyuen/cst816s-nuttx#i2c-logging */\n\n          i2cwarn(\"i2c transfer success\\n\");\n#endif /* CONFIG_INPUT_CST816S */\n        }\n```\n\n[(Source)](https://github.com/lupyuen/incubator-nuttx/blob/touch/arch/risc-v/src/bl602/bl602_i2c.c#L753-L761)\n\nAfter patching the workaround, we get the Touch Down Event (with valid Touch Data)...\n\n```text\nnsh\u003e lvgltest\ntp_init: Opening /dev/input0\ncst816s_open:\n\nbl602_expander_interrupt: Interrupt! callback=0x2305e55e, arg=0x42020a70\nbl602_expander_interrupt: Call callback=0x2305e55e, arg=0x42020a70\ncst816s_poll_notify:\n\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: i2c transfer success\ncst816s_get_touch_data: DOWN: id=0, touch=0, x=200, y=26\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       200\ncst816s_get_touch_data:   y:       26\n```\n\nLoRaWAN Test App `lorawan_test` also tested OK with the patch.\n\n__TODO:__ Investigate the internals of the [BL602 I2C Driver](https://github.com/lupyuen/incubator-nuttx/blob/touch/arch/risc-v/src/bl602/bl602_i2c.c). Look for I2C Timing Issues or Race Conditions.\n\n__TODO:__ Probe the I2C Bus with a Logic Analyser. Watch for I2C Hardware issues.\n\n__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)\n\n__TODO:__ LoRaWAN Test App, LoRaWAN Library, SX1262 Library, NimBLE Porting Layer, SPI Test Driver should have their own flags for logging\n\n__TODO:__ Move CST816S Interrupt Handler to [BL602 GPIO Expander](https://github.com/lupyuen/bl602_expander)\n\n__TODO:__ Implement SPI DMA on NuttX so that the touchscreen feels less laggy\n\n__TODO:__ [Add a button](https://docs.lvgl.io/7.11/get-started/quick-overview.html#button-with-label) and a message box to the [LVGL Test App `lvgltest`](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c#L110-L198) to demo the touchscreen\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flupyuen%2Fcst816s-nuttx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flupyuen%2Fcst816s-nuttx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flupyuen%2Fcst816s-nuttx/lists"}