Shared variables in 2 cores arduino GIGA mutex use

Hi, I am trying to use a shared variable accessible between 2 cores (M7 and M4) of arduino GIGA but I don't know what I am doing wrong:

#include <SPI.h>
#include <RPC.h>

const int sensor_1_NC = 70;                   // Output pin of Sensor 1 (normally closed).
int n = 0;
bool S1_detecting = false;
bool DETECT_S1 = false;

String currentCPU() {
  if (HAL_GetCurrentCPUID() == CM7_CPUID) {
    return "M7";
  } else {
    return "M4";
  }
}

void resetFunc2(void) {
  unsigned long *registerAddr;
  registerAddr = (unsigned long *)0xE000ED0C; //Writes to the AIRCR register of the stm32h747 to software restet the arduino
  //It is a 32 bit register set bit 2 to request a reset and write 0x05FA on the first bytes to perform
  // the write
  //See Arm® v7-M Architecture Reference Manual for more information
  //Serial.println(*registerAddr,HEX); //For debug
  //*registerAddr = *registerAddr | (unsigned long) 0x05FA0304;
  *registerAddr = (unsigned long) 0x05FA0304;
  //Serial.println(*registerAddr,HEX); //For debug
}

void setup() {
  // put your setup code here, to run once:

  Serial.begin(9600);
  delay(3000);
  pinMode(sensor_1_NC, INPUT_PULLUP); 

  RPC.begin(); // boots core M4.

}

void loop() {
  // put your main code here, to run repeatedly:

    if(currentCPU() == "M7"){ // Code for M7 core.

      delay(3000);
      Serial.println("Detected " + String(n) + " times.");

      if(n > 10){ // If it counts 10 detections, reset the board.
        resetFunc2();
      }

    } // End of M7 code.

    if(currentCPU() == "M4"){ // Code for M4 core.

      if((digitalRead(sensor_1_NC) == 0) && S1_detecting == false){ // Sensor gives 0 cuando detects y 1 in other case.
        DETECT_S1 = true;
        S1_detecting = true;
        n = n + 1; // Add one more detection to the variable used in Main core.
      }else{DETECT_S1 = false;}

      if(S1_detecting == true && (digitalRead(sensor_1_NC) == 1)){ // If it has stopped detecting, we can prepare for another detection.
        S1_detecting = false;
      }

    } // End of M4 code.

}

I have read I sould use mutex. Is there a mutex library for arduino? Is this applicable to my problem of shared variables?

thanks.

1 Like

By the way, When I puload this code to both cores and send signals to the sensor I get the following:

Detected 0 times.
Detected 0 times.
Detected 0 times.
Detected 0 times.
Detected 0 times.
Detected 0 times.
Detected 0 times.
Detected 0 times.
Detected 0 times.
Detected 0 times.

Here is an example I was playing with while testing how threads work on the GIGA. Pulled off the mbed site (ConditionVariable - API references and tutorials | Mbed OS 6 Documentation)

/*
 * Copyright (c) 2020 Arm Limited and affiliates.
 * SPDX-License-Identifier: Apache-2.0
 */
/*
 * Copyright (c) 2020 Arm Limited and affiliates.
 * SPDX-License-Identifier: Apache-2.0
 */
#include "mbed.h"
#include "LibPrintf.h"
#include "rtos.h"

using namespace rtos;

#include "mbed.h"
#include "LibPrintf.h"
#include "rtos.h"
#include "DigitalOut.h"

using namespace rtos;


Mutex mutex;
ConditionVariable cond(mutex);

// These variables are protected by locking mutex
uint32_t counter = 0;
bool done = false;

void worker_thread()
{
    mutex.lock();
    do {
        printf("Worker: Count %lu\r\n", counter);

        // Wait for a condition to change
        cond.wait();

    } while (!done);
    printf("Worker: Exiting\r\n");
    mutex.unlock();
}

int main()
{
    Thread thread;
    thread.start(worker_thread);

    for (int i = 0; i < 5; i++) {

        mutex.lock();
        // Change count and signal this
        counter++;
        printf("Main: Set count to %lu\r\n", counter);
        cond.notify_all();
        mutex.unlock();

        ThisThread::sleep_for(1000);
    }

    mutex.lock();
    // Change done and signal this
    done = true;
    printf("Main: Set done\r\n");
    cond.notify_all();
    mutex.unlock();

    thread.join();
}

void loop() {
  // put your main code here, to run repeatedly:

}

The key was to include:

#include "mbed.h"
#include "rtos.h"

using namespace rtos;

at the top of the sketch

1 Like

Based on the RPC library we have a tutorial available at https://docs.arduino.cc/tutorials/giga-r1-wifi/giga-dual-core

How could I share a string variable between 2 cores in arduino GIGA using RPC protocol?
I have tried sharing a string with the following code:

#include <RPC.h>

volatile char arraychar[12]; // common variable between cores.
String message = "";
String emptystr = "";



String currentCPU() {
  if (HAL_GetCurrentCPUID() == CM7_CPUID) {
    return "M7";
  } else {
    return "M4";
  }
}

void setup() {

  if(currentCPU() == "M7") {
    Serial.begin(9600);
    while(!Serial);
    RPC.begin();
    message = "helloworld!"; // 11 characters.
    RPC.println(message); // Prints the message in the RPC Stream for M4 to receive it.
    delay(4000);
  }

  if(currentCPU() == "M4") {
    RPC.begin();
    String bufferM4 = "";
    while (RPC.available()) { // Read the setream to get the data sent by M7.
      bufferM4 += (char)RPC.read();
    }
    strcpy(arraychar, bufferM4);
    delay(4000);
  }

}

void loop() {

  if(currentCPU() == "M7") { // In loop, M7 prints everything sent by M4 in loop every second.
    String buffer = "";

    while (RPC.available()) {
      buffer += (char)RPC.read();
    } // When it has read the stream, M7 deletes the variable.
     
    emptystr.toCharArray(arraychar, 23); // Clears the array of char.

    if (buffer.length() > 0) {
      Serial.print(buffer);
    }
    delay(1000);
  }

  if(currentCPU() == "M4") {
    delay(250);

    if(String(arraychar) == ""){ // If the array of char has been emptied, it can now send new data.
      RPC.println(String(arraychar)); // Prints the array char received by M7 in setup.
      arraychar = "anothermssg"; // I will be changing this message in every iteration of loop (this will be sensor reading info).
    }

  }

}

But I get the following error:

Compilation error: invalid conversion from 'volatile char*' to 'char*' [-fpermissive]

How can I overwrite a shared variable from M4 to M7 and vice versa? I know I should use mutex but I can't seem to make "Mutex.h" library work in my IDE.

Before I start having fun trying to work with your sketch thought I would add a few things that I am finding.

Shared memory really should be reserved to using SDRAM reserved memory otherwise you can get into trouble. Take a look at this thread: String sharing between M7 cores and M4 cores - Hardware / GIGA R1 - Arduino Forum. That gives you the general idea but not the only way.

I have been doing a bit of googling and these are a few things I found:
Best/easiest way to share data between cores in ST... - STMicroelectronics Community

Portenta H7 dual core shared memory - Portenta / Portenta H7 - Arduino Forum

The fastest (and proper way) way to share data between cores (SRAM3/4 usage on the stm32h7)? : r/embedded (reddit.com)

that last post has a link to a lib that can also be used. Now to go play :slight_smile:

1 Like

Based on what I am seeing in this thread:

Passing variable from CM7 to CM4 using RPC - Portenta / Portenta H7 - Arduino Forum

you are probably better off using the method shown in String sharing between M7 cores and M4 cores - Hardware / GIGA R1 - Arduino Forum if you have a lot of sensor data it will be faster.

1 Like

Here is a simple way to do it if you are only transferring small amounts of data:

#ifdef CORE_CM4   // Start M7 programming
 
#include "RPC.h"  // comes with the mbed board installation
#include "elapsedMillis.h"

elapsedMillis sendData;

void setup() {
   RPC.begin(); 

}

void loop() {
  
   if(sendData > 100) {
      int newData = 1;
      int myRand1 = rand() % 100;  // from 0 to 99
      int myRand2 = rand() % 100;  // from 0 to 99
      RPC.call("setVar", (int) newData, (int)myRand1, (int)myRand2);    //.as<int>();
      RPC.println("From M4 core setting M7 variable to: " + String(myRand1) +", "+ String(myRand2));
      RPC.println("Hello from M4 using regular serial piped through RPC");
      sendData = 0;
    }


}

#endif              // End all M4 core programming

/////////////////////////////////////////////////////////////////////////////////////////////

#ifdef CORE_CM7    // Start M7 programming

#include "RPC.h"  // comes with the mbed board installation

// Set an M6 core global variable
int myIntGlobal1 = 1234;
int myIntGlobal2 = 1234;
int newData = 0;

void setVar(int c, int a, int b) {
  newData = (int)c;
  myIntGlobal1 = (int)a;
  myIntGlobal2 = (int)b;
 // return String(a) +", "+ String(b);
}


void setup() {
   RPC.begin();

   Serial.begin(115200);
   while(!Serial && millis() < 5000)
   Serial.println("Begin 2 Core test");

   RPC.bind("setVar", setVar); // do these have to be the same?

}

void loop() {
   
  String buffer = "";
  while (RPC.available()) {
    buffer += (char)RPC.read();  // Fill the buffer with characters
  }
  if (buffer.length() > 0) {
    Serial.print(buffer);
  }

   
    if(newData == 1) {
      Serial.println("---------------------------------");
      Serial.println("From M4 showing global variable: "+ String(myIntGlobal1)+ ", "+ String(myIntGlobal2));
      Serial.println("Hello from M7 using regular serial piped through RPC");
      newData = 0;
    } 

}

#endif            // End all M4 core programming

seems like RPC.call uses msgpack and unpack but not sure what data can be sent - only know it doesn't like string data.

1 Like

Thanks a lot for your help. I am studying the information you have given me to see if I can solve my problem.

Some extra information from my case: I need to send a string from M4 to M7 containing 300 characters every 3 seconds and I need to read this string and copy it to a string variable located in M7. Then I need to erase the message so M4 can send another message.

You might check this post out then

Trouble using RPC to pass Arrays or Vectors from M4 to M7 - Portenta / Portenta H7 - Arduino Forum

1 Like

Think this is probably the easiest way to do it based on the previous posts:

#include <RPC.h>
#include "DigitalOut.h"

#include "SDRAM.h"
struct DATA_FRAME_RETURN {
  char debugStringData[300];
} __attribute__((aligned(8)));

struct RETURN_MSG_READY_FLAG {
  volatile bool msg_ready = false;
} __attribute__((aligned(8)));

//SDRAM Pointers
const uint32_t SDRAM_START_ADDRESS_4 = ((uint32_t)0x38000000);  //USING THE AHB SRAM4 DOMAIN SPACE
volatile uint32_t *sdramMemory = (uint32_t*)0x38000000;

RETURN_MSG_READY_FLAG* return_msg_ready_sdram = (RETURN_MSG_READY_FLAG*)(sdramMemory);
DATA_FRAME_RETURN* data_frame_return_sdram = (DATA_FRAME_RETURN*)(sdramMemory + (sizeof(RETURN_MSG_READY_FLAG)) / sizeof(uint32_t));



#ifdef CORE_CM4    // Start M4 Core programming
#include "elapsedMillis.h"
elapsedMillis sendTime;

String data_values = "This is a test of the emergency broadcasting system.  The litte brown fox jumped over the moon";

void setup() {
    RPC.begin();
    delay(1000);

  pinMode(LED_RED, OUTPUT);
  for (uint8_t i = 0; i < 5; i++ ) {
    digitalWrite(LED_RED, !digitalRead(LED_RED)); // flip blue LED on and off
    delay(250);
  }

  sendMsgToM7("M4 Started.....", true);
  delay(100);

}

void loop() {

  if(sendTime > 3000) {
    sendMsgToM7(data_values, true);
    sendTime = 0;
  }
  
  receiveMsgFromM7();
}

void sendMsgToM7(String debugStringData, bool blocking) {
  do {
    if (!return_msg_ready_sdram->msg_ready) {  //if 0 can send data
      DATA_FRAME_RETURN dataForM7;
      debugStringData.toCharArray(dataForM7.debugStringData, sizeof(dataForM7.debugStringData));
      *data_frame_return_sdram = dataForM7;
      return_msg_ready_sdram->msg_ready = true;  //1 lets m4 know it can read data
      return;                        //break out of do->while loop
    } else if (blocking) {
      delayMicroseconds(5);  //hang out and try again, in a little bit
    }
  } while (blocking);
}


void receiveMsgFromM7() {
  if (return_msg_ready_sdram->msg_ready) {  //true indicates it can read by M7
    DATA_FRAME_RETURN dataFromM7 = *data_frame_return_sdram;
    //telemetry.printTelemetry(String(dataFromM4.debugStringData), TELEMETRY_LOW_PRIORITY);
    //USERIAL->println(String(dataFromM4.debugStringData));
    //for testing
    return_msg_ready_sdram->msg_ready = false;  //false indicates data has been read by M4
    sendMsgToM7(String(dataFromM7.debugStringData), true);
  }
}

#endif //end CORE_CM4

#ifdef CORE_CM7    // Start M7 Core programming

bool hasReceivedMsgFromM4 = false;

void setup() {

  while (!Serial && millis() < 5000) {}
  Serial.begin(115200);
  RPC.begin();

  delay(100);

  SDRAM.begin(SDRAM_START_ADDRESS_4);

  delay(100);

  pinMode(LED_BLUE, OUTPUT);
  for (uint8_t i = 0; i < 5; i++ ) {
    digitalWrite(LED_BLUE, !digitalRead(LED_BLUE)); // flip blue LED on and off
    delay(250);
  }
}

void loop() {

  receiveMsgFromM4();

}

void sendMsgToM4(String debugStringData, bool blocking) {
  do {
    if (!return_msg_ready_sdram->msg_ready) {  //if 0 can send data
      DATA_FRAME_RETURN dataForM4;
      debugStringData.toCharArray(dataForM4.debugStringData, sizeof(dataForM4.debugStringData));
      *data_frame_return_sdram = dataForM4;
      return_msg_ready_sdram->msg_ready = true;  //1 lets m7 know it can read data
      return;                        //break out of do->while loop
    } else if (blocking) {
      delayMicroseconds(5);  //hang out and try again, in a little bit
    }
  } while (blocking);
}

void receiveMsgFromM4() {
  if (return_msg_ready_sdram->msg_ready) {  //true indicates it can read by M7
    DATA_FRAME_RETURN dataFromM4 = *data_frame_return_sdram;
    //telemetry.printTelemetry(String(dataFromM4.debugStringData), TELEMETRY_LOW_PRIORITY);
    return_msg_ready_sdram->msg_ready = false;  //false indicates data has been read by M7
    Serial.println(String(dataFromM4.debugStringData));
    // put your copy to local string here.........

  }
}

#endif  //end CORE_CM7

if you just want to pass a large string.

I think I may have found a simpler way to comunicate a single string from one core to another:

#ifdef CORE_CM4    // Start M4 programming

#include "RPC.h"  

String timefromserver="";

void setup() {
  RPC.begin();

  while(timefromserver == ""){ // Continues in loop until it receives the time from server from M7.
  String buffer = "";
    while (RPC.available()) { 
      buffer += (char)RPC.read();  
    }
    if (buffer.length() > 0) { // Prints what it has received + M4 to make Serial print in M7 that M4 has received the message.
      RPC.print(buffer + " from M4.");
    }

    timefromserver = String(buffer);

  }
  // If it has reached here, it has received the message from M7.

}

void loop(){
  RPC.println();
}

#endif

#ifdef CORE_CM7    // Start M7 programming

#include "RPC.h"  


String timefromserver="";
void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  while(!Serial);
  RPC.begin();
  while(!RPC);

  // Gets time from server.
  timefromserver = "20231122 12:49:16.316";

  // Prints to RPC stream so M4 can receive it.
  RPC.println(timefromserver); 
  delay(1500); // Waits for M4 to receive, read and extract it.

  // Reads RPC stream to confirm that M4 has receive the string 'timefromserver'.
  String buffer = "";
    while (RPC.available()) { 
      buffer += (char)RPC.read();  
    }
    if (buffer.length() > 0) {
      Serial.println(buffer);
    }

}

void loop() {
  // put your main code here, to run repeatedly:
  Serial.println("Has reached loop");
  delay(2000);

}
#endif

but I keep getting this in serial port:

09:45:29.893 -> Has reached loop
09:45:31.945 -> Has reached loop
09:45:34.032 -> Has reached loop
09:45:36.116 -> Has reached loop
09:45:38.199 -> Has reached loop
09:45:40.257 -> Has reached loop
09:45:42.332 -> Has reached loop

Any idea why y can't read RPC stream from M4 and store the string in an M4 global variable?

Why does it only print the last part inside loop() function?

You might try adding this to the loop:

  // Reads RPC stream to confirm that M4 has receive the string 'timefromserver'.
  String buffer = "";
    while (RPC.available()) { 
      buffer += (char)RPC.read();  
    }
    if (buffer.length() > 0) {
      Serial.println(buffer);
    }

You have it in your setup but not the loop. Hope it works

I only want to share this variable once, at the beggining of M4 code. I tried to create a sort of loop in the setup in the code above, but it doesn't work and I can't see anything on the serial monitor.

After a few days of frustration, this is the code I use that works perfectly for my needs. You could redefine the struct that is used to suit whatever you want to transmit from one core to another. In my case I just want to read info on the M7 that is captured by the M4 from a CAN stream.

GigaDisplay_Data_Definition.h

#ifndef GIGADISPLAY_DATA_DEFINITION_H
#define GIGADISPLAY_DATA_DEFINITION_H

struct CanDataFrame {
    int rpm = 0;
    int speed = 0;
    int iat = 0;
    int ect = 0;
    int egt = 0;
    int cont = 0;
} __attribute__((aligned(8)));



#endif //GIGADISPLAY_DATA_DEFINITION_H

M7 Code

// Lets make sure this code is loaded only into to core M7
#ifndef CORE_CM7
#error Expected to run on core M7
#endif

#include <RPC.h>
#include <GigaDisplay_Data_Definition.h>

// Here we will store the pointer to the shared memory
CanDataFrame* sharedCanDataPtr = nullptr;

// Flag set true when the address ir recieved.
bool addressConfigured = false;

// Procedure to be called as RPC
// Recieves an address that points to the value used by the M4 core
void setDataPointer(uint32_t dataAddress) {
    // Newline
    Serial.println("");

    // DEBUG: lets print the memory address
    Serial.println("Recieved memory address: " + String(dataAddress, HEX));

    // Lets store the value on the global pointer we created.
    sharedCanDataPtr = (CanDataFrame*) dataAddress;

    // Lets print the value that is stored on the pointer address
    //Serial.println("Value stored in address: " + String((int) *rpm));

    // Lets set this flag to true so the loop can continue monitoring the recieved address
    addressConfigured = true;

    return;
  }

void setup() {
    int startTime = millis();
    Serial.begin(115200);
    while (!Serial or startTime + 4000 < millis()) ;

    // Set the RPC call
    RPC.bind("setDataPointer", setDataPointer); 

    // Lets begin core M4
    RPC.begin();
  
    Serial.print("Waiting for Address");
}

void loop() {
  // If we already recieved an address from core M4
  if(addressConfigured) {
    // Lets clean the cache on this address for the M7 core
    CanDataFrame newData = *sharedCanDataPtr;
    SCB_CleanInvalidateDCache_by_Addr(sharedCanDataPtr, sizeof(newData));
    Serial.println("Rpm: "    + String(newData.rpm));
    Serial.println("Speed: "  + String(newData.speed));
    Serial.println("Iat: "    + String(newData.iat));
    Serial.println("Ect: "    + String(newData.ect));
    Serial.println("Egt: "    + String(newData.egt));
    Serial.println("Cont: "    + String(newData.cont));
    Serial.println("");
  } else {
    Serial.print(".");
  }
  delay(10);
}

M4 Code

// Lets make sure this code is loaded only into to core M4
#ifndef CORE_CM4
#error Expected to run on core M4
#endif

#include <RPC.h>
#include <GigaDisplay_Data_Definition.h>

// This is the struct that will be shared with the other core.
CanDataFrame sharedCanData;

// Used to blink the led and make sure that the core started and is running.
bool ledStatus = false;

void setup() {
  pinMode(LEDG, OUTPUT);

  // Lets start RPC (is it needed for RPC.Call)
  RPC.begin();

  delay(100); //needed or core hangs
  RPC.call("setDataPointer", (int32_t)&sharedCanData); // we call the procedure on core M7 and pass the address of my loopCount variable.
  //delay(100); //needed or core hangs

}

void loop() {
  static int loopNum = 1;

  // Lets blink turn on or off the led
  if(ledStatus) {
    digitalWrite(LEDG, LOW);
  } else {
    digitalWrite(LEDG, HIGH);
  }
  ledStatus = !ledStatus;
  
  // Incremente loopCount (should change the value on memory)
  CanDataFrame newData;
  newData.rpm = (sharedCanData.rpm + 112) % 7200;
  newData.speed = (sharedCanData.speed + 3) % 200;
  newData.iat = 20 + (loopNum % 7);
  newData.ect = 85 + (loopNum % 19);
  newData.egt = 800 + (loopNum % 197);
  newData.cont = loopNum++;
  
  // Copiamos la nueva info al puntero.
  memcpy(&sharedCanData, &newData, sizeof(newData));
//  sharedCanData = newData;
  // Lets delay the loop a different amounth each time so we can be cetain that loopCount is incrementing.
  delay(5); 
}

Thanks for your help! I will try this code and update any news or issues I encounter.
By the way, Can we only share int variables? Could we share a string of 23 characters?

thanks for all your contributions to this thread, I have solved my problem of sharing a string variable using two integers as flags (one for each core) which signal when each core is ready to receive/send and the string is printed from M4 using RPC.print, then read and stored in M7.

Hi tineira,
did you get the can bus to work on the m4 core? and if so do you have a code example?
Thanks
J