diff --git a/src/drivers/battery/battery_adc.c b/src/drivers/battery/battery_adc.c index 43c49d1..4607a32 100644 --- a/src/drivers/battery/battery_adc.c +++ b/src/drivers/battery/battery_adc.c @@ -19,6 +19,7 @@ #include #include #include +#include #include "battery_adc.h" #include "debug_print.h" @@ -43,11 +44,37 @@ volatile uint16_t info_batt = 0; static struct k_timer battery_timer; static struct k_work battery_work; -static uint8_t low_battery_cnt = 0; -static uint8_t high_temperature_cnt = 0; -#define BATTERY_MONITOR_LIMIT_COUNT 5U -#define HIGH_TEMPERATURE_CDEG 4000 /* 40.00 C */ +/* 60초 내부 모니터 전용 카운터 + * - 앱 측정 명령(mbb?)이 없는 일반 상태에서만 사용 + * - mbb? 성공 시 연속 측정 판정으로 넘어가므로 0으로 초기화 + */ +static uint8_t periodic_low_battery_cnt; +static uint8_t periodic_high_temperature_cnt; + +/* 연속 측정(mbb?) 응답값 전용 카운터 + * - mbb? 응답에 포함된 배터리/IMU 온도 값으로 판정 + * - 측정 실패 시 호출되지 않으므로 기존 카운트 유지 + */ +static uint8_t continuous_low_battery_cnt; +static uint8_t continuous_high_temperature_cnt; + +/* 정렬 상태 보호 판정 차단 플래그 + * - mls?로 ALIGN_SEARCHING/ALIGN_COMPLETE 진입 시 true + * - 정렬 중에는 배터리/온도 보호 판정 자체를 진행하지 않음 + */ +static bool alignment_mode_active; + +/* 최근 mbb? 성공 이후 내부 60초 모니터를 잠시 막는 기준 시각 + * - 연속 측정 중에는 mbb? 응답값으로 보호 판정 + * - 이 값보다 현재 시간이 작으면 60초 내부 모니터는 skip + */ +static int64_t continuous_monitor_active_until_ms; + +#define BATTERY_MONITOR_LIMIT_COUNT 5U +#define BATTERY_CONTINUOUS_MONITOR_LIMIT_COUNT 30U +#define HIGH_TEMPERATURE_CDEG 4000 /* 40.00 C */ +#define CONTINUOUS_MONITOR_ACTIVE_MS 10000 #define BATTERY_MONITOR_INTERVAL_MS 60000 /* 60초 주기 */ @@ -67,6 +94,77 @@ static int adc_raw_to_mv(int16_t raw) return (int)mv; } +static bool continuous_monitor_is_active(void) +{ + /* 마지막 mbb? 성공 이후 CONTINUOUS_MONITOR_ACTIVE_MS 안쪽이면 연속 측정 중으로 간주 */ + return (continuous_monitor_active_until_ms > 0) && + (k_uptime_get() < continuous_monitor_active_until_ms); +} + +static void record_battery_value(int batt_mv, uint8_t *cnt, + uint8_t limit, const char *mode) +{ + /* mode별 카운터만 전달받아 공통 판정 로직 사용 + * - periodic: 60초 내부 모니터, limit 5 + * - continuous: mbb? 응답값 기반, limit 30 + */ + if (batt_mv <= LOW_BATTERY_VOLTAGE) + { + if (*cnt < UINT8_MAX) + { + (*cnt)++; + } + + DBG_PRINTF("[BATT] LOW! mode=%s cnt=%u/%u mv=%d\r\n", + mode, *cnt, limit, batt_mv); + + if (*cnt >= limit) + { + *cnt = 0; + DBG_ERR("[BATT] %ux low (%s) -> Power OFF\r\n", limit, mode); + sleep_mode_enter_reason("battery-low"); + } + } + else + { + *cnt = 0; + } +} + +static void record_temperature_value(int16_t temp_cdeg, uint8_t *cnt, + uint8_t limit, const char *mode) +{ + /* IMU 온도는 centi-degree 단위 + * - 4000 = 40.00 C + * - 정상 온도 수신 시 기준 이하이면 해당 mode 카운터 초기화 + */ + if (temp_cdeg > HIGH_TEMPERATURE_CDEG) + { + if (*cnt < UINT8_MAX) + { + (*cnt)++; + } + + DBG_PRINTF("[TEMP] HIGH! mode=%s cnt=%u/%u temp=%d.%02d C\r\n", + mode, + *cnt, + limit, + temp_cdeg / 100, + (temp_cdeg < 0 ? -temp_cdeg : temp_cdeg) % 100); + + if (*cnt >= limit) + { + *cnt = 0; + DBG_ERR("[TEMP] %ux high (%s) -> Power OFF\r\n", limit, mode); + sleep_mode_enter_reason("temperature-high"); + } + } + else + { + *cnt = 0; + } +} + /*============================================================================== * Public functions *============================================================================*/ @@ -134,16 +232,72 @@ int battery_read_mv(void) return mv; } + +void battery_protection_set_alignment_mode(bool enabled) +{ + /* 정렬 상태 진입/해제는 mls? 명령에서 전달 + * - enabled true: 보호 판정 중지 + 기존 누적값 초기화 + * - enabled false: 다음 조건부터 다시 판정 가능 + */ + alignment_mode_active = enabled; + if (enabled) + { + periodic_low_battery_cnt = 0; + periodic_high_temperature_cnt = 0; + continuous_low_battery_cnt = 0; + continuous_high_temperature_cnt = 0; + } +} + +void battery_protection_record_continuous(int batt_mv, int16_t temp_cdeg) +{ + /* mbb? 측정 성공 시에만 호출 + * - 실패한 측정은 호출되지 않아서 continuous 카운트 유지 + * - 정렬 중에는 응답값이 있어도 보호 판정 제외 + */ + if (alignment_mode_active) + { + return; + } + + /* mbb?가 들어온 직후에는 내부 60초 모니터 대신 응답값 기반 판정 사용 + * - periodic 카운터는 섞이지 않게 초기화 + * - continuous 카운터는 아래 record_* 함수에서 값에 따라 증가/초기화 + */ + continuous_monitor_active_until_ms = k_uptime_get() + CONTINUOUS_MONITOR_ACTIVE_MS; + periodic_low_battery_cnt = 0; + periodic_high_temperature_cnt = 0; + + record_battery_value(batt_mv, + &continuous_low_battery_cnt, + BATTERY_CONTINUOUS_MONITOR_LIMIT_COUNT, + "continuous"); + record_temperature_value(temp_cdeg, + &continuous_high_temperature_cnt, + BATTERY_CONTINUOUS_MONITOR_LIMIT_COUNT, + "continuous"); +} + /*============================================================================== * 주기 모니터링 타이머 *============================================================================*/ -/** @brief k_work 핸들러 — 스레드 컨텍스트에서 ADC 읽기 (adc_read는 블로킹이라 ISR 불가) */ +/* k_work 핸들러: 스레드 컨텍스트에서 ADC 읽기 (adc_read는 블로킹이라 ISR 불가) */ static void battery_work_handler(struct k_work *work) { ARG_UNUSED(work); + /* 보호 판정 제외 구간 + * - 정렬 중: 정책상 보호 판정 미진행 + * - 최근 mbb? 성공 직후: mbb? 응답값 기반 continuous 판정이 담당 + */ + if (alignment_mode_active || continuous_monitor_is_active()) + { + return; + } + + /* 측정 처리 중이거나 IMU FIFO 사용 중(정렬모드)이면 I2C 접근 충돌 방지 */ if (processing || imu_fifo_is_active()) { return; @@ -156,24 +310,14 @@ static void battery_work_handler(struct k_work *work) return; } - /* 배터리 저전압 */ - if (batt <= LOW_BATTERY_VOLTAGE) - { - low_battery_cnt++; - DBG_PRINTF("[BATT] LOW! cnt=%d mv=%d\r\n", low_battery_cnt, batt); - - if (low_battery_cnt >= BATTERY_MONITOR_LIMIT_COUNT) - { - low_battery_cnt = 0; - DBG_ERR("[BATT] %ux low -> Power OFF\r\n", BATTERY_MONITOR_LIMIT_COUNT); - sleep_mode_enter_reason("battery-low"); - return; - } - } - else - { - low_battery_cnt = 0; - } + /* 일반 상태의 60초 내부 모니터 판정 + * - 저전압이면 periodic_low_battery_cnt 증가 + * - 정상 전압이면 periodic_low_battery_cnt 초기화 + */ + record_battery_value(batt, + &periodic_low_battery_cnt, + BATTERY_MONITOR_LIMIT_COUNT, + "periodic"); int16_t temp_cdeg = 0; int temp_ret = imu_read_temperature_cdeg(&temp_cdeg); @@ -184,26 +328,14 @@ static void battery_work_handler(struct k_work *work) return; } - if (temp_cdeg > HIGH_TEMPERATURE_CDEG) - { - high_temperature_cnt++; - DBG_PRINTF("[TEMP] HIGH! cnt=%d temp=%d.%02d C\r\n", - high_temperature_cnt, - temp_cdeg / 100, - (temp_cdeg < 0 ? -temp_cdeg : temp_cdeg) % 100); - - if (high_temperature_cnt >= BATTERY_MONITOR_LIMIT_COUNT) - { - high_temperature_cnt = 0; - DBG_ERR("[TEMP] %ux high -> Power OFF\r\n", BATTERY_MONITOR_LIMIT_COUNT); - sleep_mode_enter_reason("temperature-high"); - return; - } - } - else - { - high_temperature_cnt = 0; - } + /* 일반 상태의 IMU 온도 판정 + * - 고온이면 periodic_high_temperature_cnt 증가 + * - 정상 온도이면 periodic_high_temperature_cnt 초기화 + */ + record_temperature_value(temp_cdeg, + &periodic_high_temperature_cnt, + BATTERY_MONITOR_LIMIT_COUNT, + "periodic"); } /* 타이머 ISR → k_work 예약 */ diff --git a/src/drivers/battery/battery_adc.h b/src/drivers/battery/battery_adc.h index 400264b..0323420 100644 --- a/src/drivers/battery/battery_adc.h +++ b/src/drivers/battery/battery_adc.h @@ -21,6 +21,13 @@ extern volatile uint16_t info_batt; void battery_adc_init(void); int battery_read_mv(void); + +/* 보호 판정 제어 API + * - alignment_mode: 정렬 중 배터리/온도 보호 판정 차단 + * - record_continuous: mbb? 성공 응답값으로 연속 측정 보호 카운트 갱신 + */ +void battery_protection_set_alignment_mode(bool enabled); +void battery_protection_record_continuous(int batt_mv, int16_t temp_cdeg); void battery_timer_start(void); void battery_timer_stop(void); void battery_timer_init(void); diff --git a/src/parser.c b/src/parser.c index bb6736e..791166e 100644 --- a/src/parser.c +++ b/src/parser.c @@ -730,7 +730,7 @@ static int cmd_msr(const uint8_t *data, uint8_t data_len) /* * 현재 연결의 bond/security 정보를 먼저 지우면 같은 연결에서 rsr: 응답을 - * 보낼 수 없다. 응답 전송 완료 후 bond 삭제와 reboot를 진행한다. + * 보낼 수 없음. 응답 전송 완료 후 bond 삭제와 reboot 진행 필요 */ k_msleep(100); @@ -842,7 +842,7 @@ static int cmd_mst(const uint8_t *data, uint8_t data_len) return 1; } -/** +/* * 테스트용 * mpa?: piezo TX/RX 전원 레일만 켬 */ @@ -889,7 +889,7 @@ static int cmd_mpb(const uint8_t *data, uint8_t data_len) return 1; } -/** +/* * 테스트용 * mpc?: burst만 한 번 발생시키는 테스트 명령 * echo를 읽지 않고 초음파가 나가는지만 볼 때 사용 @@ -944,6 +944,10 @@ static int cmd_mpc(const uint8_t *data, uint8_t data_len) return 1; } +/* + * 테스트용 + * mec?: 단일 채널 burst + echo capture + */ static int cmd_mec(const uint8_t *data, uint8_t data_len) { const piezo_config_t *cfg = piezo_config_get(); @@ -964,10 +968,6 @@ static int cmd_mec(const uint8_t *data, uint8_t data_len) ARG_UNUSED(freq_option); - /* - * 테스트용 - * mec?: 단일 채널 burst + echo capture - */ if (num_samples > ECHO_ADC_MAX_SAMPLES) { num_samples = ECHO_ADC_MAX_SAMPLES; @@ -1002,12 +1002,11 @@ static int cmd_mec(const uint8_t *data, uint8_t data_len) return 1; } -/** - * 테스트용(이전 정렬모드) - * maa?: - * - piezo 6채널 burst 순서대로 쏘고 - * - echo 샘플을 채널별로 모은 뒤 - * - reb: 패킷 6개 전송 +/* + * 테스트용 + * mad?: 단일 채널 echo capture만 수행 + * - piezo burst는 하지 않고 echo ADC만 수행 + * - echo 샘플을 모은 뒤 reb: 패킷 1개 전송 * - raa: 상태값은 전체 작업 성공/실패 요약 */ static int cmd_mad(const uint8_t *data, uint8_t data_len) @@ -1058,6 +1057,14 @@ static int cmd_mad(const uint8_t *data, uint8_t data_len) return 1; } +/* + * 테스트용(이전 정렬모드) + * maa?: + * - piezo 6채널 burst 순서대로 쏘고 + * - echo 샘플을 채널별로 모은 뒤 + * - reb: 패킷 6개 전송 + * - raa: 상태값은 전체 작업 성공/실패 요약 + */ static int cmd_maa(const uint8_t *data, uint8_t data_len) { ARG_UNUSED(data); @@ -1091,8 +1098,12 @@ static int cmd_maa(const uint8_t *data, uint8_t data_len) return 1; } -/** - * 전체 측정 시 사용 +/* + * 전체 측정 명령 + * mbb?: piezo echo sweep + 배터리 + IMU + IMU 온도 측정 + * - 성공 시 rbb 1개, reb 6개, raa 상태값 전송 + * - 실패 시 rbb/reb 생략, raa에 실패 원인 코드 전송 + * - processing=true 구간에서는 내부 모니터의 I2C 접근을 막는 기준으로 사용 */ static int cmd_mbb(const uint8_t *data, uint8_t data_len) { @@ -1133,7 +1144,10 @@ static int cmd_mbb(const uint8_t *data, uint8_t data_len) if (status == ECHO_STATUS_OK) { - /* info 성격 데이터는 echo sweep이 정상 끝났을 때만 읽음 */ + /* echo sweep 성공 후에만 부가 센서값 읽기 + * - rbb에 포함되는 배터리 전압 + * - 실패 시 ECHO_STATUS_BATT로 종료 + */ DBG_PRINTF("[MBB] battery read\r\n"); batt_mv = battery_read_mv(); if (batt_mv < 0) @@ -1144,6 +1158,10 @@ static int cmd_mbb(const uint8_t *data, uint8_t data_len) if (status == ECHO_STATUS_OK) { + /* rbb에 포함되는 IMU 6축 값과 IMU 내부 온도 읽기 + * - direct read 경로 사용 + * - 실패 시 ECHO_STATUS_IMU로 종료 + */ DBG_PRINTF("[MBB] imu/temp read\r\n"); if (imu_read_with_temperature(accel, gyro, &temp_cdeg) != 0) { @@ -1153,7 +1171,17 @@ static int cmd_mbb(const uint8_t *data, uint8_t data_len) if (status == ECHO_STATUS_OK) { - /* rbb: 센서(배터리+IMU+온도) 정보 패킷 */ + /* mbb? 성공 응답값으로 연속 측정 보호 판정 갱신 + * - 실패한 측정은 호출하지 않아 기존 저전압/고온 카운트 유지 + * - rbb에 실리는 배터리/IMU 온도 값과 같은 값을 사용 + */ + battery_protection_record_continuous(batt_mv, temp_cdeg); + + /* 측정 성공 응답 순서 + * - rbb: 배터리 + IMU + IMU 온도 요약 + * - reb: 채널별 echo raw 데이터 + * - raa: 마지막 상태 코드 + */ DBG_PRINTF("[MBB] response tx start\r\n"); send_response_bundle((uint16_t)batt_mv, accel, gyro, temp_cdeg); @@ -1175,7 +1203,7 @@ static int cmd_mbb(const uint8_t *data, uint8_t data_len) return 1; } -/** +/* * mtb?: piezo sweep + IMU FIFO */ static int cmd_mtb(const uint8_t *data, uint8_t data_len) @@ -1245,7 +1273,7 @@ static int cmd_mtb(const uint8_t *data, uint8_t data_len) return 1; } -/** +/* * mcf?: piezo 측정 파라미터 읽기 * 응답 rcf: + 설정값 echo back */ @@ -1259,7 +1287,7 @@ static int cmd_mcf(const uint8_t *data, uint8_t data_len) return 1; } -/** +/* * mcs?: piezo 측정 파라미터 쓰기 */ static int cmd_mcs(const uint8_t *data, uint8_t data_len) @@ -1457,9 +1485,16 @@ static int cmd_mqz(const uint8_t *data, uint8_t data_len) return 1; } +/* + * LED 상태 변경 명령 + * mls?: 앱에서 전달한 LED 상태값을 적용하고 rls로 echo back + * - 파라미터 부족: 0xFFFF + * - 범위 초과: 0xFFFE + * - LED_STATE_OFF 진입 시 IMU FIFO 정지 + */ static int cmd_mls(const uint8_t *data, uint8_t data_len) { - /* 파라미터 부족 → 에러 코드 0xFFFF 에코 */ + /* 파라미터 부족: 에러 코드 0xFFFF 에코 */ if (data_len < 2) { send_response_u16("rls:", 0xFFFF); @@ -1467,10 +1502,10 @@ static int cmd_mls(const uint8_t *data, uint8_t data_len) return 1; } - /* state 2B BE */ + /* state 필드: 2바이트 big-endian */ uint16_t state = ((uint16_t)data[0] << 8) | (uint16_t)data[1]; - /* 범위 초과 → 에러 코드 0xFFFE 에코 */ + /* 범위 초과: 에러 코드 0xFFFE 에코 */ if (state > LED_STATE_ERROR) { send_response_u16("rls:", 0xFFFE); @@ -1479,6 +1514,15 @@ static int cmd_mls(const uint8_t *data, uint8_t data_len) } led_set_state((led_state_t)state); + + /* 정렬 LED 상태와 보호 판정 정책 연결 + * - ALIGN_SEARCHING/ALIGN_COMPLETE 동안 보호 판정 차단 + * - 다른 LED 상태에서는 기존 조건에 따라 보호 판정 재개 + */ + battery_protection_set_alignment_mode((state == LED_STATE_ALIGN_SEARCHING) || + (state == LED_STATE_ALIGN_COMPLETE)); + + /* OFF 상태에서는 정렬/측정용 FIFO를 함께 정리 */ if (state == LED_STATE_OFF) { (void)imu_fifo_stop();