I’m experimenting with an ESP32-S3 for a dual-motor setup using SimpleFOC, and I’m curious if anyone has successfully run motor control + sensor reads on separate cores/threads using FreeRTOS tasks?
I know the library isn’t strictly thread-safe, but wondering if splitting control loops across cores (e.g., using core affinity) improves responsiveness or PWM jitter? Or is it better to stick to a single loop and optimize timing?
Would love to hear if someone has already tested this or hit performance limits.
I’ve used SimpleFOC with FreeRTOS on several occasions.
On ESP32 the default tick time is 1ms, so it’s important to realize that this is too slow for motor control. You could try playing with faster tick times, but I don’t think FreeRTOS is really suited to scheduling things at microsecond speeds.
So I tend to use it in a way where I reserve one MCU core dedicated to the motor control, and one for scheduled FreeRTOS tasks. Motor control isn’t scheduled by FreeRTOS it just runs 100% of the time on its core. The FreeRTOS watchdog has to be disabled for that core, and FreeRTOS configured to schedule tasks only on the other core. Likewise, interrupts should be configured to be handled on the correct core…
By the same logic motor and sensor routines should probably not be split across different cores, and not be scheduled by FreeRTOS…
I’m running two motors on ESP32-WROOM with FreeRTOS. The motor loops (motor control + sensor reads) runs on core 1 at 100% of the time, and it’s the only task on that core. The FreeRTOS watchdog has not been disabled for that core and all my other tasks are manually pinned to core 0.
As @runger said, I don’t think it’s possible to split the loop on multiple tasks. Task switching is not fast enough compared to the loop rate we are considering here.
I have tried to put the angle reading, position loop, speed loop and current loop into two cores respectively through FREERTOS (I turned off the tick interrupt after allocation), and the FOC control frequency easily exceeded 30khz (there seems to be some other optimizations, such as canceling the SPI mutex lock, which I have forgotten a little bit)
Thanks for the explanationm, that makes sense. I also didn’t disable the watchdog on core 1, but since the loop runs at 100% and is tight, wouldn’t that eventually trigger the watchdog? Or are you feeding it somewhere in the loop?
As long as you return from loop() it gets fed between calls to loop(). If you don’t return and also don’t call any blocking FreeRTOS functions, then in my experience it does trigger after a little bit…