{"id":20316152,"url":"https://github.com/lupyuen/bl602_expander","last_synced_at":"2026-06-04T02:31:19.217Z","repository":{"id":87766620,"uuid":"480615775","full_name":"lupyuen/bl602_expander","owner":"lupyuen","description":"GPIO Expander for BL602 / BL604 on Apache NuttX RTOS","archived":false,"fork":false,"pushed_at":"2022-05-01T07:46:58.000Z","size":247,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-01-14T12:53:47.734Z","etag":null,"topics":["bl602","bl604","gpio","nuttx","pinecone","pinedio","riscv32"],"latest_commit_sha":null,"homepage":"https://lupyuen.github.io/articles/pinedio2#gpio-expander","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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":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-12T01:49:11.000Z","updated_at":"2022-04-25T00:07:31.000Z","dependencies_parsed_at":null,"dependency_job_id":"2041c1a1-bc03-480a-9cc0-c5c3dfe050ce","html_url":"https://github.com/lupyuen/bl602_expander","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%2Fbl602_expander","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lupyuen%2Fbl602_expander/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lupyuen%2Fbl602_expander/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lupyuen%2Fbl602_expander/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lupyuen","download_url":"https://codeload.github.com/lupyuen/bl602_expander/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241818902,"owners_count":20025210,"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","gpio","nuttx","pinecone","pinedio","riscv32"],"created_at":"2024-11-14T18:24:39.066Z","updated_at":"2025-12-03T02:03:23.044Z","avatar_url":"https://github.com/lupyuen.png","language":"C","funding_links":["https://github.com/sponsors/lupyuen","paypal.me/lupyuen"],"categories":[],"sub_categories":[],"readme":"![GPIO Expander for BL602 / BL604 on Apache NuttX RTOS](https://lupyuen.github.io/images/expander-title.jpg)\n\n# GPIO Expander for BL602 / BL604 on Apache NuttX RTOS\n\n[(Tested on PineDio Stack BL604)](https://lupyuen.github.io/articles/pinedio2)\n\nRead the article...\n\n-   [\"NuttX GPIO Expander for PineDio Stack BL604\"](https://lupyuen.github.io/articles/expander)\n\nPineDio Stack BL604 has an interesting problem on Apache NuttX RTOS... Too many GPIOs! Let's make it work.\n\nGPIO Expander exposes GPIOs 0 to 22 as `/dev/gpio0` to `/dev/gpio22`, for easier development of NuttX Apps for PineDio Stack BL604.\n\nGPIO Expander calls [`bl602_configgpio`](https://github.com/lupyuen/incubator-nuttx/blob/pinedio/arch/risc-v/src/bl602/bl602_gpio.c#L58-L140), [`bl602_gpioread`](https://github.com/lupyuen/incubator-nuttx/blob/pinedio/arch/risc-v/src/bl602/bl602_gpio.c#L218-L230) and [`bl602_gpiowrite`](https://github.com/lupyuen/incubator-nuttx/blob/pinedio/arch/risc-v/src/bl602/bl602_gpio.c#L197-L216) to configure / read / write GPIOs\n\nWarning: [BL602 EVB GPIO Driver](https://github.com/lupyuen/incubator-nuttx/blob/expander/boards/risc-v/bl602/bl602evb/src/bl602_gpio.c) will be disabled when we enable GPIO Expander.\n\n(Because GPIO Expander needs GPIO Lower Half which conflicts with BL602 EVB GPIO Driver)\n\nGPIO Expander verifies that the GPIO, SPI, I2C and UART Pins don't reuse the same GPIO.\n\nRobert Lipe has an excellent article that explains the current limitations of the BL602 EVB GPIO Driver (and why we need the GPIO Expander)...\n\n-   [\"Buttons on BL602 NuttX\"](https://www.robertlipe.com/buttons-on-bl602-nuttx/)\n\n# Status\n\n-   Tested OK with GPIO Interrupts from Touch Panel and LVGL Test App `lvgltest`\n\n    (With `IOEP_ATTACH` in `cst816s_register`)\n\n-   Tested OK with Push Button\n\n    (With `IOEP_ATTACH` in `bl602_bringup`)\n\n-   Tested OK with Push Button GPIO Command: `gpio -t 8 -w 1 /dev/gpio12`\n\n    (Comment out `IOEP_ATTACH` in `bl602_bringup`)\n\n-   Tested OK with LoRaWAN Test App `lorawan_test`\n\n    (With \"GPIO Informational Output\" logging disabled)\n\n-   SX1262 Library is now configured by Kconfig / menuconfig to access `/dev/gpio10`, `/dev/gpio15`, `/dev/gpio19` (instead of `dev/gpio0`, `/dev/gpio1`, `/dev/gpio2`). \n\n    In menuconfig: Library Routines → Semtech SX1262 Library\n\n    - SPI Test device path  \n    - Chip Select device path \n    - Busy device path\n    - DIO1 device path           \n\n-   Logging for SX1262 Library is now disabled by default and can be configured by Kconfig / menuconfig.\n\n    In menuconfig: Library Routines → Semtech SX1262 Library → Logging → Debugging\n\n-   Logging for SPI Test Driver has been moved from \"Enable Informational Debug Output\" to \"SPI Informational Output\"\n\n__TODO__: GPIO Expander will check that the SPI / I2C / UART Pin Functions are correctly defined (e.g. MISO vs MOSI)\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/ioexpander\ngit submodule add https://github.com/lupyuen/bl602_expander\nln -s bl602_expander/bl602_expander.c .\npopd\n\npushd nuttx/nuttx/include/nuttx/ioexpander\nln -s ../../../drivers/ioexpander/bl602_expander/bl602_expander.h .\npopd\n```\nNext update the Makefile and Kconfig...\n\n-   [See the modified Makefile and Kconfig](https://github.com/lupyuen/incubator-nuttx/commit/f72f7d546aa9b458b5cca66d090ac46ea178ca63)\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 PineDio Stack BL604: Configure the build for BL604\n./tools/configure.sh bl602evb:pinedio\n\n## For BL602: Configure the build for BL602\n./tools/configure.sh bl602evb:nsh\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 BL602 GPIO Expander under \"Device Drivers → IO Expander/GPIO Support → Enable IO Expander Support\".\n\nSet \"Number of pins\" to 23.\n\nEnable \"GPIO Lower Half\".\n\nEdit the function `bl602_bringup` in this file...\n\n```text\nnuttx/boards/risc-v/bl602/bl602evb/src/bl602_bringup.c\n```\n\nAnd call `bl602_expander_initialize` to initialise our driver, just after `bl602_gpio_initialize`:\n\nhttps://github.com/lupyuen/incubator-nuttx/blob/expander/boards/risc-v/bl602/bl602evb/src/bl602_bringup.c#L742-L768\n\n```c\n#ifdef CONFIG_IOEXPANDER_BL602_EXPANDER\n#include \u003cnuttx/ioexpander/gpio.h\u003e\n#include \u003cnuttx/ioexpander/bl602_expander.h\u003e\nFAR struct ioexpander_dev_s *bl602_expander = NULL;\n#endif /* CONFIG_IOEXPANDER_BL602_EXPANDER */\n...\nint bl602_bringup(void) {\n  ...\n  /* Existing Code */\n\n#if defined(CONFIG_DEV_GPIO) \u0026\u0026 !defined(CONFIG_GPIO_LOWER_HALF)\n  ret = bl602_gpio_initialize();\n  if (ret \u003c 0)\n    {\n      syslog(LOG_ERR, \"Failed to initialize GPIO Driver: %d\\n\", ret);\n      return ret;\n    }\n#endif\n\n  /* New Code */\n\n#ifdef CONFIG_IOEXPANDER_BL602_EXPANDER\n  /* Must load BL602 GPIO Expander before other drivers */\n\n  bl602_expander = bl602_expander_initialize(\n    bl602_gpio_inputs,\n    sizeof(bl602_gpio_inputs) / sizeof(bl602_gpio_inputs[0]),\n    bl602_gpio_outputs,\n    sizeof(bl602_gpio_outputs) / sizeof(bl602_gpio_outputs[0]),\n    bl602_gpio_interrupts,\n    sizeof(bl602_gpio_interrupts) / sizeof(bl602_gpio_interrupts[0]),\n    bl602_other_pins,\n    sizeof(bl602_other_pins) / sizeof(bl602_other_pins[0]));\n  if (bl602_expander == NULL)\n    {\n      syslog(LOG_ERR, \"Failed to initialize GPIO Expander\\n\");\n      return -ENOMEM;\n    }\n#endif /* CONFIG_IOEXPANDER_BL602_EXPANDER */\n```\n\nTo validate the GPIOs at startup, all GPIOs shall be listed in `bl602_gpio_inputs`, `bl602_gpio_outputs`, `bl602_gpio_interrupts` and `bl602_other_pins`...\n\n```c\n#ifdef CONFIG_IOEXPANDER_BL602_EXPANDER\n/* GPIO Input Pins for BL602 GPIO Expander */\n\nstatic const gpio_pinset_t bl602_gpio_inputs[] =\n{\n#ifdef BOARD_SX1262_BUSY\n  BOARD_SX1262_BUSY,\n#endif  /* BOARD_SX1262_BUSY */\n...\n};\n\n/* GPIO Output Pins for BL602 GPIO Expander */\n\nstatic const gpio_pinset_t bl602_gpio_outputs[] =\n{\n#ifdef BOARD_LCD_CS\n  BOARD_LCD_CS,\n#endif  /* BOARD_LCD_CS */\n...\n};\n\n/* GPIO Interrupt Pins for BL602 GPIO Expander */\n\nstatic const gpio_pinset_t bl602_gpio_interrupts[] =\n{\n#ifdef BOARD_TOUCH_INT\n  BOARD_TOUCH_INT,\n#endif  /* BOARD_TOUCH_INT */\n...\n};\n\n/* Other Pins for BL602 GPIO Expander (For Validation Only) */\n\nstatic const gpio_pinset_t bl602_other_pins[] =\n{\n#ifdef BOARD_UART_0_RX_PIN\n  BOARD_UART_0_RX_PIN,\n#endif  /* BOARD_UART_0_RX_PIN */\n...\n};\n#endif  /* CONFIG_IOEXPANDER_BL602_EXPANDER */\n```\n\n[(Source)](https://github.com/lupyuen/incubator-nuttx/blob/expander/boards/risc-v/bl602/bl602evb/src/bl602_bringup.c#L126-L222)\n\nWe must load the GPIO Expander before other drivers (e.g. CST816S Touch Panel), because GPIO Expander provides GPIO functions for the drivers.\n\nWe need to disable BL602 GPIO Driver when we enable GPIO Expander, because GPIO Expander needs GPIO Lower Half which can't coexist with BL602 GPIO Driver:\n\n```c\n/* Add CONFIG_GPIO_LOWER_HALF */\n#if defined(CONFIG_DEV_GPIO) \u0026\u0026 !defined(CONFIG_GPIO_LOWER_HALF)\n  ret = bl602_gpio_initialize();\n```\n\n[(Source)](https://github.com/lupyuen/incubator-nuttx/blob/expander/boards/risc-v/bl602/bl602evb/src/bl602_bringup.c#L646-L653)\n\n## Push Button Interrupt\n\nTo handle the GPIO Interrupt that's triggered when we press the Push Button...\n\n```c\n#include \u003cnuttx/ioexpander/gpio.h\u003e\n#include \u003cnuttx/ioexpander/bl602_expander.h\u003e\n...\n/* Get the Push Button Pinset and GPIO */\n\ngpio_pinset_t pinset = BOARD_BUTTON_INT;\nuint8_t gpio_pin = (pinset \u0026 GPIO_PIN_MASK) \u003e\u003e GPIO_PIN_SHIFT;\n\n/* Configure GPIO interrupt to be triggered on falling edge */\n\nDEBUGASSERT(bl602_expander != NULL);\nIOEXP_SETOPTION(bl602_expander, gpio_pin, IOEXPANDER_OPTION_INTCFG,\n                (FAR void *)IOEXPANDER_VAL_FALLING);\n\n/* Attach GPIO interrupt handler */\n\nvoid *handle = IOEP_ATTACH(bl602_expander,\n                           (ioe_pinset_t)1 \u003c\u003c gpio_pin,\n                           button_isr_handler,\n                           NULL);  //  TODO: Set the callback argument\nDEBUGASSERT(handle != NULL);\n```\n\n[(Source)](https://github.com/lupyuen/incubator-nuttx/blob/2982b3a99057c5935ca9150b9f0f1da3565c6061/boards/risc-v/bl602/bl602evb/src/bl602_bringup.c#L696-L704)\n\nThe Button Interrupt Handler `button_isr_handler` is defined as...\n\n```c\nstatic int button_isr_handler(FAR struct ioexpander_dev_s *dev,\n                              ioe_pinset_t pinset, FAR void *arg)\n{\n  gpioinfo(\"Button Pressed\\n\");\n  return 0;\n}\n```\n\n[(Source)](https://github.com/lupyuen/incubator-nuttx/blob/2982b3a99057c5935ca9150b9f0f1da3565c6061/boards/risc-v/bl602/bl602evb/src/bl602_bringup.c#L1038-L1044)\n\nLet's talk about how we created the BL602 GPIO Expander...\n\n# BL602 EVB Limitations\n\nThe NuttX GPIO Driver for BL602 EVB supports one GPIO Input, one GPIO Output and one GPIO Interrupt ... And names them sequentially: \"/dev/gpio0\", \"/dev/gpio1\", \"/dev/gpio2\"\n\n-   [BL602 EVB GPIO Driver](https://github.com/lupyuen/incubator-nuttx/blob/expander/boards/risc-v/bl602/bl602evb/src/bl602_gpio.c#L537-L607)\n\nWhich can be super confusing because \"/dev/gpio0\" doesn't actually map to BL602 GPIO Pin 0.\n\n[(\"/dev/gpio0\" maps to BL602 GPIO Pin 10)](https://github.com/lupyuen/incubator-nuttx/blob/expander/boards/risc-v/bl602/bl602evb/include/board.h#L49-L52)\n\n[(\"/dev/gpio1\" maps to BL602 GPIO Pin 15)](https://github.com/lupyuen/incubator-nuttx/blob/expander/boards/risc-v/bl602/bl602evb/include/board.h#L54-L58)\n\n[(\"/dev/gpio2\" maps to BL602 GPIO Pin 19)](https://github.com/lupyuen/incubator-nuttx/blob/expander/boards/risc-v/bl602/bl602evb/include/board.h#L59-L63)\n\nWhat happens when we try to support 23 GPIOs on PineDio Stack BL604? Yep the GPIO Names will look really messy on NuttX.\n\nAll 23 GPIOs on PineDio Stack BL604 are wired up. Let's simplify NuttX and name the GPIOs as \"/dev/gpio0\" to \"/dev/gpio22\".\n\n-   [PineDio Stack GPIO Assignment](https://lupyuen.github.io/articles/pinedio2#appendix-gpio-assignment)\n\n(So that \"/dev/gpioN\" will map to BL602 GPIO Pin N)\n\nEasier for devs to create new NuttX Drivers!\n\n# GPIO Expander\n\nNuttX lets us create I/O Expander Drivers that will handle many GPIOs (Input / Output / Interrupt). Perfect for PineDio Stack BL604!\n\n-   [NuttX I/O Expander Driver Interface](https://github.com/apache/incubator-nuttx/blob/master/include/nuttx/ioexpander/ioexpander.h)\n\nApache NuttX RTOS helpfully provides a Skeleton Driver for I/O Expander. Let's flesh it out for PineDio Stack BL604's GPIO Expander...\n\n-   [Skeleton Driver for I/O Expander](https://github.com/apache/incubator-nuttx/blob/master/drivers/ioexpander/skeleton.c)\n\nOther microcontrollers might also need a GPIO Expander. Like CH32V307, which has 80 GPIOs (!)\n\n[(See this)](https://github.com/openwch/ch32v307)\n\n## GPIO Expander Operations\n\nOur NuttX GPIO Expander implements the operations to: 1️⃣ Config / Read / Write GPIOs 2️⃣ Attach / Detach GPIO Interrupt Handlers 3️⃣ Handle GPIO Interrupts...\n\n```c\n/* I/O Expander Operations */\n\nstatic const struct ioexpander_ops_s g_bl602_expander_ops =\n{\n  bl602_expander_direction,\n  bl602_expander_option,\n  bl602_expander_writepin,\n  bl602_expander_readpin,\n  bl602_expander_readbuf\n#ifdef CONFIG_IOEXPANDER_MULTIPIN\n  , bl602_expander_multiwritepin\n  , bl602_expander_multireadpin\n  , bl602_expander_multireadbuf\n#endif\n#ifdef CONFIG_IOEXPANDER_INT_ENABLE\n  , bl602_expander_attach\n  , bl602_expander_detach\n#endif\n};\n```\n\n[(Source)](https://github.com/lupyuen/bl602_expander/blob/main/bl602_expander.c#L141-L159)\n\nWe'll cover these operations below.\n\n# GPIO Interrupts\n\nGPIO Interrupt Handling gets tricky for PineDio Stack BL604: All GPIO Interrupts are multiplexed into a single IRQ. Our GPIO Expander can help. \n\nHere's the existing code for BL602 EVB that attaches a GPIO Interrupt Handler...\n\n```c\nstatic int gpint_attach(struct gpio_dev_s *dev, pin_interrupt_t callback)\n{\n  struct bl602_gpint_dev_s *bl602xgpint =\n    (struct bl602_gpint_dev_s *)dev;\n\n  uint8_t gpio_pin =\n    (g_gpiointinputs[bl602xgpint-\u003ebl602gpio.id] \u0026 GPIO_PIN_MASK) \u003e\u003e\n    GPIO_PIN_SHIFT;\n  gpioinfo(\"Attaching the callback\\n\");\n\n  /* Make sure the interrupt is disabled */\n\n  bl602xgpint-\u003ecallback = callback;\n  bl602_gpio_intmask(gpio_pin, 1);\n\n  irq_attach(BL602_IRQ_GPIO_INT0, bl602_gpio_interrupt, dev);\n  bl602_gpio_intmask(gpio_pin, 0);\n\n  gpioinfo(\"Attach %p\\n\", callback);\n  return OK;\n}\n```\n\n[(Source)](https://github.com/lupyuen/incubator-nuttx/blob/expander/boards/risc-v/bl602/bl602evb/src/bl602_gpio.c)\n\nNote that all GPIO Interrupts are multiplexed into a single IRQ: `BL602_IRQ_GPIO_INT0`\n\nWhen handling GPIO Interrupts, our GPIO Expander needs to demultiplex the `BL602_IRQ_GPIO_INT0` IRQ into multiple GPIO Interrupts.\n\nAs noted (eloquently) by Robert Lipe, attaching a BL602 GPIO Interrupt Handler is hard (because our stars are misaligned)...\n\n-   [\"Buttons on BL602 NuttX\"](https://www.robertlipe.com/buttons-on-bl602-nuttx/)\n\nLet's fix this with our GPIO Expander for Apache NuttX RTOS.\n\n## Push Button Interrupt\n\nNow With BL602 GPIO Expander, here's how we handle the GPIO Interrupt that's triggered when we press the Push Button on PineDio Stack...\n\n```c\n#include \u003cnuttx/ioexpander/gpio.h\u003e\n#include \u003cnuttx/ioexpander/bl602_expander.h\u003e\n...\n//  Get the Push Button Pinset and GPIO\ngpio_pinset_t pinset = BOARD_BUTTON_INT;\nuint8_t gpio_pin = (pinset \u0026 GPIO_PIN_MASK) \u003e\u003e GPIO_PIN_SHIFT;\n\n//  Configure GPIO interrupt to be triggered on falling edge\nDEBUGASSERT(bl602_expander != NULL);\nIOEXP_SETOPTION(\n  bl602_expander,  //  BL602 GPIO Expander\n  gpio_pin,        //  GPIO Pin\n  IOEXPANDER_OPTION_INTCFG,            //  Configure interrupt trigger\n  (FAR void *) IOEXPANDER_VAL_FALLING  //  Trigger on falling edge\n);\n\n//  Attach GPIO interrupt handler\nvoid *handle = IOEP_ATTACH(\n  bl602_expander,                //  BL602 GPIO Expander\n  (ioe_pinset_t) 1 \u003c\u003c gpio_pin,  //  GPIO Pin converted to Pinset\n  button_isr_handler,            //  GPIO Interrupt Handler\n  NULL                           //  TODO: Set the callback argument\n);\nDEBUGASSERT(handle != NULL);\n```\n\n[(Source)](https://github.com/lupyuen/incubator-nuttx/blob/2982b3a99057c5935ca9150b9f0f1da3565c6061/boards/risc-v/bl602/bl602evb/src/bl602_bringup.c#L696-L704)\n\nMuch easier now! The Button Interrupt Handler `button_isr_handler` is defined as...\n\n```c\nstatic int button_isr_handler(FAR struct ioexpander_dev_s *dev, ioe_pinset_t pinset, FAR void *arg) {\n  gpioinfo(\"Button Pressed\\n\");\n  return 0;\n}\n```\n\n[(Source)](https://github.com/lupyuen/incubator-nuttx/blob/2982b3a99057c5935ca9150b9f0f1da3565c6061/boards/risc-v/bl602/bl602evb/src/bl602_bringup.c#L1038-L1044)\n\nNote that the Button Interrupt Handler runs in the context of the Interrupt Handler. Be careful!\n\nAnother way to test the Push Button Interrupt is to use the GPIO Command...\n\n```text\nnsh\u003e gpio -t 8 -w 1 /dev/gpio12\nDriver: /dev/gpio12\ngplh_enable: pin12: Disabling callback=0 handle=0\ngplh_enable: WARNING: pin12: Already detached\nbl602_expander_option: pin=12, option=2, value=0x6\nbl602_expander_option: Rising edge: pin=12\nbl602_expander_set_intmod: gpio_pin=12, int_ctlmod=1, int_trgmod=1\ngplh_read: pin12: value=0x42021aef\nbl602_expander_readpin: pin=12, value=1\n  Interrupt pin: Value=1\ngplh_attach: pin12: callback=0x23060808\ngplh_enable: pin12: Enabling callback=0x23060808 handle=0\ngplh_enable: pin12: Attaching 0x23060808\nbl602_expander_attach: pinset=1000, callback=0x2305f4e2, arg=0x42020d40\nbl602_expander_attach: Attach callback for gpio=12, callback=0x2305f4e2, arg=0x42020d40\nbl602_expander_interrupt: Interrupt! context=0x42012db8, priv=0x4201d0f0\nbl602_expander_interrupt: Call gpio=12, callback=0x2305f4e2, arg=0x42020d40\ngplh_handler: pin12: pinset: c callback=0x23060808\ngplh_enable: pin12: Disabling callback=0x23060808 handle=0x4201d1a0\ngplh_enable: pin12: Detaching handle=0x4201d1a0\nbl602_expander_detach: Detach callback for gpio=12, callback=0x2305f4e2, arg=0x42020d40\ngplh_attach: pin12: callback=0\ngplh_read: pin12: value=0x42021aef\nbl602_expander_readpin: pin=12, value=1\n  Verify:        Value=1\n```\n\nBut this works only if we don't call `IOEP_ATTACH` to attach the Interrupt Handler.\n\n## Touch Panel Interrupt\n\nThe CST816S Driver for PineDio Stack's Touch Panel now calls BL602 GPIO Expander to attach the GPIO Interrupt Handler...\n\n```c\n//  Register the CST816S device (e.g. /dev/input0)\nint cst816s_register(FAR const char *devpath, FAR struct i2c_master_s *i2c_dev, uint8_t i2c_devaddr) {\n  ...\n  //  Configure GPIO interrupt to be triggered on falling edge\n  DEBUGASSERT(bl602_expander != NULL);\n  IOEXP_SETOPTION(\n    bl602_expander,  //  BL602 GPIO Expander\n    gpio_pin,        //  GPIO Pin\n    IOEXPANDER_OPTION_INTCFG,            //  Configure interrupt trigger\n    (FAR void *) IOEXPANDER_VAL_FALLING  //  Trigger on falling edge\n  );\n\n  //  Attach GPIO interrupt handler\n  handle = IOEP_ATTACH(\n    bl602_expander,                //  BL602 GPIO Expander\n    (ioe_pinset_t) 1 \u003c\u003c gpio_pin,  //  GPIO Pin converted to Pinset\n    cst816s_isr_handler,  //  GPIO Interrupt Handler\n    priv                  //  Callback argument\n  );\n  if (handle == NULL) {\n    kmm_free(priv);\n    ierr(\"Attach interrupt failed\\n\");\n    return -EIO;\n  }\n```\n\n[(Source)](https://github.com/lupyuen/cst816s-nuttx/blob/expander/cst816s.c#L661-L678)\n\nThe functions for configuring and handling GPIO Interrupts have been moved from the CST816S Driver to BL602 GPIO Expander.\n\n[(See this)](https://github.com/lupyuen/bl602_expander/blob/main/bl602_expander.c#L164-L325)\n\n## LoRa SX1262 Interrupt\n\nThe Semtech SX1262 LoRa Transceiver on PineDio Stack triggers a GPIO Interrupt (on pin DIO1) when a LoRa packet is transmitted or received.\n\nHere's the existing code that configures the GPIO Interrupt and attaches the Interrupt Handler...\n\n```c\n/// Init the GPIO Pins. Return 0 on success.\nstatic int init_gpio(void) {\n  ...\n  //  Open GPIO Interrupt for SX1262 DIO1 Pin\n  dio1 = open(DIO1_DEVPATH, O_RDWR);\n  assert(dio1 \u003e 0);\n\n  //  Get SX1262 DIO1 Pin Type\n  ret = ioctl(dio1, GPIOC_PINTYPE, (unsigned long)((uintptr_t)\u0026pintype));\n  assert(ret \u003e= 0);\n\n  //  Verify that SX1262 DIO1 Pin is GPIO Interrupt (not GPIO Input or GPIO Output)\n  assert(pintype == GPIO_INTERRUPT_PIN);\n\n  //  Change DIO1 Pin to Trigger GPIO Interrupt on Rising Edge\n  //  TODO: Crashes at ioexpander/gpio.c (line 544) because change failed apparently\n  ret = ioctl(dio1, GPIOC_SETPINTYPE, (unsigned long) GPIO_INTERRUPT_RISING_PIN);\n  assert(ret \u003e= 0);\n  ...\n  //  Start the Background Thread to process DIO1 interrupts\n  static pthread_t thread;\n  ret = pthread_create(\u0026thread, \u0026attr, process_dio1, 0);\n  assert(ret == 0);\n```\n\n[(Source)](https://github.com/lupyuen/lora-sx1262/blob/lorawan/src/sx126x-nuttx.c#L759-L815)\n\nThis code calls `ioctl()` in the User Space (instead of Kernel Space), so it works OK with BL602 GPIO Expander without modification.\n\n(Because `ioctl()` calls the GPIO Lower Half Driver, which is integrated with our BL602 GPIO Expander)\n\nFor PineDio Stack, we changed the definition of `DIO1_DEVPATH` to \"/dev/gpio19\"...\n\n```text\nCONFIG_LIBSX1262_SPI_DEVPATH=\"/dev/spitest0\"\nCONFIG_LIBSX1262_CS_DEVPATH=\"/dev/gpio15\"\nCONFIG_LIBSX1262_BUSY_DEVPATH=\"/dev/gpio10\"\nCONFIG_LIBSX1262_DIO1_DEVPATH=\"/dev/gpio19\"\n```\n\n[(Source)](https://github.com/lupyuen/incubator-nuttx/blob/expander/boards/risc-v/bl602/bl602evb/configs/pinedio/defconfig#L1141-L1144)\n\nFor backward compatibility with BL602 (which doesn't use GPIO Expander), we default `DIO1_DEVPATH` to \"/dev/gpio2\" if `DIO1_DEVPATH` isn't configured...\n\n```c\n#ifdef CONFIG_LIBSX1262_DIO1_DEVPATH\n#define DIO1_DEVPATH CONFIG_LIBSX1262_DIO1_DEVPATH\n#else\n#define DIO1_DEVPATH \"/dev/gpio2\"\n#endif  //  CONFIG_LIBSX1262_DIO1_DEVPATH\n```\n\n[(Source)](https://github.com/lupyuen/lora-sx1262/blob/lorawan/src/sx126x-nuttx.c#L40-L44)\n\n# Check Reused GPIOs\n\nTracking all 23 GPIOs used by PineDio Stack BL604 can get challenging... We might reuse GPIOs by mistake! Our BL602 GPIO Expander shall validate the GPIOs at startup.\n\nHere are the GPIOs currently defined for PineDio Stack...\n\n-   [`board.h`](https://github.com/lupyuen/incubator-nuttx/blob/expander/boards/risc-v/bl602/bl602evb/include/board.h#L61-L145)\n\nAt startup, GPIO Expander verifies that the GPIO, SPI, I2C and UART Pins don't reuse the same GPIO.\n\nIf GPIOs are reused in `board.h`...\n\n```c\n#define BOARD_SPI_CLK  (GPIO_INPUT | GPIO_PULLUP | GPIO_FUNC_SPI | GPIO_PIN11)\n...\n#define BOARD_BUTTON_INT (GPIO_INPUT | GPIO_FLOAT | GPIO_FUNC_SWGPIO | GPIO_PIN11)\n```\n\nThen GPIO Expander will halt with an error at startup...\n\n```text\nbl602_expander_option: pin=11, option=2, value=0xe\nbl602_expander_option: Unsupported interrupt both edge: pin=11\ngplh_enable: pin11: Disabling callback=0 handle=0\ngplh_enable: WARNING: pin11: Already detached\ngpio_pin_register: Registering /dev/gpio11\n...\nbl602_expander_initialize: ERROR: GPIO pin 11 is already in use\nup_assert: Assertion failed at file:mm_heap/mm_free.c line: 102 task: nsh_main\n```\n\nWe implement this by listing all GPIOs in `bl602_gpio_inputs`, `bl602_gpio_outputs`, `bl602_gpio_interrupts` and `bl602_other_pins`...\n\n```c\n#ifdef CONFIG_IOEXPANDER_BL602_EXPANDER\n//  GPIO Input Pins for BL602 GPIO Expander\nstatic const gpio_pinset_t bl602_gpio_inputs[] =\n{\n#ifdef BOARD_SX1262_BUSY\n  BOARD_SX1262_BUSY,\n#endif  /* BOARD_SX1262_BUSY */\n...\n};\n\n//  GPIO Output Pins for BL602 GPIO Expander\nstatic const gpio_pinset_t bl602_gpio_outputs[] =\n{\n#ifdef BOARD_LCD_CS\n  BOARD_LCD_CS,\n#endif  /* BOARD_LCD_CS */\n...\n};\n\n//  GPIO Interrupt Pins for BL602 GPIO Expander\nstatic const gpio_pinset_t bl602_gpio_interrupts[] =\n{\n#ifdef BOARD_TOUCH_INT\n  BOARD_TOUCH_INT,\n#endif  /* BOARD_TOUCH_INT */\n...\n};\n\n//  Other Pins for BL602 GPIO Expander (For Validation Only)\nstatic const gpio_pinset_t bl602_other_pins[] =\n{\n#ifdef BOARD_UART_0_RX_PIN\n  BOARD_UART_0_RX_PIN,\n#endif  /* BOARD_UART_0_RX_PIN */\n...\n};\n#endif  /* CONFIG_IOEXPANDER_BL602_EXPANDER */\n```\n\n[(Source)](https://github.com/lupyuen/incubator-nuttx/blob/expander/boards/risc-v/bl602/bl602evb/src/bl602_bringup.c#L126-L222)\n\nAt startup, GPIO Expander verifies that the GPIOs are not reused...\n\n```c\nFAR struct ioexpander_dev_s *bl602_expander_initialize(\n  const gpio_pinset_t *gpio_inputs,     uint8_t gpio_input_count,\n  const gpio_pinset_t *gpio_outputs,    uint8_t gpio_output_count,\n  const gpio_pinset_t *gpio_interrupts, uint8_t gpio_interrupt_count,\n  const gpio_pinset_t *other_pins,      uint8_t other_pin_count) {\n  ...\n  //  Mark the GPIOs in use\n  bool gpio_is_used[CONFIG_IOEXPANDER_NPINS];\n  memset(gpio_is_used, 0, sizeof(gpio_is_used));\n\n  //  Validate the GPIO Inputs\n  for (i = 0; i \u003c gpio_input_count; i++) {\n    //  Get GPIO Pinset and GPIO Pin Number\n    gpio_pinset_t pinset = gpio_inputs[i];\n    uint8_t gpio_pin = (pinset \u0026 GPIO_PIN_MASK) \u003e\u003e GPIO_PIN_SHIFT;\n\n    //  Check that the GPIO is not in use\n    DEBUGASSERT(gpio_pin \u003c CONFIG_IOEXPANDER_NPINS);\n    if (gpio_is_used[gpio_pin]) {\n      gpioerr(\"ERROR: GPIO pin %d is already in use\\n\", gpio_pin);\n      return NULL;\n    }\n    gpio_is_used[gpio_pin] = true;\n  }\n\n  //  Omitted: Validate the GPIO Outputs, GPIO Interrupts and Other Pins\n```\n\n[(Source)](https://github.com/lupyuen/bl602_expander/blob/main/bl602_expander.c#L958-L1123)\n\n# Validate Pin Functions\n\nIn future, our BL602 GPIO Expander will validate that the SPI / I2C / UART Pin Functions are correctly assigned to the GPIO Pin Numbers...\n\n-   [BL602 Reference Manual (Table 3.1 \"Pin Description\", Page 26)](https://github.com/bouffalolab/bl_docs/blob/main/BL602_RM/en/BL602_BL604_RM_1.2_en.pdf)\n\nFor example: SPI MISO must be either GPIO 0, 4, 8, 12, 16 or 20.\n\nAny other GPIO Pin for SPI MISO will be disallowed by our BL602 GPIO Expander. (And fail at startup)\n\n_But the BL602 Pinset only tells us the Function Group (like SPI), not the specific Pin Function (like MISO)?_\n\nYeah we might have to make the Pin Functions position-dependent. So SPI Pins will always be listed in this sequence: CS, MOSI, MISO, then CLK.\n\nHere's how it might look...\n\n```c\n/* Other Pins for BL602 GPIO Expander (For Validation Only) */\n\nstatic const gpio_pinset_t bl602_other_pins[] =\n{\n#ifdef BOARD_UART_0_RX_PIN\n  RX_TX\n  (\n    BOARD_UART_0_RX_PIN,\n    BOARD_UART_0_TX_PIN\n  ),\n#endif  /* BOARD_UART_0_RX_PIN */\n\n#ifdef BOARD_UART_1_RX_PIN\n  RX_TX\n  (\n    BOARD_UART_1_RX_PIN,\n    BOARD_UART_1_TX_PIN\n  ),\n#endif  /* BOARD_UART_1_RX_PIN */\n\n#ifdef BOARD_PWM_CH0_PIN\n  CH(\n    BOARD_PWM_CH0_PIN\n  ),\n#endif  /* BOARD_PWM_CH0_PIN */\n...\n#ifdef BOARD_I2C_SCL\n  SCL_SDA\n  (\n    BOARD_I2C_SCL, \n    BOARD_I2C_SDA \n  ),\n#endif  /* BOARD_I2C_SCL */\n\n#ifdef BOARD_SPI_CS\n  CS_MOSI_MISO_CLK\n  (\n    BOARD_SPI_CS, \n    BOARD_SPI_MOSI, \n    BOARD_SPI_MISO, \n    BOARD_SPI_CLK\n  ),\n#endif  /* BOARD_SPI_CS */\n};\n```\n\n(Which looks neater with the clustering by Function Group)\n\nThe macros are simple passthroughs...\n\n```c\n#define CH(ch)            ch\n#define RX_TX(rx, tx)     rx,  tx\n#define SCL_SDA(scl, sda) scl, sda\n#define CS_MOSI_MISO_CLK(cs, mosi, miso, clk) cs, mosi, miso, clk\n```\n\nAt startup, GPIO Expander iterates through the pins and discovers that `BOARD_SPI_MISO` is the third pin (MISO) of the SPI Function Group. So it verifies that it's either GPIO 0, 4, 8, 12, 16 or 20.\n\nAre devs OK with this? Lemme know what you think!\n\n_Can we validate the Pin Functions at compile-time?_\n\nPossibly. We can enumerate all valid combinations of Pin Functions and Pin Numbers...\n\n```c\n//  MISO can be either GPIO 0, 4, 8, 12, 16 or 20\n#define SPI_MISO_PIN0  (GPIO_INPUT | GPIO_PULLUP | GPIO_FUNC_SPI | GPIO_PIN0)\n#define SPI_MISO_PIN4  (GPIO_INPUT | GPIO_PULLUP | GPIO_FUNC_SPI | GPIO_PIN4)\n#define SPI_MISO_PIN8  (GPIO_INPUT | GPIO_PULLUP | GPIO_FUNC_SPI | GPIO_PIN8)\n#define SPI_MISO_PIN12 (GPIO_INPUT | GPIO_PULLUP | GPIO_FUNC_SPI | GPIO_PIN12)\n#define SPI_MISO_PIN16 (GPIO_INPUT | GPIO_PULLUP | GPIO_FUNC_SPI | GPIO_PIN16)\n#define SPI_MISO_PIN20 (GPIO_INPUT | GPIO_PULLUP | GPIO_FUNC_SPI | GPIO_PIN20)\n```\n\nAnd we select the desired combination for each pin...\n\n```c\n//  Select GPIO0 as MISO\n#define BOARD_SPI_MISO SPI_MISO_PIN0\n```\n\nTo check whether the Pin Numbers are unique, we would still need GPIO Expander to do this at runtime.\n\n_But shouldn't the pins be defined in Kconfig / menuconfig?_\n\nPerhaps. NuttX on ESP32 uses Kconfig / menuconfig to define the pins. [(See this)](https://github.com/apache/incubator-nuttx/blob/master/arch/xtensa/src/esp32/Kconfig#L938-L984)\n\nThen we would need GPIO Expander to validate the Pin Functions at runtime.\n\n[__@Ralim__](https://mastodon.social/@Ralim/108201458447291513) has an interesting suggestion...\n\n\u003e If each pin can only be used once, could we flip the arrignment matrix and instead have it always have an entry for each pin, which is either a selected value or hi-z by default; then use kconfig rules to prevent collisions ?\n\nWhich raises the question: Shouldn't we do the same for NuttX on ESP32? What about other NuttX platforms? 🤔\n\n# Configure GPIO\n\nAt startup our BL602 GPIO Expander configures the GPIO Input / Output / Interrupt Pins by calling [`bl602_configgpio`](https://github.com/lupyuen/incubator-nuttx/blob/pinedio/arch/risc-v/src/bl602/bl602_gpio.c#L58-L140) and `gpio_lower_half` (which registers \"/dev/gpioN\")...\n\n```c\n//  Initialise the BL602 GPIO Expander\nFAR struct ioexpander_dev_s *bl602_expander_initialize(\n  const gpio_pinset_t *gpio_inputs,\n  uint8_t gpio_input_count,\n  const gpio_pinset_t *gpio_outputs,\n  uint8_t gpio_output_count,\n  const gpio_pinset_t *gpio_interrupts,\n  uint8_t gpio_interrupt_count,\n  const gpio_pinset_t *other_pins,\n  uint8_t other_pin_count)\n{\n  int i;\n  int ret;\n  uint8_t pin;\n  bool gpio_is_used[CONFIG_IOEXPANDER_NPINS];\n  FAR struct bl602_expander_dev_s *priv;\n\n  DEBUGASSERT(gpio_input_count + gpio_output_count + gpio_interrupt_count +\n    other_pin_count \u003c= CONFIG_IOEXPANDER_NPINS);\n\n#ifdef CONFIG_BL602_EXPANDER_MULTIPLE\n  /* Allocate the device state structure */\n\n  priv = (FAR struct bl602_expander_dev_s *)kmm_zalloc(sizeof(struct bl602_expander_dev_s));\n  if (!priv)\n    {\n      gpioerr(\"ERROR: Failed to allocate driver instance\\n\");\n      return NULL;\n    }\n#else\n  /* Use the one-and-only I/O Expander driver instance */\n\n  priv = \u0026g_skel;\n#endif\n\n  /* Initialize the device state structure */\n\n  priv-\u003edev.ops = \u0026g_bl602_expander_ops;\n  nxsem_init(\u0026priv-\u003eexclsem, 0, 1);\n\n#ifdef CONFIG_IOEXPANDER_INT_ENABLE\n  /* Disable GPIO interrupts */\n\n  ret = bl602_expander_irq_enable(false);\n  if (ret \u003c 0)\n    {\n      gpioerr(\"ERROR: Failed to disable GPIO interrupts\\n\");\n      kmm_free(priv);\n      return NULL;\n    }\n\n  /* Disable interrupts for all GPIO Pins */\n\n  for (pin = 0; pin \u003c CONFIG_IOEXPANDER_NPINS; pin++)\n    {\n      bl602_expander_intmask(pin, 1);\n    }\n\n  /* Attach the I/O expander interrupt handler and enable interrupts */\n\n  irq_attach(BL602_IRQ_GPIO_INT0, bl602_expander_interrupt, priv);\n\n  ret = bl602_expander_irq_enable(true);\n  if (ret \u003c 0)\n    {\n      gpioerr(\"ERROR: Failed to enable GPIO interrupts\\n\");\n      kmm_free(priv);\n      return NULL;\n    }\n#endif\n\n  /* Mark the GPIOs in use */\n\n  memset(gpio_is_used, 0, sizeof(gpio_is_used));\n\n  /* Configure and register the GPIO Inputs */\n\n  for (i = 0; i \u003c gpio_input_count; i++)\n    {\n      gpio_pinset_t pinset = gpio_inputs[i];\n      uint8_t gpio_pin = (pinset \u0026 GPIO_PIN_MASK) \u003e\u003e GPIO_PIN_SHIFT;\n\n      DEBUGASSERT(gpio_pin \u003c CONFIG_IOEXPANDER_NPINS);\n      if (gpio_is_used[gpio_pin])\n        {\n          gpioerr(\"ERROR: GPIO pin %d is already in use\\n\", gpio_pin);\n          kmm_free(priv);\n          return NULL;\n        }\n      gpio_is_used[gpio_pin] = true;\n\n      ret = bl602_configgpio(pinset);\n      DEBUGASSERT(ret == OK);\n      gpio_lower_half(\u0026priv-\u003edev, gpio_pin, GPIO_INPUT_PIN, gpio_pin);\n    }\n\n  /* Configure and register the GPIO Outputs */\n\n  for (i = 0; i \u003c gpio_output_count; i++)\n    {\n      gpio_pinset_t pinset = gpio_outputs[i];\n      uint8_t gpio_pin = (pinset \u0026 GPIO_PIN_MASK) \u003e\u003e GPIO_PIN_SHIFT;\n\n      DEBUGASSERT(gpio_pin \u003c CONFIG_IOEXPANDER_NPINS);\n      if (gpio_is_used[gpio_pin])\n        {\n          gpioerr(\"ERROR: GPIO pin %d is already in use\\n\", gpio_pin);\n          kmm_free(priv);\n          return NULL;\n        }\n      gpio_is_used[gpio_pin] = true;\n\n      ret = bl602_configgpio(pinset);\n      DEBUGASSERT(ret == OK);\n      gpio_lower_half(\u0026priv-\u003edev, gpio_pin, GPIO_OUTPUT_PIN, gpio_pin);\n    }\n\n  /* Configure and register the GPIO Interrupts */\n\n  for (i = 0; i \u003c gpio_interrupt_count; i++)\n    {\n      gpio_pinset_t pinset = gpio_interrupts[i];\n      uint8_t gpio_pin = (pinset \u0026 GPIO_PIN_MASK) \u003e\u003e GPIO_PIN_SHIFT;\n\n      DEBUGASSERT(gpio_pin \u003c CONFIG_IOEXPANDER_NPINS);\n      if (gpio_is_used[gpio_pin])\n        {\n          gpioerr(\"ERROR: GPIO pin %d is already in use\\n\", gpio_pin);\n          kmm_free(priv);\n          return NULL;\n        }\n      gpio_is_used[gpio_pin] = true;\n\n      ret = bl602_configgpio(pinset);\n      DEBUGASSERT(ret == OK);\n      gpio_lower_half(\u0026priv-\u003edev, gpio_pin, GPIO_INTERRUPT_PIN, gpio_pin);\n    }\n\n  /* Validate the other pins (I2C, SPI, etc) */\n\n  for (i = 0; i \u003c other_pin_count; i++)\n    {\n      gpio_pinset_t pinset = other_pins[i];\n      uint8_t gpio_pin = (pinset \u0026 GPIO_PIN_MASK) \u003e\u003e GPIO_PIN_SHIFT;\n\n      DEBUGASSERT(gpio_pin \u003c CONFIG_IOEXPANDER_NPINS);\n      if (gpio_is_used[gpio_pin])\n        {\n          gpioerr(\"ERROR: GPIO pin %d is already in use\\n\", gpio_pin);\n          kmm_free(priv);\n          return NULL;\n        }\n      gpio_is_used[gpio_pin] = true;\n    }\n\n  /* TODO: Validate the Pin Functions (e.g. MISO vs MOSI) */\n\n  return \u0026priv-\u003edev;\n}\n```\n\n[(Source)](https://github.com/lupyuen/bl602_expander/blob/main/bl602_expander.c#L956-L1121)\n\n[(`bl602_expander_intmask` is defined here)](https://github.com/lupyuen/bl602_expander/blob/main/bl602_expander.c#L164-L197)\n\n[(`bl602_expander_irq_enable` is defined here)](https://github.com/lupyuen/bl602_expander/blob/main/bl602_expander.c#L301-L325)\n\n# Set GPIO Options\n\nOur GPIO Expander will configure the GPIO Interrupts: Rising Edge Trigger vs Falling Edge Trigger...\n\n```c\n//  Set GPIO Options\nstatic int bl602_expander_option(FAR struct ioexpander_dev_s *dev, uint8_t pin,\n                       int opt, FAR void *value)\n{\n  FAR struct bl602_expander_dev_s *priv = (FAR struct bl602_expander_dev_s *)dev;\n  int ret = -ENOSYS;\n\n  gpioinfo(\"pin=%u, option=%u, value=%p\\n\", pin, opt, value);\n\n  DEBUGASSERT(priv != NULL);\n\n  /* Get exclusive access to the I/O Expander */\n\n  ret = bl602_expander_lock(priv);\n  if (ret \u003c 0)\n    {\n      return ret;\n    }\n\n  /* Handle each option */\n\n  switch(opt)\n    {\n      case IOEXPANDER_OPTION_INTCFG: /* Interrupt Trigger */\n        {\n          switch((uint32_t)value)\n            {\n              case IOEXPANDER_VAL_RISING: /* Rising Edge */\n                {\n                  gpioinfo(\"Rising edge: pin=%u\\n\", pin);\n                  bl602_expander_set_intmod(pin, 1, GLB_GPIO_INT_TRIG_POS_PULSE);\n                  break;\n                }\n\n              case IOEXPANDER_VAL_FALLING: /* Falling Edge */\n                {\n                  gpioinfo(\"Falling edge: pin=%u\\n\", pin);\n                  bl602_expander_set_intmod(pin, 1, GLB_GPIO_INT_TRIG_NEG_PULSE);\n                  break;\n                }\n\n              case IOEXPANDER_VAL_BOTH: /* Both Edge (Unimplemented) */\n                {\n                  gpioinfo(\"WARNING: Unimplemented interrupt both edge: pin=%u\\n\", pin);\n                  break;\n                }\n\n              case IOEXPANDER_VAL_DISABLE: /* Disable (Unimplemented) */\n                {\n                  gpioinfo(\"WARNING: Unimplemented disable interrupt, use detach instead: pin=%u\\n\", pin);\n                  break;\n                }\n\n              default: /* Unsupported Interrupt */\n                {\n                  gpioerr(\"ERROR: Unsupported interrupt: %d, pin=%u\\n\", value, pin);\n                  ret = -EINVAL;\n                  break;\n                }\n            }\n          break;\n        }\n\n      default: /* Unsupported Option */\n        {\n          gpioerr(\"ERROR: Unsupported option: %d, pin=%u\\n\", opt, pin);\n          ret = -ENOSYS;\n        }\n    }\n\n  /* Unlock the I/O Expander */\n\n  bl602_expander_unlock(priv);\n  return ret;\n}\n```\n\n[(Source)](https://github.com/lupyuen/bl602_expander/blob/main/bl602_expander.c#L456-L548)\n\n[(`bl602_expander_set_intmod` is defined here)](https://github.com/lupyuen/bl602_expander/blob/main/bl602_expander.c#L198-L246)\n\n# Read GPIO\n\nOur GPIO Expander calls the BL602 GPIO Driver to read GPIO Inputs...\n\n```c\n//  Read the GPIO Input Pin\nstatic int bl602_expander_readpin(FAR struct ioexpander_dev_s *dev, \n                                  uint8_t pin,\n                                  FAR bool *value)\n{\n  FAR struct bl602_expander_dev_s *priv = (FAR struct bl602_expander_dev_s *)dev;\n  int ret;\n\n  DEBUGASSERT(priv != NULL \u0026\u0026 pin \u003c CONFIG_IOEXPANDER_NPINS \u0026\u0026\n              value != NULL);\n\n  /* Get exclusive access to the I/O Expander */\n\n  ret = bl602_expander_lock(priv);\n  if (ret \u003c 0)\n    {\n      return ret;\n    }\n\n  /* Read the pin value. Warning: Pin Number passed as BL602 Pinset */\n\n  *value = bl602_gpioread(pin \u003c\u003c GPIO_PIN_SHIFT);\n\n  /* Unlock the I/O Expander */\n\n  bl602_expander_unlock(priv);\n  gpioinfo(\"pin=%u, value=%u\\n\", pin, *value);\n  return ret;\n}\n```\n\n[(Source)](https://github.com/lupyuen/bl602_expander/blob/main/bl602_expander.c#L596-L642)\n\n[(`bl602_gpioread` comes from the BL602 GPIO Driver)](https://github.com/lupyuen/incubator-nuttx/blob/pinedio/arch/risc-v/src/bl602/bl602_gpio.c#L218-L230)\n\n# Write GPIO\n\nOur GPIO Expander calls the BL602 GPIO Driver to write GPIO Outputs ... Wonder what happens if we flip between Input and Output ... Like for PineDio Stack's Push Button / Vibrator 🤔\n\n```c\n//  Write to the GPIO Output Pin\nstatic int bl602_expander_writepin(FAR struct ioexpander_dev_s *dev,\n                                   uint8_t pin,\n                                   bool value)\n{\n  FAR struct bl602_expander_dev_s *priv = (FAR struct bl602_expander_dev_s *)dev;\n  int ret;\n\n  gpioinfo(\"pin=%u, value=%u\\n\", pin, value);\n\n  DEBUGASSERT(priv != NULL \u0026\u0026 pin \u003c CONFIG_IOEXPANDER_NPINS);\n\n  /* Get exclusive access to the I/O Expander */\n\n  ret = bl602_expander_lock(priv);\n  if (ret \u003c 0)\n    {\n      return ret;\n    }\n\n  /* Write the pin value. Warning: Pin Number passed as BL602 Pinset */\n\n  bl602_gpiowrite(pin \u003c\u003c GPIO_PIN_SHIFT, value);\n\n  /* Unlock the I/O Expander */\n\n  bl602_expander_unlock(priv);\n  return ret;\n}\n```\n\n[(Source)](https://github.com/lupyuen/bl602_expander/blob/main/bl602_expander.c#L550-L594)\n\n[(`bl602_gpiowrite` comes from the BL602 GPIO Driver)](https://github.com/lupyuen/incubator-nuttx/blob/pinedio/arch/risc-v/src/bl602/bl602_gpio.c#L197-L216)\n\n# Attach GPIO Interrupt\n\nHere's how our BL602 GPIO Expander attaches a GPIO Interrupt Handler...\n\n```c\n//  Attach a Callback Function to a GPIO Interrupt\n#ifdef CONFIG_IOEXPANDER_INT_ENABLE\nstatic FAR void *bl602_expander_attach(FAR struct ioexpander_dev_s *dev,\n                       ioe_pinset_t pinset,\n                       ioe_callback_t callback, FAR void *arg)\n{\n  FAR struct bl602_expander_dev_s *priv = (FAR struct bl602_expander_dev_s *)dev;\n  FAR struct bl602_expander_callback_s *cb = NULL;\n  int ret = 0;\n\n  gpioinfo(\"pinset=%x, callback=%p, arg=%p\\n\", pinset, callback, arg);\n  DEBUGASSERT(priv != NULL);\n\n  /* Get exclusive access to the I/O Expander */\n\n  ret = bl602_expander_lock(priv);\n  if (ret \u003c 0)\n    {\n      gpioerr(\"ERROR: Lock failed\\n\");\n      return NULL;\n    }\n\n  /* Handle each GPIO Pin in the pinset */\n\n  for (uint8_t gpio_pin = 0; gpio_pin \u003c CONFIG_IOEXPANDER_NPINS; gpio_pin++)\n    {\n      /* If GPIO Pin is set in the pinset... */\n\n      if (pinset \u0026 ((ioe_pinset_t)1 \u003c\u003c gpio_pin))\n        {\n          cb = \u0026priv-\u003ecb[gpio_pin];\n\n          if (callback == NULL) /* Detach Callback */\n            {\n              /* Disable GPIO Interrupt and clear Interrupt Callback */\n\n              gpioinfo(\"Detach callback for gpio=%d, callback=%p, arg=%p\\n\",\n                      cb-\u003epinset, cb-\u003ecbfunc, cb-\u003ecbarg);\n              bl602_expander_intmask(gpio_pin, 1);\n              cb-\u003epinset = 0;\n              cb-\u003ecbfunc = NULL;\n              cb-\u003ecbarg  = NULL;\n              ret = 0;\n            }\n          else if (cb-\u003ecbfunc == NULL) /* Attach Callback */\n            {\n              /* Set Interrupt Callback and enable GPIO Interrupt */\n\n              gpioinfo(\"Attach callback for gpio=%d, callback=%p, arg=%p\\n\", \n                      gpio_pin, callback, arg);\n              cb-\u003epinset = gpio_pin;\n              cb-\u003ecbfunc = callback;\n              cb-\u003ecbarg  = arg;\n              bl602_expander_intmask(gpio_pin, 0);\n              ret = 0;\n            }\n          else /* Callback already attached */\n            {\n              gpioerr(\"ERROR: GPIO %d already attached\\n\", gpio_pin);\n              ret = -EBUSY;\n            }\n\n          /* Only 1 GPIO Pin allowed */\n\n          DEBUGASSERT(pinset == ((ioe_pinset_t)1 \u003c\u003c gpio_pin));\n          break;\n        }\n    }\n\n  /* Unlock the I/O Expander and return the handle */\n\n  bl602_expander_unlock(priv);\n  return (ret == 0) ? cb : NULL;\n}\n#endif\n```\n\n[(Source)](https://github.com/lupyuen/bl602_expander/blob/main/bl602_expander.c#L814-L906)\n\n[(`bl602_expander_intmask` is defined here)](https://github.com/lupyuen/bl602_expander/blob/main/bl602_expander.c#L164-L197)\n\n# Detach GPIO Interrupt\n\nHere's how our BL602 GPIO Expander detaches a GPIO Interrupt Handler...\n\n```c\n//  Detach and disable a GPIO Interrupt\n#ifdef CONFIG_IOEXPANDER_INT_ENABLE\nstatic int bl602_expander_detach(FAR struct ioexpander_dev_s *dev, FAR void *handle)\n{\n  FAR struct bl602_expander_dev_s *priv = (FAR struct bl602_expander_dev_s *)dev;\n  FAR struct bl602_expander_callback_s *cb =\n    (FAR struct bl602_expander_callback_s *)handle;\n\n  DEBUGASSERT(priv != NULL \u0026\u0026 cb != NULL);\n  DEBUGASSERT((uintptr_t)cb \u003e= (uintptr_t)\u0026priv-\u003ecb[0] \u0026\u0026\n              (uintptr_t)cb \u003c=\n              (uintptr_t)\u0026priv-\u003ecb[CONFIG_IOEXPANDER_NPINS - 1]);\n  UNUSED(priv);\n  gpioinfo(\"Detach callback for gpio=%d, callback=%p, arg=%p\\n\",\n           cb-\u003epinset, cb-\u003ecbfunc, cb-\u003ecbarg);\n\n  /* Disable the GPIO Interrupt */\n\n  DEBUGASSERT(cb-\u003epinset \u003c CONFIG_IOEXPANDER_NPINS);\n  bl602_expander_intmask(cb-\u003epinset, 1);\n\n  /* Clear the Interrupt Callback */\n\n  cb-\u003epinset = 0;\n  cb-\u003ecbfunc = NULL;\n  cb-\u003ecbarg  = NULL;\n  return OK;\n}\n#endif\n```\n\n[(Source)](https://github.com/lupyuen/bl602_expander/blob/main/bl602_expander.c#L908-L950)\n\n[(`bl602_expander_intmask` is defined here)](https://github.com/lupyuen/bl602_expander/blob/main/bl602_expander.c#L164-L197)\n\n# Handle GPIO Interrupt\n\nHere's how our BL602 GPIO Expander handles a GPIO Interrupt...\n\n```c\n//  Handle GPIO Interrupt. Based on\n//  https://github.com/apache/incubator-nuttx/blob/master/boards/risc-v/bl602/bl602evb/src/bl602_gpio.c#L256-L304\nstatic int bl602_expander_interrupt(int irq, void *context, void *arg)\n{\n  FAR struct bl602_expander_dev_s *priv = (FAR struct bl602_expander_dev_s *)arg;\n  uint32_t time_out = 0;\n  uint8_t gpio_pin;\n\n  gpioinfo(\"Interrupt! context=%p, priv=%p\\n\", context, priv);\n  DEBUGASSERT(priv != NULL);\n\n  /* TODO: Check only the GPIO Pins that have registered for interrupts */\n\n  for (gpio_pin = 0; gpio_pin \u003c CONFIG_IOEXPANDER_NPINS; gpio_pin++)\n    {\n      /* Found the GPIO for the interrupt */\n\n      if (1 == bl602_expander_get_intstatus(gpio_pin))\n        {\n          FAR struct bl602_expander_callback_s *cb = \u0026priv-\u003ecb[gpio_pin];\n          ioe_callback_t cbfunc = cb-\u003ecbfunc;\n          FAR void* cbarg = cb-\u003ecbarg;\n\n          /* Attempt to clear the Interrupt Status */\n\n          bl602_expander_intclear(gpio_pin, 1);\n\n          /* Check Interrupt Status with timeout */\n\n          time_out = 32;\n          do\n            {\n              time_out--;\n            }\n          while ((1 == bl602_expander_get_intstatus(gpio_pin)) \u0026\u0026 time_out);\n          if (!time_out)\n            {\n              gpiowarn(\"WARNING: Clear GPIO interrupt status fail.\\n\");\n            }\n\n          /* If time_out==0, Interrupt Status not cleared */\n\n          bl602_expander_intclear(gpio_pin, 0);\n\n          /* NOTE: Callback will run in the context of Interrupt Handler */\n\n          if (cbfunc == NULL)\n            {\n              gpioinfo(\"Missing callback for GPIO %d\\n\", gpio_pin);\n            }\n          else\n            {\n              gpioinfo(\"Call gpio=%d, callback=%p, arg=%p\\n\", gpio_pin, cbfunc, cbarg);\n              cbfunc(\u0026priv-\u003edev, gpio_pin, cbarg);\n            }\n        }\n    }\n\n  return OK;\n}\n```\n\n[(Source)](https://github.com/lupyuen/bl602_expander/blob/main/bl602_expander.c#L327-L393)\n\n[(`bl602_expander_intclear` is defined here)](https://github.com/lupyuen/bl602_expander/blob/main/bl602_expander.c#L275-L300)\n\n[(`bl602_expander_get_intstatus` is defined here)](https://github.com/lupyuen/bl602_expander/blob/main/bl602_expander.c#L247-L274)\n\n# Test Touch Panel\n\nBL602 GPIO Expander tested OK with Touch Panel and LVGL Test App...\n\n(With \"GPIO Informational Output\" logging enabled: `kconfig-tweak --enable CONFIG_DEBUG_GPIO_INFO`)\n\n```text\nbl602_expander_irq_enable: Disable interrupt\nbl602_expander_irq_enable: Enable interrupt\nbl602_expander_direction: Unsupported direction: pin=10, direction=IN\nbl602_expander_option: pin=10, option=2, value=0\nbl602_expander_option: ERROR: Unsupported interrupt: 0, pin=10\ngpio_pin_register: Registering /dev/gpio10\nbl602_expander_direction: Unsupported direction: pin=20, direction=OUT\ngpio_pin_register: Registering /dev/gpio20\nbl602_expander_direction: Unsupported direction: pin=3, direction=OUT\ngpio_pin_register: Registering /dev/gpio3\nbl602_expander_direction: Unsupported direction: pin=21, direction=OUT\ngpio_pin_register: Registering /dev/gpio21\nbl602_expander_direction: Unsupported direction: pin=15, direction=OUT\ngpio_pin_register: Registering /dev/gpio15\nbl602_expander_direction: Unsupported direction: pin=14, direction=OUT\ngpio_pin_register: Registering /dev/gpio14\nbl602_expander_option: pin=9, option=2, value=0xe\nbl602_expander_option: Unsupported interrupt both edge: pin=9\ngplh_enable: pin9: Disabling callback=0 handle=0\ngplh_enable: WARNING: pin9: Already detached\ngpio_pin_register: Registering /dev/gpio9\nbl602_expander_option: pin=12, option=2, value=0xe\nbl602_expander_option: Unsupported interrupt both edge: pin=12\ngplh_enable: pin12: Disabling callback=0 handle=0\ngplh_enable: WARNING: pin12: Already detached\ngpio_pin_register: Registering /dev/gpio12\nbl602_expander_option: pin=19, option=2, value=0xe\nbl602_expander_option: Unsupported interrupt both edge: pin=19\ngplh_enable: pin19: Disabling callback=0 handle=0\ngplh_enable: WARNING: pin19: Already detached\ngpio_pin_register: Registering /dev/gpio19\ncst816s_register: path=/dev/input0, addr=21\nbl602_expander_option: pin=9, option=2, value=0xa\nbl602_expander_option: Falling edge: pin=9\nbl602_expander_set_intmod: gpio_pin=9, int_ctlmod=1, int_trgmod=0\nbl602_expander_attach: pinset=200, callback=0x2305e47e, arg=0x42020f80\nbl602_expander_attach: Attach callback for gpio=9, callback=0x2305e47e, arg=0x42020f80\ncst816s_register: Driver registered\n\nNuttShell (NSH) NuttX-10.3.0-RC0\n\nnsh\u003e uname -a\nNuttX 10.3.0-RC0 ffb275b71c Apr 24 2022 10:47:29 risc-v bl602evb\n\nnsh\u003e ls /dev\n/dev:\n console\n gpio10\n gpio12\n gpio14\n gpio15\n gpio19\n gpio20\n gpio21\n gpio3\n gpio9\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\ncst816s_open:\nbl602_expander_interrupt: Interrupt! context=0x42012db8, priv=0x4201df0\nbl602_expander_interrupt: Call gpio=9, callback=0x2305e47e, arg=0x42020f80\ncst816s_poll_notify:\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=190, y=18\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       190\ncst816s_get_touch_data:   y:       18\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=190, y=18\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       190\ncst816s_get_touch_data:   y:       18\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=190, y=18\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       190\ncst816s_get_touch_data:   y:       18\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=190, y=18\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       190\ncst816s_get_touch_data:   y:       18\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=190, y=18\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       190\ncst816s_get_touch_data:   y:       18\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=190, y=18\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       190\ncst816s_get_touch_data:   y:       18\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=190, y=18\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       190\ncst816s_get_touch_data:   y:       18\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=190, y=18\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       190\ncst816s_get_touch_data:   y:       18\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=190, y=18\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       190\ncst816s_get_touch_data:   y:       18\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=190, y=18\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       190\ncst816s_get_touch_data:   y:       18\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=190, y=18\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       190\ncst816s_get_touch_data:   y:       18\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=190, y=18\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       190\ncst816s_get_touch_data:   y:       18\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=190, y=18\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       190\ncst816s_get_touch_data:   y:       18\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: i2c transfer success\ncst86s_get_touch_data: Invalid touch data: id=9, touch=2, x=639, y=1688\ncst816s_get_touch_data: UP: id=0, touch=2, x=190, y=18\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   0c\ncst816s_get_touch_data:   x:       190\ncst816s_get_touch_data:   y:       18\nbl602_expander_interrupt: Interrupt! context=0x42012db8, priv=0x4201d0f0\nbl602_expander_interrupt: Call gpio=9, callback=0x2305e47e, arg=0x42020f80\ncst816s_poll_notify:\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=211, y=199\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       211\ncst816s_get_touch_data:   y:       199\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=211, y=199\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       211\ncst816s_get_touch_data:   y:       199\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: i2c transfer success\ncst816s_get_touch_data: Invalid touch data: id=5, touch=2, x=652, y=514\ncst816s_get_touch_data: UP: id=0, touch=2, x=211, y=199\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   0c\ncst816s_get_touch_data:   x:       211\ncst816s_get_touch_data:   y:       199\nbl602_expander_interrupt: Interrupt! context=0x42012db8, priv=0x4201d0f0\nbl602_expander_interrupt: Call gpio=9, callback=0x2305e47e, arg=0x42020f80\ncst816s_poll_notify:\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=17, y=203\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       17\ncst816s_get_touch_data:   y:      203\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=17, y=203\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       17\ncst816s_get_touch_data:   y:       203\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: i2c transfer success\ncst816s_get_touch_data: Invalid touch data: id=5, touch=2, x=652, y=514\ncst816s_get_touch_data: UP: id=0, touch=2, x=17, y=203\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   0c\ncst816s_get_touch_data:   x:       17\ncst816s_get_touch_data:   y:       203\nbl602_expander_interrupt: Interrupt! context=0x42012db8, priv=0x4201d0f0\nbl602_expander_interrupt: Call gpio=9, callback=0x2305e47e, arg=0x42020f80\ncst816s_poll_notify:\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=7, y=28\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       7\ncst816s_get_touch_data:   y:       28\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=7, y=28\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       7\ncst816s_get_touch_data:   y:       28\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: i2c transfer success\ncst816s_get_touch_data: Invalid touch data: id=5, touch=2, x=652, y=514\ncst816s_get_touch_data: UP: id=0, touch=2, x=7, y=28\ncst816s_get_touch_data:   id:      0\nst816s_get_touch_data:   flags:   0c\ncst816s_get_touch_data:   x:       7\ncst816s_get_touch_data:   y:       28\nbl602_expander_interrupt: Interrupt! context=0x42012db8, priv=0x4201d0f0\nbl602_expander_interrupt: Call gpio=9, callback=0x2305e47e, arg=0x42020f80\ncst816s_poll_notify:\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=123, y=116\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       123\ncst816s_get_touch_data:   y:       116\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=123, y=116\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       123\ncst816s_get_touch_data:   y:       116\ncst816s_get_touch_data:\ncst816s_i2c_read:\nbl602_i2c_transfer: i2c transfer success\nbl602_i2c_transfer: i2c transfer success\ncst816s_get_touch_data: Invalid touch data: id=5, touch=2, x=652, y=514\ncst816s_get_touch_data: UP: id=0, touch=2, x=123, y=116\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   0c\ncst816s_get_touch_data:   x:       123\ncst816s_get_touch_data:   y:       116\ntp_cal result\noffset x:18, y:7\nrange x:181, y:183\ninvert x/y:1, x:0, y:1\n```\n\n# Test Push Button\n\nBL602 GPIO Expander tested OK with Push Button and GPIO Command...\n\n(Comment out `IOEP_ATTACH` in `bl602_bringup`)\n\n```text\nnsh\u003e uname -a\nNuttX 10.3.0-RC0 ffb275b71c Apr 24 2022 10:47:29 risc-v bl602evb\n\nnsh\u003e ls /dev\n/dev:\n console\n gpio10\n gpio12\n gpio14\n gpio15\n gpio19\n gpio20\n gpio21\n gpio3\n gpio9\n i2c0\n input0\n lcd0\n null\n spi0\n spitest0\n timer0\n urandom\n zero\n\nnsh\u003e gpio -t 8 -w 1 /dev/gpio12\nDriver: /dev/gpio12\ngplh_enable: pin12: Disabling callback=0 handle=0\ngplh_enable: WARNING: pin12: Already detached\nbl602_expander_option: pin=12, option=2, value=0x6\nbl602_expander_option: Rising edge: pin=12\nbl602_expander_set_intmod: gpio_pin=12, int_ctlmod=1, int_trgmod=1\ngplh_read: pin12: value=0x42021aef\nbl602_expander_readpin: pin=12, value=1\n  Interrupt pin: Value=1\ngplh_attach: pin12: callback=0x23060808\ngplh_enable: pin12: Enabling callback=0x23060808 handle=0\ngplh_enable: pin12: Attaching 0x23060808\nbl602_expander_attach: pinset=1000, callback=0x2305f4e2, arg=0x42020d40\nbl602_expander_attach: Attach callback for gpio=12, callback=0x2305f4e2, arg=0x42020d40\nbl602_expander_interrupt: Interrupt! context=0x42012db8, priv=0x4201d0f0\nbl602_expander_interrupt: Call gpio=12, callback=0x2305f4e2, arg=0x42020d40\ngplh_handler: pin12: pinset: c callback=0x23060808\ngplh_enable: pin12: Disabling callback=0x23060808 handle=0x4201d1a0\ngplh_enable: pin12: Detaching handle=0x4201d1a0\nbl602_expander_detach: Detach callback for gpio=12, callback=0x2305f4e2, arg=0x42020d40\ngplh_attach: pin12: callback=0\ngplh_read: pin12: value=0x42021aef\nbl602_expander_readpin: pin=12, value=1\n  Verify:        Value=1\n```\n\n# Test LoRaWAN\n\nBL602 GPIO Expander tested OK with LoRaWAN Test App...\n\n(With \"GPIO Informational Output\" logging disabled: `kconfig-tweak --disable CONFIG_DEBUG_GPIO_INFO`)\n\n```text\n▒bl602_expander_direction: Unsupported direction: pin=10, direction=IN\nbl602_expander_option: ERROR: Unsupported interrupt: 0, pin=10\nbl602_expander_direction: Unsupported direction: pin=20, direction=OUT\nbl602_expander_direction: Unsupported direction: pin=3, direction=OUT\nbl602_expander_direction: Unsupported direction: pin=21, direction=OUT\nbl602_expander_direction: Unsupported direction: pin=15, direction=OUT\nbl602_expander_direction: Unsupported direction: pin=14, direction=OUT\nbl602_expander_option: Unsupported interrupt both edge: pin=9\ngplh_enable: WARNING: pin9: Already detached\nbl602_expander_option: Unsupported interrupt both edge: pin=12\ngplh_enable: WARNING: pin12: Already detached\nbl602_expander_option: Unsupported interrupt both edge: pin=19\ngplh_enable: WARNING: pin19: Already detached\ncst816s_register: path=/dev/input0, addr=21\ncst816s_register: Driver registered\n\nNuttShell (NSH) NuttX-10.3.0-RC0\n\nnsh\u003e uname -a\nNuttX 10.3.0-RC0 cf01770616 Apr 24 2022 17:57:00 risc-v bl602evb\n\nnsh\u003e ls /dev\n/dev:\n console\n gpio10\n gpio12\n gpio14\n gpio15\n gpio19\n gpio20\n gpio21\n gpio3\n gpio9\n i2c0\n input0\n lcd0\n null\n spi0\n spitest0\n timer0\n urandom\n zero\n\nnsh\u003e lorawan_test\ninit_entropy_pool\noffset = 2228\ntemperature = 33.793369 Celsius\noffset = 2228\ntemperature = 34.567265 Celsius\noffset = 2228\ntemperature = 35.857086 Celsius\noffset = 2228\ntemperature = 35.599121 Celsius\n\n###### ===================================== ######\n\nApplication name   : lorawan_test\nApplication version: 1.2.0\nGitHub base version: 5.0.0\n\n###### ===================================== ######\n\ninit_event_queue\nTimerInit:     0x4201c764\nTimerInit:     0x4201c780\nTimerInit:     0x4201c79c\nTimerInit:     0x4201c818\nTimerInit:     0x4201c8cc\nTimerInit:     0x4201c8e8\nTimerInit:     0x4201c904\nTimerInit:     0x4201c920\nTODO: RtcGetCalendarTime\nTODO: SX126xReset\ninit_gpio\nDIO1 pintype before=5\ninit_gpio: change DIO1 to Trigger GPIO gplh_enable: WARNING: pin19: Already detached\nInterrupt on Rising Edge\nDIO1 pintype after=8\nStarting process_dio1\ninit_spi\nSX126xSetTxParams: power=22, rampTime=7\nSX126xSetPaConfig: paDutyCycle=4, hpMax=7, deviceSel=0, paLut=1\nTimerInit:     0x4201b864\nTimerInit:     0x4201b7d0\nRadioSetModem\nRadioSetModem\nRadioSetPublicNetwork: public syncword=3444\nRadioSleep\ncallout_handler: lock\nprocess_dio1 started\nprocess_dio1: event=0x4201b88c\nTODO: EepromMcuReadBuffer\nTODO: EepromMcuReadBuffer\nTODO: EepromMcuReadBuffer\nTODO: EepromMcuReadBuffer\nTODO: EepromMcuReadBuffer\nTODO: EepromMcuReadBuffer\nTODO: EepromMcuReadBuffer\nTODO: EepromMcuReadBuffer\nRadioSetModem\nRadioSetPublicNetwork: public syncword=3444\nDevEui      : 4B-C1-5E-E7-37-7B-B1-5B\nJoinEui     : 00-00-00-00-00-00-00-00\nPin         : 00-00-00-00\n\nTimerInit:     0x4201c3bc\nTimerInit:     0x4201c3d8\nTimerInit:     0x4201c29c\nTODO: RtcGetCalendarTime\nTODO: RtcBkupRead\nTODO: RtcBkupRead\nRadioSetChannel: freq=923200000\nRadioSetTxConfig: modem=1, power=13, fdev=0, bandwidth=0, datarate=10, coderate=1, preambleLen=8, fixLen=0, crcOn=1, freqHopOn=0, hopPeriod=0, iqInverted=0, timeout=4000\nRadioSetTxConfig: SpreadingFactor=10, Bandwidth=4, CodingRate=1, LowDatarateOptimize=0, PreambleLength=8, HeaderType=0, PayloadLength=255, CrcMode=1, InvertIQ=0\nRadioStandby\nRadioSetModem\nSX126xSetTxParams: power=13, rampTime=7\nSX126xSetPaConfig: paDutyCycle=4, hpMax=7, deviceSel=0, paLut=1\nSecureElementRandomNumber: 0x351affa5\nRadioSend: size=23\n00 00 00 00 00 00 00 00 00 5b b1 7b 37 e7 5e c1 4b a5 ff 18 96 ae 76\nRadioSend: PreambleLength=8, HeaderType=0, PayloadLength=23, CrcMode=1, InvertIQ=0\nTimerStop:     0x4201b864\nTimerStart2:   0x4201b864, 4000 ms\ncallout_reset: evq=0x420131a8, ev=0x4201b864\n\n###### =========== MLME-Request ============ ######\n######               MLME_JOIN               ######\n###### ===================================== ######\nSTATUS      : OK\nStartTxProcess\nTimerInit:     0x42015b08\nTimerSetValue: 0x42015b08, 42249 ms\nOnTxTimerEvent: timeout in 42249 ms, event=0\nTimerStop:     0x42015b08\nTimerSetValue: 0x42015b08, 42249 ms\nTimerStart:    0x42015b08\nTimerStop:     0x42015b08\nTimerStart2:   0x42015b08, 42249 ms\ncallout_reset: evq=0x420131a8, ev=0x42015b08\nhandle_event_queue\nDIO1 add event\nhandle_event_queue: ev=0x4201b88c\nRadioOnDioIrq\nRadioIrqProcess\nIRQ_TX_DONE\nTimerStop:     0x4201b864\nTODO: RtcGetCalendarTime\nTODO: RtcBkupRead\nRadioOnDioIrq\nRadioIrqProcess\nRadioSleep\nTimerSetValue: 0x4201c780, 4988 ms\nTimerStart:    0x4201c780\nTimerStop:     0x4201c780\nTimerStart2:   0x4201c780, 4988 ms\ncallout_reset: evq=0x420131a8, ev=0x4201c780\nTimerSetValue: 0x4201c79c, 5988 ms\nTimerStart:    0x4201c79c\nTimerStop:     0x4201c79c\nTimerStart2:   0x4201c79c, 5988 ms\ncallout_reset: evq=0x420131a8, ev=0x4201c79c\nTODO: RtcGetCalendarTime\ncallout_handler: unlock\ncallout_handler: evq=0x420131a8, ev=0x4201c780\ncallout_handler: lock\nhandle_event_queue: ev=0x4201c780\nTimerStop:     0x4201c780\nRadioStandby\nRadioSetChannel: freq=923200000\nRadioSetRxConfig\nRadioStandby\nRadioSetModem\nRadioSetRxConfig done\nRadioRx\nTimerStop:     0x4201b7d0\nTimerStart2:   0x4201b7d0, 3000 ms\ncallout_reset: evq=0x420131a8, ev=0x4201b7d0\nRadioOnDioIrq\nRadioIrqProcess\nDIO1 add event\nhandle_event_queue: ev=0x4201b88c\nRadioOnDioIrq\nRadioIrqProcess\nIRQ_PREAMBLE_DETECTED\nRadioOnDioIrq\nRadioIrqProcess\nDIO1 add event\nhandle_event_queue: ev=0x4201b88c\nRadioOnDioIrq\nRadioIrqProcess\nIRQ_HEADER_VALID\nRadioOnDioIrq\nRadioIrqProcess\nDIO1 add event\nhandle_event_queue: ev=0x4201b88c\nRadioOnDioIrq\nRadioIrqProcess\nIRQ_RX_DONE\nTimerStop:     0x4201b7d0\nRadioOnDioIrq\nRadioIrqProcess\nRadioSleep\nTimerStop:     0x4201c79c\nOnTxData\n\n###### =========== MLME-Confirm ============ ######\nSTATUS      : OK\nOnJoinRequest\n###### ===========   JOINED     ============ ######\n\nOTAA\n\nDevAddr     :  014C9548\n\n\nDATA RATE   : DR_2\n\nTODO: EepromMcuWriteBuffer\nTODO: EepromMcuWriteBuffer\nTODO: EepromMcuWriteBuffer\nTODO: EepromMcuWriteBuffer\nTODO: EepromMcuWriteBuffer\nTODO: EepromMcuWriteBuffer\nUplinkProcess\nPrepareTxFrame: Transmit to LoRaWAN: Hi NuttX (9 bytes)\nPrepareTxFrame: status=0, maxSize=11, currentSize=11\nLmHandlerSend: Data frame\nTODO: RtcGetCalendarTime\nTODO: RtcBkupRead\nRadioSetChannel: freq=923400000\nRadioSetTxConfig: modem=1, power=13, fdev=0, bandwidth=0, datarate=9, coderate=1, preambleLen=8, fixLen=0, crcOn=1, freqHopOn=0, hopPeriod=0, iqInverted=0, timeout=4000\nRadioSetTxConfig: SpreadingFactor=9, Bandwidth=4, CodingRate=1, LowDatarateOptimize=0, PreambleLength=8, HeaderType=0, PayloadLength=128, CrcMode=1, InvertIQ=0\nRadioStandby\nRadioSetModem\nSX126xSetTxParams: power=13, rampTime=7\nSX126xSetPaConfig: paDutyCycle=4, hpMax=7, deviceSel=0, paLut=1\nRadioSend: size=22\n40 48 95 4c 01 00 01 00 01 99 51 07 77 91 ab d5 56 9b 23 3b 29 16\nRadioSend: PreambleLength=8, HeaderType=0, PayloadLength=22, CrcMode=1, InvertIQ=0\nTimerStop:     0x4201b864\nTimerStart2:   0x4201b864, 4000 ms\ncallout_reset: evq=0x420131a8, ev=0x4201b864\n\n###### =========== MCPS-Request ============ ######\n######           MCPS_UNCONFIRMED            ######\n###### ===================================== ######\nSTATUS      : OK\nPrepareTxFrame: Transmit OK\nDIO1 add event\nhandle_event_queue: ev=0x4201b88c\nRadioOnDioIrq\nRadioIrqProcess\nIRQ_TX_DONE\nTimerStop:     0x4201b864\nTODO: RtcGetCalendarTime\nTODO: RtcBkupRead\nRadioOnDioIrq\nRadioIrqProcess\nRadioSleep\nTimerSetValue: 0x4201c780, 980 ms\nTimerStart:    0x4201c780\nTimerStop:     0x4201c780\nTimerStart2:   0x4201c780, 980 ms\ncallout_reset: evq=0x420131a8, ev=0x4201c780\nTimerSetValue: 0x4201c79c, 1988 ms\nTimerStart:    0x4201c79c\nTimerStop:     0x4201c79c\nTimerStart2:   0x4201c79c, 1988 ms\ncallout_reset: evq=0x420131a8, ev=0x4201c79c\nTODO: RtcGetCalendarTime\ncallout_handler: unlock\ncallout_handler: evq=0x420131a8, ev=0x4201c780\ncallout_handler: lock\nhandle_event_queue: ev=0x4201c780\nTimerStop:     0x4201c780\nRadioStandby\nRadioSetChannel: freq=923400000\nRadioSetRxConfig\nRadioStandby\nRadioSetModem\nRadioSetRxConfig done\nRadioRx\nTimerStop:     0x4201b7d0\nTimerStart2:   0x4201b7d0, 3000 ms\ncallout_reset: evq=0x420131a8, ev=0x4201b7d0\nRadioOnDioIrq\nRadioIrqProcess\nDIO1 add event\nhandle_event_queue: ev=0x4201b88c\nRadioOnDioIrq\nRadioIrqProcess\nIRQ_RX_TX_TIMEOUT\nTimerStop:     0x4201b7d0\nRadioOnDioIrq\nRadioIrqProcess\nRadioSleep\nTimerStop:     0x4201c79c\nTimerStop:     0x4201c764\nOnTxData\n\n###### =========== MCPS-Confirm ============ ######\nSTATUS      : OK\n\n###### =====   UPLINK FRAME        1   ===== ######\n\nCLASS       : A\n\nTX PORT     : 1\nTX DATA     : UNCONFIRMED\n48 69 20 4E 75 74 74 58 00\n\nDATA RATE   : DR_3\nU/L FREQ    : 923400000\nTX POWER    : 0\nCHANNEL MASK: 0003\n\nTODO: EepromMcuWriteBuffer\nTODO: EepromMcuWriteBuffer\nUplinkProcess\ncallout_handler: unlock\ncallout_handler: evq=0x420131a8, ev=0x42015b08\ncallout_handler: lock\nhandle_event_queue: ev=0x42015b08\nOnTxTimerEvent: timeout in 42249 ms, event=0x42015b08\nTimerStop:     0x42015b08\nTimerSetValue: 0x42015b08, 42249 ms\nTimerStart:    0x42015b08\nTimerStop:     0x42015b08\nTimerStart2:   0x42015b08, 42249 ms\ncallout_reset: evq=0x420131a8, ev=0x42015b08\nRadioOnDioIrq\nRadioIrqProcess\nUplinkProcess\nPrepareTxFrame: Transmit to LoRaWAN: Hi NuttX (9 bytes)\nPrepareTxFrame: status=0, maxSize=53, currentSize=53\nLmHandlerSend: Data frame\nTODO: RtcGetCalendarTime\nTODO: RtcBkupRead\nRadioSetChannel: freq=923200000\nRadioSetTxConfig: modem=1, power=13, fdev=0, bandwidth=0, datarate=9, coderate=1, preambleLen=8, fixLen=0, crcOn=1, freqHopOn=0, hopPeriod=0, iqInverted=0, timeout=4000\nRadioSetTxConfig: SpreadingFactor=9, Bandwidth=4, CodingRate=1, LowDatarateOptimize=0, PreambleLength=8, HeaderType=0, PayloadLength=128, CcMode=1, InvertIQ=0\nRadioStandby\nRadioSetModem\nSX126xSetTxParams: power=13, rampTime=7\nSX126xSetPaConfig: paDutyCycle=4, hpMax=7, deviceSel=0, paLut=1\nRadioSend: size=22\n40 48 95 4c 01 00 02 00 01 2c b3 54 eb c4 e8 2c a5 04 59 aa e1 2f\nRadioSend: PreambleLength=8, HeaderType=0, PayloadLength=22, CrcMode=1, InvertIQ=0\nTimerStop:     0x4201b864\nTimerStart2:   0x4201b864, 4000 ms\ncallout_reset: evq=0x420131a8, ev=0x4201b864\n\n###### =========== MCPS-Request ============ ######\n######           MCPS_UNCONFIRMED            ######\n###### ===================================== ######\nSTATUS      : OK\nPrepareTxFrame: Transmit OK\nDIO1 add event\nhandle_event_queue: ev=0x4201b88c\nRadioOnDioIrq\nRadioIrqProcess\nIRQ_TX_DONE\nTimerStop:     0x4201b864\nTODO: RtcGetCalendarTime\nTODO: RtcBkupRead\nRadioOnDioIrq\nRadioIrqProcess\nRadioSleep\nTimerSetValue: 0x4201c780, 980 ms\nTimerStart:    0x4201c780\nTimerStop:     0x4201c780\nTimerStart2:   0x4201c780, 980 ms\ncallout_reset: evq=0x420131a8, ev=0x4201c780\nTimerSetValue: 0x4201c79c, 1988 ms\nTimerStart:    0x4201c79c\nTimerStop:     0x4201c79c\nTimerStart2:   0x4201c79c, 1988 ms\ncallout_reset: evq=0x420131a8, ev=0x4201c79c\nTODO: RtcGetCalendarTime\ncallout_handler: unlock\ncallout_handler: evq=0x420131a8, ev=0x4201c780\ncallout_handler: lock\nhandle_event_queue: ev=0x4201c780\nTimerStop:     0x4201c780\nRadioStandby\nRadioSetChannel: freq=923200000\nRadioSetRxConfig\nRadioStandby\nRadioSetModem\nRadioSetRxConfig done\nRadioRx\nTimerStop:     0x4201b7d0\nTimerStart2:   0x4201b7d0, 3000 ms\ncallout_reset: evq=0x420131a8, ev=0x4201b7d0\nRadioOnDioIrq\nRadioIrqProcess\nDIO1 add event\nhandle_event_queue: ev=0x4201b88c\nRadioOnDioIrq\nRadioIrqProcess\nIRQ_RX_TX_TIMEOUT\nTimerStop:     0x4201b7d0\nRadioOnDioIrq\nRadioIrqProcess\nRadioSleep\nTimerStop:     0x4201c79c\nTimerStop:     0x4201c764\nOnTxData\n\n###### =========== MCPS-Confirm ============ ######\nSTATUS      : OK\n\n###### =====   UPLINK FRAME        2   ===== ######\n\nCLASS       : A\n\nTX PORT     : 1\nTX DATA     : UNCONFIRMED\n48 69 20 4E 75 74 74 58 00\n\nDATA RATE   : DR_3\nU/L FREQ    : 923200000\nTX POWER    : 0\nCHANNEL MASK: 0003\n\nTODO: EepromMcuWriteBuffer\nTODO: EepromMcuWriteBuffer\nUplinkProcess\ncallout_handler: unlock\ncallout_handler: evq=0x420131a8, ev=0x42015b08\ncallout_handler: lock\nhandle_event_queue: ev=0x42015b08\nOnTxTimerEvent: timeout in 42249 ms, event=0x42015b08\nTimerStop:     0x42015b08\nTimerSetValue: 0x42015b08, 42249 ms\nTimerStart:    0x42015b08\nTimerStop:     0x42015b08\nTimerStart2:   0x42015b08, 42249 ms\ncallout_reset: evq=0x420131a8, ev=0x42015b08\nRadioOnDioIrq\nRadioIrqProcess\nUplinkProcess\nPrepareTxFrame: Transmit to LoRaWAN: Hi NuttX (9 bytes)\nPrepareTxFrame: status=0, maxSize=53, currentSize=53\nLmHandlerSend: Data frame\nTODO: RtcGetCalendarTime\nTODO: RtcBkupRead\nRadioSetChannel: freq=923400000\nRadioSetTxConfig: modem=1, power=13, fdev=0, bandwidth=0, datarate=9, coderate=1, preambleLen=8, fixLen=0, crcOn=1, freqHopOn=0, hopPeriod=0, iqInverted=0, timeout=4000\nRadioSetTxConfig: SpreadingFactor=9, Bandwidth=4, CodingRate=1, LowDatarateOptimize=0, PreambleLength=8, HeaderType=0, PayloadLength=128, CrcMode=1, InvertIQ=0\nRadioStandby\nRadioSetModem\nSX126xSetTxParams: power=13, rampTime=7\nSX126xSetPaConfig: paDutyCycle=4, hpMax=7, deviceSel=0, paLut=1\nRadioSend: size=22\n40 48 95 4c 01 00 03 00 01 67 ec 95 34 1f 0d 3e 8f f0 99 35 f9 a4\nRadioSend: PreambleLength=8, HeaderType=0, PayloadLength=22, CrcMode=1, InvertIQ=0\nTimerStop:     0x4201b864\nTimerStart2:   0x4201b864, 4000 ms\ncallout_reset: evq=0x420131a8, ev=0x4201b864\n\n###### =========== MCPS-Request ============ ######\n######           MCPS_UNCONFIRMED            ######\n###### ===================================== ######\nSTATUS      : OK\nPrepareTxFrame: Transmit OK\nDIO1 add event\nhandle_event_queue: ev=0x4201b88c\nRadioOnDioIrq\nRadioIrqProcess\nIRQ_TX_DONE\nTimerStop:     0x4201b864\nTODO: RtcGetCalendarTime\nTODO: RtcBkupRead\nRadioOnDioIrq\nRadioIrqProcess\nRadioSleep\nTimerSetValue: 0x4201c780, 980 ms\nTimerStart:    0x4201c780\nTimerStop:     0x4201c780\nTimerStart2:   0x4201c780, 980 ms\ncallout_reset: evq=0x420131a8, ev=0x4201c780\nTimerSetValue: 0x4201c79c, 1988 ms\nTimerStart:    0x4201c79c\nTimerStop:     0x4201c79c\nTimerStart2:   0x4201c79c, 1988 ms\ncallout_reset: evq=0x420131a8, ev=0x4201c79c\nTODO: RtcGetCalendarTime\ncallout_handler: unlock\ncallout_handler: evq=0x420131a8, ev=0x4201c780\ncallout_handler: lock\nhandle_event_queue: ev=0x4201c780\nTimerStop:     0x4201c780\nRadioStandby\nRadioSetChannel: freq=923400000\nRadioSetRxConfig\nRadioStandby\nRadioSetModem\nRadioSetRxConfig done\nRadioRx\nTimerStop:     0x4201b7d0\nTimerStart2:   0x4201b7d0, 3000 ms\ncallout_reset: evq=0x420131a8, ev=0x4201b7d0\nRadioOnDioIrq\nRadioIrqProcess\nDIO1 add event\nhandle_event_queue: ev=0x4201b88c\nRadioOnDioIrq\nRadioIrqProcess\nIRQ_RX_TX_TIMEOUT\nTimerStop:     0x4201b7d0\nRadioOnDioIrq\nRadioIrqProcess\nRadioSleep\nTimerStop:     0x4201c79c\nTimerStop:     0x4201c764\nOnTxData\n\n###### =========== MCPS-Confirm ============ ######\nSTATUS      : OK\n\n###### =====   UPLINK FRAME        3   ===== ######\n\nCLASS       : A\n\nTX PORT     : 1\nTX DATA     : UNCONFIRMED\n48 69 20 4E 75 74 74 58 00\n\nDATA RATE   : DR_3\nU/L FREQ    : 923400000\nTX POWER    : 0\nCHANNEL MASK: 0003\n\nTODO: EepromMcuWriteBuffer\nTODO: EepromMcuWriteBuffer\nUplinkProcess\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flupyuen%2Fbl602_expander","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flupyuen%2Fbl602_expander","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flupyuen%2Fbl602_expander/lists"}