Inter-Processor Communication

Overview

The nRF5340 DK application uses dual-core architecture where the APP core and NET core communicate via RPMsg/OpenAMP protocol. This enables efficient separation of concerns:

  • APP Core: High-level application logic (Matter, Thread, UI)

  • NET Core: Low-level radio protocols (BLE, 802.15.4)

Communication Mechanisms

The cores communicate using:

  • RPMsg/OpenAMP: Zephyr’s inter-processor communication framework

  • Shared Memory: Zero-copy message passing

  • Interrupts: Fast notification between cores

  • Message Queues: Buffered asynchronous communication

IPC Architecture

@startuml ipc_communication
skinparam backgroundColor #FFFFFF

title Inter-Processor Communication (IPC)

participant "APP Core\napp_core.cpp" as App #LightBlue
participant "IPC Core (APP)\nsmarthome::ipc" as IPCApp #Orange
box "Shared Memory" #LightGray
    queue "RPMsg Channel" as RPMsg
end box
participant "IPC Core (NET)\nsmarthome::ipc" as IPCNet #Orange
participant "NET Core\nnet_core.cpp" as Net #LightPink

== Initialization ==
App -> IPCApp : init()
activate IPCApp
IPCApp -> RPMsg : create endpoint
RPMsg --> IPCApp : ready
deactivate IPCApp

Net -> IPCNet : init()
activate IPCNet
IPCNet -> RPMsg : create endpoint
RPMsg --> IPCNet : ready
deactivate IPCNet

== Message Sending (APP → NET) ==
App -> IPCApp : send(message)
activate IPCApp
IPCApp -> IPCApp : serialize message
IPCApp -> RPMsg : rpmsg_send()
note right
  Non-blocking send
  OpenAMP handles buffering
end note
RPMsg -> IPCNet : interrupt NET core
deactivate IPCApp

IPCNet -> RPMsg : receive callback
activate IPCNet
IPCNet -> IPCNet : deserialize message
IPCNet -> Net : notify(message)
deactivate IPCNet

== Message Sending (NET → APP) ==
Net -> IPCNet : send(message)
activate IPCNet
IPCNet -> IPCNet : serialize message
IPCNet -> RPMsg : rpmsg_send()
RPMsg -> IPCApp : interrupt APP core
deactivate IPCNet

IPCApp -> RPMsg : receive callback
activate IPCApp
IPCApp -> IPCApp : deserialize message
IPCApp -> App : notify(message)
deactivate IPCApp

note over App, Net
  **IPC Message Types:**
  - Radio control commands
  - BLE status updates
  - Network statistics
  - Power management
end note

@enduml

Inter-Processor Communication Flow

Inter-Processor Communication Flow:

APP Core                              NET Core
────────────────────────────          ────────────────────────────
│                                     │
│ smarthome::ipc::IPCCore            │ smarthome::ipc::IPCCore
│ ┌───────────────────┐              │ ┌───────────────────┐
│ │ send(message)     │              │ │ receive_callback()│
│ └────────┬──────────┘              │ └────────▲──────────┘
│          │                          │          │
│          ▼                          │          │
│  ┌──────────────┐                  │  ┌──────────────┐
│  │  serialize   │                  │  │ deserialize  │
│  └──────┬───────┘                  │  └──────▲───────┘
│         │                           │         │
│         ▼                           │         │
│  ┌────────────────────────────────────────────┴─────┐
│  │         Shared Memory (RPMsg Buffer)             │
│  │  ┌──────────────┐  ┌──────────────┐             │
│  │  │ TX Queue (→) │  │ RX Queue (←) │             │
│  │  └──────────────┘  └──────────────┘             │
│  └────────┬────────────────────────────▲───────────┘
│           │                            │
│           ▼                            │
│    [Interrupt NET]              [Interrupt APP]
│                                        │
└────────────────────────────────────────┘

Note

PlantUML sequence diagram available in CI/CD builds at doc/diagrams/ipc_communication.puml

The smarthome::ipc::IPCCore module provides a C++ wrapper around Zephyr’s OpenAMP API:

namespace smarthome {
namespace ipc {

class IPCCore {
public:
    static IPCCore& getInstance();

    void init();
    void send(const Message& msg);
    void registerCallback(MessageCallback callback);

private:
    // OpenAMP endpoint
    struct rpmsg_endpoint endpoint_;

    // Message queue for RX
    K_KERNEL_STACK_MEMBER(rx_stack_, 1024);
    struct k_thread rx_thread_;
};

} // namespace ipc
} // namespace smarthome

Note

See doc/diagrams/ipc_communication.puml for detailed PlantUML sequence diagram.

Message Types

IPC Message Structure

namespace smarthome {
namespace ipc {

enum class MessageType : uint8_t {
    RADIO_CONTROL,      // APP → NET: Radio commands
    BLE_STATUS,         // NET → APP: BLE connection state
    NETWORK_STATS,      // NET → APP: Link quality data
    POWER_MANAGEMENT,   // Bidirectional: Sleep/wake
};

struct Message {
    MessageType type;
    uint32_t timestamp;
    uint16_t length;
    uint8_t data[256];
};

} // namespace ipc
} // namespace smarthome

Communication Patterns

APP → NET Core

Use Case: APP core requests BLE advertising

// APP Core
smarthome::ipc::Message msg;
msg.type = MessageType::RADIO_CONTROL;
// ... populate data ...

smarthome::ipc::IPCCore::getInstance().send(msg);

// NET Core receives and processes
void handle_radio_control(const Message& msg) {
    smarthome::protocol::ble::BLEManager::getInstance()
        .startAdvertising();
}

NET → APP Core

Use Case: NET core notifies BLE connection

// NET Core
smarthome::ipc::Message msg;
msg.type = MessageType::BLE_STATUS;
// ... populate connection info ...

smarthome::ipc::IPCCore::getInstance().send(msg);

// APP Core receives and updates UI
void handle_ble_status(const Message& msg) {
    // Update LED status or notify Matter stack
}

Implementation Details

Initialization

Both cores must initialize IPC before communication:

APP Core (app_core.cpp):

extern "C" int main() {
    // Initialize IPC first
    smarthome::ipc::IPCCore::getInstance().init();

    // Register callback
    smarthome::ipc::IPCCore::getInstance()
        .registerCallback(app_ipc_callback);

    // ... rest of initialization ...
}

NET Core (net_core.cpp):

namespace net {

void NetCoreManager::init() {
    // Initialize IPC
    smarthome::ipc::IPCCore::getInstance().init();

    // Register callback
    smarthome::ipc::IPCCore::getInstance()
        .registerCallback([this](const auto& msg) {
            this->handleMessage(msg);
        });

    // Initialize radio subsystems
    ble_manager_.init();
    radio_manager_.init();
}

} // namespace net

Message Threading

The IPC module uses Zephyr threading for message processing:

class IPCCore {
private:
    // RX thread for processing incoming messages
    K_KERNEL_STACK_MEMBER(rx_stack_, 1024);
    struct k_thread rx_thread_;

    // Thread entry point
    static void rx_thread_entry(void* p1, void* p2, void* p3);
};

The RX thread:

  1. Blocks on OpenAMP receive

  2. Deserializes incoming message

  3. Invokes registered callback

  4. Returns to blocking receive

Performance Characteristics

Latency:

< 100μs for message passing between cores

Throughput:

Up to 10,000 messages/second

Message Size:

Up to 256 bytes per message

Queue Depth:

Configurable in OpenAMP (default 16 messages)

The low latency is achieved through:

  • Shared Memory: Zero-copy message passing

  • Hardware Interrupts: Direct core-to-core notification

  • No Context Switching: Dedicated IPC thread on each core

Configuration

OpenAMP Configuration

Zephyr configuration for inter-processor communication:

# Enable OpenAMP/RPMsg
CONFIG_OPENAMP=y
CONFIG_OPENAMP_RSC_TABLE=y

# IPC configuration
CONFIG_IPC_SERVICE=y
CONFIG_IPC_SERVICE_BACKEND_RPMSG=y

# Shared memory region
CONFIG_OPENAMP_SHM_BASE_ADDRESS=0x20070000
CONFIG_OPENAMP_SHM_SIZE=0x10000

Thread Stack Sizes

// IPC RX thread stack
K_KERNEL_STACK_MEMBER(rx_stack_, 1024);  // 1 KB sufficient

// NET core main stack
K_KERNEL_STACK_DEFINE(net_core_stack, 2048);  // 2 KB

Usage Examples

Example 1: Request Network Statistics

APP core requests network quality data from NET core:

// APP Core - request stats
smarthome::ipc::Message req;
req.type = MessageType::NETWORK_STATS;
smarthome::ipc::IPCCore::getInstance().send(req);

// NET Core - respond with stats
void handle_stats_request(const Message& req) {
    smarthome::ipc::Message resp;
    resp.type = MessageType::NETWORK_STATS;

    // Populate with actual stats
    int8_t rssi = get_current_rssi();
    memcpy(resp.data, &rssi, sizeof(rssi));

    smarthome::ipc::IPCCore::getInstance().send(resp);
}

Example 2: BLE Connection Notification

NET core notifies APP core of BLE connection changes:

// NET Core - BLE callback
void ble_connected_callback(bool connected) {
    smarthome::ipc::Message msg;
    msg.type = MessageType::BLE_STATUS;
    msg.data[0] = connected ? 1 : 0;
    msg.length = 1;

    smarthome::ipc::IPCCore::getInstance().send(msg);
}

// APP Core - handle notification
void app_ipc_callback(const smarthome::ipc::Message& msg) {
    if (msg.type == MessageType::BLE_STATUS) {
        bool connected = msg.data[0] != 0;
        LOG_INF("BLE connection status: %s",
                connected ? "connected" : "disconnected");
    }
}

Testing

Build and Flash =========both cores:

cd software
./make.sh build
  1. Flash to nRF5340 DK:

    ./make.sh flash
    
  2. Monitor serial output:

    screen /dev/ttyACM0 115200
    
  3. Test IPC by pressing buttons - observe messages between cores

Debugging IPC

Enable IPC debug logging in prj.conf:

CONFIG_OPENAMP_LOG_LEVEL_DBG=y
CONFIG_IPC_SERVICE_LOG_LEVEL_DBG=y

Key Zephyr APIs Used

OpenAMP/RPMsg

API

Description

rpmsg_service_register_endpoint()

Register IPC endpoint

rpmsg_send()

Send message to remote core

rpmsg_service_receive()

Receive message (callback)

IPC Service

API

Description

ipc_service_open_instance()

Open IPC instance

ipc_service_register_endpoint()

Register endpoint

ipc_service_send()

Send data via IPC

Threading

API

Description

K_KERNEL_STACK_MEMBER()

Define thread stack in class

k_thread_create()

Create new thread

k_sleep()

Yield thread execution

Source Code Reference

IPC Module Files

  • app/src/sdk/ipc/ipc_core.hpp - IPC interface

  • app/src/sdk/ipc/ipc_core.cpp - IPC implementation

  • app/src/app_core/app_core.cpp - APP core usage

  • app/src/net_core/net_core.cpp - NET core usage

  • app/src/modules/uart/uartmodule.hpp - UART module interface

  • app/src/modules/uart/uartmodule.cpp - UART module implementation

Task Files

  • app/src/thread/uart_task.h - UART task interface

  • app/src/thread/uart_task.cpp - UART task implementation

See Also