esp-idf-hal
esp-idf-hal copied to clipboard
Watchdog timer reset with fast main loop: How is it supposed to be written with safe Rust?
Hi,
in my program I use a fast main loop without sleeps in it. I want it to be as fast as possible. The problem looks like this (unrelated parts left out):
fn main() {
loop {
// Some work, like fast pin toggling.
}
}
This results in the watchdog to kill my tasks:
E (5342) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
E (5342) task_wdt: - IDLE (CPU 0)
E (5342) task_wdt: Tasks currently running:
E (5342) task_wdt: CPU 0: main
E (5342) task_wdt: CPU 1: IDLE
E (5342) task_wdt: Print CPU 0 (current core) backtrace
I interpret this as such that the IDLE task on CPU0 is never executed. That's actually desired. That's what I told it to do. I don't want to give up my task execution. All other tasks shall run on the second CPU. But apparently the watchdog is handled by the IDLE task. Therefore it times out.
I do not want to add a sleep to the main loop (which would eventually trigger the IDLE task).
I came up with the following workaround (using esp_idf_sys crate):
fn main() {
// Disable IDLE task WDT on this CPU.
unsafe { esp_idf_sys::esp_task_wdt_delete(esp_idf_sys::xTaskGetIdleTaskHandleForCPU(esp_idf_hal::cpu::core() as u32)) };
// Enable WDT on the main task (this task).
unsafe { esp_idf_sys::esp_task_wdt_add(esp_idf_sys::xTaskGetCurrentTaskHandle()) };
loop {
// Some work, like fast pin toggling.
// Feed WDT.
unsafe { esp_idf_sys::esp_task_wdt_reset(); }
}
}
That works. Now the watchdog doesn't trigger any more and my fast loop executes forever.
Now my question: Is there a safe-Rust way to do this with esp_idf_hal or another safe crate API?
Hi, I don't know if this is the same issue as mine. I've a similar problem. I try to use the Blinky example, but the watchdog kills the app. If I change the main loop from this:
loop {
led.set_high()?;
thread::sleep(Duration::from_millis(1000));
led.set_low()?;
thread::sleep(Duration::from_millis(1000));
}
to this:
loop {
led.set_high()?;
thread::sleep(Duration::from_millis(500));
led.set_low()?;
thread::sleep(Duration::from_millis(1500));
}
Everything is fine. I've no idea why.
This is probably due to this: https://github.com/esp-rs/rust/issues/137. I would recommend rolling back to 1.63.0.0 for now. I have been working on a proper fix which I am testing right now.
Apologies for the confusion!
@mr-sven Can you test with the latest compiler and let us know if you still see the issue? Otherwise, your approach is correct - you have to - from time to time - put your main task to sleep for at least 10ms, so that the lower priority IDLE tasks get a chance to run. Or switch to an event driven model, where your threads are reacting to events and then go to sleep again, waiting on a queue, or mutex or whatever that signals that a new event is due.
Hi, confirmed. I tested with 1.64 and default Blink example. thread::sleep
is now working.
@MabezDev Please re-open this issue. The questions from my original post have not been addressed or answered in any way. What has been resolved is the essentially (as it turned out later) unrelated sub discussion.
I am still wondering how to code a fast main loop without sleeping and without unsafe.
Can somebody please re-open this issue? It has not been resolved. The problem that @mr-sven confimed as fixed turned out to be completely unrelated to my original question.
Thanks a lot :)
If you want to busy-loop in the main (or whichever thread) forever, then I suggest you instead simply disable the TWDT as described here.
Note though that this is not a pattern encouraged by ESP-IDF, where the philosophy (as far as I understand it) is to have your threads reacting on events rather than busy looping.
With the above said, we can implement some sort of safe API around TWDT. It was postponed for now because (a) this is a relatively rare request (folks often just do not understand that busy looping has alternatives) and (b) the Watchdog traits in e-hal 0.2 disappeared (perhaps temporarily or because there was no consensus on those) from e-hal 1.0.
I can also re-open this issue (you seem to feel very strongly about this :p) but I don't have time ATM to implement it. Would you be willing to spend some time and contribute a PR? Perhaps, to the esp-idf-hal::task
module in master
?
Cool. Thanks for explaining the reasons behind this.
Yes, I do understand that often one shouldn't be using a busy loop.
However, how can we achieve small latencies without doing so? If I have to go sleeping a couple of milliseconds every now and then, then this is the lower limit to main task latency. For instance let's assume I need to toggle a pin every 100 µs. I don't think this is too uncommon. How would I implement that?
I am willing to contribute time and even PR for this. But I currently don't seem to understand the current architecture good enough and I don't fully understand how a safe wdt api could look like to fit your architecture design.
Thanks for your help.
For instance let's assume I need to toggle a pin every 100 µs. I don't think this is too uncommon. How would I implement that?
100uS is quick, yet might be slow enough so that you can use the ESP timer service which is exposed in esp-idf-svc
. Or some of the ESP IDF hardware timers which are also exposed in latest esp-idf-hal
master
. As for how a safe API might look like - I agree this deserves some thought. As for the architecture design - esp-idf-hal
and esp-idf-svc
are relatively thin wrappers.
Is there a safer or better way of doing this, like @mbuesch wrote?
unsafe { esp_idf_sys::esp_task_wdt_delete(esp_idf_sys::xTaskGetIdleTaskHandleForCPU(esp_idf_hal::cpu::core() as u32)) };
I'm not worried about using "unsafe" code as long as it works, but I want to do it in the most proper way (without taking a break from the loop for any time)
https://github.com/esp-rs/esp-idf-hal/blob/master/src/task.rs#L601
https://github.com/esp-rs/esp-idf-hal/blob/master/src/task.rs#L601
@ivmarkov am I doing this correctly (I'm still getting watchdog errors)?
use esp_idf_hal::{
peripherals::Peripherals,
task::watchdog::{config::Config, TWDTDriver},
};
use esp_idf_sys as _;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// It is necessary to call this function once,
// or else some patches to the runtime implemented by esp-idf-sys might not link properly.
esp_idf_sys::link_patches();
let peripherals = Peripherals::take().unwrap();
let mut twdt_driver = TWDTDriver::new(peripherals.twdt, &Config::default()).unwrap();
let mut sub = twdt_driver.watch_current_task().unwrap();
loop {
sub.feed().unwrap();
}
}
I admit I have forgotten this code a bit... But with that said, you are getting a TWDT alert not because of your task, but because your task does not give a chance of the IDLE tasks to run. See here: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/wdts.html
So I don't remember which method it was, but you basically have to delete the idle tasks rather than subscribing YOUR task to be watched. Or disable the idle tasks watching with a conf_ setting.
Look at the code link I pasted to figure out which method to call to delete the idle tasks, or as I said - remove the idle tasks watchdog with a conf setting.
I disabled the watchdog by editing sdkconfig.defaults
:
CONFIG_ESP_TASK_WDT_EN=n
And that solved the problem for me