Portenta with Vision Shield M4 / M7 Cores Serial UART Communication

I try not to bug Jn, but he has so much information sometimes I can’t help it.

After we get the Portenta with Vision Shield running lots of EdgeImpulse programs on either M7 or M4 core, but want to communicate with the other core. The standard Arduino method to pass a variable between cores is to use RPC my example here, which works fine but is a bit slow, see Arduino forum here.

I can send complex SerialTransfer or ArduinoJSON data over 2 UARTs using the breakout board examples here but that is a huge board. I can send info from the M4 core to the M7 core using Serial1 same as myUART1 using the PortentaH7 board D13 and D14 pins here but can’t use that simple program for sending complex data.

A solution would be SoftwareSerial.h so that I could make some of the few pins available on the Portenta use Serial but that library is not in the Arduino core. I have looked for MBED examples to reassign pins to be able to do serial, but finding nothing that works on the Portenta.

Any suggestions @janjongboom for serial communication between the 2 Portenta Cores without having to use the large breakout board. Would allow EdgeImpulse models to quickly communicate between cores for controlling actuators/motors in near real time.

Hi @Rocksetta, as far as I know the M7 and M4 share memory so I guess you can use that to set up a much more efficient IPC. Maybe do something like this:

  1. Create a structure like (shared between both cores):

    typedef struct {
        bool message_pending;
        const char *data;
    } ipc_message_t;
    
  2. Initialize the structure on the parent core:

    ipc_message_t msg;
    msg.message_pending = true;
    msg.data = "Hello world";
    
  3. Send the address of the message over the normal IPC channel (not sure if this is the exact syntax):

    RPC1.call("init_ipc_channel", (uint32_t)&msg);
    
  4. Then on the client receive the init message, and spin up a thread to monitor this block of memory:

    rtos::Thread *msg_thread = nullptr;
    ipc_message_t *msg = nullptr;
    
    void run_msg_thread() {
        while (1) {
            if (msg->message_pending) {
                printf("Received a message: %s\n", msg->data);
                msg->message_pending = false;
            }
            else {
                wait_ms(10);
            }
        }
    }
    
    // not sure if you receive a void* here or a uint32_t
    int init_ipc_channel(uint32_t msg_address) {
        // receive message
        ipc_message_t *msg = (ipc_message_t*)msg_address;
    
        // run separate thread to read messages
        msg_thread = new rtos::Thread(&run_msg_thread);
    }
    
    RPC1.bind("init_ipc_channel", &init_ipc_channel);
    
  5. On the parent core you can just update the msg object whenever you have some data:

    msg.message_pending = true;
    msg.data = "Hello world again";
    

    (Probably want to make sure message_pending is false, to ensure it was handled by the other core).

None of this was verified on the Portenta, but hopefully it’ll nudge you in the right direction!

1 Like

Thanks so much @janjongboom, with yours and other suggestions I managed to get something working with a Struct. It does not die gracefully but either works or doesn’t something to do with the M4 core delay(50);

But at least it works.

Anyone interested , here is my code to move data between the M4 core (running an edge impulse model) to the outer M7 core running your arduino actuators using the PortentaH7

#ifdef CORE_CM7    // Start M7 Core programming

#include "RPC_internal.h"  // comes with the Arduino mbed core installation

// Define the structure
struct myStruct {
  bool  myNewEntry;
  int   x;
  float y;
  char  z;
  char  myCharArray[6];
};


//Activate the structure
myStruct myTestStruct;  // = {true, 5, 3.4, 'H','e','l','l','o'};


// Procedure to change the structure
int setVar(int a) {
    myTestStruct.x = (int)a;
    myTestStruct.myNewEntry   = true;
    return a;
  }


void setup() {
   RPC1.begin();
   bootM4();  
   Serial.begin(115200);

   // Allow the M4 core to change the structure
   RPC1.bind("setVar", setVar); // do these have to be the same?      
}


void loop() {
    if (myTestStruct.myNewEntry){
       Serial.println(String(myTestStruct.x));
       myTestStruct.myNewEntry = false;
    }
    
}

#endif  // Done M7 core programming

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

#ifdef CORE_CM4  // Start M4 core programming

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

int myCount=0;

void setup() {
   RPC1.begin();         
}

void loop() {
   myCount += 1;
   if (myCount >= 10000) {
      myCount = 0;
   }
   
                // important for this delay to be before the RPC call ??
                // Does not gracefully die, either works or doesn't
   delay(50);   // delay(50); CAREFUL, THIS DELAY MUST BE ENOUGH for the RPC
   auto res = RPC1.call("setVar", myCount).as<int>();
             
}

#endif  // end M4 Core Programming

2 Likes

@rjames

Issue:
Dual core microcontrollers need to be able to exchange data between the cores quickly for some machine learning applications.

With a friend I have been messing around with the Portenta M7, M4 core communication again. This time using SDRAM, however it looks like the same process could be used for exchanging RAM memory between cores by replacing SDRAM.malloc with malloc

The concept is:

  1. Setup SDRAM pointer in M7
  2. use RPC to send that pointer address to M4
  3. Assign the M7 SDRAM pointer to a pointer in M4
  4. Change the SDRAM value in M4 (an increasing integer)
  5. Read the change in M7 and print to serial.

The weird part is that step 2 sending the pointer address to M4 needs to be done every M7 main loop. Also that RPC command is ridiculously slow. Not sure why it can’t be done once in the setup.

Here is my code for both cores. Any suggestions Raul or anyone else?

/* Original by Khoi Hoang (Github @khoih-prog ) and Jeremy Ellis (Github @hpssjellis )
*  
*  SDRAM or regular memory access from both Portenta Cores
*  For regular ram use malloc instead of SDRAM.malloc
*  Define the SDRAM memory on M7
*  Pass that memory location to M4 using RPC
*  Access / set the data in SDRAM from either core
*  Open the serial monitor to see the numbers increase
*
*  Note: Each core sketch can be seperated.
*/


#ifdef CORE_CM7    // Start M7 Core programming

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


uint32_t* mySdramM7;

void setup() {
  bootM4();
  RPC.begin();    
  Serial.begin(115200);
  
  SDRAM.begin(); // is the same as SDRAM.begin(SDRAM_START_ADDRESS);
  mySdramM7 = (uint32_t*) SDRAM.malloc(sizeof(uint32_t));

  // Good to know the memory location 
  // while (!Serial);                     // this is blocking
  // Serial.print("Address = 0x"); 
  // Serial.println((uint32_t) mySdramM7, HEX);

   //RPC.call("setVar", (uint32_t) mySdramM7);
}


void loop(){
  RPC.call("setVar", (uint32_t) mySdramM7);

  Serial.println(*mySdramM7);

}

#endif  // Done M7 core programming

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

#ifdef CORE_CM4  // Start M4 core programming

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

int myMicroDelay = 7654;   // Slow to match the M7 RPC call
//int myMicroDelay = 34;   // Want this, much faster

uint32_t* mySdramM4 = 0;
uint32_t myCount = 0;

bool setVar(uint32_t mySentPointer){
  mySdramM4 = (uint32_t*) mySentPointer; 
  return true;
}


void setup() {
  RPC.begin();
  SDRAM.begin(); // is the same as SDRAM.begin(SDRAM_START_ADDRESS);
  RPC.bind("setVar", setVar);
}


void loop() {
  myCount++;

  // Must check to avoid crash
  if (mySdramM4 != 0){
    *mySdramM4 = myCount;
  } 
  
  delayMicroseconds(myMicroDelay);
}

#endif  // end M4 Core Programming

I have given up on SDRAM communication because of the weird RPC needing to be done each loop. Here is the new method. SRAM fixed location.

Only completely strange thing about this code is it only runs when the serial monitor is open even though I have taking out the block serial call AND the first three Serial.print lines are needed or M4 can’t count anymore.

here is the output running very fast. The Blue LED blinks so fast you can barely tell it is ever off.

// Original by Khoi Hoang (Github @khoih-prog ) and Jeremy Ellis (Github @hpssjellis )
// Note: each core can be seperated to it's own IDE saved folder

#ifdef CORE_CM7    // Start M7 Core programming

#include "mbed.h"
#include "Arduino.h"

using namespace mbed;
using namespace rtos;

//#define SRAM4_START_ADDRESS       ((uint32_t) 0x30040000)   
#define SRAM4_START_ADDRESS       ((uint32_t) 0x38000000)

struct shared_data{
  int M4toM7;
  int M7toM4;
};

int localm7m4 = 0;
int localm4m7 = 0;


// Using AHB SRAM4 at 0x38000000
static struct shared_data * const xfr_ptr = (struct shared_data *) SRAM4_START_ADDRESS;


Thread M7Thread;

void setup(){
  bootM4();

  Serial.begin(115200);

  delay(10);  // give M4 time to boot!
  yield();

  //while (!Serial);  // still blocking when removed? 

  // strangly if I remove these lines the M4 freezes
  Serial.println(F("\nStarting M7_M4_sharemem_SRAM on Portenta_H7"));
  Serial.print(F("Using SRAM4 @ 0x"));  
  Serial.println(SRAM4_START_ADDRESS, HEX);
  

  M7Thread.start(callback(M7ThreadFunc));
  yield();
}

void M7ThreadFunc(){
  //localm7m4 = 777777;
  
  while (true) {   
    yield();
    localm7m4++;
    xfr_ptr -> M7toM4 = localm7m4;
    
    Serial.print("M7 to M4: "); Serial.println(xfr_ptr -> M7toM4);
    Serial.print("M4 to M7: "); Serial.println(xfr_ptr -> M4toM7);

    Serial.println();        
    yield();
    //delay(20);
    

   delayMicroseconds(1234);    // Note: micro-seconds not milli-seconds
  }
}

void loop(){
  yield();
}

#endif

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




#ifdef CORE_CM4    // Start M4 Core programming


#include "mbed.h"
#include "Arduino.h"

using namespace mbed;
using namespace rtos;


//#define SRAM4_START_ADDRESS       ((uint32_t) 0x30040000)   
#define SRAM4_START_ADDRESS       ((uint32_t) 0x38000000)

struct shared_data{
  int M4toM7;
  int M7toM4;
};

int localm7m4 = 0;
int localm4m7 = 0;


static struct shared_data * const xfr_ptr = (struct shared_data *) SRAM4_START_ADDRESS;


int myStoreFromM7;

Thread M4Thread;

void setup(){
  pinMode(LEDB, OUTPUT);  // portenta blue LED
  yield();
  M4Thread.start(callback(M4ThreadFunc));
  yield();
}

void M4ThreadFunc(){
  //localm4m7 = 44444444;
  
  while (true){
    localm4m7++;
 
    xfr_ptr -> M4toM7 = localm4m7;

    // test if M4 an read struct changed on M7 
    if (myStoreFromM7 !=  xfr_ptr -> M7toM4){
        myStoreFromM7 =  xfr_ptr -> M7toM4;
        digitalWrite(LEDB, !digitalRead(LEDB)); // flip blue LED on and off
     }
     yield();
    // delay(20);  
    delayMicroseconds(1234); //Note: micro-seconds not milli-seconds
  }
}

void loop(){
  yield();
}

#endif