Files
2026-04-08 16:59:20 +09:00

20 KiB

VivaMyo BLE Application - 프로그램 아키텍처 문서

1. 프로젝트 개요

VivaMyo는 nRF52840 기반 BLE 의료 기기 펌웨어로, 방광 모니터링을 위한 NIRS(Near-Infrared Spectroscopy) 장치입니다.

1.1 주요 특징

  • MCU: Nordic nRF52840
  • 통신: BLE (Nordic UART Service)
  • 보안: LESC 페어링, Static Passkey
  • 센서: LED 48개, Photodetector, IMU (ICM42670P), 온도센서, 압력센서
  • 저장: EEPROM (설정값 암호화 저장)

2. 시스템 아키텍처

┌─────────────────────────────────────────────────────────────────────┐
│                          main.c                                      │
│                    (Application Entry Point)                         │
└──────────────────────────────┬──────────────────────────────────────┘
                               │
        ┌──────────────────────┼──────────────────────┐
        ▼                      ▼                      ▼
┌───────────────┐    ┌─────────────────┐    ┌─────────────────────┐
│   BLE Stack   │    │  Power Control  │    │   Timer Management  │
│  (ble_core.c) │    │ (power_ctrl.c)  │    │   (main_timer.c)    │
└───────┬───────┘    └─────────────────┘    └─────────────────────┘
        │
        ▼
┌───────────────────────────────────────────────────────────────────┐
│                        nus_data_handler()                          │
│                     (BLE Data Reception)                           │
└──────────────────────────────┬────────────────────────────────────┘
                               │
                               ▼
┌───────────────────────────────────────────────────────────────────┐
│                   received_command_process()                       │
│                        (cmd_parse.c)                               │
└──────────────────────────────┬────────────────────────────────────┘
                               │
        ┌──────────────────────┴──────────────────────┐
        ▼                                             ▼
┌───────────────────────┐                   ┌─────────────────────┐
│     Mt_parser         │                   │   Legacy Parser     │
│   (dr_cmd_parser)     │                   │   (cmd_parse.c)     │
│                       │                   │                     │
│  parser.c + cmd.c     │                   │  기존 명령어 처리   │
└───────────┬───────────┘                   └─────────────────────┘
            │
            ▼
┌───────────────────────────────────────────────────────────────────┐
│                     Command Handlers (cmd.c)                       │
│                                                                    │
│  Cmd_mta, Cmd_sta, Cmd_mcj, Cmd_msn, Cmd_mag ...                  │
└───────────────────────────────────────────────────────────────────┘

3. Mt_parser 모듈 구조

Mt_parser는 명령어 파싱과 디스패치를 담당하는 독립 모듈입니다.

3.1 파일 구성

파일 경로 설명
parser.h /mt_parser/parser.h 파서 인터페이스 정의
parser.c /mt_parser/parser.c CRC 검증, TAG 추출, 명령어 디스패치
cmd.h cmd/cmd.h 명령어 테이블 구조체 정의
cmd.c cmd/cmd.c 명령어 핸들러 구현 및 테이블
dr_util.h/c /mt_parser/dr_util/ BLE 응답 유틸리티 함수

3.2 핵심 구조체

// parser.h - 플랫폼 인터페이스
typedef struct {
    void (*log)(const char *fmt, ...);      // 로그 출력 함수
    void (*tx_bin)(const uint8_t *buf, uint16_t len);  // BLE 전송 함수
    bool crc_check;                          // CRC 검사 활성화 여부
} dr_platform_if_t;

// cmd.h - 파싱된 명령어 구조체
typedef struct {
    char    tag[5];               // 4글자 명령어 + NULL ("sta?")
    uint8_t data[CMD_MAX_DATA];   // TAG 이후 데이터
    uint8_t data_len;             // 데이터 길이
} ParsedCmd;

// cmd.h - 명령어 테이블 엔트리
typedef struct {
    char tag[5];                  // 명령어 TAG ("sta?")
    bool enabled;                 // 활성화 여부
    cmd_handler_t handler;        // 핸들러 함수 포인터
} CmdEntry;

3.3 전역 변수

extern dr_platform_if_t g_plat;   // 플랫폼 인터페이스
extern bool g_log_enable;         // 로그 활성화 플래그
extern CmdEntry g_cmd_table[];    // 명령어 테이블
extern const uint16_t g_cmd_count; // 명령어 개수

4. 명령어 처리 흐름

4.1 데이터 수신 경로

[BLE Central]
    │
    ▼ (NUS RX Characteristic Write)
[ble_core.c:nus_data_handler()]
    │
    ▼
[cmd_parse.c:received_command_process(data, CMD_BLE, length)]
    │
    ├─► [Mt_parser 초기화] (최초 1회)
    │   g_plat.log = log_printf
    │   g_plat.tx_bin = binary_tx_handler
    │   g_plat.crc_check = true
    │
    ▼
[parser.c:dr_cmd_parser(data, length)]
    │
    ├─► [CRC16 검증] → 실패시 "crc!" 응답
    │
    ├─► [TAG 추출] → 4글자 명령어 파싱
    │
    ▼
[dr_cmd_dispatch()] → g_cmd_table 검색
    │
    ├─► [명령어 발견 + 활성화] → handler() 호출
    │
    ├─► [명령어 발견 + 비활성화] → return 0
    │
    └─► [명령어 미발견] → return 0 (Legacy 파서로 fallback)

4.2 CRC16 검증

// 패킷 구조: [TAG(4)] [DATA(N)] [CRC_LO] [CRC_HI]
// CRC 계산: TAG + DATA 영역
// CRC 위치: 패킷 마지막 2바이트 (Little Endian)

static bool dr_crc16_check_packet(const uint8_t *packet, uint32_t packet_len)
{
    uint32_t data_len = packet_len - 2;
    uint16_t expected_crc = (uint16_t)packet[packet_len - 2]
                          | ((uint16_t)packet[packet_len - 1] << 8);
    return dr_crc16_check(packet, data_len, expected_crc);
}

5. 명령어 테이블 (cmd.c)

5.1 명령어 카테고리

카테고리 명령어 설명
A. Device Status mta?, sta?, str?, mqq? 디바이스 상태 제어/조회
B. AGC/Gain mag?, sag?, sar? Auto Gain Control
C. LED DP Value ssa?, sab?, ssb?, srb? LED 밝기 설정/조회
D. LED On/Off ssc?, ssd?, sse?, ssf?, sif?, ssg? LED/Gain 제어
E. Simple PD ssh? 간단한 PD 측정
F. M48 Full mcj?, scj?, sdj?, sej?, sfj?, ssj?, szj? 48채널 PD 측정
G. IMM ADC saj? Immediate ADC 측정
H. PD Settings ssk?, srk?, ssl?, srl? ADC 횟수/지연 설정
I. Sensors msn?, ssn?, spn?, sso?, ssp? 배터리/압력/온도/IMU
J. System ssq?, ssr?, sss?, sst?, ssv? 전원/리셋/버전
K. Serial/Passkey ssz?, spz?, sqz?, srz? 시리얼번호/패스키
L. EEPROM Array sez?, sfz?, sgz? EEPROM 배열 R/W
M. HW/Life Cycle siz?, shz?, sxz?, syz? HW번호/사용횟수
N. Debug cmd? GPIO 테스트

5.2 명령어 상세 테이블

CmdEntry g_cmd_table[] = {
    /* Debug */
    { "cmd?", true,  Cmd_cmd },   // GPIO 테스트

    /* A. Device Status */
    { "mta?", true,  Cmd_mta },   // Device Activate/Sleep (신규)
    { "sta?", true,  Cmd_sta },   // Device Activate/Sleep (호환)
    { "str?", false, Cmd_str },   // Status Read
    { "mqq?", true,  Cmd_mqq },   // Quick Measurement

    /* B. AGC / Gain Measurement */
    { "mag?", true,  Cmd_mag },   // Full AGC (신규)
    { "sag?", true,  Cmd_mag },   // Full AGC (호환)
    { "sar?", false, Cmd_sar },   // Read LED-PD Gain Array

    /* C. LED Power / DP Value */
    { "ssa?", false, Cmd_ssa },   // Single LED Power Read
    { "sab?", false, Cmd_sab },   // All LED AGC Data Read
    { "ssb?", false, Cmd_ssb },   // Single LED Power Write
    { "srb?", false, Cmd_srb },   // Read All 48 LED DP

    /* D. LED On/Off & Gain Control */
    { "ssc?", false, Cmd_ssc },   // LED On/Off (index 0-47, 99=off)
    { "ssd?", false, Cmd_ssd },   // AGC Switch On/Off
    { "sse?", false, Cmd_sse },   // Measure DAC Voltage
    { "ssf?", false, Cmd_ssf },   // Set LED DAC Value
    { "sif?", false, Cmd_sif },   // Immediate Gain Set
    { "ssg?", false, Cmd_ssg },   // Set PD Channel

    /* E. Simple PD Measurement */
    { "ssh?", false, Cmd_ssh },   // Simple PD Measurement

    /* F. PD-ADC M48 Full Measurement */
    { "mcj?", true,  Cmd_mcj },   // MODE=2 (Pressure + M48) - 신규
    { "scj?", true,  Cmd_mcj },   // MODE=2 (호환)
    { "sdj?", false, Cmd_sdj },   // MODE=3 (M48 Only)
    { "sej?", false, Cmd_sej },   // MODE=4 (M48 + Batt + IMU)
    { "sfj?", false, Cmd_sfj },   // MODE=5 (M48 + Init)
    { "ssj?", false, Cmd_ssj },   // MODE=0 (Combined)
    { "szj?", false, Cmd_szj },   // FAST Mode Settings

    /* G. IMM ADC */
    { "saj?", false, Cmd_saj },   // 4-LED Immediate ADC

    /* H. PD-ADC Count & Delay */
    { "ssk?", false, Cmd_ssk },   // Set ADC Count (8/16/24/32)
    { "srk?", false, Cmd_srk },   // Read ADC Count
    { "ssl?", false, Cmd_ssl },   // Set PD Delay (us)
    { "srl?", false, Cmd_srl },   // Read PD Delay

    /* I. Sensor Measurements */
    { "msn?", true,  Cmd_msn },   // Battery Level (신규)
    { "ssn?", true,  Cmd_msn },   // Battery Level (호환)
    { "spn?", false, Cmd_spn },   // Pressure Measurement
    { "sso?", false, Cmd_sso },   // Temperature Measurement
    { "ssp?", false, Cmd_ssp },   // IMU Raw Data

    /* J. Power / Reset / Version */
    { "ssq?", false, Cmd_ssq },   // Power Off
    { "ssr?", false, Cmd_ssr },   // Bond Delete + Reset
    { "sss?", false, Cmd_sss },   // Device Reset
    { "sst?", false, Cmd_sst },   // Ready Response
    { "ssv?", false, Cmd_ssv },   // Firmware Version

    /* K. Serial / Passkey */
    { "ssz?", false, Cmd_ssz },   // Write Serial Number
    { "spz?", false, Cmd_spz },   // Write Passkey
    { "sqz?", false, Cmd_sqz },   // Read Passkey
    { "srz?", false, Cmd_srz },   // Read Serial Number

    /* L. EEPROM Array */
    { "sez?", false, Cmd_sez },   // Write 48*uint16 Array
    { "sfz?", false, Cmd_sfz },   // Read 48*uint16 Array
    { "sgz?", false, Cmd_sgz },   // Load DP Preset

    /* M. Hardware No / Life Cycle */
    { "siz?", false, Cmd_siz },   // Read HW Number
    { "shz?", false, Cmd_shz },   // Write HW Number
    { "sxz?", false, Cmd_sxz },   // Write Life Cycle
    { "syz?", false, Cmd_syz },   // Read Life Cycle
};

5.3 활성화된 명령어 (enabled=true)

명령어 핸들러 기능
cmd? Cmd_cmd GPIO 핀 제어 테스트
mta? Cmd_mta 디바이스 활성화/슬립
sta? Cmd_sta 디바이스 활성화/슬립 (호환)
mqq? Cmd_mqq 빠른 측정 시작
mag? Cmd_mag Full AGC 측정
sag? Cmd_mag Full AGC 측정 (호환)
mcj? Cmd_mcj M48 전체 측정 (MODE=2)
scj? Cmd_mcj M48 전체 측정 (호환)
msn? Cmd_msn 배터리 레벨 측정
ssn? Cmd_msn 배터리 레벨 측정 (호환)

6. main.c와의 연결

6.1 초기화 순서

int main(void)
{
    // 1. 기본 초기화
    uart_handler_init();     // UART 디버그
    log_init();              // NRF 로그
    gpio_init();             // GPIO 설정 (LED, PD, GAIN_SW)
    timers_init();           // 타이머 초기화

    // 2. 설정 로드
    load_device_configuration();  // EEPROM에서 설정 읽기

    // 3. BLE 초기화
    ble_stack_init();        // SoftDevice 활성화
    gap_params_init();       // GAP 파라미터 (디바이스명, Passkey)
    gatt_init();             // GATT 초기화
    services_init();         // NUS 서비스 등록
    advertising_init();      // 광고 설정
    conn_params_init();      // 연결 파라미터

    // 4. 전원 버튼 타이머 시작
    power_ctrl_timers_start();

    // 5. 메인 루프
    for (;;) {
        idle_state_handle();  // 전원 관리 + 보안 처리
    }
}

6.2 BLE 데이터 수신 경로

// ble_core.c
static void nus_data_handler(ble_nus_evt_t * p_evt)
{
    if (p_evt->type == BLE_NUS_EVT_RX_DATA) {
        // cmd_parse.c의 received_command_process() 호출
        received_command_process(
            p_evt->params.rx_data.p_data,
            CMD_BLE,
            p_evt->params.rx_data.length
        );
    }
}

6.3 Mt_parser 초기화 (cmd_parse.c)

void received_command_process(uint8_t const *data_array, which_cmd_t cmd_t, uint8_t length)
{
    // Mt_parser 초기화 (최초 1회)
    static bool parser_initialized = false;
    if (!parser_initialized) {
        g_plat.log = log_printf;           // 로그 함수 연결
        g_plat.tx_bin = binary_tx_handler; // BLE TX 함수 연결
        g_plat.crc_check = true;           // CRC 검사 활성화
        g_log_enable = true;
        parser_initialized = true;
    }

    // Mt_parser 호출
    int result = dr_cmd_parser(r_data, length);

    if (result > 0) {
        return;  // Mt_parser에서 처리됨
    }

    // Mt_parser에서 처리 안됨 → Legacy 파서로 처리
    // ... 기존 scmd 기반 처리 ...
}

7. 소스코드 구조

7.1 디렉토리 구조

ble_app_vivaMayo/
├── main.c                    # 메인 엔트리 포인트
├── main.h                    # 메인 헤더 (타입 정의, 함수 프로토타입)
├── main_timer.c/h            # 애플리케이션 타이머
│
├── cmd_parse.c/h             # 명령어 파싱 (Legacy + Mt_parser 연결)
│
├── cmd/
│   ├── cmd.c                 # 명령어 핸들러 구현 + 테이블
│   └── cmd.h                 # 명령어 구조체 정의
│
├── ble/
│   ├── ble_core.c/h          # BLE 스택, GAP, GATT, 광고
│   ├── ble_data_tx.c/h       # BLE 데이터 전송, 포맷팅
│   ├── ble_services.c/h      # BLE 서비스
│   └── ble_security.c/h      # BLE 보안 (페어링)
│
├── power/
│   └── power_ctrl.c/h        # 전원 제어, 슬립 모드
│
├── peripheral/
│   └── uart_handler.c/h      # UART 디버그 출력
│
├── config/
│   └── device_config.c/h     # EEPROM 설정 관리
│
├── measurements.c/h          # 측정 유틸리티
├── meas_pd_*.c/h             # PD ADC 측정 모듈들
├── full_agc.c/h              # AGC 자동 조절
├── battery_saadc.c/h         # 배터리 ADC
│
├── icm42670p/                # IMU 드라이버
├── i2c_manager.c/h           # I2C 관리
├── ada2200_spi.c/h           # SPI 디바이스
├── mcp4725_i2c.c/h           # DAC 제어
├── tmp235_q1.c/h             # 온도 센서
├── cat_interface.c/h         # CAT (압력센서) 인터페이스
│
└── docs/
    └── PROGRAM_ARCHITECTURE.md  # 이 문서

7.2 Mt_parser 디렉토리 (별도 위치)

/mt_parser/
├── parser.h                  # 파서 인터페이스
├── parser.c                  # 파서 구현 (CRC, TAG, 디스패치)
├── cmd.h                     # (참조용)
│
└── dr_util/
    ├── dr_util.h             # BLE 응답 유틸리티
    └── dr_util.c             # dr_ble_return_1/2/3()

8. 명령어 프로토콜

8.1 패킷 구조

┌─────────┬──────────────────┬─────────┐
│  TAG    │      DATA        │   CRC   │
│ 4 bytes │    N bytes       │ 2 bytes │
└─────────┴──────────────────┴─────────┘

예시: sta? 명령어로 디바이스 활성화
TX: [73 74 61 3F] [00 01] [CRC_LO] [CRC_HI]
     s  t  a  ?    value=1

응답: rta: value
RX: [72 74 61 3A] [00 01] [CRC_LO] [CRC_HI]
     r  t  a  :    value=1

8.2 데이터 형식

형식 설명 바이트 순서
uint16 16비트 정수 Big Endian (MSB first)
ASCII 문자열 순차적
CRC16 체크섬 Little Endian (LSB first)

8.3 응답 규칙

요청 TAG 응답 TAG 예시
sta? rta: 상태 응답
mcj? (측정 데이터) 멀티 패킷
오류 xxx! crc!, err!

9. 주요 핸들러 구현 예시

9.1 Cmd_mta (디바이스 활성화)

static int Cmd_mta(const ParsedCmd *cmd)
{
    uint16_t mode = 0;
    resetCount = 0;

    // 데이터에서 mode 추출 (word index 0)
    (void)cmd_get_u16(cmd, 0, &mode);

    if (mode == 1) {
        // 디바이스 활성화
        if (device_activated() == 0) {
            device_status = true;
        }
    }
    else if (mode == 0) {
        // 슬립 모드 진입
        if (device_status == true) {
            if (device_sleep_mode() == 0) {
                device_status = false;
            }
        }
    }

    // BLE 응답 전송
    if (g_plat.tx_bin) {
        single_format_data(ble_bin_buffer, "rta:", mode);
        binary_tx_handler(ble_bin_buffer, 3);
    }

    return 1;
}

9.2 Cmd_mcj (M48 전체 측정)

static int Cmd_mcj(const ParsedCmd *cmd)
{
    (void)cmd;

    // 디바이스 활성화 확인
    if (device_status != true) {
        param_error("mcj?");
        return 1;
    }

    // 측정 모드 설정
    ADC_PD_MODE = 2;
    info4 = true;
    ble_got_new_data = false;
    processing = true;

    // 압력 측정
    pressure_all_level_meas();

    // AGC 스위치 OFF
    AGC_GAIN_SW(false);

    // M48 ADC 시작
    m48_samples_in_buffer = m_pd_adc_cnt;
    pd_adc_m48_start = true;

    // 타이머 시작
    battery_timer_stop();
    go_batt = true;
    motion_data_once = true;
    main_timer_start();

    return 1;
}

10. 에러 코드

코드 설명
err_code1 65535 길이 오류
err_code2 65534 활성화 오류
err_code3 65533 파라미터 오류
err_code4 65532 '?' 누락
err_code5 65531 알 수 없는 명령어
err_code6 65530 CRC 오류

11. EEPROM 주소 맵

주소 크기 내용
0x0010 12 bytes HW_NO (암호화)
0x0020 6 bytes Passkey (암호화)
0x0030 12 bytes SERIAL_NO (암호화)
0x0060 1 byte bond_data_delete
0x0065 1 byte reset_status
0x0070 1 byte m_pd_adc_cnt
0x0080 2 bytes m_pd_delay_us
0x0090 4 bytes m_life_cycle
0x0480 96 bytes led_pd_dac_v[48] (AGC Gain)

12. 버전 정보

  • 펌웨어 버전: FW25LIT2B102
  • 디바이스명: MEDIDEV_2004
  • 문서 작성일: 2026-01-30
  • 작성자: Claude Assistant

13. 관련 문서