In this library, templates and overridden functions are used to specify pin configurations for the RotEncoder instead of using initialization lists. This design choice has been made to improve both flexibility and efficiency, and to ensure that pin configurations can be optimized at compile-time.
When you use templates to specify pin numbers, as in RotEncoderPins<PinA, PinB>, the pin values are determined at compile-time. This means that the compiler can optimize the code and eliminate any potential runtime overhead that could arise from using runtime configurations, such as initialization lists.
Example:
RotEncoderPins<5, 6> encoder; // PinA is 5, PinB is 6
Here, the pin numbers (5 and 6) will be inserted directly into the code during compilation, reducing the need to store these values in RAM and improving execution speed.
Initialization lists typically store values in RAM as runtime variables, which leads to increased memory consumption. By using templates and overridden functions, the pin values are handled at compile-time, avoiding the need to store them in RAM. This is especially crucial in embedded systems like Arduino, where memory is limited.
Code Example:
template <uint8_t PinA, uint8_t PinB>
class RotEncoderPins : public RotEncoder {
public:
inline uint8_t getPinA() const override __attribute__((always_inline)) { return PinA; }
inline uint8_t getPinB() const override __attribute__((always_inline)) { return PinB; }
};
In this example, the PinA and PinB values are handled at compile-time and returned by the overridden getPinA() and getPinB() functions, meaning they do not consume additional memory during runtime.
With templates, you can create multiple instances of the RotEncoder class, each with its own custom pin configurations, without altering the base code. This allows for great flexibility when working with various hardware setups.
Example:
RotEncoderPins<2, 3> encoder1; // Uses pin 2 and 3
RotEncoderPins<5, 6> encoder2; // Uses pin 5 and 6
Each encoder instance can have its own pin configuration, making it easy to work with different hardware devices in a single project.
| Method | Compile-Time Optimization | Memory Usage | Flexibility | Risk of Runtime Errors |
|---|---|---|---|---|
| Templates | Yes | Low | High | Low |
| Initialization Lists | No | High | Medium | High |
RotEncoder with Default Pin ConfigurationsRotEncoder encoder; // Uses default pins (PinA = 2, PinB = 3)
In this example, the encoder uses the default pin values for PinA (2) and PinB (3). These values are hardcoded into the RotEncoder class.
RotEncoderPins with Custom Pin ConfigurationsRotEncoderPins<5, 6> customEncoder; // Uses pin 5 and 6
In this example, the pin numbers are passed via the template parameters and resolved at compile-time. This reduces the memory footprint and ensures efficient execution. The virtual functions getPinA() and getPinB() are overridden to return the values from the template.
By using templates and overridden functions instead of initialization lists, the code is optimized for both speed and memory usage. This approach provides a more flexible structure where users can easily change pin configurations via the RotEncoderPins class, without managing runtime initialization or risking runtime errors.