diff --git a/pc_firm/parser.c b/pc_firm/parser.c index a70343e..4574aae 100644 --- a/pc_firm/parser.c +++ b/pc_firm/parser.c @@ -1,1466 +1,1189 @@ -/* - * 2025-12-08 power loop bug fix - * 2025-12-07 msn, mta, mqq - * 2025-12-04 by Charles KWON - * parser.c : Common parser + command table + handlers - * - Firmware/PC shared - * - Hardware-dependent parts left as TODO - * - Added CRC16 validation support - */ - -#include "parser.h" -#include - -#include "nrf_gpio.h" -#include "nrf_delay.h" -#include "dr_piezo.h" -#include "dr_util.h" -#include "dr_adc121s051.h" - - - -// ======================================== -// External function declarations -// ======================================== - -/* Sensor functions */ -extern void battery_level_meas(void); -extern void pressure_all_level_meas(void); -extern void tmp235_voltage_level_meas(void); - - -/* Device control functions */ -extern int device_activated(void); -extern int device_sleep_mode(void); - -/* Error handling */ -extern void param_error(const char *cmd); - -/* BLE transmission */ -extern void single_format_data(uint8_t *buffer, const char *tag, uint16_t value); -extern void ascii_format_data(uint8_t *buffer, const char *tag, const char *ascii, uint8_t len); -extern void dr_binary_tx_safe(const uint8_t *buffer, uint16_t length); -extern void dr_sd_delay_ms(uint32_t ms); /* Softdevice-friendly delay */ - -/* FDS config (fstorage) */ -#include "fstorage.h" -extern char SERIAL_NO[12]; -extern char HW_NO[12]; -extern char m_static_passkey[6]; -extern uint32_t m_life_cycle; -extern void format_data(uint8_t *buffer, const char *tag, const uint16_t *data_array, size_t length); - -/* FW Version - must match cmd_parse.c DEVICE_VERSION */ -#define DR_DEVICE_VERSION "FW25LIT2B102" - - - - -// ======================================== -// External variables -// ======================================== - -extern volatile bool processing; -extern bool device_status; -extern uint8_t resetCount; -extern uint8_t ble_bin_buffer[]; - -extern bool con_single; -extern bool lock_check; - - -extern bool info4; // addtional info -extern bool ble_got_new_data; // BLE data flag -extern bool go_batt; // battery -extern bool motion_data_once; // IMU data flag -extern bool motion_raw_data_enabled; // IMU continuous flag -extern int imu_read_direct(void); // IMU direct register read + BLE send - -extern void pressure_all_level_meas(void); // pressure sensor -extern void battery_timer_stop(void); // battery timer -extern void main_timer_start(void); // main timer -extern void hw_i2c_init_once(void); // I2C init for IMU - -/* Power / Reset / Bond */ -extern bool go_device_power_off; -extern bool go_NVIC_SystemReset; -extern bool bond_data_delete; -extern uint8_t m_reset_status; -extern void config_save(void); -extern config_data_t m_config; - -/* AGC_GAIN_SW is a macro in measurements.h - replicate here */ -#include "nrf_gpio.h" -#define GAIN_SW_PIN NRF_GPIO_PIN_MAP(0, 20) -#define AGC_GAIN_SW(x) do { if(x) nrf_gpio_pin_set(GAIN_SW_PIN); else nrf_gpio_pin_clear(GAIN_SW_PIN); } while(0) - - -extern void dr_piezo_power_on( void ); -extern void dr_piezo_power_off( void ); -extern void dr_piezo_burst_sw(uint8_t cycles); -extern void dr_piezo_burst_sw_18mhz(uint8_t cycles); -extern void dr_piezo_burst_sw_20mhz(uint8_t cycles); -extern void dr_piezo_burst_sw_17mhz(uint8_t cycles); - -/* ---- Global variable definitions (extern in header) ---- */ -dr_platform_if_t g_plat = { 0, 0, 0 }; -bool g_log_enable = false; - -/* ---- Internal constants/structures ---- */ - -#define DR_MAX_DATA 128 /* Max data length after TAG */ - -typedef struct { - char tag[5]; /* "sta?" etc 4 chars + '\0' */ - uint8_t data[DR_MAX_DATA]; /* Raw data after TAG */ - uint8_t data_len; /* Length of data[] */ -} ParsedCmd; - -/* ---- Internal utility functions ---- */ - -/* Copy TAG */ -static void dr_copy_tag(const uint8_t *buf, char *tag_out) -{ - tag_out[0] = (char)buf[0]; - tag_out[1] = (char)buf[1]; - tag_out[2] = (char)buf[2]; - tag_out[3] = (char)buf[3]; - tag_out[4] = '\0'; -} - -/* TAG comparison (4 chars) */ -static bool dr_tag_eq(const char *tag, const char *key4) -{ - return (tag[0] == key4[0] && - tag[1] == key4[1] && - tag[2] == key4[2] && - tag[3] == key4[3]); -} - -/* Extract uint16 little endian: word_index based (0 -> data[0], data[1]) */ -/* Extract uint16 BIG endian: word_index based (0 -> data[0], data[1]) */ -/* Extract uint16 LITTLE endian: word_index based (0 -> data[0], data[1]) */ -static bool dr_get_u16(const ParsedCmd *cmd, uint8_t word_index, uint16_t *out) -{ - uint8_t pos = (uint8_t)(word_index * 2); - if (cmd->data_len < (uint8_t)(pos + 2)) { - return false; - } - - // Little Endian: data[pos] = low byte, data[pos+1] = high byte - *out = (uint16_t)cmd->data[pos] - | (uint16_t)((uint16_t)cmd->data[pos + 1] << 8); - - return true; -} - -/* Extract ASCII: data[offset..offset+len] -> out, '\0' terminated */ -/* EEPROM에서 텍스트 쓸 때 ASCII 문자열 추출 -> 추후 Flash Memory 커맨드에서 재활용 가능 */ -static void dr_get_ascii(const ParsedCmd *cmd, uint8_t offset, - char *out, uint8_t max_len) -{ - uint8_t i; - uint8_t remain; - - if (offset >= cmd->data_len) { - out[0] = '\0'; - return; - } - - remain = (uint8_t)(cmd->data_len - offset); - if (remain > max_len) { - remain = max_len; - } - - for (i = 0; i < remain; i++) { - out[i] = (char)cmd->data[offset + i]; - } - out[remain] = '\0'; -} - -/* ---- CRC16 functions ---- */ - -/* CRC16 computation - matches Nordic SDK crc16_compute */ -static uint16_t dr_crc16_compute(const uint8_t *p_data, uint32_t size, const uint16_t *p_crc) -{ - uint32_t i; - uint16_t crc = (p_crc == NULL) ? 0xFFFF : *p_crc; - - for (i = 0; i < size; i++) - { - crc = (uint8_t)(crc >> 8) | (crc << 8); - crc ^= p_data[i]; - crc ^= (uint8_t)(crc & 0xFF) >> 4; - crc ^= (crc << 8) << 4; - crc ^= ((crc & 0xFF) << 4) << 1; - } - - return crc; -} - -/* CRC16 check: compare computed vs expected */ -static bool dr_crc16_check(const uint8_t *p_data, uint32_t data_len, uint16_t expected_crc) -{ - uint16_t computed_crc = dr_crc16_compute(p_data, data_len, NULL); - return (computed_crc == expected_crc); -} - -/* CRC16 packet check: last 2 bytes are CRC (little endian) */ -static bool dr_crc16_check_packet(const uint8_t *packet, uint32_t packet_len) -{ - uint16_t expected_crc; - uint32_t data_len; - - if (packet_len < 2) { - return false; - } - - data_len = packet_len - 2; - - /* Extract CRC: little endian (low byte first) */ - expected_crc = (uint16_t)packet[packet_len - 2] - | ((uint16_t)packet[packet_len - 1] << 8); - - if (g_plat.log && g_log_enable) { - g_plat.log("CRC check: expected=0x%04X\n", expected_crc); - } - - return dr_crc16_check(packet, data_len, expected_crc); -} - -/* ---- Raw buffer -> ParsedCmd ---- */ - -static bool dr_parse_cmd(const uint8_t *buffer, uint8_t length, ParsedCmd *out) -{ - uint8_t i; - - if (length < 4) { - return false; /* Not even TAG received */ - } - - /* CRC check if enabled */ - if (g_plat.crc_check) { - if (!dr_crc16_check_packet(buffer, length)) { - if (g_plat.log && g_log_enable) { - g_plat.log("CRC check FAILED!\n"); - } - return false; - } - - /* CRC validated - remove CRC bytes from data */ - length = (uint8_t)(length - 2); - - if (g_plat.log && g_log_enable) { - g_plat.log("CRC check OK\n"); - } - } - - dr_copy_tag(buffer, out->tag); - - out->data_len = (length > 4) ? (uint8_t)(length - 4) : 0; - if (out->data_len > DR_MAX_DATA) { - out->data_len = DR_MAX_DATA; - } - - for (i = 0; i < out->data_len; i++) { - out->data[i] = buffer[4 + i]; - } - - if (g_plat.log && g_log_enable) { - g_plat.log("parse_cmd: TAG='%s', data_len=%u\n", - out->tag, out->data_len); - } - - return true; -} - - - -/* ---- Handler prototypes (Harbour style: int return) ---- */ - -/* A. Device Status */ -static int Cmd_mta(const ParsedCmd *cmd); -static int Cmd_sta(const ParsedCmd *cmd); -static int Cmd_str(const ParsedCmd *cmd); - -/* F. PD-ADC M48 Full Measurement Series */ -static int Cmd_mcj(const ParsedCmd *cmd); -static int Cmd_sej(const ParsedCmd *cmd); -static int Cmd_ssj(const ParsedCmd *cmd); - -/* I. Sensor Measurements */ -static int Cmd_msn(const ParsedCmd *cmd); -static int Cmd_spn(const ParsedCmd *cmd); -static int Cmd_sso(const ParsedCmd *cmd); -static int Cmd_ssp(const ParsedCmd *cmd); - -/* J. Power / Reset / Version / Security */ -static int Cmd_ssq(const ParsedCmd *cmd); -static int Cmd_ssr(const ParsedCmd *cmd); -static int Cmd_sss(const ParsedCmd *cmd); -static int Cmd_sst(const ParsedCmd *cmd); -static int Cmd_mfv(const ParsedCmd *cmd); - -/* ------------------ Piezo ------------------ */ - -static int Cmd_msp(const ParsedCmd *cmd); /* IMU 6-axis raw data (single shot) */ - -static int Cmd_mpa(const ParsedCmd *cmd); /* Piezo TX/RX Activate */ -static int Cmd_mpb(const ParsedCmd *cmd); /* Piezo TX/RX Deactivate */ -static int Cmd_mpc(const ParsedCmd *cmd); /* Piezo Burst Capture */ -static int Cmd_mdc(const ParsedCmd *cmd); /* Piezo ADC Capture */ -static int Cmd_mec(const ParsedCmd *cmd); /* Piezo Burst + ADC capture */ -static int Cmd_maa(const ParsedCmd *cmd); /* Piezo Burst + ADC all channel capture */ - -static int Cmd_cmd(const ParsedCmd *cmd); /* Pin Test (High / Low) */ - -/* ------------------ FDS ------------------ */ - -static int Cmd_mwh(const ParsedCmd *cmd); /* Write HW Number to FDS */ -static int Cmd_mws(const ParsedCmd *cmd); /* Write Serial Number to FDS */ -static int Cmd_mrh(const ParsedCmd *cmd); /* Read HW Number from FDS */ -static int Cmd_mrs(const ParsedCmd *cmd); /* Read Serial Number from FDS */ - -static int Cmd_mpz(const ParsedCmd *cmd); /* Write Passkey to FDS */ -static int Cmd_mqz(const ParsedCmd *cmd); /* Read Passkey from FDS */ -static int Cmd_mxz(const ParsedCmd *cmd); /* Write Life Cycle to FDS */ -static int Cmd_myz(const ParsedCmd *cmd); /* Read Life Cycle from FDS */ - -/* ---- Command Table ---- */ - -typedef struct { - char tag[5]; /* "sta?" */ - bool enabled; /* false = handler won't be called */ - int (*handler)(const ParsedCmd *cmd); /* 1=success, 0=fail */ -} CmdEntry; - -static CmdEntry g_cmd_table[] = { - - /* sudo command */ - { "cmd?", true, Cmd_cmd }, // Piezo Activate - - /* Piezo command */ - { "mpa?", true, Cmd_mpa }, // Piezo Activate - { "mpb?", true, Cmd_mpb }, // Piezo Deactivate 26.03.13 - { "mpc?", true, Cmd_mpc }, // Piezo Cycles control command, 3,4,5,6,7 - { "mdc?", true, Cmd_mdc }, // Piezo burst + Echo capture (12-bit packed) - { "mec?", true, Cmd_mec }, // Piezo burst + Echo capture (16-bit raw) - { "maa?", true, Cmd_maa }, // 8-channel all capture (mode: 0=raw, 1=delta) - { "msp?", true, Cmd_msp }, // IMU 6-axis raw data (single shot) - - /* Config: HW/Serial Number (FDS) */ - { "mwh?", true, Cmd_mwh }, - { "mws?", true, Cmd_mws }, - { "mrh?", true, Cmd_mrh }, - { "mrs?", true, Cmd_mrs }, - - /* Config: Passkey / Life Cycle (FDS) */ - { "mpz?", true, Cmd_mpz }, /* 패스키 쓰기 (spz? → mpz?) */ - { "mqz?", true, Cmd_mqz }, /* 패스키 읽기 (sqz? → mqz?) */ - { "mxz?", true, Cmd_mxz }, /* life_cycle 쓰기 (sxz? → mxz?) */ - { "myz?", true, Cmd_myz }, /* life_cycle 읽기 (syz? → myz?) */ - - /* A. Device Status */ - { "mta?", true, Cmd_mta }, - { "sta?", true, Cmd_sta }, - { "str?", false, Cmd_str }, - /* F. PD-ADC M48 Full Measurement Series */ - { "mcj?", true, Cmd_mcj }, - { "scj?", true, Cmd_mcj }, - - { "sej?", true, Cmd_sej }, - { "ssj?", false, Cmd_ssj }, - /* I. Sensor Measurements */ - { "msn?", true, Cmd_msn }, - { "ssn?", true, Cmd_msn }, // snn compatible command for battery check - - { "spn?", false, Cmd_spn }, - { "sso?", false, Cmd_sso }, - { "ssp?", true, Cmd_ssp }, - - /* J. Power / Reset / Version / Security */ - { "ssq?", false, Cmd_ssq }, - { "ssr?", false, Cmd_ssr }, - { "sss?", false, Cmd_sss }, - { "sst?", false, Cmd_sst }, - - { "mfv?", true, Cmd_mfv }, /* Firmware Version Read (ssv -> mfv) 26.03.13 */ - -}; - -static const uint16_t g_cmd_count = - (uint16_t)(sizeof(g_cmd_table) / sizeof(g_cmd_table[0])); - -/* ---- Command dispatcher ---- */ -static int dr_cmd_dispatch(const ParsedCmd *cmd) -{ - uint16_t i; - char tag_lower[5]; - - /* tag command convert to lower case */ - for (i = 0; i < 4 && cmd->tag[i]; i++) { - tag_lower[i] = (cmd->tag[i] >= 'A' && cmd->tag[i] <= 'Z') - ? (cmd->tag[i] + 32) : cmd->tag[i]; - } - tag_lower[i] = '\0'; - - for (i = 0; i < g_cmd_count; i++) { - if (dr_tag_eq(tag_lower, g_cmd_table[i].tag)) { - - if (!g_cmd_table[i].enabled) { - if (g_plat.log && g_log_enable) { - g_plat.log("Command '%s' disabled\n", cmd->tag); - } - return 0; - } - - if (g_plat.log && g_log_enable) { - g_plat.log("Run handler '%s'\n", cmd->tag); - } - - return g_cmd_table[i].handler(cmd); - } - } - - if (g_plat.log && g_log_enable) { - g_plat.log("Unknown TAG '%s'\n", cmd->tag); - } - return 0; -} - -/* -Main Parser called from external code -*/ - -int dr_cmd_parser(const uint8_t *buf, uint8_t len) -{ - ParsedCmd cmd; - - if (g_plat.log) g_plat.log("[PARSER] in len=%u crc=%u\r\n", len, g_plat.crc_check); - - if (!dr_parse_cmd(buf, len, &cmd)) { - if (g_plat.log) g_plat.log("[PARSER] PARSE FAIL\r\n"); - - /* CRC 실패 시 에러 응답 전송 */ - if (g_plat.crc_check && g_plat.tx_bin) { - single_format_data(ble_bin_buffer, "crc!", 65530); - dr_binary_tx_safe(ble_bin_buffer, 3); - } - return -1; /* CRC 실패 또는 파싱 실패 → 음수로 old parser에 위임 */ - } - - if (g_plat.log) g_plat.log("[PARSER] tag=%s\r\n", cmd.tag); - return dr_cmd_dispatch(&cmd); - - - - -} - -/* ---- Each Command Handler implementation (Stub) ---- */ -/* In actual firmware, replace TODO sections with hardware/EEPROM integration */ - -/* A. Device Status */ -static int Cmd_mta(const ParsedCmd *cmd) -{ - uint16_t mode = 0; - - // Reset count (sta? replacement ) - resetCount = 0; - - // Extract mode - (void)dr_get_u16(cmd, 0, &mode); - - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_mta] mode=%u\r\n", mode); - } - - // Process mode ( mta?1 ) - if (mode == 1) { - if (device_activated() == 0) { - device_status = true; - } - } - else if (mode == 0) { // mta?0 - if (device_status == true) { - if (device_sleep_mode() == 0) { - device_status = false; - } - } - } - - if (g_plat.tx_bin) { - single_format_data(ble_bin_buffer, "rta:", mode); - dr_binary_tx_safe(ble_bin_buffer, 3); - } - - return 1; -} - - -/* ---- Each Command Handler implementation (Stub) ---- */ -/* In actual firmware, replace TODO sections with hardware/EEPROM integration */ - -/* A. Device Status */ -static int Cmd_sta(const ParsedCmd *cmd) -{ - uint16_t mode = 0; - - // Reset count (sta? replacement ) - resetCount = 0; - - // Extract mode - (void)dr_get_u16(cmd, 0, &mode); - - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_mta] mode=%u\r\n", mode); - } - - // Process mode ( mta?1 ) - if (mode == 1) { - if (device_activated() == 0) { - device_status = true; - } - } - else if (mode == 0) { // mta?0 - if (device_status == true) { - if (device_sleep_mode() == 0) { - device_status = false; - } - } - } - - if (g_plat.tx_bin) { - single_format_data(ble_bin_buffer, "sta:", mode); - dr_binary_tx_safe(ble_bin_buffer, 3); - } - - return 1; -} - - -static int Cmd_str(const ParsedCmd *cmd) -{ - (void)cmd; - /* TODO: read actual device_status */ - uint8_t status = 1; - - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_str] read status=%u\n", status); - } - - if (g_plat.tx_bin) { - uint8_t resp[4] = { 'r','t','r', status }; - g_plat.tx_bin(resp, 4); - } - return 1; -} - - -/** - * @brief PD-ADC M48 Full Measurement - MODE 2 (scj? ? mcj?) - * - * Original: scj? - * New: mcj? - * Response: rcj: (from m48 measurement callback) - * - * MODE 2: Pressure + M48 Full Measurement - * - Pressure sensor measurement (pressure1 + pressure2) - * - 48 LED-PD ADC measurement - * - Battery, Temperature, IMU data - * - * Preconditions: - * - Device must be activated (device_status == true) - * - Not currently processing - */ -static int Cmd_mcj(const ParsedCmd *cmd) -{ - (void)cmd; - - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_mcj] PD-ADC M48 MODE=2 (Press + M48)\r\n"); - } - - /* Check device activation status */ - if (device_status != true) { - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_mcj] ERROR: Device not activated\r\n"); - } - - if (g_plat.tx_bin) { - param_error("mcj?"); - } - return 1; - } - - info4 = true; - ble_got_new_data = false; - processing = true; - - /* Start pressure measurement */ - pressure_all_level_meas(); - - battery_timer_stop(); - - /* Enable battery, temperature, IMU measurement */ - go_batt = true; - motion_data_once = true; - - /* Start main timer */ - main_timer_start(); - - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_mcj] Measurement started\r\n"); - } - - return 1; -} - -static int Cmd_sej(const ParsedCmd *cmd) -{ - (void)cmd; - - /* NIRS/optical PD-ADC M48 mode removed (VesiScan-Basic) */ - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_sej] Disabled (NIRS removed)\r\n"); - } - return 0; -} - -static int Cmd_ssj(const ParsedCmd *cmd) -{ - (void)cmd; - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_ssj] MODE=0 (M48 + batt/IMU combined)\n"); - } - return 1; -} - -/* I. Sensor Measurements */ -static int Cmd_msn(const ParsedCmd *cmd) -{ - (void)cmd; - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_msn] Measure battery level\n"); - } - battery_level_meas(); - return 1; -} - - - - -static int Cmd_spn(const ParsedCmd *cmd) -{ - (void)cmd; - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_spn] Measure pressure1 & 2\n"); - } - return 1; -} - -static int Cmd_sso(const ParsedCmd *cmd) -{ - (void)cmd; - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_sso] Measure LED temperature\n"); - } - return 1; -} - -static int Cmd_ssp(const ParsedCmd *cmd) -{ - hw_i2c_init_once(); - - motion_raw_data_enabled = true; - ble_got_new_data = false; - - /* 'c' = continuous, otherwise single shot */ - if (cmd->data_len > 0 && (char)cmd->data[0] == 'c') { - motion_data_once = false; - } else { - motion_data_once = true; - } - - main_timer_start(); - - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_ssp] Motion sensor raw, once=%u\r\n", motion_data_once); - } - return 1; -} - -/* J. Power / Reset / Version / Security */ -/* ssq? - Device Power Off */ -static int Cmd_ssq(const ParsedCmd *cmd) -{ - uint16_t val = 0; - dr_get_u16(cmd, 0, &val); - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_ssq] Power off\r\n"); - } - single_format_data(ble_bin_buffer, "rsq:", val); - dr_binary_tx_safe(ble_bin_buffer, 2); - go_device_power_off = true; - main_timer_start(); - return 1; -} - -/* ssr? - Bond Info Delete + System Reset */ -static int Cmd_ssr(const ParsedCmd *cmd) -{ - uint16_t val = 0; - dr_get_u16(cmd, 0, &val); - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_ssr] Bond delete + reset\r\n"); - } - single_format_data(ble_bin_buffer, "rsr:", val); - dr_binary_tx_safe(ble_bin_buffer, 2); - bond_data_delete = true; - m_config.bond_data_delete = (uint8_t)bond_data_delete; - m_reset_status = 2; - m_config.reset_status = m_reset_status; - config_save(); - nrf_delay_ms(5); - go_NVIC_SystemReset = true; - main_timer_start(); - return 1; -} - -/* sss? - Device Reset (Reboot) */ -static int Cmd_sss(const ParsedCmd *cmd) -{ - uint16_t val = 0; - dr_get_u16(cmd, 0, &val); - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_sss] Device reset\r\n"); - } - single_format_data(ble_bin_buffer, "rss:", val); - dr_binary_tx_safe(ble_bin_buffer, 2); - m_reset_status = 2; - m_config.reset_status = m_reset_status; - config_save(); - nrf_delay_ms(5); - go_NVIC_SystemReset = true; - main_timer_start(); - return 1; -} - -/* sst? - Security Ready */ -static int Cmd_sst(const ParsedCmd *cmd) -{ - uint16_t val = 0; - dr_get_u16(cmd, 0, &val); - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_sst] Ready\r\n"); - } - single_format_data(ble_bin_buffer, "rst:", val); - dr_binary_tx_safe(ble_bin_buffer, 2); - return 1; -} - -static int Cmd_mfv(const ParsedCmd *cmd) -{ - (void)cmd; - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_mfv] FW=%s\r\n", DR_DEVICE_VERSION); - } - ascii_format_data(ble_bin_buffer, "rfv:", DR_DEVICE_VERSION, 12); - dr_binary_tx_safe(ble_bin_buffer, 8); - return 1; -} - -static int Cmd_mpa(const ParsedCmd *cmd) -{ - (void)cmd; - - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_mpa] Piezo Activation\n"); - } - - dr_piezo_power_on(); - dr_piezo_system_init(); - - if (g_plat.tx_bin) { - single_format_data(ble_bin_buffer, "rpa:", 1); - dr_binary_tx_safe(ble_bin_buffer, 3); - } - - return 1; -} - -/* mpb? - Piezo TX/RX Deactivate (Power Off) */ -static int Cmd_mpb(const ParsedCmd *cmd) -{ - (void)cmd; - - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_mpb] Piezo Deactivation\n"); - } - - dr_piezo_power_off(); - - if (g_plat.tx_bin) { - single_format_data(ble_bin_buffer, "rpb:", 1); - dr_binary_tx_safe(ble_bin_buffer, 3); - } - - return 1; -} - - -/** - * @brief Piezo burst command with frequency option - * @param cmd->data[0-1]: cycles (3~9), default=5 - * @param cmd->data[2-3]: freq_option (0=1.8MHz, 1=2.1MHz, 2=2.0MHz, 3=1.7MHz), default=1 - * @return Response: rpc:cycles - * - * Usage from Harbour: - * mpc?[cycles][freq] where freq: 0=1.8MHz, 1=2.1MHz (default), 2=2.0MHz, 3=1.7MHz, 4=2.2MHz - * Example: mpc?0x05,0x00,0x01,0x00 -> 5 cycles at 2.1MHz - * Example: mpc?0x05,0x00,0x00,0x00 -> 5 cycles at 1.8MHz - * Example: mpc?0x05,0x00,0x02,0x00 -> 5 cycles at 2.0MHz - * Example: mpc?0x05,0x00,0x03,0x00 -> 5 cycles at 1.7MHz - * Example: mpc?0x05,0x00,0x04,0x00 -> 5 cycles at 2.2MHz - */ -static int Cmd_mpc(const ParsedCmd *cmd) -{ - uint16_t cycles = 5; /* default */ - uint16_t freq_option = 1; /* 0=1.8MHz, 1=2.1MHz, 2=2.0MHz, 3=1.7MHz, 4=2.2MHz */ - uint16_t piezo_ch = 0; /* Piezo channel 0~7, default 0 */ - - /* Extract cycles (word 0) */ - (void)dr_get_u16(cmd, 0, &cycles); - - /* Extract frequency option (word 1) */ - (void)dr_get_u16(cmd, 1, &freq_option); - - /* Extract piezo channel (word 2) */ - (void)dr_get_u16(cmd, 2, &piezo_ch); - - /* Validate piezo channel: 0~7 jhChun 26.01.29 */ - if (piezo_ch > 7) piezo_ch = 0; - - if (g_plat.log && g_log_enable) { - const char *freq_str = (freq_option == 0) ? "1.8MHz" : - (freq_option == 1) ? "2.1MHz" : - (freq_option == 2) ? "2.0MHz" : - (freq_option == 3) ? "1.7MHz" : - (freq_option == 4) ? "2.2MHz" : - (freq_option == 9) ? "1.9MHz" : "unknown"; - g_plat.log("[Cmd_mpc] cycles=%u, freq=%u (%s), piezo=%u\r\n", - cycles, freq_option, freq_str, piezo_ch); - } - - /* Range check: 3~9 */ - if (cycles < 3 || cycles > 9) { - dr_ble_return_1("rpc:", 2); /* Error: out of range */ - return 1; - } - - /* Select piezo channel */ - dr_piezo_select_channel((uint8_t)piezo_ch); - - /* Execute burst based on frequency option */ - switch (freq_option) { - case 0: - dr_piezo_burst_sw_18mhz((uint8_t)cycles); - break; - case 2: - dr_piezo_burst_sw_20mhz((uint8_t)cycles); - break; - case 3: - dr_piezo_burst_sw_17mhz((uint8_t)cycles); - break; - case 4: - dr_piezo_burst_sw_22mhz((uint8_t)cycles); - break; - /*case 9: - dr_piezo_burst_sw_19mhz((uint8_t)cycles); - break;*/ - case 1: - default: - dr_piezo_burst_sw((uint8_t)cycles); /* 2.1MHz */ - break; - } - - /* Response */ - dr_ble_return_1("rpc:", (uint8_t)cycles); - - return 1; -} - - -/** - * @brief Piezo burst + Echo capture command - * @param cmd->data[0]: cycles (3~9), default=5 - * @param cmd->data[1-2]: delay_us (0~65535), default=1000 - * @param cmd->data[3-4]: num_samples (1~8192), default=4096 - * @return Multiple packets with 12-bit packed data - * - * Response format (multi-packet, 12-bit packed): - * - * First packet ("rdc:"): - * [0-3]: "rdc:" - * [4-5]: peak_raw (uint16_t, little-endian) - * [6-7]: peak_index (uint16_t) - * [8-9]: baseline_raw (uint16_t) - * [10-11]: num_samples (uint16_t) - * [12-13]: total_packets (uint16_t) - * [14...]: 12-bit packed ADC data (first chunk) - * - * Continuation packets ("rdd:"): - * [0-3]: "rdd:" - * [4-5]: packet_index (uint16_t, 1-based) - * [6...]: 12-bit packed ADC data (continuation) - * - * Last packet marker ("rde:"): - * [0-3]: "rde:" - * [4-5]: total_bytes_sent (uint16_t) - * - * 12-bit packing: 2 samples (24 bits) -> 3 bytes - * Byte0 = sample0[7:0] - * Byte1 = sample1[3:0] << 4 | sample0[11:8] - * Byte2 = sample1[11:4] - */ -#define MDC_BLE_MTU_SIZE 240 /* Safe BLE packet size */ -#define MDC_FIRST_HEADER_LEN 14 /* "rdc:" + header */ -#define MDC_CONT_HEADER_LEN 6 /* "rdd:" + packet_index */ -#define MDC_FIRST_DATA_LEN (MDC_BLE_MTU_SIZE - MDC_FIRST_HEADER_LEN) -#define MDC_CONT_DATA_LEN (MDC_BLE_MTU_SIZE - MDC_CONT_HEADER_LEN) - -/** - * @brief Piezo burst + Echo capture command (12-bit packed) - * - * PROTOCOL v3: Small header packet + separate data packets with delays - * This avoids large packet loss in BLE stack. - * - * Response format: - * Packet 1 (rdb:): header only (14 bytes) - * Packet 2~N (rdd:): pkt_idx(2) + packed_data (up to 234 bytes) - * Final (rde:): total_packets(2) - */ -static int Cmd_mdc(const ParsedCmd *cmd) -{ - uint16_t cycles = 5; - uint16_t delay_us = 1000; - uint16_t num_samples = 4096; - - /* Extract parameters */ - (void)dr_get_u16(cmd, 0, &cycles); - (void)dr_get_u16(cmd, 1, &delay_us); - (void)dr_get_u16(cmd, 2, &num_samples); - - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_mdc] cycles=%u, delay=%uus, samples=%u\r\n", - cycles, delay_us, num_samples); - } - - /* Range check */ - if (cycles < 3) cycles = 3; - if (cycles > 9) cycles = 9; - if (num_samples > 8192) num_samples = 8192; - if (num_samples < 1) num_samples = 1; - - /* Execute burst + echo capture */ - dr_adc_echo_t echo; - dr_adc_err_t err = dr_adc_burst_and_capture( - (uint8_t)cycles, delay_us, num_samples, &echo); - - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_mdc] err=%d, peak=%u@%u, base=%u, n=%u\r\n", - err, echo.peak_raw, echo.peak_index, echo.baseline_raw, echo.num_samples); - } - - if (g_plat.tx_bin) { - const uint16_t *raw_buffer = dr_adc_get_echo_buffer(); - - /* Calculate packed data size: 2 samples -> 3 bytes */ - uint16_t packed_data_bytes = ((echo.num_samples + 1) / 2) * 3; - uint16_t data_per_packet = MDC_BLE_MTU_SIZE - MDC_CONT_HEADER_LEN; /* 234 bytes */ - uint16_t data_packets = (packed_data_bytes + data_per_packet - 1) / data_per_packet; - if (data_packets == 0) data_packets = 1; - uint16_t total_packets = 1 + data_packets; /* header + data packets */ - - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_mdc] packed=%u bytes, packets=%u\r\n", packed_data_bytes, total_packets); - } - - /* Packet 1: rdb: header only (14 bytes) */ - ble_bin_buffer[0] = 'r'; - ble_bin_buffer[1] = 'd'; - ble_bin_buffer[2] = 'b'; - ble_bin_buffer[3] = ':'; - ble_bin_buffer[4] = (uint8_t)(total_packets & 0xFF); - ble_bin_buffer[5] = (uint8_t)(total_packets >> 8); - ble_bin_buffer[6] = (uint8_t)(echo.peak_raw & 0xFF); - ble_bin_buffer[7] = (uint8_t)(echo.peak_raw >> 8); - ble_bin_buffer[8] = (uint8_t)(echo.peak_index & 0xFF); - ble_bin_buffer[9] = (uint8_t)(echo.peak_index >> 8); - ble_bin_buffer[10] = (uint8_t)(echo.baseline_raw & 0xFF); - ble_bin_buffer[11] = (uint8_t)(echo.baseline_raw >> 8); - ble_bin_buffer[12] = (uint8_t)(echo.num_samples & 0xFF); - ble_bin_buffer[13] = (uint8_t)(echo.num_samples >> 8); - dr_binary_tx_safe(ble_bin_buffer, 7); /* 14 bytes = 7 words */ - nrf_delay_ms(100); /* Wait for BLE stack */ - - /* Packet 2~N: rdd: data packets with packed 12-bit samples */ - uint16_t src_idx = 0; - for (uint16_t pkt = 0; pkt < data_packets; pkt++) { - ble_bin_buffer[0] = 'r'; - ble_bin_buffer[1] = 'd'; - ble_bin_buffer[2] = 'd'; - ble_bin_buffer[3] = ':'; - ble_bin_buffer[4] = (uint8_t)(pkt & 0xFF); - ble_bin_buffer[5] = (uint8_t)(pkt >> 8); - - uint16_t dst_idx = 6; - uint16_t bytes_this_pkt = 0; - while (src_idx < echo.num_samples && bytes_this_pkt < data_per_packet) { - uint16_t s0 = raw_buffer[src_idx++] & 0x0FFF; - uint16_t s1 = (src_idx < echo.num_samples) ? (raw_buffer[src_idx++] & 0x0FFF) : 0; - - ble_bin_buffer[dst_idx++] = (uint8_t)(s0 & 0xFF); - ble_bin_buffer[dst_idx++] = (uint8_t)(((s1 & 0x0F) << 4) | ((s0 >> 8) & 0x0F)); - ble_bin_buffer[dst_idx++] = (uint8_t)(s1 >> 4); - bytes_this_pkt += 3; - } - - /* Ensure even byte count for word alignment */ - if (dst_idx & 1) ble_bin_buffer[dst_idx++] = 0; - dr_binary_tx_safe(ble_bin_buffer, dst_idx / 2); /* bytes to words */ - nrf_delay_ms(100); /* Inter-packet delay */ - } - - /* Final packet: rde: end marker */ - ble_bin_buffer[0] = 'r'; - ble_bin_buffer[1] = 'd'; - ble_bin_buffer[2] = 'e'; - ble_bin_buffer[3] = ':'; - ble_bin_buffer[4] = (uint8_t)(total_packets & 0xFF); - ble_bin_buffer[5] = (uint8_t)(total_packets >> 8); - dr_binary_tx_safe(ble_bin_buffer, 3); /* 6 bytes = 3 words */ - - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_mdc] sent rdb+rdd*%u+rde (%u samples)\r\n", - total_packets, echo.num_samples); - } - } - - return 1; -} - - -/* - -// The following code demonstrates an example of how to receive data from a client -// and echo the same data back to the client. - - - -static int Cmd_cmd(const ParsedCmd *cmd) -{ - uint16_t v1, v2, v3; - - if (cmd->data_len < 6) { - dr_ble_return_1("rmd:", 0); - return 1; - } - - // Little Endian ?? (PC? LE? ??) - v1 = (uint16_t)cmd->data[0] | ((uint16_t)cmd->data[1] << 8); - v2 = (uint16_t)cmd->data[2] | ((uint16_t)cmd->data[3] << 8); - v3 = (uint16_t)cmd->data[4] | ((uint16_t)cmd->data[5] << 8); - - dr_ble_return_3("rmd:", v1, v2, v3); - - return 1; -} - -*/ - -/** - * @brief Piezo burst + Echo capture with 16-bit raw data (no compression) - * @param cmd->data[0]: cycles (3~9), default=5 - * @param cmd->data[1-2]: delay_us (0~65535), default=100 - * @param cmd->data[3-4]: num_samples (1~200), default=140 (20cm target) - * @return Multiple packets with 16-bit raw data - * - * Response format (multi-packet, 16-bit raw - no compression): - * - * First packet ("rec:"): - * [0-3]: "rec:" - * [4-5]: peak_raw (uint16_t, little-endian) - * [6-7]: peak_index (uint16_t) - * [8-9]: baseline_raw (uint16_t) - * [10-11]: num_samples (uint16_t) - * [12-13]: total_packets (uint16_t) - * [14...]: 16-bit raw ADC data (first chunk, little-endian) - * - * Continuation packets ("red:"): - * [0-3]: "red:" - * [4-5]: packet_index (uint16_t, 1-based) - * [6...]: 16-bit raw ADC data (continuation) - * - * Last packet marker ("ree:"): - * [0-3]: "ree:" - * [4-5]: total_bytes_sent (uint16_t) - * - * 16-bit raw format: Each sample = 2 bytes (uint16, little-endian) - * Byte0 = Sample[7:0] - * Byte1 = Sample[15:8] - * - * Example: 140 samples (20cm) = 280 bytes = 2 packets - */ -static int Cmd_mec(const ParsedCmd *cmd) -{ - uint16_t freq_option = 0; /* 0=1.8MHz (default), 1=2.1MHz, 2=2.0MHz, 3=1.7MHz */ - uint16_t delay_us = 20; /* Default 20us */ - uint16_t num_samples = 140; /* Default for 20cm target */ - uint16_t cycles = 5; /* Default 5 cycles (valid: 3~7) */ - uint16_t averaging = 1; /* Default 1 (no averaging), max 1000 */ - uint16_t piezo_ch = 0; /* Default piezo channel 0 (valid: 0~7) */ - - /* Extract parameters: mec [freq_option] [delay_us] [num_samples] [cycles] [averaging] [piezo_ch] */ - (void)dr_get_u16(cmd, 0, &freq_option); - (void)dr_get_u16(cmd, 1, &delay_us); - (void)dr_get_u16(cmd, 2, &num_samples); - (void)dr_get_u16(cmd, 3, &cycles); - (void)dr_get_u16(cmd, 4, &averaging); - (void)dr_get_u16(cmd, 5, &piezo_ch); - - /* Validate averaging: 1~1000, default 1 */ - if (averaging == 0) averaging = 1; - if (averaging > 1000) averaging = 1000; - - /* Validate piezo channel: 0~7 jhChun 26.01.29 */ - if (piezo_ch > 7) piezo_ch = 0; - - if (g_plat.log && g_log_enable) { - const char *freq_str = (freq_option == 0) ? "1.8MHz" : - (freq_option == 1) ? "2.1MHz" : - (freq_option == 2) ? "2.0MHz" : - (freq_option == 3) ? "1.7MHz" : - (freq_option == 4) ? "2.2MHz" : - (freq_option == 9) ? "1.9MHz" : "unknown"; - g_plat.log("[Cmd_mec] freq=%u (%s), delay=%uus, samples=%u, cycles=%u, avg=%u, piezo=%u\r\n", - freq_option, freq_str, delay_us, num_samples, cycles, averaging, piezo_ch); - } - - /* Use integrated burst + capture + transmit function - * This function handles: - * 1. ADC power on - * 2. Select piezo channel (0~7) - * 3. Piezo burst (frequency based on freq_option, cycles from param) - * 4. ADC capture (after delay_us) - repeated 'averaging' times - * 5. Average the captured samples - * 6. BLE transmission with proper packet timing - */ - dr_adc_err_t err = dr_adc_burst_capture_transmit( - (uint8_t)freq_option, delay_us, num_samples, (uint8_t)cycles, - (uint16_t)averaging, (uint8_t)piezo_ch, ble_bin_buffer, 0); /* 0=send raa */ - - if (err != DR_ADC_OK) { - dr_ble_return_2("rer:", 0xEE00 | (uint16_t)err, num_samples); - } - - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_mec] result=%d\r\n", err); - } - - return 1; -} - - -static int Cmd_cmd(const ParsedCmd *cmd) -{ - uint16_t v1, v2, v3; - uint32_t pin_number; - - if (cmd->data_len < 6) { - dr_ble_return_1("rmd:", 0); - return 1; - } - - // Little Endian from PC to LE - v1 = (uint16_t)cmd->data[0] | ((uint16_t)cmd->data[1] << 8); // port - v2 = (uint16_t)cmd->data[2] | ((uint16_t)cmd->data[3] << 8); // pin - v3 = (uint16_t)cmd->data[4] | ((uint16_t)cmd->data[5] << 8); // 1=HIGH, 0=LOW - - // -- GPIO Test - - // Pin No: NRF_GPIO_PIN_MAP(port, pin) - pin_number = NRF_GPIO_PIN_MAP(v1, v2); - - // output - nrf_gpio_cfg_output(pin_number); - - // HIGH or LOW - if (v3 == 1) { - nrf_gpio_pin_set(pin_number); // HIGH - } else { - nrf_gpio_pin_clear(pin_number); // LOW - } - - // return : port, pin, state - dr_ble_return_3("rmd:", v1, v2, v3); - - return 1; -} - - -/** - * @brief 4-Channel All Capture Command (maa?) - ASYNC VERSION - * @param cmd->data[0-1]: mode (0=raw 16-bit async) - * @return Immediately. Data sent asynchronously via BLE_NUS_EVT_TX_RDY - * - * ASYNC ARCHITECTURE: - * - maa_async_start() initiates capture and sends CH0 header - * - BLE_NUS_EVT_TX_RDY callback drives remaining transmission - * - No blocking - SoftDevice can process events normally - * - Prevents BLE TX buffer overflow that caused firmware brick - * - * Hardcoded parameters: - * freq_option = 0 (1.8MHz) - * delay_us = 10 - * num_samples = 140 - * cycles = 7 - * averaging = 5 - * - * Response format: - * For each channel (CH0~CH3): - * reb: [total_pkts(2)] [peak(2)] [idx(2)] [baseline(2)] [samples(2)] - * red: [pkt_idx(2)] [data...] - * Final: - * raa: [status(2)] - * - * Version marker: 0xA000 (vA) = async 8-channel - */ -#define MAA_FREQ_OPTION 0 /* 1.8MHz */ -#define MAA_DELAY_US 10 /* 10us post-burst delay */ -#define MAA_NUM_SAMPLES 140 /* 140 samples (~25cm) */ -#define MAA_CYCLES 7 /* 7 cycles burst */ -#define MAA_AVERAGING 5 /* 5x averaging */ - -static int Cmd_maa(const ParsedCmd *cmd) -{ - uint16_t mode = 0; - dr_adc_err_t err; - - /* Extract mode parameter */ - (void)dr_get_u16(cmd, 0, &mode); - - /* Mode validation - only mode 0 (async raw) supported */ - if (mode > 0) { - dr_ble_return_1("raa:", 0xFFFF); - return 1; - } - - /* Check if already busy */ - if (maa_async_is_busy()) { - dr_ble_return_1("raa:", 0xFFFE); /* Busy */ - return 1; - } - - /*======================================================================= - * ASYNC 4-CHANNEL CAPTURE - * - maa_async_start() captures CH0 and sends first header - * - Subsequent packets sent via BLE_NUS_EVT_TX_RDY callback - * - No blocking delays - SoftDevice can process events normally - *=======================================================================*/ - err = maa_async_start( - (uint8_t)MAA_FREQ_OPTION, - MAA_DELAY_US, - MAA_NUM_SAMPLES, - (uint8_t)MAA_CYCLES, - (uint16_t)MAA_AVERAGING, - ble_bin_buffer - ); - - if (err != DR_ADC_OK) { - /* Start failed - error already sent by maa_async_start */ - return 1; - } - - /* Return immediately - async transmission in progress */ - /* raa: will be sent by state machine when complete */ - return 1; -} - - -/*============================================================================== - * CONFIG: HW/Serial Number (FDS) - *============================================================================*/ - -/* mwh? - Write HW Number to FDS - * Data: 12 bytes ASCII HW number - */ -static int Cmd_mwh(const ParsedCmd *cmd) -{ - char buf[13]; - - if (cmd->data_len < 12) { - dr_ble_return_1("rwh:", 0xFFFF); /* Error: insufficient data */ - return 1; - } - - dr_get_ascii(cmd, 0, buf, 12); - memcpy(HW_NO, buf, 12); - memcpy(m_config.hw_no, buf, 12); - config_save(); - - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_mwh] HW=%.12s saved to FDS\r\n", m_config.hw_no); - } - - ascii_format_data(ble_bin_buffer, "rwh:", buf, 12); - dr_binary_tx_safe(ble_bin_buffer, 8); - return 1; -} - -/* mws? - Write Serial Number to FDS - * Data: 12 bytes ASCII serial number - */ -static int Cmd_mws(const ParsedCmd *cmd) -{ - char buf[13]; - - if (cmd->data_len < 12) { - dr_ble_return_1("rws:", 0xFFFF); /* Error: insufficient data */ - return 1; - } - - dr_get_ascii(cmd, 0, buf, 12); - memcpy(SERIAL_NO, buf, 12); - memcpy(m_config.serial_no, buf, 12); - config_save(); - - if (g_plat.log && g_log_enable) { - g_plat.log("[Cmd_mws] S/N=%.12s saved to FDS\r\n", m_config.serial_no); - } - - ascii_format_data(ble_bin_buffer, "rws:", buf, 12); - dr_binary_tx_safe(ble_bin_buffer, 8); - return 1; -} - -/* mrh? - Read HW Number from FDS */ -static int Cmd_mrh(const ParsedCmd *cmd) -{ - (void)cmd; - memcpy(HW_NO, m_config.hw_no, 12); - ascii_format_data(ble_bin_buffer, "rrh:", HW_NO, 12); - dr_binary_tx_safe(ble_bin_buffer, 8); - return 1; -} - -/* mrs? - Read Serial Number from FDS */ -static int Cmd_mrs(const ParsedCmd *cmd) -{ - (void)cmd; - memcpy(SERIAL_NO, m_config.serial_no, 12); - ascii_format_data(ble_bin_buffer, "rrs:", SERIAL_NO, 12); - dr_binary_tx_safe(ble_bin_buffer, 8); - return 1; -} - - -/*============================================================================== - * Passkey / Life Cycle (FDS) - *============================================================================*/ - -/* mpz? - 패스키 쓰기 (spz? → mpz?) - * 패킷: mpz? + ASCII 6자리 패스키 - * 응답: rpz: + ASCII 6바이트 - */ -static int Cmd_mpz(const ParsedCmd *cmd) -{ - char passkey[7] = {0}; - dr_get_ascii(cmd, 0, passkey, 6); - - memcpy(m_static_passkey, passkey, 6); - memcpy(m_config.static_passkey, m_static_passkey, 6); - config_save(); - - if (g_plat.log) g_plat.log("[mpz] Passkey saved: %.6s\r\n", m_static_passkey); - - ascii_format_data(ble_bin_buffer, "rpz:", passkey, 6); - dr_binary_tx_safe(ble_bin_buffer, 5); - return 1; -} - -/* mqz? - 패스키 읽기 (sqz? → mqz?) - * 패킷: mqz? (데이터 없음) - * 응답: rqz: + ASCII 6바이트 - */ -static int Cmd_mqz(const ParsedCmd *cmd) -{ - (void)cmd; - memcpy(m_static_passkey, m_config.static_passkey, 6); - - ascii_format_data(ble_bin_buffer, "rqz:", m_static_passkey, 6); - dr_binary_tx_safe(ble_bin_buffer, 5); - return 1; -} - -/* mxz? - life_cycle 쓰기 (sxz? → mxz?) - * 패킷: mxz? + uint16 x 2 (상위16비트, 하위16비트) - * 응답: rxz: + uint16 x 2 - */ -static int Cmd_mxz(const ParsedCmd *cmd) -{ - uint16_t hi = 0, lo = 0; - dr_get_u16(cmd, 0, &hi); - dr_get_u16(cmd, 1, &lo); - - m_life_cycle = ((uint32_t)hi << 16) | (uint32_t)lo; - m_config.life_cycle = m_life_cycle; - config_save(); - - if (g_plat.log) g_plat.log("[mxz] life_cycle=%u saved\r\n", m_life_cycle); - - uint16_t result[2] = { hi, lo }; - format_data(ble_bin_buffer, "rxz:", result, 2); - dr_binary_tx_safe(ble_bin_buffer, 4); - return 1; -} - -/* myz? - life_cycle 읽기 (syz? → myz?) - * 패킷: myz? (데이터 없음) - * 응답: ryz: + uint16 x 2 - */ -static int Cmd_myz(const ParsedCmd *cmd) -{ - (void)cmd; - m_life_cycle = m_config.life_cycle; - - uint16_t result[2]; - result[0] = (uint16_t)(m_life_cycle >> 16); - result[1] = (uint16_t)(m_life_cycle & 0xFFFF); - - format_data(ble_bin_buffer, "ryz:", result, 2); - dr_binary_tx_safe(ble_bin_buffer, 4); - return 1; -} - -/*============================================================================== - * IMU: 6-axis raw data (single shot) - *============================================================================*/ - -/* msp? - Read IMU accel(xyz) + gyro(xyz) raw data, single shot - * Response: rsp: + 6 x uint16_t (accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z) - */ -static int Cmd_msp(const ParsedCmd *cmd) -{ - (void)cmd; - - if (g_plat.log) g_plat.log("[MSP] enter\r\n"); - - hw_i2c_init_once(); - - /* Direct register read — no timer, no DRDY, no callback */ - int rc = imu_read_direct(); - - if (g_plat.log) g_plat.log("[MSP] rc=%d\r\n", rc); - return 1; -} +/* + * 2025-12-08 power loop bug fix + * 2025-12-07 msn, mta, mqq + * 2025-12-04 by Charles KWON + * parser.c : Common parser + command table + handlers + * - Firmware/PC shared + * - Hardware-dependent parts left as TODO + * - Added CRC16 validation support + * + * [파일 개요] + * BLE(Bluetooth Low Energy)를 통해 수신된 바이너리 명령 패킷을 파싱하고, 명령 테이블에서 해당 핸들러를 찾아 실행하는 공통 파서 모듈 + * + * 패킷 구조: [TAG 4바이트] [데이터 N바이트] [CRC16 2바이트(선택)] + * - TAG: 4글자 ASCII 명령 식별자 (예: "mta?", "mec?") + * - 데이터: Little-Endian uint16 또는 ASCII 문자열 + * - CRC16: g_plat.crc_check가 true일 때 패킷 끝 2바이트로 무결성 검증 + * + * 동작 흐름: + * 1) dr_cmd_parser() → 외부에서 호출하는 진입점 + * 2) dr_parse_cmd() → CRC 검증 + TAG/데이터 분리 + * 3) dr_cmd_dispatch() → 명령 테이블(g_cmd_table) 순회하여 핸들러 호출 + * 4) Cmd_xxx() → 각 명령별 하드웨어 제어 및 BLE 응답 전송 + */ + +#include "parser.h" +#include + +#include "nrf_gpio.h" +#include "nrf_delay.h" +#include "dr_piezo.h" +#include "dr_util.h" +#include "dr_adc121s051.h" + + +/*============================================================================== + * 외부 함수 선언부 + * - 다른 모듈(센서, BLE, 디바이스 제어 등)에서 정의된 함수들의 프로토타입 + *============================================================================*/ + +/* 센서 측정 함수 */ +extern void battery_level_meas(void); /* 배터리 잔량 ADC 측정 */ +extern void pressure_all_level_meas(void); /* 압력 센서 1,2 동시 측정 */ +extern void tmp235_voltage_level_meas(void); /* TMP235 온도 센서 전압 측정 */ + +/* 디바이스 전원/상태 제어 함수 */ +extern int device_activated(void); /* 디바이스 활성화 (센서 전원 ON) → 0:성공 */ +extern int device_sleep_mode(void); /* 슬립 모드 진입 (저전력) → 0:성공 */ + +/* 에러 처리 */ +extern void param_error(const char *cmd); /* 파라미터 오류 시 BLE로 에러 응답 전송 */ + +/* BLE 바이너리 전송 함수 */ +extern void single_format_data(uint8_t *buffer, const char *tag, uint16_t value); /* TAG(4바이트) + uint16 값(2바이트)을 buffer에 포맷팅 */ +extern void ascii_format_data(uint8_t *buffer, const char *tag, const char *ascii, uint8_t len); /* TAG(4바이트) + ASCII 문자열을 buffer에 포맷팅 */ +extern void dr_binary_tx_safe(const uint8_t *buffer, uint16_t length); /* BLE NUS를 통해 바이너리 데이터 전송 (length는 워드 단위) */ +extern void dr_sd_delay_ms(uint32_t ms); /* SoftDevice 호환 딜레이 (BLE 스택 이벤트 처리 허용) */ + +/* FDS(Flash Data Storage) 설정 관련 */ +#include "fstorage.h" +extern char SERIAL_NO[12]; /* 시리얼 넘버 (12자 ASCII, FDS에 저장) */ +extern char HW_NO[12]; /* 하드웨어 넘버 (12자 ASCII, FDS에 저장) */ +extern char m_static_passkey[6]; /* BLE 정적 패스키 (6자리 숫자 문자열) */ +extern uint32_t m_life_cycle; /* 디바이스 수명 카운터 (FDS에 영구 저장) */ +extern void format_data(uint8_t *buffer, const char *tag, const uint16_t *data_array, size_t length); /* TAG + uint16 배열을 buffer에 포맷팅 (가변 길이 응답용) */ + +#define DR_DEVICE_VERSION "FW25LIT2B102" /* 펌웨어 버전 문자열 - cmd_parse.c의 DEVICE_VERSION과 반드시 일치해야 함 */ + + +/*============================================================================== + * 외부 변수 선언부 + * - main.c 등 다른 모듈에서 정의된 전역 변수 참조 + *============================================================================*/ + +extern volatile bool processing; /* true: 측정 진행 중 (중복 명령 방지 플래그) */ +extern bool device_status; /* true: 디바이스 활성화 상태 (센서 전원 ON) */ +extern uint8_t resetCount; /* 통신 타임아웃 카운터 (리셋 감지용) */ +extern uint8_t ble_bin_buffer[]; /* BLE 바이너리 전송 버퍼 (공용) */ + +extern bool con_single; /* 단일 연결 모드 플래그 */ +extern bool lock_check; /* 보안 잠금 상태 플래그 */ + + +extern bool info4; /* true: 추가 정보(배터리/온도/IMU) 포함 측정 */ +extern bool ble_got_new_data; /* true: BLE로 새 데이터 수신됨 (처리 대기) */ +extern bool go_batt; /* true: 배터리 측정 요청 플래그 */ +extern bool motion_data_once; /* true: IMU 1회 측정, false: 연속 측정 */ +extern bool motion_raw_data_enabled;/* true: IMU 원시 데이터 전송 활성화 */ +extern int imu_read_direct(void); /* IMU 레지스터 직접 읽기 + BLE 전송 */ + +extern void pressure_all_level_meas(void); /* 압력 센서 전체 측정 */ +extern void battery_timer_stop(void); /* 배터리 타이머 중지 */ +extern void main_timer_start(void); /* 메인 타이머 시작 (주기적 측정 트리거) */ +extern void hw_i2c_init_once(void); /* IMU용 I2C 버스 초기화 (최초 1회) */ + +/* 전원/리셋/본딩 제어 플래그 */ +extern bool go_device_power_off; /* true → 메인 루프에서 전원 OFF 실행 */ +extern bool go_NVIC_SystemReset; /* true → 메인 루프에서 시스템 리셋 실행 */ +extern bool bond_data_delete; /* true → 리셋 시 BLE 본딩 정보 삭제 */ +extern uint8_t m_reset_status; /* 리셋 상태 코드 (FDS에 저장, 부팅 시 참조) */ +extern void config_save(void); /* 설정값을 FDS(Flash)에 저장 */ +extern config_data_t m_config; /* 전체 설정 구조체 (FDS 저장 대상) */ + +/* AGC(자동 이득 제어) 게인 스위치 매크로 + * - P0.20 핀으로 아날로그 수신 회로의 게인을 HIGH/LOW 전환 + * - measurements.h의 매크로를 여기서도 사용하기 위해 재정의 */ +#include "nrf_gpio.h" +#define GAIN_SW_PIN NRF_GPIO_PIN_MAP(0, 20) +#define AGC_GAIN_SW(x) do { if(x) nrf_gpio_pin_set(GAIN_SW_PIN); else nrf_gpio_pin_clear(GAIN_SW_PIN); } while(0) + + +/* 피에조 초음파 트랜스듀서 제어 함수 */ +extern void dr_piezo_power_on( void ); /* 피에조 회로 전원 ON (TX/RX 보드) */ +extern void dr_piezo_power_off( void ); /* 피에조 회로 전원 OFF */ +extern void dr_piezo_burst_sw(uint8_t cycles); /* 2.1MHz 버스트 펄스 발생 (기본 주파수) */ +extern void dr_piezo_burst_sw_18mhz(uint8_t cycles); /* 1.8MHz 버스트 펄스 발생 */ +extern void dr_piezo_burst_sw_20mhz(uint8_t cycles); /* 2.0MHz 버스트 펄스 발생 */ +extern void dr_piezo_burst_sw_17mhz(uint8_t cycles); /* 1.7MHz 버스트 펄스 발생 */ + +/* ---- 전역 변수 정의 (헤더에서 extern 선언) ---- */ +dr_platform_if_t g_plat = { 0, 0, 0 }; + /* 플랫폼 인터페이스 구조체: + * .tx_bin = BLE 바이너리 전송 함수 포인터 + * .log = 디버그 로그 출력 함수 포인터 + * .crc_check = CRC16 검증 활성화 여부 */ +bool g_log_enable = false; + /* 디버그 로그 전역 활성화 플래그 (g_plat.log와 함께 사용) */ + +/* ---- 내부 상수/구조체 정의 ---- */ + +#define DR_MAX_DATA 128 /* TAG 이후 데이터의 최대 바이트 수 */ + +/* 파싱된 명령 구조체 - 수신 패킷을 TAG와 데이터로 분리한 결과 */ +typedef struct { + char tag[5]; /* 명령 TAG 문자열 (예: "sta?") 4글자 + '\0' */ + uint8_t data[DR_MAX_DATA]; /* TAG 뒤의 원시 데이터 바이트 배열 */ + uint8_t data_len; /* data[]의 유효 길이 (바이트) */ +} ParsedCmd; + +/* ---- 내부 유틸리티 함수 ---- */ + +/* 수신 버퍼의 처음 4바이트를 TAG 문자열로 복사 (널 종료 포함) */ +static void dr_copy_tag(const uint8_t *buf, char *tag_out) +{ + tag_out[0] = (char)buf[0]; + tag_out[1] = (char)buf[1]; + tag_out[2] = (char)buf[2]; + tag_out[3] = (char)buf[3]; + tag_out[4] = '\0'; +} + +/* TAG 4글자 비교 (memcmp 대신 바이트 단위 비교로 최적화) */ +static bool dr_tag_eq(const char *tag, const char *key4) +{ + return (tag[0] == key4[0] && + tag[1] == key4[1] && + tag[2] == key4[2] && + tag[3] == key4[3]); +} + +/* 파싱된 명령 데이터에서 uint16 값 추출 (Little-Endian) + * + * word_index: 추출할 워드 인덱스 (0번째 = data[0..1], 1번째 = data[2..3], ...) + * out: 추출된 uint16 값 저장 포인터 + * 반환값: true=성공, false=데이터 부족 + * + * PC와 nRF52 모두 Little-Endian이므로 data[pos]=하위바이트, data[pos+1]=상위바이트 */ +static bool dr_get_u16(const ParsedCmd *cmd, uint8_t word_index, uint16_t *out) +{ + uint8_t pos = (uint8_t)(word_index * 2); + if (cmd->data_len < (uint8_t)(pos + 2)) { + return false; /* 데이터 길이 부족 → 기본값 유지 */ + } + + /* Little Endian 조합: data[pos]=하위, data[pos+1]=상위 */ + *out = (uint16_t)cmd->data[pos] + | (uint16_t)((uint16_t)cmd->data[pos + 1] << 8); + + return true; +} + +/* ASCII 문자열 추출: data[offset] ~ data[offset+max_len-1] → 널 종료 문자열 + * + * 시리얼번호, HW번호, 패스키 등 FDS에 저장할 텍스트 데이터를 추출할 때 사용 + * offset: data[] 내 시작 위치 + * out: 추출된 문자열 저장 버퍼 (max_len+1 이상 크기 필요) + * max_len: 최대 추출 길이 (바이트) + */ +static void dr_get_ascii(const ParsedCmd *cmd, uint8_t offset, + char *out, uint8_t max_len) +{ + uint8_t i; + uint8_t remain; + + /* 오프셋이 데이터 범위를 초과하면 빈 문자열 반환 */ + if (offset >= cmd->data_len) { + out[0] = '\0'; + return; + } + + /* 실제 복사 가능한 길이 계산 (남은 데이터 vs 최대 길이 중 작은 값) */ + remain = (uint8_t)(cmd->data_len - offset); + if (remain > max_len) { + remain = max_len; + } + + for (i = 0; i < remain; i++) { + out[i] = (char)cmd->data[offset + i]; + } + out[remain] = '\0'; /* 널 종료 보장 */ +} + +/* ---- CRC16 무결성 검증 함수 ---- */ + +/* CRC16 계산 - Nordic SDK의 crc16_compute와 동일한 알고리즘 (CRC-CCITT 변형) + * + * p_data: 계산 대상 데이터 포인터 + * size: 데이터 길이 (바이트) + * p_crc: 초기 CRC 값 (NULL이면 0xFFFF로 시작) + * 반환값: 계산된 CRC16 값 + */ +static uint16_t dr_crc16_compute(const uint8_t *p_data, uint32_t size, const uint16_t *p_crc) +{ + uint32_t i; + uint16_t crc = (p_crc == NULL) ? 0xFFFF : *p_crc; + + for (i = 0; i < size; i++) + { + crc = (uint8_t)(crc >> 8) | (crc << 8); /* 상위/하위 바이트 교환 */ + crc ^= p_data[i]; /* 데이터 바이트 XOR */ + crc ^= (uint8_t)(crc & 0xFF) >> 4; /* 다항식 연산 1 */ + crc ^= (crc << 8) << 4; /* 다항식 연산 2 */ + crc ^= ((crc & 0xFF) << 4) << 1; /* 다항식 연산 3 */ + } + + return crc; +} + +/* CRC16 검증: 데이터에 대해 계산한 CRC와 기대값 비교 */ +static bool dr_crc16_check(const uint8_t *p_data, uint32_t data_len, uint16_t expected_crc) +{ + uint16_t computed_crc = dr_crc16_compute(p_data, data_len, NULL); + return (computed_crc == expected_crc); +} + +/* 패킷 단위 CRC16 검증 + * 패킷 구조: [데이터 N바이트] + [CRC16 2바이트(Little-Endian)] + * 마지막 2바이트를 CRC 기대값으로 추출하여, 나머지 데이터의 CRC와 비교 */ +static bool dr_crc16_check_packet(const uint8_t *packet, uint32_t packet_len) +{ + uint16_t expected_crc; + uint32_t data_len; + + if (packet_len < 2) { + return false; /* 최소 CRC 2바이트도 없으면 실패 */ + } + + data_len = packet_len - 2; /* CRC를 제외한 실제 데이터 길이 */ + + /* 패킷 끝 2바이트에서 CRC 추출 (Little-Endian: 하위바이트 먼저) */ + expected_crc = (uint16_t)packet[packet_len - 2] + | ((uint16_t)packet[packet_len - 1] << 8); + + if (g_plat.log && g_log_enable) { + g_plat.log("CRC check: expected=0x%04X\n", expected_crc); + } + + return dr_crc16_check(packet, data_len, expected_crc); +} + +/* ---- 수신 버퍼 → ParsedCmd 구조체 변환 ---- */ + +/* 수신된 원시 바이트 버퍼를 파싱하여 TAG와 데이터를 분리 + * + * buffer: 수신 패킷 [TAG(4)] [DATA(N)] [CRC16(2, 선택)] + * length: 전체 패킷 길이 + * out: 파싱 결과 저장 구조체 + * 반환값: true=파싱 성공, false=CRC 실패 또는 패킷 길이 부족 + */ +static bool dr_parse_cmd(const uint8_t *buffer, uint8_t length, ParsedCmd *out) +{ + uint8_t i; + + if (length < 4) { + return false; /* TAG 4바이트조차 수신되지 않음 */ + } + + /* CRC 검증이 활성화된 경우 패킷 무결성 확인 */ + if (g_plat.crc_check) { + if (!dr_crc16_check_packet(buffer, length)) { + if (g_plat.log && g_log_enable) { + g_plat.log("CRC check FAILED!\n"); + } + return false; + } + + /* CRC 검증 성공 → CRC 2바이트를 제외한 실제 데이터 길이로 조정 */ + length = (uint8_t)(length - 2); + + if (g_plat.log && g_log_enable) { + g_plat.log("CRC check OK\n"); + } + } + + /* TAG 4바이트 복사 */ + dr_copy_tag(buffer, out->tag); + + /* TAG 이후 데이터 길이 계산 (최대 DR_MAX_DATA까지) */ + out->data_len = (length > 4) ? (uint8_t)(length - 4) : 0; + if (out->data_len > DR_MAX_DATA) { + out->data_len = DR_MAX_DATA; + } + + /* TAG 뒤의 데이터 바이트를 ParsedCmd.data[]에 복사 */ + for (i = 0; i < out->data_len; i++) { + out->data[i] = buffer[4 + i]; + } + + if (g_plat.log && g_log_enable) { + g_plat.log("parse_cmd: TAG='%s', data_len=%u\n", + out->tag, out->data_len); + } + + return true; +} + +/*============================================================================== + * BLE 명령어 + * + * A. 디바이스 상태 제어 + * - 전원 OFF (msq? / rsq:) : 확인 필요 + * - 재부팅 (mss? / rss:) : 확인 필요 + * - 본딩 정보 삭제 및 재부팅 (msr? / rsr:) : 확인 필요 + * - GPIO 제어(핀 테스트용, cmd? / rmd:) + * + * B. 디바이스 정보 읽기, 쓰기 + * - 하드웨어 버전 읽기 (mrh? / rrh:) + * - 하드웨어 버전 쓰기 (mwh? / rwh:) + * - 시리얼 넘버 읽기 (mrs? / rrs:) + * - 시리얼 넘버 쓰기 (mws? / rws:) + * - 펌웨어 버전 읽기 (mfv? / rfv:) + * - 패스키 읽기 (mqz? / rqz:) : 확인 필요, 필요 유무 검토(암호화 혹은 삭제) + * - 패스키 쓰기 (mpz? / rpz:) : 확인 필요 + * + * C. 각종 센서 측정 + * - 배터리 전압 측정 (msn? / rsn:) - test용 + * - IMU 단발 측정 (msp? / rsp:) : 동기 - test용 + * - IMU 연속 스트리밍 (msi? / rsi:) : 비동기(타이머) 1초 주기, 확인 필요 - test용 + * - 온도 측정 (mso? / rso:) : 확인 필요 - test용 + * + * D. Piezo 초음파 측정 + * - TX/RX 전원 활성화 (mpa? / rpa:) + * - TX/RX 전원 비활성화 (mpb? / rpb:) + * - 단일 채널 Burst (mpc? / rpc:) - test용 + * - 단일 채널 Burst + ADC -> echo capture (mec? / reb: -> red: -> ree:) + * - 모든 채널 Burst + ADC -> echo capture (maa? / reb: -> red: -> raa:) : 확인 필요, 현재 모든 채널 = 4 + * + * 삭제 명령어 + * - 디바이스 활성화/슬립 : TX 전원 활성화/비활성화와 동일 기능 + * - 디바이스 상태 조회 + * - 디바이스 준비 확인 + * - 수명 카운터(life cycle) 읽기 및 쓰기 + * - 압력 센서 측정 : 센서 미탑재 + * - 단일 채널 ADC + *============================================================================*/ + + +/*============================================================================== + * 핸들러 함수 프로토타입 선언 + * - 반환값: 1=성공, 0=실패/비활성 + * - 각 핸들러는 BLE 응답 전송까지 담당 + *============================================================================*/ + +/* A. 디바이스 상태 제어 */ +static int Cmd_msq(const ParsedCmd *cmd); /* msq? 디바이스 전원 OFF */ +static int Cmd_mss(const ParsedCmd *cmd); /* mss? 디바이스 재부팅 */ +#if FEATURE_SECURE_CONNECTION +static int Cmd_msr(const ParsedCmd *cmd); /* msr? 본딩 삭제 + 디바이스 재부팅 */ +#endif +static int Cmd_cmd(const ParsedCmd *cmd); /* cmd? GPIO 핀 테스트 (HIGH/LOW 제어) */ + +/* B. 디바이스 정보 읽기, 쓰기 */ +static int Cmd_mwh(const ParsedCmd *cmd); /* mwh? HW 넘버 FDS에 쓰기 */ +static int Cmd_mws(const ParsedCmd *cmd); /* mws? 시리얼 넘버 FDS에 쓰기 */ +static int Cmd_mrh(const ParsedCmd *cmd); /* mrh? HW 넘버 FDS에서 읽기 */ +static int Cmd_mrs(const ParsedCmd *cmd); /* mrs? 시리얼 넘버 FDS에서 읽기 */ +static int Cmd_mfv(const ParsedCmd *cmd); /* mfv? 펌웨어 버전 읽기 */ +static int Cmd_mpz(const ParsedCmd *cmd); /* mpz? BLE 패스키 FDS에 쓰기 */ +static int Cmd_mqz(const ParsedCmd *cmd); /* mqz? BLE 패스키 FDS에서 읽기 */ + +/* C. 각종 센서 측정 */ +static int Cmd_msn(const ParsedCmd *cmd); /* msn? 배터리 잔량 측정 */ +static int Cmd_mso(const ParsedCmd *cmd); /* mso? TMP235 온도 센서 측정 */ +static int Cmd_msp(const ParsedCmd *cmd); /* msp? IMU 6축 원시 데이터 (단발) */ +static int Cmd_msi(const ParsedCmd *cmd); /* msi? IMU 모션 센서 스트리밍 (타이머 기반) */ + +/* D. Piezo 초음파 측정 */ +static int Cmd_mpa(const ParsedCmd *cmd); /* mpa? 피에조 TX/RX 회로 활성화 (전원 ON) */ +static int Cmd_mpb(const ParsedCmd *cmd); /* mpb? 피에조 TX/RX 회로 비활성화 (전원 OFF) */ +static int Cmd_mpc(const ParsedCmd *cmd); /* mpc? 피에조 버스트 발생 (주파수/사이클 제어) */ +static int Cmd_mec(const ParsedCmd *cmd); /* mec? 피에조 버스트 + 에코 캡처 (16비트 원시) */ +static int Cmd_maa(const ParsedCmd *cmd); /* maa? 4채널 전체 캡처 (비동기 전송) */ + + +/* ---- 명령 테이블 ---- */ + +/* 명령 엔트리 구조체: TAG 문자열 + 활성화 여부 + 핸들러 함수 포인터 */ +typedef struct { + char tag[5]; /* 명령 TAG (예: "sta?") - 4글자 + NULL */ + bool enabled; /* false면 핸들러가 호출되지 않음 (비활성 명령) */ + int (*handler)(const ParsedCmd *cmd); /* 핸들러 함수 포인터 (1=성공, 0=실패) */ +} CmdEntry; + +/* 전체 명령 테이블 - 수신된 TAG와 순차 비교하여 일치하는 핸들러 호출 + * + * enabled=false인 명령은 개발 중 + * 동일 핸들러를 여러 TAG에 매핑하여 레거시 호환성 유지 (예: mcj?=scj?, msn?=ssn?) -> 레거시 정리 중 + */ +static CmdEntry g_cmd_table[] = { + + /* A. 디바이스 상태 제어 */ + { "msq?", true, Cmd_msq }, + { "mss?", true, Cmd_mss }, +#if FEATURE_SECURE_CONNECTION + { "msr?", true, Cmd_msr }, +#endif + { "cmd?", true, Cmd_cmd }, + + /* C. 디바이스 정보 읽기, 쓰기 */ + { "mwh?", true, Cmd_mwh }, + { "mws?", true, Cmd_mws }, + { "mrh?", true, Cmd_mrh }, + { "mrs?", true, Cmd_mrs }, + { "mpz?", true, Cmd_mpz }, + { "mqz?", true, Cmd_mqz }, + { "mfv?", true, Cmd_mfv }, + + /* D. 각종 센서 측정 */ + { "msn?", true, Cmd_msn }, + { "mso?", false, Cmd_mso }, + { "msp?", true, Cmd_msp }, + { "msi?", true, Cmd_msi }, + + /* E. Piezo 초음파 측정 */ + { "mpa?", true, Cmd_mpa }, + { "mpb?", true, Cmd_mpb }, + { "mpc?", true, Cmd_mpc }, + { "mec?", true, Cmd_mec }, + { "maa?", true, Cmd_maa }, +}; + +/* 명령 테이블 엔트리 수 (컴파일 타임 계산) */ +static const uint16_t g_cmd_count = + (uint16_t)(sizeof(g_cmd_table) / sizeof(g_cmd_table[0])); + +/* ---- 명령 디스패처 ---- + * 파싱된 TAG를 소문자로 변환 후, 명령 테이블을 순회하여 일치하는 핸들러 호출 + * + * 반환값: 핸들러의 반환값 (1=성공), 매칭 실패 또는 비활성 명령이면 0 + */ +static int dr_cmd_dispatch(const ParsedCmd *cmd) +{ + uint16_t i; + char tag_lower[5]; + + /* 수신된 TAG를 소문자로 변환 (대소문자 구분 없이 명령 매칭) */ + for (i = 0; i < 4 && cmd->tag[i]; i++) { + tag_lower[i] = (cmd->tag[i] >= 'A' && cmd->tag[i] <= 'Z') + ? (cmd->tag[i] + 32) : cmd->tag[i]; + } + tag_lower[i] = '\0'; + + /* 명령 테이블 순차 검색 */ + for (i = 0; i < g_cmd_count; i++) { + if (dr_tag_eq(tag_lower, g_cmd_table[i].tag)) { + + /* 비활성화된 명령이면 실행하지 않음 */ + if (!g_cmd_table[i].enabled) { + if (g_plat.log && g_log_enable) { + g_plat.log("Command '%s' disabled\n", cmd->tag); + } + return 0; + } + + if (g_plat.log && g_log_enable) { + g_plat.log("Run handler '%s'\n", cmd->tag); + } + + /* 매칭된 핸들러 호출 */ + return g_cmd_table[i].handler(cmd); + } + } + + /* 테이블에 없는 미지의 TAG */ + if (g_plat.log && g_log_enable) { + g_plat.log("Unknown TAG '%s'\n", cmd->tag); + } + return 0; +} + +/*============================================================================== + * 메인 파서 진입점 - 외부 모듈(BLE 수신 콜백 등)에서 호출 + * + * buf: 수신된 BLE 패킷 원시 바이트 + * len: 패킷 길이 + * 반환값: + * 1 = 명령 처리 성공 + * 0 = 알 수 없는 TAG 또는 비활성 명령 + * -1 = CRC 실패 또는 파싱 실패 → 호출자가 레거시 파서로 위임 가능 + *============================================================================*/ + +int dr_cmd_parser(const uint8_t *buf, uint8_t len) +{ + ParsedCmd cmd; + g_plat.log("parser!!!!!!!!"); + + if (g_plat.log) g_plat.log("[PARSER] in len=%u crc=%u\r\n", len, g_plat.crc_check); + + /* 패킷 파싱 (CRC 검증 포함) */ + if (!dr_parse_cmd(buf, len, &cmd)) { + if (g_plat.log) g_plat.log("[PARSER] PARSE FAIL\r\n"); + + /* CRC 실패 시 "crc!" 에러 응답을 BLE로 전송 (65530 = 에러 코드) */ + if (g_plat.crc_check && g_plat.tx_bin) { + single_format_data(ble_bin_buffer, "crc!", 65530); + dr_binary_tx_safe(ble_bin_buffer, 3); + } + return -1; /* CRC/파싱 실패 → 음수 반환으로 레거시 파서에 위임 */ + } + + if (g_plat.log) g_plat.log("[PARSER] tag=%s\r\n", cmd.tag); + + /* 파싱 성공 → 명령 테이블에서 핸들러를 찾아 실행 */ + return dr_cmd_dispatch(&cmd); +} + +/*============================================================================== + * 각 명령 핸들러 구현부 + *============================================================================*/ + +/* 2026-03-17: mta?, mtr? 삭제 */ + + +/* msn? - 배터리 잔량 ADC 측정 + * 응답: battery_level_meas() 내부에서 BLE 전송 처리 */ +static int Cmd_msn(const ParsedCmd *cmd) +{ + (void)cmd; + if (g_plat.log && g_log_enable) { + g_plat.log("[Cmd_msn] Measure battery level\n"); + } + battery_level_meas(); /* ADC로 배터리 전압 측정 → BLE 응답 전송 */ + return 1; +} + + + + +/* 2026-03-17: mpn? 삭제 (압력센서 미탑재) */ + +/* mso? - TMP235-Q1 온도 센서 전압 측정 + * SAADC로 TMP235 출력 전압을 측정하여 BLE로 응답한다. + * 응답은 tmp235_q1.c의 콜백에서 자동 전송. */ +static int Cmd_mso(const ParsedCmd *cmd) +{ + (void)cmd; + if (g_plat.log && g_log_enable) { + g_plat.log("[Cmd_mso] TMP235 temperature measurement\n"); + } + tmp235_voltage_level_meas(); + return 1; +} + +/* msi? - IMU 모션 센서 원시 데이터 읽기 + * + * 파라미터: data[0]='c'이면 연속 측정, 그 외 단발 측정 + * 단발 모드: 1회 측정 후 자동 중지 + * 연속 모드: 타이머 주기마다 반복 측정 (별도 중지 명령 필요) + * + * 동작: I2C 초기화 → 플래그 설정 → 메인 타이머 시작 + */ +static int Cmd_msi(const ParsedCmd *cmd) +{ + /* IMU용 I2C 버스 초기화 (최초 호출 시 1회만 실행) */ + hw_i2c_init_once(); + + motion_raw_data_enabled = true; /* IMU 원시 데이터 전송 활성화 */ + ble_got_new_data = false; + + /* 'c' = 연속(continuous) 모드, 그 외 = 단발(single shot) 모드 */ + if (cmd->data_len > 0 && (char)cmd->data[0] == 'c') { + motion_data_once = false; /* 연속 측정 */ + } else { + motion_data_once = true; /* 1회 측정 후 자동 중지 */ + } + + main_timer_start(); /* 타이머 콜백에서 IMU 데이터 읽기 + BLE 전송 */ + + if (g_plat.log && g_log_enable) { + g_plat.log("[Cmd_msi] Motion sensor raw, once=%u\r\n", motion_data_once); + } + return 1; +} + +/*============================================================================== + * J. 전원/리셋/버전/보안 핸들러 + *============================================================================*/ + +/* msq? - 디바이스 전원 OFF + * + * BLE 응답("rsq:") 전송 후 → 메인 루프에서 실제 전원 OFF 수행 + * 즉시 전원을 끄면 BLE 응답이 전달되지 않으므로, 플래그+타이머 방식 사용 + */ +static int Cmd_msq(const ParsedCmd *cmd) +{ + uint16_t val = 0; + dr_get_u16(cmd, 0, &val); + if (g_plat.log && g_log_enable) { + g_plat.log("[Cmd_msq] Power off\r\n"); + } + single_format_data(ble_bin_buffer, "rsq:", val); + dr_binary_tx_safe(ble_bin_buffer, 2); + go_device_power_off = true; /* 메인 루프에서 전원 OFF 실행 예약 */ + main_timer_start(); /* 타이머 콜백에서 전원 OFF 처리 */ + return 1; +} + +/* msr? - BLE 본딩 정보 삭제 + 시스템 리셋 + * + * 동작 순서: + * 1. BLE 응답("rsr:") 전송 + * 2. 본딩 삭제 플래그 + 리셋 상태를 FDS에 저장 + * 3. 5ms 딜레이 (FDS 쓰기 완료 대기) + * 4. NVIC 시스템 리셋 플래그 설정 → 타이머에서 리셋 실행 + */ +#if FEATURE_SECURE_CONNECTION +static int Cmd_msr(const ParsedCmd *cmd) +{ + uint16_t val = 0; + dr_get_u16(cmd, 0, &val); + if (g_plat.log && g_log_enable) { + g_plat.log("[Cmd_msr] Bond delete + reset\r\n"); + } + single_format_data(ble_bin_buffer, "rsr:", val); + dr_binary_tx_safe(ble_bin_buffer, 2); + + /* 본딩 삭제 + 리셋 상태를 설정에 저장 (재부팅 후에도 유지) */ + bond_data_delete = true; + m_config.bond_data_delete = (uint8_t)bond_data_delete; + m_reset_status = 2; /* 리셋 상태 코드 2 = 본딩 삭제 리셋 */ + m_config.reset_status = m_reset_status; + config_save(); /* FDS에 설정 영구 저장 */ + nrf_delay_ms(5); /* Flash 쓰기 완료 대기 */ + go_NVIC_SystemReset = true; /* 시스템 리셋 예약 */ + main_timer_start(); + return 1; +} +#endif + +/* mss? - 디바이스 소프트 리셋 (리부팅) + * + * msr?과 유사하지만 본딩 정보는 삭제하지 않음 + * 리셋 상태 코드를 FDS에 저장하여 부팅 시 리셋 원인 확인 가능 + */ +static int Cmd_mss(const ParsedCmd *cmd) +{ + uint16_t val = 0; + dr_get_u16(cmd, 0, &val); + if (g_plat.log && g_log_enable) { + g_plat.log("[Cmd_mss] Device reset\r\n"); + } + single_format_data(ble_bin_buffer, "rss:", val); + dr_binary_tx_safe(ble_bin_buffer, 2); + + m_reset_status = 2; + m_config.reset_status = m_reset_status; + config_save(); + nrf_delay_ms(5); + go_NVIC_SystemReset = true; + main_timer_start(); + return 1; +} + +/* 2026-03-17: mst? 삭제 */ + +/* mfv? - 펌웨어 버전 읽기 + * + * 응답: "rfv:" + 12자 ASCII 버전 문자열 (DR_DEVICE_VERSION) + * 구 명령 ssv?에서 mfv?로 변경 (26.03.13) + */ +static int Cmd_mfv(const ParsedCmd *cmd) +{ + (void)cmd; + if (g_plat.log && g_log_enable) { + g_plat.log("[Cmd_mfv] FW=%s\r\n", DR_DEVICE_VERSION); + } + ascii_format_data(ble_bin_buffer, "rfv:", DR_DEVICE_VERSION, 12); + dr_binary_tx_safe(ble_bin_buffer, 8); /* 4(TAG) + 12(버전) = 16바이트 = 8워드 */ + return 1; +} + +/* mpa? - 피에조 초음파 TX/RX 회로 활성화 + * + * 동작: 피에조 전원 ON → 시스템 초기화 (DAC, MUX, ADC 설정) + * 응답: "rpa:" + 1 (성공) + * 주의: mec?/mdc?/maa? 실행 전에 반드시 먼저 호출해야 함 + */ +static int Cmd_mpa(const ParsedCmd *cmd) +{ + (void)cmd; + + if (g_plat.log && g_log_enable) { + g_plat.log("[Cmd_mpa] Piezo Activation\n"); + } + + dr_piezo_power_on(); /* 피에조 보드 전원 ON (LDO 활성화) */ + dr_piezo_system_init(); /* 피에조 시스템 초기화 (DAC/MUX/ADC 설정) */ + + if (g_plat.tx_bin) { + single_format_data(ble_bin_buffer, "rpa:", 1); + dr_binary_tx_safe(ble_bin_buffer, 3); + } + + return 1; +} + +/* mpb? - 피에조 초음파 TX/RX 회로 비활성화 (전원 OFF) + * + * 측정 완료 후 전력 소모 절감을 위해 호출 + * 응답: "rpb:" + 1 (성공) + */ +static int Cmd_mpb(const ParsedCmd *cmd) +{ + (void)cmd; + + if (g_plat.log && g_log_enable) { + g_plat.log("[Cmd_mpb] Piezo Deactivation\n"); + } + + dr_piezo_power_off(); /* 피에조 보드 전원 OFF */ + + if (g_plat.tx_bin) { + single_format_data(ble_bin_buffer, "rpb:", 1); + dr_binary_tx_safe(ble_bin_buffer, 3); + } + + return 1; +} + + +/** + * @brief mpc? - 피에조 버스트 발생 명령 (주파수/사이클/채널 선택) + * + * 초음파 트랜스듀서에 지정된 주파수와 사이클 수로 버스트 펄스를 발생시킴 + * 에코 캡처 없이 버스트만 발생 (테스트/디버그용) + * + * 파라미터 (Little-Endian uint16 x 3): + * word 0: cycles (3~9, 기본값=5) - 버스트 펄스 사이클 수 + * word 1: freq_option (기본값=1) - 주파수 선택 + * 0=1.8MHz, 1=2.1MHz(기본), 2=2.0MHz, 3=1.7MHz, 4=2.2MHz + * word 2: piezo_ch (0~7, 기본값=0) - 피에조 채널 선택 + * + * 응답: "rpc:" + cycles (성공), "rpc:" + 2 (범위 초과 에러) + * + * 사용 예시 (Harbour에서): + * mpc?0x05,0x00,0x01,0x00 → 5사이클 @ 2.1MHz + * mpc?0x05,0x00,0x00,0x00 → 5사이클 @ 1.8MHz + * mpc?0x07,0x00,0x02,0x00 → 7사이클 @ 2.0MHz + */ +static int Cmd_mpc(const ParsedCmd *cmd) +{ + uint16_t cycles = 5; /* 기본 5사이클 */ + uint16_t freq_option = 1; /* 기본 2.1MHz (0=1.8, 1=2.1, 2=2.0, 3=1.7, 4=2.2) */ + uint16_t piezo_ch = 0; /* 기본 채널 0 (0~7) */ + + /* 파라미터 추출 (데이터 부족 시 기본값 유지) */ + (void)dr_get_u16(cmd, 0, &cycles); /* word 0: 사이클 수 */ + (void)dr_get_u16(cmd, 1, &freq_option); /* word 1: 주파수 옵션 */ + (void)dr_get_u16(cmd, 2, &piezo_ch); /* word 2: 피에조 채널 */ + + /* 채널 범위 검증: 0~7 유효, 초과 시 0으로 리셋 */ + if (piezo_ch > 7) piezo_ch = 0; + + if (g_plat.log && g_log_enable) { + const char *freq_str = (freq_option == 0) ? "1.8MHz" : + (freq_option == 1) ? "2.1MHz" : + (freq_option == 2) ? "2.0MHz" : + (freq_option == 3) ? "1.7MHz" : + (freq_option == 4) ? "2.2MHz" : + (freq_option == 9) ? "1.9MHz" : "unknown"; + g_plat.log("[Cmd_mpc] cycles=%u, freq=%u (%s), piezo=%u\r\n", + cycles, freq_option, freq_str, piezo_ch); + } + + /* 사이클 범위 검증: 3~9 유효 */ + if (cycles < 3 || cycles > 9) { + dr_ble_return_1("rpc:", 2); /* 에러 응답: 범위 초과 */ + return 1; + } + + /* MUX로 피에조 채널 선택 (0~7번 트랜스듀서 중 하나) */ + dr_piezo_select_channel((uint8_t)piezo_ch); + + /* 선택된 주파수로 버스트 펄스 발생 */ + switch (freq_option) { + case 0: dr_piezo_burst_sw_18mhz((uint8_t)cycles); break; /* 1.8MHz */ + case 2: dr_piezo_burst_sw_20mhz((uint8_t)cycles); break; /* 2.0MHz */ + case 3: dr_piezo_burst_sw_17mhz((uint8_t)cycles); break; /* 1.7MHz */ + case 4: dr_piezo_burst_sw_22mhz((uint8_t)cycles); break; /* 2.2MHz */ + /*case 9: dr_piezo_burst_sw_19mhz((uint8_t)cycles); break;*/ /* 1.9MHz (미사용) */ + case 1: + default: dr_piezo_burst_sw((uint8_t)cycles); break; /* 2.1MHz (기본) */ + } + + /* 성공 응답: "rpc:" + 실행된 사이클 수 */ + dr_ble_return_1("rpc:", (uint8_t)cycles); + + return 1; +} + + +/* 2026-03-17: mdc? 삭제 (12비트 압축 전송, mec?로 대체) */ + +/** + * @brief mec? - 피에조 버스트 + 에코 캡처 (16비트 원시 데이터, 무압축) + * + * 12비트 압축(mdc?)과 달리 ADC 원시 16비트 값을 그대로 전송 + * 샘플 수가 적은 근거리 측정(~25cm)에 적합 + * + * 파라미터 (Little-Endian uint16 x 6): + * word 0: freq_option (기본=0) - 주파수 선택 (0=1.8MHz, 1=2.1MHz, ...) + * word 1: delay_us (기본=20) - 버스트 후 ADC 시작 지연 (us) + * word 2: num_samples (기본=140) - ADC 샘플 수 (140샘플 ≒ 20cm 거리) + * word 3: cycles (기본=5) - 버스트 사이클 수 (3~7) + * word 4: averaging (기본=1) - 평균화 횟수 (1~1000, 노이즈 저감용) + * word 5: piezo_ch (기본=0) - 피에조 채널 (0~7) + * + * 응답 멀티패킷 형식 (16비트 원시): + * 헤더 패킷 ("reb:"): total_pkts, peak, idx, baseline, samples + * 데이터 패킷 ("red:"): pkt_idx + 16비트 ADC 원시 데이터 + * 종료 패킷 ("ree:"): total_bytes_sent + * + * 16비트 포맷: 샘플당 2바이트 (Little-Endian) + * 예: 140샘플(20cm) = 280바이트 = 약 2패킷 + */ +static int Cmd_mec(const ParsedCmd *cmd) +{ + uint16_t freq_option = 0; /* 기본 1.8MHz */ + uint16_t delay_us = 20; /* 기본 20us 딜레이 */ + uint16_t num_samples = 140; /* 기본 140샘플 (약 20cm 거리) */ + uint16_t cycles = 5; /* 기본 5사이클 (유효: 3~7) */ + uint16_t averaging = 1; /* 기본 1 (평균화 없음), 최대 1000 */ + uint16_t piezo_ch = 0; /* 기본 채널 0 (유효: 0~7) */ + + /* 6개 파라미터 순서대로 추출 (데이터 부족 시 기본값 유지) */ + (void)dr_get_u16(cmd, 0, &freq_option); /* word 0: 주파수 옵션 */ + (void)dr_get_u16(cmd, 1, &delay_us); /* word 1: 버스트→ADC 딜레이 */ + (void)dr_get_u16(cmd, 2, &num_samples); /* word 2: ADC 샘플 수 */ + (void)dr_get_u16(cmd, 3, &cycles); /* word 3: 버스트 사이클 */ + (void)dr_get_u16(cmd, 4, &averaging); /* word 4: 평균화 횟수 */ + (void)dr_get_u16(cmd, 5, &piezo_ch); /* word 5: 피에조 채널 */ + + /* 평균화 횟수 범위 검증: 1~1000 */ + if (averaging == 0) averaging = 1; + if (averaging > 1000) averaging = 1000; + + /* 피에조 채널 범위 검증: 0~7 */ + if (piezo_ch > 7) piezo_ch = 0; + + if (g_plat.log && g_log_enable) { + const char *freq_str = (freq_option == 0) ? "1.8MHz" : + (freq_option == 1) ? "2.1MHz" : + (freq_option == 2) ? "2.0MHz" : + (freq_option == 3) ? "1.7MHz" : + (freq_option == 4) ? "2.2MHz" : + (freq_option == 9) ? "1.9MHz" : "unknown"; + g_plat.log("[Cmd_mec] freq=%u (%s), delay=%uus, samples=%u, cycles=%u, avg=%u, piezo=%u\r\n", + freq_option, freq_str, delay_us, num_samples, cycles, averaging, piezo_ch); + } + + /* 통합 버스트+캡처+전송 함수 호출 + * 내부 동작 순서: + * 1. ADC 전원 ON + * 2. MUX로 피에조 채널 선택 (0~7) + * 3. 주파수별 버스트 펄스 발생 (freq_option에 따라) + * 4. delay_us 후 ADC 캡처 (averaging 횟수만큼 반복) + * 5. 캡처된 샘플 평균화 (노이즈 저감) + * 6. BLE 멀티패킷 전송 (패킷 간 타이밍 자동 관리) + */ + dr_adc_err_t err = dr_adc_burst_capture_transmit( + (uint8_t)freq_option, delay_us, num_samples, (uint8_t)cycles, + (uint16_t)averaging, (uint8_t)piezo_ch, ble_bin_buffer, 0); /* 0=raa 태그로 전송 */ + + /* 에러 발생 시 에러 코드 + 요청 샘플 수 응답 */ + if (err != DR_ADC_OK) { + dr_ble_return_2("rer:", 0xEE00 | (uint16_t)err, num_samples); + } + + if (g_plat.log && g_log_enable) { + g_plat.log("[Cmd_mec] result=%d\r\n", err); + } + + return 1; +} + + +/* cmd? - GPIO 핀 직접 제어 (디버그/테스트용) + * + * 파라미터 (Little-Endian uint16 x 3, 총 6바이트): + * v1: GPIO 포트 번호 (0 또는 1, nRF52는 2개 포트) + * v2: GPIO 핀 번호 (0~31) + * v3: 출력 상태 (1=HIGH, 0=LOW) + * + * 동작: 지정된 핀을 출력 모드로 설정 후 HIGH/LOW 제어 + * 응답: "rmd:" + v1(포트) + v2(핀) + v3(상태) - 에코 확인 + * + * 주의: 잘못된 핀 제어 시 하드웨어 손상 가능 - 디버그용으로만 사용 + */ +static int Cmd_cmd(const ParsedCmd *cmd) +{ + uint16_t v1, v2, v3; + uint32_t pin_number; + + /* 최소 6바이트(3 워드) 필요 */ + if (cmd->data_len < 6) { + dr_ble_return_1("rmd:", 0); /* 데이터 부족 에러 */ + return 1; + } + + /* Little Endian 파라미터 추출 */ + v1 = (uint16_t)cmd->data[0] | ((uint16_t)cmd->data[1] << 8); /* 포트 (0~1) */ + v2 = (uint16_t)cmd->data[2] | ((uint16_t)cmd->data[3] << 8); /* 핀 (0~31) */ + v3 = (uint16_t)cmd->data[4] | ((uint16_t)cmd->data[5] << 8); /* 상태 (1=HIGH, 0=LOW) */ + + /* nRF52 GPIO 핀 번호 계산: port * 32 + pin */ + pin_number = NRF_GPIO_PIN_MAP(v1, v2); + + /* 핀을 출력 모드로 설정 */ + nrf_gpio_cfg_output(pin_number); + + /* HIGH 또는 LOW 출력 */ + if (v3 == 1) { + nrf_gpio_pin_set(pin_number); /* HIGH 출력 */ + } else { + nrf_gpio_pin_clear(pin_number); /* LOW 출력 */ + } + + /* 응답: 포트, 핀, 상태 에코 */ + dr_ble_return_3("rmd:", v1, v2, v3); + + return 1; +} + + +/** + * @brief maa? - 4채널 전체 캡처 명령 (비동기 버전) + * + * 4개 피에조 채널(CH0~CH3)을 순차적으로 버스트+캡처하고 BLE로 전송 + * 블로킹 방식 대신 비동기 상태 머신으로 구현하여 BLE 스택 안정성 확보 + * + * 파라미터: mode (uint16, word 0) + * mode=0: 16비트 원시 데이터 비동기 전송 (유일하게 지원) + * + * 비동기 아키텍처: + * - maa_async_start()가 CH0 캡처 시작 및 헤더 전송 + * - BLE_NUS_EVT_TX_RDY 콜백이 나머지 데이터/채널 전송 구동 + * - 블로킹 없음 → SoftDevice 이벤트 정상 처리 + * - BLE TX 버퍼 오버플로우로 인한 펌웨어 브릭 방지 + * + * 고정 파라미터 (하드코딩): + * 주파수 = 1.8MHz, 딜레이 = 10us, 샘플 수 = 140 + * 사이클 = 7, 평균화 = 5회 + * + * 응답 형식: + * 각 채널(CH0~CH3)마다: + * reb: [총패킷수(2)] [피크(2)] [인덱스(2)] [기준선(2)] [샘플수(2)] + * red: [패킷순번(2)] [데이터...] + * 전체 완료: + * raa: [상태(2)] + * + * 버전 마커: 0xA000 (vA) = 비동기 4채널 + */ +#define MAA_FREQ_OPTION 1 /* 기본 2.1MHz */ +#define MAA_DELAY_US 10 /* 버스트 후 10us 딜레이 */ +#define MAA_NUM_SAMPLES 140 /* 140샘플 (약 25cm 거리) */ +#define MAA_CYCLES 7 /* 7사이클 버스트 */ +#define MAA_AVERAGING 5 /* 5회 평균화 */ + +static int Cmd_maa(const ParsedCmd *cmd) +{ + uint16_t mode = 0; + dr_adc_err_t err; + + /* mode 파라미터 추출 */ + (void)dr_get_u16(cmd, 0, &mode); + + /* mode 검증 - mode 0(비동기 원시)만 지원 */ + if (mode > 0) { + dr_ble_return_1("raa:", 0xFFFF); /* 미지원 모드 에러 */ + return 1; + } + + /* 이전 캡처가 진행 중인지 확인 (비동기이므로 중복 실행 방지) */ + if (maa_async_is_busy()) { + dr_ble_return_1("raa:", 0xFFFE); /* 처리 중(Busy) 에러 */ + return 1; + } + + /*======================================================================= + * 비동기 4채널 캡처 시작 + * - maa_async_start(): CH0 캡처 실행 + 첫 헤더 패킷 전송 + * - 이후 패킷은 BLE_NUS_EVT_TX_RDY 콜백에서 자동 전송 + * - 블로킹 딜레이 없음 → SoftDevice 이벤트 정상 처리 가능 + *=======================================================================*/ + err = maa_async_start( + (uint8_t)MAA_FREQ_OPTION, + MAA_DELAY_US, + MAA_NUM_SAMPLES, + (uint8_t)MAA_CYCLES, + (uint16_t)MAA_AVERAGING, + ble_bin_buffer + ); + + if (err != DR_ADC_OK) { + /* 시작 실패 → maa_async_start 내부에서 에러 응답 이미 전송됨 */ + return 1; + } + + /* 즉시 반환 → 비동기 전송 진행 중 */ + /* 전체 완료 시 상태 머신이 "raa:" 응답을 자동 전송 */ + return 1; +} + + +/*============================================================================== + * 설정: HW/시리얼 넘버 FDS 읽기/쓰기 + * + * FDS(Flash Data Storage)에 디바이스 식별 정보를 영구 저장 + * 제조 시 공장에서 한 번 기록하고, 이후 읽기로 확인 + *============================================================================*/ + +/* mwh? - HW 넘버 FDS에 쓰기 + * + * 데이터: 12바이트 ASCII HW 넘버 (예: "VB2025A00001") + * 응답: "rwh:" + 저장된 HW 넘버 에코 + */ +static int Cmd_mwh(const ParsedCmd *cmd) +{ + char buf[13]; + + /* 최소 12바이트 데이터 필요 */ + if (cmd->data_len < 12) { + dr_ble_return_1("rwh:", 0xFFFF); /* 데이터 부족 에러 */ + return 1; + } + + /* ASCII 추출 → 전역 변수 + 설정 구조체에 복사 → FDS 저장 */ + dr_get_ascii(cmd, 0, buf, 12); + memcpy(HW_NO, buf, 12); + memcpy(m_config.hw_no, buf, 12); + config_save(); /* FDS(Flash)에 영구 저장 */ + + if (g_plat.log && g_log_enable) { + g_plat.log("[Cmd_mwh] HW=%.12s saved to FDS\r\n", m_config.hw_no); + } + + ascii_format_data(ble_bin_buffer, "rwh:", buf, 12); + dr_binary_tx_safe(ble_bin_buffer, 8); + return 1; +} + +/* mws? - 시리얼 넘버 FDS에 쓰기 + * + * 데이터: 12바이트 ASCII 시리얼 넘버 + * 응답: "rws:" + 저장된 시리얼 넘버 에코 + */ +static int Cmd_mws(const ParsedCmd *cmd) +{ + char buf[13]; + + if (cmd->data_len < 12) { + dr_ble_return_1("rws:", 0xFFFF); /* 데이터 부족 에러 */ + return 1; + } + + dr_get_ascii(cmd, 0, buf, 12); + memcpy(SERIAL_NO, buf, 12); + memcpy(m_config.serial_no, buf, 12); + config_save(); + + if (g_plat.log && g_log_enable) { + g_plat.log("[Cmd_mws] S/N=%.12s saved to FDS\r\n", m_config.serial_no); + } + + ascii_format_data(ble_bin_buffer, "rws:", buf, 12); + dr_binary_tx_safe(ble_bin_buffer, 8); + return 1; +} + +/* mrh? - HW 넘버 FDS에서 읽기 + * FDS의 설정 구조체에서 HW 넘버를 읽어 BLE로 응답 */ +static int Cmd_mrh(const ParsedCmd *cmd) +{ + (void)cmd; + memcpy(HW_NO, m_config.hw_no, 12); /* FDS 설정 → 전역 변수 동기화 */ + ascii_format_data(ble_bin_buffer, "rrh:", HW_NO, 12); + dr_binary_tx_safe(ble_bin_buffer, 8); /* 4(TAG) + 12(HW넘버) = 16바이트 = 8워드 */ + return 1; +} + +/* mrs? - 시리얼 넘버 FDS에서 읽기 */ +static int Cmd_mrs(const ParsedCmd *cmd) +{ + (void)cmd; + memcpy(SERIAL_NO, m_config.serial_no, 12); /* FDS 설정 → 전역 변수 동기화 */ + ascii_format_data(ble_bin_buffer, "rrs:", SERIAL_NO, 12); + dr_binary_tx_safe(ble_bin_buffer, 8); + return 1; +} + + +/*============================================================================== + * 패스키 / 수명 카운터 (FDS) + * + * 패스키: BLE 페어링 시 사용하는 6자리 숫자 (예: "123456") + * 수명 카운터: 디바이스 사용 횟수 추적 (uint32, 상위16+하위16으로 분할 전송) + *============================================================================*/ + +/* mpz? - BLE 패스키 FDS에 쓰기 + * 패킷: mpz? + ASCII 6자리 패스키 (예: "654321") + * 응답: rpz: + 저장된 패스키 에코 (6바이트) + */ +static int Cmd_mpz(const ParsedCmd *cmd) +{ + char passkey[7] = {0}; + dr_get_ascii(cmd, 0, passkey, 6); + + memcpy(m_static_passkey, passkey, 6); + memcpy(m_config.static_passkey, m_static_passkey, 6); + config_save(); + + if (g_plat.log) g_plat.log("[mpz] Passkey saved: %.6s\r\n", m_static_passkey); + + ascii_format_data(ble_bin_buffer, "rpz:", passkey, 6); + dr_binary_tx_safe(ble_bin_buffer, 5); + return 1; +} + +/* mqz? - BLE 패스키 FDS에서 읽기 + * 패킷: mqz? (파라미터 없음) + * 응답: rqz: + 현재 저장된 패스키 (ASCII 6바이트) + */ +static int Cmd_mqz(const ParsedCmd *cmd) +{ + (void)cmd; + memcpy(m_static_passkey, m_config.static_passkey, 6); + + ascii_format_data(ble_bin_buffer, "rqz:", m_static_passkey, 6); + dr_binary_tx_safe(ble_bin_buffer, 5); + return 1; +} + +/* 2026-03-17: mxz?, myz? 삭제 */ + +/*============================================================================== + * IMU: 6축 원시 데이터 (단발 읽기) + *============================================================================*/ + +/* msp? - IMU 가속도(xyz) + 자이로(xyz) 원시 데이터 단발 읽기 + * + * I2C로 IMU 레지스터를 직접 읽어 BLE로 전송 + * 타이머/인터럽트 없이 즉시 실행 (동기 방식) + * + * 응답: rsp: + 6 x uint16_t + * [accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z] + */ +static int Cmd_msp(const ParsedCmd *cmd) +{ + (void)cmd; + + if (g_plat.log) g_plat.log("[MSP] enter\r\n"); + + hw_i2c_init_once(); /* I2C 버스 초기화 (최초 1회) */ + + /* IMU 레지스터 직접 읽기 - 타이머, DRDY 인터럽트, 콜백 없이 동기 실행 */ + int rc = imu_read_direct(); + + if (g_plat.log) g_plat.log("[MSP] rc=%d\r\n", rc); + return 1; +} diff --git a/project/ble_peripheral/ble_app_bladder_patch/battery_saadc.c b/project/ble_peripheral/ble_app_bladder_patch/battery_saadc.c index e74e68d..247ad3d 100644 --- a/project/ble_peripheral/ble_app_bladder_patch/battery_saadc.c +++ b/project/ble_peripheral/ble_app_bladder_patch/battery_saadc.c @@ -3,11 +3,31 @@ * @author CandyPops Co. * @version V1.0.0 * @date 2022-09-05 - * @brief + * @brief + ******************************************************************************/ + +/******************************************************************************* + * [모듈 개요] 배터리 전압 및 압력센서 ADC 측정 모듈 + * + * nRF52840의 SAADC(Successive Approximation ADC)를 사용하여 다음을 수행한다: + * 1) 배터리 전압 측정 (AIN2 채널, 1/3 프리스케일링) + * - 5초 주기 타이머(battery_loop)로 반복 측정 + * - 저전압(3100mV 이하) 10회 연속 감지 시 자동 전원 OFF + * - info4 모드(전체 센서 수집)에서는 info_batt에 저장 후 온도 측정으로 전환 + * 2) 압력센서 2채널 측정 (AIN7=P1, AIN4=P2) + * - 12bit ADC 값을 mV로 변환하여 BLE 또는 UART로 전송 + * + * 배터리 전압 변환 공식: + * 전압(mV) = ADC값 x (600mV / 1023) x 6 x 1.42 (분압 저항 보정 계수) + * + * 압력센서 전압 변환 공식: + * 전압(mV) = ADC값 x 0.805(uV/LSB) / 1000, 범위 0~3500mV 클램핑 + * + * info4 모드 순서: 배터리 -> 온도(go_temp) -> IMU(motion_raw_data_enabled) ******************************************************************************/ #include "sdk_common.h" - + #include #include #include "nrf.h" @@ -22,15 +42,18 @@ //#include "fstorage.h" #include "battery_saadc.h" #include "main_timer.h" -#include +#include "main.h" /* 2026-03-17: cmd_parse.h 삭제 → main.h */ #include "debug_print.h" +/* SAADC 내부 기준전압 600mV */ #define BATTERY_REF_VOLTAGE_IN_MILLIVOLTS 600 /**< Reference voltage (in milli volts) used by ADC while doing conversion. */ +/* 1/3 프리스케일링 보상 계수 (입력 전압을 1/3로 분압하므로 x3, 추가 x2 = 총 x6) */ #define BATTERY_PRE_SCALING_COMPENSATION 6 /**< The ADC is configured to use VDD with 1/3 prescaling as input. And hence the result of conversion is to be multiplied by 3 to get the actual value of the battery voltage.*/ +/* 10비트 ADC 최대 디지털 값 */ #define BATTERY_ADC_RES_10BITS 1023 /**< Maximum digital value for 10-bit ADC conversion. */ //#define PRESSURE_RESULT_IN_MILLI_VOLTS(adc) ((adc * 3600) / 1023) -#define PRESSURE_OFFSET_DEFAULT 0 // ?? offset. ?? ? ??? ?? ??. -#define MV_PER_ADC_STEP 805 // ? 0.805mV per 1 LSB (nRF 12bit + scaling) +#define PRESSURE_OFFSET_DEFAULT 0 // 압력 offset. 캘리브레이션 시 사용 가능 +#define MV_PER_ADC_STEP 805 // 약 0.805mV per 1 LSB (nRF 12bit + scaling) /**@brief Macro to convert the result of ADC conversion in millivolts. * * @param[in] ADC_VALUE ADC result. @@ -40,29 +63,41 @@ #define BATTERY_RESULT_IN_MILLI_VOLTS(ADC_VALUE)\ ((((ADC_VALUE) * BATTERY_REF_VOLTAGE_IN_MILLIVOLTS) / BATTERY_ADC_RES_10BITS) * BATTERY_PRE_SCALING_COMPENSATION) +/* 배터리 측정용 더블 버퍼 (SAADC가 비동기로 교대 사용) */ static nrf_saadc_value_t adc_bufs[2]; +/* 압력센서 2채널 ADC 버퍼 [0]=AIN7(P1), [1]=AIN4(P2) */ static int16_t pressure_adc_buf[2]; //cj add 25/11/19 static uint16_t convert_adc_to_mV(int16_t raw_adc); //cj add 25/11/19 +/* 배터리 모니터링 반복 타이머 정의 */ APP_TIMER_DEF(m_battery_loop_timer_id); +/* 배터리 측정 주기: 5초 (밀리초 단위) */ #define BATTERY_LOOP_INTERVAL 5000 +/* 저전압 체크 플래그 — battery_loop에서 true로 설정, 핸들러에서 소비 */ bool low_battery_check = false; +/* info4: 전체 센서 데이터 수집 모드 플래그 (cmd_parse에서 설정) */ extern bool info4; //cmd_parse // cj add edit 25/11/24 +/* info4 모드에서 압력센서 측정값을 임시 저장하는 변수 (mV 단위) */ volatile uint16_t info_p1; volatile uint16_t info_p2; extern char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN]; +/* true가 되면 main_timer에서 전원 OFF 시퀀스 실행 */ extern bool go_device_power_off; +/* 다른 작업(IMU 등) 처리 중이면 true — 배터리 측정 스킵용 */ extern volatile bool processing; +/* 현재 명령 소스: CMD_UART 또는 CMD_BLE */ extern which_cmd_t cmd_type_t; extern uint8_t ble_bin_buffer[BLE_NUS_MAX_DATA_LEN] ; +/* info4 모드에서 배터리 전압을 임시 저장 (mV 단위) */ volatile uint16_t info_batt; //48_c +/* info4 순차 측정 제어 플래그: go_batt→go_temp→motion */ extern bool go_temp; // extern bool go_batt; //cmd_parse @@ -74,15 +109,27 @@ extern uint8_t ble_bin_buffer[BLE_NUS_MAX_DATA_LEN] ; * @details This function will fetch the conversion result from the ADC, convert the value into * percentage and send it to peer. */ + +/** + * @brief 압력센서 ADC 원시값을 밀리볼트(mV)로 변환 + * + * 변환 공식: mV = raw_adc x 805(uV/LSB) / 1000 + * 결과를 0~3500mV 범위로 클램핑하여 이상치 방지 + * + * @param raw_adc SAADC에서 읽은 원시 ADC 값 (12bit) + * @return 변환된 전압값 (mV), 0~3500 범위 + */ static uint16_t convert_adc_to_mV(int16_t raw_adc) { + /* 음수 ADC 값은 0으로 처리 (노이즈 등으로 발생 가능) */ if (raw_adc < 0) raw_adc = 0; - int32_t mv = (int32_t)raw_adc * MV_PER_ADC_STEP; // ?: 805 uV + /* 805 uV/LSB 스케일링: raw x 805 = uV, /1000 = mV */ + int32_t mv = (int32_t)raw_adc * MV_PER_ADC_STEP; // 단위: 805 uV mv /= 1000; - // ===== (3) 0~3500mV ??? ???? ?? ?? ===== + /* 0~3500mV 범위로 클램핑하여 유효 범위 보장 */ if (mv < 0) mv = 0; @@ -92,49 +139,61 @@ extern uint8_t ble_bin_buffer[BLE_NUS_MAX_DATA_LEN] ; return (uint16_t)mv; } + +/** + * @brief 압력센서 2채널 ADC 완료 콜백 + * + * SAADC 변환 완료 시 호출된다. + * AIN7(P1)과 AIN4(P2) 두 채널의 ADC 값을 mV로 변환한 뒤, + * info4 모드이면 글로벌 변수에 저장하고, 아니면 BLE/UART로 즉시 전송한다. + * 변환 후 SAADC를 해제하여 다른 ADC 측정(배터리, 온도)과 공유한다. + */ void pressure_all_event_handler(nrf_drv_saadc_evt_t const * p_event) { if (p_event->type == NRF_DRV_SAADC_EVT_DONE) { - int16_t p1_adc = p_event->data.done.p_buffer[0]; // AIN7 - int16_t p2_adc = p_event->data.done.p_buffer[1]; // AIN4 + /* 버퍼에서 2채널 ADC 원시값 추출 */ + int16_t p1_adc = p_event->data.done.p_buffer[0]; // AIN7 (압력센서1) + int16_t p2_adc = p_event->data.done.p_buffer[1]; // AIN4 (압력센서2) - + + /* ADC 원시값 → 밀리볼트 변환 */ uint16_t p1_mV = convert_adc_to_mV(p1_adc); uint16_t p2_mV = convert_adc_to_mV(p2_adc); - // PD Full mode(info4=true)When info_p1/info_p2 to Update + /* info4(전체 센서 수집) 모드일 때 글로벌 변수에 저장 */ if(info4 == true) { info_p1 = p1_mV; info_p2 = p2_mV; } - // Re-buffer + /* 다음 변환을 위해 버퍼 재등록 */ APP_ERROR_CHECK(nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, 2)); - // uninit + /* SAADC 해제 — 배터리/온도 측정과 하드웨어를 공유하므로 사용 후 반드시 해제 */ nrf_drv_saadc_uninit(); nrf_drv_saadc_channel_uninit(0); nrf_drv_saadc_channel_uninit(1); - // UART send - + /* 결과 전송: 명령 소스에 따라 UART 또는 BLE로 전송 */ if(cmd_type_t == CMD_UART) { DBG_PRINTF("P1:%d P2:%d\r\n", p1_mV, p2_mV); } + /* BLE 모드이고 info4가 아닌 경우(단독 압력 측정) → BLE 바이너리 전송 */ else if(cmd_type_t == CMD_BLE && info4 == false) { - DBG_PRINTF("P1:%d P2:%d\r\n", p1_mV, p2_mV); + DBG_PRINTF("P1:%d P2:%d\r\n", p1_mV, p2_mV); // uint16_t len = sprintf((char*)ble_bin_buffer, // "rpn:%04x,%04x", p1_mV, p2_mV); + /* 2채널 압력값을 "rpn:" 헤더와 함께 바이너리 포맷으로 BLE 전송 */ uint16_t result_data[2]; result_data[0] = p1_mV; result_data[1] = p2_mV; format_data(ble_bin_buffer, "rpn:", result_data,2); dr_binary_tx_safe(ble_bin_buffer,4); - - + + } } } @@ -142,34 +201,54 @@ void pressure_all_event_handler(nrf_drv_saadc_evt_t const * p_event) - +/** + * @brief 배터리 전압 ADC 완료 콜백 + * + * SAADC 변환 완료 시 호출된다. + * ADC 값을 실제 배터리 전압(mV)으로 변환하고, 동작 모드에 따라: + * - 저전압 체크 모드: 3100mV 이하 10회 연속이면 자동 전원 OFF + * - info4 모드: info_batt에 저장 후 온도 측정(go_temp)으로 전환 + * - 일반 모드: BLE 또는 UART로 즉시 전송 + * + * 전압 변환: ADC값 x (600/1023) x 6 = 기본 전압, x 1.42 = 분압 보정 후 실제 전압 + */ void battery_event_handler( nrf_drv_saadc_evt_t const * p_event ) { - + + /* 저전압 연속 감지 카운터 (static으로 호출 간 유지) */ static uint8_t low_battery_cnt = 0; - + if (p_event->type == NRF_DRV_SAADC_EVT_DONE) { nrf_saadc_value_t register_val = 0; - uint16_t batt_lvl_in_milli_volt_0 = 0; - uint16_t batt_lvl_in_milli_volt_1 = 0; + uint16_t batt_lvl_in_milli_volt_0 = 0; /* 보정 전 전압 */ + uint16_t batt_lvl_in_milli_volt_1 = 0; /* 분압 보정 후 최종 전압 */ uint32_t err_code = 0; - + + /* ADC 변환 결과 읽기 */ register_val = p_event->data.done.p_buffer[0]; + /* 다음 변환을 위해 버퍼 재등록 */ err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, 1); APP_ERROR_CHECK(err_code); + /* SAADC 해제 — 다른 ADC 측정(온도, 압력)과 하드웨어 공유 */ nrf_drv_saadc_uninit(); nrf_drv_saadc_channel_uninit(0); + /* ADC값 → mV 변환 (매크로: ADC x 600/1023 x 6) */ batt_lvl_in_milli_volt_0 = BATTERY_RESULT_IN_MILLI_VOLTS(register_val); + /* 분압 저항 보정 계수 1.42 적용 → 실제 배터리 전압 */ batt_lvl_in_milli_volt_1 = (batt_lvl_in_milli_volt_0) *1.42; + + /* === 저전압 체크 모드 (battery_loop 타이머에서 설정) === */ if(low_battery_check == true) { low_battery_check = false; + /* 배터리 전압이 LOW_BATTERY_VOLTAGE(3100mV) 이하인지 확인 */ if(batt_lvl_in_milli_volt_1 <= LOW_BATTERY_VOLTAGE) { + /* 10회 연속 저전압 감지 시 전원 OFF 시퀀스 시작 */ if(low_battery_cnt >= 10) { low_battery_cnt = 0; /*go to power off and fds save */ @@ -177,25 +256,28 @@ void battery_event_handler( nrf_drv_saadc_evt_t const * p_event ) go_device_power_off = true; main_timer_start(); }else{ + /* 아직 10회 미만 — 카운터 증가 후 경고 출력 */ low_battery_cnt++; DBG_PRINTF("WARNING!!! low_battery cnt = %d, Batt = %d(mV)\r\n", low_battery_cnt, batt_lvl_in_milli_volt_1); } } - } + } + /* === info4 모드: 전체 센서 수집 중 배터리 값 저장 === */ else if (info4 == true){ - + info_batt = batt_lvl_in_milli_volt_1; DBG_PRINTF("INFOTn%d\r\n\r\n", batt_lvl_in_milli_volt_1); - + } - - + + + /* === 일반 모드: 단독 배터리 측정 요청에 대한 응답 전송 === */ else { if(cmd_type_t == CMD_UART) { DBG_PRINTF("Tn%d\r\n\r\n", batt_lvl_in_milli_volt_1); } else if(cmd_type_t == CMD_BLE) { - + /* "rsn:" 헤더와 함께 배터리 전압을 바이너리로 BLE 전송 */ single_format_data(ble_bin_buffer, "rsn:", batt_lvl_in_milli_volt_1); dr_binary_tx_safe(ble_bin_buffer,3); @@ -203,45 +285,65 @@ void battery_event_handler( nrf_drv_saadc_evt_t const * p_event ) } } } - + + /* info4 모드: 배터리 측정 완료 → 다음 단계(온도 측정)로 전환 */ if (info4 == true){ - go_batt =false; - go_temp = true; - main_timer_start(); + go_batt =false; /* 배터리 측정 완료 표시 */ + go_temp = true; /* 온도 측정 시작 플래그 설정 */ + main_timer_start(); /* 메인 타이머 시작 → 온도 측정 트리거 */ } } -/**@brief Function for configuring ADC to do battery level conversion. +/** + * @brief SAADC를 배터리 전압 측정용으로 설정 + * + * AIN2 채널을 싱글엔드(SE) 모드, 1/3 프리스케일링으로 초기화한다. + * 더블 버퍼(adc_bufs[0], [1])를 등록하여 연속 측정이 가능하도록 한다. + * 콜백: battery_event_handler */ static void battery_configure(void) { + /* SAADC 드라이버 초기화 (기본 설정 + 배터리 이벤트 핸들러 등록) */ ret_code_t err_code = nrf_drv_saadc_init(NULL, battery_event_handler); APP_ERROR_CHECK(err_code); + /* AIN2 채널 설정: 싱글엔드 입력, 1/3 프리스케일링 (기본값) */ nrf_saadc_channel_config_t config = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN2); err_code = nrf_drv_saadc_channel_init(0, &config); APP_ERROR_CHECK(err_code); + /* 더블 버퍼 등록 — SAADC가 교대로 사용하여 데이터 손실 방지 */ err_code = nrf_drv_saadc_buffer_convert(&adc_bufs[0], 1); APP_ERROR_CHECK(err_code); err_code = nrf_drv_saadc_buffer_convert(&adc_bufs[1], 1); APP_ERROR_CHECK(err_code); } + +/** + * @brief SAADC를 압력센서 2채널 측정용으로 설정 + * + * AIN7(압력센서1)과 AIN4(압력센서2)를 싱글엔드 모드로 초기화한다. + * 이미 초기화된 상태(NRF_ERROR_INVALID_STATE)는 무시하여 안전하게 처리한다. + * 콜백: pressure_all_event_handler + */ void pressure_all_configure(void) { ret_code_t err_code; + /* SAADC 드라이버 초기화 (이미 초기화된 경우 무시) */ err_code = nrf_drv_saadc_init(NULL, pressure_all_event_handler); if (err_code != NRF_SUCCESS && err_code != NRF_ERROR_INVALID_STATE) { DBG_PRINTF("SAADC init err=%d\r\n", err_code); return; } + /* 채널 0: AIN7 (압력센서1, P1) */ nrf_saadc_channel_config_t ch0_cfg = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN7); + /* 채널 1: AIN4 (압력센서2, P2) */ nrf_saadc_channel_config_t ch1_cfg = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN4); @@ -257,6 +359,7 @@ void pressure_all_configure(void) return; } + /* 2채널 ADC 버퍼 등록 ([0]=P1, [1]=P2) */ err_code = nrf_drv_saadc_buffer_convert(pressure_adc_buf, 2); if (err_code != NRF_SUCCESS) { DBG_PRINTF("SAADC buf conv err=%d\r\n", err_code); @@ -264,53 +367,75 @@ void pressure_all_configure(void) } } +/** + * @brief 배터리 전압 1회 측정 시작 + * + * SAADC를 배터리용으로 설정 후 샘플링을 트리거한다. + * 결과는 battery_event_handler 콜백에서 비동기로 처리된다. + */ void battery_level_meas(void) { ret_code_t err_code; - - battery_configure(); - err_code = nrf_drv_saadc_sample(); + + battery_configure(); /* SAADC 배터리용 초기화 */ + err_code = nrf_drv_saadc_sample(); /* ADC 샘플링 트리거 (비동기) */ APP_ERROR_CHECK(err_code); - + } + +/** + * @brief 압력센서 2채널 1회 측정 시작 + * + * SAADC를 압력센서 2채널용으로 설정 후 샘플링을 트리거한다. + * 결과는 pressure_all_event_handler 콜백에서 비동기로 처리된다. + */ void pressure_all_level_meas(void) //add cj add 25/11/19 { ret_code_t err_code; - pressure_all_configure(); // 2 + pressure_all_configure(); /* SAADC 압력센서 2채널 초기화 */ - err_code = nrf_drv_saadc_sample(); + err_code = nrf_drv_saadc_sample(); /* ADC 샘플링 트리거 (비동기) */ APP_ERROR_CHECK(err_code); } +/** + * @brief 배터리 모니터링 타이머 콜백 (5초 주기) + * + * 다른 작업(IMU 등) 처리 중이면 측정을 건너뛴다. + * 그렇지 않으면 저전압 체크 플래그를 설정하고 배터리 측정을 시작한다. + */ void battery_loop(void * p_context) /* For 1sec */ { UNUSED_PARAMETER(p_context); + /* 다른 센서 처리 중이면 배터리 측정 스킵 (충돌 방지) */ if(processing==true) - { + { processing = false ; // add 20241218 //low_battery_check = true; return;} else{ - low_battery_check = true; - battery_level_meas(); + low_battery_check = true; /* 저전압 감지 모드로 측정 */ + battery_level_meas(); /* 배터리 ADC 1회 측정 시작 */ } } +/** @brief 배터리 모니터링 타이머 시작 (5초 반복) */ void battery_timer_start(void) { - APP_ERROR_CHECK(app_timer_start(m_battery_loop_timer_id, APP_TIMER_TICKS(BATTERY_LOOP_INTERVAL), NULL)); + APP_ERROR_CHECK(app_timer_start(m_battery_loop_timer_id, APP_TIMER_TICKS(BATTERY_LOOP_INTERVAL), NULL)); } +/** @brief 배터리 모니터링 타이머 정지 */ void battery_timer_stop(void) { - APP_ERROR_CHECK(app_timer_stop(m_battery_loop_timer_id)); + APP_ERROR_CHECK(app_timer_stop(m_battery_loop_timer_id)); } +/** @brief 배터리 모니터링 타이머 초기화 (반복 모드, 콜백: battery_loop) */ void battery_timer_init(void) { - APP_ERROR_CHECK(app_timer_create(&m_battery_loop_timer_id, APP_TIMER_MODE_REPEATED, battery_loop)); + APP_ERROR_CHECK(app_timer_create(&m_battery_loop_timer_id, APP_TIMER_MODE_REPEATED, battery_loop)); } - diff --git a/project/ble_peripheral/ble_app_bladder_patch/cmd_parse.c b/project/ble_peripheral/ble_app_bladder_patch/cmd_parse.c deleted file mode 100644 index ded1e95..0000000 --- a/project/ble_peripheral/ble_app_bladder_patch/cmd_parse.c +++ /dev/null @@ -1,1011 +0,0 @@ -/*********************************************** -EEPROM ADDRESS -HW_NO[12] -SERIAL_NO[12] - - -v1.17 p1,p2 parssurer - - -***********************************************/ - - -#include -#include "debug_print.h" -#include "fstorage.h" - -#define DEVICE_VERSION "FW25LIT2B102" -#define DEVICE_NAME "MEDIDEV_2004" -#define err_code1 65535 //length -#define err_code2 65534 //activate -#define err_code3 65533 //param -#define err_code4 65532 //? missing -#define err_code5 65531 //CMD wrong -#define err_code6 65530 //CRC wrong -#define SERIAL_NO_LENGTH 12 - -#include "i2c_manager.h" -#include "app_timer.h" - -#include "parser.h" - - - -// 0x0060 bonding address..... -extern uint8_t m_reset_status; - -char SERIAL_NO[12]; //for eeprom r/write 30 -bool bond_data_delete; //for eeprom r/write 50 -char m_static_passkey[6] = "123456";//uint8_t static_passkey[6]; eeprom r/write 20 - -uint32_t m_life_cycle; //for eeprom r/write 90 - -uint8_t resetCount = 0; //cj add - -char HW_NO[12]; //for eeprom r/write 10 -bool info4; - -extern bool motion_raw_data_enabled; - -extern char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN]; - - - -extern bool device_status; /* true : Device has been activated, false : Device entered sleep mode */ -extern bool device_reset; -//static uint32_t processing_start_time=0; -static uint32_t processing_start_tick = 0; -//ble_status_t ble_connection_st = BLE_DISCONNECTED_ST; - -extern volatile bool ble_connection_st; -extern volatile bool data_tx_in_progress; -uint8_t ble_bin_buffer[BLE_NUS_MAX_DATA_LEN] = {0}; -extern bool go_device_power_off; -extern bool go_sleep_mode_enter; -extern bool go_NVIC_SystemReset; - -extern bool go_temp; -extern bool go_batt; -extern bool ble_got_new_data; -extern bool motion_data_once; - - - - - -extern dr_platform_if_t g_plat; -extern bool g_log_enable; -extern int dr_cmd_parser(const uint8_t *buf, uint8_t len); - - - -uint16_t imsi_value; -ParsedCmd scmd; - - -/**@brief Function for handling app_uart events. - * - * @details This function will receive a single character from the app_uart module and append it to - * a string. The string will be be sent over BLE when the last character received was a - * 'new line' '\n' (hex 0x0A) or if the string has reached the maximum data length. - */ -/**@snippet [Handling the data received over UART & BLE] */ - -bool is_valid_serial_no(const char *serial) -{ - // Check for all same characters (e.g., all 'F' or all '0') - bool all_same = true; - for (int i = 1; i < SERIAL_NO_LENGTH; i++) { - if (serial[i] != serial[0]) { - all_same = false; - break; - } - } - - if (all_same) { - return false; // reject "FFFFFFFF..." or "00000000..." - } - - // Check all characters are alphanumeric - for (int i = 0; i < SERIAL_NO_LENGTH; i++) { - char c = serial[i]; - if (c == '\0' || c == (char)0xFF || !isalnum((unsigned char)c)) { - return false; - } - } - - return true; -} - - -bool is_valid_passkey(const char *passkey) -{ - - for(uint8_t i = 0 ; i<6 ;i++) - { - DBG_PRINTF("check passkey :%2X \r\n",passkey[i]); - } - // Check for NULL pointer - if (passkey == NULL) { - return false; - } - - // Check that the length is exactly 6 and all characters are digits - for (int i = 0; i < 6; i++) { - if (passkey[i] == '\0' || !isdigit((unsigned char)passkey[i])) { - return false; - } - } - - // Ensure the 7th character is null terminator -// if (passkey[6] != '\0') { -// return false; -// } - - return true; -} - -bool crc16_check(uint8_t const * p_data, uint32_t data_len, uint16_t expected_crc) -{ - uint16_t computed_crc = crc16_compute(p_data, data_len, NULL); - return (computed_crc == expected_crc); -} - -bool crc16_check_packet(uint8_t const * packet, uint32_t packet_len) -{ - if (packet_len < 2) return false; - - uint32_t data_len = packet_len - 2; - uint16_t expected_crc = (packet[packet_len - 1] << 8) | packet[packet_len - 2]; // Big endian - //DBG_PRINTF("\r\n expecterrrd_data_delete :%02X \n", expected_crc); - - return crc16_check(packet, data_len, expected_crc); -} -bool parse_cmd(const uint8_t *buffer, ParsedCmd *cmd_out, uint8_t length) { - // Extract 4-character command - for (int i = 0; i < 4; i++) { - cmd_out->tag[i] = (char)buffer[i]; - } - cmd_out->tag[4] = '\0'; // Null-terminate - - // Extract 16-bit value (little-endian) - cmd_out->value0 = (uint16_t)buffer[5] | ((uint16_t)buffer[4] << 8); - cmd_out->value1 = (uint16_t)buffer[7] | ((uint16_t)buffer[6] << 8); - cmd_out->value2 = (uint16_t)buffer[9] | ((uint16_t)buffer[8] << 8); - cmd_out->value3 = (uint16_t)buffer[11] | ((uint16_t)buffer[10] << 8); - cmd_out->value4 = (uint16_t)buffer[13] | ((uint16_t)buffer[12] << 8); - - for (int i = 0; i < length; i++) { - cmd_out->values[i] = buffer[i]; - } - - - for (int i = 0; i < 12; i++) { - cmd_out->value_ascii[i] = (char)buffer[i+4]; - } - cmd_out->value_ascii[12]= '\0'; // Null-terminate - - return true; // You could return false on validation failure -} - -bool length_error(const char *cmd , uint8_t target_length, uint8_t length) -{ - if (target_length == length) - { - - - return true; - } - else - { - char resp_error[4]; - resp_error[0] = 'r'; - resp_error[1] = cmd[1]; // 2nd letter (index 1) - resp_error[2] = cmd[2]; // 3rd letter (index 2) - resp_error[3] = '!'; - - single_format_data(ble_bin_buffer, resp_error, err_code1); - dr_binary_tx_safe(ble_bin_buffer,3); - return false; - } - -} - -bool activate_error(const char *cmd , bool device_status) -{ - if (device_status == true) - { - - - return true; - } - else - { - char resp_error[4]; - resp_error[0] = 'r'; - resp_error[1] = cmd[1]; // 2nd letter (index 1) - resp_error[2] = cmd[2]; // 3rd letter (index 2) - resp_error[3] = '!'; - - single_format_data(ble_bin_buffer, resp_error, err_code2); - dr_binary_tx_safe(ble_bin_buffer,3); - return false; - } - -} - -void param_error(const char *cmd ) -{ - - char resp_error[4]; - resp_error[0] = 'r'; - resp_error[1] = cmd[1]; // 2nd letter (index 1) - resp_error[2] = cmd[2]; // 3rd letter (index 2) - resp_error[3] = '!'; - - single_format_data(ble_bin_buffer, resp_error, err_code3); - dr_binary_tx_safe(ble_bin_buffer,3); - -} - -void quest_error(const char *cmd ) -{ - char resp_error[4]; - const char pass_init[6] = "123456"; - if( (cmd[0] == '*') && (cmd[1] == '*') && (cmd[2] == '*') && (cmd[3] == '*')){ - memcpy(m_config.static_passkey, pass_init, 6); - config_save(); - - - resp_error[0] = '*'; - resp_error[1] = cmd[1]; // 2nd letter (index 1) - resp_error[2] = cmd[2]; // 3rd letter (index 2) - resp_error[3] = '*'; - single_format_data(ble_bin_buffer, resp_error, err_code4); - dr_binary_tx_safe(ble_bin_buffer,3); - } - else{ - - - resp_error[0] = 'r'; - resp_error[1] = cmd[1]; // 2nd letter (index 1) - resp_error[2] = cmd[2]; // 3rd letter (index 2) - resp_error[3] = '!'; - - single_format_data(ble_bin_buffer, resp_error, err_code4); - dr_binary_tx_safe(ble_bin_buffer,3); - } -} - -//uint32_t serial_to_passkey_hash(const char *input) -//{ -// uint32_t hash = 1026; -// while (*input) -// { -// hash = ((hash << 5) + hash) + (uint8_t)(*input++); // hash * 33 + c -// } -// return hash % 1000000; // 6-digit range -//} -//void test_eeprom_page_rw(void) -//{ -// uint8_t tx_data[EEPROM_PAGE_SIZE]; -// uint8_t rx_data[EEPROM_PAGE_SIZE]; - -// // Fill tx_data with example values -// for (int i = 0; i < EEPROM_PAGE_SIZE; i++) { -// tx_data[i] = i*10; -// } - -// uint16_t test_address = 0x0000; - -// if (eeprom_write_page(test_address, tx_data) == NRF_SUCCESS) { -// DBG_PRINTF("Write OK\r\n"); -// } - -// if (eeprom_read_page(test_address, rx_data) == NRF_SUCCESS) { -// DBG_PRINTF("Read OK\n"); -// } - -// // Verify -// for (int i = 0; i < EEPROM_PAGE_SIZE; i++) { -// if (rx_data[i] != tx_data[i]) { -// DBG_PRINTF("Mismatch at %d: wrote %02X, read %02X\n", i, tx_data[i], rx_data[i]); -// } -// } -//} -/* eeprom_init_values_read() 삭제됨 — FDS config_load()가 동일 역할 수행 - jhChun 26.03.16 */ - - -static void log_printf(const char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - vprintf(fmt, args); - va_end(args); -} - - -void received_command_process (uint8_t const *data_array, which_cmd_t cmd_t, uint8_t length ) -{ - uint8_t r_data[BLE_NUS_MAX_DATA_LEN]={0,}; - uint16_t result_data[BLE_NUS_MAX_DATA_LEN]; - int parser_result; // ? ?? - - ble_got_new_data = true; - - memset( ble_tx_buffer, 0, BLE_NUS_MAX_DATA_LEN); - - for( uint16_t i = 0; i < length ; i++ ) { - r_data[i] = data_array[i]; - } - - DBG_PRINTF("data : %s\r\n",r_data); - DBG_PRINTF("Length : %d\r\n",length); - - // ======================================== - // Initialize parser (once) - // ======================================== - static bool parser_initialized = false; - - if (!parser_initialized) { - // Setup platform interface - g_plat.log = log_printf; // printf - g_plat.tx_bin = dr_binary_tx_safe; - g_plat.crc_check = true; // CRC - g_log_enable = true; - - parser_initialized = true; - DBG_PRINTF(">>> NEW parser initialized\r\n"); - } - - // ======================================== - // NEW PARSER: Try first - // ======================================== - parser_result = dr_cmd_parser(r_data, length); - - if (parser_result > 0) { - // Success - handled by new parser - DBG_PRINTF(">>> Handled by NEW parser\r\n"); - return; - } - // - - parse_cmd(r_data, &scmd,length); - - - - - DBG_PRINTF("parsed.tag ? %s,%d,%d,%d\r\n",scmd.tag,scmd.value0,scmd.value1,scmd.value2); - - - if(scmd.tag[3] != '?' ) - { - - quest_error(scmd.tag ); - return; - } - else if(!crc16_check_packet(data_array , length)){ - - char resp_error[4]; - resp_error[0] = 'c'; - resp_error[1] = 'r'; // 2nd letter (index 1) - resp_error[2] = 'c'; // 3rd letter (index 2) - resp_error[3] = '!'; - - single_format_data(ble_bin_buffer, resp_error, err_code6); - dr_binary_tx_safe(ble_bin_buffer,3); - return ; - } - else if(processing == true) - { - // DBG_PRINTF("mmm....\r\n"); - - uint32_t now = app_timer_cnt_get(); - - if (processing_start_tick == 0) - { - processing_start_tick = now; - } - - // - if (app_timer_cnt_diff_compute(now, processing_start_tick) > APP_TIMER_TICKS(5000)) //add cj - { - processing = false; - processing_start_tick = 0; - DBG_PRINTF("processing timeout -> force reset to false\r\n"); - } - else - { - DBG_PRINTF("mmm....\r\n"); - } - - return; - - } - else - { -#if 0 //spas -// if((data_array[0] == 'p')&&(data_array[1] == 'a')&&(data_array[2] == 's')&&(data_array[3] == 's')&&(data_array[4] == 'k')&&(data_array[5] == 'e')&&(data_array[6] == 'y')) { // Write, Passkey -// -// if(data_array[7] == '='){ -// - -// -// -// if ((data_array[8] >=0x30 && data_array[8] <=0x39) //only number is available -// &&(data_array[9] >=0x30 && data_array[9] <=0x39) -// &&(data_array[10] >=0x30 && data_array[10] <=0x39) -// &&(data_array[11] >=0x30 && data_array[11] <=0x39) -// &&(data_array[12] >=0x30 && data_array[12] <=0x39) -// &&(data_array[13] >=0x30 && data_array[13] <=0x39)) -// -// { -// memcpy(m_config.static_passkey, data_array+8, 6); -// -// DBG_PRINTF("m_config.static_passkey = %s\r\n", m_config.static_passkey); -// -// if(cmd_t == CMD_UART) { -// DBG_PRINTF("Passkey Ok\r\n"); -// } else if(cmd_t == CMD_BLE) { -// sprintf(ble_tx_buffer, "Passkey Ok\r\n"); -// data_tx_handler(ble_tx_buffer); -// } -// } -// else{ DBG_PRINTF("Nondigit!! \r\n"); -// -// } -// -// }else if(data_array[7] == '?'){ -// if(cmd_t == CMD_UART) { -// DBG_PRINTF("Passkey: %s\r\n", m_config.static_passkey); -// } else if(cmd_t == CMD_BLE) { -// sprintf(ble_tx_buffer, "Passkey: %s\r\n", m_config.static_passkey); -// data_tx_handler(ble_tx_buffer); -// } -// }else { -// if(cmd_t == CMD_UART) { -// DBG_PRINTF("ERR!!! Passkey failed!\r\n\r\n"); -// } else if(cmd_t == CMD_BLE) { -// sprintf(ble_tx_buffer, "ERR!!! Passkey failed!\r\n\r\n"); -// data_tx_handler(ble_tx_buffer); -// } -// } - -// } else -//if((data_array[0] == 's')&&(data_array[1] == 't')&&(data_array[2] == 'a')&&(data_array[3] == '?')) { -#endif //sta sta ` - -// sta -if((scmd.tag[0] == 's')&&(scmd.tag[1] == 't')&&(scmd.tag[2] == 'a')&&(scmd.tag[3] == '?')) { -//sta sta sta sta - resetCount=0; //reset hear cj chun - if(length_error(scmd.tag,8,length)==false) - { - return; - } - if(scmd.value0 == 1) { - - if(device_activated() == 0) { - device_status = true; - } - }else if(scmd.value0 == 0) { - if(device_status == true) { - if(device_sleep_mode() == 0) { - device_status = false; - } - } - - }else if(data_array[6] == '?') { - /* Nothing to do */ - }else { - if(cmd_t == CMD_UART) { - DBG_PRINTF("ERR!!! Status failed!\r\n\r\n"); - } else if(cmd_t == CMD_BLE) { - param_error(scmd.tag ); - } - } - - if(cmd_t == CMD_UART) { - DBG_PRINTF("Return%d\r\n\r\n", device_status); - } else if(cmd_t == CMD_BLE) { - - single_format_data(ble_bin_buffer, "rta:", scmd.value0); - dr_binary_tx_safe(ble_bin_buffer,3); - - } - } -// str -else if((scmd.tag[0] == 's')&&(scmd.tag[1] == 't')&&(scmd.tag[2] == 'r')&&(scmd.tag[3] == '?')) { - - - if(length_error(scmd.tag,6,length)==false) - { - return; - } - - - -// else { -// if(cmd_t == CMD_UART) { -// DBG_PRINTF("ERR!!! Status failed!\r\n\r\n"); -// } else if(cmd_t == CMD_BLE) { -// param_error(scmd.tag ); -// } -// } - - if(cmd_t == CMD_UART) { - DBG_PRINTF("Return%d\r\n\r\n", device_status); - } else if(cmd_t == CMD_BLE) { - - - single_format_data(ble_bin_buffer, "rtr:", (uint8_t)device_status); - dr_binary_tx_safe(ble_bin_buffer,3); - - } -} -else if(scmd.tag[0] == 's') { - -// ssn -if((scmd.tag[1] == 's') && (scmd.tag[2] == 'n')){ // Measuring the Battery level - - battery_level_meas(); - } -// spn -else if((scmd.tag[1] == 'p') && (scmd.tag[2] == 'n')){ // Measuring the pressure1 level read - pressure_all_level_meas(); // pressure1 + pressure2 - - } -// sso -else if((scmd.tag[1] == 's') && (scmd.tag[2] == 'o')){ // Measuring the Temperature of LED - if(length_error(scmd.tag,6,length)==false) - { - return; - } - else if(activate_error(scmd.tag,device_status)==false) - { - return; - } - else{ - tmp235_voltage_level_meas(); - } - } -// ssp -else if((scmd.tag[1] == 's') && (scmd.tag[2] == 'p')){ // Measuring the raw data of 6-axis motion sensor - - hw_i2c_init_once(); - - motion_raw_data_enabled = true; - - hw_i2c_init_once(); - - ble_got_new_data =false; - if(data_array[2] == 'c') - { - motion_data_once = false; - } - else - { - motion_data_once = true; - } - main_timer_start(); - -// }else if((scmd.tag[1] == 'P') && (scmd.tag[2] == 'p')){ // Measuring the raw data of 6-axis motion sensor -// motion_raw_data_enabled = true; -// ble_got_new_data =false; -// -// -// motion_data_once = false; -// -// main_timer_start(); - - } -// ssq -else if((scmd.tag[1] == 's') && (scmd.tag[2] == 'q')){ // Device power off - if(cmd_t == CMD_UART) { - DBG_PRINTF("Tq\r\n\r\n"); - } else if(cmd_t == CMD_BLE) { - - single_format_data(ble_bin_buffer, "rsq:", scmd.value0); - dr_binary_tx_safe(ble_bin_buffer,2); -// sprintf(ble_tx_buffer, "Tq\r\n"); -// data_tx_handler(ble_tx_buffer); - } - go_device_power_off = true; - main_timer_start(); - -#if FEATURE_SECURE_CONNECTION - } -// ssr -else if((scmd.tag[1] == 's') && (scmd.tag[2] == 'r')){ // Bond Info Delete - -// ret_code_t err_code; - if(cmd_t == CMD_UART) { - DBG_PRINTF("Tr\r\n\r\n"); - } else if(cmd_t == CMD_BLE) { - single_format_data(ble_bin_buffer, "rsr:", scmd.value0); - dr_binary_tx_safe(ble_bin_buffer,2); - } - - bond_data_delete = true; - m_config.bond_data_delete = (uint8_t)bond_data_delete; - m_reset_status = 2; - m_config.reset_status = m_reset_status; - config_save(); - nrf_delay_ms(5); - go_NVIC_SystemReset = true; - main_timer_start(); - -#endif - } -// sss -else if((scmd.tag[1] == 's') && (scmd.tag[2] == 's')){ //Device reset(Reboot) -// ret_code_t err_code; -// err_code = eeprom_write_byte(0x0060, 0x00); - //ret_code_t err_code; - if(cmd_t == CMD_UART) { - DBG_PRINTF("Ts\r\n\r\n"); - } else if(cmd_t == CMD_BLE) { - single_format_data(ble_bin_buffer, "rss:", scmd.value0); - dr_binary_tx_safe(ble_bin_buffer,2); - } - go_NVIC_SystemReset = true; - //m_config.reset_status = 2; - - m_reset_status = 2; - m_config.reset_status = m_reset_status; - config_save(); - nrf_delay_ms(5); - go_NVIC_SystemReset = true; - main_timer_start(); - } -// sst -else if((scmd.tag[1] == 's') && (scmd.tag[2] == 't')){ // Auto Gain Control - - if(cmd_t == CMD_UART) { - DBG_PRINTF("Ready\r\n\r\nREADY\r\n"); - } else if(cmd_t == CMD_BLE) { - single_format_data(ble_bin_buffer, "rst:", scmd.value0); - dr_binary_tx_safe(ble_bin_buffer,2); - - } - - - - } -// ssv — DISABLED: use ssv? in pc_firm/parser.c -else if(0 && (scmd.tag[1] == 's') && (scmd.tag[2] == 'v')){ // Auto Gain Control - - if(cmd_t == CMD_UART) { - DBG_PRINTF("%s\r\n",DEVICE_VERSION); - } else if(cmd_t == CMD_BLE) { - ascii_format_data(ble_bin_buffer, "rsv:", DEVICE_VERSION,12); - dr_binary_tx_safe(ble_bin_buffer,8); - //test_eeprom_page_rw(); - } - - - } -// ssz - Write Serial Number (FDS only) — DISABLED: use mws? in pc_firm/parser.c -else if(0 && (scmd.tag[1] == 's') && (scmd.tag[2] == 'z')){ - if(length_error(scmd.tag,18,length)==false) - { - return; - } - if(cmd_t == CMD_UART) { - DBG_PRINTF("Tz0\r\n\r\n"); - } else if(cmd_t == CMD_BLE) { - memcpy(SERIAL_NO, scmd.value_ascii, 12); - memcpy(m_config.serial_no, scmd.value_ascii, 12); - config_save(); - DBG_PRINTF("[ssz] S/N=%s saved to FDS\r\n", m_config.serial_no); - ascii_format_data(ble_bin_buffer, "rsz:", scmd.value_ascii,12); - dr_binary_tx_safe(ble_bin_buffer,8); - } - else{ - DBG_PRINTF("ERR!!! Serial_number 12\r\n\r\n"); - if(cmd_t == CMD_UART) { - DBG_PRINTF("Tz0FF\r\n\r\n"); - } else if(cmd_t == CMD_BLE) { - param_error(scmd.tag ); - } - } - } -// spz -else if((scmd.tag[1] == 'p') && (scmd.tag[2] == 'z')){ //Write, passkey - if(length_error(scmd.tag,12,length)==false) - { - return; - } - if(cmd_t == CMD_UART) { - DBG_PRINTF("Tz0\r\n\r\n"); - } else if(cmd_t == CMD_BLE) { - memcpy(m_static_passkey, scmd.value_ascii, 6); - memcpy(m_config.static_passkey, m_static_passkey, 6); - config_save(); - DBG_PRINTF("Passkey saved: %.6s\n", m_static_passkey); - - ascii_format_data(ble_bin_buffer, "rpz:", scmd.value_ascii,6); - dr_binary_tx_safe(ble_bin_buffer,5); - } - - - - else{ - DBG_PRINTF("ERR!!! passkey 6\r\n\r\n"); - if(cmd_t == CMD_UART) { - DBG_PRINTF("Tpz0FF\r\n\r\n"); - } else if(cmd_t == CMD_BLE) { - param_error(scmd.tag ); - } - - } - - - - } -// sqz -else if((scmd.tag[1] == 'q') && (scmd.tag[2] == 'z')){ // Read, Serial Number - - if(cmd_t == CMD_UART) { - DBG_PRINTF("Tz1,%s\r\n\r\n", SERIAL_NO); - } else if(cmd_t == CMD_BLE) { - memcpy(m_static_passkey, m_config.static_passkey, 6); - ascii_format_data(ble_bin_buffer, "rqz:", m_static_passkey,6); - dr_binary_tx_safe(ble_bin_buffer,5); - } - - } -// srz - Read Serial Number (FDS only) — DISABLED: use mrs? in pc_firm/parser.c -else if(0 && (scmd.tag[1] == 'r') && (scmd.tag[2] == 'z')){ - if(cmd_t == CMD_UART) { - DBG_PRINTF("Tz1,%s\r\n\r\n", SERIAL_NO); - } else if(cmd_t == CMD_BLE) { - memcpy(SERIAL_NO, m_config.serial_no, 12); - ascii_format_data(ble_bin_buffer, "rrz:", SERIAL_NO, 12); - dr_binary_tx_safe(ble_bin_buffer, 8); - } - -#if 0 // ===== EEPROM start block ===== -// }else if((scmd.tag[1] == 'a') && (scmd.tag[2] == 'z')){ // write bytes -// -// - -// uint8_t write_data[64]; -// -// memcpy(write_data,scmd.value_ascii,12); - -// eeprom_write_bytes(scmd.value0,write_data,12); -// if(cmd_t == CMD_UART) { -// DBG_PRINTF("Tz2,%s\r\n\r\n",write_data); -// } else if(cmd_t == CMD_BLE) { -// sprintf(ble_tx_buffer, "Tz22,%s\r\n", write_data); -// data_tx_handler(ble_tx_buffer); -// } - - - - -// }else if((scmd.tag[1] == 'b') && (scmd.tag[2] == 'z')){ -// - -// unsigned char *read_data; -// -// eeprom_read_bytes(scmd.value0,read_data,12); -// -// if(cmd_t == CMD_UART) { -// DBG_PRINTF("Tz3,%s,%s\r\n\r\n", read_data,read_data); -// } else if(cmd_t == CMD_BLE) { -// sprintf(ble_tx_buffer, "Tz3,%s\r\n",read_data); -// data_tx_handler(ble_tx_buffer); -// } - - -// }else if((scmd.tag[1] == 'c') && (scmd.tag[2] == 'z')){ // write page - -// -// //unsigned char read_data[EEPROM_PAGE_SIZE]; -// uint8_t write_data[64]; -// for(uint8_t i=0 ; i<64 ;i++){ -// write_data[i] = scmd.values[i+6]; -// } -// -// //memcpy(write_data,scmd.value_ascii,12); -// eeprom_write_page (scmd.value0,write_data); -// -// if(cmd_t == CMD_UART) { -// DBG_PRINTF("Tz3,%s\r\n\r\n", write_data); -// } else if(cmd_t == CMD_BLE) { -//// sprintf(ble_tx_buffer, "Tz3,%s\r\n",read_data -// ascii_format_data(ble_bin_buffer, "rcz:", "ok",3); -// dr_binary_tx_safe(ble_bin_buffer,5); -//// data_tx_handler(ble_tx_buffer); -// } - - -// -// -// -// -// -// }else if((scmd.tag[1] == 'd') && (scmd.tag[2] == 'z')){ // Read page -// - - -// uint8_t read_data[64]; - -// eeprom_read_page (scmd.value0,read_data); -// -// if(cmd_t == CMD_UART) { -// // DBG_PRINTF("Tz3,%s,%s\r\n\r\n", read_data,read_data); -// } else if(cmd_t == CMD_BLE) { -// for (int i = 0; i < EEPROM_PAGE_SIZE; i++) { -// -// DBG_PRINTF("%02X,", read_data[i]); -// } -// -// -// format_data_byte(ble_bin_buffer, "rdz:", read_data,64); -// dr_binary_tx_safe(ble_bin_buffer,34); -// } - - - - - //ret_code_t eeprom_write_uint16_array(uint16_t start_address, const uint16_t *data, size_t count) - #endif // ===== EEPROM block End ===== - } -// sez -else if((scmd.tag[1] == 'e') && (scmd.tag[2] == 'z')){ // Read, Serial Number - - /* AGC gain array — AD5272/MCP4725 HW 제거됨, 명령 비활성화 - jhChun 26.03.16 */ - if(cmd_t == CMD_UART) { - DBG_PRINTF("sez: HW not present\r\n"); - } else if(cmd_t == CMD_BLE) { - param_error(scmd.tag); - } - - } -// sfz -else if((scmd.tag[1] == 'f') && (scmd.tag[2] == 'z')){ // Read, Serial Number sfz arry eeprom read - if(length_error(scmd.tag,8,length)==false) - { - return; - } -// else if(activate_error(scmd.tag,device_status)==false) -// { -// return; -// } - - else{ - /* AGC gain array — AD5272/MCP4725 HW 제거됨, 명령 비활성화 - jhChun 26.03.16 */ - if(cmd_t == CMD_UART) { - DBG_PRINTF("sfz: HW not present\r\n"); - } else if(cmd_t == CMD_BLE) { - param_error(scmd.tag); - } - } - } -// siz - Read HW Number (FDS only) — DISABLED: use mrh? in pc_firm/parser.c -else if(0 && (scmd.tag[1] == 'i') && (scmd.tag[2] == 'z')){ - if(cmd_t == CMD_UART) { - DBG_PRINTF("Tz1,%s\r\n\r\n", HW_NO); - } else if(cmd_t == CMD_BLE) { - memcpy(HW_NO, m_config.hw_no, 12); - ascii_format_data(ble_bin_buffer, "riz:", HW_NO, 12); - dr_binary_tx_safe(ble_bin_buffer, 8); - } - } -// shz - Write HW Number (FDS only) — DISABLED: use mwh? in pc_firm/parser.c -else if(0 && (scmd.tag[1] == 'h') && (scmd.tag[2] == 'z')){ - if(length_error(scmd.tag,18,length)==false) - { - return; - } - if(cmd_t == CMD_UART) { - DBG_PRINTF("Tz0\r\n\r\n"); - } else if(cmd_t == CMD_BLE) { - memcpy(HW_NO, scmd.value_ascii, 12); - memcpy(m_config.hw_no, scmd.value_ascii, 12); - config_save(); - DBG_PRINTF("[shz] HW=%s saved to FDS\r\n", m_config.hw_no); - ascii_format_data(ble_bin_buffer, "rhz:", scmd.value_ascii,12); - dr_binary_tx_safe(ble_bin_buffer,8); - } - else{ - DBG_PRINTF("ERR!!! HW_NO 12\r\n\r\n"); - if(cmd_t == CMD_UART) { - DBG_PRINTF("Tpz0FF\r\n\r\n"); - } else if(cmd_t == CMD_BLE) { - param_error(scmd.tag ); - } - } - } -// sxz -else if((scmd.tag[1] == 'x') && (scmd.tag[2] == 'z')){ // Set the delay for PD stabilization. 0�s ~ FFFF(65535)�s - - m_life_cycle = ((uint16_t)scmd.value0 << 16) | ((uint16_t)scmd.value1 & 0xFFFF); - result_data[0] = scmd.value0; - result_data[1] = scmd.value1; - - if(length_error(scmd.tag,10,length)==false) - { - return; - } - - else if((m_life_cycle > 0)||(m_life_cycle <= 99999999)) { - - if(cmd_t == CMD_UART) { - DBG_PRINTF("rxz\r\n\r\n"); - } else if(cmd_t == CMD_BLE) { - m_config.life_cycle = m_life_cycle; - config_save(); - format_data(ble_bin_buffer, "rxz:", result_data,2); - dr_binary_tx_safe(ble_bin_buffer,4); - } - }else{ - if(cmd_t == CMD_UART) { - DBG_PRINTF("ERR!!! pd_delay_us failed!\r\n\r\n"); - } else if(cmd_t == CMD_BLE) { - param_error(scmd.tag ); - } - } - - - - - -} -// syz -else if((scmd.tag[1] == 'y') && (scmd.tag[2] == 'z')){ // - - - - if(length_error(scmd.tag,6,length)==false) - { - return; - } - - else { - - m_life_cycle = m_config.life_cycle; - - result_data[0] = (uint16_t)(m_life_cycle >> 16); - result_data[1] = (uint16_t)(m_life_cycle & 0xFFFF); - - - if(cmd_t == CMD_UART) { - DBG_PRINTF("ryz:\r\n\r\n"); - } else if(cmd_t == CMD_BLE) { - - format_data(ble_bin_buffer, "ryz:", result_data,4); - dr_binary_tx_safe(ble_bin_buffer,4); - } - - } - - - - //ret_code_t eeprom_write_uint32(uint16_t mem_address, uint32_t data); - }else { - if(cmd_t == CMD_UART) { - DBG_PRINTF("ERR!!! UART Command Failed, %c%c%c%c\r\n",data_array[0], data_array[1], data_array[2], data_array[3]); - } else if(cmd_t == CMD_BLE) { - single_format_data(ble_bin_buffer, "err!", err_code5); - dr_binary_tx_safe(ble_bin_buffer,3); - - - - } - } - } - } - -} - -/** - * @} - */ - -//0x0A - - diff --git a/project/ble_peripheral/ble_app_bladder_patch/cmd_parse.h b/project/ble_peripheral/ble_app_bladder_patch/cmd_parse.h deleted file mode 100644 index cb1fdf8..0000000 --- a/project/ble_peripheral/ble_app_bladder_patch/cmd_parse.h +++ /dev/null @@ -1,74 +0,0 @@ -/******************************************************************************* - * @file cmd_parse.h - * @brief - ******************************************************************************/ -#ifndef _CMD_PARSE_H_ -#define _CMD_PARSE_H_ -#include "sdk_config.h" -#include -#include -#include -#include -#include -#include - -#include "nordic_common.h" -#include "nrf.h" -#include "ble_hci.h" -#include "ble_advdata.h" -#include "ble_advertising.h" -#include "ble_conn_params.h" -#include "nrf_sdh.h" -#include "nrf_sdh_soc.h" -#include "nrf_sdh_ble.h" -#include "nrf_ble_gatt.h" -#include "nrf_ble_qwr.h" -#include "app_timer.h" -#include "ble_nus.h" -#include "app_uart.h" -#include "app_util_platform.h" -#include "bsp_btn_ble.h" -#include "nrf_pwr_mgmt.h" -#include "nrf_delay.h" -#include "math.h" -#include "crc16.h" //add 25.04.23 -#include "nrf_ble_lesc.h" -#include "nrf_crypto.h" -#include "nrf_pwr_mgmt.h" -#include - -#include "system_interface.h" -#include "main.h" -#include "app_raw_main.h" //0117 -#include "main_timer.h" -#include "power_control.h" -#include "tmp235_q1.h" -//#include "fstorage.h" -#include "battery_saadc.h" -typedef struct { - char tag[5]; // Null-terminated 4-char command - uint16_t value0; // Data value - uint16_t value1; // Data value - uint16_t value2; // Data value - uint16_t value3; // Data value - uint16_t value4; // Data value - char value_ascii[13]; - uint8_t values[24]; // data vlue - // uint16_t crc; // CRC value -} ParsedCmd; -uint32_t serial_to_passkey_hash(const char *input); -void received_command_process(uint8_t const *data_array, which_cmd_t cmd_t,uint8_t length); -bool length_error(const char *cmd , uint8_t target_length, uint8_t length); -bool parse_cmd(const uint8_t *buffer, ParsedCmd *cmd_out,uint8_t length); -bool activate_error(const char *cmd , bool device_status); -void param_error(const char *cmd ); -void quest_error(const char *cmd ); -bool is_valid_serial_no(const char *serial); - - -bool is_valid_passkey(const char *passkey); - -bool crc16_check(uint8_t const * p_data, uint32_t data_len, uint16_t expected_crc); -bool crc16_check_packet(uint8_t const * packet, uint32_t packet_len); -#endif /* */ - diff --git a/project/ble_peripheral/ble_app_bladder_patch/icm42670p/app_raw/app_raw.c b/project/ble_peripheral/ble_app_bladder_patch/icm42670p/app_raw/app_raw.c index a0b7693..45cdd44 100644 --- a/project/ble_peripheral/ble_app_bladder_patch/icm42670p/app_raw/app_raw.c +++ b/project/ble_peripheral/ble_app_bladder_patch/icm42670p/app_raw/app_raw.c @@ -3,7 +3,33 @@ * @author CandyPops Co. * @version V1.0.0 * @date 2022-09-05 - * @brief + * @brief + ******************************************************************************/ + +/******************************************************************************* + * [모듈 개요] ICM42670P IMU 드라이버 상위 레이어 + * + * ICM42670P IMU 센서의 초기화, 설정, 데이터 읽기를 담당하는 애플리케이션 + * 레이어 모듈이다. InvenSense 드라이버 API를 래핑하여 사용한다. + * + * 주요 기능: + * 1) setup_imu_device() - IMU 초기화 및 WHOAMI 확인 (0x67 = ICM42670P) + * 2) configure_imu_device() - 센서 파라미터 설정 + * - 가속도계: ±4g FSR, 100Hz(저전력) 또는 800Hz(저잡음) + * - 자이로: ±2000dps FSR, 100Hz 또는 800Hz + * - FIFO 비활성화 (레지스터 직접 읽기 모드) + * 3) get_imu_data() - FIFO 또는 레지스터에서 센서 데이터 읽기 + * 4) imu_callback() - 센서 데이터 수신 콜백 + * - 마운팅 매트릭스 적용 (보드 방향 보정) + * - info4 모드: info_imu[6]에 데이터 저장 + * - BLE 모드: "rsp:" 태그로 6축 데이터 BLE 전송 + * - UART 모드: 텍스트 형식으로 시리얼 출력 + * 5) imu_read_direct() - 드라이버 API를 우회한 직접 I2C 레지스터 읽기 + * - 센서 설정 → 전원 ON → 80ms 대기 → 12바이트 읽기 → 슬립 + * + * 마운팅 매트릭스: + * Q30 고정소수점 형식의 3x3 회전 매트릭스로, 보드에 장착된 센서의 + * 물리적 방향을 소프트웨어 좌표계에 맞춰 보정한다. ******************************************************************************/ #include "sdk_config.h" @@ -17,15 +43,15 @@ #include "app_util_platform.h" #include "main.h" -#include +/* 2026-03-17: cmd_parse.h 삭제 — main.h는 이미 포함됨 */ #include "debug_print.h" #include "nrf_delay.h" /* - * Print raw data or scaled data - * 0 : print raw accel, gyro and temp data - * 1 : print scaled accel, gyro and temp data in g, dps and degree Celsius + * 데이터 출력 형식 선택 + * 0 : 원시 데이터 (raw accel, gyro, temp) 출력 + * 1 : 스케일링된 데이터 (g, dps, 섭씨) 출력 */ #define SCALED_DATA_G_DPS 0 @@ -34,12 +60,20 @@ * Static and extern variables * -------------------------------------------------------------------------------------- */ -/* Just a handy variable to handle the IMU object */ +/* IMU 드라이버 객체 — 드라이버 API 호출 시 항상 이 구조체를 전달 */ static struct inv_imu_device icm_driver; + +/* BLE 전송용 바이너리 버퍼 */ uint8_t imu_bin_buffer[BLE_NUS_MAX_DATA_LEN]; + /* - * ICM mounting matrix - * Coefficients are coded as Q30 integer + * ICM42670P 마운팅 매트릭스 (Q30 고정소수점) + * + * 센서가 보드에 장착된 물리적 방향에 따라 좌표 변환을 수행한다. + * Q30 형식: 1.0 = (1 << 30) = 0x40000000 + * + * SM_REVB_DB (개발보드): X→-Y, Y→X 변환 (90도 회전) + * 기본 (SmartMotion): 단위 행렬 (변환 없음) */ #if (SM_BOARD_REV == SM_REVB_DB) /* when DB or EVB are used */ static int32_t icm_mounting_matrix[9] = { 0, -(1<<30), 0, @@ -50,13 +84,14 @@ static int32_t icm_mounting_matrix[9] = {(1<<30), 0, 0, 0, (1<<30), 0, 0, 0, (1<<30)}; #endif -bool custom_add_data; -extern bool motion_raw_data_enabled; -extern char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN]; -extern which_cmd_t cmd_type_t; -uint16_t ssp_data[6]={0,}; -extern bool info4; //cmd_parse - volatile uint16_t info_imu[6]; + +bool custom_add_data; /* 커스텀 데이터 추가 플래그 (BLE 전송 제어) */ +extern bool motion_raw_data_enabled; /* 외부에서 원시 데이터 읽기를 요청하는 플래그 */ +extern char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN]; /* BLE 텍스트 전송 버퍼 */ +extern which_cmd_t cmd_type_t; /* 현재 명령 소스 (BLE 또는 UART) */ +uint16_t ssp_data[6]={0,}; /* BLE 전송용 6축 데이터 배열 (accel XYZ + gyro XYZ) */ +extern bool info4; /* info4 모드 플래그 (cmd_parse에서 설정) */ + volatile uint16_t info_imu[6]; /* info4 모드에서 IMU 데이터를 저장하는 전역 배열 */ /* -------------------------------------------------------------------------------------- * static function declaration @@ -67,25 +102,37 @@ static void apply_mounting_matrix(const int32_t matrix[9], int32_t raw[3]); * Functions definition * -------------------------------------------------------------------------------------- */ +/* + * setup_imu_device() + * IMU 디바이스 초기화 및 식별 확인 + * + * 처리 흐름: + * 1) inv_imu_init()으로 드라이버 초기화 (시리얼 인터페이스 + 콜백 등록) + * 2) WHOAMI 레지스터 읽기로 디바이스 확인 + * 3) WHOAMI 값이 ICM_WHOAMI(0x67)와 일치하는지 검증 + * + * 반환값: 0=성공, 음수=에러 + */ int setup_imu_device(struct inv_imu_serif *icm_serif) { int rc = 0; uint8_t who_am_i; - /* Init device */ + /* IMU 드라이버 초기화 — 시리얼 인터페이스 연결 및 콜백 함수 등록 */ rc = inv_imu_init(&icm_driver, icm_serif, imu_callback); if (rc != INV_ERROR_SUCCESS) { printf("!!! ERROR : Failed to initialize IMU!\r\n"); return rc; } - - /* Check WHOAMI */ + + /* WHOAMI 레지스터 읽기 — 디바이스 존재 및 통신 확인 */ rc = inv_imu_get_who_am_i(&icm_driver, &who_am_i); if (rc != INV_ERROR_SUCCESS) { printf("!!! ERROR : Failed to read whoami!\r\n"); return rc; } - + + /* WHOAMI 값 검증 — ICM42670P의 경우 0x67이어야 함 */ if (who_am_i != ICM_WHOAMI) { printf("!!! ERROR : Bad WHOAMI value! Read 0x%02x, expected 0x%02x\r\n", who_am_i, ICM_WHOAMI); return INV_ERROR; @@ -94,55 +141,89 @@ int setup_imu_device(struct inv_imu_serif *icm_serif) return rc; } +/* + * configure_imu_device() + * IMU 센서 동작 파라미터를 설정한다. + * + * 설정 항목: + * - FIFO: 비활성화 (USE_FIFO=0일 때, 레지스터 직접 읽기 모드) + * - 가속도계 FSR: ±4g (USE_HIGH_RES_MODE=0일 때) + * - 자이로 FSR: ±2000dps + * - ODR(출력 데이터율): + * - 저잡음 모드(USE_LOW_NOISE_MODE=1): 800Hz + * - 저전력 모드(USE_LOW_NOISE_MODE=0): 100Hz + * - 자이로는 항상 저잡음 모드로 동작 + * - FIFO 미사용 시 자이로 스타트업 시간만큼 대기 + * + * 반환값: 0=성공, 음수=에러 + */ int configure_imu_device(void) { int rc = 0; + /* FIFO 비활성화 — 레지스터에서 직접 데이터를 읽는다 */ if (!USE_FIFO) rc |= inv_imu_configure_fifo(&icm_driver, INV_IMU_FIFO_DISABLED); if (USE_HIGH_RES_MODE) { + /* 고해상도 FIFO 모드: 20비트 데이터, FSR은 16g/2000dps로 고정됨 */ rc |= inv_imu_enable_high_resolution_fifo(&icm_driver); } else { + /* 표준 모드: 가속도계 ±4g, 자이로 ±2000dps FSR 설정 */ rc |= inv_imu_set_accel_fsr(&icm_driver, ACCEL_CONFIG0_FS_SEL_4g); rc |= inv_imu_set_gyro_fsr(&icm_driver, GYRO_CONFIG0_FS_SEL_2000dps); } - + if (USE_LOW_NOISE_MODE) { + /* 저잡음 모드: 800Hz ODR, 가속도계 저잡음 모드 활성화 */ rc |= inv_imu_set_accel_frequency(&icm_driver, ACCEL_CONFIG0_ODR_800_HZ); rc |= inv_imu_set_gyro_frequency(&icm_driver, GYRO_CONFIG0_ODR_800_HZ); rc |= inv_imu_enable_accel_low_noise_mode(&icm_driver); } else { + /* 저전력 모드: 100Hz ODR, 가속도계 저전력 모드 활성화 */ rc |= inv_imu_set_accel_frequency(&icm_driver, ACCEL_CONFIG0_ODR_100_HZ); rc |= inv_imu_set_gyro_frequency(&icm_driver, GYRO_CONFIG0_ODR_100_HZ); rc |= inv_imu_enable_accel_low_power_mode(&icm_driver); } - + + /* 자이로는 모드에 관계없이 항상 저잡음 모드로 동작 */ rc |= inv_imu_enable_gyro_low_noise_mode(&icm_driver); + /* FIFO 미사용 시 자이로 스타트업 시간만큼 대기 (첫 유효 데이터까지의 지연) */ if (!USE_FIFO) inv_imu_sleep_us(GYR_STARTUP_TIME_US); - + return rc; } +/* + * get_imu_data() + * IMU에서 센서 데이터를 읽는다. + * USE_FIFO 설정에 따라 FIFO 또는 레지스터에서 데이터를 가져온다. + * 읽은 데이터는 imu_callback()을 통해 처리된다. + */ int get_imu_data(void) { #if USE_FIFO return inv_imu_get_data_from_fifo(&icm_driver); #else - + return inv_imu_get_data_from_registers(&icm_driver); #endif } #if SCALED_DATA_G_DPS +/* + * get_accel_and_gyr_fsr() + * 현재 설정된 가속도계와 자이로의 FSR(Full Scale Range) 값을 가져온다. + * 스케일링된 데이터(g, dps) 변환에 사용된다. + */ static void get_accel_and_gyr_fsr(int16_t * accel_fsr_g, int16_t * gyro_fsr_dps) { ACCEL_CONFIG0_FS_SEL_t accel_fsr_bitfield; GYRO_CONFIG0_FS_SEL_t gyro_fsr_bitfield; - + inv_imu_get_accel_fsr(&icm_driver, &accel_fsr_bitfield); switch(accel_fsr_bitfield) { case ACCEL_CONFIG0_FS_SEL_2g: *accel_fsr_g = 2; @@ -155,7 +236,7 @@ static void get_accel_and_gyr_fsr(int16_t * accel_fsr_g, int16_t * gyro_fsr_dps) break; default: *accel_fsr_g = -1; } - + inv_imu_get_gyro_fsr(&icm_driver, &gyro_fsr_bitfield); switch(gyro_fsr_bitfield) { case GYRO_CONFIG0_FS_SEL_250dps: *gyro_fsr_dps = 250; @@ -172,10 +253,24 @@ static void get_accel_and_gyr_fsr(int16_t * accel_fsr_g, int16_t * gyro_fsr_dps) #endif +/* + * imu_callback() + * IMU 드라이버가 새 센서 데이터를 읽을 때마다 호출되는 콜백 함수. + * + * 처리 흐름: + * 1) 이벤트에서 가속도/자이로 원시 데이터 추출 + * - FIFO 모드: 타임스탬프 롤오버 처리, 고해상도(20비트) 지원 + * - 레지스터 모드: 16비트 데이터 직접 사용 + * 2) 마운팅 매트릭스 적용 (보드 장착 방향 보정) + * 3) 데이터 출력 (모드에 따라 분기): + * - info4 모드: info_imu[6] 전역 배열에 저장 (외부에서 폴링) + * - UART 모드: "Tp" 접두사로 6축 데이터 텍스트 출력 + * - BLE 모드: "rsp:" 태그로 바이너리 패킷 전송 + UART 텍스트 동시 출력 + */ void imu_callback(inv_imu_sensor_event_t *event) { int32_t accel[3], gyro[3]; - + #if SCALED_DATA_G_DPS float accel_g[3]; float gyro_dps[3]; @@ -183,40 +278,43 @@ void imu_callback(inv_imu_sensor_event_t *event) int16_t accel_fsr_g, gyro_fsr_dps; #endif -#if USE_FIFO +#if USE_FIFO static uint64_t last_fifo_timestamp = 0; static uint32_t rollover_num = 0; - - // Handle rollover + + /* FIFO 타임스탬프 롤오버 처리 (16비트 → 64비트 확장) */ if (last_fifo_timestamp > event->timestamp_fsync) rollover_num++; last_fifo_timestamp = event->timestamp_fsync; - // Compute timestamp in us + /* 타임스탬프를 마이크로초 단위로 변환 (Q24 해상도 적용) */ timestamp = event->timestamp_fsync + rollover_num * UINT16_MAX; timestamp *= inv_imu_get_fifo_timestamp_resolution_us_q24(&icm_driver); timestamp /= (1UL << 24); - + if (icm_driver.fifo_highres_enabled) { + /* 고해상도 모드: 16비트 데이터를 4비트 좌측 시프트 + 하위 4비트 추가 → 20비트 */ accel[0] = (((int32_t)event->accel[0] << 4)) | event->accel_high_res[0]; accel[1] = (((int32_t)event->accel[1] << 4)) | event->accel_high_res[1]; accel[2] = (((int32_t)event->accel[2] << 4)) | event->accel_high_res[2]; - + gyro[0] = (((int32_t)event->gyro[0] << 4)) | event->gyro_high_res[0]; gyro[1] = (((int32_t)event->gyro[1] << 4)) | event->gyro_high_res[1]; gyro[2] = (((int32_t)event->gyro[2] << 4)) | event->gyro_high_res[2]; - + } else { + /* 표준 해상도: 16비트 데이터 그대로 사용 */ accel[0] = event->accel[0]; accel[1] = event->accel[1]; accel[2] = event->accel[2]; - + gyro[0] = event->gyro[0]; gyro[1] = event->gyro[1]; gyro[2] = event->gyro[2]; } #else + /* 레지스터 직접 읽기 모드: 16비트 원시 데이터 추출 */ accel[0] = event->accel[0]; accel[1] = event->accel[1]; accel[2] = event->accel[2]; @@ -225,18 +323,20 @@ void imu_callback(inv_imu_sensor_event_t *event) gyro[1] = event->gyro[1]; gyro[2] = event->gyro[2]; - // Force sensor_mask so it gets displayed below + /* 레지스터 모드에서는 센서 마스크를 강제 설정하여 아래 출력 로직이 동작하도록 함 */ event->sensor_mask |= (1 << INV_SENSOR_TEMPERATURE); event->sensor_mask |= (1 << INV_SENSOR_ACCEL); event->sensor_mask |= (1 << INV_SENSOR_GYRO); #endif - + + /* 마운팅 매트릭스 적용 — 센서의 물리적 장착 방향을 소프트웨어 좌표계로 보정 */ apply_mounting_matrix(icm_mounting_matrix, accel); apply_mounting_matrix(icm_mounting_matrix, gyro); - + #if SCALED_DATA_G_DPS /* - * Convert raw data into scaled data in g and dps + * 원시 데이터를 물리 단위(g, dps)로 변환 + * 변환 공식: 물리값 = 원시값 * FSR / INT16_MAX */ get_accel_and_gyr_fsr(&accel_fsr_g, &gyro_fsr_dps); accel_g[0] = (float)(accel[0] * accel_fsr_g) / INT16_MAX; @@ -245,31 +345,37 @@ void imu_callback(inv_imu_sensor_event_t *event) gyro_dps[0] = (float)(gyro[0] * gyro_fsr_dps) / INT16_MAX; gyro_dps[1] = (float)(gyro[1] * gyro_fsr_dps) / INT16_MAX; gyro_dps[2] = (float)(gyro[2] * gyro_fsr_dps) / INT16_MAX; + + /* 온도 변환: 고해상도/레지스터 모드는 /128, FIFO 표준 모드는 /2 */ if (USE_HIGH_RES_MODE || !USE_FIFO) temp_degc = 25 + ((float)event->temperature / 128); - else + else temp_degc = 25 + ((float)event->temperature / 2); - + /* - * Output scaled data on UART link + * 스케일링된 데이터를 UART로 출력 */ if (event->sensor_mask & (1 << INV_SENSOR_ACCEL) && event->sensor_mask & (1 << INV_SENSOR_GYRO)) - printf("%u: %.3f, \t%.3f, \t%.3f, \t%.3f, \t%.3f, \t%.3f, \t%.3f\r\n", + printf("%u: %.3f, \t%.3f, \t%.3f, \t%.3f, \t%.3f, \t%.3f, \t%.3f\r\n", (uint32_t)timestamp, - accel_g[0], accel_g[1], accel_g[2], + accel_g[0], accel_g[1], accel_g[2], temp_degc, gyro_dps[0], gyro_dps[1], gyro_dps[2]); #else - + /* - * Output raw data on UART link + * 원시 데이터 출력 — 명령 소스(info4/UART/BLE)에 따라 분기 */ if (event->sensor_mask & (1 << INV_SENSOR_ACCEL) && event->sensor_mask & (1 << INV_SENSOR_GYRO) || motion_raw_data_enabled) { motion_raw_data_enabled = false; - + if (info4 == true) { + /* + * info4 모드: 전역 배열 info_imu[6]에 데이터를 저장한다. + * 외부 모듈에서 이 배열을 폴링하여 데이터를 사용한다. + */ info_imu[0] = (uint16_t)accel[0]; info_imu[1] = (uint16_t)accel[1]; info_imu[2] = (uint16_t) accel[2]; @@ -277,17 +383,23 @@ void imu_callback(inv_imu_sensor_event_t *event) info_imu[4] = (uint16_t) gyro[1]; info_imu[5] = (uint16_t)gyro[2]; } - + else if(cmd_type_t == CMD_UART) { + /* UART 모드: "Tp" 접두사로 6축 데이터를 텍스트 형식으로 출력 */ printf("Tp%d,%d,%d,%d,%d,%d\r\n\r\n", accel[0], accel[1], accel[2], gyro[0], gyro[1], gyro[2]); } else if(cmd_type_t == CMD_BLE) { - //sprintf(ble_tx_buffer, "Tp%d,%d,%d,%d,%d,%d\r\n\r\n", accel[0], accel[1], accel[2], gyro[0], gyro[1], gyro[2]); - ssp_data[0] = (uint16_t)accel[0]; - ssp_data[1] = (uint16_t)accel[1]; - ssp_data[2] = (uint16_t)accel[2]; - ssp_data[3] = (uint16_t)gyro[0]; - ssp_data[4] = (uint16_t)gyro[1]; - ssp_data[5] = (uint16_t)gyro[2]; + /* + * BLE 모드: 6축 데이터를 바이너리 패킷으로 BLE 전송 + * ssp_data[0~2] = 가속도 XYZ, ssp_data[3~5] = 자이로 XYZ + * format_data()로 "rsp:" 태그 + 12바이트 데이터를 패킷화 + * dr_binary_tx_safe()로 8바이트 BLE 전송 + */ + ssp_data[0] = (uint16_t)accel[0]; + ssp_data[1] = (uint16_t)accel[1]; + ssp_data[2] = (uint16_t)accel[2]; + ssp_data[3] = (uint16_t)gyro[0]; + ssp_data[4] = (uint16_t)gyro[1]; + ssp_data[5] = (uint16_t)gyro[2]; format_data(imu_bin_buffer, "rsp:", ssp_data,12); printf("Tp%d,%d,%d,%d,%d,%d\r\n\r\n", accel[0], accel[1], accel[2], gyro[0], gyro[1], gyro[2]); dr_binary_tx_safe(imu_bin_buffer,8); @@ -307,6 +419,16 @@ void imu_callback(inv_imu_sensor_event_t *event) * Static functions definition * -------------------------------------------------------------------------------------- */ +/* + * apply_mounting_matrix() + * Q30 고정소수점 회전 매트릭스를 3축 벡터에 적용한다. + * + * 계산 방식: + * result[i] = matrix[i*3+0]*raw[0] + matrix[i*3+1]*raw[1] + matrix[i*3+2]*raw[2] + * 결과를 30비트 우측 시프트하여 Q30 → 정수 변환 + * + * 이를 통해 센서의 물리적 장착 방향에 관계없이 일관된 좌표계를 사용할 수 있다. + */ static void apply_mounting_matrix(const int32_t matrix[9], int32_t raw[3]) { unsigned i; @@ -317,12 +439,32 @@ static void apply_mounting_matrix(const int32_t matrix[9], int32_t raw[3]) data_q30[i] += ((int64_t)matrix[3*i+1] * raw[1]); data_q30[i] += ((int64_t)matrix[3*i+2] * raw[2]); } + /* Q30 → 정수 변환: 30비트 우측 시프트 */ raw[0] = (int32_t)(data_q30[0]>>30); raw[1] = (int32_t)(data_q30[1]>>30); raw[2] = (int32_t)(data_q30[2]>>30); } +/* + * imu_read_direct() + * 드라이버 API를 우회하여 직접 I2C 레지스터를 읽는 함수. + * DRDY(데이터 준비) 인터럽트를 기다리지 않고 즉시 데이터를 읽는다. + * + * 처리 흐름: + * 1) TWI 초기화 확인 (최초 1회만) + * 2) 자이로 설정: ±2000dps, 100Hz ODR (GYRO_CONFIG0 = 0x09) + * 3) 가속도 설정: ±4g, 100Hz ODR (ACCEL_CONFIG0 = 0x29) + * 4) 전원 ON: 가속도+자이로 저잡음 모드 (PWR_MGMT0 = 0x0F) + * 5) 80ms 대기 (자이로 스타트업: 최소 45ms + 여유) + * 6) ACCEL_DATA_X1(0x0B)부터 12바이트 연속 읽기 (accel 6 + gyro 6) + * 7) 빅엔디안 → int16_t 변환 + * 8) 마운팅 매트릭스 적용 + * 9) "rsp:" 태그로 BLE 전송 + * 10) IMU 슬립 모드로 전환 (전력 절감) + * + * 반환값: 0=성공, -1=TX 실패, -2=RX 실패 + */ /* Raw I2C read from ICM42670P — bypasses driver API entirely */ #include "system_interface.h" #include "nrfx_twi.h" @@ -330,12 +472,12 @@ static void apply_mounting_matrix(const int32_t matrix[9], int32_t raw[3]) extern const nrfx_twi_t m_twi_icm42670; #define IMU_I2C_ADDR 0x68 -#define REG_ACCEL_X1 0x0B /* ACCEL_DATA_X1 */ +#define REG_ACCEL_X1 0x0B /* ACCEL_DATA_X1 — 가속도 X축 상위 바이트 레지스터 */ /* Direct IMU register read — raw I2C, no DRDY, sends rsp: via BLE */ int imu_read_direct(void) { - uint8_t raw[12]; /* 6 accel + 6 gyro */ + uint8_t raw[12]; /* 가속도 6바이트 + 자이로 6바이트 */ int32_t accel[3], gyro[3]; uint8_t reg; uint32_t ret; @@ -344,46 +486,50 @@ int imu_read_direct(void) DBG_PRINTF("[IMU] enter\r\n"); - /* Ensure ICM42670P TWI is initialized (once only) */ + /* TWI(I2C) 초기화 — 최초 1회만 수행 (재초기화로 클린 상태 보장) */ if (!twi_ready) { inv_i2c_master_uninitialize(); inv_i2c_master_initialize(); twi_ready = true; } - /* Configure gyro: ±2000dps, 100Hz ODR — GYRO_CONFIG0(0x20) = 0x09 */ + /* 자이로 설정: GYRO_CONFIG0(0x20) = 0x09 → ±2000dps FSR, 100Hz ODR */ { uint8_t gyro_cfg[2] = { 0x20, 0x09 }; icm42670_twi_tx(IMU_I2C_ADDR, gyro_cfg, 2, false); } - /* Configure accel: ±4g, 100Hz ODR — ACCEL_CONFIG0(0x21) = 0x29 */ + /* 가속도 설정: ACCEL_CONFIG0(0x21) = 0x29 → ±4g FSR, 100Hz ODR */ { uint8_t accel_cfg[2] = { 0x21, 0x29 }; icm42670_twi_tx(IMU_I2C_ADDR, accel_cfg, 2, false); } - /* Enable accel (low-noise) + gyro (low-noise): PWR_MGMT0 = 0x0F */ + /* 전원 ON: PWR_MGMT0(0x1F) = 0x0F → 가속도(저잡음) + 자이로(저잡음) 활성화 */ { uint8_t pwr_cmd[2] = { 0x1F, 0x0F }; /* reg=0x1F, val=0x0F */ icm42670_twi_tx(IMU_I2C_ADDR, pwr_cmd, 2, false); - nrf_delay_ms(80); /* gyro needs min 45ms + margin for first valid sample */ + nrf_delay_ms(80); /* 자이로 스타트업: 최소 45ms + 안전 마진 */ } - /* Read 12 bytes starting from ACCEL_DATA_X1 (0x0B..0x16) */ + /* ACCEL_DATA_X1(0x0B)부터 12바이트 연속 읽기 (0x0B~0x16) */ reg = REG_ACCEL_X1; - ret = icm42670_twi_tx(IMU_I2C_ADDR, ®, 1, true); + ret = icm42670_twi_tx(IMU_I2C_ADDR, ®, 1, true); /* 레지스터 주소 전송 (STOP 없음) */ if (ret) { DBG_PRINTF("[IMU] tx FAIL %u\r\n", ret); return -1; } - ret = icm42670_twi_rx(IMU_I2C_ADDR, raw, 12); + ret = icm42670_twi_rx(IMU_I2C_ADDR, raw, 12); /* 12바이트 데이터 수신 */ if (ret) { DBG_PRINTF("[IMU] rx FAIL %u\r\n", ret); return -2; } - /* Big-Endian register layout: [0..5]=accel, [6..11]=gyro */ + /* + * 빅엔디안 레지스터 레이아웃을 int16_t로 변환 + * raw[0..5] = 가속도 X,Y,Z (각 2바이트, MSB first) + * raw[6..11] = 자이로 X,Y,Z (각 2바이트, MSB first) + */ accel[0] = (int16_t)((raw[0] << 8) | raw[1]); accel[1] = (int16_t)((raw[2] << 8) | raw[3]); accel[2] = (int16_t)((raw[4] << 8) | raw[5]); @@ -391,12 +537,14 @@ int imu_read_direct(void) gyro[1] = (int16_t)((raw[8] << 8) | raw[9]); gyro[2] = (int16_t)((raw[10] << 8) | raw[11]); + /* 마운팅 매트릭스 적용 — 보드 장착 방향 보정 */ apply_mounting_matrix(icm_mounting_matrix, accel); apply_mounting_matrix(icm_mounting_matrix, gyro); DBG_PRINTF("[IMU] A:%d,%d,%d G:%d,%d,%d\r\n", accel[0], accel[1], accel[2], gyro[0], gyro[1], gyro[2]); + /* BLE 전송용 데이터 패킹: "rsp:" 태그 + 6축 데이터(12바이트) */ ssp_data[0] = (uint16_t)accel[0]; ssp_data[1] = (uint16_t)accel[1]; ssp_data[2] = (uint16_t)accel[2]; @@ -407,7 +555,7 @@ int imu_read_direct(void) format_data(imu_bin_buffer, "rsp:", ssp_data, 12); dr_binary_tx_safe(imu_bin_buffer, 8); - /* Put IMU back to sleep: accel OFF + gyro OFF */ + /* IMU 슬립 모드: PWR_MGMT0 = 0x00 → 가속도/자이로 모두 OFF (전력 절감) */ { uint8_t pwr_off[2] = { 0x1F, 0x00 }; /* reg=PWR_MGMT0, val=0x00 */ icm42670_twi_tx(IMU_I2C_ADDR, pwr_off, 2, false); diff --git a/project/ble_peripheral/ble_app_bladder_patch/icm42670p/app_raw/app_raw_main.c b/project/ble_peripheral/ble_app_bladder_patch/icm42670p/app_raw/app_raw_main.c index d5ef99a..ffe85b6 100644 --- a/project/ble_peripheral/ble_app_bladder_patch/icm42670p/app_raw/app_raw_main.c +++ b/project/ble_peripheral/ble_app_bladder_patch/icm42670p/app_raw/app_raw_main.c @@ -3,7 +3,28 @@ * @author CandyPops Co. * @version V1.0.0 * @date 2022-09-05 - * @brief + * @brief + ******************************************************************************/ + +/******************************************************************************* + * [모듈 개요] ICM42670P 메인 초기화 및 폴링 루프 + * + * ICM42670P IMU 센서의 전체 초기화 시퀀스와 메인 루프를 담당한다. + * + * 초기화 흐름 (icm42670_init): + * 1) setup_mcu() - I2C 시리얼 인터페이스 구조체 설정 및 TWI 초기화 + * 2) setup_imu_device() - IMU 드라이버 초기화 + WHOAMI 확인 + * 3) configure_imu_device() - 센서 파라미터 설정 (FSR, ODR, 전원 모드) + * 4) inv_gpio_sensor_irq_init() - INT1(P1.13) GPIO 인터럽트 설정 + * + * 메인 루프 (icm42670_main): + * - INT1 인터럽트 발생 시 irq_from_device 플래그가 세팅됨 + * - 메인 루프에서 플래그를 확인하고, 세팅되어 있으면 센서 데이터를 읽음 + * - 인터럽트는 하강 에지(HITOLO)에서 발생 (INT1 핀 풀업 설정) + * + * 보조 함수: + * - inv_imu_sleep_us() - nrf_delay_us 래퍼 (IMU 드라이버가 사용) + * - inv_imu_get_time_us() - RTC1 카운터로 타임스탬프 제공 ******************************************************************************/ #include "sdk_config.h" #include "app_raw.h" @@ -22,7 +43,7 @@ #include "nrf_delay.h" #include "app_util_platform.h" -#include +#include "main.h" /* 2026-03-17: cmd_parse.h 삭제 → main.h */ #include "i2c_manager.h" /* -------------------------------------------------------------------------------------- * Global variables @@ -32,7 +53,12 @@ * Static variables * -------------------------------------------------------------------------------------- */ -/* Flag set from IMU device irq handler */ +/* + * IMU 인터럽트 플래그 + * INT1 핀의 하강 에지 인터럽트 발생 시 1로 세팅된다. + * 메인 루프에서 이 플래그를 확인 후 데이터를 읽고 0으로 클리어한다. + * volatile: ISR에서 변경되므로 컴파일러 최적화 방지 + */ static volatile int irq_from_device; @@ -55,41 +81,66 @@ static int setup_mcu(struct inv_imu_serif *icm_serif); * @return NULL * */ +/* + * inv_gpio_sensor_interrupt_handler() + * INT1 핀 인터럽트 핸들러 (ISR). + * 센서가 새 데이터를 준비했을 때 호출되며, 플래그만 세팅하고 즉시 반환한다. + * 실제 데이터 처리는 메인 루프(icm42670_main)에서 수행한다. + */ static void inv_gpio_sensor_interrupt_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action) { irq_from_device = 1; -} +} +/* + * inv_gpio_sensor_irq_init() + * INT1(P1.13) GPIO 인터럽트를 초기화한다. + * + * 설정: + * - 트리거: 하강 에지 (HITOLO) — 센서가 INT를 Low로 끌어내릴 때 + * - 풀업 저항: 내부 풀업 활성화 + * - 핸들러: inv_gpio_sensor_interrupt_handler + * - GPIOTE 모듈이 미초기화 상태이면 먼저 초기화 + */ void inv_gpio_sensor_irq_init(void) { ret_code_t err_code; - /* Initialize int pin */ + /* GPIOTE 모듈 초기화 (이미 초기화되어 있으면 건너뜀) */ if (!nrfx_gpiote_is_init()) { err_code = nrfx_gpiote_init(); APP_ERROR_CHECK(err_code); } + /* 하강 에지 인터럽트 설정: High→Low 전환 시 트리거, 내부 풀업 사용 */ nrfx_gpiote_in_config_t in_config = NRFX_GPIOTE_CONFIG_IN_SENSE_HITOLO(true); in_config.pull = NRF_GPIO_PIN_PULLUP; + /* INT1 핀에 인터럽트 핸들러 등록 */ err_code = nrfx_gpiote_in_init(ICM42670_INT1_PIN, &in_config, inv_gpio_sensor_interrupt_handler); APP_ERROR_CHECK(err_code); + /* 인터럽트 이벤트 활성화 */ nrfx_gpiote_in_event_enable(ICM42670_INT1_PIN, true); } +/* + * inv_gpio_sensor_irq_uninit() + * INT1 GPIO 인터럽트를 비활성화하고 해제한다. + * 센서 비활성화 시 또는 재초기화 전에 호출된다. + */ void inv_gpio_sensor_irq_uninit(void) { - + /* 인터럽트 이벤트 비활성화 */ nrfx_gpiote_in_event_disable(ICM42670_INT1_PIN); + /* INT1 핀 인터럽트 설정 해제 */ nrfx_gpiote_in_uninit(ICM42670_INT1_PIN); - /* Initialize int pin */ + /* GPIOTE 모듈 해제 (초기화된 경우에만) */ if (nrfx_gpiote_is_init()) { nrfx_gpiote_uninit(); @@ -100,6 +151,19 @@ void inv_gpio_sensor_irq_uninit(void) /* -------------------------------------------------------------------------------------- * Main * -------------------------------------------------------------------------------------- */ + +/* + * icm42670_init() + * ICM42670P 전체 초기화 시퀀스를 수행한다. + * + * 초기화 순서: + * 1) setup_mcu() - I2C 인터페이스 구조체 설정 및 TWI 하드웨어 초기화 + * 2) setup_imu_device() - IMU 드라이버 초기화, WHOAMI(0x67) 확인 + * 3) configure_imu_device() - FSR, ODR, 전원 모드 설정 + * 4) inv_gpio_sensor_irq_init() - INT1 인터럽트 활성화 (데이터 준비 알림) + * + * 반환값: 0=성공, -1=초기화 실패 + */ int icm42670_init(void) { int rc = 0; @@ -113,29 +177,45 @@ int icm42670_init(void) printf("!!!error during initialization\r\n"); return -1; } - + + /* 초기화 성공 후 INT1 인터럽트 활성화 — 이후 데이터 준비 시 ISR이 호출됨 */ inv_gpio_sensor_irq_init(); return rc; } +/* + * icm42670_main() + * ICM42670P 메인 폴링 루프. + * 메인 애플리케이션 루프에서 주기적으로 호출되어야 한다. + * + * 동작: + * 1) I2C 하드웨어가 초기화되었는지 확인 (hw_i2c_init_once) + * 2) irq_from_device 플래그 확인 (ISR에서 세팅됨) + * 3) 플래그가 세팅되어 있으면 센서 데이터 읽기 (get_imu_data) + * 4) 데이터 읽기 완료 후 플래그 클리어 + * + * 참고: 인터럽트 기반 폴링 방식으로, ISR에서는 플래그만 세팅하고 + * 실제 I2C 통신은 메인 컨텍스트에서 수행한다. + */ void icm42670_main(void) { int rc = 0; hw_i2c_init_once(); - /* Poll device for data */ + /* 인터럽트 발생 여부 확인 후 데이터 읽기 */ if (irq_from_device) { rc = get_imu_data(); - + if(rc < 0) { printf("error while getting data\r\n"); } + /* 플래그 클리어 — 다음 인터럽트까지 대기 */ irq_from_device = 0; } - + } @@ -144,25 +224,29 @@ void icm42670_main(void) * -------------------------------------------------------------------------------------- */ /* - * This function initializes MCU on which this software is running. - * It configures: - * - a UART link used to print some messages - * - interrupt priority group and GPIO so that MCU can receive interrupts from IMU - * - a microsecond timer requested by IMU driver to compute some delay - * - a microsecond timer used to get some timestamps - * - a serial link to communicate from MCU to IMU + * setup_mcu() + * MCU 측 시리얼 인터페이스를 설정한다. + * + * inv_imu_serif 구조체에 다음을 등록: + * - read_reg / write_reg : I2C 읽기/쓰기 콜백 함수 (system_interface.c에서 구현) + * - max_read / max_write : 최대 전송 크기 (32KB) + * - serif_type : 통신 타입 (UI_I2C) + * + * 설정 후 inv_io_hal_init()을 호출하여 실제 TWI 하드웨어를 초기화한다. */ static int setup_mcu(struct inv_imu_serif *icm_serif) { int rc = 0; - /* Initialize serial interface between MCU and IMU */ - icm_serif->context = 0; /* no need */ - icm_serif->read_reg = inv_io_hal_read_reg; - icm_serif->write_reg = inv_io_hal_write_reg; - icm_serif->max_read = 1024*32; /* maximum number of bytes allowed per serial read */ - icm_serif->max_write = 1024*32; /* maximum number of bytes allowed per serial write */ - icm_serif->serif_type = SERIF_TYPE; + /* IMU 드라이버용 시리얼 인터페이스 구조체 설정 */ + icm_serif->context = 0; /* 컨텍스트 미사용 */ + icm_serif->read_reg = inv_io_hal_read_reg; /* 레지스터 읽기 콜백 */ + icm_serif->write_reg = inv_io_hal_write_reg; /* 레지스터 쓰기 콜백 */ + icm_serif->max_read = 1024*32; /* 1회 읽기 최대 바이트 수 */ + icm_serif->max_write = 1024*32; /* 1회 쓰기 최대 바이트 수 */ + icm_serif->serif_type = SERIF_TYPE; /* UI_I2C (app_raw.h에서 정의) */ + + /* TWI 하드웨어 초기화 */ rc |= inv_io_hal_init(icm_serif); return rc; @@ -173,16 +257,28 @@ static int setup_mcu(struct inv_imu_serif *icm_serif) * Extern functions definition * -------------------------------------------------------------------------------------- */ -/* Sleep implementation */ +/* + * inv_imu_sleep_us() + * IMU 드라이버가 사용하는 마이크로초 단위 슬립 함수. + * nrf_delay_us()를 래핑하여 플랫폼 독립적 인터페이스를 제공한다. + * 예: 자이로 스타트업 대기(GYR_STARTUP_TIME_US) 시 사용 + */ void inv_imu_sleep_us(uint32_t us) { nrf_delay_us(us); } -/* Get time implementation */ +/* + * inv_imu_get_time_us() + * IMU 드라이버가 사용하는 타임스탬프 함수. + * nRF52840의 RTC1 카운터 값을 반환한다. + * + * 주의: RTC1은 32.768kHz로 동작하므로, 반환값의 단위는 엄밀히 + * 마이크로초가 아닌 RTC 틱(약 30.5us/tick)이다. + * 드라이버 내부에서 상대적 시간 비교 용도로 사용된다. + */ uint64_t inv_imu_get_time_us(void) { return NRF_RTC1->COUNTER; } - diff --git a/project/ble_peripheral/ble_app_bladder_patch/main.c b/project/ble_peripheral/ble_app_bladder_patch/main.c index 8e7ccfe..6670842 100644 --- a/project/ble_peripheral/ble_app_bladder_patch/main.c +++ b/project/ble_peripheral/ble_app_bladder_patch/main.c @@ -4,40 +4,77 @@ * @version 1.17 * @date 2025-12-09 * @author Charles KWON - * - * @note Build Modes: - * DEBUG_MINIMAL_BOOT = 1 : Power + BLE only (no sensors, no EEPROM) - * DEBUG_MINIMAL_BOOT = 0 : Full initialization - * BLE_DEV_MODE = 1 : No security (fast pairing) - * BLE_DEV_MODE = 0 : Full security (passkey required) + * + * [시스템 아키텍처 개요] + * 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 프리스케일) + * - 압력: 2채널 압력센서 (SAADC AIN7/AIN4) + * - 전원: 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 + * 인클루드 (INCLUDES) *============================================================================*/ #include #include #include "nordic_common.h" #include "nrf.h" -#include "ble_hci.h" -#include "ble_advdata.h" -#include "ble_advertising.h" -#include "ble_srv_common.h" -#include "ble_conn_params.h" -#include "nrf_sdh.h" -#include "nrf_sdh_soc.h" -#include "nrf_sdh_ble.h" -#include "nrf_ble_gatt.h" -#include "nrf_ble_qwr.h" -#include "app_timer.h" -#include "ble_nus.h" -#include "app_uart.h" -#include "app_util_platform.h" -#include "bsp_btn_ble.h" -#include "nrf_pwr_mgmt.h" -#include "nrf_delay.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" +#include "crc16.h" /* CRC16 계산 (BLE 패킷 무결성 검증) */ #if defined (UART_PRESENT) #include "nrf_uart.h" @@ -60,27 +97,30 @@ #include "nrf_svci_async_handler.h" #endif -#include "system_interface.h" -#include "main.h" -#include "app_raw_main.h" -#include "main_timer.h" -#include "power_control.h" -#include "tmp235_q1.h" -#include "fds.h" -#include "battery_saadc.h" +/* ── 애플리케이션 모듈 헤더 ── */ +#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" -#include "peer_manager_handler.h" -#include "nrf_ble_lesc.h" -#include "ble_quick_security.h" -#include "i2c_manager.h" +#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" -#include -#include "debug_print.h" -#include "fstorage.h" +/* ── 암호화/명령 파서/디버그 ── */ +#include "nrf_crypto.h" /* nRF 암호화 라이브러리 (AES 등) */ +#include "parser.h" /* 새 바이너리 명령 파서 (dr_cmd_parser) */ +#include "debug_print.h" /* 디버그 출력 매크로 (DBG_PRINTF) */ +#include "fstorage.h" /* Flash Storage 래퍼 (FDS 초기화/저장/로드) */ #define HARDWARE_VERSION "VB0HW0000" @@ -89,149 +129,160 @@ /*============================================================================== - * BUILD CONFIGURATION + * 빌드 설정 *============================================================================*/ -#define BLE_DEV_MODE 1 /**< 1: DEV (no security), 0: PROD (full security) */ -#define DEBUG_MINIMAL_BOOT 1 /**< 1: Power+BLE only, 0: Full boot */ +#define BLE_DEV_MODE 1 /* 1: 개발 모드 (보안 없음), 0: 양산 모드 (패스키 필수) */ +#define DEBUG_MINIMAL_BOOT 1 /* 1: 최소 부팅 (전원+BLE만), 0: 전체 부팅 (센서 포함) */ /*============================================================================== - * HARDWARE PIN DEFINITIONS + * 하드웨어 핀 정의 *============================================================================*/ -#define POWER_HOLD NRF_GPIO_PIN_MAP(0,8) -#define POWER_BUTTON NRF_GPIO_PIN_MAP(1,8) +#define POWER_HOLD NRF_GPIO_PIN_MAP(0,8) /* 전원 자가유지 핀 (HIGH=전원 유지) */ +#define POWER_BUTTON NRF_GPIO_PIN_MAP(1,8) /* 전원 버튼 입력 핀 (LOW=눌림) */ /*============================================================================== - * BLE CONFIGURATION + * BLE 설정 *============================================================================*/ -#define APP_BLE_CONN_CFG_TAG 1 -#define NUS_SERVICE_UUID_TYPE BLE_UUID_TYPE_VENDOR_BEGIN -#define APP_BLE_OBSERVER_PRIO 3 -#define APP_ADV_INTERVAL 64 +#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 +#define APP_ADV_DURATION 0 /* 슬립 비활성화 시: 무한 광고 */ #else -#define APP_ADV_DURATION 18000 +#define APP_ADV_DURATION 18000 /* 광고 지속시간: 18000 x 10ms = 3분 → 타임아웃 시 슬립 */ #endif -#define MIN_CONN_INTERVAL MSEC_TO_UNITS(20, UNIT_1_25_MS) -#define MAX_CONN_INTERVAL MSEC_TO_UNITS(75, UNIT_1_25_MS) -#define SLAVE_LATENCY 0 -#define CONN_SUP_TIMEOUT MSEC_TO_UNITS(4000, UNIT_10_MS) -#define FIRST_CONN_PARAMS_UPDATE_DELAY APP_TIMER_TICKS(5000) -#define NEXT_CONN_PARAMS_UPDATE_DELAY APP_TIMER_TICKS(30000) -#define MAX_CONN_PARAMS_UPDATE_COUNT 3 +#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 -#define SEC_PARAM_BOND 1 -#define SEC_PARAM_MITM 1 +#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 +#define SEC_PARAM_LESC 0 /* 정적 패스키 모드: LESC 비활성 */ #else -#define SEC_PARAM_LESC 1 +#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 -#define SEC_PARAM_MIN_KEY_SIZE 7 -#define SEC_PARAM_MAX_KEY_SIZE 16 -#define PASSKEY_TXT_LENGTH 8 +#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 /*============================================================================== - * SYSTEM CONSTANTS + * 시스템 상수 *============================================================================*/ -#define DEAD_BEEF 0xDEADBEEF -#define UART_TX_BUF_SIZE 16384 -#define UART_RX_BUF_SIZE 512 -#define POWER_ON_DELAY 5 -#define POWER_OFF_DELAY 3000 -#define POWER_RESET_DELAY 2000 -#define LED_NUM 24 -#define AES_KEY_SIZE 16 -#define AES_BLOCK_SIZE 16 +#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 INSTANCES + * BLE 인스턴스 (SoftDevice 매크로로 정적 할당) *============================================================================*/ -BLE_NUS_DEF(m_nus, NRF_SDH_BLE_TOTAL_LINK_COUNT); -NRF_BLE_GATT_DEF(m_gatt); -NRF_BLE_QWR_DEF(m_qwr); -BLE_ADVERTISING_DEF(m_advertising); +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); /* 광고 모듈 인스턴스 */ /*============================================================================== - * TIMER INSTANCES + * 타이머 인스턴스 *============================================================================*/ -APP_TIMER_DEF(m_power_on_delay_timer_id); -APP_TIMER_DEF(m_power_off_delay_timer_id); -APP_TIMER_DEF(m_PM_timer_id); +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 타이머 (연결 강제 해제용) */ /*============================================================================== - * STATIC VARIABLES + * 정적 변수 *============================================================================*/ #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; -static uint16_t m_ble_nus_max_data_len = BLE_GATT_ATT_MTU_DEFAULT - 3; -static ble_uuid_t m_adv_uuids[] = {{BLE_UUID_NUS_SERVICE, NUS_SERVICE_UUID_TYPE}}; +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]; -static uint16_t m_tx_len = 0; -static volatile bool m_tx_in_progress = false; -static volatile bool m_tx_complete_pending = false; /* TX completion wait flag */ -static uint8_t c_addr[6]; +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"}; /*============================================================================== - * GLOBAL VARIABLES + * 전역 변수 *============================================================================*/ -uint8_t m_encrypted_text[AES_BLOCK_SIZE]; -uint8_t m_encrypted_text2[AES_BLOCK_SIZE]; -uint8_t m_decrypted_text[AES_BLOCK_SIZE]; +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; -volatile bool power_state = false; +volatile uint8_t Sj_type; /* 명령 타입 식별자 */ +volatile bool processing; /* 센서 데이터 처리 중 플래그 (중복 명령 방지) */ +bool power_off_duble_prohibit = false; /* 전원 OFF 중복 방지 플래그 */ +volatile bool power_state = false; /* 전원 상태 추적 */ -extern bool go_device_power_off; -extern bool go_sleep_mode_enter; -extern bool go_NVIC_SystemReset; -extern bool ble_got_new_data; -extern bool motion_data_once; -extern bool con_single; -extern bool info4; -extern uint8_t add_cycle; -extern bool motion_raw_data_enabled; +/* ── 외부 모듈 플래그 (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; -char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN]; -uint16_t ble_bin_buff[BLE_NUS_MAX_DATA_LEN/2]; -which_cmd_t cmd_type_t; +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; -bool device_reset = true; +bool device_status = false; /* 디바이스 활성 상태 (true=동작 중) */ +bool device_reset = true; /* 부팅 중 플래그 (true=아직 미부팅) */ #if FEATURE_SECURE_CONNECTION -bool erase_bonds; +bool erase_bonds; /* 본딩 삭제 플래그 (부팅 시 버튼 상태에 따라 설정) */ #endif -volatile bool ble_connection_st; -volatile bool data_tx_in_progress = false; +volatile bool ble_connection_st; /* BLE 연결 상태 (1=연결됨, 0=미연결) */ +volatile bool data_tx_in_progress = false; /* 바이너리 TX 진행 중 플래그 */ -extern char m_static_passkey[PASSKEY_LENGTH]; -extern char SERIAL_NO[SERIAL_NO_LENGTH]; -extern bool bond_data_delete; -uint8_t m_reset_status; - -extern uint32_t m_life_cycle; +/* 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=본딩완료) */ /*============================================================================== - * SUPPRESS WARNINGS + * 미사용 변수 경고 억제 *============================================================================*/ #if FEATURE_SECURE_CONNECTION static void __attribute__((unused)) suppress_unused_warnings(void) @@ -243,28 +294,49 @@ static void __attribute__((unused)) suppress_unused_warnings(void) #endif /*============================================================================== - * POWER MANAGEMENT + * 전원 관리 *============================================================================*/ + +/** + * @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); - NRF_P0->OUTSET = (1 << 8); + 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); + 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); + nrf_gpio_pin_set(POWER_HOLD); /* P0.8 HIGH → 전원 유지 */ DBG_PRINTF("[PWR] ON\r\n"); } } /*============================================================================== - * GPIO INITIALIZATION + * GPIO 초기화 *============================================================================*/ + +/** + * @brief 최소 GPIO 초기화 (DEBUG_MINIMAL_BOOT 모드용) + * + * 전원 버튼 입력과 전원 유지 핀만 설정 + * 센서 관련 GPIO는 초기화하지 않음 + */ static void minimal_gpio_init(void) { nrf_gpio_cfg_input(POWER_BUTTON, NRF_GPIO_PIN_NOPULL); @@ -276,6 +348,12 @@ static void minimal_gpio_init(void) } #if !DEBUG_MINIMAL_BOOT +/** + * @brief 전체 GPIO 초기화 (양산 모드, DEBUG_MINIMAL_BOOT=0) + * + * minimal_gpio_init()를 먼저 호출한 뒤, EEPROM 전원을 OFF하여 + * 불필요한 전력 소모를 방지한다. + */ static void full_gpio_init(void) { minimal_gpio_init(); @@ -287,8 +365,17 @@ static void full_gpio_init(void) #endif /*============================================================================== - * CONFIGURATION LOADING + * 설정 로드 + * FDS(Flash Data Storage)에서 설정을 읽기 전에 기본값을 먼저 로드 + * FDS 초기화 후 load_flash_config()에서 플래시 저장값으로 덮어씀 *============================================================================*/ + +/** + * @brief 기본 설정값 로드 + * + * FDS에서 설정을 읽기 전에 안전한 기본값을 전역 변수에 설정 + * 시리얼 번호, 패스키, 리셋 상태 등을 기본값으로 초기화 + */ static void load_default_config(void) { memset(SERIAL_NO, 0, SERIAL_NO_LENGTH); @@ -303,10 +390,12 @@ static void load_default_config(void) DBG_PRINTF("[CFG] Default (S/N=%s)\r\n", SERIAL_NO); } -/**@brief Load configuration from internal Flash (FDS) into global variables. +/** + * @brief 내장 Flash(FDS)에서 설정값을 전역 변수로 로드 * - * Must be called AFTER ble_stack_init() → fs_storage_init() → config_load(). - * Reads m_config (populated by config_load) and copies to runtime globals. + * 반드시 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; @@ -314,7 +403,7 @@ static void load_flash_config(void) { m_need_save_defaults = false; - /* Hardware Number — fill default if empty */ + /* 하드웨어 번호 — 비어있으면 기본값 채움 */ 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)); @@ -322,7 +411,7 @@ static void load_flash_config(void) m_need_save_defaults = true; } - /* Serial Number — fill default if empty */ + /* 시리얼 번호 — 비어있으면 기본값 채움 */ 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)); @@ -330,18 +419,18 @@ static void load_flash_config(void) m_need_save_defaults = true; } - /* Serial Number → BLE device name */ + /* 시리얼 번호 → BLE 디바이스 이름으로 복사 */ memset(SERIAL_NO, 0, SERIAL_NO_LENGTH); memcpy(SERIAL_NO, m_config.serial_no, SERIAL_NO_LENGTH); - /* Passkey */ + /* 패스키 복사 */ memset(m_static_passkey, 0, PASSKEY_LENGTH); memcpy(m_static_passkey, m_config.static_passkey, PASSKEY_LENGTH); - /* Bond data delete flag */ + /* 본딩 데이터 삭제 플래그 */ bond_data_delete = m_config.bond_data_delete; - /* Reset status */ + /* 리셋 상태 */ m_reset_status = m_config.reset_status; DBG_PRINTF("[CFG] HW=%.12s S/N=%s passkey=%.6s bond=%d rst=%d\r\n", @@ -349,29 +438,40 @@ static void load_flash_config(void) m_reset_status); } -/* load_eeprom_config() 삭제 — EEPROM 제거됨, FDS가 대체 - jhChun 26.03.16 */ - /*============================================================================== - * TIMER CALLBACKS + * 타이머 콜백 함수 *============================================================================*/ -static void main_s(void * p_context); -static void t_power_off_timeout_handler(void * p_context); -static void PM_s(void * p_context); +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); - power_control_handler(OFF); + 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); @@ -379,8 +479,20 @@ static void PM_s(void * p_context) } /*============================================================================== - * TIMER FUNCTIONS + * 타이머 초기화/시작 함수 *============================================================================*/ + +/** + * @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(); @@ -399,6 +511,12 @@ static void timers_init(void) #endif } +/** + * @brief 전원 버튼 폴링 타이머 시작 + * + * 5ms(POWER_ON_DELAY) 후 main_s() 콜백 + * main_s()에서 버튼 상태를 확인하고, 아직 눌려있으면 다시 호출하여 상태머신 구동 + */ static void timers_start(void) { ret_code_t err_code; @@ -407,7 +525,9 @@ static void timers_start(void) } /*============================================================================== - * BLE DFU HANDLER + * BLE DFU 핸들러 + * DFU(Device Firmware Update) 이벤트를 처리하여 BLE를 통한 펌웨어 업데이트를 지원한다. + * 부트로더 진입 준비, 진입 성공/실패 등의 이벤트를 로그로 출력한다. *============================================================================*/ #if BLE_DFU_ENABLED static void ble_dfu_evt_handler(ble_dfu_buttonless_evt_type_t event) @@ -434,8 +554,16 @@ static void ble_dfu_evt_handler(ble_dfu_buttonless_evt_type_t event) #endif /*============================================================================== - * BLE GAP FUNCTIONS + * 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; @@ -469,28 +597,36 @@ static void gap_params_init(void) } /*============================================================================== - * BLE SERVICE FUNCTIONS + * BLE 서비스 초기화 *============================================================================*/ static void nrf_qwr_error_handler(uint32_t nrf_error) { APP_ERROR_HANDLER(nrf_error); } -/* Forward declaration for async MAA TX ready handler */ +/* 비동기 MAA TX 완료 핸들러 (전방 선언) */ extern bool maa_async_on_tx_ready(void); extern bool maa_async_is_busy(void); +/** + * @brief NUS(Nordic UART Service) 데이터 수신 핸들러 + * + * BLE를 통해 스마트폰 앱에서 데이터를 수신했을 때 호출됨 + * - RX_DATA 이벤트: 수신된 명령을 received_command_process()로 전달 + * - 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; DBG_PRINTF("[NUS] RX len=%d\r\n", p_evt->params.rx_data.length); - received_command_process(p_evt->params.rx_data.p_data, CMD_BLE, p_evt->params.rx_data.length); + dr_cmd_parser(p_evt->params.rx_data.p_data, p_evt->params.rx_data.length); } else if (p_evt->type == BLE_NUS_EVT_TX_RDY) { - /* BLE TX buffer has space - continue async MAA transmission */ + /* BLE TX 버퍼에 공간이 생김 - 비동기 MAA 전송 계속 */ if (maa_async_is_busy()) { maa_async_on_tx_ready(); @@ -498,6 +634,12 @@ static void nus_data_handler(ble_nus_evt_t * p_evt) } } +/** + * @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; @@ -522,8 +664,9 @@ static void services_init(void) } /*============================================================================== - * BLE CONNECTION PARAMETERS + * 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) @@ -538,6 +681,12 @@ 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; @@ -558,10 +707,18 @@ static void conn_params_init(void) } /*============================================================================== - * BLE STACK INITIALIZATION + * 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; @@ -580,8 +737,11 @@ static void ble_stack_init(void) } /*============================================================================== - * BLE GATT + * 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)) @@ -590,6 +750,7 @@ void gatt_evt_handler(nrf_ble_gatt_t * p_gatt, nrf_ble_gatt_evt_t const * p_evt) } } +/** @brief GATT 모듈 초기화 — MTU를 최대 크기로 설정 */ static void gatt_init(void) { ret_code_t err_code; @@ -602,8 +763,15 @@ static void gatt_init(void) } /*============================================================================== - * BLE ADVERTISING + * BLE 광고 (Advertising) *============================================================================*/ + +/** + * @brief BLE 광고 이벤트 핸들러 + * + * - FAST: 광고 시작됨 (LED 표시) + * - IDLE: 광고 타임아웃 → 슬립 모드 진입 플래그 설정 + */ static void on_adv_evt(ble_adv_evt_t ble_adv_evt) { switch (ble_adv_evt) @@ -622,6 +790,12 @@ static void on_adv_evt(ble_adv_evt_t ble_adv_evt) } } +/** + * @brief BLE 광고 초기화 + * + * 광고 데이터 설정: 디바이스 이름(전체), NUS UUID + * 광고 모드: Fast (40ms 간격), 지속시간 3분 (FEATURE_NO_SLEEP 시 무한) + */ static void advertising_init(void) { uint32_t err_code; @@ -653,6 +827,7 @@ static void advertising_init(void) } #if FEATURE_SECURE_CONNECTION +/** @brief 모든 본딩(페어링) 정보를 Flash에서 삭제 */ static void delete_bonds(void) { ret_code_t err_code; @@ -661,12 +836,19 @@ static void delete_bonds(void) 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(); + delete_bonds(); /* 삭제 완료 후 pm_evt_handler에서 광고 시작 */ } else { @@ -675,6 +857,7 @@ static void advertising_start(bool erase_bonds_flag) } } #else +/** @brief BLE 광고 시작 (비보안 모드, 즉시 Fast 광고) */ static void advertising_start(void) { uint32_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST); @@ -683,36 +866,51 @@ static void advertising_start(void) #endif /*============================================================================== - * BLE SECURITY (PEER MANAGER) + * 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(); + 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); @@ -721,7 +919,8 @@ static void pm_evt_handler(pm_evt_t const * p_evt) } } 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) @@ -733,18 +932,21 @@ static void pm_evt_handler(pm_evt_t const * p_evt) } } 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); @@ -752,15 +954,21 @@ static void pm_evt_handler(pm_evt_t const * p_evt) { memcpy(c_addr, peer_bonding_data.peer_ble_id.id_addr_info.addr, sizeof(c_addr)); } - m_reset_status = 10; + 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; @@ -775,8 +983,23 @@ static void peer_manager_init(void) #endif /*============================================================================== - * BLE EVENT HANDLER + * 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; @@ -883,7 +1106,7 @@ static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context) case BLE_GATTS_EVT_HVN_TX_COMPLETE: m_tx_in_progress = false; - m_tx_complete_pending = false; /* Signal TX completion to waiting functions */ + m_tx_complete_pending = false; /* TX 완료를 대기 중인 함수에 알림 */ break; default: @@ -892,8 +1115,17 @@ static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context) } /*============================================================================== - * BSP EVENT HANDLER + * 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; @@ -940,8 +1172,10 @@ void bsp_event_handler(bsp_event_t event) } /*============================================================================== - * UTILITY FUNCTIONS + * 유틸리티 함수 *============================================================================*/ + +/** @brief BSP 버튼/LED 초기화 (부트 Phase 5) */ static void buttons_leds_init(bool * p_erase_bonds) { bsp_event_t startup_event; @@ -962,6 +1196,13 @@ static void log_init(void) 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}; @@ -980,7 +1221,8 @@ void uart_event_handle(app_uart_evt_t * p_event) if (index > 1) { cmd_type_t = CMD_UART; - received_command_process(data_array, CMD_UART, index); + ble_got_new_data = true; + dr_cmd_parser(data_array, index); } index = 0; } @@ -998,6 +1240,7 @@ void uart_event_handle(app_uart_evt_t * p_event) } } +/** @brief UART 초기화 (1Mbps, 흐름제어 없음, 패리티 없음) */ static void uart_init(void) { uint32_t err_code; @@ -1032,6 +1275,13 @@ static void power_management_init(void) APP_ERROR_CHECK(err_code); } +/** + * @brief 유휴 상태 처리 + * + * BLE 보안(LESC) 요청을 처리하고, 로그를 플러시한 뒤 + * nrf_pwr_mgmt_run()으로 CPU를 저전력 상태로 전환한다. + * BLE 이벤트 발생 시 자동으로 깨어난다. + */ static void idle_state_handle(void) { #if FEATURE_SECURE_CONNECTION @@ -1051,6 +1301,12 @@ 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); @@ -1058,6 +1314,12 @@ void sleep_mode_enter(void) 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); @@ -1065,9 +1327,18 @@ void device_power_off(void) } /*============================================================================== - * DATA TRANSMISSION + * 데이터 전송 함수 + * BLE NUS를 통해 센서 데이터/응답을 스마트폰 앱으로 전송한다. + * 패킷 구조: [데이터][CRC16 2바이트] *============================================================================*/ -void data_tx_handler(char const *p_data_to_send) // 문자열 (ASCII 텍스트) 전송 함수 + +/** + * @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; @@ -1104,6 +1375,12 @@ void data_tx_handler(char const *p_data_to_send) // 문자열 (ASCII 텍스 } } +/** + * @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]; @@ -1117,6 +1394,12 @@ void single_format_data(uint8_t *buffer, const char *tag, const uint16_t value) 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]; @@ -1133,6 +1416,11 @@ void format_data(uint8_t *buffer, const char *tag, const uint16_t *data_array, s } } +/** + * @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]; @@ -1148,6 +1436,12 @@ void format_data_byte(uint8_t *buffer, const char *tag, const uint8_t *data_arra } } +/** + * @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]; @@ -1164,26 +1458,25 @@ void ascii_format_data(uint8_t *buffer, const char *tag, const char *data_ascii, } /** - * @brief Safe BLE binary transmission with proper error handling + * @brief BLE 바이너리 안전 전송 (dr_binary_tx_safe용 선행 설명) * - * Properly handles NRF_ERROR_RESOURCES by retrying - * and returns gracefully on connection errors. + * NRF_ERROR_RESOURCES(TX 큐 가득 참) 발생 시 5ms 간격으로 재시도하고, + * 연결 오류 시 graceful하게 리턴한다. * - * @param ble_bin_buff Data buffer to send - * @param length Length in uint16_t words (actual bytes = length * 2) + * @param ble_bin_buff 전송할 데이터 버퍼 + * @param length uint16_t 워드 수 (실제 바이트 = length * 2) */ /** - * @brief Delay for BLE operations - simple and safe + * @brief BLE 작업용 안전한 딜레이 * - * Uses pure nrf_delay_ms() which is interrupt-safe. - * BLE stack runs on interrupts, so TX completion events - * are processed even during delay. + * 인터럽트 안전한 nrf_delay_ms()를 사용한다. + * BLE 스택은 인터럽트로 동작하므로 딜레이 중에도 TX 완료 이벤트가 처리된다. * - * NOTE: Do NOT use __WFE() or sd_app_evt_wait() here! - * They conflict with SoftDevice power management. + * 주의: __WFE()나 sd_app_evt_wait()를 여기서 사용하면 안 됨! + * SoftDevice 전원 관리와 충돌한다. * - * @param ms Delay in milliseconds + * @param ms 딜레이 시간 (밀리초) */ void dr_sd_delay_ms(uint32_t ms) { @@ -1191,13 +1484,35 @@ void dr_sd_delay_ms(uint32_t ms) 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; /* Max retries (~500ms at 5ms each) */ + const uint16_t MAX_RETRIES = 100; /* 최대 재시도 횟수 (~500ms, 5ms x 100) */ if (ble_connection_st == 0) return; // 연결 확인 @@ -1217,18 +1532,18 @@ void dr_binary_tx_safe(uint8_t const *ble_bin_buff, uint16_t length) // BLE uint16_t total_len = length * sizeof(uint16_t) + 2; - /* Retry loop - allow SoftDevice to process events between retries */ + /* 재시도 루프 - 재시도 사이에 SoftDevice가 이벤트를 처리할 수 있도록 함 */ do { - uint16_t send_len = total_len; /* MUST reset each iteration - ble_nus_data_send modifies it! */ + 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 queue full - wait for connection event to complete TX */ - /* Use small delay to allow BLE stack to process TX complete events */ - nrf_delay_ms(5); /* Wait ~5ms for TX slot to free up */ + /* 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"); @@ -1244,7 +1559,7 @@ void dr_binary_tx_safe(uint8_t const *ble_bin_buff, uint16_t length) // BLE if (retry_count >= MAX_RETRIES) { // 최대 재시도(100회) 초과 시 해당 패킷 드롭, 연결은 유지 DBG_PRINTF("[BLE TX] FAIL %u retries\r\n", retry_count); data_tx_in_progress = false; - /* Don't set ble_connection_st = 0 here - just drop this packet and continue */ + /* ble_connection_st = 0 설정하지 않음 - 해당 패킷만 드롭하고 연결은 유지 */ return; } @@ -1253,7 +1568,14 @@ void dr_binary_tx_safe(uint8_t const *ble_bin_buff, uint16_t length) // BLE } /*============================================================================== - * MAIN TIMER CALLBACK (BUTTON HANDLING) + * 전원 버튼 상태머신 (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) { @@ -1336,7 +1658,11 @@ static void main_s(void * p_context) } /*============================================================================== - * MAIN FUNCTION + * 메인 함수 + * + * 시스템 초기화를 단계별(Phase 0~9)로 수행한 뒤 무한 루프에 진입한다. + * 무한 루프에서는 idle_state_handle()로 CPU를 저전력 상태로 유지하며, + * BLE/타이머 인터럽트에 의해 깨어나 이벤트를 처리한다. *============================================================================*/ int main(void) { @@ -1427,6 +1753,13 @@ int main(void) 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 8: 완료 DBG_PRINTF("\r\n========================================\r\n"); DBG_PRINTF(" READY [%s]\r\n", SERIAL_NO); diff --git a/project/ble_peripheral/ble_app_bladder_patch/main.h b/project/ble_peripheral/ble_app_bladder_patch/main.h index 26d047f..898fbc4 100644 --- a/project/ble_peripheral/ble_app_bladder_patch/main.h +++ b/project/ble_peripheral/ble_app_bladder_patch/main.h @@ -3,74 +3,164 @@ * @author CandyPops Co. * @version V1.0.0 * @date 2022-09-05 - * @brief + * @brief VesiScan BASIC 메인 헤더 파일 + * + * [시스템 개요] + * VesiScan BASIC은 nRF52840 기반 BLE 방광 모니터링 패치 디바이스이다. + * 본 헤더는 시스템 전역에서 사용하는 열거형, 함수 선언, 전역 변수를 정의한다. + * + * [통신 방식] + * - BLE NUS (Nordic UART Service): 스마트폰 앱과 바이너리 프로토콜 통신 + * - 물리 UART (1Mbps): 디버그 및 공장 테스트용 + * + * [데이터 전송 흐름] + * 1. 앱/UART에서 명령 수신 → received_command_process() + * 2. 센서 데이터 수집 (배터리, 온도, IMU, 압력) + * 3. format_data() 계열 함수로 바이너리 패킷 생성 + * 4. dr_binary_tx_safe()로 CRC16 추가 후 BLE 전송 ******************************************************************************/ #ifndef MAIN_H__ #define MAIN_H__ /*============================================================================== - * DATA LENGTH CONSTANTS + * 데이터 길이 상수 *============================================================================*/ -#define SERIAL_NO_LENGTH 12 -#define HW_NO_LENGTH 12 -#define PASSKEY_LENGTH 6 +#define SERIAL_NO_LENGTH 12 /* 시리얼 번호 길이 (예: "VB026030000") */ +#define HW_NO_LENGTH 12 /* 하드웨어 번호(버전) 길이 */ +#define PASSKEY_LENGTH 6 /* BLE 페어링 패스키 길이 (숫자 6자리) */ #include +#include #include #include #include #include "boards.h" -typedef enum +/*============================================================================== + * 열거형 정의 + *============================================================================*/ + +/* 디바이스 ON/OFF 제어용 열거형 (EEPROM, 전원 등) */ +typedef enum { - OFF = 0, - ON = 1 + OFF = 0, /* 꺼짐 */ + ON = 1 /* 켜짐 */ }on_off_cont_t; -typedef enum +/* 명령 수신 경로 구분 (BLE 또는 UART) */ +typedef enum { - CMD_BLE = 0, - CMD_UART = 1 + CMD_BLE = 0, /* BLE NUS를 통해 수신된 명령 */ + CMD_UART = 1 /* 물리 UART를 통해 수신된 명령 */ }which_cmd_t; +/* 챔버 자동 테스트 모드 (FEATURE_CHAMBER_AUTO_TEST 활성 시) */ #if FEATURE_CHAMBER_AUTO_TEST -typedef enum +typedef enum { - SIMPLE_AUTO_MODE = 0, - HALF_AUTO_MODE = 1, - FULL_AUTO_MODE = 2, - NONE_AUTO_MODE = 3 + SIMPLE_AUTO_MODE = 0, /* 간단 자동 모드 */ + HALF_AUTO_MODE = 1, /* 반자동 모드 */ + FULL_AUTO_MODE = 2, /* 전체 자동 모드 */ + NONE_AUTO_MODE = 3 /* 자동 모드 없음 */ }auto_meas_mode_t; #endif -typedef enum + +/* BLE 연결 상태 */ +typedef enum { - BLE_DISCONNECTED_ST = 0, - BLE_CONNECTED_ST = 1 + BLE_DISCONNECTED_ST = 0, /* BLE 미연결 */ + BLE_CONNECTED_ST = 1 /* BLE 연결됨 */ }ble_status_t; + +/*============================================================================== + * 함수 선언 + *============================================================================*/ + #if FEATURE_SECURE_CONNECTION +/* BLE 광고 시작 (erase_bonds=true이면 본딩 정보 삭제 후 시작) */ static void advertising_start(bool erase_bonds); #endif + +/* 슬립 모드 진입: LED 표시 후 POWER_OFF_DELAY(3초) 후 전원 차단 */ void sleep_mode_enter(void); + +/* 전원 OFF 타이머 콜백: POWER_OFF_DELAY 경과 후 실제 전원 차단 */ static void t_power_off_timeout_handler(void * p_context); + +/* 디바이스 전원 OFF: LED 표시 후 타이머로 지연 전원 차단 */ void device_power_off(void); + +/* 전원 제어 핸들러: POWER_HOLD 핀으로 물리적 전원 ON/OFF */ static void power_control_handler(on_off_cont_t device_power_st); + +/* 전원 버튼 상태머신 (타이머 콜백, 5ms 간격): + * - 짧은 눌림(<1.5초): 전원 OFF + * - 중간 눌림(1.5초~10초): 부팅 시퀀스 시작 + * - 긴 눌림(>10초): 공장 초기화 (패스키 리셋 + 전원 OFF) */ static void main_s(void * p_context); + +/* Peer Manager 타이머 콜백: reset_status==5이면 BLE 연결 강제 해제 */ static void PM_s(void * p_context); + //static void main_re(void * p_context); + +/* 메인 루틴 핸들러 (레거시, 현재 미사용) */ static void main_routine_handler(void * p_context); -void received_command_process(uint8_t const *data_array, which_cmd_t cmd_t,uint8_t length); + +/*------------------------------------------------------------------------------ + * 데이터 전송 함수 + *----------------------------------------------------------------------------*/ + +/* ASCII 텍스트 BLE 전송 ('\r'까지 전송, CRC16 자동 추가) */ void data_tx_handler(char const *p_data_to_send); + +/* 바이너리 데이터 BLE 안전 전송 (CRC16 자동 추가, 재시도 로직 포함) + * @param ble_bin_buff 전송할 바이너리 버퍼 + * @param length 데이터 길이 (uint16_t 워드 단위, 실제 바이트 = length × 2) */ void dr_binary_tx_safe(uint8_t const *ble_bin_buff, uint16_t length); + +/* SoftDevice 호환 딜레이 (nrf_delay_ms 래퍼) */ void dr_sd_delay_ms(uint32_t ms); + +/*------------------------------------------------------------------------------ + * 바이너리 패킷 포맷 함수 + * 패킷 구조: [4바이트 태그][데이터][2바이트 CRC16] + *----------------------------------------------------------------------------*/ + +/* 단일 uint16_t 값 포맷: [tag 4B][value 2B] */ void single_format_data(uint8_t *buffer, const char *tag, const uint16_t value) ; + +/* uint16_t 배열 포맷: [tag 4B][data0 2B][data1 2B]... */ void format_data(uint8_t *buffer, const char *tag, const uint16_t *data_array, size_t length); +/* uint8_t 바이트 배열 포맷: [tag 4B][byte0][byte1]... */ void format_data_byte(uint8_t *buffer, const char *tag, const uint8_t *data_array, size_t length); -void ascii_format_data(uint8_t *buffer, const char *tag, const char *data_ascii, size_t length); -extern volatile bool data_tx_in_progress; -extern volatile bool ble_connection_st; -extern volatile bool processing; +/* ASCII 문자열 포맷: [tag 4B][char0][char1]... */ +void ascii_format_data(uint8_t *buffer, const char *tag, const char *data_ascii, size_t length); + +/*============================================================================== + * 전역 변수 (extern) + *============================================================================*/ +extern volatile bool data_tx_in_progress; /* BLE TX 전송 진행 중 플래그 */ +extern volatile bool ble_connection_st; /* BLE 연결 상태 (0=미연결, 1=연결) */ +extern volatile bool processing; /* 센서 데이터 처리 중 플래그 (중복 명령 방지) */ + +/* 2026-03-17: cmd_parse.c에서 main.c로 이동한 전역변수 */ +extern char SERIAL_NO[SERIAL_NO_LENGTH]; /* 시리얼 번호 */ +extern char HW_NO[HW_NO_LENGTH]; /* 하드웨어 번호 */ +extern char m_static_passkey[PASSKEY_LENGTH]; /* BLE 정적 패스키 */ +extern bool bond_data_delete; /* 본딩 데이터 삭제 요청 플래그 */ +extern uint32_t m_life_cycle; /* 디바이스 수명 사이클 카운터 */ +extern uint8_t resetCount; /* 통신 타임아웃 카운터 */ +extern bool info4; /* 추가 정보 포함 측정 플래그 */ +extern uint8_t m_reset_status; /* 리셋 상태 코드 */ + +extern uint8_t ble_bin_buffer[]; /* BLE 바이너리 응답 버퍼 */ + +/* 에러 응답 전송 */ +void param_error(const char *cmd); + #endif //MAIN_H__ diff --git a/project/ble_peripheral/ble_app_bladder_patch/main_timer.c b/project/ble_peripheral/ble_app_bladder_patch/main_timer.c index 66e0e18..8a43b3a 100644 --- a/project/ble_peripheral/ble_app_bladder_patch/main_timer.c +++ b/project/ble_peripheral/ble_app_bladder_patch/main_timer.c @@ -1,5 +1,32 @@ /******************************************************************************* TEST medi50 Dec 23 + ******************************************************************************* + * + * [모듈 개요] + * 메인 이벤트 루프 타이머 모듈 (10ms 간격, 싱글샷 모드). + * + * 센서 데이터 수집 및 시스템 제어 이벤트를 플래그 기반으로 디스패치한다. + * app_timer 싱글샷 모드를 사용하므로, 이벤트 처리 완료 후 + * 필요 시 main_timer_start()로 수동 재시작해야 한다. + * + * [이벤트 플래그 및 처리 순서] + * motion_raw_data_enabled → IMU 데이터 읽기 (icm42670_main 호출) + * - motion_data_once == true: 단발성 읽기 (HW I2C 초기화 후 1회) + * - motion_data_once == false: 연속 읽기 (BLE 전송 대기 중이 아닐 때) + * go_batt → 배터리 전압 측정 (battery_level_meas) + * go_temp → 온도 측정 (tmp235_voltage_level_meas) + * go_device_power_off → 디바이스 전원 OFF (device_power_off) + * go_sleep_mode_enter → 슬립 모드 진입 (sleep_mode_enter) + * go_NVIC_SystemReset → NVIC 시스템 리셋 + * + * [info4 모드 측정 순서] + * IMU 연속 읽기 → go_batt(배터리) → go_temp(온도) → motion_data_once(IMU 단발) + * 온도 측정 완료 시 motion_data_once=true로 설정하여 다시 IMU로 돌아간다. + * + * [타이머 설정] + * - 일반 모드: 10ms 간격 (MAIN_LOOP_INTERVAL) + * - FEATURE_DETAIL_VALUE_FULL 모드: 80ms 간격 (디테일 프린트아웃용) + * ******************************************************************************/ #include "sdk_common.h" @@ -29,12 +56,15 @@ #include "tmp235_q1.h" //#include "fstorage.h" #include "power_control.h" -#include +#include "main.h" /* 2026-03-17: cmd_parse.h 삭제 → main.h */ #include "debug_print.h" #include "i2c_manager.h" //add cj + +/* 메인 루프 싱글샷 타이머 인스턴스 */ APP_TIMER_DEF(m_main_loop_timer_id); #if FEATURE_DETAIL_VALUE_FULL +/* 디테일 프린트아웃 모드: 80ms 간격으로 메인 루프 실행 */ #define MAIN_LOOP_INTERVAL 80 /* 디테일 프린트아웃이 있을경우 Full_Mode Main Prosessing 수행하는 타이머 */ //extern bool pd_adc_full_a_start; @@ -45,16 +75,37 @@ APP_TIMER_DEF(m_main_loop_timer_id); extern which_cmd_t cmd_type_t; #else +/* 일반 모드: 10ms 간격으로 메인 루프 실행 */ #define MAIN_LOOP_INTERVAL 10 #endif -bool go_batt= false; -bool go_temp= false; -bool go_device_power_off = false; -bool go_sleep_mode_enter = false; -bool go_NVIC_SystemReset = false; -bool motion_raw_data_enabled = false; -bool ble_got_new_data = false; -bool motion_data_once = false; + +/* ========================================================================== */ +/* 이벤트 플래그 (외부 모듈에서 설정, main_loop에서 처리) */ +/* ========================================================================== */ +bool go_batt= false; /* 배터리 측정 요청 플래그 */ +bool go_temp= false; /* 온도 측정 요청 플래그 */ +bool go_device_power_off = false; /* 디바이스 전원 OFF 요청 플래그 */ +bool go_sleep_mode_enter = false; /* 슬립 모드 진입 요청 플래그 */ +bool go_NVIC_SystemReset = false; /* 시스템 리셋 요청 플래그 */ +bool motion_raw_data_enabled = false; /* IMU 모션 데이터 읽기 활성화 플래그 */ +bool ble_got_new_data = false; /* BLE로 새 데이터 전송 완료 여부 */ +bool motion_data_once = false; /* IMU 단발성 읽기 모드 (true: 1회만 읽기) */ + +/** + * @brief 메인 이벤트 루프 (싱글샷 타이머 콜백) + * + * 플래그 기반 이벤트 디스패처로, 설정된 플래그에 따라 해당 처리를 수행한다. + * 싱글샷 타이머이므로 연속 실행이 필요한 경우 처리 내부에서 main_timer_start()를 + * 다시 호출하여 타이머를 재시작해야 한다. + * + * [처리 우선순위] (코드 순서대로 검사) + * 1. IMU 모션 데이터 (motion_raw_data_enabled) + * 2. 배터리 측정 (go_batt) + * 3. 온도 측정 (go_temp) + * 4. 전원 OFF (go_device_power_off) + * 5. 슬립 모드 (go_sleep_mode_enter) + * 6. 시스템 리셋 (go_NVIC_SystemReset) + */ void main_loop(void * p_context) /* For x ms */ { UNUSED_PARAMETER(p_context); @@ -87,27 +138,38 @@ void main_loop(void * p_context) /* For x ms */ // } #endif - // For Motion Data Sampling + // For Motion Data Sampling + /* ---- IMU 모션 데이터 읽기 ---- */ + /* + * motion_raw_data_enabled가 true이면 IMU(ICM42670P) 데이터를 읽는다. + * + * motion_data_once == true: + * 단발성 읽기 모드. HW I2C를 초기화한 후 icm42670_main()을 1회 호출. + * info4 모드에서 배터리/온도 측정 후 다시 IMU로 돌아올 때 사용. + * + * motion_data_once == false: + * 연속 읽기 모드. BLE 전송 대기 중(ble_got_new_data==false)이면 + * icm42670_main()을 호출하고 10ms 후 타이머를 재시작하여 반복 실행. + */ if(motion_raw_data_enabled == true) { - main_timer_stop(); + main_timer_stop(); /* 타이머 정지 (재진입 방지) */ if(motion_data_once == true) { - - hw_i2c_init_once(); - icm42670_main(); + /* 단발성 모드: HW I2C 초기화 후 IMU 데이터 1회 읽기 */ + hw_i2c_init_once(); /* HW TWI 모드로 전환 (400kHz) */ + icm42670_main(); /* IMU 데이터 읽기 및 처리 */ } else{ - + /* 연속 모드: BLE 전송 대기 중이 아니면 반복 읽기 */ if(ble_got_new_data==false){ - //for(uint16_t i=0 ; i<60 ;i++) + //for(uint16_t i=0 ; i<60 ;i++) //{ DBG_PRINTF("IMU \r\n"); - - icm42670_main(); - nrf_delay_ms(10); - motion_raw_data_enabled = true; - main_timer_start(); + + icm42670_main(); /* IMU 데이터 읽기 */ + motion_raw_data_enabled = true; /* 플래그 유지 (연속 읽기) */ + main_timer_start_ms(1000); /* 1초 후 다음 IMU 읽기 */ } // else if(ble_got_new_data==true){ // motion_data_once = true; @@ -116,73 +178,113 @@ void main_loop(void * p_context) /* For x ms */ } + /* ---- 배터리 전압 측정 ---- */ + /* + * go_batt 플래그가 true이면 배터리 레벨을 측정한다. + * info4 모드에서 IMU 연속 읽기 이후 호출되는 단계. + * 측정 완료 후 타이머가 정지된 상태로 유지된다. + */ if(go_batt == true) { DBG_PRINTF("IMU BATT\r\n"); - main_timer_stop(); - go_batt = false; + main_timer_stop(); /* 타이머 정지 */ + go_batt = false; /* 플래그 소비 (1회 실행) */ // go_temp = true; - battery_level_meas(); + battery_level_meas(); /* SAADC를 이용한 배터리 전압 측정 */ // nrf_delay_ms(20); // m48_adc_start_init(); // main_timer_start(); } - - + + + /* ---- 온도 측정 ---- */ + /* + * go_temp 플래그가 true이면 TMP235-Q1 센서로 온도를 측정한다. + * info4 모드에서 배터리 측정 이후 호출되는 단계. + * 측정 완료 후 motion_data_once=true로 설정하여 + * 다음 IMU 읽기는 단발성 모드(HW I2C 재초기화)로 전환된다. + */ if(go_temp == true) { DBG_PRINTF("IMU Temp\r\n"); - main_timer_stop(); + main_timer_stop(); /* 타이머 정지 */ // go_batt = false; - go_temp = false; - motion_data_once = true; - tmp235_voltage_level_meas(); + go_temp = false; /* 플래그 소비 (1회 실행) */ + motion_data_once = true; /* 다음 IMU 읽기를 단발성 모드로 전환 */ + tmp235_voltage_level_meas(); /* TMP235-Q1 온도 센서 전압 측정 */ // motion_raw_data_enabled = true; // main_timer_start(); - + } /* For System Control */ - - + /* ---- 시스템 제어 이벤트 처리 ---- */ + + /* 디바이스 전원 OFF 처리 */ if(go_device_power_off == true){ - main_timer_stop(); + main_timer_stop(); /* 타이머 정지 */ DBG_PRINTF("Off main_timer\r\n"); - device_power_off(); + device_power_off(); /* 디바이스 전원 OFF 실행 */ } + /* 슬립 모드 진입 처리 */ if(go_sleep_mode_enter == true){ - main_timer_stop(); + main_timer_stop(); /* 타이머 정지 */ DBG_PRINTF("sleep main timer\r\n"); - - - sleep_mode_enter(); + + + sleep_mode_enter(); /* 슬립 모드 진입 실행 */ } + /* NVIC 시스템 리셋 처리 */ if(go_NVIC_SystemReset == true) { - - - - main_timer_stop(); - - NVIC_SystemReset(); + + + + main_timer_stop(); /* 타이머 정지 */ + + NVIC_SystemReset(); /* ARM Cortex-M4 시스템 리셋 */ } } +/** + * @brief 메인 루프 타이머 시작 + * + * 싱글샷 모드로 MAIN_LOOP_INTERVAL(10ms 또는 80ms) 후 main_loop()를 호출한다. + */ void main_timer_start(void) { APP_ERROR_CHECK(app_timer_start(m_main_loop_timer_id, APP_TIMER_TICKS(MAIN_LOOP_INTERVAL), NULL)); } +/** + * @brief 지정된 간격(ms)으로 메인 루프 타이머 시작 + * + * IMU 연속 스트리밍 등 기본 간격과 다른 주기가 필요할 때 사용. + */ +void main_timer_start_ms(uint32_t interval_ms) +{ + APP_ERROR_CHECK(app_timer_start(m_main_loop_timer_id, APP_TIMER_TICKS(interval_ms), NULL)); +} + +/** + * @brief 메인 루프 타이머 정지 + */ void main_timer_stop(void) { APP_ERROR_CHECK(app_timer_stop(m_main_loop_timer_id)); } +/** + * @brief 메인 루프 타이머 초기화 (앱 시작 시 1회 호출) + * + * 싱글샷 모드 타이머를 생성하고, 콜백으로 main_loop()를 등록한다. + * 싱글샷이므로 매 호출마다 main_timer_start()로 수동 재시작해야 한다. + */ void main_timer_init(void) { APP_ERROR_CHECK(app_timer_create(&m_main_loop_timer_id, APP_TIMER_MODE_SINGLE_SHOT, main_loop)); diff --git a/project/ble_peripheral/ble_app_bladder_patch/tmp235_q1.c b/project/ble_peripheral/ble_app_bladder_patch/tmp235_q1.c index c715d11..379fd8d 100644 --- a/project/ble_peripheral/ble_app_bladder_patch/tmp235_q1.c +++ b/project/ble_peripheral/ble_app_bladder_patch/tmp235_q1.c @@ -3,11 +3,29 @@ * @author CandyPops Co. * @version V1.0.0 * @date 2022-09-05 - * @brief + * @brief + ******************************************************************************/ + +/******************************************************************************* + * [모듈 개요] TMP235-Q1 아날로그 온도센서 드라이버 + * + * TMP235-Q1은 온도에 비례하는 아날로그 전압(Vout)을 출력하는 센서이다. + * nRF52840 SAADC의 AIN3 채널로 Vout을 읽고, mV로 변환한 뒤 + * 온도(°C)로 계산한다. + * + * 온도 계산 공식 (구간별 선형 보간): + * - Vout <= 1500mV (0~100°C): Ta = (Vout - 500) / 10.0 + * - Vout <= 1750mV (100~125°C): Ta = (Vout - 1500) / 10.1 + 100 + * - Vout <= 2000mV (125~150°C): Ta = (Vout - 1752.5) / 10.6 + 125 + * - Vout > 2000mV: 오류 (150°C 초과) + * + * info4 모드(전체 센서 수집) 동작 순서: + * 배터리(go_batt) → 온도(go_temp) → IMU(motion_raw_data_enabled) + * 온도 측정 완료 시 go_temp=false, motion_raw_data_enabled=true 로 전환 ******************************************************************************/ #include "sdk_common.h" - + #include #include #include @@ -19,11 +37,14 @@ #include "ble_nus.h" #include "tmp235_q1.h" #include "main.h" -#include +/* 2026-03-17: cmd_parse.h 삭제 — main.h는 이미 포함됨 */ #include "main_timer.h" #include "debug_print.h" +/* SAADC 내부 기준전압 600mV (부동소수점) */ #define TMP235_REF_VOLTAGE_IN_MILLIVOLTS 600.0f /**< Reference voltage (in milli volts) used by ADC while doing conversion. */ +/* 1/3 프리스케일링 보상 계수 x6 (부동소수점) */ #define TMP235_PRE_SCALING_COMPENSATION 6.0f /**< The ADC is configured to use VDD with 1/3 prescaling as input. And hence the result of conversion is to be multiplied by 3 to get the actual value of the battery voltage.*/ +/* 10비트 ADC 최대값 1024 (부동소수점, 분해능 기준) */ #define TMP235_ADC_RES_10BITS 1024.0f /**< Maximum digital value for 10-bit ADC conversion. */ /**@brief Macro to convert the result of ADC conversion in millivolts. @@ -32,102 +53,151 @@ * * @retval Result converted to millivolts. */ +/* ADC 원시값 → TMP235 출력전압(mV) 변환 매크로: ADC x (600/1024) x 6 */ #define TMP235_VOUT_IN_MILLI_VOLTS(ADC_VALUE)\ ((((ADC_VALUE) * TMP235_REF_VOLTAGE_IN_MILLIVOLTS) / TMP235_ADC_RES_10BITS) * TMP235_PRE_SCALING_COMPENSATION) +/* SAADC 변환 결과 저장 버퍼 (1채널) */ static nrf_saadc_value_t adc_buf; extern char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN]; extern uint8_t ble_bin_buffer[BLE_NUS_MAX_DATA_LEN] ; +/* 현재 명령 소스: CMD_UART 또는 CMD_BLE */ extern which_cmd_t cmd_type_t; +/* info4: 전체 센서 데이터 수집 모드 플래그 */ extern bool info4; //cmd_parse +/* 온도 측정 순서 제어 플래그 */ extern bool go_temp; //cmd_parse +/* info4 모드에서 온도값을 임시 저장 (°C x 100, 정수 표현) */ volatile uint16_t info_temp; //48_C -extern bool motion_raw_data_enabled; +extern bool motion_raw_data_enabled; /**@brief Function for handling the ADC interrupt. * * @details This function will fetch the conversion result from the ADC, convert the value into * percentage and send it to peer. */ + +/** + * @brief TMP235 온도센서 ADC 완료 콜백 + * + * SAADC 변환 완료 시 호출된다. + * ADC값 → Vout(mV) → 온도(°C) 순으로 변환하고, 동작 모드에 따라: + * - info4 모드: info_temp에 저장 (°C x 100 정수), 이후 IMU 측정으로 전환 + * - 일반 모드: BLE("rso:" 바이너리) 또는 UART로 온도값 전송 + */ void tmp235_voltage_handler(nrf_drv_saadc_evt_t const * p_event) /* TMP325 Vout reading */ { - float led_temp; - uint16_t led_temp_16; + float led_temp; /* 계산된 온도 (°C, 부동소수점) */ + uint16_t led_temp_16; /* BLE 전송용 온도 (°C x 100, 정수) */ if (p_event->type == NRF_DRV_SAADC_EVT_DONE) { nrf_saadc_value_t adc_result; float tmp235_voltage_in_milli_volts = 0; + /* ADC 변환 결과 읽기 */ adc_result = p_event->data.done.p_buffer[0]; + /* SAADC 해제 — 배터리/압력센서 측정과 하드웨어 공유 */ nrf_drv_saadc_uninit(); nrf_drv_saadc_channel_uninit(0); + /* ADC값 → TMP235 출력전압(mV) 변환 */ tmp235_voltage_in_milli_volts = TMP235_VOUT_IN_MILLI_VOLTS(adc_result); + /* + * Vout → 온도(°C) 변환 (구간별 선형 보간) + * TMP235 데이터시트 기반: + * 0~100°C 구간: 기울기 10.0 mV/°C, 오프셋 500mV + * 100~125°C 구간: 기울기 10.1 mV/°C + * 125~150°C 구간: 기울기 10.6 mV/°C + */ if(tmp235_voltage_in_milli_volts <= 1500) { + /* 0~100°C: Ta = (Vout - 500mV) / 10.0 mV/°C */ led_temp = (tmp235_voltage_in_milli_volts - 500.0f) / 10.0f + 0.0f; }else if(tmp235_voltage_in_milli_volts <= 1750) { + /* 100~125°C: 기울기가 10.1로 약간 증가 */ led_temp = (tmp235_voltage_in_milli_volts - 1500.0f) / 10.1f + 100.0f; }else if(tmp235_voltage_in_milli_volts <= 2000) { + /* 125~150°C: 기울기가 10.6으로 더 증가 */ led_temp = (tmp235_voltage_in_milli_volts - 1752.5f) / 10.6f + 125.0f; }else { + /* 150°C 초과 — 센서 측정 범위 벗어남 */ DBG_PRINTF("ERR!!! Temprature is over 150c\r\n"); } + + /* info4 모드: 온도값을 정수(°C x 100)로 저장 (예: 36.50°C → 3650) */ if (info4 == true){ - + info_temp =(uint16_t)(led_temp*100); - + } + /* UART 모드: 소수점 2자리까지 텍스트로 출력 */ else if(cmd_type_t == CMD_UART) { DBG_PRINTF("To%.2f\r\n\r\n",led_temp); - } else if(cmd_type_t == CMD_BLE) { + /* BLE 모드: °C x 100 정수를 "rso:" 헤더로 바이너리 전송 */ + } else if(cmd_type_t == CMD_BLE) { led_temp_16 = (uint16_t)(led_temp*100); single_format_data(ble_bin_buffer, "rso:", led_temp_16); - + dr_binary_tx_safe(ble_bin_buffer,3); - + // sprintf(ble_tx_buffer, "To%.2f\r\n",led_temp); // data_tx_handler(ble_tx_buffer); } } - - - if (info4 == true){ - - go_temp = false; - - motion_raw_data_enabled = true; - main_timer_start(); + /* info4 모드: 온도 측정 완료 → 다음 단계(IMU 측정)로 전환 */ + if (info4 == true){ + + go_temp = false; /* 온도 측정 완료 표시 */ + + + motion_raw_data_enabled = true; /* IMU 데이터 수집 시작 플래그 */ + main_timer_start(); /* 메인 타이머 시작 → IMU 측정 트리거 */ } } +/** + * @brief TMP235 온도센서 SAADC 초기화 및 측정 시작 + * + * AIN3 채널을 싱글엔드 모드로 설정하고 즉시 샘플링을 트리거한다. + * 결과는 tmp235_voltage_handler 콜백에서 비동기로 처리된다. + */ void tmp235_init(void) { + /* SAADC 드라이버 초기화 (기본 설정 + 온도센서 이벤트 핸들러 등록) */ ret_code_t err_code = nrf_drv_saadc_init(NULL, tmp235_voltage_handler); APP_ERROR_CHECK(err_code); + /* AIN3 채널 설정: TMP235-Q1 Vout 핀 (싱글엔드 입력) */ nrf_saadc_channel_config_t config = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN3); /* TMP235_Q1 Voltage Output Measurement */ err_code = nrf_drv_saadc_channel_init(0, &config); APP_ERROR_CHECK(err_code); + /* ADC 버퍼 등록 (1채널, 1샘플) */ err_code = nrf_drv_saadc_buffer_convert(&adc_buf, 1); APP_ERROR_CHECK(err_code); + /* 즉시 ADC 샘플링 시작 (비동기) */ err_code = nrf_drv_saadc_sample(); - APP_ERROR_CHECK(err_code); + APP_ERROR_CHECK(err_code); } /* Ta = (Vout – Voffs ) / Tc + Tinfl */ +/** + * @brief 온도 측정 외부 호출 함수 + * + * tmp235_init()을 호출하여 SAADC 초기화 + 측정을 일괄 수행한다. + * 내부적으로 init 시 바로 샘플링이 시작되므로 별도 sample 호출 불필요. + */ void tmp235_voltage_level_meas(void) { tmp235_init(); //tmp235_uninit(); - + }