Interrupting while someone is talking is rude for humans, but smart for computers. [Ivan Voras] shows how to use interrupts to service the ESP32 analog to digital converters when sampling sound. Interestingly, he uses the Arduino IDE mixed with native ESP-IDF APIs to get the best performance.
Like most complex interrupt-driven software, [Ivan’s] code uses a two-stage interrupt strategy. When a timer expires, an interrupt occurs. The handler needs to complete quickly so it does nothing but set a flag. Another routine blocks on the flag and then does the actual work required.
Because the interrupt service routine needs to be fast, it has to be in RAM. [Ivan] uses the IRAM_ATTR attribute to make this work and explains what’s going on when you use it.
…the CPU cores can only execute instructions (and access data) from the embedded RAM, not from the flash storage where the program code and data are normally stored. To get around this, a part of the total 520 KiB of RAM is dedicated as IRAM, a 128 KiB cache used to transparently load code from flash storage.The ESP32 uses separate buses for code and data (“Harvard architecture”) so they are very much handled separately, and that extends to memory properties: IRAM is special, and can only be accessed at 32-bit address boundaries.
This is very important because some ESP-IDF calls — including adc1_get_raw — do not use this attribute and will, therefore, crash if they get pushed out to flash memory. At the end, he muses between the benefit of using an OS with the ESP32 or going bare metal.
If you want to know more about the Arduino on ESP32, we covered that. We also dug deeper into the chip a few times.
9 thoughts on “ESP32 Audio Sampling With Interrupts And IRAM”
Most things you can do on an ESP32 you can also do on an ESP8266, except that the ESP8266 doesn’t have Bluetooth.
The ESP8266 can do Arduino as well.
If you’re deciding between Arduino and bare metal then there is a third choice.
NodeMCU is implemented in LUA.
LUA is a very fast and efficient language that is strongly event driven and would he perfect for this.
a) Lua is not an acronym.
b) it’s not actually event driven at all.
c) nodemcu implements it, not the other way around
and it’s definitely not perfect for this kind of realtime task, especially compared to the bare metal approach chosen.
No lower level language is inherently event driven.
While Lua is technically a high level language, it is much closer to bare metal than what is being used here, Arduino (Sketch) and the native API.
Lua has first class functions which means functions are the same as variables and objects. In Lua a function or object can return or possess (as an attribute) another function as opposed to the “results” of another function.
A function can be carefully constructed as an event. This function or event then can be assigned to multiple objects. Unlike other languages, this function is not duplicated for each object. Instead there remains one function and several tables if operands.
Any of these operands can be be a function or object that may contain other functions.
This is in part where Lua gets it’s speed and ease of coding from once you understand how it works under the hood, even though it is an interpreted language.
I don’t know definitively whether or not the implementations below would be a better solution.
I’m just suggesting these as they may be worth a look.
“… crash if they get pushed out to flash memory.” should be “… crash if they get flushed from IRAM and an attempt is made to reload them from flash in certain circumstances.”?
In my experience the guru overlooking the OS can be easily upset if not everything is done in accordance with his rules, and they are not always easy to comply with when it comes to interrupt handling.
looks like you’re right. I’ve never used ESP32 but I wonder if there is a way to tell the compiler that certain functions always need to be in RAM?
There is, for functions use the IRAM_ATTR attribute as referred to in the main article. In some circumstances you also need to ensure any constants accessed by the interrupt handler are in RAM or IRAM too.
And we both got the reference to IRAM/RAM wrong way around – my comment above should refer to RAM not IRAM and yours to IRAM…
As I understand it IRAM is a piece of RAM that never gets flushed, functions not decorated with IRAM_ATTR may be paged in and out of RAM by the OS as it sees fit since no code can be run directly from flash.
Did something change in ESP32? I was able to run code directly from flash on ESP8266 by compiling it as position independent, dropping it to a known flash area, and then setting the program counter to that address. The only problem with this was calling into some of the ESP8266 library functions that worked on byte level addresses, like printf family example is %c. Those would crash.
The story with the IRAM and flash cache is more complicated. The usual user code is executed from the “flash cache”, which is filled from the SPI flash transparently as the code executes. The flash cache can’t operate when the SPI flash is accessed explicitly (for example, for writing), so the functions responsible for flash access disable the flash cache for the duration of the operation. Obviously, these functions are executed from IRAM.
Now comes the interrupt handler. If you try to run an interrupt handler from the flash cache, it will run fine most of the time. But if the interrupt hits when the flash cache is disabled, your handler code will not load and “bad things” will happen. The OS can either disable the interrupts for the duration of flash ops (up to hundreds of ms, not good), or require you to have the interrupt handler located in IRAM. I *think* that ESP-IDF gives you an option to specify which interrupt handlers are IRAM-safe.
Also check out the Arduino->Examples->Esp32->i2s->HiFreq_ADC example reading the ADC through I2S to take advantage of existing hardware instead requiring an interrupt per sample.
Please be kind and respectful to help make the comments section excellent. (Comment Policy)