feat(d1l): Add TCA9535 INT pin optimization for event-driven interrupts #48
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/d1l-int-pin-optimization"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implement hardware interrupt-driven optimization for SenseCAP Indicator D1L's TCA9535 IO expander using ESP32 GPIO 42, replacing 5ms polling with event-driven design.
Changes
HAL Implementation:
Performance:
Design:
Documentation:
Testing:
Resolves: Phase D (TCA9535 INT Pin Optimization)
Refs: docs/hardware/d1l_int_pin_optimization.md
Description
Type of Change
Related Issues
Testing Performed
Hardware Tested
Code Quality Checklist
Breaking Changes
Screenshots/Logs
Additional Notes
Summary of Changes
Hello @okapteinis, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request significantly enhances the efficiency and responsiveness of the SenseCAP Indicator D1L's TCA9535 IO expander by transitioning from a continuous polling model to an event-driven, hardware interrupt-based system. This optimization aims to reduce I2C bus traffic, lower power consumption, and decrease interrupt latency, making the device more resource-efficient and performant without altering its external behavior or introducing breaking changes.
Highlights
USE_TCA9535_INT_PINflag is not defined. The existing two-mutex design for thread safety is preserved, and there are zero breaking changes to the public API.readAllInputs()method inTCA9535_GPIOfor efficient bulk pin state reads and extracted common interrupt processing logic intoprocessVirtualInterrupts(). Comprehensive design and implementation summary documents (d1l_int_pin_optimization.mdandd1l_int_pin_implementation_summary.md) have been added.Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point by creating a comment using either
/gemini <command>or@gemini-code-assist <command>. Below is a summary of the supported commands on the current page./gemini review/gemini summary/gemini helpCustomization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a
.gemini/folder in the base of the repository. Detailed instructions can be found here.Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩︎
Code Review
This pull request introduces a significant performance optimization by replacing polling with an event-driven interrupt mechanism for the TCA9535 IO expander. The changes are well-documented with detailed design and implementation summaries. However, I've identified a critical correctness issue in the interrupt handling logic that could lead to a deadlock, and a high-severity performance issue in the new
readAllInputsfunction which undermines some of the performance goals of this PR. My review includes suggestions to address both of these points.@ -26,6 +26,85 @@ SemaphoreHandle_t d1l_i2c_mutex = NULL;*/There is a potential for a deadlock in the interrupt handling logic. The ESP32 ISR is configured to trigger on a
FALLINGedge, but the interrupt source (TCA9535INTpin) is level-triggered (active-low). If a new pin change occurs while the task is processing an interrupt, theINTline may never return HIGH, preventing a newFALLINGedge from being generated. The current logic attempts to handle this with a single re-read if the pin is still low, but this is not sufficient to handle all race conditions and can lead to the task blocking indefinitely, waiting for a notification that will never arrive.To make this more robust, I suggest processing events in a loop until the interrupt line is confirmed to be high. This ensures all pending changes are handled before the task goes back to sleep, preventing missed interrupts.
@ -217,6 +217,34 @@ uint8_t TCA9535_GPIO::digitalRead(uint16_t pin) {return value;This implementation of
readAllInputs()is inefficient as it performs 16 separate I2C read transactions. The underlyingTCA9555library supports reading an entire 8-bit port with a singleread(port)call. You can significantly improve performance and reduce I2C bus contention by using two port reads instead of 16 individual pin reads.Since this pull request is focused on performance optimization, making this change would be a valuable improvement.
@gemini-code-assist /gemini review
Code Review
This pull request introduces a significant and well-designed optimization by replacing the polling mechanism for the TCA9535 IO expander with an event-driven interrupt-based approach. This change will drastically reduce I2C bus load and power consumption, which is a great improvement for the device. The implementation is robust, featuring a fallback to the old polling mode and maintaining thread safety. The accompanying documentation is extensive, though there are a few inconsistencies between the documents and the final code that should be addressed. I've also identified a missed opportunity to fully refactor the polling-mode logic to use the new shared helper function, which would improve its efficiency and reduce code duplication.
@ -0,0 +65,4 @@if (pinState == HIGH) {allInputs |= (1 << pin);}}The code snippet for
readAllInputs()is misleading. It shows 16 individualread1()calls in a loop, with a comment stating the library lacks a bulk read. However, the actual implementation inTCA9535_GPIO.cppcorrectly uses the more efficientioExpander->read16()method. The documentation should be updated to reflect the optimized implementation to avoid confusion for future maintainers.@ -0,0 +133,4 @@if (digitalRead(TCA9535_INT_PIN) == LOW) {inputState = hal->ioExpander->readAllInputs();hal->processVirtualInterrupts(inputState);}The logic for handling burst interrupts is misrepresented here. The snippet shows a single
ifstatement to re-process interrupts if the INT pin is still low. The actual implementation inCustomRadioLibHal.cppuses a more robustdo-whileloop, which correctly handles multiple, rapid-fire interrupts. Please update the documentation to match the superior implementation.@ -0,0 +305,4 @@if (digitalRead(TCA9535_INT_PIN) == LOW) {Serial.println("[CustomHAL] INT still LOW - re-triggering handler");vTaskNotifyGive(xTaskGetCurrentTaskHandle()); // Self-signal}The design for handling missed/burst interrupts described here differs from the final implementation. The document suggests an
ifcheck and self-signaling the task, whereas the code inCustomRadioLibHal.cppuses ado-whileloop. Thedo-whileapproach is more robust for level-triggered interrupts. The design document should be updated to reflect the final, improved implementation logic.This new helper function
processVirtualInterruptsis a great refactoring to share logic between polling and interrupt-driven modes, as stated in the design documents. However, the existingpollVirtualInterruptsInternalfunction (used in the polling-mode fallback) has not been updated to use it. It still contains duplicated logic and performs inefficient single-pin reads in a loop.To complete the refactoring and improve the polling mode's efficiency,
pollVirtualInterruptsInternalshould be updated to callioExpander->readAllInputs()once and then pass the result to this function. This would align the implementation with the stated goals, remove code duplication, and optimize the fallback polling mechanism.For example: