# 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 핵심 구조체 ```c // 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 전역 변수 ```c 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 검증 ```c // 패킷 구조: [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 명령어 상세 테이블 ```c 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 초기화 순서 ```c 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 데이터 수신 경로 ```c // 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) ```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 (디바이스 활성화) ```c 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 전체 측정) ```c 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. 관련 문서 - [W25Q32RV_FLASH_MEMORY.md](W25Q32RV_FLASH_MEMORY.md) - Flash 메모리 스펙 - Nordic nRF52840 Datasheet - Nordic SDK 17.x Documentation