From b584df14833825180ba1b238ef46c83b6367d38b Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 3 Nov 2021 12:56:47 +0100 Subject: [PATCH] HardwareTimer: Allow delaying initialization to setup method Previously, the only way to initialize a HardwareTimer instance was to pass an instance to the constructor. When using a global (more specifically, with static storage duration) HardwareTimer instance, the initialization would happen early in startup, before main() and setup() had a chance to run. This would mean that on any errors (e.g. no timer found for a specific pin when using e.g. pinmap_peripheral), the board would just lock up in early startup, unable to show a meaningful error at all. To prevent this, you would have to allocate the HardwareTimer object dynamically on the heap, but that is not always a good idea from a memory management perspective. This commit adds an argumentless constructor that does not initialize the timer yet, and a setup() method that accepts the timer instance and does the actual initialization. This allows delaying the actual initialization until in or after the sketch setup() function, and also makes it easier to change the timer to use dynamically, based on e.g. user input or EEPROM contents or similar. Note that de-initializing a timer and switching to using a different one is not currently supported, trying to call setup() twice results in an error. --- cores/arduino/HardwareTimer.cpp | 35 +++++++++++++++++++++++++++++++++ cores/arduino/HardwareTimer.h | 3 +++ 2 files changed, 38 insertions(+) diff --git a/cores/arduino/HardwareTimer.cpp b/cores/arduino/HardwareTimer.cpp index 11fa86b92f..30c3a60531 100644 --- a/cores/arduino/HardwareTimer.cpp +++ b/cores/arduino/HardwareTimer.cpp @@ -35,18 +35,53 @@ /* Private Variables */ timerObj_t *HardwareTimer_Handle[TIMER_NUM] = {NULL}; +/** + * @brief HardwareTimer constructor: make uninitialized timer + * Before calling any methods, call setup to select and setup + * the timer to be used. + * @retval None + */ +HardwareTimer::HardwareTimer() +{ + _timerObj.handle.Instance = nullptr; +} + /** * @brief HardwareTimer constructor: set default configuration values + * The timer will be usable directly, there is no need to call + * setup(). Using this constructor is not recommended for + * global variables that are automatically initalized at + * startup, since this will happen to early to report any + * errors. Better use the argumentless constructor and call the + * setup() method during initialization later. * @param Timer instance ex: TIM1, ... * @retval None */ HardwareTimer::HardwareTimer(TIM_TypeDef *instance) +{ + _timerObj.handle.Instance = nullptr; + setup(instance); +} + +/** + * @brief HardwareTimer setup: configuration values. Must be called + * exactly once before any other methods, except when an instance is + * passed to the constructor. + * @param Timer instance ex: TIM1, ... + * @retval None + */ +void HardwareTimer::setup(TIM_TypeDef *instance) { uint32_t index = get_timer_index(instance); if (index == UNKNOWN_TIMER) { Error_Handler(); } + // Already initialized? + if (_timerObj.handle.Instance) { + Error_Handler(); + } + HardwareTimer_Handle[index] = &_timerObj; _timerObj.handle.Instance = instance; diff --git a/cores/arduino/HardwareTimer.h b/cores/arduino/HardwareTimer.h index 2bbbc68b21..5d1c01b9e6 100644 --- a/cores/arduino/HardwareTimer.h +++ b/cores/arduino/HardwareTimer.h @@ -98,9 +98,12 @@ using callback_function_t = std::function; /* Class --------------------------------------------------------*/ class HardwareTimer { public: + HardwareTimer(); HardwareTimer(TIM_TypeDef *instance); ~HardwareTimer(); // destructor + void setup(TIM_TypeDef *instance); // Setup, only needed if no instance was passed to the constructor + void pause(void); // Pause counter and all output channels void pauseChannel(uint32_t channel); // Timer is still running but channel (output and interrupt) is disabled void resume(void); // Resume counter and all output channels