ESP 32 Elapsed Timer Alerting Framework
A totally flexible and general timer interrupt framework for designing solutions needing one or many individual elapsing and separate timers
Components and supplies
ESP32S
Apps and platforms
Arduino IDE
Project description
Code
Example 1: An ESP 32 sketch to configure an interrupt timer
c_cpp
A sketch to demonstrate configuring an ESP 32 interrupt timer
1// 2// Basic example of ESP 32 interrupt timer sketch 3// 4// This example and code is in the public domain and 5// may be used without restriction and without warranty. 6// 7volatile int interrupt_counter; 8volatile int current_interrupt_counter; 9int total_interrupt_counter; 10 11hw_timer_t * timer = NULL; 12portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; 13 14void IRAM_ATTR onTimer() { 15 portENTER_CRITICAL_ISR(&timerMux); 16 interrupt_counter++; 17 portEXIT_CRITICAL_ISR(&timerMux); 18} 19 20void setup() { 21 Serial.begin(115200); 22 timer = timerBegin(0, 80, true); // prescale down to microsecond timing 23 timerAttachInterrupt(timer, &onTimer, true); 24 timerAlarmWrite(timer, 1000, true); // step down to millisecond interrupts 25 timerAlarmEnable(timer); 26} 27 28void loop() { 29 // Take a copy of the current interrupt_counter value 30 // so we may test it in our if/then process without 31 // running into a conflict between our ISR and main 32 // loop processing 33 portENTER_CRITICAL(&timerMux); 34 current_interrupt_counter = interrupt_counter; 35 portEXIT_CRITICAL(&timerMux); 36 if (current_interrupt_counter > 0) { 37 // A timer interrupt has occurred 38 portENTER_CRITICAL(&timerMux); 39 interrupt_counter--; 40 portEXIT_CRITICAL(&timerMux); 41 total_interrupt_counter++; 42 // report every 1000 interrupts, ie every 1 second 43 if (total_interrupt_counter % 1000 == 0) { 44 Serial.print("A timer interrupt has occurred. Total number: "); 45 Serial.println(total_interrupt_counter); 46 Serial.flush(); 47 } 48 } 49} 50
Example 3: A Home Environmental Monitor based on the ETA Framework
c_cpp
An example of how we can use the ETA Framework for developing projects needing multiple elapsing timers. This dummy example will monitor temp, humidity, soil moisture and light levels.
1// 2// R D Bentley (Stafford, UK), May 2022 3// 4// This example and code is in the public domain and 5// may be used without restriction and without warranty. 6// 7// ESP 32 Multiple Elapsed Timer Alerting - Home Environmental Monitoring Example 8// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 9// 10// This example shows how the ETA framework can be used to define elapsed time 11// alerts that can be used to process specific event requirements, here a home 12// environment monitoring requirement. 13// The example does not provide a complete solution, rather it provides the hooks 14// and eyes for fully integrated solution for the monitoring and adjustments of 15// temperature, humidity, light and soil moisture that my be necessary for maintaining 16// for example, garden plants, etc. 17// 18// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 19// % Timer declarations and timer ISR % 20// % Initialisation occurs in setup() % 21// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 22// 23volatile int interrupt_counter; // used to signify a timer interrupt event has occurred 24volatile int current_interrupt_counter; 25hw_timer_t * timer = NULL; // establish timer structure 26portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; 27 28// 29// The timer_ISR simply increments the varuable interrupt_counter. 30// This variable is decremented by the main loop which will deal with 31// timer interrupt events until they have all been processed, ie 32// until interrupt_counter = 0 33// 34void IRAM_ATTR timer_ISR() { 35 portENTER_CRITICAL_ISR(&timerMux); 36 interrupt_counter++; 37 portEXIT_CRITICAL_ISR(&timerMux); 38} 39 40#define max_num_timers 4 // 4 timers on ESP 32 - 0-3 41#define default_timer 0 // used if specified timer is invalid 42 43// 44// Create an ESP 32 timer instance for given parameter 45// 46void create_and_start_timer(uint8_t timer_num) { 47 if (timer_num >= max_num_timers) {// valid range is 0 to 3 48 Serial.println("!!Invalid timer number - restting to default timer!!"); 49 Serial.flush(); 50 timer_num = default_timer; 51 } 52 // set up the timer interrupt for 1 millisecond interrupts 53 timer = timerBegin(timer_num, 80, true); // 80 Mhz processor, scale down to 1 Mhz 54 timerAttachInterrupt(timer, &timer_ISR, true); // link to our interrupt service routine (ISR) 55 timerAlarmWrite(timer, 1000, true); // define for 1 millisecond interrupts 0.001 Mhz 56 timerAlarmEnable(timer); // start timer 57} 58 59// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 60// % Elapsed Time Alert(s) declarations and functions % 61// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 62 63#define max_ETAs 7 // User defined - we are configuring for 7 ETAs 64 65// ETA function and decision control macros 66#define one_off_ETA 1 67#define recurring_ETA 2 68#define ETA_inserted 1 // positive result for success 69#define ETA_insert_failure -1 // negative result for failure 70#define ETA_invalid_type -2 // ditto 71#define ETA_duplicate -3 // ditto 72#define ETA_extracted true // converse is !ETA_extracted 73#define ETA_deleted true // converse is !ETA_deleted 74 75// ETA_struct is the control structure at the heart of this design 76// It will hold details for each ETA created 77struct ETA_struct { 78 uint8_t ETA_type; 79 uint16_t ETA_alert_id; // alert id range is 0 - 65,535 80 uint32_t ETA_interval; 81 uint32_t ETA_count_down; 82} ETAs[max_ETAs]; 83 84// ETA_alerts_struct is used dynamically to record the details of each 85// ETA that has elapsed. It is this structure that is used for processing 86// elapsed ETAs. It has the form that entry [0] holds a count of the number 87// of entries that follow in the structure. ETA details then follow on from 88// entry [1], for example: 89// [0]:3 (int) 90// [1]:1, 23, (unit8_t/uint16_t) 91// [2]:2, 2047, (unit8_t/uint16_t) 92// [3]:1, 97 (unit8_t/uint16_t) 93struct ETA_alerts_struct { 94 uint8_t ETA_type; 95 uint16_t ETA_alert_id; 96 union { 97 uint16_t ETA_num_alerts; 98 }; 99} ETA_alerts[max_ETAs + 1]; // reserve an entry for the list count 100 101// Creates an ETA with the given parameters. Validation of the parameters 102// is carried out. The ETA is created if there is available space in the 103// ETA structure. Note also that ETAs must be unique in the ETA structure. 104// An attempt to create a duplicate ETA will generate an error 105// 106int create_ETA(uint8_t ETA_type, 107 uint16_t ETA_alert_id, 108 uint32_t ETA_interval) { 109 if (ETA_type != one_off_ETA && 110 ETA_type != recurring_ETA)return ETA_invalid_type; 111 int free_entry = -1; // used to track first free ETA entry in ETA list 112 for (int entry = max_ETAs - 1; entry >= 0; entry--) { 113 if (ETAs[entry].ETA_type == 0) { 114 // this is an empty slot so remember it 115 free_entry = entry; 116 } else { 117 // this entry is already occupied so check if it is the same 118 // as the ETA create requested 119 if (ETAs[entry].ETA_type == ETA_type && 120 ETAs[entry].ETA_alert_id == ETA_alert_id) { 121 // an ETA entry already exists for the requested ETA create 122 return ETA_duplicate; 123 } 124 } 125 } 126 // ok, so requested ETA is not already in the ETA list 127 // so add it if there is space, ie free_entry != -1 128 if (free_entry != -1) { 129 ETAs[free_entry].ETA_type = ETA_type; // eg one off or recurring ETA 130 ETAs[free_entry].ETA_alert_id = ETA_alert_id; 131 ETAs[free_entry].ETA_interval = ETA_interval; 132 ETAs[free_entry].ETA_count_down = ETA_interval; 133 return ETA_inserted; 134 } 135 return ETA_insert_failure; 136} 137 138// 139// Delete the given ETA from the ETA control structure 140// 141bool delete_ETA(uint8_t ETA_type, 142 uint16_t ETA_alert_id) { 143 for (uint8_t entry = 0; entry < max_ETAs; entry++) { 144 if (ETAs[entry].ETA_type == ETA_type && 145 ETAs[entry].ETA_alert_id == ETA_alert_id) { 146 // found, so delete it 147 ETAs[entry].ETA_type = 0; 148 return ETA_deleted; 149 } 150 } 151 return !ETA_deleted; 152} 153 154// 155// Prints the ETA structure contents. Useful for confirming 156// that the configuration for ETA is as required 157// 158void print_ETAs() { 159 Serial.begin(115200); 160 for (uint8_t entry = 0; entry < max_ETAs; entry++) { 161 Serial.print("\ 162ETA entry = "); 163 Serial.print(entry); 164 if (ETAs[entry].ETA_type != 0) { 165 Serial.print("\ 166 ETA_type = "); 167 if (ETAs[entry].ETA_type == one_off_ETA) 168 Serial.println("one off ETA"); 169 else Serial.println("recurring ETA"); 170 Serial.print(" ETA_alert_id = "); 171 Serial.println(ETAs[entry].ETA_alert_id); 172 Serial.print(" ETA_interval = "); 173 Serial.println(ETAs[entry].ETA_interval); 174 Serial.print(" ETA_count_down = "); 175 Serial.println(ETAs[entry].ETA_count_down); 176 } else { 177 Serial.println(" *** entry empty"); 178 } 179 } 180 Serial.flush(); 181} 182 183// 184// Extracts an ETA alert from the ETA alert structure. Note that the 185// entries in this structure have been generated because their respective 186// ETAs have elapsed. The ETA alert structure should be continually 187// processed until it is empty. If an entry is removed then its ETA type 188// and ETA alert id are returned in ETA_type and ETA_alert_id respectively 189// along with a success return value (ETA_extracted). Otherwise these variables 190// are set to 0 with a corresponding failure return value (!ETA_extracted) 191// 192bool get_ETA_alert(uint8_t &ETA_type, 193 uint16_t &ETA_alert_id) { 194 uint8_t last_entry = ETA_alerts[0].ETA_num_alerts; 195 if (last_entry > 0) { 196 ETA_type = ETA_alerts[last_entry].ETA_type; 197 ETA_alert_id = ETA_alerts[last_entry].ETA_alert_id; 198 ETA_alerts[last_entry].ETA_type = 0; 199 ETA_alerts[last_entry].ETA_alert_id = 0; 200 ETA_alerts[0].ETA_num_alerts--; // reduce list by 1 201 return ETA_extracted; 202 } 203 ETA_type = 0; 204 ETA_alert_id = 0; 205 return !ETA_extracted; 206} 207 208// 209// clear down the ETA structure and reset the alert count 210// of the ETA alerts strucure 211// 212void clear_ETAs() { 213 // ETA_type = 0 signifies an empty slot 214 for (uint8_t entry = 0; entry < max_ETAs; entry++) { 215 ETAs[entry].ETA_type = 0; 216 } 217 ETA_alerts[0].ETA_num_alerts = 0; // reset alert list counter 218} 219 220// 221// The function scans the ETA control, structure and decrements any ETAs 222// defined. If an ETA has reached its count down interval it will be added 223// to the ETA_alerts structure so that it may be processed subsequently 224// and asynchronously 225// 226void update_ETAs() { 227 for (uint8_t entry = 0; entry < max_ETAs; entry++) { 228 if (ETAs[entry].ETA_type != 0) { 229 // this ETA entry is defined 230 if (ETAs[entry].ETA_count_down > 0) { 231 // decrement count down timer, it has not yet elapsed 232 ETAs[entry].ETA_count_down--; // reduce elapse interval by 1 233 if (ETAs[entry].ETA_count_down == 0) { 234 // add this lapsed ETA alert to the ETA alert list 235 int next_entry = ETA_alerts[0].ETA_num_alerts + 1; // next free entry in the list 236 ETA_alerts[next_entry].ETA_type = ETAs[entry].ETA_type; 237 ETA_alerts[next_entry].ETA_alert_id = ETAs[entry].ETA_alert_id; 238 ETA_alerts[0].ETA_num_alerts = next_entry; 239 // now determine if this ETA needs to be reset or removed/deleted 240 if (ETAs[entry].ETA_type == recurring_ETA) { 241 // recurring ETA so reset the elapse interval count down 242 ETAs[entry].ETA_count_down = ETAs[entry].ETA_interval; 243 } else { 244 // this must be a one off ETA, so delete it 245 ETAs[entry].ETA_type = 0; 246 } 247 } 248 } 249 } 250 } 251} 252 253// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 254// % Data specific to this use of the ETA framework for this example % 255// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 256// 257// These macros can be used for creating ETAs and are provided 258// for convenience of programming standard intervals, as required. 259// They may be modified to achieve the desired count down frequencies, 260// for example 2 * one_second, 5 * minute + 500, etc (all in milliseconds) 261// 262const uint32_t one_day = 86400000; // millisecs in 24 hours 263const uint32_t one_hour = 3600000; // millisecs in 1 hour 264const uint16_t one_minute = 60000; // millisecs in 1 minute 265const uint16_t one_second = 1000; // millisecs in 1 second 266 267// These macros define our ETA alert IDs for our sketch design/example 268#define heart_beat LED_BUILTIN // ETA alert ID - GPIO pin of in-built LED 269#define check_temp 10 // ETA alert ID - an arbitary but unique value 270#define check_moisture 20 // ETA alert ID - an arbitary but unique value 271#define check_humidity 30 // ETA alert ID - an arbitary but unique value 272#define check_light 40 // ETA alert ID - an arbitary but unique value 273#define watchdog 100 // ETA alert ID - an arbitary but unique value 274#define report_stats 200 // ETA alert ID - an arbitary but unique value 275 276// Now put together all of our ETA configs so we may create them more 277// easily in our setup process. The array is defined as follows: 278// my_ETA_data[][0] is the ETA_type, one off or recurring 279// [][1] is the ETA alert id defining the function/purpose of the ETA 280// [][2] is the duration for the ETA's elapsed time count down (duration) 281// in milliseconds 282// 283int my_ETA_data[max_ETAs][3] = { 284 recurring_ETA, heart_beat, one_second / 2, // heart beat data 285 recurring_ETA, check_temp, 10 * one_second, //5 * one_minute, // temperature check data 286 recurring_ETA, check_moisture, 5 * one_second, //20 * one_minute,// soil moisture check data 287 recurring_ETA, check_humidity, 20 * one_second, //5 * one_minute, // humidity check data 288 recurring_ETA, check_light, 15 * one_second, //one_hour, // light level check data 289 recurring_ETA, watchdog, 3 * one_second, // watchdog data 290 recurring_ETA, report_stats, one_minute //one_day // statistics data 291}; 292 293// 294// statistics variables 295// 296uint16_t temp_cycles, moisture_cycles, humidity_cycles, 297 light_cycles, watch_cycles, stats_cycles; 298 299// Establish the starting parameters for temp, moisture, humidity and light levels 300// These will be modified as the various sensors are read 301float min_temp = 25; 302float max_temp = 25; 303float alert_high_temp = 50; 304float alert_low_temp = -15; 305 306float min_moisture = 65; 307float max_moisture = 65; 308float alert_high_moisture = 90; 309float alert_low_moister = 25; 310 311float min_humidity = 80; 312float max_humidity = 80; 313float alert_high_humidity = 95; 314float alert_low_humidity = 10; 315 316float min_light = 75; 317float max_light = 75; 318// 319// resets any gathered statistics 320// 321void reset_stats(bool reset_all) { 322 temp_cycles = 0; 323 moisture_cycles = 0; 324 humidity_cycles = 0; 325 light_cycles = 0; 326 watch_cycles = 0; 327 if (reset_all) stats_cycles = 0; 328} 329 330// 331// Dummy function to read temp 332// 333float read_temp() { 334 static float temp = 25; 335 temp = pow(-1, random(1, 3)) + temp; 336 // check for min and max values 337 if (temp > max_temp) max_temp = temp; 338 else if (temp < min_temp) min_temp = temp; 339 return temp; 340} 341 342// 343// Dummy function to read moisture 344// 345float read_moisture() { 346 static float moisture = 65; 347 moisture = pow(-1, random(1, 3)) + moisture; 348 // check for min and max values 349 if (moisture > max_moisture) max_moisture = moisture; 350 else if (moisture < min_moisture) min_moisture = moisture; 351 return moisture; 352} 353 354// 355// Dummy function to read humidity 356// 357float read_humidity() { 358 static float humidity = 80; 359 humidity = pow(-1, random(1, 3)) + humidity; 360 // check for min and max values 361 if (humidity > max_humidity) max_humidity = humidity; 362 else if (humidity < min_humidity) min_humidity = humidity; 363 return humidity; 364} 365 366// 367// Dummy function to read light level 368// 369float read_light() { 370 static float light = 75; 371 light = pow(-1, random(1, 3)) + light; 372 // check for min and max values 373 if (light > max_light) max_light = light; 374 else if (light < min_light) min_light = light; 375 return light; 376} 377 378void print_stats() { 379 Serial.println("\ 380Statistics:"); 381 Serial.print("number stats cycles: "); 382 Serial.println(stats_cycles); 383 Serial.print("number temperture cycles : "); 384 Serial.print(temp_cycles); 385 Serial.print(",\ min/max temperature = "); 386 Serial.print(min_temp); 387 Serial.print("/"); 388 Serial.println(max_temp); 389 Serial.print("number moisture cycles : "); 390 Serial.print(moisture_cycles); 391 Serial.print(",\ min/max moisture = "); 392 Serial.print(min_moisture); 393 Serial.print("/"); 394 Serial.println(max_moisture); 395 Serial.print("number humidity cycles : "); 396 Serial.print(humidity_cycles); 397 Serial.print(",\ min/max humidity = "); 398 Serial.print(min_humidity); 399 Serial.print("/"); 400 Serial.println(max_humidity); 401 Serial.print("number light cycles : "); 402 Serial.print(light_cycles); 403 Serial.print(",\ min/max light level = "); 404 Serial.print(min_light); 405 Serial.print("/"); 406 Serial.println(max_light); 407 Serial.print("number watchdog cycles : "); 408 Serial.println(watch_cycles); 409 Serial.println(); 410 Serial.flush(); 411} 412 413void setup() { 414 Serial.begin(115200); 415 // clear down the ETA structures 416 clear_ETAs(); 417 // create ETAs before the start of the timer interrupt process. 418 // We use the ETA data defined above to create all the ETAs 419 // we require. Note the error checking 420 int result; 421 uint8_t entry; 422 for (entry = 0; entry < max_ETAs; entry++) { 423 result = create_ETA(my_ETA_data[entry][0], // ETA type 424 my_ETA_data[entry][1], // ETA alert id 425 my_ETA_data[entry][2]);// ETA alert interval (elapse/count down) 426 if (result < 0) { 427 Serial.print("create_ETA error:"); 428 if (result == ETA_invalid_type) Serial.println(" invalid ETA type"); 429 else { 430 if (result == ETA_duplicate) Serial.println(" duplicate ETA"); 431 else Serial.println(" ETA insert failure"); 432 } 433 Serial.flush(); 434 } 435 } 436 reset_stats(true); // reset all stats variables 437 print_ETAs(); // confirm the ETA set ups 438 // 439 // initialise the heart beat GPIO pin 440 pinMode(heart_beat, OUTPUT); 441 digitalWrite(heart_beat, LOW); // set output to low 442 // 443 // finally, set up and start default timer 444 create_and_start_timer(default_timer); 445} 446 447void loop() { 448 // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 449 // % This main loop design ensures that all timner inetrrupts get processed % 450 // % non-timer interrupt handling code may be added at the end of the main % 451 // % timer interrupt processing section % 452 // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 453 454 // Take a copy of the current interrupt_counter value 455 // so we may test it in our while/do process without 456 // running into a conflict between our ISR and main 457 // loop processing 458 portENTER_CRITICAL(&timerMux); 459 current_interrupt_counter = interrupt_counter; 460 portEXIT_CRITICAL(&timerMux); 461 while (current_interrupt_counter > 0) { 462 // at least one timer event has occurred so process the ETAs 463 // while we have unprocessed timer interrupt events 464 portENTER_CRITICAL(&timerMux); 465 interrupt_counter--; 466 current_interrupt_counter = interrupt_counter; 467 portEXIT_CRITICAL(&timerMux); 468 // 469 // Update the ETAs to decrement their count downs for this 470 // timer interrupt cycle and create a list of elapsed ETAs 471 update_ETAs(); 472 // 473 // Now process any ETA alert list of elasped ETAs. Keep processing 474 // the ETA_alert list until it has been emptied 475 uint8_t ETA_type; 476 uint16_t ETA_alert_id; 477 while (get_ETA_alert(ETA_type, ETA_alert_id) == ETA_extracted) { 478 // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 479 // % Put ETA elapsing code here, use 'ETA_type' and/or 'ETA_alert_id'% 480 // % for decision control and management % 481 // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 482 // 483 // We have an alert to process. 484 // In this design the ETA_alert_id returned tells us which ETA alert we 485 // need to process - we may ignore the ETA type as all are of type recurring 486 // 487 switch (ETA_alert_id) {// switch on ETA_alter_id 488 case heart_beat: 489 // Toggle the on board LED to show sketch operating 490 // The on board LED pin is defined as the ETA_alert_id 491 digitalWrite(ETA_alert_id, digitalRead(ETA_alert_id) ^ 1); // invert current status 492 break; 493 case check_temp: 494 // Code for processing temperature levels 495 Serial.println("Check temperature process entered"); 496 temp_cycles++; 497 float temp; 498 temp = read_temp(); 499 // now decide if the current temperature is too high or too low 500 // respond accordingly... 501 Serial.print(" Current temperature = "); 502 Serial.print(temp); 503 Serial.println(" C."); 504 break; 505 case check_moisture: 506 // Code for processing moisture levels 507 Serial.println("Check moisture process entered"); 508 moisture_cycles++; 509 float moisture; 510 moisture = read_moisture(); // read current moisture level 511 // now decide if the current moisture is too high or too low 512 // respond accordingly... 513 Serial.print(" Current soil moisture = "); 514 Serial.print(moisture); 515 Serial.println("%."); 516 break; 517 case check_humidity: 518 // Code for processing humidity levels 519 Serial.println("Check humidity process entered"); 520 humidity_cycles++; 521 float humidity; 522 humidity = read_humidity(); // read current humidiy level 523 // now decide if the current humidity is too high or too low 524 // respond accordingly... 525 Serial.print(" Current humidity = "); 526 Serial.print(humidity); 527 Serial.println("%."); 528 break; 529 case check_light: 530 // Code for processing light levels 531 Serial.println("Check light process entered"); 532 light_cycles++; 533 float light; 534 light = read_light(); // read current ight level 535 // now decide if the current light is too high or too low 536 // respond accordingly... 537 Serial.print(" Current light level = "); 538 Serial.println(light); 539 break; 540 case watchdog: 541 // Code for processing the watchdog 542 Serial.println("Watchdog process entered"); 543 watch_cycles++; 544 Serial.print(" number of watchdog cycles = "); 545 Serial.println(watch_cycles); 546 Serial.flush(); 547 if (max_temp >= alert_high_temp || min_temp <= alert_low_temp 548 || max_moisture >= alert_high_moisture || min_moisture <= alert_low_moister 549 || max_humidity >= alert_high_humidity || min_humidity <= alert_low_humidity) { 550 // The environment is out of spec! Report via the internet target 551 // to raise an alert requiring attention... 552 Serial.println("!!!!Environment out of spec!!!!"); // dummy alert 553 print_stats(); 554 } 555 break; 556 case report_stats: 557 // Report daily statistics via the internet to the target agent 558 stats_cycles++; 559 print_stats(); 560 reset_stats(false); // reset stats apart from stats cycle count 561 break; 562 default: 563 // Shoud never arrive here! 564 Serial.println("Invalid ETA_alert_id encountered!!"); 565 break; 566 } 567 Serial.flush(); // flush the buffer 568 } 569 } 570 // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 571 // % Put any none ETA handling code here % 572 // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 573 574 575} 576
Example 2: The ETA Framework Sketch
c_cpp
The starting sketch for developing your own elapsed timer projects
1// 2// R D Bentley (Stafford, UK), May 2022 3// 4// This example and code is in the public domain and 5// may be used without restriction and without warranty. 6// 7// ESP 32 Multiple Elapsed Timer Alerting Framework 8// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 9// ESP 32 timer driven sketch to process requirements for ANY number of 10// user defined elapsed timer alerts (ETAs). 11// 12// The sketch provides for any number of ETAs with a resolution of 13// 1 millisecond or multiples thereof. The alerts are handled asynchronously. 14// The ESP 32 timer can be specified (0-3). 15// 16// Summary of key functions: 17// 18// create_ETA - has 3 parameters - ETA_type, ETA_alert_id and ETA_interval (elapse interval timein milliseconds) 19// ETA_type - 'one_off_ETA' or 'recurring_ETA' 20// ETA_alert_id - user defined value 21// ETA_interval - elapsed time of alert in milliseconds 22// eg result = create_ETA(recurring_ETA, 25, 1000); 23// delete_ETA - has 2 parameters - ETA_type and ETA_alert_id 24// eg result = delete_ETA(recurring_ETA, 25); 25// print_ETAs - no parameters - prints the ETA structure contents for each defined/created ETA 26// eg print_ETAs(); 27// update_ETAs - no parameters - function that determines if any of the defined/created 28// ETAs have elapsed. It performs a count down for each ETA every timer cycle. When 29// an ETA has reached its count down its details are inserted into a list which 30// will be processed by the get_ETA_alert function. These two functions work 31// together and provide asynchronicity of processing 32// eg update_ETAs(); 33// get_ETA_alert - no parameters - retrieves elapsed ETAs from the alert list providing 34// their ETA type and ETA alert id which can then be used for decision control 35// and specific to ETA processing 36// create_and_start_timer 37// - creates and starts the EPS 32 given timer 38// 39// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 40// % Timer declarations and timer ISR % 41// % Initialisation occurs in setup() % 42// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 43// 44volatile int interrupt_counter; // used to signify a timer interrupt event has occurred 45volatile int current_interrupt_counter; 46hw_timer_t * timer = NULL; // establish timer structure 47portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; 48 49// 50// The timer_ISR simply increments the varuable interrupt_counter. 51// This variable is decremented by the main loop which will deal with 52// timer interrupt events until they have all been processed, ie 53// until interrupt_counter = 0 54// 55void IRAM_ATTR timer_ISR() { 56 portENTER_CRITICAL_ISR(&timerMux); 57 interrupt_counter++; 58 portEXIT_CRITICAL_ISR(&timerMux); 59} 60 61#define max_num_timers 4 // 4 timers on ESP 32 - 0-3 62#define default_timer 0 // used if specified timer is invalid 63 64// 65// Create an ESP 32 timer instance for given parameter 66// 67void create_and_start_timer(uint8_t timer_num) { 68 if (timer_num >= max_num_timers) {// valid range is 0 to 3 69 Serial.println("!!Invalid timer number - restting to default timer!!"); 70 Serial.flush(); 71 timer_num = default_timer; 72 } 73 // set up the timer interrupt for 1 millisecond interrupts 74 timer = timerBegin(timer_num, 80, true); // 80 Mhz processor, scale down to 1 Mhz 75 timerAttachInterrupt(timer, &timer_ISR, true); // link to our interrupt service routine (ISR) 76 timerAlarmWrite(timer, 1000, true); // define for 1 millisecond interrupts 0.001 Mhz 77 timerAlarmEnable(timer); // start timer 78} 79 80// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 81// % Elapsed Time Alert(s) declarations and functions % 82// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 83 84#define max_ETAs 1 // User defined - at least 1, the heart beat 85 86// ETA function and decision control macros 87#define one_off_ETA 1 88#define recurring_ETA 2 89#define ETA_inserted 1 // positive result for success 90#define ETA_insert_failure -1 // negative result for failure 91#define ETA_invalid_type -2 // ditto 92#define ETA_duplicate -3 // ditto 93#define ETA_extracted true // converse is !ETA_extracted 94#define ETA_deleted true // converse is !ETA_deleted 95 96// ETA_struct is the control structure at the heart of this design 97// It will hold details for each ETA created 98struct ETA_struct { 99 uint8_t ETA_type; 100 uint16_t ETA_alert_id; // alert id range is 0 - 65,535 101 uint32_t ETA_interval; 102 uint32_t ETA_count_down; 103} ETAs[max_ETAs]; 104 105// ETA_alerts_struct is used dynamically to record the details of each 106// ETA that has elapsed. It is this structure that is used for processing 107// elapsed ETAs. It has the form that entry [0] holds a count of the number 108// of entries that follow in the structure. ETA details then follow on from 109// entry [1], for example: 110// [0]:3 (int) 111// [1]:1, 23, (unit8_t/uint16_t) 112// [2]:2, 2047, (unit8_t/uint16_t) 113// [3]:1, 97 (unit8_t/uint16_t) 114struct ETA_alerts_struct { 115 uint8_t ETA_type; 116 uint16_t ETA_alert_id; 117 union { 118 uint16_t ETA_num_alerts; 119 }; 120} ETA_alerts[max_ETAs + 1]; // reserve an entry for the list count 121 122// Creates an ETA with the given parameters. Validation of the parameters 123// is carried out. The ETA is created if there is available space in the 124// ETA structure. Note also that ETAs must be unique in the ETA structure. 125// An attempt to create a duplicate ETA will generate an error 126// 127int create_ETA(uint8_t ETA_type, 128 uint16_t ETA_alert_id, 129 uint32_t ETA_interval) { 130 if (ETA_type != one_off_ETA && 131 ETA_type != recurring_ETA)return ETA_invalid_type; 132 int free_entry = -1; // used to track first free ETA entry in ETA list 133 for (int entry = max_ETAs - 1; entry >= 0; entry--) { 134 if (ETAs[entry].ETA_type == 0) { 135 // this is an empty slot so remember it 136 free_entry = entry; 137 } else { 138 // this entry is already occupied so check if it is the same 139 // as the ETA create requested 140 if (ETAs[entry].ETA_type == ETA_type && 141 ETAs[entry].ETA_alert_id == ETA_alert_id) { 142 // an ETA entry already exists for the requested ETA create 143 return ETA_duplicate; 144 } 145 } 146 } 147 // ok, so requested ETA is not already in the ETA list 148 // so add it if there is space, ie free_entry != -1 149 if (free_entry != -1) { 150 ETAs[free_entry].ETA_type = ETA_type; // eg one off or recurring ETA 151 ETAs[free_entry].ETA_alert_id = ETA_alert_id; 152 ETAs[free_entry].ETA_interval = ETA_interval; 153 ETAs[free_entry].ETA_count_down = ETA_interval; 154 return ETA_inserted; 155 } 156 return ETA_insert_failure; 157} 158 159// 160// Delete the given ETA from the ETA control structure 161// 162bool delete_ETA(uint8_t ETA_type, 163 uint16_t ETA_alert_id) { 164 for (uint8_t entry = 0; entry < max_ETAs; entry++) { 165 if (ETAs[entry].ETA_type == ETA_type && 166 ETAs[entry].ETA_alert_id == ETA_alert_id) { 167 // found, so delete it 168 ETAs[entry].ETA_type = 0; 169 return ETA_deleted; 170 } 171 } 172 return !ETA_deleted; 173} 174 175// 176// Prints the ETA structure contents. Useful for confirming 177// that the configuration for ETA is as required 178// 179void print_ETAs() { 180 Serial.begin(115200); 181 for (uint8_t entry = 0; entry < max_ETAs; entry++) { 182 Serial.print("\ 183ETA entry = "); 184 Serial.print(entry); 185 if (ETAs[entry].ETA_type != 0) { 186 Serial.print("\ 187 ETA_type = "); 188 if (ETAs[entry].ETA_type == one_off_ETA) 189 Serial.println("one off ETA"); 190 else Serial.println("recurring ETA"); 191 Serial.print(" ETA_alert_id = "); 192 Serial.println(ETAs[entry].ETA_alert_id); 193 Serial.print(" ETA_interval = "); 194 Serial.println(ETAs[entry].ETA_interval); 195 Serial.print(" ETA_count_down = "); 196 Serial.println(ETAs[entry].ETA_count_down); 197 } else { 198 Serial.println(" *** entry empty"); 199 } 200 } 201 Serial.flush(); 202} 203 204// 205// Extracts an ETA alert from the ETA alert structure. Note that the 206// entries in this structure have been generated because their respective 207// ETAs have elapsed. The ETA alert structure should be continually 208// processed until it is empty. If an entry is removed then its ETA type 209// and ETA alert id are returned in ETA_type and ETA_alert_id respectively 210// along with a success return value (ETA_extracted). Otherwise these variables 211// are set to 0 with a corresponding failure return value (!ETA_extracted) 212// 213bool get_ETA_alert(uint8_t &ETA_type, 214 uint16_t &ETA_alert_id) { 215 uint8_t last_entry = ETA_alerts[0].ETA_num_alerts; 216 if (last_entry > 0) { 217 ETA_type = ETA_alerts[last_entry].ETA_type; 218 ETA_alert_id = ETA_alerts[last_entry].ETA_alert_id; 219 ETA_alerts[last_entry].ETA_type = 0; 220 ETA_alerts[last_entry].ETA_alert_id = 0; 221 ETA_alerts[0].ETA_num_alerts--; // reduce list by 1 222 return ETA_extracted; 223 } 224 ETA_type = 0; 225 ETA_alert_id = 0; 226 return !ETA_extracted; 227} 228 229// 230// clear down the ETA structure and reset the alert count 231// of the ETA alerts strucure 232// 233void clear_ETAs() { 234 // ETA_type = 0 signifies an empty slot 235 for (uint8_t entry = 0; entry < max_ETAs; entry++) { 236 ETAs[entry].ETA_type = 0; 237 } 238 ETA_alerts[0].ETA_num_alerts = 0; // reset alert list counter 239} 240 241// 242// The function scans the ETA control, structure and decrements any ETAs 243// defined. If an ETA has reached its count down interval it will be added 244// to the ETA_alerts structure so that it may be processed subsequently 245// and asynchronously 246// 247void update_ETAs() { 248 for (uint8_t entry = 0; entry < max_ETAs; entry++) { 249 if (ETAs[entry].ETA_type != 0) { 250 // this ETA entry is defined 251 if (ETAs[entry].ETA_count_down > 0) { 252 // decrement count down timer, it has not yet elapsed 253 ETAs[entry].ETA_count_down--; // reduce elapse interval by 1 254 if (ETAs[entry].ETA_count_down == 0) { 255 // add this lapsed ETA alert to the ETA alert list 256 int next_entry = ETA_alerts[0].ETA_num_alerts + 1; // next free entry in the list 257 ETA_alerts[next_entry].ETA_type = ETAs[entry].ETA_type; 258 ETA_alerts[next_entry].ETA_alert_id = ETAs[entry].ETA_alert_id; 259 ETA_alerts[0].ETA_num_alerts = next_entry; 260 // now determine if this ETA needs to be reset or removed/deleted 261 if (ETAs[entry].ETA_type == recurring_ETA) { 262 // recurring ETA so reset the elapse interval count down 263 ETAs[entry].ETA_count_down = ETAs[entry].ETA_interval; 264 } else { 265 // this must be a one off ETA, so delete it 266 ETAs[entry].ETA_type = 0; 267 } 268 } 269 } 270 } 271 } 272} 273 274// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 275// % Data specific to this use of the ETA framework for this example % 276// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 277// 278// These macros can be used for creating ETAs and are provided 279// for convenience of programming standard intervals, as required. 280// They may be modified to achieve the desired count down frequencies, 281// for example 2 * one_second, 5 * minute + 500, etc (all in milliseconds) 282// 283const uint32_t one_day = 86400000; // millisecs in 24 hours 284const uint32_t one_hour = 3600000; // millisecs in 1 hour 285const uint16_t one_minute = 60000; // millisecs in 1 minute 286const uint16_t one_second = 1000; // millisecs in 1 second 287 288// These macros define our ETA alert IDs for our sketch design/example 289#define heart_beat LED_BUILTIN // ETA alert ID - GPIO pin of in-built LED 290 291// Now put together all of our ETA configs so we may create them more 292// easily in our setup process. The array is defined as follows: 293// my_ETA_data[][0] is the ETA_type, one off or recurring 294// [][1] is the ETA alert id defining the function/purpose of the ETA 295// [][2] is the duration for the ETA's elapsed time count down (duration) 296// in milliseconds 297// 298int my_ETA_data[max_ETAs][3] = { 299 recurring_ETA, heart_beat, one_second / 2 // heart beat data 300}; 301 302void setup() { 303 Serial.begin(115200); 304 // clear down the ETA structures 305 clear_ETAs(); 306 // create ETAs before the start of the timer interrupt process. 307 // We use the ETA data defined above to create all the ETAs 308 // we require. Note the error checking 309 int result; 310 uint8_t entry; 311 for (entry = 0; entry < max_ETAs; entry++) { 312 result = create_ETA(my_ETA_data[entry][0], // ETA type 313 my_ETA_data[entry][1], // ETA alert id 314 my_ETA_data[entry][2]);// ETA alert interval (elapse/count down) 315 if (result < 0) { 316 Serial.print("create_ETA error:"); 317 if (result == ETA_invalid_type) Serial.println(" invalid ETA type"); 318 else { 319 if (result == ETA_duplicate) Serial.println(" duplicate ETA"); 320 else Serial.println(" ETA insert failure"); 321 } 322 Serial.flush(); 323 } 324 } 325 print_ETAs(); // confirm the ETA set ups 326 // 327 // initialise the heart beat GPIO pin 328 pinMode(heart_beat, OUTPUT); 329 digitalWrite(heart_beat, LOW); // set output to low 330 // 331 // finally, set up and start default timer 332 create_and_start_timer(default_timer); 333} 334 335void loop() { 336 // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 337 // % This main loop design ensures that all timner inetrrupts get processed % 338 // % non-timer interrupt handling code may be added at the end of the main % 339 // % timer interrupt processing section % 340 // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 341 342 // Take a copy of the current interrupt_counter value 343 // so we may test it in our while/do process without 344 // running into a conflict between our ISR and main 345 // loop processing 346 portENTER_CRITICAL(&timerMux); 347 current_interrupt_counter = interrupt_counter; 348 portEXIT_CRITICAL(&timerMux); 349 while (current_interrupt_counter > 0) { 350 // at least one timer event has occurred so process the ETAs 351 // while we have unprocessed timer interrupt events 352 portENTER_CRITICAL(&timerMux); 353 interrupt_counter--; 354 current_interrupt_counter = interrupt_counter; 355 portEXIT_CRITICAL(&timerMux); 356 // 357 // Update the ETAs to decrement their count downs for this 358 // timer interrupt cycle and create a list of elapsed ETAs 359 update_ETAs(); 360 // 361 // Now process any ETA alert list of elasped ETAs. Keep processing 362 // the ETA_alert list until it has been emptied 363 uint8_t ETA_type; 364 uint16_t ETA_alert_id; 365 while (get_ETA_alert(ETA_type, ETA_alert_id) == ETA_extracted) { 366 // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 367 // % Put ETA elapsing code here, use 'ETA_type' and/or 'ETA_alert_id'% 368 // % for decision control and management % 369 // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 370 // 371 // We have an alert to process. 372 // In this design the ETA_alert_id returned tells us which ETA alert we 373 // need to process - we may ignore the ETA type as all are of type recurring 374 // 375 switch (ETA_alert_id) {// switch on ETA_alter_id 376 case heart_beat: 377 // Toggle the on board LED to show sketch operating 378 // The on board LED pin is defined as the ETA_alert_id 379 digitalWrite(ETA_alert_id, digitalRead(ETA_alert_id) ^ 1); // invert current status 380 break; 381 default: 382 // Shoud never arrive here! 383 Serial.println("Invalid ETA_alert_id encountered!!"); 384 break; 385 } 386 Serial.flush(); // flush the buffer 387 } 388 } 389 // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 390 // % Put any none ETA handling code here % 391 // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 392 393 394} 395
Example 3: A Home Environmental Monitor based on the ETA Framework
c_cpp
An example of how we can use the ETA Framework for developing projects needing multiple elapsing timers. This dummy example will monitor temp, humidity, soil moisture and light levels.
1// 2// R D Bentley (Stafford, UK), May 2022 3// 4// This example 5 and code is in the public domain and 6// may be used without restriction and without 7 warranty. 8// 9// ESP 32 Multiple Elapsed Timer Alerting - Home Environmental 10 Monitoring Example 11// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 12// 13// 14 This example shows how the ETA framework can be used to define elapsed time 15// 16 alerts that can be used to process specific event requirements, here a home 17// 18 environment monitoring requirement. 19// The example does not provide a complete 20 solution, rather it provides the hooks 21// and eyes for fully integrated solution 22 for the monitoring and adjustments of 23// temperature, humidity, light and soil 24 moisture that my be necessary for maintaining 25// for example, garden plants, 26 etc. 27// 28// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 29// % Timer declarations 30 and timer ISR % 31// % Initialisation occurs in setup() % 32// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 33// 34volatile 35 int interrupt_counter; // used to signify a timer interrupt event has occurred 36volatile 37 int current_interrupt_counter; 38hw_timer_t * timer = NULL; // establish timer 39 structure 40portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; 41 42// 43// 44 The timer_ISR simply increments the varuable interrupt_counter. 45// This variable 46 is decremented by the main loop which will deal with 47// timer interrupt events 48 until they have all been processed, ie 49// until interrupt_counter = 0 50// 51void 52 IRAM_ATTR timer_ISR() { 53 portENTER_CRITICAL_ISR(&timerMux); 54 interrupt_counter++; 55 56 portEXIT_CRITICAL_ISR(&timerMux); 57} 58 59#define max_num_timers 4 // 60 4 timers on ESP 32 - 0-3 61#define default_timer 0 // used if specified 62 timer is invalid 63 64// 65// Create an ESP 32 timer instance for given parameter 66// 67void 68 create_and_start_timer(uint8_t timer_num) { 69 if (timer_num >= max_num_timers) 70 {// valid range is 0 to 3 71 Serial.println("!!Invalid timer number - restting 72 to default timer!!"); 73 Serial.flush(); 74 timer_num = default_timer; 75 76 } 77 // set up the timer interrupt for 1 millisecond interrupts 78 timer = 79 timerBegin(timer_num, 80, true); // 80 Mhz processor, scale down to 1 Mhz 80 81 timerAttachInterrupt(timer, &timer_ISR, true); // link to our interrupt service 82 routine (ISR) 83 timerAlarmWrite(timer, 1000, true); // define for 84 1 millisecond interrupts 0.001 Mhz 85 timerAlarmEnable(timer); // 86 start timer 87} 88 89// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 90// 91 % Elapsed Time Alert(s) declarations and functions % 92// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 93 94#define 95 max_ETAs 7 // User defined - we are configuring for 7 ETAs 96 97// 98 ETA function and decision control macros 99#define one_off_ETA 1 100#define 101 recurring_ETA 2 102#define ETA_inserted 1 // positive result 103 for success 104#define ETA_insert_failure -1 // negative result for failure 105#define 106 ETA_invalid_type -2 // ditto 107#define ETA_duplicate -3 // ditto 108#define 109 ETA_extracted true // converse is !ETA_extracted 110#define ETA_deleted true 111 // converse is !ETA_deleted 112 113// ETA_struct is the control structure at the 114 heart of this design 115// It will hold details for each ETA created 116struct ETA_struct 117 { 118 uint8_t ETA_type; 119 uint16_t ETA_alert_id; // alert id range is 0 - 120 65,535 121 uint32_t ETA_interval; 122 uint32_t ETA_count_down; 123} ETAs[max_ETAs]; 124 125// 126 ETA_alerts_struct is used dynamically to record the details of each 127// ETA that 128 has elapsed. It is this structure that is used for processing 129// elapsed ETAs. 130 It has the form that entry [0] holds a count of the number 131// of entries that 132 follow in the structure. ETA details then follow on from 133// entry [1], for example: 134// 135 [0]:3 (int) 136// [1]:1, 23, (unit8_t/uint16_t) 137// [2]:2, 2047, (unit8_t/uint16_t) 138// 139 [3]:1, 97 (unit8_t/uint16_t) 140struct ETA_alerts_struct { 141 uint8_t ETA_type; 142 143 uint16_t ETA_alert_id; 144 union { 145 uint16_t ETA_num_alerts; 146 }; 147} 148 ETA_alerts[max_ETAs + 1]; // reserve an entry for the list count 149 150// Creates 151 an ETA with the given parameters. Validation of the parameters 152// is carried 153 out. The ETA is created if there is available space in the 154// ETA structure. 155 Note also that ETAs must be unique in the ETA structure. 156// An attempt to create 157 a duplicate ETA will generate an error 158// 159int create_ETA(uint8_t ETA_type, 160 161 uint16_t ETA_alert_id, 162 uint32_t ETA_interval) 163 { 164 if (ETA_type != one_off_ETA && 165 ETA_type != recurring_ETA)return 166 ETA_invalid_type; 167 int free_entry = -1; // used to track first free ETA entry 168 in ETA list 169 for (int entry = max_ETAs - 1; entry >= 0; entry--) { 170 if 171 (ETAs[entry].ETA_type == 0) { 172 // this is an empty slot so remember it 173 174 free_entry = entry; 175 } else { 176 // this entry is already occupied 177 so check if it is the same 178 // as the ETA create requested 179 if (ETAs[entry].ETA_type 180 == ETA_type && 181 ETAs[entry].ETA_alert_id == ETA_alert_id) { 182 // 183 an ETA entry already exists for the requested ETA create 184 return ETA_duplicate; 185 186 } 187 } 188 } 189 // ok, so requested ETA is not already in the ETA list 190 191 // so add it if there is space, ie free_entry != -1 192 if (free_entry != -1) 193 { 194 ETAs[free_entry].ETA_type = ETA_type; // eg one off or recurring 195 ETA 196 ETAs[free_entry].ETA_alert_id = ETA_alert_id; 197 ETAs[free_entry].ETA_interval 198 = ETA_interval; 199 ETAs[free_entry].ETA_count_down = ETA_interval; 200 return 201 ETA_inserted; 202 } 203 return ETA_insert_failure; 204} 205 206// 207// Delete 208 the given ETA from the ETA control structure 209// 210bool delete_ETA(uint8_t ETA_type, 211 212 uint16_t ETA_alert_id) { 213 for (uint8_t entry = 0; entry < max_ETAs; 214 entry++) { 215 if (ETAs[entry].ETA_type == ETA_type && 216 ETAs[entry].ETA_alert_id 217 == ETA_alert_id) { 218 // found, so delete it 219 ETAs[entry].ETA_type 220 = 0; 221 return ETA_deleted; 222 } 223 } 224 return !ETA_deleted; 225} 226 227// 228// 229 Prints the ETA structure contents. Useful for confirming 230// that the configuration 231 for ETA is as required 232// 233void print_ETAs() { 234 Serial.begin(115200); 235 236 for (uint8_t entry = 0; entry < max_ETAs; entry++) { 237 Serial.print("\ 238ETA 239 entry = "); 240 Serial.print(entry); 241 if (ETAs[entry].ETA_type != 0) { 242 243 Serial.print("\ 244 ETA_type = "); 245 if (ETAs[entry].ETA_type == one_off_ETA) 246 247 Serial.println("one off ETA"); 248 else Serial.println("recurring 249 ETA"); 250 Serial.print(" ETA_alert_id = "); 251 Serial.println(ETAs[entry].ETA_alert_id); 252 253 Serial.print(" ETA_interval = "); 254 Serial.println(ETAs[entry].ETA_interval); 255 256 Serial.print(" ETA_count_down = "); 257 Serial.println(ETAs[entry].ETA_count_down); 258 259 } else { 260 Serial.println(" *** entry empty"); 261 } 262 } 263 264 Serial.flush(); 265} 266 267// 268// Extracts an ETA alert from the ETA alert 269 structure. Note that the 270// entries in this structure have been generated because 271 their respective 272// ETAs have elapsed. The ETA alert structure should be continually 273// 274 processed until it is empty. If an entry is removed then its ETA type 275// and 276 ETA alert id are returned in ETA_type and ETA_alert_id respectively 277// along 278 with a success return value (ETA_extracted). Otherwise these variables 279// are 280 set to 0 with a corresponding failure return value (!ETA_extracted) 281// 282bool 283 get_ETA_alert(uint8_t &ETA_type, 284 uint16_t &ETA_alert_id) { 285 286 uint8_t last_entry = ETA_alerts[0].ETA_num_alerts; 287 if (last_entry > 0) { 288 289 ETA_type = ETA_alerts[last_entry].ETA_type; 290 ETA_alert_id = ETA_alerts[last_entry].ETA_alert_id; 291 292 ETA_alerts[last_entry].ETA_type = 0; 293 ETA_alerts[last_entry].ETA_alert_id 294 = 0; 295 ETA_alerts[0].ETA_num_alerts--; // reduce list by 1 296 return ETA_extracted; 297 298 } 299 ETA_type = 0; 300 ETA_alert_id = 0; 301 return !ETA_extracted; 302} 303 304// 305// 306 clear down the ETA structure and reset the alert count 307// of the ETA alerts strucure 308// 309void 310 clear_ETAs() { 311 // ETA_type = 0 signifies an empty slot 312 for (uint8_t entry 313 = 0; entry < max_ETAs; entry++) { 314 ETAs[entry].ETA_type = 0; 315 } 316 ETA_alerts[0].ETA_num_alerts 317 = 0; // reset alert list counter 318} 319 320// 321// The function scans the ETA 322 control, structure and decrements any ETAs 323// defined. If an ETA has reached 324 its count down interval it will be added 325// to the ETA_alerts structure so that 326 it may be processed subsequently 327// and asynchronously 328// 329void update_ETAs() 330 { 331 for (uint8_t entry = 0; entry < max_ETAs; entry++) { 332 if (ETAs[entry].ETA_type 333 != 0) { 334 // this ETA entry is defined 335 if (ETAs[entry].ETA_count_down 336 > 0) { 337 // decrement count down timer, it has not yet elapsed 338 ETAs[entry].ETA_count_down--; 339 // reduce elapse interval by 1 340 if (ETAs[entry].ETA_count_down == 0) { 341 342 // add this lapsed ETA alert to the ETA alert list 343 int next_entry 344 = ETA_alerts[0].ETA_num_alerts + 1; // next free entry in the list 345 ETA_alerts[next_entry].ETA_type 346 = ETAs[entry].ETA_type; 347 ETA_alerts[next_entry].ETA_alert_id = 348 ETAs[entry].ETA_alert_id; 349 ETA_alerts[0].ETA_num_alerts = next_entry; 350 351 // now determine if this ETA needs to be reset or removed/deleted 352 353 if (ETAs[entry].ETA_type == recurring_ETA) { 354 // recurring 355 ETA so reset the elapse interval count down 356 ETAs[entry].ETA_count_down 357 = ETAs[entry].ETA_interval; 358 } else { 359 // this must be 360 a one off ETA, so delete it 361 ETAs[entry].ETA_type = 0; 362 } 363 364 } 365 } 366 } 367 } 368} 369 370// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 371// 372 % Data specific to this use of the ETA framework for this example % 373// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 374// 375// 376 These macros can be used for creating ETAs and are provided 377// for convenience 378 of programming standard intervals, as required. 379// They may be modified to achieve 380 the desired count down frequencies, 381// for example 2 * one_second, 5 * minute 382 + 500, etc (all in milliseconds) 383// 384const uint32_t one_day = 86400000; 385 // millisecs in 24 hours 386const uint32_t one_hour = 3600000; // millisecs in 387 1 hour 388const uint16_t one_minute = 60000; // millisecs in 1 minute 389const 390 uint16_t one_second = 1000; // millisecs in 1 second 391 392// These macros 393 define our ETA alert IDs for our sketch design/example 394#define heart_beat LED_BUILTIN 395 // ETA alert ID - GPIO pin of in-built LED 396#define check_temp 10 // ETA 397 alert ID - an arbitary but unique value 398#define check_moisture 20 // ETA alert 399 ID - an arbitary but unique value 400#define check_humidity 30 // ETA alert ID 401 - an arbitary but unique value 402#define check_light 40 // ETA alert ID - an 403 arbitary but unique value 404#define watchdog 100 // ETA alert ID - an arbitary 405 but unique value 406#define report_stats 200 // ETA alert ID - an arbitary but 407 unique value 408 409// Now put together all of our ETA configs so we may create 410 them more 411// easily in our setup process. The array is defined as follows: 412// 413 my_ETA_data[][0] is the ETA_type, one off or recurring 414// [][1] is 415 the ETA alert id defining the function/purpose of the ETA 416// [][2] 417 is the duration for the ETA's elapsed time count down (duration) 418// in 419 milliseconds 420// 421int my_ETA_data[max_ETAs][3] = { 422 recurring_ETA, heart_beat, 423 one_second / 2, // heart beat data 424 recurring_ETA, check_temp, 10 * 425 one_second, //5 * one_minute, // temperature check data 426 recurring_ETA, check_moisture, 427 5 * one_second, //20 * one_minute,// soil moisture check data 428 recurring_ETA, 429 check_humidity, 20 * one_second, //5 * one_minute, // humidity check data 430 recurring_ETA, 431 check_light, 15 * one_second, //one_hour, // light level check data 432 433 recurring_ETA, watchdog, 3 * one_second, // watchdog data 434 recurring_ETA, 435 report_stats, one_minute //one_day // statistics data 436}; 437 438// 439// 440 statistics variables 441// 442uint16_t temp_cycles, moisture_cycles, humidity_cycles, 443 444 light_cycles, watch_cycles, stats_cycles; 445 446// Establish the starting 447 parameters for temp, moisture, humidity and light levels 448// These will be modified 449 as the various sensors are read 450float min_temp = 25; 451float max_temp = 25; 452float 453 alert_high_temp = 50; 454float alert_low_temp = -15; 455 456float min_moisture 457 = 65; 458float max_moisture = 65; 459float alert_high_moisture = 90; 460float alert_low_moister 461 = 25; 462 463float min_humidity = 80; 464float max_humidity = 80; 465float alert_high_humidity 466 = 95; 467float alert_low_humidity = 10; 468 469float min_light = 75; 470float max_light 471 = 75; 472// 473// resets any gathered statistics 474// 475void reset_stats(bool 476 reset_all) { 477 temp_cycles = 0; 478 moisture_cycles = 0; 479 humidity_cycles 480 = 0; 481 light_cycles = 0; 482 watch_cycles = 0; 483 if (reset_all) stats_cycles 484 = 0; 485} 486 487// 488// Dummy function to read temp 489// 490float read_temp() 491 { 492 static float temp = 25; 493 temp = pow(-1, random(1, 3)) + temp; 494 // 495 check for min and max values 496 if (temp > max_temp) max_temp = temp; 497 else 498 if (temp < min_temp) min_temp = temp; 499 return temp; 500} 501 502// 503// Dummy 504 function to read moisture 505// 506float read_moisture() { 507 static float moisture 508 = 65; 509 moisture = pow(-1, random(1, 3)) + moisture; 510 // check for min and 511 max values 512 if (moisture > max_moisture) max_moisture = moisture; 513 else 514 if (moisture < min_moisture) min_moisture = moisture; 515 return moisture; 516} 517 518// 519// 520 Dummy function to read humidity 521// 522float read_humidity() { 523 static float 524 humidity = 80; 525 humidity = pow(-1, random(1, 3)) + humidity; 526 // check for 527 min and max values 528 if (humidity > max_humidity) max_humidity = humidity; 529 530 else if (humidity < min_humidity) min_humidity = humidity; 531 return humidity; 532} 533 534// 535// 536 Dummy function to read light level 537// 538float read_light() { 539 static float 540 light = 75; 541 light = pow(-1, random(1, 3)) + light; 542 // check for min and 543 max values 544 if (light > max_light) max_light = light; 545 else if (light < 546 min_light) min_light = light; 547 return light; 548} 549 550void print_stats() 551 { 552 Serial.println("\ 553Statistics:"); 554 Serial.print("number stats cycles: 555 "); 556 Serial.println(stats_cycles); 557 Serial.print("number temperture cycles 558 : "); 559 Serial.print(temp_cycles); 560 Serial.print(",\ min/max temperature 561 = "); 562 Serial.print(min_temp); 563 Serial.print("/"); 564 Serial.println(max_temp); 565 566 Serial.print("number moisture cycles : "); 567 Serial.print(moisture_cycles); 568 569 Serial.print(",\ min/max moisture = "); 570 Serial.print(min_moisture); 571 572 Serial.print("/"); 573 Serial.println(max_moisture); 574 Serial.print("number 575 humidity cycles : "); 576 Serial.print(humidity_cycles); 577 Serial.print(",\ min/max 578 humidity = "); 579 Serial.print(min_humidity); 580 Serial.print("/"); 581 582 Serial.println(max_humidity); 583 Serial.print("number light cycles : "); 584 585 Serial.print(light_cycles); 586 Serial.print(",\ min/max light level = "); 587 588 Serial.print(min_light); 589 Serial.print("/"); 590 Serial.println(max_light); 591 592 Serial.print("number watchdog cycles : "); 593 Serial.println(watch_cycles); 594 595 Serial.println(); 596 Serial.flush(); 597} 598 599void setup() { 600 Serial.begin(115200); 601 602 // clear down the ETA structures 603 clear_ETAs(); 604 // create ETAs before 605 the start of the timer interrupt process. 606 // We use the ETA data defined above 607 to create all the ETAs 608 // we require. Note the error checking 609 int result; 610 611 uint8_t entry; 612 for (entry = 0; entry < max_ETAs; entry++) { 613 result 614 = create_ETA(my_ETA_data[entry][0], // ETA type 615 my_ETA_data[entry][1], 616 // ETA alert id 617 my_ETA_data[entry][2]);// ETA alert interval 618 (elapse/count down) 619 if (result < 0) { 620 Serial.print("create_ETA 621 error:"); 622 if (result == ETA_invalid_type) Serial.println(" invalid ETA 623 type"); 624 else { 625 if (result == ETA_duplicate) Serial.println(" 626 duplicate ETA"); 627 else Serial.println(" ETA insert failure"); 628 } 629 630 Serial.flush(); 631 } 632 } 633 reset_stats(true); // reset all stats 634 variables 635 print_ETAs(); // confirm the ETA set ups 636 // 637 // initialise 638 the heart beat GPIO pin 639 pinMode(heart_beat, OUTPUT); 640 digitalWrite(heart_beat, 641 LOW); // set output to low 642 // 643 // finally, set up and start default timer 644 645 create_and_start_timer(default_timer); 646} 647 648void loop() { 649 // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 650 651 // % This main loop design ensures that all timner inetrrupts get processed % 652 653 // % non-timer interrupt handling code may be added at the end of the main % 654 655 // % timer interrupt processing section % 656 657 // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 658 659 660 // Take a copy of the current interrupt_counter value 661 // so we may test it 662 in our while/do process without 663 // running into a conflict between our ISR 664 and main 665 // loop processing 666 portENTER_CRITICAL(&timerMux); 667 current_interrupt_counter 668 = interrupt_counter; 669 portEXIT_CRITICAL(&timerMux); 670 while (current_interrupt_counter 671 > 0) { 672 // at least one timer event has occurred so process the ETAs 673 // 674 while we have unprocessed timer interrupt events 675 portENTER_CRITICAL(&timerMux); 676 677 interrupt_counter--; 678 current_interrupt_counter = interrupt_counter; 679 680 portEXIT_CRITICAL(&timerMux); 681 // 682 // Update the ETAs to decrement 683 their count downs for this 684 // timer interrupt cycle and create a list of 685 elapsed ETAs 686 update_ETAs(); 687 // 688 // Now process any ETA alert 689 list of elasped ETAs. Keep processing 690 // the ETA_alert list until it has 691 been emptied 692 uint8_t ETA_type; 693 uint16_t ETA_alert_id; 694 while 695 (get_ETA_alert(ETA_type, ETA_alert_id) == ETA_extracted) { 696 // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 697 698 // % Put ETA elapsing code here, use 'ETA_type' and/or 'ETA_alert_id'% 699 700 // % for decision control and management % 701 702 // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 703 704 // 705 // We have an alert to process. 706 // In this design the 707 ETA_alert_id returned tells us which ETA alert we 708 // need to process - 709 we may ignore the ETA type as all are of type recurring 710 // 711 switch 712 (ETA_alert_id) {// switch on ETA_alter_id 713 case heart_beat: 714 // 715 Toggle the on board LED to show sketch operating 716 // The on board LED 717 pin is defined as the ETA_alert_id 718 digitalWrite(ETA_alert_id, digitalRead(ETA_alert_id) 719 ^ 1); // invert current status 720 break; 721 case check_temp: 722 723 // Code for processing temperature levels 724 Serial.println("Check 725 temperature process entered"); 726 temp_cycles++; 727 float temp; 728 729 temp = read_temp(); 730 // now decide if the current temperature 731 is too high or too low 732 // respond accordingly... 733 Serial.print(" 734 Current temperature = "); 735 Serial.print(temp); 736 Serial.println(" 737 C."); 738 break; 739 case check_moisture: 740 // Code 741 for processing moisture levels 742 Serial.println("Check moisture process 743 entered"); 744 moisture_cycles++; 745 float moisture; 746 moisture 747 = read_moisture(); // read current moisture level 748 // now decide if 749 the current moisture is too high or too low 750 // respond accordingly... 751 752 Serial.print(" Current soil moisture = "); 753 Serial.print(moisture); 754 755 Serial.println("%."); 756 break; 757 case check_humidity: 758 759 // Code for processing humidity levels 760 Serial.println("Check 761 humidity process entered"); 762 humidity_cycles++; 763 float 764 humidity; 765 humidity = read_humidity(); // read current humidiy level 766 767 // now decide if the current humidity is too high or too low 768 // 769 respond accordingly... 770 Serial.print(" Current humidity = "); 771 772 Serial.print(humidity); 773 Serial.println("%."); 774 break; 775 776 case check_light: 777 // Code for processing light levels 778 Serial.println("Check 779 light process entered"); 780 light_cycles++; 781 float light; 782 783 light = read_light(); // read current ight level 784 // now decide 785 if the current light is too high or too low 786 // respond accordingly... 787 788 Serial.print(" Current light level = "); 789 Serial.println(light); 790 791 break; 792 case watchdog: 793 // Code for processing the 794 watchdog 795 Serial.println("Watchdog process entered"); 796 watch_cycles++; 797 798 Serial.print(" number of watchdog cycles = "); 799 Serial.println(watch_cycles); 800 801 Serial.flush(); 802 if (max_temp >= alert_high_temp || min_temp 803 <= alert_low_temp 804 || max_moisture >= alert_high_moisture || min_moisture 805 <= alert_low_moister 806 || max_humidity >= alert_high_humidity || 807 min_humidity <= alert_low_humidity) { 808 // The environment is out of 809 spec! Report via the internet target 810 // to raise an alert requiring 811 attention... 812 Serial.println("!!!!Environment out of spec!!!!"); 813 // dummy alert 814 print_stats(); 815 } 816 break; 817 818 case report_stats: 819 // Report daily statistics via the internet 820 to the target agent 821 stats_cycles++; 822 print_stats(); 823 824 reset_stats(false); // reset stats apart from stats cycle count 825 break; 826 827 default: 828 // Shoud never arrive here! 829 Serial.println("Invalid 830 ETA_alert_id encountered!!"); 831 break; 832 } 833 Serial.flush(); 834 // flush the buffer 835 } 836 } 837 // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 838 839 // % Put any none ETA handling code here % 840 // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 841 842 843} 844
Example 1: An ESP 32 sketch to configure an interrupt timer
c_cpp
A sketch to demonstrate configuring an ESP 32 interrupt timer
1// 2// Basic example of ESP 32 interrupt timer sketch 3// 4// This 5 example and code is in the public domain and 6// may be used without restriction 7 and without warranty. 8// 9volatile int interrupt_counter; 10volatile int current_interrupt_counter; 11int 12 total_interrupt_counter; 13 14hw_timer_t * timer = NULL; 15portMUX_TYPE timerMux 16 = portMUX_INITIALIZER_UNLOCKED; 17 18void IRAM_ATTR onTimer() { 19 portENTER_CRITICAL_ISR(&timerMux); 20 21 interrupt_counter++; 22 portEXIT_CRITICAL_ISR(&timerMux); 23} 24 25void setup() 26 { 27 Serial.begin(115200); 28 timer = timerBegin(0, 80, true); // prescale 29 down to microsecond timing 30 timerAttachInterrupt(timer, &onTimer, true); 31 32 timerAlarmWrite(timer, 1000, true); // step down to millisecond interrupts 33 34 timerAlarmEnable(timer); 35} 36 37void loop() { 38 // Take a copy of the 39 current interrupt_counter value 40 // so we may test it in our if/then process 41 without 42 // running into a conflict between our ISR and main 43 // loop processing 44 45 portENTER_CRITICAL(&timerMux); 46 current_interrupt_counter = interrupt_counter; 47 48 portEXIT_CRITICAL(&timerMux); 49 if (current_interrupt_counter > 0) { 50 // 51 A timer interrupt has occurred 52 portENTER_CRITICAL(&timerMux); 53 interrupt_counter--; 54 55 portEXIT_CRITICAL(&timerMux); 56 total_interrupt_counter++; 57 // report 58 every 1000 interrupts, ie every 1 second 59 if (total_interrupt_counter % 1000 60 == 0) { 61 Serial.print("A timer interrupt has occurred. Total number: "); 62 63 Serial.println(total_interrupt_counter); 64 Serial.flush(); 65 } 66 67 } 68} 69
Downloadable files
ESP 32 to PC
Standard connection to PC via USB cable
ESP 32 to PC

Comments
Only logged in users can leave comments