The GIGA R1's STM32H747XI has two cores, the M4 and the M7. Each core can be programmed individually, with M7 acting as the main processor, and the M4 as a co-processor.
The M7 is referred to as the main processor due its superior hardware features, as well as it is required to run to boot the M4 core (you boot the M4 from within the M7).
These two cores can run applications in parallel, for example, running a servo motor on one core, and a display on another, without blocking each other. In a single core, such operations would slow down the program, resulting in lesser performance.
The M4 and M7 cores are programmed with separate sketches, using the same serial port. In the Arduino IDE, you can select the core you want to program, and then upload the sketch you want to run on that specific core.
In this guide you will discover:
RPC
library API.*For instructions on how to install the GIGA Core, follow the Getting Started with GIGA R1 guide.
Programming the cores is done via the Arduino IDE, in a special interface that appears only when you select the Arduino GIGA R1 board from the board menu.
To allocate memory for the M4, the flash memory can be partitioned. This is done by navigating to Tools > Flash Split in the IDE.
It is required to use option 2 or 3 if you intend to program the M4 via the IDE, as the default option provides no memory allocation for the M4.
To select the core you want to program, navigate to Tools > Target Core in the IDE.
Here you can choose between:
As both cores share the same serial port, choosing the Flash Split + Target Core is required so that the program is uploaded to the correct core.
Uploading is no different than to any other Arduino board: simply click the upload button and wait for it to finish.
The M4 core does not boot by itself as it requires interaction from the M7 core. This boot function is built into the
RPC
library, and needs to be included in the sketch uploaded to the M7:1#include <RPC.h>2
3void setup() {4 RPC.begin(); //boots M45}6void loop(){7}
Once the M4 is booted from the M7, both cores will run in parallel, much like two Arduinos sharing the same board.
Uploading new sketches works the same as a typical upload procedure. The new sketch will overwrite the current sketch running on the core you upload to.
The M7 and M4 cores are two separate cores, and when initialized, they will continue to execute their corresponding program.
In this section you will find some known limitations of using the two parallel cores.
As mentioned in the previous section, the M4 requires to be booted from the M7, by using the
RPC.begin()
method. If this is not included, the M4 will not boot.Serial communication is not available by default on the M4 core. A work around for this is by sending data using the
RPC
library, and printing it from the M7 core. To achieve this, see the following examples:M4 Sketch
1#include <RPC.h>2
3void setup() {4 RPC.begin();5}6
7void loop() {8 // put your main code here, to run repeatedly:9 RPC.println("Hello World!");10}
M7 Sketch
1#include <RPC.h>2
3void setup() {4 // put your setup code here, to run once:5 Serial.begin(9600);6 RPC.begin();7}8
9void loop() {10 String buffer = "";11 while (RPC.available()) {12 buffer += (char)RPC.read();13 }14
15 if (buffer.length() > 0) {16 Serial.print(buffer);17 }18}
Note that both of these sketches needs to be uploaded to their corresponding cores.
Programming the M4 and M7 cores is straightforward, but can be complicated to track. Having a strategy for how you want to build your dual core applications is key.
In this section we introduce the "single" and "multiple" sketch approach, and the pros and cons of each method.
The single sketch approach means writing a single sketch that is uploaded to both cores each time a change is made. In the sketch, we can keep track of what each core does, simply by querying the core used with a simple function:
1String currentCPU() {2 if (HAL_GetCurrentCPUID() == CM7_CPUID) {3 return "M7";4 } else {5 return "M4";6 }7}
With this function, we check whether the M4 or M7 is running, and we can write code for each of the core like this:
1if (currentCPU() == "M4") {2 //run M4 code3 }4
5 if (currentCPU() == "M7") {6 //run M7 code7 }
The pros of using this approach is that you can write all code in a single file, therefore, revisioning code, as well as the provisioning is easier to manage.
The cons of using this approach is that you will run out of program memory faster. You will also upload code to the cores that will never execute (the M7 code will not execute on M4 and vice versa).
The multiple sketch approach means developing two separate sketches, one for each core. This does not require the use of the
HAL_GetCurrentCPUID()
to retrieve the core you are using, you can instead just write sketches as you would normally do.The pros of using this approach is that the code you write is optimized only for one core, and this saves a lot of program memory.
The cons is to manage the versions becomes harder, and while flashing the board, you'd need to keep track on which version is uploaded to which core. It is easier to upload to the wrong core by accident using this approach, but you have more optimized code.
When writing multiple sketches, there are some things to consider to make your development experience easier:
_M4
or _M7
suffix or prefix. This will make it easier if the code is intended to be shared with others.RPC.begin()
on your M7 core sketch.RPC is a method that allows programs to make requests to programs located elsewhere. It is based on the client-server model (also referred to as caller/callee), where the client makes a request to the server.
An RPC is a synchronous operation, and while a request is being made from the caller to another system, the operation is suspended. On return of the results, the operation is resumed.
The server side then performs the subroutine on request, and suspends any other operation as well. After it sends the result to the client, it resumes its operation, while waiting for another request.
At the moment, only a limited amount of boards supports RPC, as in this context, it is designed to be a communication line between two cores. The GIGA R1 is one of them.
What makes this implementation possible is the
RPC
library (see API section), which utilises the rpclib C++ library as well as functions from the Stream class.The library makes it possible to set up either of the M4/M7 cores as a server/client, where remote calls can be made between them. This is done by "binding" a function to a name on the server side, and calling that function from the client side.
On the server side, it could look like this:
1//server side, for example M72int addFunction(int a, int b){ 3 return a + b;4}5
6RPC.bind("addFunction", addFunction);
On the client side, it could look like this:
1int x,y = 10;2
3RPC.call("addFunction", x, y);
When
call()
is used, a request is sent, it is processed on the server side, and returned. The x
and y
variables are used as arguments, and the result returned should be 20 (10+10).
In this section, you will find a series of examples that is based on the
RPC
library. The
Serial.print()
command only works on the M7 core. In order to print values on the M4, we need to:RPC.println()
on the M4. This will print the values to the RPC1 stream.RPC.available()
and RPC.read()
.M4 Sketch:
1#include <RPC.h>2
3void setup() {4RPC.begin();5}6
7void loop() {8RPC.println("Printed from M4 core");9delay(1000);10}
M7 Sketch:
1#include <RPC.h>2
3void setup() {4Serial.begin(9600);5RPC.begin();6}7
8void loop() {9 String buffer = "";10 while (RPC.available()) {11 buffer += (char)RPC.read(); // Fill the buffer with characters12 }13 if (buffer.length() > 0) {14 Serial.print(buffer);15 }16}
This example demonstrates how to request a sensor reading from one core to the other, using:
M4 Sketch:
1#include "Arduino.h"2#include "RPC.h"3
4using namespace rtos;5
6Thread sensorThread;7
8void setup() {9 RPC.begin();10 Serial.begin(115200);11
12 /*13 Starts a new thread that loops the requestReading() function14 */15 sensorThread.start(requestReading);16}17
18void loop() {19}20
21/*22This thread calls the sensorThread() function remotely23every second. Result is printed to the RPC1 stream.24*/25void requestReading() {26 while (true) {27 delay(1000);28 auto result = RPC.call("sensorRead").as<int>();29 RPC.println("Result is " + String(result));30 }31}
M7 Sketch:
1#include "Arduino.h"2#include "RPC.h"3
4void setup() {5 RPC.begin();6 Serial.begin(115200);7
8 //Bind the sensorRead() function on the M79 RPC.bind("sensorRead", sensorRead);10}11
12void loop() {13 // On M7, let's print everything that is received over the RPC1 stream interface14 // Buffer it, otherwise all characters will be interleaved by other prints15 String buffer = "";16 while (RPC.available()) {17 buffer += (char)RPC.read(); // Fill the buffer with characters18 }19 if (buffer.length() > 0) {20 Serial.print(buffer);21 }22}23
24/*25Function on the M7 that returns an analog reading (A0)26*/27int sensorRead() {28 int result = analogRead(A0);29 return result;30}
This example demonstrates how to request a servo motor on another core to move to a specific angle, using:
Each example is written as a single sketch intended to be uploaded to both cores.
M4 sketch:
1#include "Arduino.h"2#include "RPC.h"3
4using namespace rtos;5
6Thread servoThread;7
8void setup() {9 RPC.begin();10 Serial.begin(115200);11
12 /*13 Starts a new thread that loops the requestServoMove() function14 */15 servoThread.start(requestServoMove);16}17
18void loop() {19}20
21/*22This thread calls the servoMove() function remotely23every second, passing the angle variable (0-180).24*/25void requestServoMove() {26 while (true) {27 //Read a pot meter28 int rawAnalog = analogRead(A0);29
30 //Map value to 18031 int angle = map(rawAnalog, 0, 1023, 0, 180);32
33 delay(1000);34 auto result = RPC.call("servoMove", angle).as<int>();35 RPC.println("Servo angle is: " + String(result));36 }37}
M7 sketch:
1#include "Arduino.h"2#include "RPC.h"3#include <Servo.h>4
5Servo myservo;6
7void setup() {8 RPC.begin();9 myservo.attach(5); //attach servo to pin 510
11 Serial.begin(115200);12
13 //Bind the servoMove() function on the M714 RPC.bind("servoMove", servoMove);15}16
17void loop() {18 // On M7, let's print everything that is received over the RPC1 stream interface19 // Buffer it, otherwise all characters will be interleaved by other prints20 String buffer = "";21 while (RPC.available()) {22 buffer += (char)RPC.read(); // Fill the buffer with characters23 }24 if (buffer.length() > 0) {25 Serial.print(buffer);26 }27}28
29/*30Function on the M7 that returns an analog reading (A0)31*/32int servoMove(int angle) {33 myservo.write(angle);34 delay(10);35 return angle;36 /*37 After the operation is done, return angle to the client.38 The value passed to this function does not change, but this39 verifies it has been passed correctly.40 */41}
The
RPC
library is based on the rpclib C++ library which provides a client and server implementation. In addition, it provides a method for communication between the M4 and M7 cores. This library is included in the GIGA core, so it is automatically installed with the core. To use this library, you need to include
RPC.h
:1#include <RPC.h>
Initializes the library. This function also boots the M4 core.
1RPC.begin()
1
on success.0
on failure.Used on the server side to bind a name to a function, and makes it possible for remotely calling it from another system.
1RPC.bind("this_function", thisfunction)
"name_of_func"
- name given for the function to be called from the client side.name_of_func
- name of the function on the server side.Used on the client side to call a function with optional parameters.
1RPC.call("this_function", int args)
"name_of_func"
- the name of the function declared on the server side.args
- arguments to be passed to the function.The RPC Serial methods are also included in the
RPC
library, and uses methods from the Stream base class, and is similar to the Serial class.As the
Serial
class is only available on the M7 core, the M4 core uses RPC
library to print data, where the M7 can read the data and print it to a computer.Prints data to a serial port. This is used on the M4 core to send data to the M7.
1RPC.println(val);
Get the number of available bytes to read from the M4.
1RPC.available();
-1
if there is none.Reads the first available byte from the M4.
1RPC.read();
-1
if there is none.