I’m trying to follow this tutorial explaining the interaction between FreeRTOS and the ESP32 hardware timers. As such, I have the following code right now:
static const uint16_t timer_prescaler = 80; // Clock ticks at 80 MHz / 80 = 1 MHz
static const uint64_t timer_max_count = 1e7; // 1e7 max count means that the timer will reset after 1 second
static hw_timer_t *timer = NULL;
// IRAM_ATTR forces the ISR to live in internal RAM and not the flash for faster access
void IRAM_ATTR onTimer() {
Serial.println("ISR");
int pin_state = digitalRead(led_pin);
Serial.println(pin_state);
digitalWrite(led_pin, !pin_state);
}
void setup() {
Serial.begin(115200);
pinMode(led_pin, OUTPUT);
vTaskDelay(1000 / portTICK_PERIOD_MS); // Wait a moment to start
Serial.println();
Serial.println("--- FreeRTOS Hardware Timer Demo ---");
// Create and start the hardware timer and attach the ISR
timer = timerBegin(0 /* timer number */, timer_prescaler, true /* countUp? */);
timerAttachInterrupt(timer, &onTimer, true /* edge */);
timerAlarmWrite(timer, timer_max_count, true /* autoreload? */);
// Allow the ISR to trigger
timerAlarmEnable(timer);
}
void loop() {
}
I would expect this to at the very least print “ISR” to the serial port every second, but it doesn’t for some reason:
Doing a print in an interrupt function should be an instant crash. The way I see the code in the tutorial is that it only increments a variable, protected by a critical section. The main loop then continuously prints the variable. Does that work?
The API in those examples is quite foreign to me… I don’t think I’ve ever seen those functions/objects before. Could you briefly explain what the notable differences are for hardware timer interrupts?
EDIT: I realize my prior comments may seem agitated or angry. I’m very interested in what you have to say, but I honestly don’t really understand how the libraries you linked to relate to the ESP-IDF Arduino core’s implementation of hardware timer interrupts.
Ah, sorry for forgetting about this, but yes, I’ve just tested the linked sketch and it actually does not work. There is no blinky on my defined LED pin (2). Even looking with an oscilliscope at 12MHz (for a possible high-speed signal due to wrongly configured timer), IO2 is always low. Let me investigate on why that is.
are not marked as volatile and so the compiler does some breaking optimizations. Putting volatile in front of every uint32_t ... member as in
typedef struct {
union {
struct {
volatile uint32_t reserved0: 10;
volatile uint32_t alarm_en: 1; /*When set alarm is enabled*/
volatile uint32_t level_int_en: 1; /*When set level type interrupt will be generated during alarm*/
volatile uint32_t edge_int_en: 1; /*When set edge type interrupt will be generated during alarm*/
volatile uint32_t divider: 16; /*Timer clock (T0/1_clk) pre-scale value.*/
volatile uint32_t autoreload: 1; /*When set timer 0/1 auto-reload at alarming is enabled*/
volatile uint32_t increase: 1; /*When set timer 0/1 time-base counter increment. When cleared timer 0 time-base counter decrement.*/
volatile uint32_t enable: 1; /*When set timer 0/1 time-base counter is enabled*/
};
volatile uint32_t val;
} config;
volatile uint32_t cnt_low; /*Register to store timer 0/1 time-base counter current value lower 32 bits.*/
volatile uint32_t cnt_high; /*Register to store timer 0 time-base counter current value higher 32 bits.*/
volatile uint32_t update; /*Write any value will trigger a timer 0 time-base counter value update (timer 0 current value will be stored in registers above)*/
volatile uint32_t alarm_low; /*Timer 0 time-base counter value lower 32 bits that will trigger the alarm*/
volatile uint32_t alarm_high; /*Timer 0 time-base counter value higher 32 bits that will trigger the alarm*/
volatile uint32_t load_low; /*Lower 32 bits of the value that will load into timer 0 time-base counter*/
volatile uint32_t load_high; /*higher 32 bits of the value that will load into timer 0 time-base counter*/
volatile uint32_t reload; /*Write any value will trigger timer 0 time-base counter reload*/
} hw_timer_reg_t;
solved the problem for me, now without any additional call to timerAlarmEnable(), it works.
Let me check if this behavior is still the same with 2.0.0-alpha1 and the new compiler. Sadly the register definitions have stayed the same.
For the “normal” 1.0.6 version you get with latest platform = espressif32 and not no additional platform_packages, above workaround is valid (use delayMicroseconds()) as well as the real bugfix with volatile.
As for the versions after that, it needs volatileand calling timerAttachInterrupt with the last parameter set to false instead of true in the sketch code for ESP32 chips (edge interrupts do not work properly there – this does not apply to other ESP32-S2, etc. chip types)
So @ifconfig, can you test it first whether the LED blinky works with the simple delayMicroseconds workaround, or even better, the volatile fix?
Ah @ifconfig, I forgot that you’re not using the stable 1.0.6 core since you have a ESP32-S2 board. So, also the bit about the timer function not supporting edge = true should be invalid for you.
to get to the master branch with the fix (along with a necessary esptoolpy update).
Notice that if you have used some master version before, PlatformIO might not do a git update, so it’s best to remove all folders C:\Users\<user>\.platformio\packages\framework-arduinoespressif32* to get a clean build.
WOW! Thanks for all of the detective work here, and for writing it all down so I understand your thought process! Seriously.
I’ll try this when I get home from work tonight. I suppose you’re right that changing to the master branch instead of using the 2.0.0-alpha1 release will incorporate the recent commit you were able to get made to add volatile to the hw_timer_reg_t struct, so I’ll try letting go of the edge trigger flag.
However, I’m a little confused about what the timer interrupt actually triggers on if not on the counter edge… could you explain a little?
Well per the ESP32’s technical reference a timer can be set up to either “generate an edge type interrupt.” or “generate a level type interrupt”. And per chapter 3 of that document and here a CPU core can have 32 interrupt sources where you can stick the timer interrupt into, but each slot is hardcoded for level or edge-type interrupt. But not really sure what it means specifically for the counter / timer after reading that…
Lucky. In any ISR, you aren’t supposed to be able to wait on mutexes, since if they are already taken by someone else, you would have to wait until the user releases it (for a specified ‘block time’ in FreeRTOS), however if the ISR halts to wait then whole CPU is halted (this is different to when only a FreeRTOS thread gets halted).
A Serial.println() goes eventually to the HardwareSerial::write function
where
So I still stand by it that if you write to the UART in the exact moment in which another thread was writing a byte to the UART, the mutex locking will lock up the system. This may be hard to trigger though (but not impossible). And in any case, calling xSemaphoreTake from inside an ISR is a FreeRTOS spec violation.