|
| 1 | +/**************************************************************************************************************************** |
| 2 | + ISR_16_Timers_Array_Complex_OneShot.ino |
| 3 | + For ESP32, ESP32_S2, ESP32_S3, ESP32_C3 boards with ESP32 core v2.0.2+ |
| 4 | + Written by Khoi Hoang |
| 5 | +
|
| 6 | + Built by Khoi Hoang https://github.com/khoih-prog/ESP32TimerInterrupt |
| 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 even you use all these new 16 ISR-based timers,with their maximum interval practically unlimited (limited only by |
| 19 | + unsigned long miliseconds), you just consume only one ESP32-S2 timer and avoid conflicting with other cores' tasks. |
| 20 | + The accuracy is nearly perfect compared to software timers. The most important feature is they're ISR-based timers |
| 21 | + Therefore, their executions are not blocked by bad-behaving functions / tasks. |
| 22 | + This important feature is absolutely necessary for mission-critical tasks. |
| 23 | +*****************************************************************************************************************************/ |
| 24 | +/* |
| 25 | + Notes: |
| 26 | + Special design is necessary to share data between interrupt code and the rest of your program. |
| 27 | + Variables usually need to be "volatile" types. Volatile tells the compiler to avoid optimizations that assume |
| 28 | + variable can not spontaneously change. Because your function may change variables while your program is using them, |
| 29 | + the compiler needs this hint. But volatile alone is often not enough. |
| 30 | + When accessing shared variables, usually interrupts must be disabled. Even with volatile, |
| 31 | + if the interrupt changes a multi-byte variable between a sequence of instructions, it can be read incorrectly. |
| 32 | + If your data is multiple variables, such as an array and a count, usually interrupts need to be disabled |
| 33 | + or the entire sequence of your code which accesses the data. |
| 34 | +
|
| 35 | + This example will demonstrate the nearly perfect accuracy compared to software timers by printing the actual elapsed millisecs. |
| 36 | + Being ISR-based timers, their executions are not blocked by bad-behaving functions / tasks, such as connecting to WiFi, Internet |
| 37 | + and Blynk services. You can also have many (up to 16) timers to use. |
| 38 | + This non-being-blocked important feature is absolutely necessary for mission-critical tasks. |
| 39 | + You'll see blynkTimer is blocked while connecting to WiFi / Internet / Blynk, and elapsed time is very unaccurate |
| 40 | + In this super simple example, you don't see much different after Blynk is connected, because of no competing task is |
| 41 | + written |
| 42 | +*/ |
| 43 | + |
| 44 | +#if !defined( ESP32 ) |
| 45 | + #error This code is intended to run on the ESP32 platform! Please check your Tools->Board setting. |
| 46 | +#endif |
| 47 | + |
| 48 | +// These define's must be placed at the beginning before #include "ESP32TimerInterrupt.h" |
| 49 | +#define _TIMERINTERRUPT_LOGLEVEL_ 4 |
| 50 | + |
| 51 | +// To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error |
| 52 | +#include "ESP32TimerInterrupt.h" |
| 53 | + |
| 54 | +#include <SimpleTimer.h> // https://github.com/jfturcot/SimpleTimer |
| 55 | + |
| 56 | +// Don't use PIN_D1 in core v2.0.0 and v2.0.1. Check https://github.com/espressif/arduino-esp32/issues/5868 |
| 57 | + |
| 58 | +#ifndef LED_BUILTIN |
| 59 | + #define LED_BUILTIN 2 |
| 60 | +#endif |
| 61 | + |
| 62 | +#ifndef LED_BLUE |
| 63 | + #define LED_BLUE 25 |
| 64 | +#endif |
| 65 | + |
| 66 | +#ifndef LED_RED |
| 67 | + #define LED_RED 27 |
| 68 | +#endif |
| 69 | + |
| 70 | +#define HW_TIMER_INTERVAL_US 10000L |
| 71 | + |
| 72 | +volatile uint32_t startMillis = 0; |
| 73 | + |
| 74 | +// Init ESP32 timer 1 |
| 75 | +ESP32Timer ITimer(1); |
| 76 | + |
| 77 | +// Init ESP32_ISR_Timer |
| 78 | +ESP32_ISR_Timer ISR_Timer; |
| 79 | + |
| 80 | +#define LED_TOGGLE_INTERVAL_MS 2000L |
| 81 | + |
| 82 | +// With core v2.0.0+, you can't use Serial.print/println in ISR or crash. |
| 83 | +// and you can't use float calculation inside ISR |
| 84 | +// Only OK in core v1.0.6- |
| 85 | +bool IRAM_ATTR TimerHandler(void * timerNo) |
| 86 | +{ |
| 87 | + static bool toggle = false; |
| 88 | + static int timeRun = 0; |
| 89 | + |
| 90 | + ISR_Timer.run(); |
| 91 | + |
| 92 | + // Toggle LED every LED_TOGGLE_INTERVAL_MS = 2000ms = 2s |
| 93 | + if (++timeRun == ((LED_TOGGLE_INTERVAL_MS * 1000) / HW_TIMER_INTERVAL_US) ) |
| 94 | + { |
| 95 | + timeRun = 0; |
| 96 | + |
| 97 | + //timer interrupt toggles pin LED_BUILTIN |
| 98 | + digitalWrite(LED_BUILTIN, toggle); |
| 99 | + toggle = !toggle; |
| 100 | + } |
| 101 | + |
| 102 | + return true; |
| 103 | +} |
| 104 | + |
| 105 | +///////////////////////////////////////////////// |
| 106 | + |
| 107 | +#define NUMBER_ISR_TIMERS 16 |
| 108 | + |
| 109 | +typedef void (*irqCallback) (); |
| 110 | + |
| 111 | +///////////////////////////////////////////////// |
| 112 | + |
| 113 | +#define USE_COMPLEX_STRUCT true |
| 114 | + |
| 115 | +#if USE_COMPLEX_STRUCT |
| 116 | + |
| 117 | +typedef struct |
| 118 | +{ |
| 119 | + irqCallback irqCallbackFunc; |
| 120 | + uint32_t TimerInterval; |
| 121 | + unsigned long deltaMillis; |
| 122 | + unsigned long previousMillis; |
| 123 | +} ISRTimerData; |
| 124 | + |
| 125 | +// In ESP32, avoid doing something fancy in ISR, for example Serial.print() |
| 126 | +// The pure simple Serial.prints here are just for demonstration and testing. Must be eliminate in working environment |
| 127 | +// Or you can get this run-time error / crash |
| 128 | + |
| 129 | +void doingSomething(int index); |
| 130 | + |
| 131 | +#else |
| 132 | + |
| 133 | +volatile unsigned long deltaMillis [NUMBER_ISR_TIMERS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; |
| 134 | +volatile unsigned long previousMillis [NUMBER_ISR_TIMERS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; |
| 135 | + |
| 136 | +// You can assign any interval for any timer here, in milliseconds |
| 137 | +uint32_t TimerInterval[NUMBER_ISR_TIMERS] = |
| 138 | +{ |
| 139 | + 5000L, 10000L, 15000L, 20000L, 25000L, 30000L, 35000L, 40000L, |
| 140 | + 45000L, 50000L, 55000L, 60000L, 65000L, 70000L, 75000L, 80000L |
| 141 | +}; |
| 142 | + |
| 143 | +void doingSomething(int index) |
| 144 | +{ |
| 145 | + unsigned long currentMillis = millis(); |
| 146 | + |
| 147 | + deltaMillis[index] = currentMillis - previousMillis[index]; |
| 148 | + previousMillis[index] = currentMillis; |
| 149 | +} |
| 150 | + |
| 151 | +#endif |
| 152 | + |
| 153 | +//////////////////////////////////// |
| 154 | +// Shared |
| 155 | +//////////////////////////////////// |
| 156 | + |
| 157 | +void doingSomething0() |
| 158 | +{ |
| 159 | + doingSomething(0); |
| 160 | +} |
| 161 | + |
| 162 | +void doingSomething1() |
| 163 | +{ |
| 164 | + doingSomething(1); |
| 165 | +} |
| 166 | + |
| 167 | +void doingSomething2() |
| 168 | +{ |
| 169 | + doingSomething(2); |
| 170 | +} |
| 171 | + |
| 172 | +void doingSomething3() |
| 173 | +{ |
| 174 | + doingSomething(3); |
| 175 | +} |
| 176 | + |
| 177 | +void doingSomething4() |
| 178 | +{ |
| 179 | + doingSomething(4); |
| 180 | +} |
| 181 | + |
| 182 | +void doingSomething5() |
| 183 | +{ |
| 184 | + doingSomething(5); |
| 185 | +} |
| 186 | + |
| 187 | +void doingSomething6() |
| 188 | +{ |
| 189 | + doingSomething(6); |
| 190 | +} |
| 191 | + |
| 192 | +void doingSomething7() |
| 193 | +{ |
| 194 | + doingSomething(7); |
| 195 | +} |
| 196 | + |
| 197 | +void doingSomething8() |
| 198 | +{ |
| 199 | + doingSomething(8); |
| 200 | +} |
| 201 | + |
| 202 | +void doingSomething9() |
| 203 | +{ |
| 204 | + doingSomething(9); |
| 205 | +} |
| 206 | + |
| 207 | +void doingSomething10() |
| 208 | +{ |
| 209 | + doingSomething(10); |
| 210 | +} |
| 211 | + |
| 212 | +void doingSomething11() |
| 213 | +{ |
| 214 | + doingSomething(11); |
| 215 | +} |
| 216 | + |
| 217 | +void doingSomething12() |
| 218 | +{ |
| 219 | + doingSomething(12); |
| 220 | +} |
| 221 | + |
| 222 | +void doingSomething13() |
| 223 | +{ |
| 224 | + doingSomething(13); |
| 225 | +} |
| 226 | + |
| 227 | +void doingSomething14() |
| 228 | +{ |
| 229 | + doingSomething(14); |
| 230 | +} |
| 231 | + |
| 232 | +void doingSomething15() |
| 233 | +{ |
| 234 | + doingSomething(15); |
| 235 | +} |
| 236 | + |
| 237 | +#if USE_COMPLEX_STRUCT |
| 238 | + |
| 239 | +ISRTimerData curISRTimerData[NUMBER_ISR_TIMERS] = |
| 240 | +{ |
| 241 | + //irqCallbackFunc, TimerInterval, deltaMillis, previousMillis |
| 242 | + { doingSomething0, 5000L, 0, 0 }, |
| 243 | + { doingSomething1, 10000L, 0, 0 }, |
| 244 | + { doingSomething2, 15000L, 0, 0 }, |
| 245 | + { doingSomething3, 20000L, 0, 0 }, |
| 246 | + { doingSomething4, 25000L, 0, 0 }, |
| 247 | + { doingSomething5, 30000L, 0, 0 }, |
| 248 | + { doingSomething6, 35000L, 0, 0 }, |
| 249 | + { doingSomething7, 40000L, 0, 0 }, |
| 250 | + { doingSomething8, 45000L, 0, 0 }, |
| 251 | + { doingSomething9, 50000L, 0, 0 }, |
| 252 | + { doingSomething10, 55000L, 0, 0 }, |
| 253 | + { doingSomething11, 60000L, 0, 0 }, |
| 254 | + { doingSomething12, 65000L, 0, 0 }, |
| 255 | + { doingSomething13, 70000L, 0, 0 }, |
| 256 | + { doingSomething14, 75000L, 0, 0 }, |
| 257 | + { doingSomething15, 80000L, 0, 0 } |
| 258 | +}; |
| 259 | + |
| 260 | +void doingSomething(int index) |
| 261 | +{ |
| 262 | + unsigned long currentMillis = millis(); |
| 263 | + |
| 264 | + curISRTimerData[index].deltaMillis = currentMillis - curISRTimerData[index].previousMillis; |
| 265 | + curISRTimerData[index].previousMillis = currentMillis; |
| 266 | +} |
| 267 | + |
| 268 | +#else |
| 269 | + |
| 270 | +irqCallback irqCallbackFunc[NUMBER_ISR_TIMERS] = |
| 271 | +{ |
| 272 | + doingSomething0, doingSomething1, doingSomething2, doingSomething3, |
| 273 | + doingSomething4, doingSomething5, doingSomething6, doingSomething7, |
| 274 | + doingSomething8, doingSomething9, doingSomething10, doingSomething11, |
| 275 | + doingSomething12, doingSomething13, doingSomething14, doingSomething15 |
| 276 | +}; |
| 277 | + |
| 278 | +#endif |
| 279 | +/////////////////////////////////////////// |
| 280 | + |
| 281 | +#define SIMPLE_TIMER_MS 2000L |
| 282 | + |
| 283 | +// Init SimpleTimer |
| 284 | +SimpleTimer simpleTimer; |
| 285 | + |
| 286 | +// Here is software Timer, you can do somewhat fancy stuffs without many issues. |
| 287 | +// But always avoid |
| 288 | +// 1. Long delay() it just doing nothing and pain-without-gain wasting CPU power.Plan and design your code / strategy ahead |
| 289 | +// 2. Very long "do", "while", "for" loops without predetermined exit time. |
| 290 | +void simpleTimerDoingSomething2s() |
| 291 | +{ |
| 292 | + static unsigned long previousMillis = startMillis; |
| 293 | + |
| 294 | + unsigned long currMillis = millis(); |
| 295 | + |
| 296 | + Serial.print(F("SimpleTimer : ")); Serial.print(SIMPLE_TIMER_MS / 1000); |
| 297 | + Serial.print(F(", ms : ")); Serial.print(currMillis); |
| 298 | + Serial.print(F(", Dms : ")); Serial.println(currMillis - previousMillis); |
| 299 | + |
| 300 | + for (uint16_t i = 0; i < NUMBER_ISR_TIMERS; i++) |
| 301 | + { |
| 302 | +#if USE_COMPLEX_STRUCT |
| 303 | + Serial.print(F("Timer : ")); Serial.print(i); |
| 304 | + Serial.print(F(", programmed : ")); Serial.print(curISRTimerData[i].TimerInterval); |
| 305 | + Serial.print(F(", actual : ")); Serial.println(curISRTimerData[i].deltaMillis); |
| 306 | + // reset to 0 to be sure the timer is running one-shot or permanent |
| 307 | + curISRTimerData[i].deltaMillis = 0; |
| 308 | +#else |
| 309 | + Serial.print(F("Timer : ")); Serial.print(i); |
| 310 | + Serial.print(F(", programmed : ")); Serial.print(TimerInterval[i]); |
| 311 | + Serial.print(F(", actual : ")); Serial.println(deltaMillis[i]); |
| 312 | + // reset to 0 to be sure the timer is running one-shot or permanent |
| 313 | + deltaMillis[i] = 0; |
| 314 | +#endif |
| 315 | + } |
| 316 | + |
| 317 | + previousMillis = currMillis; |
| 318 | +} |
| 319 | + |
| 320 | +void setup() |
| 321 | +{ |
| 322 | + pinMode(LED_BUILTIN, OUTPUT); |
| 323 | + |
| 324 | + Serial.begin(115200); |
| 325 | + while (!Serial); |
| 326 | + |
| 327 | + delay(200); |
| 328 | + |
| 329 | + Serial.print(F("\nStarting ISR_16_Timers_Array_Complex_OneShot on ")); Serial.println(ARDUINO_BOARD); |
| 330 | + Serial.println(ESP32_TIMER_INTERRUPT_VERSION); |
| 331 | + Serial.print(F("CPU Frequency = ")); Serial.print(F_CPU / 1000000); Serial.println(F(" MHz")); |
| 332 | + |
| 333 | + // Interval in microsecs |
| 334 | + if (ITimer.attachInterruptInterval(HW_TIMER_INTERVAL_US, TimerHandler)) |
| 335 | + { |
| 336 | + startMillis = millis(); |
| 337 | + Serial.print(F("Starting ITimer OK, millis() = ")); Serial.println(startMillis); |
| 338 | + } |
| 339 | + else |
| 340 | + Serial.println(F("Can't set ITimer. Select another freq. or timer")); |
| 341 | + |
| 342 | + startMillis = millis(); |
| 343 | + |
| 344 | + // Just to demonstrate, don't use too many ISR Timers if not absolutely necessary |
| 345 | + // You can use up to 16 timer for each ISR_Timer |
| 346 | + for (uint16_t i = 0; i < NUMBER_ISR_TIMERS; i++) |
| 347 | + { |
| 348 | +#if USE_COMPLEX_STRUCT |
| 349 | + curISRTimerData[i].previousMillis = startMillis; |
| 350 | + // Set even timer one shot, odd timers run forever |
| 351 | + if ( i % 2 ) |
| 352 | + ISR_Timer.setInterval(curISRTimerData[i].TimerInterval, curISRTimerData[i].irqCallbackFunc); |
| 353 | + else |
| 354 | + ISR_Timer.setTimeout(curISRTimerData[i].TimerInterval, curISRTimerData[i].irqCallbackFunc); |
| 355 | +#else |
| 356 | + previousMillis[i] = millis(); |
| 357 | + |
| 358 | + // Set even timer one shot, odd timers run forever |
| 359 | + if ( i % 2 ) |
| 360 | + ISR_Timer.setInterval(TimerInterval[i], irqCallbackFunc[i]); |
| 361 | + else |
| 362 | + ISR_Timer.setTimeout(TimerInterval[i], irqCallbackFunc[i]); |
| 363 | +#endif |
| 364 | + } |
| 365 | + |
| 366 | + // You need this timer for non-critical tasks. Avoid abusing ISR if not absolutely necessary. |
| 367 | + simpleTimer.setInterval(SIMPLE_TIMER_MS, simpleTimerDoingSomething2s); |
| 368 | +} |
| 369 | + |
| 370 | +#define BLOCKING_TIME_MS 10000L |
| 371 | + |
| 372 | +void loop() |
| 373 | +{ |
| 374 | + // This unadvised blocking task is used to demonstrate the blocking effects onto the execution and accuracy to Software timer |
| 375 | + // You see the time elapse of ISR_Timer still accurate, whereas very unaccurate for Software Timer |
| 376 | + // The time elapse for 2000ms software timer now becomes 3000ms (BLOCKING_TIME_MS) |
| 377 | + // While that of ISR_Timer is still prefect. |
| 378 | + delay(BLOCKING_TIME_MS); |
| 379 | + |
| 380 | + // You need this Software timer for non-critical tasks. Avoid abusing ISR if not absolutely necessary |
| 381 | + // You don't need to and never call ISR_Timer.run() here in the loop(). It's already handled by ISR timer. |
| 382 | + simpleTimer.run(); |
| 383 | +} |
0 commit comments