Notifications
Clear all

[Solved] ESP 32 Timer Interrupts – ‘Unlimited’ Programmatic User Definable Timers

Page 1 / 4

ron bentley
(@ronbentley1)
Estimable Member
Joined: 6 months ago
Posts: 207
Topic starter  

Hi,

If you have an interest in interrupt timers and want a flexible approach to their use in your projects then you might find this post of interest.

I was recently finalising some work after my hols relating to ESP 32 timer interrupts and thought some members may find an interest in it.

There is much on the net about timer interrupts, for both ESP and Arduino architectures, and this was my starting point.  Bill’s video library includes an excellent article on Arduino Interrupts which also covers its timer interrupt capabilities but not ESP 32, as far as I can see. 

I found the basic ESP 32 timer interrupt example sketch on the net and played around with this.  What did strike me was that, although the concept of timer interrupts between Arduino and ESP is much the same, their respective set up and configuration is very different.  If I had to take a view, I would say that ESP 32 set up and configuration of timer interrupts is, perhaps, less ‘fiddly’, although there is an excellent resource for auto-creating Arduino timer interrupt ISR code and configs on a Slovak web site – AVT Timer Interrupts Calculator – well worth a look and dabble if this sort of thing ‘floats your boat’.

After playing around with the basic example ‘skeletal’ sketch I thought it would be a useful exercise to use this as the basis and driver for a more sophisticated and comprehensive solution, one that put some ‘meat on the bones’ of the ‘skeleton’.  What I wanted to achieve was an approach that would meet the following objectives:

  • A solution based on the standard ESP 32 timer interrupt approach, scaled to provide a 1 millisecond resolution. (1 millisecond should be adequate for all but the most demanding and time critical processes.)
  • The ability to create many user elapsed time timer alerts/events of different frequencies, all running concurrently. Note that a user defined timer is not a timer interrupt – it is an alert/event that occurs once a user defined timer’s running time interval has elapsed.
  • A user definable timer frequency/interval ranging from 1 millisecond to a full unsigned 32 bit time interval (around 49 days!).
  • The ability to create one-off and recurring user timers. A one-off timer to be deleted once it has elapsed, recurring timers to be restarted after their elapse.
  • A simple and straight forward programmatic design for dynamically creating and managing user timer requirements e.g. functions for creating and deleting user timers at set up and in the running life cycle of a sketch.
  • A separation of ‘duties’ between the interrupt timer ISR process and non-ISR timer handling code (the sketch’s functions and main loop coding) – i.e. an asynchronous design and operation.
  • A simple method of encoding/identifying each user timer created.
  • A design that would ensure that no timer interrupt is ‘missed’.
  • A design that would accommodate any number of user timers elapsing at the same time.
  • The provision of basic debug reporting.
  • A design that would also allow non-timer based processing, e.g. switch processing, IOT operations, etc.

A tall order?  Perhaps.  What I ended up with was a solution that met all of the above objectives and one that can be used as a framework sketch, as a starting point, were a solution requiring a number of user definable timers are to be handled – the ESP 32 ETA Framework.

The ETA framework was developed using an ESP 32S 30 pin WROOM microcontroller and the Arduino IDE. The IDE board selection was "DOIT ESP32 DEVKIT V1". Connection of the ESP 32 was via a USB cable, as standard, and there are no associated schematics - it is not a library but a framework sketch ready for crafting into a solution/project.

There is much more to the framework than I can cover in this post but if this has spawned an interest and you wish to read more and, perhaps, even download the framework, then it is now available on the Arduino Project Hub – ESP 32 ETA Framework.  By the way, the approach refers to user defined timers as ‘Elapsed Time Alerts’, or ETAs and you will see the term and reference throughout the Project Hub article.

Ron B

Ron Bentley
Creativity is an input to innovation and change is the output from innovation. Braden Kelley
A computer is a machine for constructing mappings from input to output. Michael Kirby
Through great input you get great output. RZA
Gauss is great but Euler rocks!!


frogandtoad, THRandell, Inq and 1 people liked
Quote
ron bentley
(@ronbentley1)
Estimable Member
Joined: 6 months ago
Posts: 207
Topic starter  

Update:

The ESP 32 timer used for the ETA framework is set at timer 0, by default.

However, this is selectable (0, 1, 2, or 3), simply by changing the defined macro 'default_timer' to the the timer number required.  An attempt to specify any other timer value in the set up call will result in the timer being set to the default timer value.

Ron B

Ron Bentley
Creativity is an input to innovation and change is the output from innovation. Braden Kelley
A computer is a machine for constructing mappings from input to output. Michael Kirby
Through great input you get great output. RZA
Gauss is great but Euler rocks!!


ReplyQuote
Inq
 Inq
(@inq)
Honorable Member
Joined: 5 months ago
Posts: 711
 

@ronbentley1 - It sounds like you have a lot of experience with interrupts.  I have a question.  I must admit in my twenty-odd years of making a living as a developer on Windows, I never used interrupts.  That was for hardware driver developers.  And maybe the answer would be different on my primary MPU - ESP8266 versus your topic's ESP32.

On an ESP8266, "the proverbial they" always say don't do anything in the interrupt callback... just set a flag and handle the grunt work in the main loop.  It seems (to me) that just checking a pin's high/low state is just as simple and just as fast as checking a volatile bool and then doing something based on the pin's state.  The few times I've used interrupt code using that strategy and then re-writing it to simply poll for the pins state in the loop resulted in far simpler code that didn't seem to have any less performance.

What am I missing?  What kind of use case, does it make sense to use an interrupt?  

Thanks.

VBR,

Inq

3 lines of code = InqPortal = Complete IoT, App, Web Server w/ GUI Admin Client, Access Point Manager, Drag & Drop File Manager, OTA, Performance Metrics, Web Socket Comms, Easy App API, All running on ESP8266...
Even usable on ESP-01S - Quickest Start Guide


ReplyQuote
ron bentley
(@ronbentley1)
Estimable Member
Joined: 6 months ago
Posts: 207
Topic starter  

@inq

Hi Inq,

Yes, I have had quite a lot of exposure to developing software with interrupts in my now long past career working on mini computers. Gosh, is it that long ago?

The ETA framework is more about:

a) removing some of the complexities (muck and bullets) of dealing with timer interrupts directly, at a basic level and in near real time (the framework does all of the low level set up) and, instead

b) providing a higher level capability for developers to use to concentrate simply on how often they want timer notifications, what they are for and whether they are a one-off or recurring event.  The framework provides a degree of asynchronicity  to using timer interrupts - you don't have to check pins, flags, statuses or anything else other than if timer events have happened and then what it is you decided to want do with them.  

In-between timer events (ETAs - elapsed time alerts) the code can be doing many other things; for most uses there is likely to be a good deal of 'down time' between timer alert processing so plenty of steam left in the 'boiler'.

For example, say I have a requirement to process a number of tasks at recurring fixed intervals of time, say 5 tasks (we can create as many as we wish):

task 1: process every 0.5 seconds

task 2: process every 5 seconds

task 3: process every 30 seconds

task 4: process every 60 minutes

task 5: process every 24 hours

We can assign a unique alert ID to each ETA, for example 100, 101, 102, 103, 104, respectively so that we can identify which timer has elapsed.

Error checking aside, these are set up as follows each as a recurring elapsed timer (ETA):

create_ETA(recurring_ETA, 100, one_second / 2);

create_ETA(recurring_ETA, 101, 5 * one_second); 

create_ETA(recurring_ETA, 102, 30 * one_second);

create_ETA(recurring_ETA, 103,  one_hour); 

create_ETA(recurring_ETA, 104,  one_day); 

(Note the final elapsed time interval parameters are framework macros to provide a degree of self documentation and readability.)

Now, we do nothing but wait for a timer(s) to elapse and process it by its/their alert ID that we assigned during their set up (here 100, 101, 102, 103 or 104).  In the meantime, the code can be doing whatever else it needs to do.

The structure of the man loop of the ETA framework deals with the handling of elapsing timers and all the developer needs to do is to decide how each should be processed - the example framework uses a switch/case construct switched on the alerting timer's alert ID (in this example 100, 101, 102, 103 or 104) that was initially set up.

Hope this helps?

Ron B

 

 

 

 

Ron Bentley
Creativity is an input to innovation and change is the output from innovation. Braden Kelley
A computer is a machine for constructing mappings from input to output. Michael Kirby
Through great input you get great output. RZA
Gauss is great but Euler rocks!!


Inq liked
ReplyQuote
Ron
 Ron
(@zander)
Famed Member
Joined: 2 years ago
Posts: 2755
 

@inq At least on the ESP32 'they' have relaxed what can and can't be done in an ISR. ALSO I recently found another technique for using interrupts with system supplied semaphores. It is entirely possible to only do that within the ISR and leave the bulk of the code to be inside the loop. Of course that raises the question of why bother with an ISR. I share that question. This is not the only 'issue' with ISR's, they must be stored in RAM not FLASH using a 'memory macro', and if much code is to be included inside the ISR then the code must be wrapped in a CRITICAL_SECTION macro so other interrupts don't upset the flow of instructions. I most recently found a whole different approach with semaphore macros but still have the same bug. My recent adventures with ISR's resulted in a bug I could not find so @will provided me with a different solution using simple millis() based timers. The bug FYI was I turned on timer 1 and timer 2 fired after it's interval. I tried several different example based solutions, they all worked the same way, some would run a long time before a failure, the latest iteration failed every time. I hate being stumped, but this one is winning.

It is considered poor judgement to traverse a chasm in 2 leaps.


ReplyQuote
robotBuilder
(@robotbuilder)
Noble Member
Joined: 3 years ago
Posts: 1373
 

@inq 

The few times I've used interrupt code using that strategy and then re-writing it to simply poll for the pins state in the loop resulted in far simpler code that didn't seem to have any less performance.

What am I missing?  What kind of use case, does it make sense to use an interrupt?

Some events can start and finish before you can poll them such as detecting a pulse from an encoder or a keypress or making sure an update occurs at a fixed time interval such as the display in a computer game.

Imagine the brain continually checking to see if one of the thousands of possible events has happened while doing some high-level task vs just being told what has just happened when it happens.

 


Inq liked
ReplyQuote
ron bentley
(@ronbentley1)
Estimable Member
Joined: 6 months ago
Posts: 207
Topic starter  

Hi,

I think the understanding is a bit of the mark. My post concerns timer interrupts, no other type.

The framework design ensures no timer interrupts get missed, no flags, semaphores or polling are/is required, etc. It a method to use a standard timer interrupt process (just one ESP 32) timer to allow the creation of any number of user definable releasing timers.

Cheers

Ron B

Ron Bentley
Creativity is an input to innovation and change is the output from innovation. Braden Kelley
A computer is a machine for constructing mappings from input to output. Michael Kirby
Through great input you get great output. RZA
Gauss is great but Euler rocks!!


ReplyQuote
Ron
 Ron
(@zander)
Famed Member
Joined: 2 years ago
Posts: 2755
 

@ronbentley1 I admit I may misunderstand, but htis is from the espressif documentation. You can see the macro to place the ISR in fast RAM, the critical section macro, and the semaphore calls to give and take the semaphore. Isn't this relevant?

/*
Repeat timer example

This example shows how to use hardware timer in ESP32. The timer calls onTimer
function every second. The timer can be stopped with button attached to PIN 0
(IO0).

This example code is in the public domain.
*/

// Stop button is attached to PIN 0 (IO0)
#define BTN_STOP_ALARM 0

hw_timer_t * timer = NULL;
volatile SemaphoreHandle_t timerSemaphore; to give and take
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

volatile uint32_t isrCounter = 0;
volatile uint32_t lastIsrAt = 0;

void ARDUINO_ISR_ATTR onTimer(){
// Increment the counter and set the time of ISR
portENTER_CRITICAL_ISR(&timerMux);
isrCounter++;
lastIsrAt = millis();
portEXIT_CRITICAL_ISR(&timerMux);
// Give a semaphore that we can check in the loop
xSemaphoreGiveFromISR(timerSemaphore, NULL);
// It is safe to use digitalRead/Write here if you want to toggle an output
}

void setup() {
Serial.begin(115200);

// Set BTN_STOP_ALARM to input mode
pinMode(BTN_STOP_ALARM, INPUT);

// Create semaphore to inform us when the timer has fired
timerSemaphore = xSemaphoreCreateBinary();

// Use 1st timer of 4 (counted from zero).
// Set 80 divider for prescaler (see ESP32 Technical Reference Manual for more
// info).
timer = timerBegin(0, 80, true);

// Attach onTimer function to our timer.
timerAttachInterrupt(timer, &onTimer, true);

// Set alarm to call onTimer function every second (value in microseconds).
// Repeat the alarm (third parameter)
timerAlarmWrite(timer, 1000000, true);

// Start an alarm
timerAlarmEnable(timer);
}

void loop() {
// If Timer has fired
if (xSemaphoreTake(timerSemaphore, 0) == pdTRUE){
uint32_t isrCount = 0, isrTime = 0;
// Read the interrupt count and time
portENTER_CRITICAL(&timerMux);
isrCount = isrCounter;
isrTime = lastIsrAt;
portEXIT_CRITICAL(&timerMux);
// Print it
Serial.print("onTimer no. ");
Serial.print(isrCount);
Serial.print(" at ");
Serial.print(isrTime);
Serial.println(" ms");
}
// If button is pressed
if (digitalRead(BTN_STOP_ALARM) == LOW) {
// If timer is still running
if (timer) {
// Stop and free timer
timerEnd(timer);
timer = NULL;
}
}
}

It is considered poor judgement to traverse a chasm in 2 leaps.


ReplyQuote
Ron
 Ron
(@zander)
Famed Member
Joined: 2 years ago
Posts: 2755
 

@ronbentley1 Its from https://espressif-docs.readthedocs-hosted.com/projects/arduino-esp32/en/latest/api/timer.html

It is considered poor judgement to traverse a chasm in 2 leaps.


ReplyQuote
ron bentley
(@ronbentley1)
Estimable Member
Joined: 6 months ago
Posts: 207
Topic starter  

@zander et al,

It's really good this had spawn interest, but let me assure you that no ESP 32 protocol for defining and using the interrupt timer capabilities has been violated. The framework adheres to the correct approach. If you examine the code posted on the Arduino Project Hub you will see it all above board - see this link in my initial post.

Regards

Ron B

Ron Bentley
Creativity is an input to innovation and change is the output from innovation. Braden Kelley
A computer is a machine for constructing mappings from input to output. Michael Kirby
Through great input you get great output. RZA
Gauss is great but Euler rocks!!


ReplyQuote
Ron
 Ron
(@zander)
Famed Member
Joined: 2 years ago
Posts: 2755
 

@ronbentley1 Ok, but didn't you say no semaphores etc?

It is considered poor judgement to traverse a chasm in 2 leaps.


ReplyQuote
ron bentley
(@ronbentley1)
Estimable Member
Joined: 6 months ago
Posts: 207
Topic starter  

@zander 

I did, the framework simply uses it's own interrupt counter, no externally produced semaphores, flags or other notifications.

I think that if you follow through my Arduino Project Hub all will become clear.

Ron B

Ron Bentley
Creativity is an input to innovation and change is the output from innovation. Braden Kelley
A computer is a machine for constructing mappings from input to output. Michael Kirby
Through great input you get great output. RZA
Gauss is great but Euler rocks!!


ReplyQuote
DaveE
(@davee)
Honorable Member
Joined: 1 year ago
Posts: 507
 

Hi @inq, and @robotbuilder, @zander and @ronbentley1,

  re: What am I missing? What kind of use case, does it make sense to use an interrupt?

The two Rons and robotbuilder have already given some good advice which I endorse.

I also totally agree with you (@inq), that in circumstances that a simple polling loop approach meets the project requirements, then it is simpler. In addition, interrupt routines can easily suffer from a bug, that can only be observed if the interrupt occurs a particular point in the 'main' code. Fixing such bugs can be a nightmare, and 'proving' that no such bugs exist is even harder.

Whilst I don't have any 'hard' evidence, I would be surprised if the processor choice, assuming mainstream processors are being discussed, (ESP8286 vs ESP32 is mentioned), affects the intrinsic ability to use interrupts. Of course, libraries on Github, etc. may have bugs which could make using a particular processor more difficult.

......................

A simple use case for interrupts, is to imagine you have a UART acting as RS232 (or similar) comms interface, with incoming characters appaering at a fairly high, but unpredictable, rate. Furthermore, assume it is a single processor system, which does much more than just service the serial port, so it must spend the majority of its time on its 'main programme' function, so a simple tight loop checking for incoming characters is not practicable.

In some cases, the processor may provide a 'special' function to store the incoming characters into a buffer, but a common implementation can only store 2 or 3 characters, and sometimes it maybe only one character. Thus the processor must service the serial port before its buffer overflows.  (I am assuming the incoming data cannot be rate controlled.)

As an example, if the baud rate is 115200, a common 'mid' speed, the time per character is around 85 microseconds, so even a 3 character buffer could fill in about 0.25 milliseconds.

In this case, an interrupt routine might be arranged to be called when the buffer has two characters. The interrupt routine may just transfer the received characters into a larger buffer that the 'main' program can access when it is ready to deal with it.

------------

Please note that this interrupt routine does more than 'just set a flag', but does not get involved in the more detailed processing of the incoming data. 

My understanding for the "they always say don't do anything in the interrupt callback."  approach is actually an oversimplification of what was originally envisaged.

Perhaps, something like "Do as much as possible in the 'main' program, which leaves a minimum to do in the interrupt service routine. " , would be a better scheme.

The obvious reason for this strategy is that whilst the processor is in the ISR, it is not 'usually' available for dealing with other 'urgent' tasks. Hence, minimising the time spent in each ISR is likely to produce the most responsive system.

Of course, it is possible to re-enable interrupts in the ISR, so that ISR calls can be nested, but this increases the complexity ... and the chance of a bug that only shows up at the most embarassing moments.

Best wishes all,

Dave


ron bentley and Inq liked
ReplyQuote
Inq
 Inq
(@inq)
Honorable Member
Joined: 5 months ago
Posts: 711
 
Posted by: @zander

ALSO I recently found another technique for using interrupts with system supplied semaphores.

Interesting... one of these days, I'll actually need to dig into the ESP32.  I may not be interrupt savvy... but I can multithread with the critical-sections, mutex and semaphors all day long.  I've written Windows apps that will happily run the CPU at 100% all-day and all-night-long (apologies to Lionel Richie) and still let you break in and work with the interface or even do other programs.

Posted by: @robotbuilder

Some events can start and finish before you can poll them such as detecting a pulse from an encoder or a keypress or making sure an update occu

Now this makes sense as a interrupt necessary valid example!  I almost forgot from my just starting out days with Arduino how hard it was to handle de-bounce of a simple momentary switch.  I don't think I ever found a 100% reliable method.  I could always make it fail "occasionally".  I think it is one of the reasons I like the virtual buttons on a web page... never-ever a debounce issue!

Thanks for the bad flash-back @robotbuilder.  🤣 😘 

Posted by: @ronbentley1

I think the understanding is a bit of the mark. My post concerns timer interrupts, no other type.

Sorry for opening Pandora's Box... I might as well be a bull seeing red when I see "Interrupts".  

3 lines of code = InqPortal = Complete IoT, App, Web Server w/ GUI Admin Client, Access Point Manager, Drag & Drop File Manager, OTA, Performance Metrics, Web Socket Comms, Easy App API, All running on ESP8266...
Even usable on ESP-01S - Quickest Start Guide


ReplyQuote
Inq
 Inq
(@inq)
Honorable Member
Joined: 5 months ago
Posts: 711
 
Posted by: @davee

In addition, interrupt routines can easily suffer from a bug, that can only be observed if the interrupt occurs a particular point in the 'main' code. Fixing such bugs can be a nightmare, and 'proving' that no such bugs exist is even harder.

I've always said... any bug can be fixed in minutes if it is repeatable!  There is nothing worse than a multi-threaded bug that only happens after DAYS... of 100% CPU on a massively multi-core machine.  I'm glad I got out when main-stream only had 4 cores / 8 threads to deal with. 

Posted by: @davee

Furthermore, assume it is a single processor system, which does much more than just service the serial port, so it must spend the majority of its time on its 'main programme' function, so a simple tight loop checking for incoming characters is not practicable.

I haven't done much with serial communications, but I am constantly amazed at how a lowly ESP8266 can do this exact type of thing with multiple browsers hitting my webserver library and firing events of incoming requests WHILE handling normal Arduino sensor / actuator type duties... all on one thread!  I've run over a hundred browsers banging on a server without hick-up... the requests all get served on all browsers... just very slowly, but still before the browsers time-out.

VBR,

Inq

3 lines of code = InqPortal = Complete IoT, App, Web Server w/ GUI Admin Client, Access Point Manager, Drag & Drop File Manager, OTA, Performance Metrics, Web Socket Comms, Easy App API, All running on ESP8266...
Even usable on ESP-01S - Quickest Start Guide


ron bentley and DaveE liked
ReplyQuote
Page 1 / 4