Skip to content

Update the Giga RPC/dual-core tutorial. #1872

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 24, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Guide to GIGA R1 Dual Cores
description: Learn how to access and control the M4 and M7 cores on the GIGA R1, and how to communicate between them using RPC.
author: Karl Söderby
author: Karl Söderby, Ibrahim Abdalkader
hardware:
- hardware/10.mega/boards/giga-r1-wifi
software:
Expand All @@ -24,7 +24,8 @@ The M4 and M7 cores are programmed with separate sketches, using the same serial
In this guide you will discover:
- How to configure and program the M4/M7 cores and conventional approaches to do so.
- How to boot the M4 core.
- How to communicate between the cores via Remote Call Procedures (RPC).
- How to communicate between the cores via Remote Procedure Call (RPC).
- Using the RPC Library with MicroPython.
- Useful examples based on the dual core & RPC features.
- The `RPC` library API.

Expand Down Expand Up @@ -144,9 +145,9 @@ Once the M4 is booted from the M7, both cores will run in parallel, much like tw

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.

## Identify Core Used
## Identifying the Current CPU Core

To identify which core is being used, use the `HAL_GetCurrentCPUID()` method. Below is a function that returns which core is currently being used. This can be useful to identify that your program is running on the right core.
The `RPC.cpu_id()` method can be used to identify current CPU core running the sketch. This can be used to run different code paths based on the CPU core ID. For example, the following sketch blinks the blue LED if the current core is the Cortex-M7, and the Green LED if the current core is the Cortex-M4:

```arduino
/*
Expand All @@ -169,7 +170,7 @@ void setup() {
pinMode(LEDB, OUTPUT);
pinMode(LEDG, OUTPUT);

if (HAL_GetCurrentCPUID() == CM7_CPUID) {
if (RPC.cpu_id() == CM7_CPUID) {
blink(LEDB, 100); //blink blue LED (M7 core)
} else {
blink(LEDG, 100); //blink green LED (M4 core)
Expand All @@ -190,11 +191,7 @@ void blink(int led, int delaySeconds) {
}
```

- The `HAL_GetCurrentCPUID()` is a method that checks the CPU ID, and returns the value in a `uint32_t` format.
- The `CM7_CPUID` flag that we compare with holds the value `0x00000003` (hexadecimal), or `3` (decimal).
- It is also possible to use `CM4_CPUID` flag which holds the value `0x00000003`, or `1` (decimal).

## Remote Call Procedures (RPC)
## Remote Procedure Call (RPC)

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.

Expand Down Expand Up @@ -226,7 +223,8 @@ RPC.bind("addFunction", addFunction);
On the client side, it could look like this:

```arduino
int x,y = 10;
int x = 10;
int y = 10;

RPC.call("addFunction", x, y);
```
Expand All @@ -235,6 +233,111 @@ When `call()` is used, a request is sent, it is processed on the server side, an

![Communication between M7 and M4 core.](assets/rpc-m7-m4.png)

### Using the RPC Library with MicroPython

The `msgpackrpc` library provides the same functionality as the Arduino RPC library for MicroPython, i.e., it allows the binding of local functions or objects, starting the M4 core, and invoking remote calls from Python scripts. This library and its supporting features are enabled by default on all compatible Arduino boards, starting with **MicroPython release v1.23**, and require no external dependencies to use. However, there are a few restrictions to using the RPC library with MicroPython:

- Arduino sketches can only run on the M4 core.
- SDRAM-based firmware is **not** supported<sup>[1]</sup>.
- Flash-based firmware must use a **1.5MB M7 + 0.5MB M4** flash partitioning scheme.

The following sections introduce the `msgpackrpc` library API and some use cases in detail.

*1. Although the `msgpackrpc` library does support loading firmware images to SDRAM, the firmware currently generated for the M4 core with an SDRAM target does not work. This issue may be fixed in future releases.*

#### The `msgpackrpc` Library

The `msgpackrpc` library is the RPC library's counterpart for MicroPython, and it provides the same functionality as the Arduino RPC library. The first steps to using the `msgpackrpc` library, are importing the module and creating a `MsgPackRPC` object:

```python
import msgpackrpc

# Create a MsgPackRPC object.
rpc = msgpackrpc.MsgPackRPC()
```

The RPC object created above can then be used to bind Python callables, start the M4 core and invoke remote calls from MicroPython scripts.

#### Binding Python Functions, Callables and Objects

Any Python callable object (such as functions, bound methods, callable instances, etc.) can be invoked by the remote core. To allow the remote core to invoke a callable, it must be first bound to a name. The following example binds a function to the name `sub`:

```python
def sub(a, b):
return a - b

# Register a function to be called by the remote processor.
rpc.bind("sub", sub)
```

Note that the name a callable is bound to does not have to match its name.

Similarly, an object's bound method can also be bound to a name. For example:

```python
foo = Foo()
rpc.bind("sub", foo.add)
```

Both of those functions can be called in the same way from the Arduino sketch:

```arduino
int res = RPC.call("sub", 2, 1).as<int>();
```

Objects can also be bound to allow their methods to be called by the remote core. When an object is passed to `bind()`, all of its public methods (the ones that don't start with an `_`) are bound to their respective qualified names. For example, the following code binds the methods of an object of class `Foo`:

```python
class Foo:
def __init__(self):
pass

def add(self, a, b):
return a + b

def sub(self, a, b):
return a - b

# Register an object of Foo
rpc.bind("foo1", Foo())
```

Now the object's methods can be invoked by the Arduino sketch using their fully qualified name, for example:

```arduino
int res1 = RPC.call("foo1.add", 1, 2).as<int>();
int res2 = RPC.call("foo1.sub", 2, 1).as<int>();
```

#### Starting the M4 Core From Micropython

The next step is starting the M4 core by calling `MsgPackRPC.start()` with the firmware entry point as an argument:

```python
# Start the remote processor and wait for it to be ready to communicate.
rpc.start(firmware=0x08180000)
```

This function will start the remote core (the M4), boot it from the specified firmware address, and wait for the core to be ready to communicate before it returns. The default arguments passed to this function are compatible with the Arduino RPC library and do not need to be changed for the purposes of this tutorial. Note that the firmware address used is a flash address, where the M4 firmware starts, and it's the same one used for the flash split of `1.5MB M7 + 0.5MB M4`.

#### Calling Remote Functions From Micropython

Once the M4 core is started, the `MsgPackRPC` object can be used to invoke its remote functions. Remote calls can be invoked synchronously, i.e., the call does not return until a response is received from the other side, or asynchronously. In this case, the call returns a `Future` object that must be joined at some point to read back the call's return value.

```python
# Perform a synchronous call, which blocks until it returns.
res = rpc.call("add", 1, 2)

# Perform an asynchronous call, which returns immediately with a Future object.
f1 = rpc.call_async("add", 1, 2)

# The Future object returned above, must be joined at some point to get the results.
print(f1.join())
```

That covered most of the `msgpackrpc` library's API and use cases. For a complete example, see the [MicroPython RPC LED Example](#micropython-rpc-led). For more examples and applications, see the `msgpackrpc` [repository](https://github.com/arduino/arduino-lib-mpy/tree/main/lib/msgpackrpc).


## RPC Examples

In this section, you will find a series of examples that is based on the `RPC` library.
Expand All @@ -251,12 +354,12 @@ The `Serial.print()` command only works on the **M7 core**. In order to print va
#include <RPC.h>

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

void loop() {
RPC.println("Printed from M4 core");
delay(1000);
RPC.println("Printed from M4 core");
delay(1000);
}
```

Expand All @@ -266,8 +369,8 @@ delay(1000);
#include <RPC.h>

void setup() {
Serial.begin(9600);
RPC.begin();
Serial.begin(9600);
RPC.begin();
}

void loop() {
Expand Down Expand Up @@ -308,6 +411,7 @@ void setup() {
}

void loop() {

}

/*
Expand Down Expand Up @@ -454,6 +558,54 @@ int servoMove(int angle) {
}
```

### MicroPython RPC LED

This example demonstrates how to use MicroPython (running on the M7 core) to remotely control an LED from the M4 core.

**M4 sketch:**

```arduino
#include "RPC.h"

void led(bool on) {
if (on) {
digitalWrite(LEDG, LOW);
} else {
digitalWrite(LEDG, HIGH);
}
}

void setup() {
pinMode(LEDG, OUTPUT);
RPC.bind("led", led);
RPC.begin();
}

void loop() {

}
```

**M7 Python script:**

```python
import time
import msgpackrpc

# Create an RPC object
rpc = msgpackrpc.MsgPackRPC()

# Start the remote processor.
rpc.start(firmware=0x08180000)

# Toggle the LED every 500ms
while True:
rpc.call("led", True)
time.sleep_ms(500)
rpc.call("led", False)
time.sleep_ms(500)
```

## RPC Library API

The `RPC` library is based on the [rpclib](https://github.com/rpclib/rpclib) C++ library which provides a client and server implementation. In addition, it provides a method for communication between the M4 and M7 cores.
Expand Down Expand Up @@ -575,4 +727,23 @@ RPC.read();
#### Returns

- The first available byte from the M4.
- `-1` if there is none.
- `-1` if there is none.

### RPC.cpu_id()

Returns the current CPU ID, which can be one of two values: `CM7_CPUID` for the Cortex-M7 core, or `CM4_CPUID` for the Cortex-M4 core.

#### Syntax

```arduino
RPC.cpu_id();
```

#### Parameters

- None.

#### Returns

- `CM7_CPUID` for the Cortex-M7 core.
- `CM4_CPUID` for the Cortex-M4 core.
Loading