pw_stream_shmem_mcuxpresso#

pw_stream_shmem_mcuxpresso implements the pw_stream interface for reading and writing between two different processor cores via shared memory using the NXP MCUXpresso SDK. It uses the messaging unit module (MU) to signal data readiness between cores.

Setup#

This module requires a little setup:

  1. Use pw_build_mcuxpresso to create a pw_source_set for an MCUXpresso SDK.

  2. Include the debug console component in this SDK definition.

  3. Specify the pw_third_party_mcuxpresso_SDK GN global variable to specify the name of this source set.

The name of the SDK source set must be set in the “pw_third_party_mcuxpresso_SDK” GN arg

Usage#

ShmemMcuxpressoStream blocks on both reads and writes, as only one outstanding buffer can be transferred at a time in each direction. This means a dedicated thread should be used for both reading and writing. A typical use case for this class would be as the underlying transport for a pw_rpc network between cores. Use with the pw::rpc::StreamRpcFrameSender and pw::rpc::StreamRpcDispatcher classes.

Interrupt handlers and shared buffers on both cores must be setup before using this stream. The shared buffer must be mapped as uncacheable on both sides.

As an example on the RT595, we connect the M33 core to the FusionF1 DSP. On the FusionF1 side, the MU interrupt must be explicitly routed.

Initialization for the M33 Core:

// `kSharedBuffer` is a pointer to memory that is shared between the M33 and
// F1 cores, and it is at least `2 * kSharedBufferSize` in size.
ByteSpan read_buffer = ByteSpan{kSharedBuffer, kSharedBufferSize};
ByteSpan write_buffer = ByteSpan{kSharedBuffer + kSharedBufferSize, kSharedBufferSize};
ShmemMcuxpressoStream stream{MUA, read_buffer, write_buffer};

PW_EXTERN_C void MU_A_DriverIRQHandler() {
  stream.HandleInterrupt();
}

void Init() {
  return stream.Enable();
}

Initialization for the FusionF1 Core:

ByteSpan write_buffer = ByteSpan{kSharedBuffer, kSharedBufferSize};
ByteSpan read_buffer = ByteSpan{kSharedBuffer + kSharedBufferSize, kSharedBufferSize};
ShmemMcuxpressoStream stream{MUB, read_buffer, write_buffer};

PW_EXTERN_C void MU_B_IrqHandler(void*) {
  stream.HandleInterrupt();
}

void Init() {
 // Enables the clock for the Input Mux
 INPUTMUX_Init(INPUTMUX);
 // MUB interrupt signal is selected for DSP interrupt input 1
 INPUTMUX_AttachSignal(INPUTMUX, 1U, kINPUTMUX_MuBToDspInterrupt);
 // Disables the clock for the Input Mux to save power
 INPUTMUX_Deinit(INPUTMUX);

 xt_set_interrupt_handler(kMuBIrqNum, MU_B_IrqHandler, NULL);
 xt_interrupt_enable(kMuBIrqNum);
 stream.Enable();
}

Read/Write example where each core has threads for reading and writing.

Core 0:

constexpr std::byte kCore0Value = std::byte{0xab};
constexpr std::byte kCore1Value = std::byte{0xcd};

void ReadThread() {
  while(true) {
    std::array<std::byte, 1> read = {};
    auto status = stream.Read(read);
    if (!status.ok() || status.size() != 1 || read[0] != kCore1Value) {
      PW_LOG_WARN("Incorrect value read from core1");
    }
  }
}


void WriteThread() {
  std::array<std::byte, 1> write = {kCore0Value};
  while(true) {
    stream.Write(write);
  }
}

Core 1:

 void ReadThread() {
  while(true) {
    std::array<std::byte, 1> read = {};
    auto status = stream.Read(read);
    if (!status.ok() || status.size() != 1 || read[0] != kCore0Value) {
      PW_LOG_WARN("Incorrect value read from core0");
    }
  }

}

void WriteThread() {
  std::array<std::byte, 1> write = {kCore1Value};
  while(true) {
    stream.Write(write);
  }
}