Skip to content

RP2350 / Pico 2 플랫폼 가이드

Raspberry Pi Pico 2 (RP2350)용 Xylolabs SDK 통합 가이드이다. RP2350은 SDK의 주요 기준 대상이다.


RP2350 사양

리소스 사양 비고
CPU 듀얼 Cortex-M33 @ 150 MHz ~300 MIPS 총합; DSP 확장
SRAM 520 KB 정적 할당, 힙 없음
Flash 외장 QSPI (일반적으로 2–16 MB) 펌웨어 + 로그 저장
I2S PIO 기반 외부 ADC로 96kHz/24-bit 처리 가능
UART 2채널 LTE-M1 모뎀 AT 명령
SPI 최대 62.5 MHz 외장 ADC, SD 카드
GPIO 30핀 센서, 모터 신호, 상태 LED
WDT 하드웨어 워치독 장기간 무인 운전
DMA 12채널 오디오 캡처 및 전송 제로카피

내장 ADC는 12-bit/500ksps로 96kHz/24-bit 오디오 캡처에 부족하다. 외부 I2S ADC(예: PCM1808, CS5343)가 필요하다.


하드웨어 설정

I2S ADC 연결

RP2350 GPIO               I2S ADC (예: PCM1808 / CS5343)
───────────────           ────────────────────────────────
GPIO10 (PIO SCK)  ─────   BCK (비트 클록)
GPIO11 (PIO WS)   ─────   LRCK (워드 선택)
GPIO12 (PIO DIN)  ─────   DOUT (ADC 데이터 출력)
3V3               ─────   VDD
GND               ─────   GND
GPIO13 (GPIO)     ─────   /RESET 또는 FORMAT 선택

PIO 상태 머신을 96kHz, 24-bit I2S 슬레이브 모드로 설정한다. SDK 플랫폼 레이어는 DMA 더블 버퍼링을 사용하여 CPU 개입 없이 캡처된 프레임을 오디오 링 버퍼로 전송한다.

LTE 모뎀 (UART0) — BG770A

RP2350 GPIO               LTE 모뎀
───────────────           ─────────────────
GPIO0  (UART0_TX) ─────   RXD
GPIO1  (UART0_RX) ─────   TXD
GPIO2  (GPIO)     ─────   PWRKEY
GPIO3  (GPIO)     ─────   RESET_N
VSYS (5V)         ─────   VCC (LDO 경유; 모뎀 데이터시트 확인)
GND               ─────   GND

센서 버스

버스 GPIO 주요 장치
I2C0 GPIO4 (SDA), GPIO5 (SCL) BME280 (온도/습도), LIS3DH (진동)
I2C1 GPIO6 (SDA), GPIO7 (SCL) MAG3110 (자기장)
SPI0 GPIO16–GPIO19 + CS 모터 ADC, 전류 센서

I2S를 위한 PIO

RP2350의 PIO(Programmable I/O) 서브시스템이 하드웨어에서 I2S 타이밍을 처리하여 CPU를 비트 뱅잉에서 해방한다:

// Rust (Embassy) — PCM1860에서 2-SM I2S 마스터 모드 캡처
// 전체 구현: sdk/examples/rp2350-full-hardware/src/main.rs 참조
use embassy_rp::pio::{Pio, Config as PioConfig, FifoJoin, ShiftConfig, ShiftDirection};

let Pio { mut common, sm0: mut sm_clock, sm1: mut sm_data, .. } =
    Pio::new(p.PIO0, Irqs);

// SM0: 클럭 생성기 — BCK (sideset) + LRCK (set), LRCK 반주기당 32 BCK
let clock_prg = pio_proc::pio_asm!(
    ".side_set 1",
    ".wrap_target",
    "    set pins, 0    side 0",    // LRCK=0, BCK low
    "    set x, 30      side 1",    // BCK high, x=30
    "left:",
    "    nop             side 0",
    "    jmp x--, left   side 1",
    "    set pins, 1    side 0",    // LRCK=1, BCK low
    "    set x, 30      side 1",
    "right:",
    "    nop             side 0",
    "    jmp x--, right  side 1",
    ".wrap",
);

// SM1: 데이터 수신기 — DOUT 샘플링, 32비트 워드를 RX FIFO에 자동 푸시
let data_prg = pio_proc::pio_asm!(
    ".wrap_target",
    "    nop",           // BCK low — 데이터 전환 중
    "    in pins, 1",    // BCK high — 안정된 DOUT 샘플링
    ".wrap",
);

// 두 SM 모두 동일한 클럭 분주기 공유 (150MHz / 12.288MHz ≈ 12.207)
// SM1은 FifoJoin::RxOnly로 8엔트리 FIFO 깊이 확보
// DMA 핑퐁: embassy_futures::join으로 DMA + CPU 데시메이션 오버랩
Legacy C equivalent
// PIO0에 I2S 수신 프로그램 로드
uint offset = pio_add_program(pio0, &i2s_rx_program);
i2s_rx_program_init(pio0, 0, offset,
    GPIO_I2S_SCK, GPIO_I2S_WS, GPIO_I2S_DIN,
    96000);  // 96kHz 샘플레이트

// DMA 채널 A: PIO FIFO → 핑 버퍼
// DMA 채널 B: PIO FIFO → 퐁 버퍼 (더블 버퍼)
// DMA 완료 IRQ에서 버퍼를 교체하고 xylolabs_audio_feed() 호출

DMA 더블 버퍼링은 오디오 데이터 대기에 CPU 사이클을 전혀 사용하지 않고 연속으로 캡처할 수 있다.


듀얼 코어 아키텍처

RP2350의 두 코어는 SDK의 오디오 + 네트워크 워크로드에 자연스럽게 매핑된다:

코어 역할 예산
Core 0 I2S 캡처, FIR 다운샘플 (96kHz→16kHz), XAP 인코딩, 링 버퍼 쓰기 ~80 MIPS
Core 1 센서 폴링, XMBP 조립, LTE-M1 TCP, xylolabs_tick(), 워치독 ~40 MIPS
// Rust (Embassy) - 권장
use embassy_rp::multicore::{spawn_core1, Stack};
use embassy_executor::Spawner;

static mut CORE1_STACK: Stack<4096> = Stack::new();

// Core 1: 네트워크 + 센서 루프
#[embassy_executor::task]
async fn network_task(stack: embassy_net::Stack<'static>) {
    let mut client = XylolabsClient::new(stack);
    client.set_api_key("xk_your_key");
    client.set_device_id(1);
    client.session_create("pico_node_1", &streams).await;

    loop {
        poll_sensors(&mut client).await;
        client.tick().await;
        Timer::after_millis(10).await;
    }
}

// Core 0: 오디오 캡처 루프
#[embassy_executor::task]
async fn audio_task(mut pio_rx: PioRx<'static>) {
    let mut buf = [0i16; AUDIO_BLOCK_SAMPLES];
    loop {
        pio_rx.read(&mut buf).await;
        xylolabs_audio_feed(&buf);
    }
}
Legacy C equivalent
// core1_entry: 네트워크 + 센서 루프
void core1_entry(void) {
    xylolabs_init(&g_platform);
    xylolabs_set_api_key("xk_your_key");
    xylolabs_set_device_id(1);
    xylolabs_session_create("pico_node_1", streams, stream_count);

    while (1) {
        poll_sensors();
        xylolabs_tick();
        sleep_ms(10);
    }
}

int main(void) {
    multicore_launch_core1(core1_entry);

    // Core 0: 오디오 캡처 루프
    while (1) {
        // DMA 핑퐁 IRQ 대기
        uint32_t notif = multicore_fifo_pop_blocking();
        int16_t *buf   = (int16_t *)(uintptr_t)notif;
        xylolabs_audio_feed(buf, AUDIO_BLOCK_SAMPLES);
    }
}

Cortex-M33에서의 XAP 코덱

XAP는 RP2350의 기본 권장 코덱이다:

XAP 및 XMBP는 Xylolabs Inc.의 특허 출원 중인 기술이다.

항목
압축비 ~10:1
CPU 비용 (순수 C) ~20 MIPS/ch
CPU 비용 (DSP 가속) ~14 MIPS/ch
4ch @96kHz 합계 ~56 MIPS (DSP)
RP2350 예산 300 MIPS
앱용 여유 ~244 MIPS

Cortex-M33 DSP 확장은 단일 사이클 32×32 MAC, 이중 16-bit SIMD(SMLAD), 포화 연산을 제공하여 순수 C 대비 XAP 인코딩 비용을 ~30% 절감한다. 자세한 벤치마크는 docs/CODEC-ANALYSIS.md를 참조한다.

대역폭 결과: XAP 80 kbps/채널 × 4ch = 320 kbps = 40 KB/s로 LTE-M1의 ~47 KB/s 예산 이내이다.


메모리 예산

영역 크기 설정 매크로
오디오 링 버퍼 32 KB XYLOLABS_AUDIO_RING_SIZE
XAP 배치 버퍼 ~4 KB XYLOLABS_XAP_BATCH_BYTES
XMBP 패킷 버퍼 16 KB XYLOLABS_XMBP_BUF_SIZE
HTTP 버퍼 4 KB XYLOLABS_HTTP_BUF_SIZE
메타데이터 누적 ~12 KB 26ch × 100 샘플 × f32
XAP 인코더 상태 (4ch) ~8 KB
스택 + SDK 내부 ~12 KB
SDK 합계 ~88 KB 520 KB의 17%
앱 사용 가능 ~432 KB

오디오를 위한 DMA 더블 버퍼링

// Rust (Embassy) - 권장
use embassy_rp::dma::{AnyChannel, Channel};

// Embassy는 비동기 읽기를 통해 DMA 더블 버퍼링을 자동으로 처리한다.
// PIO DMA 전송은 대기 중 실행기에 양보하고,
// 완료된 버퍼를 처리를 위해 반환한다.
#[embassy_executor::task]
async fn audio_capture(mut rx_dma: AnyChannel, pio_rx: &mut PioStateMachine<'static>) {
    let mut ping = [0i16; AUDIO_BLOCK_SAMPLES * AUDIO_CHANNELS];
    let mut pong = [0i16; AUDIO_BLOCK_SAMPLES * AUDIO_CHANNELS];
    let mut use_ping = true;

    loop {
        let buf = if use_ping { &mut ping } else { &mut pong };
        pio_rx.rx().dma_pull(&mut rx_dma, buf).await;
        xylolabs_audio_feed(buf);
        use_ping = !use_ping;
    }
}
Legacy C equivalent
// 연속 DMA 캡처를 위한 두 개의 핑퐁 버퍼
static int16_t s_audio_ping[AUDIO_BLOCK_SAMPLES * AUDIO_CHANNELS];
static int16_t s_audio_pong[AUDIO_BLOCK_SAMPLES * AUDIO_CHANNELS];
static volatile bool s_use_ping = true;

void dma_irq_handler(void) {
    dma_hw->ints0 = 1u << DMA_CHANNEL_AUDIO;

    // 다른 버퍼로 DMA 재시작
    int16_t *next = s_use_ping ? s_audio_pong : s_audio_ping;
    dma_channel_set_write_addr(DMA_CHANNEL_AUDIO, next, true);

    // Core 0에 완료된 버퍼 처리 신호 전송
    int16_t *done = s_use_ping ? s_audio_ping : s_audio_pong;
    multicore_fifo_push_blocking((uint32_t)(uintptr_t)done);
    s_use_ping = !s_use_ping;
}

이 패턴으로 제로카피 오디오 캡처가 가능하다: PIO가 DMA를 통해 하나의 버퍼를 채우는 동안 Core 0이 다른 버퍼를 인코딩한다.


Rust 빌드 (권장)

# 의존성 설치
rustup target add thumbv8m.main-none-eabihf
cargo install probe-rs-tools

# 빌드
cd sdk/examples/rp2350-sensor
cargo build --release

# 플래시
cargo run --release  # .cargo/config.toml을 통해 probe-rs 사용

모든 RP2350 예제는 sdk/examples/를 참고한다.


레거시 C 빌드 시스템

cmake_minimum_required(VERSION 3.13)
include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)

project(my_xylolabs_pico C CXX ASM)
pico_sdk_init()

add_subdirectory(sdk/c/pico)

add_executable(my_xylolabs_pico
    src/main.c
    src/platform_pico.c
    src/sensors.c
)

target_link_libraries(my_xylolabs_pico
    pico_stdlib
    pico_multicore
    hardware_pio
    hardware_dma
    hardware_i2c
    hardware_spi
    hardware_watchdog
    hardware_timer
    xylolabs_sdk
)

# 드래그 앤 드롭 플래시를 위한 UF2 생성
pico_add_extra_outputs(my_xylolabs_pico)

요구사항: - Pico SDK >= 2.0.0 (RP2350 지원) - CMake >= 3.13 - arm-none-eabi-gcc 툴체인


플랫폼 콜백 구현

// Rust (Embassy) - 권장
use embassy_rp::uart::{Uart, Config as UartConfig};
use embassy_rp::watchdog::Watchdog;
use embassy_time::Instant;

struct PicoPlatform {
    uart: Uart<'static, embassy_rp::peripherals::UART0>,
    watchdog: Watchdog,
}

impl PicoPlatform {
    async fn tcp_connect(&mut self, host: &str, port: u16) -> Result<(), XylolabsError> {
        let cmd = format!("AT+QIOPEN=1,0,\"TCP\",\"{}\",{},0,1\r\n", host, port);
        at_send_expect(&mut self.uart, &cmd, "CONNECT", 10_000).await
    }

    async fn tcp_send(&mut self, data: &[u8]) -> Result<(), XylolabsError> {
        at_tcp_send(&mut self.uart, data).await
    }

    fn get_time_us(&self) -> u64 {
        Instant::now().as_micros()
    }

    fn watchdog_feed(&mut self) {
        self.watchdog.feed();
    }
}
Legacy C equivalent
// platform_pico.c
#include "xylolabs/client.h"
#include "pico/stdlib.h"
#include "hardware/watchdog.h"
#include "hardware/timer.h"
#include "uart_at.h"  // LTE 모뎀용 AT 명령 드라이버

static xylolabs_err_t pico_tcp_connect(const char *host, uint16_t port) {
    char cmd[128];
    snprintf(cmd, sizeof(cmd),
        "AT+QIOPEN=1,0,\"TCP\",\"%s\",%u,0,1\r\n", host, port);
    return at_send_expect(cmd, "CONNECT", 10000) == AT_OK
        ? XYLOLABS_OK : XYLOLABS_ERR_NETWORK;
}

static uint64_t pico_get_time_us(void) {
    return time_us_64();
}

static void pico_sleep_ms(uint32_t ms) {
    sleep_ms(ms);
}

static void pico_watchdog_feed(void) {
    watchdog_update();
}

const xylolabs_platform_t g_platform = {
    .tcp_connect      = pico_tcp_connect,
    .tcp_send         = pico_tcp_send,
    .tcp_recv         = pico_tcp_recv,
    .tcp_close        = pico_tcp_close,
    .tcp_is_connected = pico_tcp_is_connected,
    .get_time_us      = pico_get_time_us,
    .sleep_ms         = pico_sleep_ms,
    .watchdog_feed    = pico_watchdog_feed,
};

LTE-M1 모뎀 통합

검증된 모뎀 및 권장 AT 명령 세트:

모뎀 인터페이스 비고
BG770A UART Cat-M1/NB1/EGPRS; PSM 및 eDRX 지원
BG770A UART Cat-M1/NB2; 간단한 AT 명령 세트
SIM7670G UART LTE-M1/NB2/GNSS 콤보

모뎀 부팅 시퀀스

// Rust (Embassy) - 권장
use embassy_rp::gpio::{Output, Level};
use embassy_time::Timer;

async fn modem_boot(pwrkey: &mut Output<'static>, uart: &mut Uart<'static>) {
    // 1. PWRKEY를 1.5초 동안 어서트하여 전원 켜기
    pwrkey.set_high();
    Timer::after_millis(1500).await;
    pwrkey.set_low();
    Timer::after_millis(5000).await; // 모뎀 네트워크 등록 대기

    // 2. AT 초기화 시퀀스
    at_send_expect(uart, "AT\r\n",             "OK", 1000).await.unwrap();
    at_send_expect(uart, "ATE0\r\n",           "OK", 1000).await.unwrap();
    at_send_expect(uart, "AT+CMEE=2\r\n",      "OK", 1000).await.unwrap();
    at_send_expect(uart, "AT+CEREG=1\r\n",     "OK", 1000).await.unwrap();
    at_send_expect(uart, "AT+CGDCONT=1,\"IP\",\"your.apn\"\r\n", "OK", 5000).await.unwrap();
    at_send_expect(uart, "AT+CGACT=1,1\r\n",   "OK", 30000).await.unwrap();
}
Legacy C equivalent
// 1. PWRKEY를 1.5초 동안 어서트하여 전원 켜기
gpio_put(GPIO_MODEM_PWRKEY, 1);
sleep_ms(1500);
gpio_put(GPIO_MODEM_PWRKEY, 0);
sleep_ms(5000);  // 모뎀 네트워크 등록 대기

// 2. AT 초기화 시퀀스
at_send_expect("AT\r\n",             "OK", 1000);
at_send_expect("ATE0\r\n",           "OK", 1000);  // 에코 끄기
at_send_expect("AT+CMEE=2\r\n",      "OK", 1000);  // 상세 오류
at_send_expect("AT+CEREG=1\r\n",     "OK", 1000);  // 네트워크 등록 URC
at_send_expect("AT+CGDCONT=1,\"IP\",\"your.apn\"\r\n", "OK", 5000);
at_send_expect("AT+CGACT=1,1\r\n",   "OK", 30000); // PDP 활성화

전력 관리

배치 사이 슬립

RP2350은 Dormant 모드(GPIO 웨이크업 딥 슬립)와 Sleep 모드(타이머 웨이크업)를 지원한다. 연속 스트리밍에는 틱 사이에 슬립을 사용한다:

// Rust (Embassy) - 권장
// Embassy 실행기가 비동기 폴링 사이에 자동으로 WFE에 진입한다.
// PLL 유지; 타이머 인터럽트로 웨이크업.
Timer::after_millis(10).await;
Legacy C equivalent
// 경량 슬립: PLL 유지, 타이머로 웨이크업
best_effort_wfe_or_timeout(make_timeout_time_ms(10));

워치독 설정

SDK는 매 xylolabs_tick() 호출 시 워치독을 피드한다. 하드웨어 워치독 설정:

// Rust (Embassy) - 권장
use embassy_rp::watchdog::Watchdog;

// 8초 워치독 타임아웃
let mut watchdog = Watchdog::new(p.WATCHDOG);
watchdog.start(Duration::from_millis(8_000));
// SDK가 매 틱마다 워치독을 피드
watchdog.feed();
Legacy C equivalent
// 8초 워치독 타임아웃
watchdog_enable(XYLOLABS_WATCHDOG_TIMEOUT_MS, true);
// pause_on_debug = true: 디버깅 중 오작동 리셋 방지

Rust (권장)

Rust는 새로운 RP2350 프로젝트의 권장 언어이다. Embassy 기반 Rust 예제는 컴파일 타임 타입 안전성을 유지하면서 비동기 태스크 관리를 구현한다.

cd sdk/examples/rp2350-audio
cargo build --release
# probe-rs를 통해 결과 ELF를 플래시한다
probe-rs run --chip RP2350 target/thumbv8m.main-none-eabihf/release/rp2350-audio

Rust 예제는 C 예제와 동일한 듀얼 코어 오디오 + 센서 스트리밍을 구현하지만 원시 C 콜백 대신 Embassy 태스크를 사용한다. 자세한 빌드 지침과 10가지 MCU 변형은 sdk/examples/README.md를 참고한다.


C 예제 (대안) ### 기존 예제 `sdk/c/pico/examples/` 디렉토리에 바로 실행 가능한 예제가 있다: | 예제 | 설명 | |------|------| | `continuous_stream.c` | 전체 듀얼 코어 오디오(XAP) + 26ch 센서 스트리밍 | | `periodic_sampling.c` | 주기적 LTE-M1 업로드를 사용하는 센서 전용 노드 | | `audio_upload.c` | 대역폭 측정이 포함된 XAP 오디오 인코딩 데모 |

문제 해결

증상 가능한 원인 해결 방법
워치독 리셋 루프 xylolabs_tick() 호출 빈도 부족 틱이 최소 4초마다 실행되는지 확인
오디오 링 오버플로우 Core 1 네트워크가 너무 오래 블록 XYLOLABS_AUDIO_RING_SIZE 증가 또는 배치 간격 축소
모뎀 무응답 UART 보드레이트 불일치 또는 PWRKEY 타이밍 보드레이트(기본 115200) 및 PWRKEY 펄스 시간 확인
XAP 인코딩 느림 클록 너무 낮거나 최적화 플래그 누락 -O2/-O3-mcpu=cortex-m33+nodsp with CMSIS-DSP 확인
I2S 데이터 손상 PIO 클록 분주기 오계산 재계산: clkdiv = sys_clk / (96000 * 64 * 2)
전송 지연 높음 TCP 전송이 Core 1 블록 뮤텍스로 보호된 링 핸드오프와 함께 TCP를 별도 태스크로 이동

추가 자료

  • docs/FEASIBILITY-RP2350.md — 전체 대역폭 및 CPU 실현 가능성 분석
  • docs/CODEC-ANALYSIS.md — XAP vs ADPCM 상세 벤치마크 결과
  • sdk/c/pico/README.md — SDK 파일 목록 및 빠른 빌드 안내
  • Pico SDK 문서
  • RP2350 데이터시트