|
| 1 | +/**************************************************************************************************************************** |
| 2 | + ESP32_New_FastTimerInterrupt.h |
| 3 | + For ESP32, ESP32_S2, ESP32_S3, ESP32_C3 boards with ESP32 core v2.0.0+ |
| 4 | + Written by Khoi Hoang |
| 5 | +
|
| 6 | + Built by Khoi Hoang https://github.com/khoih-prog/ESP32_New_ISR_Servo |
| 7 | + Licensed under MIT license |
| 8 | +
|
| 9 | + The ESP32, ESP32_S2, ESP32_S3, ESP32_C3 have two timer groups, TIMER_GROUP_0 and TIMER_GROUP_1 |
| 10 | + 1) each group of ESP32, ESP32_S2, ESP32_S3 has two general purpose hardware timers, TIMER_0 and TIMER_1 |
| 11 | + 2) each group of ESP32_C3 has ony one general purpose hardware timer, TIMER_0 |
| 12 | + |
| 13 | + All the timers are based on 64-bit counters (except 54-bit counter for ESP32_S3 counter) and 16 bit prescalers. |
| 14 | + The timer counters can be configured to count up or down and support automatic reload and software reload. |
| 15 | + They can also generate alarms when they reach a specific value, defined by the software. |
| 16 | + The value of the counter can be read by the software program. |
| 17 | +
|
| 18 | + Now these new 16 ISR-based PWM servo contro uses only 1 hardware timer. |
| 19 | + The accuracy is nearly perfect compared to software timers. The most important feature is they're ISR-based timers |
| 20 | + Therefore, their executions are not blocked by bad-behaving functions / tasks. |
| 21 | + This important feature is absolutely necessary for mission-critical tasks. |
| 22 | +
|
| 23 | + Based on SimpleTimer - A timer library for Arduino. |
| 24 | + |
| 25 | + Copyright (c) 2010 OTTOTECNICA Italy |
| 26 | +
|
| 27 | + Based on BlynkTimer.h |
| 28 | + Author: Volodymyr Shymanskyy |
| 29 | +
|
| 30 | + Version: 1.2.0 |
| 31 | +
|
| 32 | + Version Modified By Date Comments |
| 33 | + ------- ----------- ---------- ----------- |
| 34 | + 1.0.0 K Hoang 15/08/2021 Initial coding for ESP32, ESP32_S2, ESP32_C3 boards with ESP32 core v2.0.0-rc1+ |
| 35 | + 1.1.0 K Hoang 12/02/2022 Add support to new ESP32-S3. Convert to h-only library. Optimize code |
| 36 | + 1.2.0 K Hoang 08/05/2022 Fix issue with core v2.0.1+ |
| 37 | + *****************************************************************************************************************************/ |
| 38 | + |
| 39 | +#pragma once |
| 40 | + |
| 41 | +#ifndef ESP32_New_FastTimerInterrupt_h |
| 42 | +#define ESP32_New_FastTimerInterrupt_h |
| 43 | + |
| 44 | +#if ( ARDUINO_ESP32S2_DEV || ARDUINO_FEATHERS2 || ARDUINO_ESP32S2_THING_PLUS || ARDUINO_MICROS2 || \ |
| 45 | + ARDUINO_METRO_ESP32S2 || ARDUINO_MAGTAG29_ESP32S2 || ARDUINO_FUNHOUSE_ESP32S2 || \ |
| 46 | + ARDUINO_ADAFRUIT_FEATHER_ESP32S2_NOPSRAM ) |
| 47 | + #define USING_ESP32_S2_NEW_ISR_SERVO true |
| 48 | +#elif ( defined(ARDUINO_ESP32S3_DEV) || defined(ARDUINO_ESP32_S3_BOX) || defined(ARDUINO_TINYS3) || \ |
| 49 | + defined(ARDUINO_PROS3) || defined(ARDUINO_FEATHERS3) ) |
| 50 | + #define USING_ESP32_S3_NEW_ISR_SERVO true |
| 51 | +#elif ( ARDUINO_ESP32C3_DEV ) |
| 52 | + #define USING_ESP32_C3_NEW_ISR_SERVO true |
| 53 | +#elif defined(ESP32) |
| 54 | + #define USING_ESP32_NEW_ISR_SERVO true |
| 55 | +#else |
| 56 | + #error This code is intended to run on the ESP32 platform! Please check your Tools->Board setting. |
| 57 | +#endif |
| 58 | + |
| 59 | +#include "ESP32_New_ISR_Servo_Debug.h" |
| 60 | + |
| 61 | +#include <driver/timer.h> |
| 62 | + |
| 63 | +/* |
| 64 | + //ESP32 core v1.0.6, hw_timer_t defined in esp32/tools/sdk/include/driver/driver/timer.h: |
| 65 | +
|
| 66 | + #define TIMER_BASE_CLK (APB_CLK_FREQ) //Frequency of the clock on the input of the timer groups |
| 67 | +
|
| 68 | +
|
| 69 | + //@brief Selects a Timer-Group out of 2 available groups |
| 70 | + |
| 71 | +typedef enum |
| 72 | +{ |
| 73 | + TIMER_GROUP_0 = 0, /*!<Hw timer group 0 |
| 74 | + TIMER_GROUP_1 = 1, /*!<Hw timer group 1 |
| 75 | + TIMER_GROUP_MAX, |
| 76 | +} timer_group_t; |
| 77 | +
|
| 78 | +
|
| 79 | + //@brief Select a hardware timer from timer groups |
| 80 | + |
| 81 | +typedef enum |
| 82 | +{ |
| 83 | + TIMER_0 = 0, /*!<Select timer0 of GROUPx |
| 84 | + TIMER_1 = 1, /*!<Select timer1 of GROUPx |
| 85 | + TIMER_MAX, |
| 86 | +} timer_idx_t; |
| 87 | +
|
| 88 | +
|
| 89 | + //@brief Decides the direction of counter |
| 90 | + |
| 91 | +typedef enum |
| 92 | +{ |
| 93 | + TIMER_COUNT_DOWN = 0, //Descending Count from cnt.high|cnt.low |
| 94 | + TIMER_COUNT_UP = 1, //Ascending Count from Zero |
| 95 | + TIMER_COUNT_MAX |
| 96 | +} timer_count_dir_t; |
| 97 | +
|
| 98 | +
|
| 99 | + //@brief Decides whether timer is on or paused |
| 100 | + |
| 101 | +typedef enum |
| 102 | +{ |
| 103 | + TIMER_PAUSE = 0, //Pause timer counter |
| 104 | + TIMER_START = 1, //Start timer counter |
| 105 | +} timer_start_t; |
| 106 | +
|
| 107 | +
|
| 108 | + //@brief Decides whether to enable alarm mode |
| 109 | + |
| 110 | +typedef enum |
| 111 | +{ |
| 112 | + TIMER_ALARM_DIS = 0, //Disable timer alarm |
| 113 | + TIMER_ALARM_EN = 1, //Enable timer alarm |
| 114 | + TIMER_ALARM_MAX |
| 115 | +} timer_alarm_t; |
| 116 | +
|
| 117 | +
|
| 118 | + //@brief Select interrupt type if running in alarm mode. |
| 119 | + |
| 120 | +typedef enum |
| 121 | +{ |
| 122 | + TIMER_INTR_LEVEL = 0, //Interrupt mode: level mode |
| 123 | + //TIMER_INTR_EDGE = 1, //Interrupt mode: edge mode, Not supported Now |
| 124 | + TIMER_INTR_MAX |
| 125 | +} timer_intr_mode_t; |
| 126 | +
|
| 127 | +
|
| 128 | + //@brief Select if Alarm needs to be loaded by software or automatically reload by hardware. |
| 129 | + |
| 130 | +typedef enum |
| 131 | +{ |
| 132 | + TIMER_AUTORELOAD_DIS = 0, //Disable auto-reload: hardware will not load counter value after an alarm event |
| 133 | + TIMER_AUTORELOAD_EN = 1, //Enable auto-reload: hardware will load counter value after an alarm event |
| 134 | + TIMER_AUTORELOAD_MAX, |
| 135 | +} timer_autoreload_t; |
| 136 | +
|
| 137 | +
|
| 138 | + //@brief Data structure with timer's configuration settings |
| 139 | + |
| 140 | +typedef struct |
| 141 | +{ |
| 142 | + bool alarm_en; //Timer alarm enable |
| 143 | + bool counter_en; //Counter enable |
| 144 | + timer_intr_mode_t intr_type; //Interrupt mode |
| 145 | + timer_count_dir_t counter_dir; //Counter direction |
| 146 | + bool auto_reload; //Timer auto-reload |
| 147 | + uint32_t divider; //Counter clock divider. The divider's range is from from 2 to 65536. |
| 148 | +} timer_config_t; |
| 149 | +
|
| 150 | +*/ |
| 151 | + |
| 152 | +#ifndef USE_ESP32_TIMER_NO |
| 153 | + #define USE_ESP32_TIMER_NO 1 |
| 154 | +#endif |
| 155 | + |
| 156 | +#if USING_ESP32_C3_NEW_ISR_SERVO |
| 157 | + #ifndef USE_ESP32_TIMER_NO |
| 158 | + #define USE_ESP32_TIMER_NO 1 |
| 159 | + #endif |
| 160 | + |
| 161 | + #define MAX_ESP32_NUM_TIMERS 2 |
| 162 | +#else |
| 163 | + #ifndef USE_ESP32_TIMER_NO |
| 164 | + #define USE_ESP32_TIMER_NO 3 |
| 165 | + #endif |
| 166 | + |
| 167 | + #define MAX_ESP32_NUM_TIMERS 4 |
| 168 | +#endif |
| 169 | + |
| 170 | +class ESP32FastTimerInterrupt; |
| 171 | + |
| 172 | +typedef ESP32FastTimerInterrupt ESP32FastTimer; |
| 173 | + |
| 174 | +#define TIMER_DIVIDER 80 // Hardware timer clock divider |
| 175 | +// TIMER_BASE_CLK = APB_CLK_FREQ = Frequency of the clock on the input of the timer groups |
| 176 | +#define TIMER_SCALE (TIMER_BASE_CLK / TIMER_DIVIDER) // convert counter value to seconds |
| 177 | + |
| 178 | + |
| 179 | +// In esp32/1.0.6/tools/sdk/esp32s2/include/driver/include/driver/timer.h |
| 180 | +// typedef bool (*timer_isr_t)(void *); |
| 181 | +//esp_err_t timer_isr_callback_add(timer_group_t group_num, timer_idx_t timer_num, timer_isr_t isr_handler, void *arg, int intr_alloc_flags); |
| 182 | +//esp_err_t timer_isr_callback_remove(timer_group_t group_num, timer_idx_t timer_num); |
| 183 | +//timer_deinit(timer_group_t group_num, timer_idx_t timer_num); |
| 184 | +//esp_err_t timer_group_intr_enable(timer_group_t group_num, timer_intr_t intr_mask); |
| 185 | +//esp_err_t timer_group_intr_disable(timer_group_t group_num, timer_intr_t intr_mask); |
| 186 | + |
| 187 | + |
| 188 | +typedef bool (*esp32_timer_callback) (void *); |
| 189 | + |
| 190 | +class ESP32FastTimerInterrupt |
| 191 | +{ |
| 192 | + private: |
| 193 | + |
| 194 | + timer_config_t stdConfig = |
| 195 | + { |
| 196 | + .alarm_en = TIMER_ALARM_EN, //enable timer alarm |
| 197 | + .counter_en = TIMER_START, //starts counting counter once timer_init called |
| 198 | + .intr_type = TIMER_INTR_MAX, |
| 199 | + .counter_dir = TIMER_COUNT_UP, //counts from 0 to counter value |
| 200 | + .auto_reload = TIMER_AUTORELOAD_EN, // reloads counter automatically |
| 201 | + .divider = TIMER_DIVIDER |
| 202 | + }; |
| 203 | + |
| 204 | + timer_idx_t _timerIndex; |
| 205 | + timer_group_t _timerGroup; |
| 206 | + uint32_t interruptFlag; // either TIMER_INTR_T0 or TIMER_INTR_T1 |
| 207 | + |
| 208 | + uint8_t _timerNo; |
| 209 | + |
| 210 | + esp32_timer_callback _callback; // pointer to the callback function |
| 211 | + float _frequency; // Timer frequency |
| 212 | + uint64_t _timerCount; // count to activate timer |
| 213 | + |
| 214 | + public: |
| 215 | + |
| 216 | + ESP32FastTimerInterrupt(uint8_t timerNo) |
| 217 | + { |
| 218 | + if (timerNo < MAX_ESP32_NUM_TIMERS) |
| 219 | + { |
| 220 | + _timerNo = timerNo; |
| 221 | + |
| 222 | +#if USING_ESP32_C3_NEW_ISR_SERVO |
| 223 | + |
| 224 | + // Always using TIMER_INTR_T0 |
| 225 | + _timerIndex = (timer_idx_t) ( (uint32_t) 0 ); |
| 226 | + |
| 227 | + // timerNo == 0 => Group 0, timerNo == 1 => Group 1 |
| 228 | + _timerGroup = (timer_group_t) ( (uint32_t) timerNo); |
| 229 | + |
| 230 | +#else |
| 231 | + |
| 232 | + _timerIndex = (timer_idx_t) (_timerNo % TIMER_MAX); |
| 233 | + |
| 234 | + _timerGroup = (timer_group_t) (_timerNo / TIMER_MAX); |
| 235 | + |
| 236 | +#endif |
| 237 | + } |
| 238 | + else |
| 239 | + { |
| 240 | + _timerNo = MAX_ESP32_NUM_TIMERS; |
| 241 | + } |
| 242 | + |
| 243 | + _frequency = 0; |
| 244 | + _timerCount = 0; |
| 245 | + _callback = NULL; |
| 246 | + }; |
| 247 | + |
| 248 | + // frequency (in hertz) |
| 249 | + // No params and duration now. To be added in the future by adding similar functions here or to esp32-hal-timer.c |
| 250 | + bool setFrequency(const float& frequency, esp32_timer_callback callback) |
| 251 | + { |
| 252 | + if (_timerNo < MAX_ESP32_NUM_TIMERS) |
| 253 | + { |
| 254 | + // select timer frequency is 1MHz for better accuracy. We don't use 16-bit prescaler for now. |
| 255 | + // Will use later if very low frequency is needed. |
| 256 | + _frequency = TIMER_BASE_CLK / TIMER_DIVIDER; //1000000; |
| 257 | + _timerCount = (uint64_t) _frequency / frequency; |
| 258 | + // count up |
| 259 | + |
| 260 | +#if USING_ESP32_C3_NEW_ISR_SERVO |
| 261 | + ISR_SERVO_LOGERROR3(F("ESP32_C3_TimerInterrupt: _timerNo ="), _timerNo, F(", _fre ="), TIMER_BASE_CLK / TIMER_DIVIDER); |
| 262 | + ISR_SERVO_LOGERROR3(F("TIMER_BASE_CLK ="), TIMER_BASE_CLK, F(", TIMER_DIVIDER ="), TIMER_DIVIDER); |
| 263 | +#elif USING_ESP32_S2_NEW_ISR_SERVO |
| 264 | + ISR_SERVO_LOGERROR3(F("ESP32_S2_TimerInterrupt: _timerNo ="), _timerNo, F(", _fre ="), TIMER_BASE_CLK / TIMER_DIVIDER); |
| 265 | + ISR_SERVO_LOGERROR3(F("TIMER_BASE_CLK ="), TIMER_BASE_CLK, F(", TIMER_DIVIDER ="), TIMER_DIVIDER); |
| 266 | + ISR_SERVO_LOGERROR3(F("_timerIndex ="), _timerIndex, F(", _timerGroup ="), _timerGroup); |
| 267 | + ISR_SERVO_LOGERROR3(F("_count ="), (uint32_t) (_timerCount >> 32) , F("-"), (uint32_t) (_timerCount)); |
| 268 | + ISR_SERVO_LOGERROR1(F("timer_set_alarm_value ="), TIMER_SCALE / frequency); |
| 269 | +#elif USING_ESP32_S3_NEW_ISR_SERVO |
| 270 | + // ESP32-S3 is embedded with four 54-bit general-purpose timers, which are based on 16-bit prescalers |
| 271 | + // and 54-bit auto-reload-capable up/down-timers |
| 272 | + ISR_SERVO_LOGERROR3(F("ESP32_S3_TimerInterrupt: _timerNo ="), _timerNo, F(", _fre ="), TIMER_BASE_CLK / TIMER_DIVIDER); |
| 273 | + ISR_SERVO_LOGERROR3(F("TIMER_BASE_CLK ="), TIMER_BASE_CLK, F(", TIMER_DIVIDER ="), TIMER_DIVIDER); |
| 274 | + ISR_SERVO_LOGERROR3(F("_timerIndex ="), _timerIndex, F(", _timerGroup ="), _timerGroup); |
| 275 | + ISR_SERVO_LOGERROR3(F("_count ="), (uint32_t) (_timerCount >> 32) , F("-"), (uint32_t) (_timerCount)); |
| 276 | + ISR_SERVO_LOGERROR1(F("timer_set_alarm_value ="), TIMER_SCALE / frequency); |
| 277 | +#else |
| 278 | + ISR_SERVO_LOGERROR3(F("ESP32_TimerInterrupt: _timerNo ="), _timerNo, F(", _fre ="), TIMER_BASE_CLK / TIMER_DIVIDER); |
| 279 | + ISR_SERVO_LOGERROR3(F("TIMER_BASE_CLK ="), TIMER_BASE_CLK, F(", TIMER_DIVIDER ="), TIMER_DIVIDER); |
| 280 | + ISR_SERVO_LOGERROR3(F("_timerIndex ="), _timerIndex, F(", _timerGroup ="), _timerGroup); |
| 281 | + ISR_SERVO_LOGERROR3(F("_count ="), (uint32_t) (_timerCount >> 32) , F("-"), (uint32_t) (_timerCount)); |
| 282 | + ISR_SERVO_LOGERROR1(F("timer_set_alarm_value ="), TIMER_SCALE / frequency); |
| 283 | +#endif |
| 284 | + |
| 285 | + timer_init(_timerGroup, _timerIndex, &stdConfig); |
| 286 | + |
| 287 | + // Counter value to 0 => counting up to alarm value as .counter_dir == TIMER_COUNT_UP |
| 288 | + timer_set_counter_value(_timerGroup, _timerIndex , 0x00000000ULL); |
| 289 | + |
| 290 | + timer_set_alarm_value(_timerGroup, _timerIndex, TIMER_SCALE / frequency); |
| 291 | + |
| 292 | + // enable interrupts for _timerGroup, _timerIndex |
| 293 | + timer_enable_intr(_timerGroup, _timerIndex); |
| 294 | + |
| 295 | + _callback = callback; |
| 296 | + |
| 297 | + // Register the ISR handler |
| 298 | + // If the intr_alloc_flags value ESP_INTR_FLAG_IRAM is set, the handler function must be declared with IRAM_ATTR attribute |
| 299 | + // and can only call functions in IRAM or ROM. It cannot call other timer APIs. |
| 300 | + timer_isr_callback_add(_timerGroup, _timerIndex, _callback, (void *) (uint32_t) _timerNo, 0); |
| 301 | + |
| 302 | + timer_start(_timerGroup, _timerIndex); |
| 303 | + |
| 304 | + return true; |
| 305 | + } |
| 306 | + else |
| 307 | + { |
| 308 | +#if USING_ESP32_C3_NEW_ISR_SERVO |
| 309 | + ISR_SERVO_LOGERROR(F("Error. Timer must be 0-1")); |
| 310 | +#else |
| 311 | + ISR_SERVO_LOGERROR(F("Error. Timer must be 0-3")); |
| 312 | +#endif |
| 313 | + |
| 314 | + return false; |
| 315 | + } |
| 316 | + } |
| 317 | + |
| 318 | + // interval (in microseconds) and duration (in milliseconds). Duration = 0 or not specified => run indefinitely |
| 319 | + // No params and duration now. To be addes in the future by adding similar functions here or to esp32-hal-timer.c |
| 320 | + bool attachInterruptInterval(const unsigned long& interval, esp32_timer_callback callback) |
| 321 | + { |
| 322 | + return setFrequency( (float) ( 1000000.0f / interval), callback); |
| 323 | + } |
| 324 | + |
| 325 | + void detachInterrupt() |
| 326 | + { |
| 327 | +#if USING_ESP32_C3_NEW_ISR_SERVO |
| 328 | + timer_group_intr_disable(_timerGroup, TIMER_INTR_T0); |
| 329 | +#else |
| 330 | + timer_group_intr_disable(_timerGroup, (_timerIndex == 0) ? TIMER_INTR_T0 : TIMER_INTR_T1); |
| 331 | +#endif |
| 332 | + } |
| 333 | + |
| 334 | + // Duration (in milliseconds). Duration = 0 or not specified => run indefinitely |
| 335 | + void reattachInterrupt() |
| 336 | + { |
| 337 | + if ( (_frequency != 0) && (_timerCount != 0) && (_callback != NULL) ) |
| 338 | + { |
| 339 | +#if USING_ESP32_C3_NEW_ISR_SERVO |
| 340 | + timer_group_intr_enable(_timerGroup, TIMER_INTR_T0); |
| 341 | +#else |
| 342 | + timer_group_intr_enable(_timerGroup, (_timerIndex == 0) ? TIMER_INTR_T0 : TIMER_INTR_T1); |
| 343 | +#endif |
| 344 | + } |
| 345 | + } |
| 346 | +}; // class ESP32FastTimerInterrupt |
| 347 | + |
| 348 | + |
| 349 | +#endif // ESP32_S2_FastTimerInterrupt_h |
0 commit comments