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
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
워치독 설정¶
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
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 데이터시트