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
Devices & Components
ESP32S
Software & Tools
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