System APIs

APIs to manage system processes.

Inter-processor communication (IPC)

APIs to initiate multicore processing and pass messages between the M7 and M4 cores.

For example, the following code (from examples/multi_core_ipc/) executes on the M7. It registers a handler to receive messages from the M4 program right before it starts the M4. Then it starts a loop to also periodically send messages to the M4:

namespace coralmicro {
namespace {

void HandleM4Message(const uint8_t data[kIpcMessageBufferDataSize]) {
  const auto* msg = reinterpret_cast<const ExampleAppMessage*>(data);
  if (msg->type == ExampleMessageType::kAck) {
    printf("[M7] ACK received from M4\r\n");
  }
}

[[noreturn]] void Main() {
  printf("Multicore IPC Example!\r\n");
  // Turn on Status LED to show the board is on.
  LedSet(Led::kStatus, true);

  auto* ipc = IpcM7::GetSingleton();
  ipc->RegisterAppMessageHandler(HandleM4Message);
  ipc->StartM4();
  CHECK(ipc->M4IsAlive(500));

  bool led_status = false;
  while (true) {
    led_status = !led_status;
    printf("---\r\n[M7] Sending M4 LED_STATUS: %s\r\n",
           led_status ? "ON" : "OFF");

    IpcMessage msg{};
    msg.type = IpcMessageType::kApp;
    auto* app_msg = reinterpret_cast<ExampleAppMessage*>(&msg.message.data);
    app_msg->type = ExampleMessageType::kLedStatus;
    app_msg->led_status = led_status;
    ipc->SendMessage(msg);

    vTaskDelay(pdMS_TO_TICKS(1000));
  }
}

}  // namespace
}  // namespace coralmicro

extern "C" [[noreturn]] void app_main(void* param) {
  (void)param;
  coralmicro::Main();
}

And the following is the counterpart code that runs on the M4. It registers a message handler to receive incoming messages from the M7, which simply passes another message back to the M7:

namespace coralmicro {
namespace {

void HandleM7Message(const uint8_t data[kIpcMessageBufferDataSize]) {
  const auto* msg = reinterpret_cast<const ExampleAppMessage*>(data);
  if (msg->type == ExampleMessageType::kLedStatus) {
    printf("[M4] LED_STATUS received: %s\r\n", msg->led_status ? "ON" : "OFF");
    LedSet(Led::kUser, msg->led_status);

    IpcMessage ack_msg{};
    ack_msg.type = IpcMessageType::kApp;
    auto* app_msg = reinterpret_cast<ExampleAppMessage*>(&ack_msg.message.data);
    app_msg->type = ExampleMessageType::kAck;
    IpcM4::GetSingleton()->SendMessage(ack_msg);
  }
}

}  // namespace
}  // namespace coralmicro

extern "C" void app_main(void* param) {
  (void)param;
  printf("M4 started.\r\n");

  coralmicro::IpcM4::GetSingleton()->RegisterAppMessageHandler(
      coralmicro::HandleM7Message);
  vTaskSuspend(nullptr);
}

Notice that the data for IpcMessage uses a custom message format, which is defined in examples/multi_core_ipc/example_message.h like this:

enum class ExampleMessageType : uint8_t {
  kLedStatus,
  kAck,
};

struct ExampleAppMessage {
  ExampleMessageType type;
  bool led_status;
} __attribute__((packed));

For information about how to get started with multicore processing with the M4, see the guide to create a multicore app.

namespace coralmicro
class Ipc
#include <ipc.h>

Do not instantiate this class. It provides shared IPC functions for IpcM7 and IpcM4.

Public Functions

void SendMessage(const IpcMessage &message)

Sends an IPC message to the other core.

Parameters

message – The message to send.

inline void RegisterAppMessageHandler(AppMessageHandler handler)

Sets a callback function to process incoming IPC messages.

Parameters

handler – The function to receive incoming messages.

Public Types

using AppMessageHandler = std::function<void(const uint8_t data[kIpcMessageBufferDataSize])>

The function type to handle incoming IPC messages, which must be given to RegisterAppMessageHandler() via IpcM7 or IpcM4, respective of which core is receiving the messages.

The function receives the IPC message as a byte array of size kIpcMessageBufferDataSize (127).

namespace coralmicro
class IpcM7 : public coralmicro::Ipc
#include <ipc_m7.h>

Singleton object that provides IPC on the M7 core so it can start the M4 core and relay messages with the M4.

The M4 can not operate independent from the M7: the M7 must start the M4 with StartM4(), and then the M7 task may suspend itself.

The M7 can send messages to the M4 with SendMessage() and register a callback to receive messages from the M4 with RegisterAppMessageHandler().

Public Functions

void StartM4()

Starts the M4 core, invoking the app_main() function in the executable declared with the CMake add_executable_m4() command.

bool M4IsAlive(uint32_t millis)

Checks if the M4 core is alive.

Parameters

millis – The amount of time (in milliseconds) to wait for a response from the M4 core.

Returns

True if the M4 core signals that it is ready to preform a task or preforming a task within the millis time limit, false otherwise.

Public Static Functions

static inline IpcM7 *GetSingleton()

Gets the IpcM7 singleton that can start the M4 and perform IPC with the M4.

Returns

A reference to the singleton IpcM7 object.

static bool HasM4Application()

Checks if the M4 core is running a process.

Returns

True if the M4 is running a process, false otherwise.

namespace coralmicro
class IpcM4 : public coralmicro::Ipc
#include <ipc_m4.h>

Singleton object that provides IPC on the M4 core to relay messages with the M7 core.

The M4 can not operate independent from the M7: the M7 must start the M4 with IpcM7::StartM4(), and then the M7 task may suspend itself.

The M4 can send messages to the M7 with SendMessage() and register a callback to receive messages from the M7 with RegisterAppMessageHandler().

Public Static Functions

static inline IpcM4 *GetSingleton()

Gets the IpcM4 singleton to use for IPC with the M7.

Returns

A reference to the singleton IpcM4 object.

namespace coralmicro
struct IpcMessage
#include <ipc_message_buffer.h>

A message to be sent with Ipc::SendMessage() (using either IpcM4 or IpcM7).

The message union is designed to support two types of message, but you should always use an “app” message. So you should set type to IpcMessageType::kApp and then populate data with a custom data format that both processes know how to read/write.

For an example, see examples/multi_core_ipc/.

Public Members

IpcMessageType type

Identifier for the type of message (apps should always use kApp).

IpcSystemMessage system

Internal use only.

uint8_t data[kIpcMessageBufferDataSize]

A byte array, which should be a structured data format that’s defined by the app, but limited to size kIpcMessageBufferDataSize (127 bytes).

union coralmicro::IpcMessage::[anonymous] message

The message to be sent (system or data).

Functions

struct coralmicro::IpcMessage __attribute__ ((packed))

Enums

enum class IpcMessageType : uint8_t

The types of message that may be sent in an IpcMessage.

Values:

enumerator kSystem

Internal use only.

enumerator kApp

A custom app message with a byte array of size kIpcMessageBufferDataSize (127).

Mutex

APIs to ensure mutual-exclusive access to resources.

namespace coralmicro
class MulticoreMutexLock
#include <mutex.h>

Defines a mutex lock that is unique across MCU cores, ensuring safe handling of any resources that are shared between the M7 and M4 cores.

Public Functions

inline explicit MulticoreMutexLock(uint8_t gate)

Acquires a multi-core mutex lock using a semaphore gate number.

Any code following this request for the mutex lock is blocked until the lock is successfully acquired, and each unique mutex lock can be held by only one task at a time, regardless of which MCU core the task is on. Thus, code-blocks that use the same gate to get a MulticoreMutexLock will have thread-safe variables, even across cores.

After execution leaves the code block where the lock is acquired, the mutex lock is automatically released.

Parameters

gate – An integer representing a unique semaphore gate. Must be within range of the maximum number of gates available on the hardware (16).

class MutexLock
#include <mutex.h>

Defines a mutex lock for the active MCU core, ensuring safe handling of any resources that are shared between tasks.

Public Functions

inline explicit MutexLock(SemaphoreHandle_t sema)

Acquires a mutex lock using a semaphore.

Any code following this request for the mutex lock is blocked until the lock is successfully acquired, and each unique mutex lock can be held by only one task at a time (on the same MCU core). Thus, code-blocks that use the same semaphore to get a MutexLock (on the same MCU core) will have thread-safe variables between said code-blocks.

After execution leaves the code block where the lock is acquired, the mutex lock is automatically released.

Parameters

sema – The SemaphoreHandle to define a unique mutex lock.

Watchdog

APIs to create watchdog timers that monitor the MCU behavior and reset it when it appears to be malfunctioning.

namespace coralmicro
struct WatchdogConfig
#include <watchdog.h>

Represents a watchdog config, this config needs to be passed into WatchdogStart().

Public Members

int timeout_s

Number of seconds before the watchdog resets the CPU (unless pet).

int pet_rate_s

The rate to refresh the watchdog timer in seconds.

bool enable_irq

Set to true to enable the watchdog interrupt. If watchdog interrupt is enabled, be sure to implement and extern WDOG1_IRQHandler. Otherwise, you will call DefaultISR.

int irq_s_before_timeout

When enable_irq is set, time remaining before watchdog expiration that will trigger an interrupt (e.g. timeout_s = 10, irq_s_before_timeout=2 means the interrupt will fire at 8 seconds).

Functions

void WatchdogStart(const WatchdogConfig &config)

Enables watchdog and starts SW timer to refresh at a given rate.

Parameters

config – The watchdog config to enable watchdog.

void WatchdogStop()

Stops SW refresh timer, disables watchdog.

Reset

APIs to reset the Dev Board Micro into different states and read reset stats.

namespace coralmicro
struct ResetStats
#include <reset.h>

Represents reset stats.

Public Members

uint32_t reset_reason

Reset reason, could hold kSRC_M7CoreWdogResetFlag or kSRC_M7CoreM7LockUpResetFlag.

uint32_t watchdog_resets

Number of watchdog resets.

uint32_t lockup_resets

Number of lockup resets.

Functions

void ResetToBootloader()

Reset the board to bootloader mode.

void ResetToFlash()

Reset the board to flash mode.

void ResetStoreStats()

Stores the current reset stats.

ResetStats ResetGetStats()

Gets the current reset stats.