STM32 플랫폼 가이드¶
STM32 마이크로컨트롤러용 Xylolabs SDK 통합 가이드이다.
지원 대상¶
| MCU | 코어 | 클록 | Flash | RAM | 오디오 지원 |
|---|---|---|---|---|---|
| STM32F103C8 (Blue Pill) | Cortex-M3 | 72 MHz | 64 KB | 20 KB | 센서 전용 (ADPCM 1–2ch 선택적) |
| STM32F411CE (Black Pill) | Cortex-M4F + FPU | 100 MHz | 512 KB | 128 KB | 4ch XAP @48kHz |
| STM32WB55RG (Nucleo WB) | Cortex-M4F + FPU | 64 MHz | 1 MB | 256 KB | 2ch XAP @48kHz (BLE 오프로드) |
하드웨어 설정¶
I2S ADC 연결 (F411 / WB55)¶
SDK는 스테레오 I2S ADC(예: ICS-43434 또는 INMP441 마이크 2개, 좌/우 채널)로부터 인터리브된 PCM 샘플을 입력받는다.
MCU I2S 주변장치 ADC (예: ICS-43434)
───────────────────── ──────────────────────
I2S_CK (PB13) ───── BCK
I2S_WS (PB12) ───── WS / LRCK
I2S_SD (PB15) ───── SD (데이터 입력)
3V3 ───── VDD
GND ───── GND
PB14 (GPIO) ───── L/R 선택 (GND=좌, 3V3=우)
4채널 오디오의 경우, 두 개의 I2S 버스를 연결하거나(또는 L/R 선택을 다르게 설정한 하나의 버스) 펌웨어에서 병합한다.
LTE 모뎀 (UART) — SIM7080G / BG96¶
MCU UART2 LTE 모뎀
────────────────── ─────────────────
PA2 (TX) ───── RXD
PA3 (RX) ───── TXD
PA0 (GPIO) ───── PWRKEY
PA1 (GPIO) ───── RESET_N
3V3 / VBAT ───── VCC (모뎀 데이터시트 전압 확인)
GND ───── GND
센서 버스¶
| 버스 | 핀 (F411 예시) | 주요 장치 |
|---|---|---|
| I2C1 | PB6 (SCL), PB7 (SDA) | BME280 (온도/습도), LIS3DH (진동) |
| SPI1 | PA5/PA6/PA7 + CS | MAX31865 (RTD), ADS1256 (ADC) |
| ADC1 | PA0–PA7 | 전류 센서, 전압 분배기 |
Rust 빌드 (권장)¶
# 의존성 설치
rustup target add thumbv7em-none-eabihf # F411/WB55용
rustup target add thumbv7m-none-eabi # F103용
cargo install probe-rs-tools
# 빌드
cd sdk/examples/stm32f411-audio
cargo build --release
# ST-Link를 통해 플래시
cargo run --release
모든 STM32 예제는 sdk/examples/를 참고한다.
레거시 C 빌드 시스템¶
옵션 A: STM32CubeIDE¶
- 대상 MCU(F411CE 또는 F103C8)에 맞는 새 STM32 프로젝트를 생성한다.
- CubeMX에서 I2S, UART2, I2C1, SPI1 주변장치를 활성화한다.
- SDK를 링크된 폴더로 추가한다:
- 프로젝트 우클릭 → Properties → C/C++ General → Paths and Symbols
- 인클루드 경로에
sdk/c/common/include추가 - 소스 폴더에
sdk/c/common/src,sdk/c/stm32/src추가 - 프로젝트 속성에 컴파일 정의 추가: F103용 (ADPCM 레거시 경로):
옵션 B: CMake + arm-none-eabi-gcc¶
cmake_minimum_required(VERSION 3.22)
project(my_device C)
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_C_FLAGS "-mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard -Os")
add_subdirectory(sdk/c/stm32)
add_executable(my_device
src/main.c
src/platform_stm32.c
)
target_link_libraries(my_device PRIVATE xylolabs_stm32)
target_include_directories(my_device PRIVATE sdk/c/common/include)
F103용 (Cortex-M3, FPU 없음):
set(CMAKE_C_FLAGS "-mcpu=cortex-m3 -mthumb -Os")
target_compile_definitions(my_device PRIVATE
XYLOLABS_ENABLE_ADPCM=1
XYLOLABS_AUDIO_CODEC=XYLOLABS_CODEC_ADPCM
XYLOLABS_AUDIO_CHANNELS=0 # 센서 전용
)
메모리 예산¶
STM32F103 (20 KB RAM) — 센서 전용¶
| 영역 | 크기 |
|---|---|
| XMBP 패킷 버퍼 | 2 KB |
| HTTP 버퍼 | 1 KB |
| 메타데이터 링 | 2 KB |
| 스택 + HAL | ~8 KB |
| SDK 합계 | ~5 KB |
| 앱 사용 가능 | ~7 KB |
권장 오버라이드:
// Rust (Embassy) - 권장
// F103 (20 KB RAM) 메모리 제약 설정 — build.rs에서 설정
const AUDIO_CHANNELS: usize = 0;
const XMBP_BUF_SIZE: usize = 2048;
const HTTP_BUF_SIZE: usize = 1024;
const META_RING_SIZE: usize = 2048;
const SENSOR_CHANNELS: usize = 2;
const MOTOR_CHANNELS: usize = 0;
Legacy C equivalent
STM32F411 (128 KB RAM) — 풀 오디오¶
| 영역 | 크기 |
|---|---|
| 오디오 링 버퍼 | 32 KB |
| LC3 배치 버퍼 | ~4 KB |
| XMBP 패킷 버퍼 | 16 KB |
| HTTP 버퍼 | 4 KB |
| 메타데이터 링 | 8 KB |
| LC3 인코더 상태 (4ch) | ~4 KB |
| 스택 + HAL | ~12 KB |
| SDK 합계 | ~68 KB |
| 앱 사용 가능 | ~60 KB |
XAP 코덱 적합성¶
| 대상 | 코어 | 클록 | XAP 예산 | 적합성 |
|---|---|---|---|---|
| STM32F103 | 1x M3 | 72 MHz | ~72 MIPS | 4ch @48kHz에 부족 (~40 MIPS 필요); ADPCM 또는 센서 전용 사용 |
| STM32F411 | 1x M4F | 100 MHz | ~100 MIPS | 4ch @48kHz에 충분 (~40 MIPS); ~60 MIPS 앱용 여유 |
| STM32WB55 | 1x M4F | 64 MHz | ~64 MIPS | 2ch @48kHz에 충분 (~20 MIPS); M0+ 코어가 BLE 처리 |
F103 ADPCM 폴백: 오디오가 필요한 STM32F103 배포(1–2채널, 낮은 샘플레이트)에는 ADPCM 레거시 경로를 활성화한다. ADPCM은 채널당 <1 MIPS만 필요하여 M3에서도 사용 가능하다.
// Rust (Embassy) - 권장
// F103 ADPCM 폴백 설정 — build.rs 또는 Cargo.toml features에서 설정
const ENABLE_ADPCM: bool = true;
const AUDIO_CODEC: Codec = Codec::Adpcm;
const AUDIO_CHANNELS: usize = 2; // F103에서 1–2ch만
const AUDIO_SAMPLE_RATE: u32 = 8000;
Legacy C equivalent
Rust (F411 / WB55에 권장)¶
Rust는 Embassy 지원이 완숙한 새로운 STM32 프로젝트의 권장 언어이다(F411, WB55, U5 시리즈). Embassy 기반 Rust 예제는 비동기 태스크, 타입 안전 주변장치 액세스, 컴파일 타임 보장을 제공한다.
cd sdk/examples/stm32f411-audio # 또는 stm32wb55, stm32u585
cargo build --release
# probe-rs를 통해 플래시: probe-rs run --chip STM32F411CE target/thumbv7em-none-eabihf/release/stm32f411-audio
전체 아키텍처 개요와 대상별 빌드 지침은 sdk/examples/README.md를 참고한다.
주의: STM32F103(Cortex-M3, 20KB RAM)의 경우 극도로 제한된 메모리 환경과 이 대상에 대한 제한된 Embassy HAL 커버리지로 인해 C가 주요 권장사항이다. ADPCM 또는 센서 전용 모드로 C SDK를 사용한다.
C 예제 (대안)
### 예제: STM32F411 오디오 스트리밍 ### 1. 플랫폼 콜백 구현// platform_stm32f411.c
#include "xylolabs/client.h"
#include "stm32f4xx_hal.h"
static UART_HandleTypeDef *s_modem_uart;
static I2S_HandleTypeDef *s_i2s;
static xylolabs_err_t stm32_tcp_connect(const char *host, uint16_t port) {
// LTE 모뎀으로 AT+CIPOPEN 전송
char cmd[128];
snprintf(cmd, sizeof(cmd), "AT+CIPOPEN=0,\"TCP\",\"%s\",%u\r\n", host, port);
HAL_UART_Transmit(s_modem_uart, (uint8_t *)cmd, strlen(cmd), 1000);
// 모뎀 OK 응답 대기...
return XYLOLABS_OK;
}
static uint64_t stm32_get_time_us(void) {
return (uint64_t)HAL_GetTick() * 1000;
}
static void stm32_watchdog_feed(void) {
HAL_IWDG_Refresh(&hiwdg);
}
const xylolabs_platform_t g_platform = {
.tcp_connect = stm32_tcp_connect,
.tcp_send = stm32_tcp_send,
.tcp_recv = stm32_tcp_recv,
.tcp_close = stm32_tcp_close,
.tcp_is_connected = stm32_tcp_is_connected,
.get_time_us = stm32_get_time_us,
.sleep_ms = stm32_sleep_ms,
.watchdog_feed = stm32_watchdog_feed,
};
// Rust (Embassy) - 권장
use xylolabs_sdk::{XylolabsClient, Config, StreamDef};
use xylolabs_hal_stm32::Stm32Platform;
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
let platform = Stm32Platform::new(/* 모뎀 UART, 워치독 */);
let mut client = XylolabsClient::new(platform, Config::default());
client.set_api_key("xk_your_key_here");
client.set_device_id(1);
let streams = [
StreamDef::new(0, "vibration_x", ValueType::F32, "g", 100.0),
StreamDef::new(1, "temperature", ValueType::F32, "celsius", 1.0),
];
client.session_create("factory_floor_1", &streams).unwrap();
loop {
let samples = i2s_read(&mut i2s, &mut buf).await;
client.audio_feed(samples).unwrap();
client.meta_feed_f32(0, read_accel_x().await).unwrap();
client.meta_feed_f32(1, read_temp().await).unwrap();
client.tick().unwrap();
}
}
레거시 C 코드
// main.c
#include "xylolabs/client.h"
#include "platform_stm32f411.h"
static int16_t s_i2s_buf[256 * 4]; // 256 샘플 x 4ch 인터리브
int main(void) {
HAL_Init();
SystemClock_Config();
MX_I2S2_Init();
MX_UART2_Init();
MX_IWDG_Init();
xylolabs_init(&g_platform);
xylolabs_set_api_key("xk_your_key_here");
xylolabs_set_device_id(1);
xylolabs_stream_def_t streams[] = {
{ 0, "vibration_x", XMBP_VT_F32, "g", 100.0f },
{ 1, "temperature", XMBP_VT_F32, "celsius", 1.0f },
};
xylolabs_session_create("factory_floor_1", streams, 2);
while (1) {
// DMA로 I2S 주변장치에서 s_i2s_buf 채우기
HAL_I2S_Receive_DMA(&hi2s2, (uint16_t *)s_i2s_buf, 256 * 4);
xylolabs_audio_feed(s_i2s_buf, 256);
// 센서 데이터 공급
xylolabs_meta_feed_f32(0, read_accelerometer_x());
xylolabs_meta_feed_f32(1, read_temperature_bme280());
xylolabs_tick();
}
}
// Rust (Embassy) - 권장
use embassy_stm32::low_power::{stop_with_rtc, Executor};
// Embassy는 비동기 태스크 폴링 사이에 자동으로 STOP 모드에 진입한다.
// xylolabs 틱 이후, 대기 작업이 없으면 실행기가 다음 웨이크업까지 슬립한다.
loop {
client.tick().await;
// 대기 작업이 없으면 Embassy가 여기서 STOP 모드에 진입;
// RTC 알람 또는 EXTI 웨이크업이 자동으로 클록을 복원한다.
Timer::after_millis(10).await;
}
Legacy C equivalent
// Rust (Embassy) - 권장
use embassy_stm32::wdg::IndependentWatchdog;
// 8초 타임아웃으로 IWDG 설정
let mut wdg = IndependentWatchdog::new(p.IWDG, 8_000_000); // 마이크로초 단위 8초
wdg.unleash();
// SDK가 매 틱마다 워치독을 피드
wdg.pet();
Legacy C equivalent
// Rust (Embassy) - 권장
use embassy_stm32::rtc::{Rtc, RtcConfig};
// Embassy의 저전력 실행기가 RTC 웨이크업을 자동으로 사용한다.
// 초기화 시 RTC 설정:
let rtc = Rtc::new(p.RTC, RtcConfig::default());
// 실행기가 정확한 간격으로 STOP에서 깨어나도록 RTC 알람을 설정한다.
Legacy C equivalent
// Rust (Embassy) - 권장
use embassy_stm32::usart::{Uart, Config as UartConfig};
// TCP 소켓 설정을 위한 AT 명령 시퀀스
async fn modem_init(uart: &mut Uart<'static>) {
let init_cmds = [
("AT\r\n", "OK"), // 모뎀 응답 확인
("AT+CMEE=2\r\n", "OK"), // 상세 오류 코드 활성화
("AT+CNCFG=0,1,\"your.apn\"\r\n", "OK"), // APN 설정
("AT+CNACT=0,1\r\n", "OK"), // PDP 컨텍스트 활성화
];
for (cmd, expect) in init_cmds {
at_send_expect(uart, cmd, expect).await.unwrap();
}
}