Skip to content

Split View: Seeed Studio XIAO nRF52840 완전 가이드 — BLE IoT 프로젝트 실전

|

Seeed Studio XIAO nRF52840 완전 가이드 — BLE IoT 프로젝트 실전

XIAO nRF52840

이 글은 이 시리즈의 소형 BLE 엣지 디바이스 편이다. XIAO nRF52840의 장점은 초소형 폼팩터, BLE, 저전력, TinyML 가능성을 한 보드에 묶어 빠르게 실험할 수 있다는 점이다. 센서 노드와 웨어러블 중심으로 보고 싶다면 여기서 시작하고, PID 제어와 자율 비행처럼 더 큰 시스템을 다루고 싶다면 Arduino + Raspberry Pi 드론 & 제어 시스템 만들기 완전 가이드를 다음 글로 읽으면 된다.

들어가며

XIAO nRF52840 — 21mm × 17.5mm 크기에 BLE 5.0, 6축 IMU, 마이크, 256KB RAM을 가진 초소형 보드. 웨어러블, IoT 센서, TinyML 디바이스에 최적입니다.

스펙 비교

항목XIAO nRF52840XIAO nRF52840 SenseXIAO ESP32C3
프로세서ARM Cortex-M4F 64MHzARM Cortex-M4F 64MHzRISC-V 160MHz
RAM256KB256KB400KB
Flash1MB + 2MB QSPI1MB + 2MB QSPI4MB
무선BLE 5.0BLE 5.0Wi-Fi + BLE 5.0
IMU✅ LSM6DS3TR-C
마이크✅ PDM
배터리리튬 충전 회로 내장리튬 충전 회로 내장
크기21×17.5mm21×17.5mm21×17.5mm
가격~$5.99~$15.99~$4.99

개발 환경 설정

Arduino IDE

1. 보드 매니저 URL 추가:
   https://files.seeedstudio.com/arduino/package_seeeduino_boards_index.json

2. 보드 매니저 → "Seeed nRF52 mbed-enabled Boards" 설치

3. 보드 선택: "Seeed XIAO BLE - nRF52840"

4. ⚠️ 부트로더 모드 진입: RST2번 빠르게 터치
   → 오렌지 LED 페이딩 = 부트로더 모드

BLE (Bluetooth Low Energy) 프로그래밍

BLE 기본 개념

BLE 구조:
├── GAP (Generic Access Profile) — 연결 관리
│   ├── Peripheral (서버): 데이터 제공 (센서)
│   └── Central (클라이언트): 데이터 요청 (스마트폰)
└── GATT (Generic Attribute Profile) — 데이터 구조
    ├── Service: 기능 그룹 (: 온도 서비스)
    │   ├── Characteristic: 데이터 포인트 (: 온도 값)
    │   │   ├── Value: 실제 데이터
    │   │   ├── Properties: Read/Write/Notify
    │   │   └── Descriptor: 메타데이터
    │   └── Characteristic: ...
    └── Service: ...

BLE 센서 서버 (Peripheral)

#include <ArduinoBLE.h>

// 커스텀 서비스 + 특성 정의
BLEService envService("181A");  // Environmental Sensing

BLEFloatCharacteristic tempChar("2A6E", BLERead | BLENotify);
BLEFloatCharacteristic humChar("2A6F", BLERead | BLENotify);
BLEByteCharacteristic ledChar("2A57", BLERead | BLEWrite);

void setup() {
    Serial.begin(115200);
    BLE.begin();

    // 디바이스 설정
    BLE.setLocalName("XIAO-Sensor");
    BLE.setAdvertisedService(envService);

    // 특성 추가
    envService.addCharacteristic(tempChar);
    envService.addCharacteristic(humChar);
    envService.addCharacteristic(ledChar);
    BLE.addService(envService);

    // 초기값
    tempChar.writeValue(0.0f);
    humChar.writeValue(0.0f);
    ledChar.writeValue(0);

    // LED 제어 콜백
    ledChar.setEventHandler(BLEWritten, onLedWrite);

    BLE.advertise();
    Serial.println("BLE Sensor Ready!");
}

void onLedWrite(BLEDevice central, BLECharacteristic characteristic) {
    byte value = ledChar.value();
    digitalWrite(LED_BUILTIN, value ? LOW : HIGH);
    Serial.print("LED: "); Serial.println(value ? "ON" : "OFF");
}

void loop() {
    BLEDevice central = BLE.central();

    if (central) {
        Serial.print("Connected: "); Serial.println(central.address());

        while (central.connected()) {
            // 센서 읽기 (예시: 아날로그)
            float temp = analogRead(A0) * 0.1;  // 실제로는 DHT22 등 사용
            float hum = analogRead(A1) * 0.1;

            tempChar.writeValue(temp);
            humChar.writeValue(hum);

            delay(1000);
        }
        Serial.println("Disconnected");
    }
}

Python BLE 클라이언트 (Central)

import asyncio
from bleak import BleakClient, BleakScanner
import struct

DEVICE_NAME = "XIAO-Sensor"
TEMP_UUID = "00002a6e-0000-1000-8000-00805f9b34fb"
HUM_UUID = "00002a6f-0000-1000-8000-00805f9b34fb"
LED_UUID = "00002a57-0000-1000-8000-00805f9b34fb"

async def main():
    # 스캔
    print("Scanning...")
    device = await BleakScanner.find_device_by_name(DEVICE_NAME)
    if not device:
        print("Device not found!")
        return

    # 연결
    async with BleakClient(device) as client:
        print(f"Connected: {device.address}")

        # Notify 구독
        def on_temp(sender, data):
            temp = struct.unpack('<f', data)[0]
            print(f"  Temperature: {temp:.1f}°C")

        await client.start_notify(TEMP_UUID, on_temp)

        # LED 켜기
        await client.write_gatt_char(LED_UUID, bytes([1]))

        await asyncio.sleep(30)  # 30초 모니터링

asyncio.run(main())

저전력 설계

#include <nrf_power.h>

// 딥 슬립 모드 (System OFF — 0.5μA!)
void enterDeepSleep(int wakeupPin) {
    // 웨이크업 핀 설정
    nrf_gpio_cfg_sense_input(
        digitalPinToInterrupt(wakeupPin),
        NRF_GPIO_PIN_PULLUP,
        NRF_GPIO_PIN_SENSE_LOW
    );

    // System OFF
    NRF_POWER->SYSTEMOFF = 1;
    // 여기서 멈춤 — 웨이크업 시 리부팅
}

// 경량 슬립 (System ON + WFE — 1.5μA)
void lightSleep(uint32_t ms) {
    delay(ms);  // WFE 기반, BLE 연결 유지
}

// 배터리 수명 계산:
// 110mAh LiPo, 평균 소비 50μA (슬립 주기적 BLE 광고)
// 수명 = 110mAh / 0.05mA = 2,200시간 = ~91일!

센서 메시 네트워크

XIAO #1 (거실)          XIAO #2 (침실)          XIAO #3 (주방)
  온도/습도                조도                   가스 센서
    │                      │                      │
    └──── BLE ─────────────┴──── BLE ─────────────┘
                    Raspberry Pi (Gateway)
                    MQTTHome Assistant
                      대시보드 / 알림

TinyML (XIAO Sense)

// 내장 IMU로 제스처 인식!
#include <LSM6DS3.h>
#include <gesture_model.h>  // TensorFlow Lite 모델

LSM6DS3 imu(I2C_MODE, 0x6A);
tflite::MicroInterpreter* interpreter;

void loop() {
    float ax, ay, az, gx, gy, gz;
    imu.readAcceleration(ax, ay, az);
    imu.readGyroscope(gx, gy, gz);

    // 모델 입력
    input->data.f[0] = ax; input->data.f[1] = ay; input->data.f[2] = az;
    input->data.f[3] = gx; input->data.f[4] = gy; input->data.f[5] = gz;

    interpreter->Invoke();

    // 제스처 판별
    int gesture = output->data.f[0] > 0.8 ? WAVE :
                  output->data.f[1] > 0.8 ? PUNCH : IDLE;
    Serial.println(gestureName[gesture]);
}

이 임베디드 실전 시리즈에서 다음에 읽을 글


📝 퀴즈 — XIAO nRF52840 & BLE IoT (클릭해서 확인!)

Q1. BLE에서 Peripheral과 Central의 차이는? ||Peripheral(서버): 데이터를 제공하고 광고하는 쪽 (센서). Central(클라이언트): 스캔하고 연결하여 데이터를 요청하는 쪽 (스마트폰)||

Q2. GATT의 Service, Characteristic, Descriptor 관계는? ||Service: 기능 그룹 (온도 서비스). Characteristic: 실제 데이터 포인트 (온도 값). Descriptor: 메타데이터 (단위, 설명). Service > Characteristic > Descriptor 계층 구조||

Q3. XIAO nRF52840의 System OFF 모드 전류는? ||0.5μA. 110mAh 배터리로 약 91일 동작 가능. 웨이크업 시 리부팅됨 (RAM 내용 소실)||

Q4. BLE Notify와 Read의 차이는? ||Read: Central이 요청할 때만 값 전송 (폴링). Notify: 값이 변경되면 Peripheral이 자동으로 푸시 (이벤트 기반). Notify가 전력/지연 면에서 효율적||

Seeed Studio XIAO nRF52840 Complete Guide — BLE IoT Projects in Practice

XIAO nRF52840

This is the compact BLE edge-device track in the series. The point of XIAO nRF52840 is that it lets you prototype BLE, low-power behavior, and TinyML on a very small board with minimal setup. Start here if your focus is sensor nodes and wearables. If you want a larger system that includes PID control, sensor fusion, and autonomy, continue with Complete Guide to Building a Drone & Control System with Arduino + Raspberry Pi.

Introduction

XIAO nRF52840 — an ultra-compact board measuring 21mm x 17.5mm with BLE 5.0, 6-axis IMU, microphone, and 256KB RAM. It is ideal for wearables, IoT sensors, and TinyML devices.

Spec Comparison

ItemXIAO nRF52840XIAO nRF52840 SenseXIAO ESP32C3
ProcessorARM Cortex-M4F 64MHzARM Cortex-M4F 64MHzRISC-V 160MHz
RAM256KB256KB400KB
Flash1MB + 2MB QSPI1MB + 2MB QSPI4MB
WirelessBLE 5.0BLE 5.0Wi-Fi + BLE 5.0
IMUNoYes (LSM6DS3TR-C)No
MicNoYes (PDM)No
BatteryBuilt-in Li chargerBuilt-in Li chargerNo
Size21x17.5mm21x17.5mm21x17.5mm
Price~$5.99~$15.99~$4.99

Development Environment Setup

Arduino IDE

1. Add Board Manager URL:
   https://files.seeedstudio.com/arduino/package_seeeduino_boards_index.json

2. Board ManagerInstall "Seeed nRF52 mbed-enabled Boards"

3. Select Board: "Seeed XIAO BLE - nRF52840"

4. Warning: Enter bootloader mode: quickly double-tap the RST pin
Orange LED fading = bootloader mode

BLE (Bluetooth Low Energy) Programming

BLE Fundamentals

BLE Structure:
├── GAP (Generic Access Profile)Connection management
│   ├── Peripheral (Server): Provides data (sensor)
│   └── Central (Client): Requests data (smartphone)
└── GATT (Generic Attribute Profile)Data structure
    ├── Service: Functional group (e.g., Temperature Service)
    │   ├── Characteristic: Data point (e.g., temperature value)
    │   │   ├── Value: Actual data
    │   │   ├── Properties: Read/Write/Notify
    │   │   └── Descriptor: Metadata
    │   └── Characteristic: ...
    └── Service: ...

BLE Sensor Server (Peripheral)

#include <ArduinoBLE.h>

// Custom service + characteristic definitions
BLEService envService("181A");  // Environmental Sensing

BLEFloatCharacteristic tempChar("2A6E", BLERead | BLENotify);
BLEFloatCharacteristic humChar("2A6F", BLERead | BLENotify);
BLEByteCharacteristic ledChar("2A57", BLERead | BLEWrite);

void setup() {
    Serial.begin(115200);
    BLE.begin();

    // Device configuration
    BLE.setLocalName("XIAO-Sensor");
    BLE.setAdvertisedService(envService);

    // Add characteristics
    envService.addCharacteristic(tempChar);
    envService.addCharacteristic(humChar);
    envService.addCharacteristic(ledChar);
    BLE.addService(envService);

    // Initial values
    tempChar.writeValue(0.0f);
    humChar.writeValue(0.0f);
    ledChar.writeValue(0);

    // LED control callback
    ledChar.setEventHandler(BLEWritten, onLedWrite);

    BLE.advertise();
    Serial.println("BLE Sensor Ready!");
}

void onLedWrite(BLEDevice central, BLECharacteristic characteristic) {
    byte value = ledChar.value();
    digitalWrite(LED_BUILTIN, value ? LOW : HIGH);
    Serial.print("LED: "); Serial.println(value ? "ON" : "OFF");
}

void loop() {
    BLEDevice central = BLE.central();

    if (central) {
        Serial.print("Connected: "); Serial.println(central.address());

        while (central.connected()) {
            // Read sensor (example: analog)
            float temp = analogRead(A0) * 0.1;  // In practice, use DHT22 etc.
            float hum = analogRead(A1) * 0.1;

            tempChar.writeValue(temp);
            humChar.writeValue(hum);

            delay(1000);
        }
        Serial.println("Disconnected");
    }
}

Python BLE Client (Central)

import asyncio
from bleak import BleakClient, BleakScanner
import struct

DEVICE_NAME = "XIAO-Sensor"
TEMP_UUID = "00002a6e-0000-1000-8000-00805f9b34fb"
HUM_UUID = "00002a6f-0000-1000-8000-00805f9b34fb"
LED_UUID = "00002a57-0000-1000-8000-00805f9b34fb"

async def main():
    # Scan
    print("Scanning...")
    device = await BleakScanner.find_device_by_name(DEVICE_NAME)
    if not device:
        print("Device not found!")
        return

    # Connect
    async with BleakClient(device) as client:
        print(f"Connected: {device.address}")

        # Subscribe to notifications
        def on_temp(sender, data):
            temp = struct.unpack('<f', data)[0]
            print(f"  Temperature: {temp:.1f}°C")

        await client.start_notify(TEMP_UUID, on_temp)

        # Turn on LED
        await client.write_gatt_char(LED_UUID, bytes([1]))

        await asyncio.sleep(30)  # Monitor for 30 seconds

asyncio.run(main())

Low-Power Design

#include <nrf_power.h>

// Deep sleep mode (System OFF — 0.5uA!)
void enterDeepSleep(int wakeupPin) {
    // Configure wakeup pin
    nrf_gpio_cfg_sense_input(
        digitalPinToInterrupt(wakeupPin),
        NRF_GPIO_PIN_PULLUP,
        NRF_GPIO_PIN_SENSE_LOW
    );

    // System OFF
    NRF_POWER->SYSTEMOFF = 1;
    // Halts here — reboots on wakeup
}

// Light sleep (System ON + WFE — 1.5uA)
void lightSleep(uint32_t ms) {
    delay(ms);  // WFE-based, maintains BLE connection
}

// Battery life calculation:
// 110mAh LiPo, average consumption 50uA (sleep with periodic BLE advertising)
// Life = 110mAh / 0.05mA = 2,200 hours = ~91 days!

Sensor Mesh Network

XIAO #1 (Living Room)   XIAO #2 (Bedroom)       XIAO #3 (Kitchen)
  Temp/Humidity            Light Level              Gas Sensor
    |                      |                      |
    └──── BLE ─────────────┴──── BLE ─────────────┘
                           |
                    Raspberry Pi (Gateway)
                           |
                    MQTTHome Assistant
                           |
                      Dashboard / Alerts

TinyML (XIAO Sense)

// Gesture recognition using the built-in IMU!
#include <LSM6DS3.h>
#include <gesture_model.h>  // TensorFlow Lite model

LSM6DS3 imu(I2C_MODE, 0x6A);
tflite::MicroInterpreter* interpreter;

void loop() {
    float ax, ay, az, gx, gy, gz;
    imu.readAcceleration(ax, ay, az);
    imu.readGyroscope(gx, gy, gz);

    // Model input
    input->data.f[0] = ax; input->data.f[1] = ay; input->data.f[2] = az;
    input->data.f[3] = gx; input->data.f[4] = gy; input->data.f[5] = gz;

    interpreter->Invoke();

    // Gesture classification
    int gesture = output->data.f[0] > 0.8 ? WAVE :
                  output->data.f[1] > 0.8 ? PUNCH : IDLE;
    Serial.println(gestureName[gesture]);
}

Quiz — XIAO nRF52840 and BLE IoT (Click to reveal!)

Q1. What is the difference between Peripheral and Central in BLE? ||Peripheral (server): The side that provides data and advertises (sensor). Central (client): The side that scans, connects, and requests data (smartphone)||

Q2. What is the relationship between Service, Characteristic, and Descriptor in GATT? ||Service: Functional group (Temperature Service). Characteristic: Actual data point (temperature value). Descriptor: Metadata (units, description). Hierarchical structure: Service > Characteristic > Descriptor||

Q3. What is the current draw in XIAO nRF52840's System OFF mode? ||0.5uA. Can run for approximately 91 days on a 110mAh battery. Reboots on wakeup (RAM contents are lost)||

Q4. What is the difference between BLE Notify and Read? ||Read: Value is sent only when Central requests it (polling). Notify: Peripheral automatically pushes when the value changes (event-driven). Notify is more efficient in terms of power and latency||