Dual Core State Machines (ESP32) - Can one affect the other?

Hi internet friends. I have a question regarding running separate state machines on their own cores (in an ESP32 microcontroller) simultaneously:

Can I change the case of the state machine in core 2 from core 1?

void setup() {


  xTaskCreatePinnedToCore(
    core1assignments,
    "Core_1",
    10000,
    NULL,
    2,
    &C1,
    0);
  delay(100);
  xTaskCreatePinnedToCore(
    core2assignments,
    "Core_2",
    10000,
    NULL,
    1,
    &C2,
    1);
  delay(100);


enum switch_states {ON, OFF, IDLE, ALL_OFF}
switch_states core_1_state = ON;

enum switch_states_2 {ON, OFF, IDLE}
switch_states_2 core_2_state = ON;
}

void loop() {
      //empty
}


// Core 1

void core1assignments( void * pvParameters ) {
for (;;) {
    core_1_state_machine();
    vTaskDelay(10);
  };
}
void core2assignments( void * pvParameters ) {
for (;;) {
    core_2_state_machine();
    vTaskDelay(10);
  };
}

void core_1_state_machine() {

switch(core_1_state) {
case ON:
              //stuff
break;
case OFF:
              //stuff
break;
case IDLE:
              //stuff
break;

case ALL_OFF:
                       core_1_state = OFF;
                       core_2_state = OFF; 
/* this is the relevant part  ^^^  core 1 code changing core 2 state */
break;

}
}

--------------------------------------------------



// Core 2

void core_2_state_machine() {

switch(core_2_state) {
case ON:
              //stuff
break;
case OFF:
              //stuff
break;
case IDLE:
              //stuff
break;
}
}

You want to turn off a ESP32 core?

You can use code to put the ESP32 in 2 different power modes.

You can use the ULP core to turn off core 0 and core 1.

Putting the ESP core0 and core1 to sleep is a broad subject with different paths towards that goal, I'd give particular subject references but those are many in the ESP32 API Reference. Here is the link to the API reference [urlhttps://docs.espressif.com/projects/esp-idf/en/latest/api-reference/index.html[/url] finding the information is up to you.

You can use the Arduino IDE to program the ULP.

Idahowalker:
You want to turn off a ESP32 core?

No that was just an example, I know how to work with the cores, I'm just asking about the running of simultaneous state machines. You can ignore the example.

You can run separate tasks on each core with each task running at the same time.
A simple example of running two tasks with each task on its own core, whiles the tasks are linked through event triggers, that link can be eliminated. If the event link was removed, which I am thinking of doing, the queue will be the thing to pass info from one task to another.

#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 = 10; // 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
////
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 (64 bit) 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
   {
     leds.setPixelColor( position, leds.Color( value , 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)
//////////////////////////////////////////////[/core]

You want to use the ESP32 OS called freeRTOS to put tasks on differing cores. 

How to work with the cores, the information is in that link I sent you. 

Oh, this example is also using the third ESP32 core known as the ULP. The ULP core is programmed in macro code. 

Oi! you got 3 programs with each program running on their own cores.

:expressionless:

OK so there seemed to be a misunderstanding regarding what I was trying to ask, so I went and made a quick test with a few LEDs to see if I can switch the state of the state machine on one core from another. The answer is YES. I'll post the code below if anyone wants to see what I'm talking about better or has the same issue:

TaskHandle_t C1;
TaskHandle_t C2;
int red = 19;
int yel = 5;
int grn = 12;
enum Switch_states {ON, OFF, IDLE1, ALL_OFF};
Switch_states core_1_state = ON;

enum Switch_states_2 {ON2, OFF2, IDLE2};
Switch_states_2 core_2_state = ON2;

void setup() {
  pinMode(red, OUTPUT);
  pinMode(yel, OUTPUT);
  pinMode(grn, OUTPUT);
  digitalWrite(red, LOW);
  digitalWrite(yel, LOW);
  digitalWrite(grn, LOW);

  xTaskCreatePinnedToCore(
    core1assignments,
    "Core_1",
    10000,
    NULL,
    2,
    &C1,
    0);
  delay(100);
  xTaskCreatePinnedToCore(
    core2assignments,
    "Core_2",
    10000,
    NULL,
    1,
    &C2,
    1);
  delay(100);
}


void loop() {
      //empty
}



void core1assignments( void * pvParameters ) {
for (;;) {
    core_1_state_machine();
    vTaskDelay(10);
  };
}
void core2assignments( void * pvParameters ) {
for (;;) {
    core_2_state_machine();
    vTaskDelay(10);
  };
}



// Core 1


void core_1_state_machine() {

switch(core_1_state) {
case ON:
              digitalWrite(red, HIGH);
              digitalWrite(yel, HIGH);
              delay(2000);
              core_2_state = IDLE2;
break;
case OFF:
              //stuff
break;
case IDLE1:
              //stuff
break;

case ALL_OFF:
               //stuff
break;

}
}


// Core 2

void core_2_state_machine() {

switch(core_2_state) {
case ON2:
              delay(500);
break;
case OFF2:
              //stuff
break;
case IDLE2:
              digitalWrite(grn, HIGH);
              delay(500);
              digitalWrite(grn, LOW);
              delay(500);
break;
}
}

The end result is that you have the red LED and yellow LED on constantly, whilst the green LED blinks - red and yellow are running from core 1, whilst green is running in core 2. Core 1 tells Core 2 to change state to blinking mode (IDLE2).