@Whandall perhaps you can help out making this ESP32 task example more indicative of what ESP32 can do.
The aim is the do high speed analog reads and blink the led
But the problem I have with ESP32 is that if I use vTaskDelay(1); the maximum read speed is ~1mS
On the otherhand if I use yield() the led does not blink.
// ESP32 example
#include <loopTimer.h>
// install the SafeString for loopTimer
// download SafeString V4.1.5+ library from the Arduino Library manager or from
// https://www.forward.com.au/pfod/ArduinoProgramming/SafeString/index.html
#if CONFIG_FREERTOS_UNICORE
#define ARDUINO_RUNNING_CORE 0
#else
#define ARDUINO_RUNNING_CORE 1
#endif
void TaskBlink( void *pvParameters );
void TaskAnalogRead( void *pvParameters );
// the setup function runs once when you press reset or power the board
void setup() {
// initialize serial communication at 9600 bits per second:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB, on LEONARDO, MICRO, YUN, and other 32u4 based boards.
}
for (int i = 10; i > 0; i--) {
Serial.println(i);
delay(500);
}
Serial.println("ESP32_FreeRTOS");
// Now set up two tasks to run independently.
xTaskCreatePinnedToCore(
TaskBlink
, "Blink" // A name just for humans
, 1204 // This stack size can be checked & adjusted by reading the Stack Highwater
, NULL
, 1 // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
, NULL
, ARDUINO_RUNNING_CORE);
xTaskCreatePinnedToCore(
TaskAnalogRead
, "AnalogRead"
, 1204 // Stack size
, NULL
, 2 // Priority higher for AnalogRead
, NULL
, ARDUINO_RUNNING_CORE);
// Now the task scheduler, which takes over control of scheduling individual tasks, is automatically started.
}
void loop() {
// Empty. Things are done in Tasks.
}
// TaskBlink Turns on an LED on for one second, then off for one second, repeatedly.
void TaskBlink(void *pvParameters) {
(void) pvParameters;
pinMode(LED_BUILTIN, OUTPUT); // initialize digital LED_BUILTIN on pin 13 as an output.
for (;;) // A Task shall never return or exit.
{
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
vTaskDelay( 1000 / portTICK_PERIOD_MS ); // wait for one second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
vTaskDelay( 1000 / portTICK_PERIOD_MS ); // wait for one second
}
}
// TaskAnalogRead Reads an analog input on pin 0, (optionally prints the result to the serial monitor but that slows things down)
void TaskAnalogRead(void *pvParameters) {
(void) pvParameters;
for (;;) {
loopTimer.check(Serial);
int sensorValue = analogRead(A0); // read the input on analog pin 0:
(void)(sensorValue);
// print out the value you read: BUT when Serial TX buffer fills this blocks
//Serial.println(sensorValue);
vTaskDelay(1); // one tick delay (1ms) in between reads to give blink time to run, limits max AnalogReads to ~1mS/sample
// yield(); // blink does not run max AnalogRead ~20uS
}
}
Output with vTaskDelay(1)
loop uS Latency
5sec max:998 avg:997
sofar max:998 avg:997 max - prt:90
I've read the entire thread you linked to, and your assertion is incorrect. The RTOS was failing to pre-empt due to a bug in the code example given. This quote is from further down the thread:
FreeRTOS scheduler in ESP-IDF is configured with preemption enabled. This is also the case in Arduino-esp32.
(snip)
The code sample you are running (from espressif/arduino-esp32#2814 (comment)), has a small bug which prevents you from getting the result you want.
So, as I thought (and to confirm what @Whandall says), ESP32's FreeRTOS really is a pre-emptive multi-tasking system, provided you've set it up that way and use it correctly.
Happy to update my advice if someone revise the example above to show that the ESP32 does actually run that way?
Currently the example has the AnalogRead in a higher priority task and a vTaskDelay(1) is necessary for the blink task to run (so not preemptive, to lower priority task, lower tasks need to treated co-operatively)
However if I change the priority of both tasks to be 1 say, then the analogRead only runs oddly.
The average time is 22uS BUT the maximum time is 2mS so it seems every now and again the analog read is blocked for 2mS interfering with the high speed sampling
I don't expect anyone feels the need to revise your code just to prove a point. Unfortunately I can't experiment anyway, because I'm moving house and everything from my workshop is in plastic boxes in the garage!
True, That link was only to confirm that pre-emptive was enabled for Arduino
The problem is getting it to actually work pre-emptively, even in a simple example
I was actually asking for how to set up the RTOS so that with any high speed task would play nicely with the rest of the tasks.
The preemptive part of the ESP32 does not seem to work as one might expect.
I avoid this issue by assigning tasks pinned to Core 0 even priorities and those pinned to Core 1 odd priorities. I don't use the 'tskNO_AFFINITY' designation.
Ok that is clear.
So lower priority tasks have to be run in co-operative manner.
That is higher priority tasks need to yield()/delay() or block to let the any lower priority tasks run other wise they will never run.
The ESP32 preemption timeslice does not guarantee any fairness in running all tasks.
If you pin tasks to specific cores and also segregate priorities by core (like I said, I use odd/even). The ESP32 will look like two separate processors running their respective tasks as expected.
Only if the higher priority tasks yield or become non-runnable. If not then they hog the cpu.
The problem in the example above was that I allocated the 'important' task a higher priority.
But the preemption timeslice did not run the lower priority task because there was no yield/delay in the analogRead task.
It seems the only way to (apart from the timer solution you provide) is to have all the other tasks on Core 1 running at the same priority as the analogRead task.
Setting both tasks to priority 1 does not work.
The analogRead task is has 2mS delay at least once every 5sec (average delay 22uS)
So the preemptive timeslice does not seem to be working consistently.
Just changing the priority of both tasks to 2 (everything else unchanged) gives the expected result
max delay 29uS avg 10 for the analogRead task.
I notice the loop() runs on priority 1, but it is just an empty loop().
When the other two tasks are set to priority 1 then the empty loop() gets a slice,
but when they set to priority 2, the loop() will never be run.
Why would running loop() {} add a 2mS delay in running the analogRead task?
But that is at the heart of designing with an RTOS. Issues like that are part of the story, so must be taken into account at the design stage. The fact that low priority tasks only run when all the higher priority tasks are blocked is both a strength and a shortcoming, depending on how you see it.
I don't know if FreeRTOS does this, but more sophisticated multi-taskers such as Windows will dynamically promote low priority tasks if they've been blocked for a certain amount of time, to help ensure they get at least some of the action.