Skip to content

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

  1. 대상 MCU(F411CE 또는 F103C8)에 맞는 새 STM32 프로젝트를 생성한다.
  2. CubeMX에서 I2S, UART2, I2C1, SPI1 주변장치를 활성화한다.
  3. SDK를 링크된 폴더로 추가한다:
  4. 프로젝트 우클릭 → Properties → C/C++ General → Paths and Symbols
  5. 인클루드 경로에 sdk/c/common/include 추가
  6. 소스 폴더에 sdk/c/common/src, sdk/c/stm32/src 추가
  7. 프로젝트 속성에 컴파일 정의 추가:
    XYLOLABS_PLATFORM_STM32
    
    F103용 (ADPCM 레거시 경로):
    XYLOLABS_ENABLE_ADPCM=1
    XYLOLABS_AUDIO_CODEC=XYLOLABS_CODEC_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
#define XYLOLABS_AUDIO_CHANNELS    0
#define XYLOLABS_XMBP_BUF_SIZE    2048
#define XYLOLABS_HTTP_BUF_SIZE    1024
#define XYLOLABS_META_RING_SIZE   2048
#define XYLOLABS_SENSOR_CHANNELS  2
#define XYLOLABS_MOTOR_CHANNELS   0

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
// 프로젝트 설정 또는 CMakeLists.txt에 추가:
#define XYLOLABS_ENABLE_ADPCM        1
#define XYLOLABS_AUDIO_CODEC         XYLOLABS_CODEC_ADPCM
#define XYLOLABS_AUDIO_CHANNELS      2   // F103에서 1–2ch만
#define XYLOLABS_AUDIO_SAMPLE_RATE   8000

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,
};
### 2. 메인 루프
// 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();
    }
}
--- ## 전력 관리 ### 배치 사이 STOP 모드 SDK는 오디오를 500ms마다, 메타데이터를 1000ms마다 전송한다. 전송 사이에 MCU는 STOP 모드로 진입할 수 있다:
// 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
// xylolabs_tick() 이후, 다음 전송까지 여유가 있을 때
uint32_t next_send_ms = xylolabs_next_send_ms();
if (next_send_ms > 50) {
    HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
    // RTC 웨이크업 또는 EXTI가 클록을 복원하고 재개
    SystemClock_Config();  // STOP 이후 PLL 복원
}
### IWDG 설정 SDK는 매 `xylolabs_tick()` 호출 시 워치독을 피드한다. IWDG를 8초 타임아웃으로 설정:
// 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
// CubeMX IWDG 설정:
// 프리스케일러: IWDG_PRESCALER_256
// 리로드: 0xFFF  → F411에서 ~32초 (LSI ~32kHz / 256 / 4096)
// XYLOLABS_WATCHDOG_TIMEOUT_MS에 맞게 조정
### RTC 웨이크업 타이머 정확한 간격으로 STOP 모드에서 깨어나려면 RTC 웨이크업을 사용:
// Rust (Embassy) - 권장
use embassy_stm32::rtc::{Rtc, RtcConfig};

// Embassy의 저전력 실행기가 RTC 웨이크업을 자동으로 사용한다.
// 초기화 시 RTC 설정:
let rtc = Rtc::new(p.RTC, RtcConfig::default());
// 실행기가 정확한 간격으로 STOP에서 깨어나도록 RTC 알람을 설정한다.
Legacy C equivalent
HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 1, RTC_WAKEUPCLOCK_CK_SPRE_16BITS);
// ISR이 STOP을 종료, xylolabs_tick()이 실행, 이후 다시 STOP 진입
--- ## LTE-M1 모뎀 통합 SDK는 UART를 통해 AT 명령으로 LTE 모뎀과 통신한다. 검증된 모뎀: | 모뎀 | 인터페이스 | 비고 | |------|-----------|------| | SIM7080G | UART | Cat-M1/NB2; 기본 115200 baud | | BG96 | UART | Cat-M1/NB1/EGPRS; PSM 지원 | | BC660K | UART | NB-IoT 중심 | ### 모뎀 초기화 시퀀스 (SIM7080G 예시)
// 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();
    }
}
Legacy C equivalent
static const char *s_init_cmds[] = {
    "AT\r\n",                          // 모뎀 응답 확인
    "AT+CMEE=2\r\n",                   // 상세 오류 코드 활성화
    "AT+CNCFG=0,1,\"your.apn\"\r\n",  // APN 설정
    "AT+CNACT=0,1\r\n",               // PDP 컨텍스트 활성화
    NULL
};
### BG96 PSM (전력 절약 모드)
// Rust (Embassy) - 권장
// PSM 활성화: T3412=1h, T3324=10s 활성 윈도우
at_send_expect(uart, "AT+CPSMS=1,,,\"00100001\",\"00000001\"\r\n", "OK").await.unwrap();
// 모뎀이 스케줄에 따라 깨어나 SDK가 배치를 연결·전송 후 다시 슬립
Legacy C equivalent
// PSM 활성화: T3412=1h, T3324=10s 활성 윈도우
AT+CPSMS=1,,,"00100001","00000001"
// 모뎀이 스케줄에 따라 깨어나 SDK가 배치를 연결·전송 후 다시 슬립
--- ## 핀아웃 참조 상세 핀아웃 다이어그램은 다음 SVG를 참조한다: - `docs/diagrams/stm32f411-pinout.svg` — Black Pill I2S + UART + I2C/SPI 연결 - `docs/diagrams/stm32f103-pinout.svg` — Blue Pill 센서 전용 배선 --- ## 문제 해결 | 증상 | 가능한 원인 | 해결 방법 | |------|-----------|----------| | 초기화 중 Hardfault | 스택 오버플로우 (F103) | `XYLOLABS_XMBP_BUF_SIZE`, `XYLOLABS_AUDIO_RING_SIZE` 축소 | | 오디오 품질 저하 | I2S 클록 불일치 | I2S 프리스케일러가 `XYLOLABS_AUDIO_ORIGINAL_RATE` (96kHz)와 일치하는지 확인 | | 모뎀 타임아웃 | UART DMA 충돌 | 모뎀과 I2S에 별도 DMA 채널 또는 인터럽트 모드 사용 | | LC3 인코딩 멈춤 | F103에 FPU 없음 | `XYLOLABS_ENABLE_ADPCM=1` 설정 또는 센서 전용 모드 사용 | | 높은 지연 | STOP 모드 과도 사용 | 웨이크업 빈도 증가 또는 오디오 대상에서 STOP 비활성화 |