- main.c: BLE disconnected -> maa_async_abort() 함수 호출 - dr_adc121s051c: maa_async_abort() 함수에서 상태를 IDLE로 초기화 및 정리
1813 lines
69 KiB
C
1813 lines
69 KiB
C
/*******************************************************************************
|
|
* @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 0: 전원 유지 (power_hold_init → P0.8 HIGH)
|
|
* Phase 1: UART 초기화 (1Mbps), 로그 초기화
|
|
* Phase 2: GPIO 초기화 (DEBUG_MINIMAL_BOOT에 따라 분기)
|
|
* Phase 3: 타이머 초기화 (app_timer, 배터리, 전원 시퀀스)
|
|
* Phase 4: 설정 로드 (기본값 → FDS에서 덮어쓰기)
|
|
* Phase 5: 버튼/LED BSP 초기화
|
|
* Phase 6: BLE 스택 (SoftDevice → GAP → GATT → NUS → Advertising)
|
|
* Phase 6.5: FDS(Flash Data Storage) 초기화 및 설정 로드
|
|
* Phase 7: BLE 보안 (Peer Manager, LESC/정적 패스키)
|
|
* Phase 8: 부트 완료 → 전원 버튼 상태머신(main_s) 시작
|
|
* Phase 9: idle 루프 (nrf_pwr_mgmt_run)
|
|
*
|
|
* [전원 버튼 상태머신 — 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회 재시도)
|
|
*
|
|
* [빌드 모드]
|
|
* DEBUG_MINIMAL_BOOT = 1 : 전원+BLE만 (센서/EEPROM 초기화 생략, 개발용)
|
|
* DEBUG_MINIMAL_BOOT = 0 : 전체 초기화 (양산용)
|
|
* 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" /* 시스템 인터페이스 (센서/EEPROM 제어 추상화) */
|
|
#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" /* 피에조 초음파 드라이버 */
|
|
|
|
|
|
#define HARDWARE_VERSION "VB0HW0000"
|
|
#define FIRMWARE_VERSION "VB0FW0000"
|
|
#define FIRMWARE_SERIAL_NO "VB026030000"
|
|
|
|
|
|
/*==============================================================================
|
|
* 빌드 설정
|
|
*============================================================================*/
|
|
#define BLE_DEV_MODE 1 /* 1: 개발 모드 (보안 없음), 0: 양산 모드 (패스키 필수) */
|
|
#define DEBUG_MINIMAL_BOOT 1 /* 1: 최소 부팅 (전원+BLE만), 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(20, UNIT_1_25_MS) /* 최소 연결 간격: 20ms */
|
|
#define MAX_CONN_INTERVAL MSEC_TO_UNITS(75, UNIT_1_25_MS) /* 최대 연결 간격: 75ms */
|
|
#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 진행 중 플래그 */
|
|
|
|
/* 2026-03-17: cmd_parse.c에서 main.c로 이동한 전역변수 */
|
|
char m_static_passkey[PASSKEY_LENGTH] = "123456"; /* 정적 패스키 (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 초기화 (DEBUG_MINIMAL_BOOT 모드용)
|
|
*
|
|
* 전원 버튼 입력과 전원 유지 핀만 설정
|
|
* 센서 관련 GPIO는 초기화하지 않음
|
|
*/
|
|
static void minimal_gpio_init(void)
|
|
{
|
|
nrf_gpio_cfg_input(POWER_BUTTON, NRF_GPIO_PIN_NOPULL);
|
|
nrf_gpio_cfg_output(POWER_HOLD);
|
|
nrf_gpio_pin_set(POWER_HOLD);
|
|
power_gpio_init();
|
|
|
|
DBG_PRINTF("[GPIO] Minimal OK (BTN=%d)\r\n", nrf_gpio_pin_read(POWER_BUTTON));
|
|
}
|
|
|
|
#if !DEBUG_MINIMAL_BOOT
|
|
/**
|
|
* @brief 전체 GPIO 초기화 (양산 모드, DEBUG_MINIMAL_BOOT=0)
|
|
*
|
|
* minimal_gpio_init()를 먼저 호출한 뒤, EEPROM 전원을 OFF하여
|
|
* 불필요한 전력 소모를 방지한다.
|
|
*/
|
|
static void full_gpio_init(void)
|
|
{
|
|
minimal_gpio_init();
|
|
|
|
eeprom_control(OFF);
|
|
|
|
DBG_PRINTF("[GPIO] Full OK\r\n");
|
|
}
|
|
#endif
|
|
|
|
/*==============================================================================
|
|
* 설정 로드
|
|
* 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, FIRMWARE_SERIAL_NO, strlen(FIRMWARE_SERIAL_NO));
|
|
|
|
memset(m_static_passkey, 0, PASSKEY_LENGTH);
|
|
memcpy(m_static_passkey, "123456", 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, FIRMWARE_SERIAL_NO, strlen(FIRMWARE_SERIAL_NO));
|
|
DBG_PRINTF("[CFG] S/N empty, set default: %s\r\n", FIRMWARE_SERIAL_NO);
|
|
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");
|
|
bsp_indication_set(BSP_INDICATE_USER_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);
|
|
|
|
/* 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;
|
|
|
|
/* 콜백에서는 복사만 하고, 메인 루프에서 처리 */
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @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)
|
|
{
|
|
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:
|
|
bsp_indication_set(BSP_INDICATE_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 기본 핸들러 체인 호출 */
|
|
pm_handler_on_pm_evt(p_evt);
|
|
pm_handler_disconnect_on_sec_failure(p_evt);
|
|
pm_handler_flash_clean(p_evt);
|
|
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;
|
|
|
|
/* 보안 연결 실패 → PIN/키 누락 시 강제 재페어링 시도 */
|
|
case PM_EVT_CONN_SEC_FAILED:
|
|
DBG_PRINTF("[PM] Sec failed\r\n");
|
|
if (p_evt->params.conn_sec_failed.error == PM_CONN_SEC_ERROR_PIN_OR_KEY_MISSING)
|
|
{
|
|
err_code = pm_conn_secure(p_evt->conn_handle, true);
|
|
if (err_code != NRF_ERROR_INVALID_STATE)
|
|
{
|
|
APP_ERROR_CHECK(err_code);
|
|
}
|
|
}
|
|
break;
|
|
|
|
/* 모든 본딩 삭제 완료 → 광고 재시작 */
|
|
case PM_EVT_PEERS_DELETE_SUCCEEDED:
|
|
advertising_start(false);
|
|
break;
|
|
|
|
/* 보안 설정 요청 → 재페어링 허용 */
|
|
case PM_EVT_CONN_SEC_CONFIG_REQ:
|
|
{
|
|
pm_conn_sec_config_t conn_sec_config = {.allow_repairing = true};
|
|
pm_conn_sec_config_reply(p_evt->conn_handle, &conn_sec_config);
|
|
}
|
|
break;
|
|
|
|
/* 피어 데이터(본딩 정보) 갱신 성공 → 피어 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
|
|
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\r\n");
|
|
ble_connection_st = 0;
|
|
m_conn_handle = BLE_CONN_HANDLE_INVALID;
|
|
m_tx_in_progress = false;
|
|
|
|
maa_async_abort(); // 비동기 측정 상태에 의한 먹통 현상 방지
|
|
|
|
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, 8);
|
|
|
|
err_code = bsp_indication_set(BSP_INDICATE_CONNECTED);
|
|
APP_ERROR_CHECK(err_code);
|
|
|
|
#if DEBUG_MINIMAL_BOOT
|
|
DBG_PRINTF("[BLE] Minimal mode\r\n");
|
|
#endif
|
|
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_GATTC_EVT_TIMEOUT:
|
|
case BLE_GATTS_EVT_TIMEOUT:
|
|
DBG_PRINTF("[BLE] Timeout\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);
|
|
#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");
|
|
bsp_indication_set(BSP_INDICATE_USER_STATE_ON);
|
|
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_LEDS | BSP_INIT_BUTTONS, bsp_event_handler);
|
|
APP_ERROR_CHECK(err_code);
|
|
|
|
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, 흐름제어 없음, 패리티 없음) */
|
|
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);
|
|
}
|
|
|
|
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)
|
|
{
|
|
bsp_indication_set(BSP_INDICATE_USER_STATE_ON);
|
|
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)
|
|
{
|
|
bsp_indication_set(BSP_INDICATE_USER_STATE_ON);
|
|
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);
|
|
}
|
|
|
|
/* 2026-03-17: cmd_parse.c에서 main.c로 이동 */
|
|
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);
|
|
}
|
|
|
|
/* 기존 binary_tx_handler 함수 삭제 및 호출처 교체 - jhChun 26.03.16 */
|
|
void dr_binary_tx_safe(uint8_t const *ble_bin_buff, uint16_t length) // BLE로 바이너리 데이터를 안전하게 전송하는 함수
|
|
{
|
|
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 = 100; /* 최대 재시도 횟수 (~500ms, 5ms x 100) */
|
|
|
|
if (ble_connection_st == 0) return; // 연결 확인
|
|
|
|
data_tx_in_progress = true;
|
|
|
|
if (length * sizeof(uint16_t) > (BLE_NUS_MAX_DATA_LEN - 2)) { // 길이 검증 (CRC 2바이트 공간 확보)
|
|
data_tx_in_progress = false;
|
|
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); // CRC16 2바이트 추가
|
|
|
|
uint16_t total_len = length * sizeof(uint16_t) + 2;
|
|
|
|
/* 재시도 루프 - 재시도 사이에 SoftDevice가 이벤트를 처리할 수 있도록 함 */
|
|
do {
|
|
uint16_t send_len = total_len; /* 매 반복마다 리셋 필수 - ble_nus_data_send()가 값을 변경함! */
|
|
err_code = ble_nus_data_send(&m_nus, tx_buffer, &send_len, m_conn_handle);
|
|
|
|
if (err_code == NRF_SUCCESS) { // 전송 성공 -> 루프 탈출
|
|
/* TX queued successfully */
|
|
break;
|
|
} else if (err_code == NRF_ERROR_RESOURCES) { // TX 큐가 가득 찬 경우 5ms 대기 후 재시도 (최대 100회, ~500ms)
|
|
/* BLE TX 큐 가득 참 - 연결 이벤트가 TX를 완료할 때까지 대기 */
|
|
/* BLE 스택이 TX 완료 이벤트를 처리할 수 있도록 짧은 딜레이 */
|
|
nrf_delay_ms(5); /* ~5ms 대기하여 TX 슬롯 확보 */
|
|
retry_count++;
|
|
} else if (err_code == NRF_ERROR_INVALID_STATE || err_code == NRF_ERROR_NOT_FOUND) { // 연결 끊김, 리턴
|
|
DBG_PRINTF("[BLE TX] Disconnected\r\n");
|
|
data_tx_in_progress = false;
|
|
return;
|
|
} else { // 기타 에러 -> 로그 출력 후 리턴
|
|
DBG_PRINTF("[BLE TX] Err:0x%X\r\n", err_code);
|
|
data_tx_in_progress = false;
|
|
return;
|
|
}
|
|
} while (retry_count < MAX_RETRIES);
|
|
|
|
if (retry_count > 0) {
|
|
DBG_PRINTF("[BLE TX] retry=%u (+%ums)\r\n", retry_count, retry_count * 5);
|
|
}
|
|
if (retry_count >= MAX_RETRIES) { // 최대 재시도(100회) 초과 시 해당 패킷 드롭, 연결은 유지
|
|
DBG_PRINTF("[BLE TX] FAIL %u retries\r\n", retry_count);
|
|
data_tx_in_progress = false;
|
|
/* ble_connection_st = 0 설정하지 않음 - 해당 패킷만 드롭하고 연결은 유지 */
|
|
return;
|
|
}
|
|
|
|
data_tx_in_progress = false;
|
|
}
|
|
}
|
|
|
|
/*==============================================================================
|
|
* 전원 버튼 상태머신 (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");
|
|
bsp_indication_set(BSP_INDICATE_USER_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] = "123456";
|
|
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 DEBUG_MINIMAL_BOOT
|
|
DBG_PRINTF("[BOOT] Minimal\r\n");
|
|
#if FEATURE_SECURE_CONNECTION
|
|
advertising_start(erase_bonds);
|
|
#else
|
|
advertising_start();
|
|
#endif
|
|
DBG_PRINTF("[BOOT] ADV started\r\n");
|
|
#else
|
|
DBG_PRINTF("[BOOT] Full\r\n");
|
|
icm42670_init();
|
|
nrf_delay_ms(2);
|
|
|
|
#if FEATURE_SECURE_CONNECTION
|
|
advertising_start(erase_bonds);
|
|
#else
|
|
advertising_start();
|
|
#endif
|
|
|
|
|
|
m_reset_status = 1;
|
|
m_config.reset_status = m_reset_status;
|
|
config_save();
|
|
#endif
|
|
m_reset_status = 1;
|
|
DBG_PRINTF("[BOOT] Ready\r\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cnt_s++;
|
|
device_reset = false;
|
|
|
|
if (cnt_s == 150) {
|
|
bsp_indication_set(BSP_INDICATE_USER_STATE_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 0: 전원 유지
|
|
power_hold_init();
|
|
|
|
// PHASE 1: 기본 시스템
|
|
if (power_off_duble_prohibit) return 0;
|
|
cnt_s = 0;
|
|
|
|
uart_init();
|
|
log_init();
|
|
|
|
DBG_PRINTF("\r\n========================================\r\n");
|
|
DBG_PRINTF(" Medithings v1.17 [%s]\r\n", DEBUG_MINIMAL_BOOT ? "MIN" : "FULL");
|
|
DBG_PRINTF("========================================\r\n");
|
|
DBG_PRINTF("[0] PWR_HOLD\r\n");
|
|
|
|
// PHASE 2: GPIO
|
|
DBG_PRINTF("[1] GPIO\r\n");
|
|
#if DEBUG_MINIMAL_BOOT
|
|
minimal_gpio_init();
|
|
#else
|
|
full_gpio_init();
|
|
#endif
|
|
|
|
info4 = false;
|
|
|
|
// PHASE 3: 타이머
|
|
DBG_PRINTF("[2] Timers\r\n");
|
|
timers_init();
|
|
|
|
// PHASE 4: 설정 (기본값 로드)
|
|
DBG_PRINTF("[3] Config (defaults)\r\n");
|
|
load_default_config();
|
|
|
|
// PHASE 5: 버튼/LED
|
|
DBG_PRINTF("[4] Buttons\r\n");
|
|
buttons_leds_init(&erase_bonds_local);
|
|
|
|
#if FEATURE_SECURE_CONNECTION
|
|
erase_bonds = erase_bonds_local;
|
|
#endif
|
|
|
|
// PHASE 6: BLE 스택
|
|
DBG_PRINTF("[5] BLE\r\n");
|
|
|
|
power_management_init();
|
|
DBG_PRINTF(" pwr OK\r\n");
|
|
|
|
ble_stack_init();
|
|
DBG_PRINTF(" stack OK\r\n");
|
|
|
|
// PHASE 6.5: 내장 Flash (FDS) - BLE 스택 이후에 초기화해야 함
|
|
DBG_PRINTF("[5.5] FDS\r\n");
|
|
fs_storage_init();
|
|
config_load();
|
|
load_flash_config();
|
|
DBG_PRINTF(" fds OK\r\n");
|
|
|
|
gap_params_init();
|
|
DBG_PRINTF(" gap OK\r\n");
|
|
|
|
gatt_init();
|
|
DBG_PRINTF(" gatt OK\r\n");
|
|
|
|
services_init();
|
|
DBG_PRINTF(" svc OK\r\n");
|
|
|
|
advertising_init();
|
|
DBG_PRINTF(" adv OK\r\n");
|
|
|
|
conn_params_init();
|
|
DBG_PRINTF(" conn OK\r\n");
|
|
|
|
// PHASE 7: 보안
|
|
DBG_PRINTF("[6] Security\r\n");
|
|
#if FEATURE_SECURE_CONNECTION
|
|
peer_manager_init();
|
|
#endif
|
|
|
|
// PHASE 7.5: FDS 기본값 저장 (advertising 이후)
|
|
if (m_need_save_defaults) {
|
|
DBG_PRINTF("[FDS] Saving defaults after ADV\r\n");
|
|
config_save();
|
|
m_need_save_defaults = false;
|
|
}
|
|
|
|
// PHASE 7.6: 새 파서(dr_cmd_parser) 초기화
|
|
g_plat.log = log_printf;
|
|
g_plat.tx_bin = dr_binary_tx_safe;
|
|
g_plat.crc_check = true;
|
|
g_log_enable = true;
|
|
DBG_PRINTF(" parser OK\r\n");
|
|
|
|
// PHASE 7.7: 피에조 드라이버 초기화 (GPIO/GPIOTE/Timer/PPI + 전원)
|
|
dr_piezo_system_init();
|
|
DBG_PRINTF(" piezo OK\r\n");
|
|
|
|
// PHASE 8: 완료
|
|
DBG_PRINTF("\r\n========================================\r\n");
|
|
DBG_PRINTF(" READY [%s]\r\n", SERIAL_NO);
|
|
DBG_PRINTF("========================================\r\n\r\n");
|
|
|
|
// PHASE 9: 시작
|
|
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();
|
|
}
|
|
}
|