Good point, and a good way of looking at it. An RTOS like FreeRTOS is a sophisticated toolkit for producing real-time applications, but it does need a considerable amount of understanding to use it to its full potential.
Found this on stackexchange rtos - Does priority inheritance cure priority inversion? - Electrical Engineering Stack Exchange
Quote ====
An example: The normal situation used is that task A, B, C are in decreasing priority. C takes a mutex, then B becomes ready and is running. Now A becomes ready. A needs the mutex, so blocks on it. Then B continues running which prevents C from releasing the mutex. This effectively makes B have enough priority to block A from doing work.
The priority inheritance would make C have the same priority as A. But consider the case where C cannot complete its work to release the mutex without B doing work. Then because of the priority inheritance, you have a deadlock.
Therefore, priority inversion can exist, and it can be hard to detect potential failures. The solution is to attempt to avoid it by design as much as possible.
Here's the entire blurb from the freertos site:
> Priority inheritance does not cure priority inversion! It just minimises its effect in some situations. Hard real time applications should be designed such that priority inversion does not happen in the first place.
====== End Quote
Here is a link with diagrams. FreeRTOS Priority Inversion
It also takes about Priority Ceiling Protocol
Point being?
This is much more complicated then I imagined when I posed the problem.
Agree.
Just to be sure: Have you tried the RTOS macro "taskYIELD()" instead of "yield()"?
Same thing. In the end, all "yields" are calls to vPortYield()
From 'esp32-hal-misc.c':
void yield()
{
vPortYield();
}
From 'task.h':
#define taskYIELD() portYIELD()
from 'portmacro.h':
#define portYIELD() vPortYield()
I wasn't sure how "yield()" was implemented, so thanks for clarifying that! ![]()
In the end the solutions are to: 1) Give the blink task the higher priority; 2) Run the tasks on different cores; 3) Use a peripheral like RMT to do the blinking or; 4) Do both blinking and reading in the same task.
That's when freeRTOS is doing clean up tasks.
Want faster analog reads use the ESP32 A:D API instead of using the additional add on layer of the Arduino core wrappers.
And, per the link I posted, use a timer interrupt (set at the desired sample period) to wake up a high-priority task to do the ADC reads.
fast analog reading
#include "sdkconfig.h"
#include "esp32/ulp.h"
#include "driver/rtc_io.h"
#include "esp_system.h" //This inclusion configures the peripherals in the ESP system.
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/event_groups.h"
#include <Adafruit_NeoPixel.h>
#include "AudioAnalyzer.h"
////
/* define event group and event bits */
EventGroupHandle_t eg;
#define evtDo_AudioReadFreq ( 1 << 0 ) // 1
////
TickType_t xTicksToWait0 = 0;
////
QueueHandle_t xQ_LED_Info;
////
const int NeoPixelPin = 26;
const int LED_COUNT = 24; //total number of leds in the strip
const int NOISE = 0; // noise that you want to chop off
const int SEG = 6; // how many parts you want to separate the led strip into
const int Priority4 = 4;
const int TaskStack40K = 40000;
const int TaskCore1 = 1;
const int TaskCore0 = 0;
const int AudioSampleSize = 6;
const int Brightness = 180;
const int A_D_ConversionBits = 4096; // arduino use 1024, ESP32 use 4096
const float LED0_Multiplier = 2.0;
////
Analyzer Audio = Analyzer( 5, 15, 36 );//Strobe pin ->15 RST pin ->2 Analog Pin ->36
// When we setup the NeoPixel library, we tell it how many pixels, and which pin to use to send signals.
Adafruit_NeoPixel leds = Adafruit_NeoPixel( LED_COUNT, NeoPixelPin, NEO_GRB + NEO_KHZ800 );
////
int FreqVal[7];//create an array to store the value of different freq
////
void ULP_BLINK_RUN(uint32_t us);
////
void setup()
{
ULP_BLINK_RUN(100000);
eg = xEventGroupCreate();
Audio.Init(); // start the audio analyzer
leds.begin(); // Call this to start up the LED strip.
clearLEDs(); // This function, defined below, de-energizes all LEDs...
leds.show(); // ...but the LEDs don't actually update until you call this.
////
xQ_LED_Info = xQueueCreate ( 1, sizeof(FreqVal) );
//////////////////////////////////////////////////////////////////////////////////////////////
xTaskCreatePinnedToCore( fDo_AudioReadFreq, "fDo_ AudioReadFreq", TaskStack40K, NULL, Priority4, NULL, TaskCore1 ); //assigned to core
xTaskCreatePinnedToCore( fDo_LEDs, "fDo_ LEDs", TaskStack40K, NULL, Priority4, NULL, TaskCore0 ); //assigned to core
xEventGroupSetBits( eg, evtDo_AudioReadFreq );
} // setup()
////
void loop() {} // void loop
////
void fDo_LEDs( void *pvParameters )
{
int iFreqVal[7];
int j;
leds.setBrightness( Brightness ); // 1 = min brightness (off), 255 = max brightness.
for (;;)
{
if (xQueueReceive( xQ_LED_Info, &iFreqVal, portMAX_DELAY) == pdTRUE)
{
j = 0;
//assign different values for different parts of the led strip
for (j = 0; j < LED_COUNT; j++)
{
if ( (0 <= j) && (j < (LED_COUNT / SEG)) )
{
set(j, iFreqVal[0]); // set the color of led
}
else if ( ((LED_COUNT / SEG) <= j) && (j < (LED_COUNT / SEG * 2)) )
{
set(j, iFreqVal[1]); //orginal code
}
else if ( ((LED_COUNT / SEG * 2) <= j) && (j < (LED_COUNT / SEG * 3)) )
{
set(j, iFreqVal[2]);
}
else if ( ((LED_COUNT / SEG * 3) <= j) && (j < (LED_COUNT / SEG * 4)) )
{
set(j, iFreqVal[3]);
}
else if ( ((LED_COUNT / SEG * 4) <= j) && (j < (LED_COUNT / SEG * 5)) )
{
set(j, iFreqVal[4]);
}
else
{
set(j, iFreqVal[5]);
}
}
leds.show();
}
xEventGroupSetBits( eg, evtDo_AudioReadFreq );
}
vTaskDelete( NULL );
} // void fDo_ LEDs( void *pvParameters )
////
void fDo_AudioReadFreq( void *pvParameters )
{
int64_t EndTime = esp_timer_get_time();
int64_t StartTime = esp_timer_get_time(); //gets time in uSeconds like Arduino Micros
for (;;)
{
xEventGroupWaitBits (eg, evtDo_AudioReadFreq, pdTRUE, pdTRUE, portMAX_DELAY);
EndTime = esp_timer_get_time() - StartTime;
// log_i( "TimeSpentOnTasks: %d", EndTime );
Audio.ReadFreq(FreqVal);
for (int i = 0; i < 7; i++)
{
FreqVal[i] = constrain( FreqVal[i], NOISE, A_D_ConversionBits );
FreqVal[i] = map( FreqVal[i], NOISE, A_D_ConversionBits, 0, 255 );
// log_i( "Freq %d Value: %d", i, FreqVal[i]);//used for debugging and Freq choosing
}
xQueueSend( xQ_LED_Info, ( void * ) &FreqVal, xTicksToWait0 );
StartTime = esp_timer_get_time();
}
vTaskDelete( NULL );
} // fDo_ AudioReadFreq( void *pvParameters )
////
//the following function set the led color based on its position and freq value
//
void set(byte position, int value)
{
// segment 0, red
if ( (0 <= position) && (position < LED_COUNT / SEG) ) // segment 0 (bottom to top), red
{
if ( value == 0 )
{
leds.setPixelColor( position, 0, 0, 0 );
} else {
// increase light output of a low number
// value += 10;
// value = constrain( value, 0, 255 ); // keep raised value within limits
if ( value <= 25 )
{
leds.setPixelColor( position, leds.Color( value , 0, 0) );
} else {
if ( (value * LED0_Multiplier) >= 255 )
{
leds.setPixelColor( position, leds.Color( 255 , 0, 0) );
} else {
leds.setPixelColor( position, leds.Color( (value * LED0_Multiplier) , 0, 0) );
}
}
}
}
else if ( (LED_COUNT / SEG <= position) && (position < LED_COUNT / SEG * 2) ) // segment 1 yellow
{
if ( value == 0 )
{
leds.setPixelColor(position, leds.Color(0, 0, 0));
}
else
{
leds.setPixelColor(position, leds.Color( value, value, 0)); // works better to make yellow
}
}
else if ( (LED_COUNT / SEG * 2 <= position) && (position < LED_COUNT / SEG * 3) ) // segment 2 pink
{
if ( value == 0 )
{
leds.setPixelColor(position, leds.Color(0, 0, 0));
}
else
{
leds.setPixelColor(position, leds.Color( value, 0, value * .91) ); // pink
}
}
else if ( (LED_COUNT / SEG * 3 <= position) && (position < LED_COUNT / SEG * 4) ) // seg 3, green
{
if ( value == 0 )
{
leds.setPixelColor(position, leds.Color( 0, 0, 0));
}
else //
{
leds.setPixelColor( position, leds.Color( 0, value, 0) ); //
}
}
else if ( (LED_COUNT / SEG * 4 <= position) && (position < LED_COUNT / SEG * 5) ) // segment 4, leds.color( R, G, B ), blue
{
if ( value == 0 )
{
leds.setPixelColor(position, leds.Color( 0, 0, 0));
}
else //
{
leds.setPixelColor(position, leds.Color( 0, 0, value) ); // blue
}
}
else // segment 5
{
if ( value == 0 )
{
leds.setPixelColor(position, leds.Color( 0, 0, 0)); // only helps a little bit in turning the leds off
}
else
{
leds.setPixelColor( position, leds.Color( value, value * .3, 0) ); // orange
}
}
} // void set(byte position, int value)
////
void clearLEDs()
{
for (int i = 0; i < LED_COUNT; i++)
{
leds.setPixelColor(i, 0);
}
} // void clearLEDs()
//////////////////////////////////////////////
/*
Each I_XXX preprocessor define translates into a single 32-bit instruction. So you can count instructions to learn which memory address are used and where the free mem space starts.
To generate branch instructions, special M_ preprocessor defines are used. M_LABEL define can be used to define a branch target.
Implementation note: these M_ preprocessor defines will be translated into two ulp_insn_t values: one is a token value which contains label number, and the other is the actual instruction.
*/
void ULP_BLINK_RUN(uint32_t us)
{
size_t load_addr = 0;
RTC_SLOW_MEM[12] = 0;
ulp_set_wakeup_period(0, us);
const ulp_insn_t ulp_blink[] =
{
I_MOVI(R3, 12), // #12 -> R3
I_LD(R0, R3, 0), // R0 = RTC_SLOW_MEM[R3(#12)]
M_BL(1, 1), // GOTO M_LABEL(1) IF R0 < 1
I_WR_REG(RTC_GPIO_OUT_REG, 26, 27, 1), // RTC_GPIO2 = 1
I_SUBI(R0, R0, 1), // R0 = R0 - 1, R0 = 1, R0 = 0
I_ST(R0, R3, 0), // RTC_SLOW_MEM[R3(#12)] = R0
M_BX(2), // GOTO M_LABEL(2)
M_LABEL(1), // M_LABEL(1)
I_WR_REG(RTC_GPIO_OUT_REG, 26, 27, 0),// RTC_GPIO2 = 0
I_ADDI(R0, R0, 1), // R0 = R0 + 1, R0 = 0, R0 = 1
I_ST(R0, R3, 0), // RTC_SLOW_MEM[R3(#12)] = R0
M_LABEL(2), // M_LABEL(2)
I_HALT() // HALT COPROCESSOR
};
const gpio_num_t led_gpios[] =
{
GPIO_NUM_2,
// GPIO_NUM_0,
// GPIO_NUM_4
};
for (size_t i = 0; i < sizeof(led_gpios) / sizeof(led_gpios[0]); ++i) {
rtc_gpio_init(led_gpios[i]);
rtc_gpio_set_direction(led_gpios[i], RTC_GPIO_MODE_OUTPUT_ONLY);
rtc_gpio_set_level(led_gpios[i], 0);
}
size_t size = sizeof(ulp_blink) / sizeof(ulp_insn_t);
ulp_process_macros_and_load( load_addr, ulp_blink, &size);
ulp_run( load_addr );
} // void ULP_BLINK_RUN(uint32_t us)
//////////////////////////////////////////////
Here the 2 tasks are running as fast as they can.
The actual analog reading code
/*
AudioAnalyzer.cpp - Library for audio spectrum analyzer.
Created by Lauren Pan,Dec 5, 2012.
E-Mail: Lauren.pan@dfrobot.com
Version 1.3
Log:
2012.12.05
Improve the code to make it compatible with the Arduino IDE 1.0 or later
2010.11.06
Add optional analog pin
*/
#include <driver/adc.h>
#include "AudioAnalyzer.h"
#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
/**************************** Init Analyzer connecter Pin ****************************/
Analyzer::Analyzer(void)
{
_StrobePin = 4;
_RSTPin = 5;
_DCPin = 0;
}
Analyzer::Analyzer(int StrobePin,int RstPin,int AnalogPin)
{
_StrobePin = StrobePin;
_RSTPin = RstPin;
_DCPin = AnalogPin;
// set up A:D channels
adc1_config_width(ADC_WIDTH_12Bit);
adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_11); // esp32 GPIO36
}
void Analyzer::Init()
{
pinMode(_StrobePin,OUTPUT);
pinMode(_RSTPin,OUTPUT);
RstModule();
}
/**************************** Reset analyzer module ****************************/
void Analyzer::RstModule()
{
digitalWrite(_StrobePin,LOW);
digitalWrite(_RSTPin,HIGH);
digitalWrite(_StrobePin,HIGH);
digitalWrite(_StrobePin,LOW);
digitalWrite(_RSTPin,LOW);
delayMicroseconds(72);
}
/**************************** Read DC out value ****************************/
void Analyzer::ReadFreq(int *value)
{
static boolean RstState = false;
if(!RstState)
{
_TimepointSt = millis();
RstState = true;
}
else
{
_TimepointNow = millis();
if(_TimepointNow - _TimepointSt > 3000)
{
RstModule();
RstState = false;
//Serial.println("Rst");
}
}
for(byte band = 0;band<Maxband;band++)
{
delayMicroseconds(10);
// value[band] = analogRead(_DCPin);
value[band] = adc1_get_raw(ADC1_CHANNEL_0);
delayMicroseconds(50);
digitalWrite(_StrobePin,HIGH);
delayMicroseconds(18);
digitalWrite(_StrobePin,LOW);
}
}
Thanks for all the input and teaching.
I would add another solution option to the above
5) Run Blink and AnalogRead at the save priority level (with nothing running at a higher level) so that pre-emptive time slicing works
Seems at least level 2 is needed.
Still unclear to me why running them at level 1 add 2mS
Can't see anything like that in the loop() task.
main.cpp is
bool loopTaskWDTEnabled;
void loopTask(void *pvParameters) {
setup();
for(;;) {
if(loopTaskWDTEnabled){
esp_task_wdt_reset();
}
loop();
}
}
extern "C" void app_main() {
loopTaskWDTEnabled = false;
initArduino();
xTaskCreateUniversal(loopTask, "loopTask", 8192, NULL, 1, &loopTaskHandle, CONFIG_ARDUINO_RUNNING_CORE);
}
You'd have to look at the FreeRTOS implementation code. Which, I believe, is not open-source on ESP32.
Can't see the link from the loop() task unless the name "loopTask" is hard coded some where deep
Your not going to see it. FreeRTOS is the OS that controls and schedules tasks. Why would you expect to see it inside user code?
Because I would expect to see a function call to something that does the work.
If I set the levels so that the loop() task is not called the code runs 2mS faster.
Without an explicit call to some cleanup code from loop(), the only way I can imagine is that the clean up happens is that the name loopTask triggers extra code each time the scheduler meets it.
Nope. FreeRTOS executes outside of anything you see. Task swapping happens outside anything you see. And, there's also an Idle task running that you don't see. Clean up happens in the Idle task.
When you run a program on Windoze or L!nux, the OS does background work for that program transparently and without it being instructed to by the program. The same is happening in the background on FreeRTOS without you being able to control it. ESP32 is not an AVR based MCU like the Arduino Uno where you can control everything.