Files
VesiScan-Basic_Zephyr/src/drivers/battery/battery_adc.c
T
2026-07-06 11:14:35 +09:00

335 lines
10 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*******************************************************************************
* @file battery_adc.c
* @brief Battery voltage ADC measurement
*
* ADC 채널 설정은 디바이스트리 overlay에서 관리:
* - AIN2 (P0.04), Single-ended
* - 12-bit 해상도, 1/6 gain, 4X oversample, 40us acq time
*
* 변환 공식: mV = (ADC × 600 / 4095) × 6 × 1.42
* → 프로젝트 고유 분압비(1.42x) 때문에 adc_raw_to_millivolts_dt() 사용 불가
*
* 동작 모드:
* - 단독 측정 (msn? 커맨드) → battery_read_mv() 호출
* - 주기 모니터링 (60초) → 저전압 또는 고온 5회 연속 시 자동 전원 OFF
* - 측정 중(processing) 또는 IMU FIFO 동작 중에는 주기 모니터링 스킵
* - mbb? 측정 응답 -> parser에서 battery_read_mv() 호출 후 패킷에 포함
******************************************************************************/
#include <zephyr/kernel.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/devicetree.h>
#include <string.h>
#include <limits.h>
#include "battery_adc.h"
#include "debug_print.h"
#include "imu_i2c.h"
#include "main.h"
/* ADC 디바이스트리 설정 */
#define ZEPHYR_USER_NODE DT_PATH(zephyr_user)
/* 디바이스트리에서 ADC 채널 스펙 가져오기 (gain, ref, acq time, input, resolution, oversampling) */
static const struct adc_dt_spec battery_adc = ADC_DT_SPEC_GET_BY_NAME(ZEPHYR_USER_NODE, battery);
static int16_t adc_buffer;
/* Module variables */
volatile bool battery_saadc_done = false;
volatile uint16_t info_batt = 0;
static struct k_timer battery_timer;
static struct k_work battery_work;
/*
* 60초 내부 모니터 전용 카운터
* - 앱 측정 명령(mbb?/mtb?)이 없는 일반 상태에서만 사용
* - 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 // 정렬모드(mtb?)나 주기 측정(mbb?)이 아닌 경우 60초 주기 5회 연속 저전압 감지 시 전원 OFF(5분)
#define BATTERY_CONTINUOUS_MONITOR_LIMIT_COUNT 10U // 주기 측정 10회 연속 저전압 감지 시 전원 OFF(100초)
#define HIGH_TEMPERATURE_CDEG 4000 // 40.00 C
#define CONTINUOUS_MONITOR_ACTIVE_MS 15000 // mbb? 10초 주기 대비 여유
#define BATTERY_MONITOR_INTERVAL_MS 60000 // 정렬모드(mtb?)나 주기 측정(mbb?)이 아닌 경우 저전압/고온 모니터 주기 60초
/*
* 전압 변환 (ADC raw → mV 변환)
* 공식: (raw × 600 / 4095) × 6 × 1.42
* 정수 연산: raw × 600 × 6 × 142 / (4095 × 100)
*/
static int adc_raw_to_mv(int16_t raw)
{
if (raw < 0)
{
raw = 0;
}
// 오버플로 방지: 600 × 6 × 142 = 511,200 → raw 최대 4095 → 4095 × 511200 = ~2B → int32 OK
int32_t mv = ((int32_t)raw * 600 * 6 * 142) / (4095 * 100);
return (int)mv;
}
static bool continuous_monitor_is_active(void)
{
// 마지막 mbb? 성공 이후 CONTINUOUS_MONITOR_ACTIVE_MS 안쪽이면 주기 측정(mbb?) 중으로 간주
return (continuous_monitor_active_until_ms > 0) && (k_uptime_get() < continuous_monitor_active_until_ms);
}
/*
* mode별 카운터만 전달받아 공통 판정 로직 사용
* - periodic: 60초 내부 모니터, limit 5
* - continuous: mbb? 응답값 기반, limit 10
*/
static void record_battery_value(int batt_mv, uint8_t *cnt, uint8_t limit, const char *mode)
{
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;
}
}
/*
* IMU 온도는 centi-degree 단위
* - 4000 = 40.00 C
* - 40.00 C 이상이면 고온 카운트 증가
* - 기준 미만이면 해당 mode 카운터 초기화
*/
static void record_temperature_value(int16_t temp_cdeg, uint8_t *cnt, uint8_t limit, const char *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 */
void battery_adc_init(void)
{
// ADC 디바이스 준비 상태 확인
if (!adc_is_ready_dt(&battery_adc))
{
DBG_PRINTF("[BATT] ADC device not ready\r\n");
return;
}
// 디바이스트리 설정으로 채널 구성
int err = adc_channel_setup_dt(&battery_adc);
if (err)
{
DBG_PRINTF("[BATT] Channel setup failed (err %d)\r\n", err);
return;
}
DBG_PRINTF("[BATT] ADC init OK (DT-based, ch=%d, res=%d, os=%d)\r\n",
battery_adc.channel_id,
battery_adc.resolution,
battery_adc.oversampling);
}
int battery_read_mv(void)
{
// 매번 채널 재설정 (다른 센서와 SAADC 공유 시 필요)
int err = adc_channel_setup_dt(&battery_adc);
if (err)
{
DBG_PRINTF("[BATT] Channel setup failed (err %d)\r\n", err);
return -1;
}
// 디바이스트리에서 시퀀스 초기화 (channels, resolution, oversampling)
struct adc_sequence seq = {0};
err = adc_sequence_init_dt(&battery_adc, &seq);
if (err)
{
DBG_PRINTF("[BATT] Sequence init failed (err %d)\r\n", err);
return -1;
}
seq.buffer = &adc_buffer;
seq.buffer_size = sizeof(adc_buffer);
// ADC 읽기
err = adc_read_dt(&battery_adc, &seq);
if (err)
{
DBG_PRINTF("[BATT] Read failed (err %d)\r\n", err);
return -1;
}
int16_t raw = adc_buffer;
int mv = adc_raw_to_mv(raw);
//DBG_PRINTF("[BATT] raw=%d mv=%d\r\n", raw, mv);
battery_saadc_done = true;
return mv;
}
/*
* 정렬 상태 진입/해제는 mls? 명령에서 전달
* - enabled true: 보호 판정 중지 + 기존 누적값 초기화
* - enabled false: 다음 조건부터 다시 판정 가능
*/
void battery_protection_set_alignment_mode(bool enabled)
{
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;
}
}
/*
* mbb? 측정 성공 시에만 호출
* - 실패한 측정은 호출되지 않아서 continuous 카운트 유지
* - 정렬 중에는 응답값이 있어도 보호 판정 제외
* - mbb?가 들어온 직후에는 내부 60초 모니터 대신 응답값 기반 판정 사용
*/
void battery_protection_record_continuous(int batt_mv, int16_t temp_cdeg)
{
// 정렬모드 중에는 continuous 보호 판정 제외
if (alignment_mode_active)
{
return;
}
// 15초 후 일반 60초 주기 모니터 재개
continuous_monitor_active_until_ms = k_uptime_get() + CONTINUOUS_MONITOR_ACTIVE_MS;
// periodic 모니터 카운트 초기화
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");
}
/* k_work 핸들러: 스레드 컨텍스트에서 ADC 읽기 (adc_read는 블로킹이라 ISR 불가) */
static void battery_work_handler(struct k_work *work)
{
ARG_UNUSED(work);
// 일반 60초 주기 보호 판정 제외 구간: 정렬모드 또는 주기 측정
if (alignment_mode_active || continuous_monitor_is_active())
{
return;
}
// I2C 접근 충돌 방지: 긴 측정 명령 처리 중이거나 IMU FIFO 동작 중
if (processing || imu_fifo_is_active())
{
return;
}
int batt = battery_read_mv();
if (batt < 0)
{
return;
}
// 정렬모드나 주기 측정 중이 아닌 경우 60초 내부 모니터 판정(배터리)
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);
if (temp_ret != 0)
{
DBG_PRINTF("[TEMP] IMU read failed ret=%d\r\n", temp_ret);
return;
}
// 정렬모드나 주기 측정 중이 아닌 경우 60초 내부 모니터 판정(온도)
record_temperature_value(temp_cdeg, &periodic_high_temperature_cnt, BATTERY_MONITOR_LIMIT_COUNT, "periodic");
}
/* 타이머 ISR → k_work 예약 */
static void battery_timer_handler(struct k_timer *timer)
{
ARG_UNUSED(timer);
k_work_submit(&battery_work);
}
void battery_timer_init(void)
{
k_work_init(&battery_work, battery_work_handler);
k_timer_init(&battery_timer, battery_timer_handler, NULL);
}
void battery_timer_start(void)
{
k_timer_start(&battery_timer, K_MSEC(BATTERY_MONITOR_INTERVAL_MS), K_MSEC(BATTERY_MONITOR_INTERVAL_MS));
}
void battery_timer_stop(void)
{
k_timer_stop(&battery_timer);
}