20 KiB
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. 관련 문서
- W25Q32RV_FLASH_MEMORY.md - Flash 메모리 스펙
- Nordic nRF52840 Datasheet
- Nordic SDK 17.x Documentation