Inter-core Messages

Overview

The RP2040 MCU has two FIFO queues between the processor cores, one from core 0 to core 1, and the other vice versa. They can be used for whatever purpose the programmer sees fit. Module Messages uses them two transfer messages between threads on the two cores. Messages are one means to synchronise threads on different cores. Threads on the same core can use Semaphores and Signals.

There are also spinlocks, but we don’t consider these here.

Requirement

Each thread on one core must be able to send messages to any thread on the other core.

Design Considerations

Each FIFO is 32 bits wide, eight values deep. After some experimentation with different use cases and scenarios, I have decided to make the messaging stateless, that is, each message is self-contained, and not part of a stateful protocol.

The reasons for this decision are mainly simplicity, and the many different possible requirements of various scenarios. The stateless approach is much easier to handle in general, not least in the case of a run-time error, or a timing issue on one of the cores, or buffer overflows, or missing parts of stateful multi-part messages. Think: error handling, and in particular autonomous recovery from errors.

Stateful protocol can be implemented on program level if needed, based on the facilities provided by module Messages, designed to fit the specific requirements of the use case.

Since each message must be self-contained, it can only hold 32 bits of information. As we want to be able to send messages to any thread on the other core, these 32 bits must contain the target thread’s identification, limiting the usable data payload that can be transferred. In other words, it’s not possible to transfer 32 bit data with one message. However, this can easily be solved on program level, using shared buffers, or splitting the data into two messages.

Each message is simply:

TYPE Message = ARRAY 4 OF BYTE;

With this definition, making use of Astrobe’s corresponding language extension, we can easily send any four byte value across the FIFO, including basic types.

Module Messages has one requirement, however: element zero of the above array must contain the id of the target thread, or rather, as we’ll see the id of the thread’s sender-receiver.

The other three bytes are usable for whatever purpose for a specific program. See example program Messaging.

Architecture

+--------+         +-------------+
| thread |-subscr->| SndRcv      |
|        |         |  +--------+ |     +---------+     +---------+     +---------+
|        |---put-->|  | outBuf | |---->|         |---->| FIFO >> |---->|         |---->
|        |         |  +--------+ |     | FIFO    |     +---------+     | FIFO    |
|        |         |  +--------+ |     | thread  |     +---------+     | thread  |
|        |<--get---|  | inBuf  | |<----|         |<----| << FIFO |<----|         |<----
|        |         |  +--------+ |     +---------+     +---------+     +---------+
+--------+         +-------------+

<------------------ core 0 -------------------------------->|<---------- core 1 -------

A thread:

  • subscribes to a sender-receiver (SndRcv)
  • puts messages into an output buffer (outBuf)
  • gets messages from an input buffer (inBuf)
  • can await an incoming message
  • can await the output buffer getting emptied

A dedicated thread on each core handles the transfers across the FIFOs. Since the FIFO handling thread and any messaging client threads are scheduled cooperatively, and each message sent and received is self-contained without state on this basic level, the out and in buffers can be shared without interference.

Each SndRcv has an id, which is unique per core. The sender must know the id of the receiver’s SndRcv. This relationship between thread and its SndRcv is fixed in the program code. There’s no fancy dynamic lookup or the like. Simplicity and robustness.

Buffer Overruns

Any buffer overruns are handled like the other buffers of the RP2040: when full, new arrivals are lost, but the condition is registered in corresponding flags.

Data Transfers

To transfer larger amounts of data, two threads can synchronise the use of a shared buffer via corresponding messages.

The FIFO Handler

The threads handling the FIFOs on both sides are “normal” application level threads. That is, they are also time-triggered with the current version of the kernel, and they share the execution time window of the scheduler with all other threads on the core. Their period must be selected based on the timing requirements of the communicating threads.

Installation

The messaging framework is installed by

Messages.Install(period, priority)

after installing the kernel. Thereafter, the application threads can subscribe to their SndRcv. Note that we don’t want the id of these SndRcv to change in case we make changes to the program, for example the order in which the threads are initialised and started, so the programmer assigns the ids explicitly, unlike the thread ids, which are assigned in the order the threads are allocated.

Example Program