전원 + BLE + 배터리
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
/*******************************************************************************
|
||||
* @file battery_adc.c
|
||||
* @brief Battery voltage ADC measurement (Zephyr devicetree 기반)
|
||||
*
|
||||
* 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초) → 저전압 10회 연속 시 자동 전원 OFF
|
||||
* - info4 모드 (mbb?) → info_batt에 저장
|
||||
******************************************************************************/
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/drivers/adc.h>
|
||||
#include <zephyr/devicetree.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "battery_adc.h"
|
||||
#include "debug_print.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(ZEPHYR_USER_NODE);
|
||||
|
||||
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;
|
||||
static uint8_t low_battery_cnt = 0;
|
||||
|
||||
#define BATTERY_MONITOR_INTERVAL_MS 60000 /* 60초 주기 */
|
||||
|
||||
/*==============================================================================
|
||||
* 전압 변환
|
||||
*============================================================================*/
|
||||
|
||||
/** @brief 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;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* 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;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* 주기 모니터링 타이머
|
||||
*============================================================================*/
|
||||
|
||||
/** @brief k_work 핸들러 — 스레드 컨텍스트에서 ADC 읽기 (adc_read는 블로킹이라 ISR 불가) */
|
||||
|
||||
static void battery_work_handler(struct k_work *work)
|
||||
{
|
||||
ARG_UNUSED(work);
|
||||
|
||||
if (processing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int batt = battery_read_mv();
|
||||
|
||||
if (batt < 0)
|
||||
{
|
||||
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 >= 10)
|
||||
{
|
||||
low_battery_cnt = 0;
|
||||
DBG_PRINTF("[BATT] 10x low -> Power OFF\r\n");
|
||||
sleep_mode_enter();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
low_battery_cnt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 타이머 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);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*******************************************************************************
|
||||
* @file battery_adc.h
|
||||
* @brief Battery voltage ADC measurement (Zephyr port)
|
||||
*
|
||||
* AIN2 채널, 12-bit, 1/6 gain, 4X oversample
|
||||
* 저전압(3500mV) 10회 연속 감지 시 자동 전원 OFF
|
||||
******************************************************************************/
|
||||
#ifndef BATTERY_ADC_H__
|
||||
#define BATTERY_ADC_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define LOW_BATTERY_VOLTAGE 3500 /* 저전압 임계값 (mV) */
|
||||
|
||||
/* 배터리 측정 완료 플래그 (all_sensors 대기용) */
|
||||
extern volatile bool battery_saadc_done;
|
||||
|
||||
/* info4 모드에서 배터리 전압 저장 (mV) */
|
||||
extern volatile uint16_t info_batt;
|
||||
|
||||
void battery_adc_init(void);
|
||||
int battery_read_mv(void);
|
||||
void battery_timer_start(void);
|
||||
void battery_timer_stop(void);
|
||||
void battery_timer_init(void);
|
||||
|
||||
#endif /* BATTERY_ADC_H__ */
|
||||
@@ -0,0 +1,246 @@
|
||||
/*******************************************************************************
|
||||
* @file led_control.c
|
||||
* @brief LED direct control driver (Zephyr port)
|
||||
*
|
||||
* k_timer based 2-color LED (green/orange) pattern control
|
||||
* Simple on/off states use immediate GPIO, complex patterns use state machine
|
||||
******************************************************************************/
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/devicetree.h>
|
||||
#include "led_control.h"
|
||||
#include "debug_print.h"
|
||||
|
||||
/*==============================================================================
|
||||
* Devicetree LED specs
|
||||
*============================================================================*/
|
||||
#define LED_BLE_NODE DT_NODELABEL(led_ble)
|
||||
#define FUNCTION_LED_NODE DT_NODELABEL(function_led)
|
||||
|
||||
static const struct gpio_dt_spec led_ble = GPIO_DT_SPEC_GET(LED_BLE_NODE, gpios);
|
||||
static const struct gpio_dt_spec function_led = GPIO_DT_SPEC_GET(FUNCTION_LED_NODE, gpios);
|
||||
|
||||
/*==============================================================================
|
||||
* Color constants
|
||||
*============================================================================*/
|
||||
#define COLOR_NONE 0
|
||||
#define COLOR_GREEN 1
|
||||
#define COLOR_ORANGE 2
|
||||
|
||||
/*==============================================================================
|
||||
* Error pattern constants (State 7)
|
||||
*============================================================================*/
|
||||
#define ERROR_BLINK_ON_MS 166
|
||||
#define ERROR_BLINK_OFF_MS 166
|
||||
#define ERROR_BLINK_COUNT 3
|
||||
#define ERROR_PAUSE_MS 1000
|
||||
|
||||
/*==============================================================================
|
||||
* Pattern table
|
||||
*============================================================================*/
|
||||
typedef struct {
|
||||
uint32_t on_ms;
|
||||
uint32_t off_ms;
|
||||
uint8_t color;
|
||||
bool repeat;
|
||||
} led_pattern_t;
|
||||
|
||||
static const led_pattern_t m_patterns[LED_STATE_COUNT] = {
|
||||
[LED_STATE_OFF] = { 0, 0, COLOR_NONE, false },
|
||||
[LED_STATE_POWER_ON] = { 2000, 0, COLOR_GREEN, false },
|
||||
[LED_STATE_POWER_OFF] = { 2000, 0, COLOR_GREEN, false },
|
||||
[LED_STATE_ADVERTISING] = { 500, 500, COLOR_GREEN, true },
|
||||
[LED_STATE_DETACH_WARNING] = { 1000, 3000, COLOR_GREEN, true },
|
||||
[LED_STATE_ALIGN_SEARCHING] = { 1000, 1000, COLOR_ORANGE, true },
|
||||
[LED_STATE_ALIGN_COMPLETE] = { 3000, 1000, COLOR_GREEN, true },
|
||||
[LED_STATE_ERROR] = { 0, 0, COLOR_ORANGE, true },
|
||||
};
|
||||
|
||||
/*==============================================================================
|
||||
* Module variables
|
||||
*============================================================================*/
|
||||
static struct k_timer m_led_timer;
|
||||
|
||||
static led_state_t m_current_state = LED_STATE_OFF;
|
||||
static bool m_phase_on;
|
||||
|
||||
/* Error pattern state machine */
|
||||
static uint8_t m_error_blink_cnt;
|
||||
static uint8_t m_error_phase;
|
||||
|
||||
/*==============================================================================
|
||||
* GPIO helpers
|
||||
*============================================================================*/
|
||||
static inline void led_ble_on(void) { gpio_pin_set_dt(&led_ble, 1); }
|
||||
static inline void led_ble_off(void) { gpio_pin_set_dt(&led_ble, 0); }
|
||||
static inline void function_led_on(void) { gpio_pin_set_dt(&function_led, 1); }
|
||||
static inline void function_led_off(void) { gpio_pin_set_dt(&function_led, 0); }
|
||||
|
||||
static void led_all_off(void)
|
||||
{
|
||||
led_ble_off();
|
||||
function_led_off();
|
||||
}
|
||||
|
||||
static void led_color_on(uint8_t color)
|
||||
{
|
||||
led_all_off();
|
||||
if (color == COLOR_GREEN) led_ble_on();
|
||||
else if (color == COLOR_ORANGE) function_led_on();
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Timer helper
|
||||
*============================================================================*/
|
||||
static void timer_start_ms(uint32_t ms)
|
||||
{
|
||||
if (ms == 0) return;
|
||||
k_timer_start(&m_led_timer, K_MSEC(ms), K_NO_WAIT);
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Error pattern state machine
|
||||
*============================================================================*/
|
||||
static void error_pattern_start(void)
|
||||
{
|
||||
m_error_blink_cnt = 0;
|
||||
m_error_phase = 0;
|
||||
led_color_on(COLOR_ORANGE);
|
||||
timer_start_ms(ERROR_BLINK_ON_MS);
|
||||
}
|
||||
|
||||
static void error_pattern_tick(void)
|
||||
{
|
||||
switch (m_error_phase)
|
||||
{
|
||||
case 0: /* ON period done -> OFF */
|
||||
led_all_off();
|
||||
m_error_phase = 1;
|
||||
timer_start_ms(ERROR_BLINK_OFF_MS);
|
||||
break;
|
||||
case 1: /* OFF period done */
|
||||
m_error_blink_cnt++;
|
||||
if (m_error_blink_cnt < ERROR_BLINK_COUNT) {
|
||||
m_error_phase = 0;
|
||||
led_color_on(COLOR_ORANGE);
|
||||
timer_start_ms(ERROR_BLINK_ON_MS);
|
||||
} else {
|
||||
m_error_phase = 2;
|
||||
timer_start_ms(ERROR_PAUSE_MS);
|
||||
}
|
||||
break;
|
||||
case 2: /* Pause done -> restart */
|
||||
m_error_blink_cnt = 0;
|
||||
m_error_phase = 0;
|
||||
led_color_on(COLOR_ORANGE);
|
||||
timer_start_ms(ERROR_BLINK_ON_MS);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Timer callback
|
||||
*============================================================================*/
|
||||
static void led_timer_handler(struct k_timer *timer)
|
||||
{
|
||||
ARG_UNUSED(timer);
|
||||
|
||||
if (m_current_state == LED_STATE_ERROR)
|
||||
{
|
||||
error_pattern_tick();
|
||||
return;
|
||||
}
|
||||
|
||||
const led_pattern_t *p = &m_patterns[m_current_state];
|
||||
|
||||
if (m_phase_on)
|
||||
{
|
||||
/* ON -> OFF transition */
|
||||
led_all_off();
|
||||
m_phase_on = false;
|
||||
|
||||
if (p->off_ms > 0)
|
||||
{
|
||||
timer_start_ms(p->off_ms);
|
||||
}
|
||||
else if (!p->repeat)
|
||||
{
|
||||
if (m_current_state == LED_STATE_POWER_OFF) {
|
||||
led_all_off();
|
||||
m_current_state = LED_STATE_OFF;
|
||||
}
|
||||
/* POWER_ON: stays lit, no timer */
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* OFF -> ON transition */
|
||||
if (p->repeat)
|
||||
{
|
||||
led_color_on(p->color);
|
||||
m_phase_on = true;
|
||||
timer_start_ms(p->on_ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Public functions
|
||||
*============================================================================*/
|
||||
|
||||
void led_init(void)
|
||||
{
|
||||
gpio_pin_configure_dt(&led_ble, GPIO_OUTPUT_INACTIVE);
|
||||
gpio_pin_configure_dt(&function_led, GPIO_OUTPUT_INACTIVE);
|
||||
led_all_off();
|
||||
|
||||
k_timer_init(&m_led_timer, led_timer_handler, NULL);
|
||||
|
||||
m_current_state = LED_STATE_OFF;
|
||||
}
|
||||
|
||||
void led_set_state(led_state_t state)
|
||||
{
|
||||
if (state >= LED_STATE_COUNT) return;
|
||||
|
||||
|
||||
k_timer_stop(&m_led_timer);
|
||||
led_all_off();
|
||||
|
||||
m_current_state = state;
|
||||
m_phase_on = false;
|
||||
|
||||
const led_pattern_t *p = &m_patterns[state];
|
||||
|
||||
switch (state) {
|
||||
case LED_STATE_OFF:
|
||||
break;
|
||||
case LED_STATE_ERROR:
|
||||
error_pattern_start();
|
||||
break;
|
||||
default:
|
||||
led_color_on(p->color);
|
||||
m_phase_on = true;
|
||||
|
||||
if (p->on_ms > 0)
|
||||
{
|
||||
timer_start_ms(p->on_ms);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
led_state_t led_get_state(void)
|
||||
{
|
||||
return m_current_state;
|
||||
}
|
||||
|
||||
void led_ble_solid(void)
|
||||
{
|
||||
k_timer_stop(&m_led_timer);
|
||||
led_all_off();
|
||||
led_ble_on();
|
||||
m_current_state = LED_STATE_OFF; /* no pattern running */
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*******************************************************************************
|
||||
* @file led_control.h
|
||||
* @brief LED direct control driver (Zephyr port)
|
||||
*
|
||||
* Green (P0.12) + Orange (P0.29) 2-color LED with k_timer based patterns
|
||||
******************************************************************************/
|
||||
#ifndef LED_CONTROL_H__
|
||||
#define LED_CONTROL_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/*==============================================================================
|
||||
* LED state enumeration
|
||||
*============================================================================*/
|
||||
typedef enum
|
||||
{
|
||||
LED_STATE_OFF = 0,
|
||||
LED_STATE_POWER_ON, /* 1: Green ON 2s -> stay on */
|
||||
LED_STATE_POWER_OFF, /* 2: Green ON 2s -> OFF */
|
||||
LED_STATE_ADVERTISING, /* 3: Green blink 500ms/500ms */
|
||||
LED_STATE_DETACH_WARNING, /* 4: Green 1s on / 3s off */
|
||||
LED_STATE_ALIGN_SEARCHING, /* 5: Orange blink 1s/1s */
|
||||
LED_STATE_ALIGN_COMPLETE, /* 6: Green 3s on / 1s off */
|
||||
LED_STATE_ERROR, /* 7: Orange 3Hz x3 / 1s off */
|
||||
LED_STATE_COUNT
|
||||
} led_state_t;
|
||||
|
||||
void led_init(void);
|
||||
void led_set_state(led_state_t state);
|
||||
led_state_t led_get_state(void);
|
||||
void led_ble_solid(void);
|
||||
|
||||
#endif /* LED_CONTROL_H__ */
|
||||
Reference in New Issue
Block a user