How can i do Dual core & task use same memory (ESP32-RTOS)

Hello All;

I have two TASKs in a project with Esp32 and it runs separately on the CPU0 and CPU1. What solution should I apply to prevent them from using them at the same time?

Basically what I want to do is as follows.

for Variabla A : CPU-0 can Write / CPU-1 can READ
for Variabla B : CPU-1 can Write / CPU-0 can READ
for Variabla C : CPU-0 can Read&Write / CPU-0 can Read&Write

I added eample easy use:
int Cpu0_Read__Cpu1_Write=0;
int Cpu1_Read__Cpu0_Write=0;
int Cpu1_WriteRead__Cpu0_WriteRead=0;
int Cpu1_Read__Cpu0_Read=0;

How can I do this without the program breaking. I need max speed.
(Please sharing your simple code... )

#include <Arduino.h>
//#include <WiFiUdp.h>
//#include <WiFi.h> //ESP32 wifi
#include <string>
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_spi_flash.h"

#include "soc/timer_group_struct.h"
#include "soc/timer_group_reg.h"


TaskHandle_t LanCon;
TaskHandle_t Stepper;

void LanConcode( void * pvParameters );
void Steppercode( void * pvParameters );


int Cpu0_Read__Cpu1_Write=0;
int Cpu1_Read__Cpu0_Write=0;
int Cpu1_WriteRead__Cpu0_WriteRead=0;
int Cpu1_Read__Cpu0_Read=0;


void setup(){
Serial.begin(9600); 
while (!Serial) {
    delay(1); // wait for serial port to connect. Needed for native USB port only
  };


//---------------------------------------

 xTaskCreatePinnedToCore(
                    LanConcode,   // Task function. //
                    "LanCon",     // name of task. //
                    10000,       // Stack size of task //
                    NULL,        // parameter of the task //
                    1,           // priority of the task //
                    &LanCon,      // Task handle to keep track of created task //
                    0);          // pin task to core 0 //                  
  delay(500); 

  //create a task that will be executed in the Task2code() function, with priority 1 and executed on core 1
  
  xTaskCreatePinnedToCore(
                    Steppercode,   // Task function. 
                    "Stepper",     // name of task. 
                    10000,       // Stack size of task /
                    NULL,        // parameter of the task /
                    1,           // priority of the task /
                    &Stepper,      // Task handle to keep track of created task /
                    1);          // pin task to core 1 /
    delay(500); 


}


void loop(){
// empty loop
}


//--- CPU 0
void LanConcode( void * pvParameters ){
  Serial.print("##LanConcode## running on core ");
  Serial.println(xPortGetCoreID());
  delay(100);
  //**************************************
/*
int Cpu0_Read__Cpu1_Write=0;
int Cpu1_Read__Cpu0_Write=0;
int Cpu1_WriteRead__Cpu0_WriteRead=0;
int Cpu1_Read__Cpu0_Read=0;
*/
  for(;;){
        //---------------
		if (Cpu0_Read__Cpu1_Write==0){Serial.println("ok-1");};
		
		if (True){
			Cpu1_Read__Cpu0_Write=123456;
			Serial.println("ok-2");
			};
		
		if (Cpu1_WriteRead__Cpu0_WriteRead==0){
			Cpu1_WriteRead__Cpu0_WriteRead=1;
			Serial.println("ok-3a");
			}else{
			Cpu1_WriteRead__Cpu0_WriteRead=0;
			Serial.println("ok-3b");
			};

		if (Cpu1_Read__Cpu0_Read==0){Serial.println("ok-4");}
        //--------------
  };

}


//  --- > CPU 1
void Steppercode( void * pvParameters ){
  Serial.print("###Steppercode### running on core ");
  Serial.println(xPortGetCoreID());
  delay(1000);
//--*************************************

/*
int Cpu0_Read__Cpu1_Write=0;
int Cpu1_Read__Cpu0_Write=0;
int Cpu1_WriteRead__Cpu0_WriteRead=0;
int Cpu1_Read__Cpu0_Read=0;
*/
  for(;;){
        //---------------
		if (Cpu1_Read__Cpu0_Write==0){Serial.println("ok-1");};
		
		if (True){
			Cpu0_Read__Cpu1_Write=123456;
			Serial.println("ok-2");
			};
		
		if (Cpu1_WriteRead__Cpu0_WriteRead==0){
			Cpu1_WriteRead__Cpu0_WriteRead=1;
			Serial.println("ok-3a");
			}else{
			Cpu1_WriteRead__Cpu0_WriteRead=0;
			Serial.println("ok-3b");
			};

		if (Cpu1_Read__Cpu0_Read==0){Serial.println("ok-4");}
        //--------------
  };

}

a conventional method is the use of semaphores which block. interrupts to each processor can trigger a driver that services the semaphore.

there's no need for protection to read, although it might be helpful to have flag indicating that something is available to read.

need to consider the mechanisms used for inter-process vs inter-processor communication.

is it shared data or just trying to pass information between processors?

gcjr:
a conventional method is the use of semaphores which block. interrupts to each processor can trigger a driver that services the semaphore.

there's no need for protection to read, although it might be helpful to have flag indicating that something is available to read.

need to consider the mechanisms used for inter-process vs inter-processor communication.

is it shared data or just trying to pass information between processors?

Thank you for quick answer. that is shared data.
its like under below.
Please show us with updating my code for each types.

  for(;;){
        //---------------
		if (Cpu0_Read__Cpu1_Write==0){Serial.println("ok-1");};
		
		if (True){
			Cpu1_Read__Cpu0_Write=123456;
			Serial.println("ok-2");
			};
		
		if (Cpu1_WriteRead__Cpu0_WriteRead==0){
			Cpu1_WriteRead__Cpu0_WriteRead=1;
			Serial.println("ok-3a");
			}else{
			Cpu1_WriteRead__Cpu0_WriteRead=0;
			Serial.println("ok-3b");
			};

		if (Cpu1_Read__Cpu0_Read==0){Serial.println("ok-4");}
        //--------------
  };

As a note, i do not write code for you.

Both of your tasks are running at full steam ahead with no controls.

You can use an event group.

You can use a semaphore.

You can use a Queue.

for Variable A : CPU-0 can Write / CPU-1 can READ

define a queue, copy, to pass variable A to cpu 1

for Variable B : CPU-1 can Write / CPU-0 can READ

define a queue. copy, to pass the variable B to 0

for Variable C : CPU-0 can Read&Write / CPU-0 can Read&Write

use a semaphore

I added eample easy use:
int Cpu0_Read__Cpu1_Write=0;
int Cpu1_Read__Cpu0_Write=0;
int Cpu1_WriteRead__Cpu0_WriteRead=0;
int Cpu1_Read__Cpu0_Read=0;

To create a Queue

In global declare the queue handle variable

QueueHandle_t xQ_eData;

in setup()

xQ_eData    = xQueueCreate( 1, sizeof(int) ); // sends a queue copy of

You could start to learn how to use a multitasking operating system such as FreeRTOS :wink:
Your sketch looks familiar, did you follow this tutorial: ESP32 Dual Core with Arduino IDE | Random Nerd Tutorials.

Why do you use both cores ? It is so nicely divided: Wifi things on core 0 and Arduino things on core 1.
When using all your own tasks on core 1, then it should be possible to use a 'volatile' variable. As far as I know, FreeRTOS does not run in preemptive mode.

With two tasks running on two different hardware cores, then I think that you are still okay if reading and writing is done in one operation, with a 32-bit variable such as a 'int'.

If you have a block of data, then you should use the FreeRTOS functions with queues (to pass on data) or semaphores (to share the same hardware).

This could be interesting: #151 - ESP32 Passing Values 💾 Between Tasks - Deep Dive (2 Easy Ways) - YouTube. I have not seen it fully yet, but he talks about using core 1, queues and semaphores.

what specific problems are you having? looks like you're polling

The task waits for receipt of queue

void fSendMQTTpressure( void *pvParameters )
{
  struct stu_pressureMessage px_message;
  for (;; )
  {
    if ( xQueueReceive(xQ_pMessage, &px_message, portMAX_DELAY) == pdTRUE )
    {
        xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY );
        if ( MQTTclient.connected() )
        {
                MQTTclient.publish( topicTimeStamp_ap, String(px_message.TimeStamp).c_str() );
                vTaskDelay( 2 );
                MQTTclient.publish( topicDataPoint_ap, String(px_message.DataPoint).c_str() );
                vTaskDelay( 2 );
                MQTTclient.publish( topic_nTimeStamp, String(px_message.nTimeStamp).c_str() );
                vTaskDelay( 2 );
                MQTTclient.publish( topic_nDataPoint, String(px_message.nDataPoint).c_str() );
        }
        xSemaphoreGive( sema_MQTT_KeepAlive );
    }
  }
  vTaskDelete( NULL );
} //void fSendMQTTpresure( void *pvParameters )

this task sends queue data

void fDoTrends( void *pvParameters )
{
  const int magicNumber = 24 * 4;
  double    values[2];
  int       lrCount = 0;
  float     lrData = 0.0f;
  float     DataPoints[magicNumber] = {0.0f};
  float     TimeStamps[magicNumber] = {0.0f};
  float     dpHigh = 702.0f;
  float     dpLow  = 683.0f;
  float     dpAtom = 0.0f;
  float     dpMean = 0.0f; //data point mean
  float     tsAtom = 0.0f;
  float     tsUnit = 0.0f;
  float     tsMean = 0.0f;
  bool      dpRecalculate = true;
  bool      FirstTimeMQTT = true;
  for (;;)
  {
    if ( xQueueReceive(xQ_lrData, &lrData, portMAX_DELAY) == pdTRUE )
    {
      //find dpHigh and dpLow, collects historical high and low data points, used for data normalization
      if ( lrData > dpHigh )
      {
        dpHigh = lrData;
        dpRecalculate = true;
      }
      if ( lrData < dpLow )
      {
        dpLow = lrData;
        dpRecalculate = true;
      }
      if ( lrCount != magicNumber )
      {
        DataPoints[lrCount] = lrData;
        TimeStamps[lrCount] = (float)xTaskGetTickCount() / 1000.0f;
        log_i( "lrCount %d TimeStamp %f lrData %f", lrCount, TimeStamps[lrCount], DataPoints[lrCount] );
        lrCount++;
      } else {
        //shift datapoints collected one place to the left
        for ( int i = 0; i < magicNumber; i++ )
        {
          DataPoints[i] = DataPoints[i + 1];
          TimeStamps[i] = TimeStamps[i + 1];
        }
        //insert new data points and time stamp (ts) at the end of the data arrays
        DataPoints[magicNumber - 1] = lrData;
        TimeStamps[magicNumber - 1] = (float)xTaskGetTickCount() / 1000.0f;
        lr.Reset(); //reset the LinearRegression Parameters
        // use dpHigh and dpLow to calculate data mean atom for normalization
        if ( dpRecalculate )
        {
          dpAtom = 1.0f / (dpHigh - dpLow); // a new high or low data point has been found adjust mean dpAtom
          dpRecalculate = false;
        }
        //timestamp mean is ts * (1 / ts_Firstcell - ts_Lastcell[magicNumber]). ts=time stamp
        tsAtom = 1.0f / (TimeStamps[magicNumber - 1] - TimeStamps[0]); // no need to do this part of the calculation every for loop ++
        for (int i = 0; i < magicNumber; i++)
        {
          dpMean = (DataPoints[i] - dpLow) * dpAtom;
          tsMean = TimeStamps[i] * tsAtom;
          lr.Data( tsMean, dpMean ); // train lr
          //send to mqtt the first time
          if ( FirstTimeMQTT )
          {
            x_pMessage.TimeStamp  = TimeStamps[i];
            x_pMessage.DataPoint  = DataPoints[i];
            x_pMessage.nTimeStamp = tsMean;
            x_pMessage.nDataPoint = dpMean;
            xQueueSend( xQ_pMessage, (void *) &x_pMessage, portMAX_DELAY ); // wait for queue space to become available
          }
        }
        if ( !FirstTimeMQTT )
        {
          x_pMessage.TimeStamp  = TimeStamps[magicNumber-1];
          x_pMessage.DataPoint  = DataPoints[magicNumber-1];
          x_pMessage.nTimeStamp = tsMean; //the last calculation is on the most recent data
          x_pMessage.nDataPoint = dpMean; //send the most recent data to the MQTT Broker
          xQueueSend( xQ_pMessage, (void *) &x_pMessage, portMAX_DELAY );
        }
        FirstTimeMQTT = false;
        lr.Parameters( values );
        //calculate
        tsUnit = TimeStamps[magicNumber - 1] - TimeStamps[magicNumber - 2]; //get the time stamp quantity
        tsUnit += TimeStamps[magicNumber - 1]; //get a future time
        tsUnit *= tsAtom; //setting time units to the same scale
        log_i( "Calculate(x) = %f", lr. Calculate( tsUnit ) ); //calculate next time unit?
        log_i( "Correlation: %f Values: Y=%f and *X + %f ", lr.Correlation(), values[0], values[1] ); // correlation is the strength and direction of the relationship
        ////


        

      }
      log_i( "DoTheBME280Thing high watermark % d",  uxTaskGetStackHighWaterMark( NULL ) );
    } //if ( xQueueReceive(xQ_lrData, &lrData, portMAX_DELAY) == pdTRUE )
  } //for(;;)
  vTaskDelete ( NULL );
} //void fDoTrends( void *pvParameters )

here I declare a semaphore

SemaphoreHandle_t sema_mqttOK;

in setup() the semaphore is initialized

sema_mqttOK    =  xSemaphoreCreateBinary();
  xSemaphoreGive( sema_mqttOK );

here the semaphore is being used to protect a variable

void fparseMQTT( void *pvParameters )
{
  struct stu_message px_message;
  for (;;)
  {
    if ( xQueueReceive(xQ_Message, &px_message, portMAX_DELAY) == pdTRUE )
    {
      xSemaphoreTake( sema_mqttOK, portMAX_DELAY );
      mqttOK = 0;
      xSemaphoreGive( sema_mqttOK );
      if ( px_message.topic == topicRemainingMoisture_0 )
      {
        x_eData.RM0  = String(px_message.payload).toFloat();
      }
    } //if ( xQueueReceive(xQ_Message, &px_message, portMAX_DELAY) == pdTRUE )
  } //for(;;)
  vTaskDelete( NULL );
} // void fparseMQTT( void *pvParameters )
////

it's tasks on different processors

gcjr:
it's tasks on different processors

Why is that a problem?

posted below is a program with tasks on different processors that passes data with a queue.

Here is a program that uses both cores and the 2nd processor and queues to pass info

xTaskCreatePinnedToCore( fDo_AudioReadFreq, "fDo_ AudioReadFreq", TaskStack40K, NULL, Priority4, NULL, TaskCore1 ); //assigned to core1

xTaskCreatePinnedToCore( fDo_LEDs, "fDo_ LEDs", TaskStack40K, NULL, Priority4, NULL, TaskCore0 ); //assigned to core0

And

ULP_BLINK_RUN(100000); runs a task on the ULP, the other processor on the ESP32.

#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)
//////////////////////////////////////////////

If WiFi is NOT used then using core1 and cor0 are thing do's.

ESP32 API reference

ESP32 freeRTOS reference

BTW, Queues are the preferred method for inter-core communications.

Idahowalker:
Why is that a problem?

is it obvious that there are potentially cooperating semaphores on both processors that are linked by a interrupt between processors?

gcjr:
is it obvious that there are potentially cooperating semaphores on both processors that are linked by a interrupt between processors?

As already noted, queues can be used to safely exchange data between cores (both task and ISR contexts). Notifications offer a lighter-weight method when just notice that an event has occurred (or a simple 32-value) needs to be communicated. Notifications can't be received by ISRs.

If you absolutely must access the same data from multiple processes on multiple cores, then there's std::atomic, critical sections using MUTEXes, and other techniques. Critical sections are relatively heavy-weight, but they provide completely atomic protection while accessing an arbitrary number of resources in both task and ISR contexts.

my understanding is that a semaphore is the underlying mechanism used to block processing depending on either HW or SW events. so a queue would use a semaphore to block until something becomes available in the queue.

gcjr:
my understanding is that a semaphore is the underlying mechanism used to block processing depending on either HW or SW events. so a queue would use a semaphore to block until something becomes available in the queue.

What's your point? Usage is described in the FreeRTOS documentation without having to deal with implementation details.

i'm curious