전원 + BLE + 배터리

This commit is contained in:
jhChun
2026-04-10 17:57:09 +09:00
parent 750f2d139e
commit 8dcf4adf31
22 changed files with 1791 additions and 175 deletions
+324 -32
View File
@@ -1,48 +1,340 @@
/*
* Copyright (c) 2016 Intel Corporation
/*******************************************************************************
* @file main.c
* @brief VesiScan BASIC - Zephyr 포팅 (Stage 1: GPIO + 전원 + LED + BLE)
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
* 부팅 시퀀스:
* Phase 1: 전원 유지 핀, GPIO, 타이머, LED 초기화
* Phase 2: BLE 스택 + NUS 초기화, advertising 시작
* Phase 3+: (이후 단계: 센서 등)
*
* 전원 버튼 상태머신 (5ms 폴링, k_timer):
* [OFF→ON] 버튼 2초 이상 유지 → P0.08 래치 ON + 녹색 LED 깜빡임
* 2초 미만 놓으면 전원 OFF (래치 안 됨)
* [ON→OFF] 버튼 2초 이상 유지 → sleep_mode_enter() → 전원 차단
******************************************************************************/
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/printk.h>
/* 1000 msec = 1 sec */
#define SLEEP_TIME_MS 1000
#include "main.h"
#include "debug_print.h"
#include "power_control.h"
#include "led_control.h"
#include "ble_service.h"
#include "battery_adc.h"
#include "parser.h"
/* The devicetree node identifier for the "led0" alias. */
#define LED0_NODE DT_ALIAS(led0)
LOG_MODULE_REGISTER(vesiscan, LOG_LEVEL_INF);
/*==============================================================================
* Devicetree GPOP Settings
*============================================================================*/
#define POWER_HOLD_NODE DT_NODELABEL(pwr_hold) // 전원 래치
#define POWER_BTN_NODE DT_NODELABEL(button_check) // 전원 버튼
static const struct gpio_dt_spec power_hold = GPIO_DT_SPEC_GET(POWER_HOLD_NODE, gpios);
static const struct gpio_dt_spec power_btn = GPIO_DT_SPEC_GET(POWER_BTN_NODE, gpios);
/*==============================================================================
* Timer
*============================================================================*/
static struct k_timer m_power_on_delay_timer; /* 전원 버튼 폴링용 (5ms 싱글샷) */
static struct k_timer m_power_off_delay_timer; /* 전원 OFF 지연용 (3초 후 전원 차단) */
/*==============================================================================
* 전역 변수
*============================================================================*/
volatile bool ble_connection_st = false; /* BLE 연결 상태 */
volatile bool processing = false; /* 센서 데이터 처리 중 플래그 */
bool bond_data_delete = true; /* 본딩 데이터 삭제 요청 */
uint32_t m_life_cycle = 0; /* 디바이스 수명 카운터 */
uint8_t m_reset_status = 1; /* 리셋 상태 코드 */
char SERIAL_NO[SERIAL_NO_LENGTH]; /* 시리얼 번호 */
char HW_NO[HW_NO_LENGTH]; /* 하드웨어 번호 */
char m_static_passkey[PASSKEY_LENGTH]; /* BLE 정적 패스키 */
static uint16_t cnt_s; /* 전원 버튼 폴링 카운터 (5ms 단위) */
static bool device_on = false; /* 디바이스 전원 상태 (래치 완료 여부) */
static bool boot_btn_released = false; /* 부팅 후 버튼 놓았는지 여부 */
/*
* A build error on this line means your board is unsupported.
* See the sample documentation for information on how to fix this.
* BLE advertising 제어용 워크 아이템
*
* BLE HCI 명령(bt_le_adv_start/stop 등)은 내부적으로 세마포어 대기가 필요하므로
* 인터럽트 컨텍스트(타이머 콜백 등)에서 직접 호출하면 커널 oops가 발생한다.
* (증상: "Controller unresponsive, command opcode 0x2006 timeout")
*
* k_work를 사용하면 시스템 워크큐 스레드에서 실행되므로 안전하게 BLE API 호출 가능.
* main_s() 타이머 콜백에서 k_work_submit()으로 예약하여 사용한다.
*/
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
static struct k_work adv_start_work;
static struct k_work adv_stop_work;
/* 시스템 워크큐에서 BLE advertising 시작 */
static void adv_start_work_handler(struct k_work *work)
{
ARG_UNUSED(work);
ble_advertising_start();
}
/* 시스템 워크큐에서 BLE advertising 중지 */
static void adv_stop_work_handler(struct k_work *work)
{
ARG_UNUSED(work);
ble_advertising_stop();
}
/*==============================================================================
* BLE RX 콜백
*============================================================================*/
static void ble_rx_handler(const uint8_t *data, uint16_t len)
{
DBG_PRINTF("[BLE RX] %d bytes: ", len);
for (uint16_t i = 0; i < len && i < 32; i++)
{
DBG_PRINTF("%02X ", data[i]);
}
if (len > 32)
{
DBG_PRINTF("...");
}
DBG_PRINTF("\r\n");
dr_parser(data, len);
}
/*==============================================================================
* 전원 유지 (POWER_HOLD) 제어
*============================================================================*/
/* 전원 유지 핀(P0.08) 초기화 - 아직 래치하지 않음
* 버튼을 물리적으로 누르고 있는 동안만 전원 유지됨
* 2초 후 main_s()에서 래치(HIGH) 설정 */
static void power_hold_init(void)
{
gpio_pin_configure_dt(&power_hold, GPIO_OUTPUT_INACTIVE);
}
/* 전원 ON/OFF 제어 - P0.08 핀으로 물리적 전원 래치/해제 */
static void power_control_handler(on_off_cont_t device_power_st)
{
if (device_power_st == OFF) {
gpio_pin_set_dt(&power_hold, 0); /* P0.08 LOW → 전원 래치 해제 → 전원 차단 */
DBG_PRINTF("[PWR] OFF\r\n");
} else {
gpio_pin_set_dt(&power_hold, 1); /* P0.08 HIGH → 전원 유지 */
DBG_PRINTF("[PWR] ON\r\n");
}
}
/*==============================================================================
* GPIO 초기화
*============================================================================*/
static void gpio_init(void)
{
gpio_pin_configure_dt(&power_btn, GPIO_INPUT); /* 전원 버튼(P1.08) 입력 설정 */
DBG_PRINTF("[GPIO] OK (BTN=%d)\r\n", gpio_pin_get_dt(&power_btn));
}
/*==============================================================================
* 기본 설정값 로드
*============================================================================*/
static void load_default_config(void)
{
memset(SERIAL_NO, 0, SERIAL_NO_LENGTH);
memcpy(SERIAL_NO, SERIAL_NUMBER, strlen(SERIAL_NUMBER));
memset(m_static_passkey, 0, PASSKEY_LENGTH);
memcpy(m_static_passkey, DEFAULT_PASSKEY, PASSKEY_LENGTH);
m_reset_status = 1;
bond_data_delete = true;
DBG_PRINTF("[CFG] Default (S/N=%s)\r\n", SERIAL_NO);
}
/*==============================================================================
* 전원 OFF 타임아웃 콜백
* LED 표시 후 3초(POWER_OFF_DELAY) 경과 시 호출되어 물리적 전원 차단
*============================================================================*/
static void t_power_off_timeout_handler(struct k_timer *timer)
{
ARG_UNUSED(timer);
DBG_PRINTF("[PWR] Off timeout\r\n");
led_set_state(LED_STATE_OFF);
power_control_handler(OFF);
}
/*==============================================================================
* 슬립 모드 / 전원 OFF
* LED로 사용자에게 알린 후 3초 뒤 전원 차단
*============================================================================*/
void sleep_mode_enter(void)
{
led_set_state(LED_STATE_POWER_ON);
DBG_PRINTF("[SYS] Sleep\r\n");
k_timer_start(&m_power_off_delay_timer, K_MSEC(POWER_OFF_DELAY), K_NO_WAIT);
}
void device_power_off(void)
{
led_set_state(LED_STATE_POWER_OFF);
k_timer_start(&m_power_off_delay_timer, K_MSEC(POWER_OFF_DELAY), K_NO_WAIT);
}
/*==============================================================================
* 전원 버튼 상태머신 (5ms 폴링)
*
* [부팅 시퀀스] (device_on == false)
* 1. 버튼 누름 → 전원 공급 → MCU 부팅 → 5ms마다 폴링 시작
* 2. cnt_s < 400 (2초 미만)에서 놓으면 → 전원 OFF (래치 안 됨)
* 3. cnt_s == 400 (2초) 도달 → P0.08 래치 ON + 녹색 LED 깜빡임
* 4. 버튼 놓으면 → 깜빡임 유지, 폴링 계속
*
* [전원 OFF 시퀀스] (device_on == true)
* 1. 버튼 2초 이상 누름 → sleep_mode_enter() → 전원 차단
* 2. 버튼 2초 미만 놓으면 → 카운터 리셋, 무시
*============================================================================*/
#define BOOT_THRESHOLD 400 /* 5ms x 400 = 2초 */
static void main_s(struct k_timer *timer)
{
ARG_UNUSED(timer);
bool button_pressed = (gpio_pin_get_dt(&power_btn) == 1);
if (!device_on) /* ── 부팅 시퀀스 (OFF → ON) ── */
{
if (!button_pressed) /* 버튼 놓음 → 2초 미만이면 전원 OFF */
{
DBG_PRINTF("[BTN] Short press (%d) -> OFF\r\n", cnt_s);
power_control_handler(OFF);
cnt_s = 0;
}
else /* 버튼 계속 누르고 있음 */
{
cnt_s++;
if (cnt_s == BOOT_THRESHOLD) /* 2초 도달: 래치 + 부팅 완료 */
{
device_on = true;
cnt_s = 0; /* 카운터 리셋: 안 하면 다음 틱에서 ON→OFF 분기가
* cnt_s >= 400 조건을 즉시 만족하여 전원 OFF됨 */
power_control_handler(ON);
led_set_state(LED_STATE_ADVERTISING);
k_work_submit(&adv_start_work);
battery_timer_start();
m_reset_status = 1;
DBG_PRINTF("[BTN] 2s -> Power latched, LED blink\r\n");
DBG_PRINTF("[BOOT] Complete, device ON\r\n");
DBG_PRINTF("[DEV] device_on=%d\r\n", device_on);
}
}
}
else /* ── 전원 OFF 시퀀스 (ON → OFF) ── */
{
if (!boot_btn_released) /* 부팅 시 눌렀던 버튼을 아직 안 놓음 → 대기 */
{
if (!button_pressed)
{
boot_btn_released = true;
DBG_PRINTF("[BTN] Boot button released\r\n");
}
}
else if (button_pressed) /* 버튼 새로 누르고 있음 */
{
cnt_s++;
if (cnt_s >= BOOT_THRESHOLD) /* 2초 이상 → 전원 OFF */
{
DBG_PRINTF("[BTN] 2s long press -> Power OFF\r\n");
battery_timer_stop();
k_work_submit(&adv_stop_work);
device_on = false;
boot_btn_released = false;
cnt_s = 0;
sleep_mode_enter();
return;
}
}
else /* 버튼 놓음 → 카운터 리셋 */
{
if (cnt_s > 0)
{
DBG_PRINTF("[BTN] Short press (%d) -> ignored\r\n", cnt_s);
}
cnt_s = 0;
}
}
k_timer_start(&m_power_on_delay_timer, K_MSEC(POWER_ON_DELAY), K_NO_WAIT);
}
/*==============================================================================
* 타이머 초기화 / 시작
*============================================================================*/
static void timers_init(void)
{
k_timer_init(&m_power_on_delay_timer, main_s, NULL);
k_timer_init(&m_power_off_delay_timer, t_power_off_timeout_handler, NULL);
k_work_init(&adv_start_work, adv_start_work_handler);
k_work_init(&adv_stop_work, adv_stop_work_handler);
power_timer_init();
}
/* 전원 버튼 폴링 시작 (5ms 후 main_s 콜백) */
static void timers_start(void)
{
k_timer_start(&m_power_on_delay_timer, K_MSEC(POWER_ON_DELAY), K_NO_WAIT);
}
/*==============================================================================
* 메인 함수
*============================================================================*/
int main(void)
{
int ret;
bool led_state = true;
/*── Phase 1: 하드웨어 기본 초기화 ──*/
power_hold_init();
cnt_s = 0;
if (!gpio_is_ready_dt(&led)) {
return 0;
}
DBG_PRINTF("\r\n========================================\r\n");
DBG_PRINTF(" VesiScan BASIC %s (Zephyr)\r\n", FIRMWARE_VERSION);
DBG_PRINTF("========================================\r\n");
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
return 0;
}
DBG_PRINTF("[1] HW Init\r\n");
gpio_init();
timers_init();
load_default_config();
led_init();
battery_adc_init();
battery_timer_init();
DBG_PRINTF(" gpio/timer/config/led/batt OK\r\n");
while (1) {
ret = gpio_pin_toggle_dt(&led);
if (ret < 0) {
return 0;
}
/*── Phase 2: BLE 스택 + NUS ──*/
DBG_PRINTF("[2] BLE Init\r\n");
if (ble_service_init(ble_rx_handler) == 0)
{
DBG_PRINTF(" ble/nus OK\r\n");
}
else
{
DBG_PRINTF(" ble FAIL\r\n");
}
led_state = !led_state;
printf("LED state: %s\n", led_state ? "ON" : "OFF");
k_msleep(SLEEP_TIME_MS);
}
return 0;
/*── Phase 3: FDS/NVS (TODO) ──*/
/*── Phase 4: 애플리케이션 (TODO) ──*/
DBG_PRINTF("\r\n========================================\r\n");
DBG_PRINTF(" READY [%s]\r\n", SERIAL_NO);
DBG_PRINTF("========================================\r\n\r\n");
/* 전원 버튼 상태머신 시작 (부팅 시 버튼이 눌려있는 상태) */
timers_start();
/* 메인 루프 - idle */
for (;;) {
k_msleep(100);
}
return 0;
}