Introduction to RTOS Part 9 - Hardware Interrupts | Digi-Key Electronics

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
in previous episodes i talked about how you should only use certain free rtos function calls inside interrupt service routines because interrupts are such an important part of many embedded systems let's talk about how we can integrate those with an rtos [Music] many microcontrollers have a number of built-in peripherals that are capable of generating hardware interrupts these can include things like a simple timer expiring or reaching a certain count or it could be something like a push button being pressed by a user which causes the voltage on a pin to change some microcontrollers have communication buses built into the hardware so that it can send and receive data using various protocols without the involvement of the cpu maybe a serial receive buffer fills up and it needs to notify the cpu that it's time to process the transmission in all cases an electrical signal is either sent to the cpu or changes a value in a register either way it tells the processor that some asynchronous event has happened and it should stop whatever it's doing to handle it these are all examples of hardware interrupts we won't go into much detail of how to use them as they are all architecture and chip specific you'll want to consult your particular chips datasheet or api to see how to use these various peripherals however we'll work with a simple timer in the esp32 so we can see how to sample an analog value at a set interval with a greater precision than what the rtos tick timer can provide in a previous lecture we saw how tasks and priorities work in an rtos so this graphic should look familiar the important thing here is that for most r tosses and definitely for free rtos a hardware interrupt will always have higher priority than any task running regardless of that task's priority some microcontrollers allow only one hardware interrupt to occur at a time while others allow hardware interrupts to interrupt other interrupts this is known as nested interrupts and it's a whole bag of worms that we won't get into in this series for our purposes it's safe to assume that any hardware interrupt will pause execution of any task and force the processor to execute the associated interrupt service routine or isr when the isr is done execution will return to a task this may or may not be the previously running task as we'll see in a minute note that you have the option of disabling some or all interrupts in your tasks which should be done sparingly if you need to protect some critical shared resource let's start by creating a simple hardware timer interrupt in our esp32 the esp32 comes with four timers each has a 16-bit prescaler and a 64-bit counter the default base timer clock is 80 megahertz and i believe that's what's used for most of these esp32 dev boards first we set our prescaler or divider this is a simple clock divider so if the clock is ticking at 80 megahertz and we set a divider of 80 the timer will now count up at 1 megahertz let's do a simple hardware interrupt driven blinky at one hertz since the timer is now ticking away at one megahertz we just need it to count to one million you'll want to make sure that the divider can fit into a 16 bit variable and the max count can fit into a 64-bit variable we'll use our built-in led and create a timer handle for this demo we're using the esp32 hal timer that comes with the arduino package library next we define our isr in the esp32 we want our isr to reside in the internal ram instead of flash so that it can be accessed faster so we use this iram attribute qualifier all we do in this isr is toggle the led if you're using a different microcontroller or ide feel free to set up a timer interrupt and isr that works with your hardware what we care most about in this lesson is how to synchronize isrs and free rtos tasks in setup we configure the led pin as output we then create and start hardware timer number 0 and give it the divider we specified we then configure the isr as the callback function for when the timer reaches the specified max value and give it the max value in the next function we also tell the timer to automatically reload after triggering the isr so that it will call the isr again periodically we then enable the timer interrupt and let it run let's upload this each time the timer reaches the max number it triggers the isr which toggles the led that means the led should be on for one second and off for one second over and over again let's change up our code sum to see one option for synchronizing isrs and tasks we'll add our usual one cpu limitation we'll also change the divider to eight so that the timer reaches the max count in 100 milliseconds instead of one second let's add a two second delay to the task we're going to create which will allow the isr to execute a few times before the task runs again i'll also create a new global variable that is a simple integer counter note that i need to add the volatile qualifier to let the compiler know that the value of the variable may change outside the scope of the currently executing task which means inside an isr in our case without it the compiler may simply think we're not using the variable and just remove it then we want to add a special kind of mutex in the esp idf version of freertos we need to use spin locks to prevent tasks in the other core from entering a critical section these spin locks work like mutexes but they cause any task that attempts to take them to loop forever waiting for it to be available as a result you want to only use this with specially designated critical section functions as you do not want the current core to wait forever on a spin lock in the isr we'll delete the blinky code and add our critical section here we protect a critical section with the spin lock and these special functions in addition to preventing tasks in the other core from taking the spin lock entering a critical section this way disables all interrupts in the current core this is an easy way to prevent nested interrupts from occurring but it might have the effect of delaying or missing other interrupts if they happen to trigger while you're in this critical section so you'll want to keep isr's short and critical sections even shorter also note that the free rtos documentation explicitly tells us not to call other free rtos api functions from within a critical section all we're going to do is increment our global variable before giving back the lock and re-enabling interrupts next let's create a task which i'll call print values in this task we'll decrement the global counter by 1 in a loop and print out the value each time note that we need to protect the decrement function as a critical section much like we did with a mutex in a previous lecture however a mutex does not do us much good as we cannot block an interrupt and we do not want it to spin instead we use a similar set of non-isr critical section functions to disable interrupts and prevent context switching while the current core executes the code between them note that we still need to use a spin lock here to prevent the other core from accessing this shared variable the vanilla free rtos version of the critical section is a bit simpler as it does not include the spin lock if a hardware interrupt does happen to occur during execution of this critical section it would not be dropped instead the isr will trigger as soon as the current execution leaves the critical section and interrupts are re-enabled we'll tell our task to wait for two seconds to let the isr increment the global variable a few times in setup we start the serial terminal and start the task as we've done before we also set up the hardware timer and delete the setup and loop task let's upload and run this if we open the serial terminal we can see that the global variable counts down from 19 every two seconds we also see a number repeat itself every now and then which means the interrupt triggered and the isr ran between serial print statements that's expected behavior as we want the interrupt service routine to run asynchronously and separately from the task other free rtos functions exist to help you synchronize variables and passing data between isrs and tasks however you'll want to make sure you only use the functions that end in from isr if you're calling them from within an interrupt service routine let's take a look at how to use a binary semaphore from within an isr we'll start with a time chart like the one before this time we'll have two tasks running at priority one and priority two let's say task a runs for a while with some idling at some point task b the task running at priority 2 interrupts and runs for a short while however task b reaches a point where it's waiting for a semaphore so it goes into the block state this causes the scheduler to run which chooses the only available task in the ready state task a so task a runs for a while and at some point a hardware interrupt occurs which calls our isr the isr uses some shared resource like changing a global variable or adding values to a buffer when it's done it gives the semaphore this action along with a special yield api call will force the scheduler to run and see that task b has just been unlocked because the semaphore is now available so the scheduler chooses to run task b task b can then process whatever data was made available by the isr this is known as a deferred interrupt the isr should be as short as possible so we defer processing the data to a task instead processing the data might take a while if you're doing heavy calculations like say computing the fast fourier transform by doing so in a task we at least give other tasks the chance to run for our demo we'll say that task b quickly goes back to waiting for the semaphore which allows task a to run here is our demo code it should look similar to the previous example first we change the divider and timer max count so that the isr will trigger every second again notice that we're now reading from the analog to digital converter in the interrupt service routine and storing the output into a global variable we use a binary semaphore to signal to our reading task that it's time to read from the global variable however we need to use this special from isr function to do so when inside the isr such from isr functions will never block as you cannot block an isr since it's not a task so if you're trying to take a mutex or semaphore and it's not available you'll want to deal with that differently rather than wait many of these functions also have this special task woken parameter one feature of many of these from isr functions is to determine if a new task is to be unlocked because a mutex or semaphore was given if a higher priority task was unblocked because of this action the scheduler should be called to start running that task immediately as soon as the isr is done so we record that fact in this boolean value which gets passed to the port yield from isr function note that the esp-idf version does not take it as a parameter so we can create a simple if statement to accomplish the same thing inside the task we simply wait forever for the binary semaphore to be available when it does become available thanks to the isr running we take it and print out the analog to digital value in setup we create the binary semaphore it's a good idea to check to make sure it's not null after creation notice here that i'm now running the print values task at priority 2 which is higher than the setup and loop task i start the timer and let loop run forever only to be interrupted by the isr and print values task let's upload this to our esp32 when we run this we should see an adc value being printed to the terminal once per second this is a good way to make a task wait for data to be available from an isr something you should be aware of is that newer versions of freertos have these direct-to-task notifications available if you know exactly which task is waiting to use some piece of data you can use a notification instead of a semaphore to let it know the advantage is that these task notifications are faster and more efficient than semaphores here is your challenge and it's a doozy you were to create a hardware timer that runs an isr to sample from the analog to digital converter 10 times per second these values are to be placed in some kind of buffer once 10 samples have been collected over the course of one second the isr is to wake up task a which then computes the average of those 10 samples i recommend using a double buffer or circular buffer here so that data can be read from part of it while the isr continues to fill up the other part the average should be a floating point value and stored in a global variable assume that this floating point global variable cannot be written to or read from in a single instruction cycle task b is to handle the serial terminal it should echo back any character it sees if you enter the command avg it should print out the value found in that global variable all this may look straightforward at first glance but i assure you that you'll need to use much of what you've learned in the last few lectures to synchronize the tasks and interrupts for example you'll need to think about how to handle the case where your buffer overruns meaning the isr tries to write to an element that task a has not read from yet there are lots of different ways to get this to work but i'll post one possible solution in case you get stuck the good news is that this is a very common design for working with data in an rtos where you have one task performing complex calculations on incoming data while another task handles user input and output you can easily extrapolate this template to do things like working with 16 kilohertz audio data and computing the fast fourier transform in real time while displaying the results to a console you'd ideally want something like a direct memory access controller to help you out with the data sampling but the isr and task synchronization would be much the same as what we have here if i did everything right i should be able to type stuff into a serial terminal and have it be immediately echoed back to me this is happening while the isr is sampling from the adc pin and another task is computing the average in real time when i enter avg the most recently computed average is displayed this is kind of like a final project in that it forces you to use many of the concepts we covered previously in the next few lectures we're going to go over some of the common pitfalls with multi-threading the theory behind them and what you can do to avoid them we'll start with deadlocks in the next episode see you then [Music] you
Info
Channel: DigiKey
Views: 65,050
Rating: undefined out of 5
Keywords: rtos, freertos, esp32, iot, microcontroller, embedded system, arduino, interrupt, isr, timer
Id: qsflCf6ahXU
Channel Id: undefined
Length: 15min 10sec (910 seconds)
Published: Mon Mar 15 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.