Upgrade GPIO With Harmony 3 API: A Refactor Guide
Hey guys, let's dive into a crucial update for our firmware: refactoring the GPIO pin handling to leverage the power of the Harmony 3 GPIO_Pin*
API. This shift isn't just about making the code cleaner; it's about aligning with Microchip's best practices, boosting maintainability, and future-proofing our project. Currently, we're using an older method that predates Harmony 3. It involves breaking down GPIO pins into port and bit positions. While it works, it's a bit clunky and not as efficient as what Harmony 3 offers. This article will guide you through the benefits, implementation details, and a step-by-step migration strategy to make this transition as smooth as possible. Ready? Let's go!
Current GPIO Implementation: The Old Way
Let's take a quick look at how we're currently handling GPIO pins. Our existing system uses a struct
called DIOConfig
to store the configuration for each digital I/O pin. Inside this struct, we have members like DataChannel
and DataBitPos
, which specify the GPIO port and the bit position within that port, respectively. This approach means that when we want to write to a pin, read from a pin, or set its direction (input or output), we need to use custom functions that work with these port and bit position values.
For example, consider how we might write to a GPIO pin: We'd call a function like WriteGpioPin()
, passing in the channel and bit position, along with the value we want to write. Similarly, reading a pin would involve reading the entire port and then masking out the specific bit we're interested in. Setting the direction (input or output) involves another custom function. While this works, it introduces a layer of abstraction that isn't necessary and can make the code harder to read and understand. Also, it separates us from the standardized ways of handling GPIO pins.
// DIOConfig.h - Current
typedef struct {
GPIO_PORT DataChannel;
uint8_t DataBitPos; // Bit position (0-15)
GPIO_PORT EnableChannel;
uint8_t EnableBitPos; // Bit position (0-15)
...
} DIOConfig;
// DIO.c - Current usage
WriteGpioPin(enableChannel, enableBitPos, value);
SetGpioDir(dataChannel, dataBitPos, isInput);
if (GPIO_PortRead(dataChannel) & (1 << dataBitPos)) { ... }
Proposed Implementation: Embracing Harmony 3
The good news is that Harmony 3 provides a much cleaner and more efficient way to handle GPIO pins, and it's based around the GPIO_PIN
type. This approach simplifies everything. Instead of dealing with port and bit positions, we'll directly use a GPIO_PIN
variable, which encapsulates all the necessary information about the pin. So, our DIOConfig
struct will be updated to use GPIO_PIN
members, such as DataPin
and EnablePin
. Using the Harmony 3 API, we can now directly write to a pin using GPIO_PinWrite()
, set its direction with GPIO_PinInputEnable()
or GPIO_PinOutputEnable()
, and read its value using GPIO_PinRead()
. It streamlines the way we interact with GPIO pins.
This shift has significant advantages: It simplifies the code, making it easier to read, understand, and maintain. Also, it brings us in line with the standard Microchip approach, making it easier to integrate with other Harmony 3 components and helping new developers get up to speed more quickly. It also potentially improves performance, as Harmony 3 API functions are optimized for the specific hardware. Finally, it reduces the risk of errors by eliminating the need for custom wrappers.
// DIOConfig.h - Proposed
typed struct {
GPIO_PIN DataPin; // Full GPIO_PIN encoding (e.g., GPIO_PIN_RD1)
GPIO_PIN EnablePin; // Full GPIO_PIN encoding (e.g., GPIO_PIN_RD2)
...
} DIOConfig;
// DIO.c - Proposed usage (using Harmony 3 API)
GPIO_PinWrite(enablePin, value);
if (isInput)
GPIO_PinInputEnable(dataPin);
else
GPIO_PinOutputEnable(dataPin);
if (GPIO_PinRead(dataPin)) { ... }
Benefits of the Harmony 3 Approach
Why make this change? Well, the advantages are plentiful:
- Leveraging Harmony's API: You're using a function that is maintained and optimized by Microchip. That means you benefit from their expertise and any future improvements they make to the API. Also, it ensures that our code will stay up-to-date with the latest best practices. This also reduces the chances of compatibility issues when upgrading to newer versions of the Harmony 3 framework.
- No Custom Wrappers: This eliminates the need for our own custom wrapper functions like
WriteGpioPin()
andSetGpioDir()
. This simplifies the code, making it easier to read, understand, and maintain. Removing custom wrappers also reduces the risk of introducing bugs. Also, we reduce the amount of code we have to write and maintain. - No Port/Bit Decomposition: This approach simplifies our board configuration files and makes them much more readable. It's easier to see at a glance which pins are being used for what purpose. It also reduces the chance of errors related to incorrect bit position calculations.
- Consistency with Harmony 3: Adopting this approach brings us into alignment with the Harmony 3 ecosystem's conventions. This makes it easier for other developers to understand and work with our code. It also simplifies the process of integrating our code with other Harmony 3 components. Aligning with industry standards makes the code more maintainable.
- Potential Performance Boost: Harmony functions often use inline optimizations, leading to better performance. By using the Harmony 3 API, we ensure that our code is as efficient as possible. It is a win-win situation, improving both code readability and performance.
Harmony 3 GPIO Functions: Your Toolkit
Let's take a look at the key functions from plib_gpio.h
that we'll be using:
GPIO_PinWrite(GPIO_PIN pin, bool value)
: Writes a value to the specified pin. This is the equivalent of setting a pin high or low.GPIO_PinRead(GPIO_PIN pin)
: Reads the value of the specified pin (high or low).GPIO_PinToggle(GPIO_PIN pin)
: Toggles the state of the pin (from high to low or low to high).GPIO_PinSet(GPIO_PIN pin)
: Sets the pin high.GPIO_PinClear(GPIO_PIN pin)
: Sets the pin low.GPIO_PinOutputEnable(GPIO_PIN pin)
: Configures the pin as an output.GPIO_PinInputEnable(GPIO_PIN pin)
: Configures the pin as an input.GPIO_PinIntEnable(GPIO_PIN pin, GPIO_INTERRUPT_STYLE style)
: Enables interrupts on the specified pin. This is useful for handling events triggered by external devices.
Under the hood, all of these functions use GPIO_PortWrite()
, which is a lower-level function. By using GPIO_Pin*
functions, we avoid having to deal with these lower-level details directly, making the code easier to manage.
Files to Refactor: Where the Magic Happens
Now, let's get specific about the files we need to modify:
state/board/DIOConfig.h
: This is where we'll change theDIOConfig
struct to useGPIO_PIN
types. This is the foundation for all of our GPIO operations.state/board/NQ*BoardConfig.c
: Here, we'll update our board configuration files to use the MCC-generated pin constants (e.g.,DIO_0_PIN
). This will ensure that our code is correctly configured for the specific hardware.HAL/DIO.c
: This is the core file where we'll replace our custom functions with the Harmony 3GPIO_Pin*
API calls. This is where the refactoring work is done.HAL/Power/PowerApi.c
: This file likely uses a similar port/bit decomposition pattern, so it will need to be reviewed and updated accordingly.- Other HAL drivers: We'll need to review any other HAL drivers that handle GPIO pins and make similar changes. This ensures that our refactoring is comprehensive and consistent.
Migration Strategy: Step-by-Step Guide
To make this transition as smooth as possible, follow these steps:
- Update DIOConfig struct: Change the
DIOConfig
struct to useGPIO_PIN
types. This is a crucial first step because it changes how we define our pins. For each pin, make sure you use aGPIO_PIN
type. - Update board config files: Modify all board config files to use MCC-generated pin constants (e.g.,
DIO_0_PIN
). These constants are defined in the MCC-generated files and provide a standardized way to refer to the pins. - Replace WriteGpioPin(): Replace all instances of
WriteGpioPin()
withGPIO_PinWrite()
. This changes the way we write values to the pins. - Replace SetGpioDir(): Replace
SetGpioDir()
calls withGPIO_PinInputEnable()
orGPIO_PinOutputEnable()
, depending on whether you want to set the pin as an input or output. This will simplify how we handle pin direction. - Replace port read + bit masking: Replace port read + bit masking with
GPIO_PinRead()
. This will make it easy to read pin values. - Remove custom GPIO wrapper functions: After you have replaced all of the custom functions with the Harmony 3 API, you can safely remove the custom GPIO wrapper functions. This will simplify the code and make it easier to maintain.
- Test thoroughly: Test on all board variants (NQ1, NQ3, etc.) to ensure that the changes haven't introduced any regressions. Thorough testing is important.
Related Work and Priority
- Recent cleanup in PR #117: This addresses some immediate qodo concerns by using bit position macros. It lays the groundwork for the current refactor. This ensures consistency.
- Created pin_definitions.h: This was created as an interim solution and can be removed after this refactor. This improves code organization.
- The proper long-term solution: This refactor provides the proper long-term solution for GPIO handling.
This refactor is classified as Medium priority because the current implementation works correctly, but this refactor improves:
- Code maintainability: It makes the code easier to understand and maintain.
- Consistency with Harmony 3 patterns: It brings our code in line with the Harmony 3 framework. This makes it easier for other developers to understand and work with the code.
- Future developer onboarding: This simplifies the process of integrating our code with other Harmony 3 components. It will help new developers.
By following these steps and implementing the Harmony 3 GPIO_Pin*
API, we'll create a more maintainable, efficient, and standardized codebase. This transition is not just about updating code; it's about embracing best practices and setting ourselves up for future success. Let's get coding!