Files
VesiScan-Basic-firmware-test/project/ble_peripheral/ble_app_bladder_patch/main.c

1809 lines
72 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*******************************************************************************
* @file main.c
* @brief Medithings VesiScan BASIC
* @version 1.17
* @date 2025-12-09
* @author Charles KWON
*
* [시스템 아키텍처 개요]
* VesiScan BASIC은 nRF52840(ARM Cortex-M4F, 64MHz) 기반 BLE 방광 모니터링 패치 디바이스이다.
* SoftDevice S140(BLE 5.0)을 사용하며, 스마트폰 앱과 BLE NUS(Nordic UART Service) 프로토콜로 바이너리 통신한다.
*
* [하드웨어 구성]
* - MCU: nRF52840 (SoftDevice S140 v7.x)
* - IMU: ICM42670P 6축 가속도/자이로 (I2C, 0x68)
* - 온도: TMP235-Q1 아날로그 센서 (SAADC AIN3)
* - 배터리: 분압 회로 (SAADC AIN2, 1/3 프리스케일)
* - 전원: POWER_HOLD(P0.8) 자가유지 회로, POWER_BUTTON(P1.8) 입력
*
* [소프트웨어 부트 시퀀스 — main() 함수]
* Phase 1: 하드웨어 기본 (전원유지, 로그, GPIO, 타이머, 설정, 버튼/LED)
* Phase 2: BLE 스택 (SoftDevice, 전원관리, DC-DC)
* Phase 3: FDS + 설정 로드
* Phase 4: BLE 프로토콜 (GAP, GATT, NUS, Advertising, 보안)
* Phase 5: 애플리케이션 (파서, 피에조)
* Boot 완료 → 전원 버튼 상태머신(main_s) → idle 루프
*
* [전원 버튼 상태머신 — main_s() 콜백, 5ms 간격]
* - 짧은 눌림 (<1.5초, cnt_s<150): 전원 OFF
* - 중간 눌림 (1.5초~10초, cnt_s≥150): 부팅 시퀀스 시작 (센서+BLE 광고)
* - 긴 눌림 (>10초, cnt_s>1000): 공장 초기화 (패스키 리셋 + 전원 OFF)
*
* [이벤트 흐름]
* 1. 앱에서 BLE NUS로 명령 수신 → nus_data_handler → dr_cmd_parser
* 2. UART에서 명령 수신 → uart_event_handle → dr_cmd_parser
* 3. 명령 파서(cmd_parse.c)가 태그 기반 디스패치 (sta?, ssn?, ssp? 등)
* 4. 센서 데이터를 format_data() 계열 함수로 바이너리 패킷 생성
* 5. dr_binary_tx_safe()로 CRC16 추가 후 BLE 전송 (최대 100회 재시도)
*
* [빌드 모드]
* BLE_DEV_MODE = 1 : 보안 없음 (빠른 페어링, 개발용)
* BLE_DEV_MODE = 0 : 정적 패스키 + MITM 보호 (양산용)
******************************************************************************/
/*==============================================================================
* 인클루드 (INCLUDES)
*============================================================================*/
#include <stdint.h>
#include <string.h>
#include "nordic_common.h"
#include "nrf.h"
#include "ble_hci.h" /* BLE HCI 에러 코드 정의 */
#include "ble_advdata.h" /* BLE 광고 데이터 구조체 */
#include "ble_advertising.h" /* BLE 광고 모듈 */
#include "ble_srv_common.h" /* BLE 서비스 공통 유틸리티 */
#include "ble_conn_params.h" /* BLE 연결 파라미터 협상 */
#include "nrf_sdh.h" /* SoftDevice 핸들러 (BLE 스택 관리) */
#include "nrf_sdh_soc.h" /* SoftDevice SoC 이벤트 핸들러 */
#include "nrf_sdh_ble.h" /* SoftDevice BLE 이벤트 핸들러 */
#include "nrf_ble_gatt.h" /* BLE GATT 모듈 (MTU 협상) */
#include "nrf_ble_qwr.h" /* BLE Queued Write 모듈 (긴 특성값 쓰기) */
#include "app_timer.h" /* 앱 타이머 (RTC1 기반, 소프트 타이머) */
#include "ble_nus.h" /* Nordic UART Service (BLE 양방향 데이터 전송) */
#include "app_uart.h" /* 물리 UART 드라이버 (디버그/공장 테스트용) */
#include "app_util_platform.h" /* 인터럽트 우선순위 매크로 */
#include "bsp_btn_ble.h" /* BSP 버튼-BLE 연동 모듈 */
#include "nrf_pwr_mgmt.h" /* 전원 관리 (idle 시 WFE/슬립) */
#include "nrf_delay.h" /* 블로킹 딜레이 (us/ms) */
#include "math.h"
#include "crc16.h" /* CRC16 계산 (BLE 패킷 무결성 검증) */
#if defined (UART_PRESENT)
#include "nrf_uart.h"
#endif
#if defined (UARTE_PRESENT)
#include "nrf_uarte.h"
#endif
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"
#if BLE_DFU_ENABLED
#include "nrf_pwr_mgmt.h"
#include "ble_dfu.h"
#include "nrf_power.h"
#include "nrf_bootloader_info.h"
#include "nrf_dfu_ble_svci_bond_sharing.h"
#include "nrf_svci_async_function.h"
#include "nrf_svci_async_handler.h"
#endif
/* ── 애플리케이션 모듈 헤더 ── */
#include "system_interface.h" /* 시스템 인터페이스 (IMU 센서 I2C 통신) */
#include "main.h" /* 메인 헤더 (전역 구조체, 상수, 외부 함수 선언) */
#include "app_raw_main.h" /* 센서 원시 데이터 처리 모듈 */
#include "main_timer.h" /* 메인 이벤트 루프 타이머 (10ms 주기) */
#include "power_control.h" /* 전원 시퀀스 제어 (ON/OFF/슬립) */
#include "tmp235_q1.h" /* TMP235-Q1 온도 센서 드라이버 */
#include "fds.h" /* Flash Data Storage (비휘발성 설정 저장) */
#include "battery_saadc.h" /* 배터리 전압 측정 (SAADC) */
/* ── BLE 보안 관련 헤더 (FEATURE_SECURE_CONNECTION 활성 시) ── */
#if FEATURE_SECURE_CONNECTION
#include "peer_manager.h" /* Peer Manager (본딩/페어링 관리) */
#include "peer_manager_handler.h" /* Peer Manager 기본 이벤트 핸들러 */
#include "nrf_ble_lesc.h" /* BLE LESC(LE Secure Connections) 지원 */
#include "ble_quick_security.h" /* 간편 보안 설정 래퍼 */
#include "i2c_manager.h" /* I2C 버스 관리자 (센서 통신) */
#endif
/* ── 암호화/명령 파서/디버그 ── */
#include "nrf_crypto.h" /* nRF 암호화 라이브러리 (AES 등) */
#include "parser.h" /* 새 바이너리 명령 파서 (dr_cmd_parser) */
#include "debug_print.h" /* 디버그 출력 매크로 (DBG_PRINTF) */
#include "fstorage.h" /* Flash Storage 래퍼 (FDS 초기화/저장/로드) */
#include "dr_piezo.h" /* 피에조 초음파 드라이버 */
#include "led_control.h" /* LED 직접 제어 드라이버 (BSP 미사용) */
/*==============================================================================
* 빌드 설정
*============================================================================*/
#define BLE_DEV_MODE 1 /* 1: 개발 모드 (보안 없음), 0: 양산 모드 (패스키 필수) */
/*==============================================================================
* 하드웨어 핀 정의
*============================================================================*/
#define POWER_HOLD NRF_GPIO_PIN_MAP(0,8) /* 전원 자가유지 핀 (HIGH=전원 유지) */
#define POWER_BUTTON NRF_GPIO_PIN_MAP(1,8) /* 전원 버튼 입력 핀 (LOW=눌림) */
/*==============================================================================
* BLE 설정
*============================================================================*/
#define APP_BLE_CONN_CFG_TAG 1 /* SoftDevice BLE 설정 태그 */
#define NUS_SERVICE_UUID_TYPE BLE_UUID_TYPE_VENDOR_BEGIN /* NUS UUID 타입 (벤더 특정) */
#define APP_BLE_OBSERVER_PRIO 3 /* BLE 이벤트 옵저버 우선순위 */
#define APP_ADV_INTERVAL 64 /* 광고 간격: 64 x 0.625ms = 40ms */
#if FEATURE_NO_SLEEP
#define APP_ADV_DURATION 0 /* 슬립 비활성화(테스트) 시: 무한 광고 */
#else
#define APP_ADV_DURATION 18000 /* 광고 지속시간: 18000 x 10ms = 3분 → 타임아웃 시 슬립 */
#endif
#define MIN_CONN_INTERVAL MSEC_TO_UNITS(30, UNIT_1_25_MS) /* 최소 연결 간격: 30ms */
#define MAX_CONN_INTERVAL MSEC_TO_UNITS(30, UNIT_1_25_MS) /* 최대 연결 간격: 30ms */
#define SLAVE_LATENCY 0 /* 슬레이브 지연: 0 (매 연결 이벤트마다 응답) */
#define CONN_SUP_TIMEOUT MSEC_TO_UNITS(4000, UNIT_10_MS) /* 연결 감독 타임아웃: 4초 */
#define FIRST_CONN_PARAMS_UPDATE_DELAY APP_TIMER_TICKS(5000) /* 첫 파라미터 갱신 요청까지 5초 대기 */
#define NEXT_CONN_PARAMS_UPDATE_DELAY APP_TIMER_TICKS(30000) /* 이후 갱신 요청 간격: 30초 */
#define MAX_CONN_PARAMS_UPDATE_COUNT 3 /* 최대 파라미터 갱신 시도 횟수 */
/* ── BLE 보안 파라미터 (FEATURE_SECURE_CONNECTION 활성 시) ──
* 본딩(BOND=1): 페어링 정보를 Flash에 저장하여 재연결 시 재사용
* MITM 보호(MITM=1): 중간자 공격 방지 (패스키 인증 필수)
* LESC: 정적 패스키 모드에서는 비활성(0), 동적 패스키 모드에서는 활성(1)
* IO 능력: DISPLAY_ONLY → 디바이스가 패스키를 표시, 사용자가 앱에 입력
*/
#if FEATURE_SECURE_CONNECTION
#define LESC_DEBUG_MODE 0 /* LESC 디버그 모드 비활성 */
#define SEC_PARAM_BOND 1 /* 본딩 활성화 (페어링 정보 저장) */
#define SEC_PARAM_MITM 1 /* MITM 보호 활성화 */
#if FEATURE_STATIC_PASSKEY
#define SEC_PARAM_LESC 0 /* 정적 패스키 모드: LESC 비활성 */
#else
#define SEC_PARAM_LESC 1 /* 동적 패스키 모드: LESC 활성 */
#endif
#define SEC_PARAM_KEYPRESS 0 /* 키 입력 알림 비활성 */
#define SEC_PARAM_IO_CAPABILITIES BLE_GAP_IO_CAPS_DISPLAY_ONLY /* 디스플레이만 가능 */
#define SEC_PARAM_OOB 0 /* OOB(Out-of-Band) 데이터 없음 */
#define SEC_PARAM_MIN_KEY_SIZE 7 /* 최소 암호화 키 크기 (바이트) */
#define SEC_PARAM_MAX_KEY_SIZE 16 /* 최대 암호화 키 크기 (바이트) */
#define PASSKEY_TXT_LENGTH 8 /* 패스키 텍스트 버퍼 길이 */
#endif
/*==============================================================================
* 시스템 상수
*============================================================================*/
#define DEAD_BEEF 0xDEADBEEF /* SDK 에러 핸들러 매직 넘버 */
#define UART_TX_BUF_SIZE 16384 /* UART 송신 FIFO 버퍼 크기 (16KB) */
#define UART_RX_BUF_SIZE 512 /* UART 수신 FIFO 버퍼 크기 */
#define POWER_ON_DELAY 5 /* 전원 버튼 폴링 간격 (5ms) */
#define POWER_OFF_DELAY 3000 /* 전원 OFF 지연: LED 표시 후 3초 대기 */
#define POWER_RESET_DELAY 2000 /* 리셋 지연: 2초 */
#define LED_NUM 24 /* LED 핀 번호 */
#define AES_KEY_SIZE 16 /* AES 암호화 키 크기 (128비트) */
#define AES_BLOCK_SIZE 16 /* AES 블록 크기 (128비트) */
/*==============================================================================
* BLE 인스턴스 (SoftDevice 매크로로 정적 할당)
*============================================================================*/
BLE_NUS_DEF(m_nus, NRF_SDH_BLE_TOTAL_LINK_COUNT); /* Nordic UART Service 인스턴스 */
NRF_BLE_GATT_DEF(m_gatt); /* GATT 모듈 인스턴스 (MTU 협상) */
NRF_BLE_QWR_DEF(m_qwr); /* Queued Write 인스턴스 */
BLE_ADVERTISING_DEF(m_advertising); /* 광고 모듈 인스턴스 */
/*==============================================================================
* 타이머 인스턴스
*============================================================================*/
APP_TIMER_DEF(m_power_on_delay_timer_id); /* 전원 버튼 폴링 타이머 (5ms 싱글샷, main_s 콜백) */
APP_TIMER_DEF(m_power_off_delay_timer_id); /* 전원 OFF 지연 타이머 (3초 후 물리적 전원 차단) */
APP_TIMER_DEF(m_PM_timer_id); /* Peer Manager 타이머 (연결 강제 해제용) */
/*==============================================================================
* 정적 변수
*============================================================================*/
#if FEATURE_SECURE_CONNECTION
static pm_peer_id_t m_peer_to_be_deleted = PM_PEER_ID_INVALID;
#endif
static uint16_t m_conn_handle = BLE_CONN_HANDLE_INVALID; /* 현재 BLE 연결 핸들 */
static uint16_t m_ble_nus_max_data_len = BLE_GATT_ATT_MTU_DEFAULT - 3; /* NUS 최대 데이터 길이 (MTU - 오버헤드) */
static ble_uuid_t m_adv_uuids[] = {{BLE_UUID_NUS_SERVICE, NUS_SERVICE_UUID_TYPE}}; /* 광고에 포함될 UUID */
static uint8_t m_tx_buffer[BLE_NUS_MAX_DATA_LEN]; /* ASCII 텍스트 전송용 버퍼 */
static uint16_t m_tx_len = 0; /* 전송할 데이터 길이 */
static volatile bool m_tx_in_progress = false; /* TX 전송 진행 중 플래그 */
static volatile bool m_tx_complete_pending = false; /* TX 완료 대기 플래그 */
static uint8_t c_addr[6]; /* 연결된 피어의 BLE 주소 */
static char * roles_str[] = {"INVALID_ROLE", "CENTRAL", "PERIPHERAL"};
/*==============================================================================
* 전역 변수
*============================================================================*/
uint8_t m_encrypted_text[AES_BLOCK_SIZE]; /* AES 암호화 결과 버퍼 */
uint8_t m_encrypted_text2[AES_BLOCK_SIZE]; /* AES 암호화 결과 버퍼 2 */
uint8_t m_decrypted_text[AES_BLOCK_SIZE]; /* AES 복호화 결과 버퍼 */
volatile uint8_t Sj_type; /* 명령 타입 식별자 */
volatile bool processing; /* 센서 데이터 처리 중 플래그 (중복 명령 방지) */
bool power_off_duble_prohibit = false; /* 전원 OFF 중복 방지 플래그 */
volatile bool power_state = false; /* 전원 상태 추적 */
/* ── 외부 모듈 플래그 (main_timer/power_control에서 정의) ── */
extern bool go_device_power_off; /* 전원 OFF 요청 플래그 */
extern bool go_sleep_mode_enter; /* 슬립 모드 진입 요청 플래그 */
extern bool go_NVIC_SystemReset; /* 시스템 리셋 요청 플래그 */
extern bool ble_got_new_data; /* BLE 새 데이터 수신 플래그 */
extern bool motion_data_once; /* 모션 데이터 1회 전송 플래그 */
extern bool con_single; /* 단일 연결 모드 플래그 */
extern bool info4; /* 디바이스 정보 전송 완료 플래그 */
extern uint8_t add_cycle; /* 추가 측정 사이클 카운터 */
extern bool motion_raw_data_enabled; /* 모션 원시 데이터 스트리밍 활성화 */
uint16_t cnt_s; /* 전원 버튼 폴링 카운터 (5ms 단위, 150=0.75초) */
char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN]; /* BLE 전송 텍스트 버퍼 */
uint8_t ble_bin_buffer[BLE_NUS_MAX_DATA_LEN]; /* BLE 바이너리 응답 버퍼 (2026-03-17: cmd_parse.c에서 이동) */
uint16_t ble_bin_buff[BLE_NUS_MAX_DATA_LEN/2]; /* BLE 바이너리 전송 버퍼 (워드 단위) */
which_cmd_t cmd_type_t; /* 현재 명령 소스 (CMD_BLE 또는 CMD_UART) */
bool device_status = false; /* 디바이스 활성 상태 (true=동작 중) */
bool device_reset = true; /* 부팅 중 플래그 (true=아직 미부팅) */
#if FEATURE_SECURE_CONNECTION
bool erase_bonds; /* 본딩 삭제 플래그 (부팅 시 버튼 상태에 따라 설정) */
#endif
volatile bool ble_connection_st; /* BLE 연결 상태 (1=연결됨, 0=미연결) */
volatile bool data_tx_in_progress = false; /* 바이너리 TX 진행 중 플래그 */
/* ── BLE TX 비동기 재시도 상태 ── */
static volatile bool s_tx_pending = false; /* TX 재시도 대기 중 */
static uint8_t s_tx_pending_buf[BLE_NUS_MAX_DATA_LEN]; /* 대기 중인 패킷 (CRC 포함) */
static uint16_t s_tx_pending_len = 0; /* 대기 중인 패킷 길이 */
char m_static_passkey[PASSKEY_LENGTH] = DEFAULT_PASSKEY; /* 정적 패스키 (6자리, FDS에서 로드) */
char SERIAL_NO[SERIAL_NO_LENGTH]; /* 시리얼 번호 (BLE 디바이스 이름으로 사용) */
char HW_NO[HW_NO_LENGTH]; /* 하드웨어 번호 (FDS 저장/읽기) */
bool bond_data_delete; /* 본딩 데이터 삭제 요청 플래그 */
uint32_t m_life_cycle; /* 디바이스 수명 사이클 카운터 */
uint8_t resetCount = 0; /* 통신 타임아웃 카운터 (리셋 감지용) */
bool info4; /* 센서 측정 정보(배터리/IMU/온도) 포함 측정 플래그 */
uint8_t m_reset_status; /* 리셋 상태 코드 (1=정상, 2=SW리셋, 5=보안리셋, 10=본딩완료) */
/*==============================================================================
* 미사용 변수 경고 억제
*============================================================================*/
#if FEATURE_SECURE_CONNECTION
static void __attribute__((unused)) suppress_unused_warnings(void)
{
(void)m_peer_to_be_deleted;
(void)roles_str;
(void)c_addr;
}
#endif
/*==============================================================================
* 전원 관리
*============================================================================*/
/**
* @brief 전원 자가유지 핀 초기화
*
* P0.8(POWER_HOLD)을 출력으로 설정하고 HIGH로 구동하여 외부 전원 래치 회로를 유지
* P0.8(POWER_HOLD) 핀이 LOW가 되면 물리적으로 전원이 차단
* main() 진입 직후 가장 먼저 호출해야 함(Phase 0)
*/
static void power_hold_init(void)
{
NRF_P0->DIRSET = (1 << 8); /* P0.8을 출력으로 설정 */
NRF_P0->OUTSET = (1 << 8); /* P0.8을 HIGH로 → 전원 유지 */
}
/**
* @brief 물리적 전원 ON/OFF 제어
*
* POWER_HOLD 핀을 직접 제어하여 디바이스 전원을 물리적으로 ON/OFF
* OFF 시 전원 래치가 풀리면서 전체 시스템이 꺼짐(복귀 불가)
*/
static void power_control_handler(on_off_cont_t device_power_st)
{
if (device_power_st == OFF)
{
nrf_gpio_pin_clear(POWER_HOLD); /* P0.8 LOW → 전원 래치 해제 → 전원 차단 */
DBG_PRINTF("[PWR] OFF\r\n");
}
else if (device_power_st == ON)
{
nrf_gpio_pin_set(POWER_HOLD); /* P0.8 HIGH → 전원 유지 */
DBG_PRINTF("[PWR] ON\r\n");
}
}
/*==============================================================================
* GPIO 초기화
*============================================================================*/
/**
* @brief GPIO 초기화
*
* 전원 버튼 입력과 전원 유지 핀만 설정
*/
static void gpio_init(void)
{
nrf_gpio_cfg_input(POWER_BUTTON, NRF_GPIO_PIN_NOPULL);
DBG_PRINTF("[GPIO] OK (BTN=%d)\r\n", nrf_gpio_pin_read(POWER_BUTTON));
}
/*==============================================================================
* 설정 로드
* FDS(Flash Data Storage)에서 설정을 읽기 전에 기본값을 먼저 로드
* FDS 초기화 후 load_flash_config()에서 플래시 저장값으로 덮어씀
*============================================================================*/
/**
* @brief 기본 설정값 로드
*
* FDS에서 설정을 읽기 전에 안전한 기본값을 전역 변수에 설정
* 시리얼 번호, 패스키, 리셋 상태 등을 기본값으로 초기화
*/
static void load_default_config(void)
{
memset(SERIAL_NO, 0, SERIAL_NO_LENGTH);
memcpy(SERIAL_NO, SERIAL_NUMBER, strlen(SERIAL_NUMBER));
memset(m_static_passkey, 0, PASSKEY_LENGTH);
memcpy(m_static_passkey, DEFAULT_PASSKEY, PASSKEY_LENGTH);
m_reset_status = 1;
bond_data_delete = 1;
DBG_PRINTF("[CFG] Default (S/N=%s)\r\n", SERIAL_NO);
}
/**
* @brief 내장 Flash(FDS)에서 설정값을 전역 변수로 로드
*
* 반드시 ble_stack_init() → fs_storage_init() → config_load() 이후에 호출해야 한다.
* config_load()가 FDS에서 읽어온 m_config 구조체를 런타임 전역 변수로 복사한다.
* 빈 필드(0x00 또는 0xFF)는 기본값으로 채우고 m_need_save_defaults 플래그를 설정한다.
*/
static bool m_need_save_defaults = false;
static void load_flash_config(void)
{
m_need_save_defaults = false;
/* 하드웨어 번호 — 비어있으면 기본값 채움 */
if (m_config.hw_no[0] == 0 || m_config.hw_no[0] == (char)0xFF)
{
memset(m_config.hw_no, 0, HW_NO_LENGTH);
memcpy(m_config.hw_no, HARDWARE_VERSION, strlen(HARDWARE_VERSION));
DBG_PRINTF("[CFG] HW empty, set default: %s\r\n", HARDWARE_VERSION);
m_need_save_defaults = true;
}
/* 시리얼 번호 — 비어있으면 기본값 채움 */
if (m_config.serial_no[0] == 0 || m_config.serial_no[0] == (char)0xFF)
{
memset(m_config.serial_no, 0, SERIAL_NO_LENGTH);
memcpy(m_config.serial_no, SERIAL_NUMBER, strlen(SERIAL_NUMBER));
DBG_PRINTF("[CFG] S/N empty, set default: %s\r\n", SERIAL_NUMBER);
m_need_save_defaults = true;
}
/* 시리얼 번호 → BLE 디바이스 이름으로 복사 */
memset(SERIAL_NO, 0, SERIAL_NO_LENGTH);
memcpy(SERIAL_NO, m_config.serial_no, SERIAL_NO_LENGTH);
/* 패스키 복사 */
memset(m_static_passkey, 0, PASSKEY_LENGTH);
memcpy(m_static_passkey, m_config.static_passkey, PASSKEY_LENGTH);
/* 본딩 데이터 삭제 플래그 */
bond_data_delete = m_config.bond_data_delete;
/* 리셋 상태 */
m_reset_status = m_config.reset_status;
DBG_PRINTF("[CFG] HW=%.12s S/N=%s passkey=%.6s bond=%d rst=%d\r\n",
m_config.hw_no, SERIAL_NO, m_static_passkey, bond_data_delete,
m_reset_status);
DBG_PRINTF("[CFG] piezo: freq=%u cyc=%u avg=%u delay=%u samples=%u\r\n",
m_config.piezo_freq_option, m_config.piezo_cycles,
m_config.piezo_averaging, m_config.piezo_delay_us,
m_config.piezo_num_samples);
}
/*==============================================================================
* 타이머 콜백 함수
*============================================================================*/
static void main_s(void * p_context); /* 전원 버튼 상태머신 (전방 선언) */
static void t_power_off_timeout_handler(void * p_context); /* 전원 OFF 타임아웃 */
static void PM_s(void * p_context); /* Peer Manager 타이머 */
/**
* @brief 전원 OFF 타임아웃 콜백
*
* POWER_OFF_DELAY(3초) 경과 후 호출됨
* LED를 끄고 POWER_HOLD 핀을 LOW로 설정하여 물리적 전원을 차단
* 이 함수 이후 디바이스는 완전히 꺼짐(복귀 불가)
*/
static void t_power_off_timeout_handler(void * p_context)
{
UNUSED_PARAMETER(p_context);
APP_ERROR_CHECK(app_timer_stop(m_power_off_delay_timer_id));
DBG_PRINTF("[PWR] Off timeout\r\n");
led_set_state(LED_STATE_OFF); /* LED OFF */
power_control_handler(OFF); /* P0.8 LOW → 전원 차단 */
}
/**
* @brief Peer Manager 타이머 콜백
*
* 보안 관련 상태(m_reset_status==5)에서 BLE 연결을 강제 해제
* 본딩 정보 삭제 후 재연결을 위해 기존 연결을 끊을 때 사용
*/
static void PM_s(void * p_context)
{
UNUSED_PARAMETER(p_context);
APP_ERROR_CHECK(app_timer_stop(m_PM_timer_id));
if (m_reset_status == 5)
{
DBG_PRINTF("[PM] Kill\r\n");
sd_ble_gap_disconnect(m_conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
}
}
/*==============================================================================
* 타이머 초기화/시작 함수
*============================================================================*/
/**
* @brief 모든 앱 타이머 초기화 (부트 Phase 3)
*
* app_timer 모듈 초기화 후 각 타이머 생성:
* - m_power_on_delay_timer: 전원 버튼 폴링 (5ms 싱글샷, main_s 콜백)
* - m_power_off_delay_timer: 전원 OFF 지연 (3초 싱글샷)
* - m_PM_timer: Peer Manager 연결 해제 (싱글샷)
* - main_timer: 메인 이벤트 루프 (10ms 싱글샷, main_loop 콜백)
* - battery_timer: 배터리 모니터링 (5초 반복)
* - power_timer: 전원 시퀀스 (20ms 싱글샷, power_loop 콜백)
*/
static void timers_init(void)
{
ret_code_t err_code = app_timer_init();
APP_ERROR_CHECK(err_code);
APP_ERROR_CHECK(app_timer_create(&m_power_on_delay_timer_id, APP_TIMER_MODE_SINGLE_SHOT, main_s));
APP_ERROR_CHECK(app_timer_create(&m_power_off_delay_timer_id, APP_TIMER_MODE_SINGLE_SHOT, t_power_off_timeout_handler));
APP_ERROR_CHECK(app_timer_create(&m_PM_timer_id, APP_TIMER_MODE_SINGLE_SHOT, PM_s));
main_timer_init();
battery_timer_init();
power_timer_init();
#if FEATURE_PRINTF
full_timer_init();
#endif
}
/**
* @brief 전원 버튼 폴링 타이머 시작
*
* 5ms(POWER_ON_DELAY) 후 main_s() 콜백
* main_s()에서 버튼 상태를 확인하고, 아직 눌려있으면 다시 호출하여 상태머신 구동
*/
static void timers_start(void)
{
ret_code_t err_code;
err_code = app_timer_start(m_power_on_delay_timer_id, APP_TIMER_TICKS(POWER_ON_DELAY), NULL);
APP_ERROR_CHECK(err_code);
}
/*==============================================================================
* BLE DFU 핸들러
* DFU(Device Firmware Update) 이벤트를 처리하여 BLE를 통한 펌웨어 업데이트를 지원한다.
* 부트로더 진입 준비, 진입 성공/실패 등의 이벤트를 로그로 출력한다.
*============================================================================*/
#if BLE_DFU_ENABLED
static void ble_dfu_evt_handler(ble_dfu_buttonless_evt_type_t event)
{
switch (event)
{
case BLE_DFU_EVT_BOOTLOADER_ENTER_PREPARE:
DBG_PRINTF("[DFU] Prepare\r\n");
break;
case BLE_DFU_EVT_BOOTLOADER_ENTER:
DBG_PRINTF("[DFU] Enter\r\n");
break;
case BLE_DFU_EVT_BOOTLOADER_ENTER_FAILED:
DBG_PRINTF("[DFU] Failed\r\n");
break;
case BLE_DFU_EVT_RESPONSE_SEND_ERROR:
DBG_PRINTF("[DFU] Error\r\n");
APP_ERROR_CHECK(false);
break;
default:
break;
}
}
#endif
/*==============================================================================
* BLE GAP 초기화
*============================================================================*/
/**
* @brief GAP(Generic Access Profile) 파라미터 초기화
*
* 1) 디바이스 이름을 SERIAL_NO(시리얼 번호)로 설정 → BLE 스캔 시 표시됨
* 2) 연결 파라미터 설정 (20~75ms 간격, 슬레이브 지연 0, 감독 타임아웃 4초)
* 3) 정적 패스키 설정 (FEATURE_STATIC_PASSKEY 활성 시)
*/
static void gap_params_init(void)
{
uint32_t err_code;
ble_gap_conn_params_t gap_conn_params;
ble_gap_conn_sec_mode_t sec_mode;
#if FEATURE_STATIC_PASSKEY
ble_opt_t ble_opt;
#endif
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);
err_code = sd_ble_gap_device_name_set(&sec_mode,
(const uint8_t *) SERIAL_NO,
strlen(SERIAL_NO));
APP_ERROR_CHECK(err_code);
memset(&gap_conn_params, 0, sizeof(gap_conn_params));
gap_conn_params.min_conn_interval = MIN_CONN_INTERVAL;
gap_conn_params.max_conn_interval = MAX_CONN_INTERVAL;
gap_conn_params.slave_latency = SLAVE_LATENCY;
gap_conn_params.conn_sup_timeout = CONN_SUP_TIMEOUT;
err_code = sd_ble_gap_ppcp_set(&gap_conn_params);
APP_ERROR_CHECK(err_code);
#if FEATURE_STATIC_PASSKEY
ble_opt.gap_opt.passkey.p_passkey = (const uint8_t *)m_static_passkey;
err_code = sd_ble_opt_set(BLE_GAP_OPT_PASSKEY, &ble_opt);
APP_ERROR_CHECK(err_code);
#endif
}
/*==============================================================================
* BLE 서비스 초기화
*============================================================================*/
static void nrf_qwr_error_handler(uint32_t nrf_error)
{
APP_ERROR_HANDLER(nrf_error);
}
/* 비동기 MAA TX 완료 핸들러 (전방 선언) */
extern bool maa_async_on_tx_ready(void);
extern bool maa_async_is_busy(void);
extern void maa_async_abort(void);
/* 2026-03-17: BLE 콜백에서 블로킹 방지 — 명령을 버퍼에 저장 후 메인 루프에서 처리 */
static volatile uint8_t pending_cmd_buf[BLE_NUS_MAX_DATA_LEN];
static volatile uint8_t pending_cmd_len = 0;
/**
* @brief NUS(Nordic UART Service) 데이터 수신 핸들러
*
* BLE를 통해 스마트폰 앱에서 데이터를 수신했을 때 호출됨
* - RX_DATA 이벤트: 수신 데이터를 버퍼에 복사 (메인 루프에서 dr_cmd_parser 호출)
* - TX_RDY 이벤트: BLE TX 버퍼에 공간이 생겼을 때 비동기 MAA 전송 계속
*/
static void nus_data_handler(ble_nus_evt_t * p_evt)
{
if (p_evt->type == BLE_NUS_EVT_RX_DATA)
{
cmd_type_t = CMD_BLE;
ble_got_new_data = true;
/* Central이 연결 간격을 늘렸을 수 있으므로 빠른 간격 재요청 (30초에 1회) */
{
static uint32_t last_update_tick = 0;
uint32_t now_tick = app_timer_cnt_get();
if (last_update_tick == 0 ||
app_timer_cnt_diff_compute(now_tick, last_update_tick) >= APP_TIMER_TICKS(30000))
{
ble_gap_conn_params_t conn_params;
conn_params.min_conn_interval = MIN_CONN_INTERVAL;
conn_params.max_conn_interval = MAX_CONN_INTERVAL;
conn_params.slave_latency = SLAVE_LATENCY;
conn_params.conn_sup_timeout = CONN_SUP_TIMEOUT;
sd_ble_gap_conn_param_update(m_conn_handle, &conn_params);
last_update_tick = now_tick;
}
}
/* 콜백에서는 복사만 하고, 메인 루프에서 처리 */
if (p_evt->params.rx_data.length <= BLE_NUS_MAX_DATA_LEN)
{
memcpy((void *)pending_cmd_buf, p_evt->params.rx_data.p_data, p_evt->params.rx_data.length);
pending_cmd_len = p_evt->params.rx_data.length;
}
}
else if (p_evt->type == BLE_NUS_EVT_TX_RDY)
{
/* BLE TX 버퍼에 공간이 생김 - 비동기 MAA 전송 계속 */
if (maa_async_is_busy())
{
maa_async_on_tx_ready();
}
/* pending 패킷 재시도 (dr_binary_tx_safe 비동기) */
else if (s_tx_pending)
{
uint16_t send_len = s_tx_pending_len;
uint32_t err = ble_nus_data_send(&m_nus, s_tx_pending_buf, &send_len, m_conn_handle);
if (err == NRF_SUCCESS)
{
s_tx_pending = false;
}
/* NRF_ERROR_RESOURCES → 다음 TX_RDY에서 다시 시도 */
}
}
}
/**
* @brief BLE 서비스 초기화
*
* QWR(Queued Write) + NUS(Nordic UART Service) + DFU(선택) 서비스 초기화
* NUS의 data_handler로 nus_data_handler를 등록하여 BLE 수신 데이터 처리
*/
static void services_init(void)
{
uint32_t err_code;
ble_nus_init_t nus_init;
nrf_ble_qwr_init_t qwr_init = {0};
qwr_init.error_handler = nrf_qwr_error_handler;
err_code = nrf_ble_qwr_init(&m_qwr, &qwr_init);
APP_ERROR_CHECK(err_code);
memset(&nus_init, 0, sizeof(nus_init));
nus_init.data_handler = nus_data_handler;
err_code = ble_nus_init(&m_nus, &nus_init);
APP_ERROR_CHECK(err_code);
#if BLE_DFU_ENABLED
ble_dfu_buttonless_init_t dfus_init = {0};
dfus_init.evt_handler = ble_dfu_evt_handler;
err_code = ble_dfu_buttonless_init(&dfus_init);
APP_ERROR_CHECK(err_code);
#endif
}
/*==============================================================================
* BLE 연결 파라미터 협상
*============================================================================*/
/** @brief 연결 파라미터 협상 실패 시 연결 해제 */
static void on_conn_params_evt(ble_conn_params_evt_t * p_evt)
{
if (p_evt->evt_type == BLE_CONN_PARAMS_EVT_FAILED)
{
DBG_PRINTF("[CONN] Param negotiation FAILED -> disconnect\r\n");
uint32_t err_code = sd_ble_gap_disconnect(m_conn_handle, BLE_HCI_CONN_INTERVAL_UNACCEPTABLE);
APP_ERROR_CHECK(err_code);
}
}
static void conn_params_error_handler(uint32_t nrf_error)
{
APP_ERROR_HANDLER(nrf_error);
}
/**
* @brief BLE 연결 파라미터 모듈 초기화
*
* 연결 후 5초 대기 → 파라미터 갱신 요청 → 30초 간격으로 최대 3회 재시도
* 실패 시 on_conn_params_evt에서 연결 해제 처리
*/
static void conn_params_init(void)
{
uint32_t err_code;
ble_conn_params_init_t cp_init;
memset(&cp_init, 0, sizeof(cp_init));
cp_init.p_conn_params = NULL;
cp_init.first_conn_params_update_delay = FIRST_CONN_PARAMS_UPDATE_DELAY;
cp_init.next_conn_params_update_delay = NEXT_CONN_PARAMS_UPDATE_DELAY;
cp_init.max_conn_params_update_count = MAX_CONN_PARAMS_UPDATE_COUNT;
cp_init.start_on_notify_cccd_handle = BLE_GATT_HANDLE_INVALID;
cp_init.disconnect_on_fail = false;
cp_init.evt_handler = on_conn_params_evt;
cp_init.error_handler = conn_params_error_handler;
err_code = ble_conn_params_init(&cp_init);
APP_ERROR_CHECK(err_code);
}
/*==============================================================================
* BLE 스택 초기화
*============================================================================*/
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context);
/**
* @brief SoftDevice BLE 스택 초기화
*
* 1) SoftDevice 활성화 요청
* 2) BLE 기본 설정 (RAM 시작 주소 자동 계산)
* 3) BLE 활성화
* 4) BLE 이벤트 옵저버 등록 (ble_evt_handler)
*/
static void ble_stack_init(void)
{
ret_code_t err_code;
err_code = nrf_sdh_enable_request();
APP_ERROR_CHECK(err_code);
uint32_t ram_start = 0;
err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start);
APP_ERROR_CHECK(err_code);
err_code = nrf_sdh_ble_enable(&ram_start);
APP_ERROR_CHECK(err_code);
NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);
}
/*==============================================================================
* BLE GATT (Generic Attribute Profile)
* MTU 협상을 통해 한 번에 전송 가능한 최대 데이터 크기를 결정한다.
*============================================================================*/
/** @brief GATT MTU 갱신 이벤트 핸들러 — NUS 최대 데이터 길이를 업데이트 */
void gatt_evt_handler(nrf_ble_gatt_t * p_gatt, nrf_ble_gatt_evt_t const * p_evt)
{
if ((m_conn_handle == p_evt->conn_handle) && (p_evt->evt_id == NRF_BLE_GATT_EVT_ATT_MTU_UPDATED))
{
m_ble_nus_max_data_len = p_evt->params.att_mtu_effective - OPCODE_LENGTH - HANDLE_LENGTH;
}
}
/** @brief GATT 모듈 초기화 — MTU를 최대 크기로 설정 */
static void gatt_init(void)
{
ret_code_t err_code;
err_code = nrf_ble_gatt_init(&m_gatt, gatt_evt_handler);
APP_ERROR_CHECK(err_code);
err_code = nrf_ble_gatt_att_mtu_periph_set(&m_gatt, NRF_SDH_BLE_GATT_MAX_MTU_SIZE);
APP_ERROR_CHECK(err_code);
}
/*==============================================================================
* BLE 광고 (Advertising)
*============================================================================*/
/**
* @brief BLE 광고 이벤트 핸들러
*
* - FAST: 광고 시작됨 (LED 표시)
* - IDLE: 광고 타임아웃 → 슬립 모드 진입 플래그 설정
*/
static void on_adv_evt(ble_adv_evt_t ble_adv_evt)
{
switch (ble_adv_evt)
{
case BLE_ADV_EVT_FAST:
led_set_state(LED_STATE_ADVERTISING);
DBG_PRINTF("[ADV] Fast\r\n");
break;
case BLE_ADV_EVT_IDLE:
DBG_PRINTF("[ADV] Idle\r\n");
go_sleep_mode_enter = true;
main_timer_start();
break;
default:
break;
}
}
/**
* @brief BLE 광고 초기화
*
* 광고 데이터 설정: 디바이스 이름(전체), NUS UUID
* 광고 모드: Fast (40ms 간격), 지속시간 3분 (FEATURE_NO_SLEEP 시 무한)
*/
static void advertising_init(void)
{
uint32_t err_code;
ble_advertising_init_t init;
memset(&init, 0, sizeof(init));
init.advdata.name_type = BLE_ADVDATA_FULL_NAME;
init.advdata.include_appearance = true;
#if FEATURE_NO_SLEEP
init.advdata.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
#else
init.advdata.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE;
#endif
init.srdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
init.srdata.uuids_complete.p_uuids = m_adv_uuids;
init.config.ble_adv_fast_enabled = true;
init.config.ble_adv_fast_interval = APP_ADV_INTERVAL;
init.config.ble_adv_fast_timeout = APP_ADV_DURATION;
init.evt_handler = on_adv_evt;
err_code = ble_advertising_init(&m_advertising, &init);
APP_ERROR_CHECK(err_code);
ble_advertising_conn_cfg_tag_set(&m_advertising, APP_BLE_CONN_CFG_TAG);
}
#if FEATURE_SECURE_CONNECTION
/** @brief 모든 본딩(페어링) 정보를 Flash에서 삭제 */
static void delete_bonds(void)
{
ret_code_t err_code;
DBG_PRINTF("[PM] Erase bonds\r\n");
err_code = pm_peers_delete();
APP_ERROR_CHECK(err_code);
}
/**
* @brief BLE 광고 시작 (보안 모드)
*
* erase_bonds_flag가 true이면 먼저 모든 본딩 정보를 삭제한 후
* PM_EVT_PEERS_DELETE_SUCCEEDED 이벤트에서 광고를 시작한다.
* false이면 즉시 Fast 모드로 광고를 시작한다.
*/
static void advertising_start(bool erase_bonds_flag)
{
if (erase_bonds_flag == true)
{
bond_data_delete = false;
delete_bonds(); /* 삭제 완료 후 pm_evt_handler에서 광고 시작 */
}
else
{
ret_code_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);
APP_ERROR_CHECK(err_code);
}
}
#else
/** @brief BLE 광고 시작 (비보안 모드, 즉시 Fast 광고) */
static void advertising_start(void)
{
uint32_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);
APP_ERROR_CHECK(err_code);
}
#endif
/*==============================================================================
* BLE 보안 (Peer Manager)
* LESC/정적 패스키 기반 MITM 보호, 본딩 관리
*============================================================================*/
#if FEATURE_SECURE_CONNECTION
/**
* @brief Peer Manager 이벤트 핸들러
*
* BLE 보안 관련 이벤트를 처리한다:
* - CONN_SEC_SUCCEEDED: 보안 연결 성공 → MITM 보호 확인 후 연결 활성화
* - CONN_SEC_FAILED: 보안 실패 → PIN/키 누락 시 재시도
* - PEERS_DELETE_SUCCEEDED: 본딩 삭제 완료 → 광고 재시작
* - CONN_SEC_CONFIG_REQ: 재페어링 허용 설정
* - PEER_DATA_UPDATE_SUCCEEDED: 피어 데이터 갱신 → 주소 저장, 리셋 상태 업데이트
*/
static void pm_evt_handler(pm_evt_t const * p_evt)
{
pm_peer_data_bonding_t peer_bonding_data;
uint32_t return_code;
ret_code_t err_code;
/* SDK 기본 핸들러 + 보안 처리 (ble_quick_security 내부에서 SDK 핸들러 호출) */
ble_security_quick_pm_handler(p_evt);
switch (p_evt->evt_id)
{
/* 보안 연결 성공 */
case PM_EVT_CONN_SEC_SUCCEEDED:
{
pm_conn_sec_status_t conn_sec_status;
err_code = pm_conn_sec_status_get(p_evt->conn_handle, &conn_sec_status);
APP_ERROR_CHECK(err_code);
/* MITM 보호 확인 (개발 모드에서는 무조건 통과) */
if (conn_sec_status.mitm_protected || BLE_DEV_MODE)
{
DBG_PRINTF("[PM] Secured\r\n");
ble_connection_st = 1; /* 연결 상태 활성화 */
battery_timer_start(); /* 배터리 모니터링 시작 */
}
else
{
/* MITM 보호 안 됨 → 피어 삭제 후 연결 해제 */
DBG_PRINTF("[PM] Sec FAIL\r\n");
err_code = pm_peer_id_get(m_conn_handle, &m_peer_to_be_deleted);
APP_ERROR_CHECK(err_code);
err_code = sd_ble_gap_disconnect(m_conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
APP_ERROR_CHECK(err_code);
}
}
break;
/* 보안 연결 실패 (retry/disconnect는 ble_quick_security에서 처리) */
case PM_EVT_CONN_SEC_FAILED:
DBG_PRINTF("[PM] Sec failed\r\n");
break;
/* 모든 본딩 삭제 완료 → 활성 연결이 없을 때만 광고 재시작 */
case PM_EVT_PEERS_DELETE_SUCCEEDED:
DBG_PRINTF("[PM] Bonds erased\r\n");
if (m_conn_handle == BLE_CONN_HANDLE_INVALID) {
advertising_start(false);
}
break;
/* PM_EVT_CONN_SEC_CONFIG_REQ → ble_quick_security에서 처리 */
/* 피어 데이터(본딩 정보) 갱신 성공 → 피어 BLE 주소 저장 */
case PM_EVT_PEER_DATA_UPDATE_SUCCEEDED:
{
return_code = pm_peer_data_bonding_load(p_evt->peer_id, &peer_bonding_data);
if (return_code == NRF_SUCCESS)
{
memcpy(c_addr, peer_bonding_data.peer_ble_id.id_addr_info.addr, sizeof(c_addr));
}
m_reset_status = 10; /* 본딩 완료 상태 */
}
break;
default:
break;
}
}
/**
* @brief Peer Manager 초기화
*
* BLE 보안 모듈을 초기화하고 이벤트 핸들러를 등록한다.
* BLE_DEV_MODE에 따라 보안 수준이 결정된다 (0=패스키 필수, 1=보안 없음).
*/
static void peer_manager_init(void)
{
ret_code_t err_code;
ble_security_quick_init(BLE_DEV_MODE);
err_code = pm_register(pm_evt_handler);
APP_ERROR_CHECK(err_code);
DBG_PRINTF("[PM] Init (mode=%d)\r\n", BLE_DEV_MODE);
}
#endif
/*==============================================================================
* BLE 이벤트 핸들러
* SoftDevice에서 발생하는 모든 BLE 이벤트를 처리한다.
*============================================================================*/
/**
* @brief BLE 이벤트 핸들러 (SoftDevice 옵저버)
*
* 주요 이벤트 처리:
* - DISCONNECTED: 연결 해제 → 디바이스 슬립, 상태 초기화
* - CONNECTED: 연결 성공 → QWR 핸들 할당, TX 파워 +8dBm 설정
* - PHY_UPDATE_REQUEST: PHY 업데이트 자동 수락
* - TIMEOUT: 연결/GATT 타임아웃 → 강제 연결 해제
* - SEC_PARAMS_REQUEST: 보안 파라미터 요청 (보안 미사용 시 거부)
* - PASSKEY_DISPLAY: 패스키 표시 (디버그 로그)
* - AUTH_KEY_REQUEST: 정적 패스키 응답
* - HVN_TX_COMPLETE: TX 완료 → 전송 플래그 해제
*/
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
{
uint32_t err_code;
#if FEATURE_STATIC_PASSKEY
ble_gap_evt_t const * p_gap_evt = &p_ble_evt->evt.gap_evt;
#endif
#if FEATURE_SECURE_CONNECTION
if (!ble_security_is_dev_mode()) {
pm_handler_secure_on_connection(p_ble_evt);
}
#endif
switch (p_ble_evt->header.evt_id)
{
/* BLE 연결이 끊어지는 경우 */
case BLE_GAP_EVT_DISCONNECTED:
DBG_PRINTF("[BLE] Disconnected (reason 0x%02X)\r\n",
p_ble_evt->evt.gap_evt.params.disconnected.reason);
ble_connection_st = 0;
m_conn_handle = BLE_CONN_HANDLE_INVALID;
m_tx_in_progress = false;
maa_async_abort(); // 비동기 측정 상태에 의한 먹통 현상 방지
s_tx_pending = false; // pending TX 클리어
if (device_status == true)
{
if (device_sleep_mode() == 0)
{
device_status = false;
}
}
break;
case BLE_GAP_EVT_CONNECTED:
DBG_PRINTF("[BLE] Connected\r\n");
#if FEATURE_SECURE_CONNECTION
ble_connection_st = 1;
battery_timer_start();
#endif
m_conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
err_code = nrf_ble_qwr_conn_handle_assign(&m_qwr, m_conn_handle);
APP_ERROR_CHECK(err_code);
sd_ble_gap_tx_power_set(BLE_GAP_TX_POWER_ROLE_CONN, m_conn_handle, 4);
// 2M PHY 요청 (지원 안 되면 자동으로 1M 유지)
{
ble_gap_phys_t const phys = {
.rx_phys = BLE_GAP_PHY_2MBPS,
.tx_phys = BLE_GAP_PHY_2MBPS,
};
err_code = sd_ble_gap_phy_update(m_conn_handle, &phys);
APP_ERROR_CHECK(err_code);
}
led_set_state(LED_STATE_OFF); /* 연결 완료 → LED OFF */
break;
case BLE_GAP_EVT_PHY_UPDATE_REQUEST:
{
ble_gap_phys_t const phys = {
.rx_phys = BLE_GAP_PHY_AUTO,
.tx_phys = BLE_GAP_PHY_AUTO,
};
err_code = sd_ble_gap_phy_update(p_ble_evt->evt.gap_evt.conn_handle, &phys);
APP_ERROR_CHECK(err_code);
}
break;
case BLE_GAP_EVT_PHY_UPDATE:
{
ble_gap_evt_phy_update_t const * p_phy = &p_ble_evt->evt.gap_evt.params.phy_update;
DBG_PRINTF("[BLE] PHY updated: TX=%s, RX=%s\r\n",
p_phy->tx_phy == BLE_GAP_PHY_2MBPS ? "2M" : "1M",
p_phy->rx_phy == BLE_GAP_PHY_2MBPS ? "2M" : "1M");
}
break;
case BLE_GATTC_EVT_TIMEOUT:
case BLE_GATTS_EVT_TIMEOUT:
DBG_PRINTF("[BLE] GATT Timeout -> disconnect\r\n");
err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gap_evt.conn_handle,
BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
APP_ERROR_CHECK(err_code);
break;
case BLE_GAP_EVT_SEC_PARAMS_REQUEST:
#if !FEATURE_SECURE_CONNECTION
err_code = sd_ble_gap_sec_params_reply(m_conn_handle,
BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP,
NULL, NULL);
APP_ERROR_CHECK(err_code);
#else
if (ble_security_is_dev_mode()) {
err_code = sd_ble_gap_sec_params_reply(m_conn_handle,
BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP,
NULL, NULL);
APP_ERROR_CHECK(err_code);
DBG_PRINTF("DEV: Rejected security request\r\n");
}
#endif
break;
#if FEATURE_SECURE_CONNECTION
case BLE_GAP_EVT_PASSKEY_DISPLAY:
{
char passkey[PASSKEY_LENGTH + 1];
memcpy(passkey, p_ble_evt->evt.gap_evt.params.passkey_display.passkey, PASSKEY_LENGTH);
passkey[PASSKEY_LENGTH] = 0;
DBG_PRINTF("[BLE] Passkey: %s\r\n", passkey);
}
break;
case BLE_GAP_EVT_AUTH_KEY_REQUEST:
#if FEATURE_STATIC_PASSKEY
if (p_gap_evt->params.auth_key_request.key_type == BLE_GAP_AUTH_KEY_TYPE_PASSKEY)
{
err_code = sd_ble_gap_auth_key_reply(p_gap_evt->conn_handle,
BLE_GAP_AUTH_KEY_TYPE_PASSKEY,
(const uint8_t *)m_static_passkey);
APP_ERROR_CHECK(err_code);
}
#endif
break;
case BLE_GAP_EVT_LESC_DHKEY_REQUEST:
break;
#endif
case BLE_GATTS_EVT_HVN_TX_COMPLETE:
m_tx_in_progress = false;
m_tx_complete_pending = false; /* TX 완료를 대기 중인 함수에 알림 */
break;
default:
break;
}
}
/*==============================================================================
* BSP(Board Support Package) 이벤트 핸들러
*============================================================================*/
/**
* @brief BSP 이벤트 핸들러 (버튼/LED)
*
* - SLEEP: 슬립 모드 진입
* - DISCONNECT: BLE 연결 강제 해제
* - WHITELIST_OFF: 화이트리스트 없이 광고 재시작
* - POWER_CONTROL: 전원 OFF 시퀀스 시작
*/
void bsp_event_handler(bsp_event_t event)
{
uint32_t err_code;
switch (event)
{
case BSP_EVENT_SLEEP:
DBG_PRINTF("[BSP] Sleep\r\n");
go_sleep_mode_enter = true;
main_timer_start();
break;
case BSP_EVENT_DISCONNECT:
err_code = sd_ble_gap_disconnect(m_conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
if (err_code != NRF_SUCCESS) {
m_reset_status = 2;
}
break;
case BSP_EVENT_WHITELIST_OFF:
if (m_conn_handle == BLE_CONN_HANDLE_INVALID)
{
err_code = ble_advertising_restart_without_whitelist(&m_advertising);
if (err_code != NRF_ERROR_INVALID_STATE)
{
APP_ERROR_CHECK(err_code);
}
}
break;
case BSP_EVENT_POWER_CONTROL:
if (processing == false)
{
DBG_PRINTF("[BSP] Power\r\n");
led_set_state(LED_STATE_POWER_OFF);
go_device_power_off = true;
main_timer_start();
}
break;
default:
break;
}
}
/*==============================================================================
* 유틸리티 함수
*============================================================================*/
/** @brief BSP 버튼 초기화 + LED 직접 초기화 (부트 Phase 5) */
static void buttons_leds_init(bool * p_erase_bonds)
{
bsp_event_t startup_event;
uint32_t err_code = bsp_init(BSP_INIT_BUTTONS, bsp_event_handler);
APP_ERROR_CHECK(err_code);
led_init();
err_code = bsp_btn_ble_init(NULL, &startup_event);
APP_ERROR_CHECK(err_code);
*p_erase_bonds = (startup_event == BSP_EVENT_CLEAR_BONDING_DATA);
}
static void log_init(void)
{
ret_code_t err_code = NRF_LOG_INIT(NULL);
APP_ERROR_CHECK(err_code);
NRF_LOG_DEFAULT_BACKENDS_INIT();
}
/**
* @brief UART 수신 이벤트 핸들러
*
* 물리 UART(1Mbps)로 수신된 데이터를 한 바이트씩 버퍼에 축적하다가
* '\n' 또는 '\r'를 수신하면 received_command_process()로 전달한다.
* 디버그 및 공장 테스트용.
*/
void uart_event_handle(app_uart_evt_t * p_event)
{
static uint8_t data_array[BLE_NUS_MAX_DATA_LEN] = {0};
static uint8_t index = 0;
switch (p_event->evt_type)
{
case APP_UART_DATA_READY:
UNUSED_VARIABLE(app_uart_get(&data_array[index]));
index++;
if ((data_array[index - 1] == '\n') ||
(data_array[index - 1] == '\r') ||
(index >= m_ble_nus_max_data_len))
{
if (index > 1)
{
cmd_type_t = CMD_UART;
ble_got_new_data = true;
dr_cmd_parser(data_array, index);
}
index = 0;
}
break;
case APP_UART_COMMUNICATION_ERROR:
break;
case APP_UART_FIFO_ERROR:
APP_ERROR_HANDLER(p_event->data.error_code);
break;
default:
break;
}
}
/** @brief UART 초기화 (1Mbps, 흐름제어 없음, 패리티 없음) — 현재 미사용 (전력 절감) */
#if 0
static void uart_init(void)
{
uint32_t err_code;
app_uart_comm_params_t const comm_params =
{
.rx_pin_no = RX_PIN_NUMBER,
.tx_pin_no = TX_PIN_NUMBER,
.rts_pin_no = RTS_PIN_NUMBER,
.cts_pin_no = CTS_PIN_NUMBER,
.flow_control = APP_UART_FLOW_CONTROL_DISABLED,
.use_parity = false,
#if defined (UART_PRESENT)
.baud_rate = NRF_UART_BAUDRATE_1000000
#else
.baud_rate = NRF_UARTE_BAUDRATE_1000000
#endif
};
APP_UART_FIFO_INIT(&comm_params,
UART_RX_BUF_SIZE,
UART_TX_BUF_SIZE,
uart_event_handle,
APP_IRQ_PRIORITY_LOWEST,
err_code);
APP_ERROR_CHECK(err_code);
}
#endif
static void power_management_init(void)
{
ret_code_t err_code;
err_code = nrf_pwr_mgmt_init();
APP_ERROR_CHECK(err_code);
}
/**
* @brief 유휴 상태 처리
*
* BLE 보안(LESC) 요청을 처리하고, 로그를 플러시한 뒤
* nrf_pwr_mgmt_run()으로 CPU를 저전력 상태로 전환한다.
* BLE 이벤트 발생 시 자동으로 깨어난다.
*/
static void idle_state_handle(void)
{
#if FEATURE_SECURE_CONNECTION
ret_code_t err_code;
err_code = nrf_ble_lesc_request_handler();
APP_ERROR_CHECK(err_code);
#endif
if (NRF_LOG_PROCESS() == false)
{
nrf_pwr_mgmt_run();
}
}
void assert_nrf_callback(uint16_t line_num, const uint8_t * p_file_name)
{
app_error_handler(DEAD_BEEF, line_num, p_file_name);
}
/**
* @brief 슬립 모드 진입
*
* LED를 켜서 사용자에게 알린 후, 3초(POWER_OFF_DELAY) 대기 타이머를 시작한다.
* 타이머 만료 시 t_power_off_timeout_handler()에서 물리적 전원을 차단한다.
*/
void sleep_mode_enter(void)
{
led_set_state(LED_STATE_POWER_OFF);
DBG_PRINTF("[SYS] Sleep\r\n");
APP_ERROR_CHECK(app_timer_start(m_power_off_delay_timer_id, APP_TIMER_TICKS(POWER_OFF_DELAY), NULL));
}
/**
* @brief 디바이스 전원 OFF
*
* LED를 켜서 사용자에게 알린 후, 3초 대기 타이머를 시작한다.
* sleep_mode_enter()와 동일한 메커니즘으로 물리적 전원을 차단한다.
*/
void device_power_off(void)
{
led_set_state(LED_STATE_POWER_OFF);
APP_ERROR_CHECK(app_timer_start(m_power_off_delay_timer_id, APP_TIMER_TICKS(POWER_OFF_DELAY), NULL));
}
/*==============================================================================
* 데이터 전송 함수
* BLE NUS를 통해 센서 데이터/응답을 스마트폰 앱으로 전송한다.
* 패킷 구조: [데이터][CRC16 2바이트]
*============================================================================*/
/**
* @brief ASCII 텍스트 BLE 전송
*
* 문자열에서 '\r'까지의 내용을 복사하고 CRC16을 추가하여 BLE로 전송한다.
* 이미 전송 중(m_tx_in_progress)이면 즉시 리턴한다.
*/
void data_tx_handler(char const *p_data_to_send)
{
if (m_tx_in_progress) return;
char const *p_end_char = strchr(p_data_to_send, '\r');
if (p_end_char == NULL) return;
uint16_t data_len = p_end_char - p_data_to_send;
if (data_len > (BLE_NUS_MAX_DATA_LEN - 2)) return;
memcpy(m_tx_buffer, p_data_to_send, data_len);
uint16_t crc = crc16_compute(m_tx_buffer, data_len, NULL);
m_tx_buffer[data_len] = (uint8_t)(crc & 0xFF);
m_tx_buffer[data_len + 1] = (uint8_t)((crc >> 8) & 0xFF);
m_tx_len = data_len + 2;
if (ble_connection_st == BLE_CONNECTED_ST)
{
m_tx_in_progress = true;
uint32_t err_code = ble_nus_data_send(&m_nus, m_tx_buffer, &m_tx_len, m_conn_handle);
if (err_code == NRF_SUCCESS) {
// OK
} else if (err_code == NRF_ERROR_RESOURCES) {
// Retry later
} else if (err_code == NRF_ERROR_INVALID_STATE || err_code == NRF_ERROR_NOT_FOUND) {
ble_connection_st = BLE_DISCONNECTED_ST;
m_conn_handle = BLE_CONN_HANDLE_INVALID;
m_tx_in_progress = false;
} else {
DBG_PRINTF("[BLE TX] Err:0x%X\r\n", err_code); // APP_ERROR_CHECK 대신 로그 - jhChun 26.03.16
m_tx_in_progress = false;
//APP_ERROR_CHECK(err_code);
}
}
}
/**
* @brief 단일 uint16_t 값을 바이너리 패킷으로 포맷
*
* 패킷 구조: [태그 4바이트][값 2바이트] = 총 6바이트 (3 워드)
* 태그는 ASCII 4문자 (예: "rta:", "rsn:"), 값은 빅엔디안으로 저장
*/
void single_format_data(uint8_t *buffer, const char *tag, const uint16_t value)
{
uint16_t tag1 = ((uint16_t)tag[1] << 8) | (uint16_t)tag[0];
uint16_t tag2 = ((uint16_t)tag[3] << 8) | (uint16_t)tag[2];
buffer[0] = (uint8_t)(tag1 & 0xFF);
buffer[1] = (uint8_t)((tag1 >> 8) & 0xFF);
buffer[2] = (uint8_t)(tag2 & 0xFF);
buffer[3] = (uint8_t)((tag2 >> 8) & 0xFF);
buffer[5] = (uint8_t)(value & 0xFF);
buffer[4] = (uint8_t)((value >> 8) & 0xFF);
}
/**
* @brief uint16_t 배열을 바이너리 패킷으로 포맷
*
* 패킷 구조: [태그 4바이트][데이터0 2바이트][데이터1 2바이트]...
* 각 데이터는 빅엔디안으로 저장, length는 바이트 수가 아닌 배열 원소 수
*/
void format_data(uint8_t *buffer, const char *tag, const uint16_t *data_array, size_t length)
{
uint16_t tag1 = ((uint16_t)tag[1] << 8) | (uint16_t)tag[0];
uint16_t tag2 = ((uint16_t)tag[3] << 8) | (uint16_t)tag[2];
buffer[0] = (uint8_t)(tag1 & 0xFF);
buffer[1] = (uint8_t)((tag1 >> 8) & 0xFF);
buffer[2] = (uint8_t)(tag2 & 0xFF);
buffer[3] = (uint8_t)((tag2 >> 8) & 0xFF);
for (size_t i = 0; i < length; i++) {
buffer[4 + i * 2 + 1] = (uint8_t)(data_array[i] & 0xFF);
buffer[4 + i * 2] = (uint8_t)((data_array[i] >> 8) & 0xFF);
}
}
/**
* @brief uint8_t 바이트 배열을 바이너리 패킷으로 포맷
*
* 패킷 구조: [태그 4바이트][바이트0][바이트1]...
*/
void format_data_byte(uint8_t *buffer, const char *tag, const uint8_t *data_array, size_t length)
{
uint16_t tag1 = ((uint16_t)tag[1] << 8) | (uint16_t)tag[0];
uint16_t tag2 = ((uint16_t)tag[3] << 8) | (uint16_t)tag[2];
buffer[0] = (uint8_t)(tag1 & 0xFF);
buffer[1] = (uint8_t)((tag1 >> 8) & 0xFF);
buffer[2] = (uint8_t)(tag2 & 0xFF);
buffer[3] = (uint8_t)((tag2 >> 8) & 0xFF);
for (size_t i = 0; i < length; i++) {
buffer[4 + i] = (uint8_t)(data_array[i] & 0xFF);
}
}
/**
* @brief ASCII 문자열을 바이너리 패킷으로 포맷
*
* 패킷 구조: [태그 4바이트][문자0][문자1]...
* 시리얼 번호, 패스키, 펌웨어 버전 등 문자열 전송에 사용
*/
void ascii_format_data(uint8_t *buffer, const char *tag, const char *data_ascii, size_t length)
{
uint16_t tag1 = ((uint16_t)tag[1] << 8) | (uint16_t)tag[0];
uint16_t tag2 = ((uint16_t)tag[3] << 8) | (uint16_t)tag[2];
buffer[0] = (uint8_t)(tag1 & 0xFF);
buffer[1] = (uint8_t)((tag1 >> 8) & 0xFF);
buffer[2] = (uint8_t)(tag2 & 0xFF);
buffer[3] = (uint8_t)((tag2 >> 8) & 0xFF);
for (size_t i = 0; i < length; i++) {
buffer[4 + i] = (uint8_t)(data_ascii[i] & 0xFF);
}
}
/**
* @brief BLE 바이너리 안전 전송 (dr_binary_tx_safe용 선행 설명)
*
* NRF_ERROR_RESOURCES(TX 큐 가득 참) 발생 시 5ms 간격으로 재시도하고,
* 연결 오류 시 graceful하게 리턴한다.
*
* @param ble_bin_buff 전송할 데이터 버퍼
* @param length uint16_t 워드 수 (실제 바이트 = length * 2)
*/
/**
* @brief BLE 작업용 안전한 딜레이
*
* 인터럽트 안전한 nrf_delay_ms()를 사용한다.
* BLE 스택은 인터럽트로 동작하므로 딜레이 중에도 TX 완료 이벤트가 처리된다.
*
* 주의: __WFE()나 sd_app_evt_wait()를 여기서 사용하면 안 됨!
* SoftDevice 전원 관리와 충돌한다.
*
* @param ms 딜레이 시간 (밀리초)
*/
void dr_sd_delay_ms(uint32_t ms)
{
if (ms == 0) return;
nrf_delay_ms(ms);
}
extern int SEGGER_RTT_vprintf(unsigned, const char *, va_list *);
static void log_printf(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
SEGGER_RTT_vprintf(0, fmt, &args);
va_end(args);
}
void param_error(const char *cmd)
{
char resp_error[4];
resp_error[0] = 'r';
resp_error[1] = cmd[1];
resp_error[2] = cmd[2];
resp_error[3] = '!';
single_format_data(ble_bin_buffer, resp_error, 65533); /* err_code3 */
dr_binary_tx_safe(ble_bin_buffer, 3);
}
void dr_binary_tx_safe(uint8_t const *ble_bin_buff, uint16_t length)
{
uint32_t err_code;
static uint8_t tx_buffer[BLE_NUS_MAX_DATA_LEN] = {0};
uint16_t retry_count = 0;
const uint16_t MAX_RETRIES = 20; /* 최대 재시도: 2ms × 20 = 40ms (기존 5ms × 100 = 500ms) */
if (ble_connection_st == 0) return;
if (length * sizeof(uint16_t) > (BLE_NUS_MAX_DATA_LEN - 2)) return;
if (ble_connection_st == BLE_CONNECTED_ST)
{
memcpy(tx_buffer, ble_bin_buff, length * sizeof(uint16_t));
uint16_t crc = crc16_compute(tx_buffer, length * sizeof(uint16_t), NULL);
tx_buffer[length * sizeof(uint16_t)] = (uint8_t)(crc & 0xFF);
tx_buffer[length * sizeof(uint16_t) + 1] = (uint8_t)((crc >> 8) & 0xFF);
uint16_t total_len = length * sizeof(uint16_t) + 2;
do {
uint16_t send_len = total_len;
err_code = ble_nus_data_send(&m_nus, tx_buffer, &send_len, m_conn_handle);
if (err_code == NRF_SUCCESS)
{
return;
}
else if (err_code == NRF_ERROR_RESOURCES)
{
nrf_delay_ms(2); /* 기존 5ms → 2ms */
retry_count++;
}
else if (err_code == NRF_ERROR_INVALID_STATE || err_code == NRF_ERROR_NOT_FOUND)
{
DBG_PRINTF("[BLE TX] Disconnected\r\n");
return;
}
else
{
DBG_PRINTF("[BLE TX] Err:0x%X\r\n", err_code);
return;
}
} while (retry_count < MAX_RETRIES);
if (retry_count >= MAX_RETRIES)
{
DBG_PRINTF("[BLE TX] FAIL %u retries\r\n", retry_count);
}
}
}
/*==============================================================================
* 전원 버튼 상태머신 (main_s)
*
* 5ms 간격의 싱글샷 타이머(m_power_on_delay_timer)에 의해 반복 호출된다.
* 버튼 릴리즈 시점의 cnt_s 값에 따라 동작이 결정된다:
* - cnt_s < 150 (< 0.75초): 짧은 눌림 → 전원 OFF
* - cnt_s ≥ 150 (~0.75초): 정상 부팅 시퀀스 시작
* - cnt_s > 1000 (~5초): 공장 초기화 (패스키 리셋 + 전원 OFF)
* - m_reset_status == 2: 소프트웨어 리셋 후 재부팅
*============================================================================*/
static void main_s(void * p_context)
{
UNUSED_PARAMETER(p_context);
APP_ERROR_CHECK(app_timer_stop(m_power_on_delay_timer_id));
bool button_released = nrf_gpio_pin_read(POWER_BUTTON);
if (button_released)
{
if ((cnt_s < 150) && (m_reset_status != 2))
{
DBG_PRINTF("[BTN] Short->OFF\r\n");
led_set_state(LED_STATE_OFF);
power_control_handler(OFF);
cnt_s = 0;
}
else if (cnt_s > 1000)
{
DBG_PRINTF("[BTN] Long->Reset\r\n");
power_control_handler(ON);
nrf_delay_ms(100);
bond_data_delete = true;
m_config.bond_data_delete = (uint8_t)bond_data_delete;
const char pass_init[PASSKEY_LENGTH] = DEFAULT_PASSKEY;
memcpy(m_config.static_passkey, pass_init, PASSKEY_LENGTH);
config_save();
nrf_delay_ms(1000);
go_device_power_off = true;
main_timer_start();
}
else if (cnt_s > 150 || (m_reset_status == 2))
{
DBG_PRINTF("[BTN] Boot (cnt=%d)\r\n", cnt_s);
device_reset = false;
power_control_handler(ON);
battery_timer_start();
#if FEATURE_SECURE_CONNECTION
/* DEV 모드: ble_security_quick_init()에서 이미 bond 삭제됨 → 중복 방지 */
advertising_start(!ble_security_is_dev_mode() && (erase_bonds || bond_data_delete));
#else
advertising_start();
#endif
DBG_PRINTF("[BOOT] ADV started\r\n");
m_reset_status = 1;
DBG_PRINTF("[BOOT] Ready\r\n");
}
}
else
{
cnt_s++;
device_reset = false;
if (cnt_s == 150) {
led_set_state(LED_STATE_POWER_ON);
DBG_PRINTF("[BTN] 1.5s\r\n");
}
timers_start();
}
}
/*==============================================================================
* 메인 함수
*
* 시스템 초기화를 단계별(Phase 0~9)로 수행한 뒤 무한 루프에 진입한다.
* 무한 루프에서는 idle_state_handle()로 CPU를 저전력 상태로 유지하며,
* BLE/타이머 인터럽트에 의해 깨어나 이벤트를 처리한다.
*============================================================================*/
int main(void)
{
bool erase_bonds_local = false;
/*──────────────────────────────────────────────────────────────
* Phase 1: 하드웨어 기본 초기화 (BLE 무관)
*
* - 전원 자가유지 래치 (P0.8 HIGH)
* - RTT 로그 출력
* - GPIO (전원 버튼 입력)
* - 앱 타이머 (전원 폴링, 배터리, 메인 루프)
* - 기본 설정값 (시리얼 번호, 패스키)
* - 버튼/LED (BSP)
*────────────────────────────────────────────────────────────*/
power_hold_init();
if (power_off_duble_prohibit) return 0;
cnt_s = 0;
log_init();
DBG_PRINTF("\r\n========================================\r\n");
DBG_PRINTF(" Medithings v1.17\r\n");
DBG_PRINTF("========================================\r\n");
DBG_PRINTF("[1] HW Init\r\n");
gpio_init();
info4 = false;
timers_init();
load_default_config();
buttons_leds_init(&erase_bonds_local);
#if FEATURE_SECURE_CONNECTION
erase_bonds = erase_bonds_local;
#endif
DBG_PRINTF(" gpio/timer/config/btn OK\r\n");
/*──────────────────────────────────────────────────────────────
* Phase 2: BLE 스택 초기화
*
* - 전원 관리 모듈 (idle 시 WFE/슬립)
* - SoftDevice S140 (BLE 5.0 프로토콜 스택)
* - DC-DC 컨버터 활성화 (SoftDevice 이후 필수)
*────────────────────────────────────────────────────────────*/
DBG_PRINTF("[2] BLE Stack\r\n");
power_management_init();
ble_stack_init();
APP_ERROR_CHECK(sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE));
DBG_PRINTF(" pwr/stack/dcdc OK\r\n");
/*──────────────────────────────────────────────────────────────
* Phase 3: 내장 플래시 설정 (BLE 스택 이후에 초기화해야 함)
*
* - FDS(Flash Data Storage) 초기화
* - 플래시에서 저장된 설정 읽기 (시리얼, 패스키, 피에조 등)
* - 기본값 위에 플래시 값 덮어쓰기
*────────────────────────────────────────────────────────────*/
DBG_PRINTF("[3] FDS\r\n");
fs_storage_init();
config_load();
load_flash_config();
DBG_PRINTF(" fds OK\r\n");
/*──────────────────────────────────────────────────────────────
* Phase 4: BLE 프로토콜 설정
*
* - GAP: 디바이스 이름(시리얼 번호), 연결 파라미터, 패스키
* - GATT: MTU 크기 협상
* - 서비스: NUS(Nordic UART Service), QWR, DFU
* - Advertising: 광고 데이터 구성 (이름, UUID)
* - 연결 파라미터 협상 모듈
* - 보안: Peer Manager (본딩/패스키)
*────────────────────────────────────────────────────────────*/
DBG_PRINTF("[4] BLE Protocol\r\n");
gap_params_init();
gatt_init();
services_init();
advertising_init();
conn_params_init();
#if FEATURE_SECURE_CONNECTION
peer_manager_init();
#endif
DBG_PRINTF(" gap/gatt/svc/adv/conn/sec OK\r\n");
if (m_need_save_defaults) {
config_save();
m_need_save_defaults = false;
DBG_PRINTF(" fds defaults saved\r\n");
}
/*──────────────────────────────────────────────────────────────
* Phase 5: 애플리케이션 초기화
*
* - 명령 파서: BLE 수신 명령 처리 (로그, BLE 전송, CRC)
* - 피에조 드라이버: 초음파 측정용 GPIO/Timer/PPI 설정
* - IMU(ICM42670P)는 여기서 초기화하지 않음
* → msp? 명령 시 imu_read_direct()가 매번 자체 설정/읽기/슬립 처리
*────────────────────────────────────────────────────────────*/
DBG_PRINTF("[5] App\r\n");
g_plat.log = log_printf;
g_plat.tx_bin = dr_binary_tx_safe;
g_plat.crc_check = true;
g_log_enable = true;
dr_piezo_init();
DBG_PRINTF(" parser/piezo OK\r\n");
/*──────────────────────────────────────────────────────────────
* Boot 완료 → 전원 버튼 상태머신 시작
*────────────────────────────────────────────────────────────*/
DBG_PRINTF("\r\n========================================\r\n");
DBG_PRINTF(" READY [%s]\r\n", SERIAL_NO);
DBG_PRINTF("========================================\r\n\r\n");
timers_start();
// MAIN LOOP
for (;;)
{
/* BLE 콜백에서 수신된 명령이 있으면 여기서 처리 (블로킹 방지) */
if (pending_cmd_len > 0) {
uint8_t len = pending_cmd_len;
pending_cmd_len = 0;
dr_cmd_parser((const uint8_t *)pending_cmd_buf, len);
}
idle_state_handle();
}
}