전원 + 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
+16 -2
View File
@@ -2,6 +2,20 @@
cmake_minimum_required(VERSION 3.20.0) cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(blinky) project(vesiscan)
target_sources(app PRIVATE src/main.c) target_include_directories(app PRIVATE
src
src/ble
src/drivers/battery
src/drivers/led
)
target_sources(app PRIVATE
src/main.c
src/parser.c
src/power_control.c
src/ble/ble_service.c
src/drivers/battery/battery_adc.c
src/drivers/led/led_control.c
)
+147
View File
@@ -0,0 +1,147 @@
# VesiScan BASIC - Zephyr (nRF Connect SDK)
nRF52840 기반 VesiScan BASIC 펌웨어의 Zephyr 포팅 프로젝트입니다.
## 현재 구현 상태
| Stage | 내용 | 상태 |
|-------|------|------|
| 1 | GPIO + 전원 래치 + 버튼 상태머신 | 완료 |
| 1 | LED 제어 (green/orange 패턴) | 완료 |
| 2 | BLE 스택 + NUS (advertising, 연결, 재연결) | 완료 |
| 3 | 배터리 ADC (AIN2, 12bit, 저전압 자동 OFF) | 진행중 |
| 3 | 커맨드 파서 (msn? 구현) | 진행중 |
| - | Flash 저장소 (NVS) | 미구현 |
| - | 온도 센서 (TMP235, AIN3) | 미구현 |
| - | IMU (ICM42670P, I2C) | 미구현 |
| - | 피에조 + ADC121S051 | 미구현 |
| - | BLE 보안 (본딩, 패스키) | 미구현 |
## 프로젝트 구조
```
blinky/
├── CMakeLists.txt # 빌드 설정 (소스 파일 등록)
├── prj.conf # Kconfig 설정 (기능 ON/OFF)
├── boards/
│ └── nrf52840dk_nrf52840.overlay # Devicetree Overlay (핀 매핑)
└── src/
├── main.c # 메인 진입점 + 부팅 시퀀스 + 버튼 상태머신
├── main.h # 전역 상수/변수/타입 정의
├── debug_print.h # 디버그 출력 매크로 (RTT)
├── parser.c / .h # BLE 커맨드 파서 (TAG+DATA+CRC16)
├── power_control.c / .h # 전원 시퀀스 상태머신
├── ble/
│ └── ble_service.c / .h # BLE NUS 서비스 (advertising, 연결, TX/RX)
└── drivers/
├── battery/
│ └── battery_adc.c / .h # 배터리 전압 ADC (AIN2, 12bit)
└── led/
└── led_control.c / .h # LED 패턴 제어 (k_timer 기반)
```
## 빌드 방법
### 일반 빌드
소스 파일(.c/.h)만 변경한 경우.
### Pristine Build
아래 파일을 변경한 경우 반드시 Pristine Build:
- `prj.conf` (Kconfig 설정)
- `boards/*.overlay` (Devicetree 핀 매핑)
- `CMakeLists.txt` (소스/include 경로 추가)
nRF Connect for VS Code → Build 패널 → **Pristine Build** 또는 `build` 폴더 삭제 후 빌드.
## 핀 매핑 (Devicetree Overlay)
| 이름 | 핀 | 방향 | 역할 |
|------|-----|------|------|
| `PWR_HOLD` | P0.08 | Output, Active HIGH | 전원 래치 |
| `BUTTON_CHECK` | P1.08 | Input, Pull-up, Active LOW | 전원 버튼 |
| `LED_BLE` | P0.12 | Output, Active LOW | 초록 LED |
| `FUNCTION_LED` | P0.29 | Output, Active LOW | 주황 LED |
| ADC AIN2 | P0.04 | Analog Input | 배터리 전압 분압 |
> UART0은 P0.08 충돌 방지를 위해 비활성화 (`status = "disabled"`)
## 전원 버튼 동작
| 동작 | 조건 | 결과 |
|------|------|------|
| 부팅 (OFF→ON) | 2초 이상 홀드 | 전원 래치 + LED 깜빡임 + BLE advertising |
| 무시 | 부팅 후 2초 미만 릴리스 | 전원 OFF (래치 안 됨) |
| 전원 OFF (ON→OFF) | 버튼 놓은 뒤 다시 2초 홀드 | advertising 중지 + LED OFF + 3초 후 전원 차단 |
## BLE 커맨드 프로토콜
### 패킷 포맷
```
[TAG 4바이트] [DATA N바이트] [CRC16 2바이트]
```
- TAG: ASCII 4글자 (예: `msn?`, `mfv?`)
- DATA: uint16 little-endian 값들
- CRC16: CRC-CCITT (초기값 0xFFFF)
- 응답 TAG: 첫 글자 `m``r` (예: `msn?``rsn:`)
### 구현된 커맨드
| 커맨드 | 응답 | 기능 |
|--------|------|------|
| `msn?` | `rsn:` + uint16 mV | 배터리 전압 측정 |
### 전체 커맨드 목록 (구현 예정)
| # | 내용 | 명령 | 응답 | 비고 |
|---|------|------|------|------|
| 1 | 전원 OFF | `msq?` | `rsq:` | |
| 2 | 재부팅 | `mss?` | `rss:` | |
| 3 | 본딩 삭제 + 재부팅 | `msr?` | `rsr:` | |
| 4 | LED 상태 | `mls?` | - | |
| 5 | HW Version 읽기 | `mrh?` | `rrh:` | FDS |
| 6 | HW Version 쓰기 | `mwh?` | `rwh:` | FDS |
| 7 | Serial Number 읽기 | `mrs?` | `rrs:` | FDS |
| 8 | Serial Number 쓰기 | `mws?` | `rws:` | FDS |
| 9 | FW Version 읽기 | `mfv?` | `rfv:` | |
| 10 | Passkey 읽기 | `mqz?` | `rqz:` | FDS |
| 11 | Passkey 쓰기 | `mpz?` | `rpz:` | FDS |
| 12 | 배터리 측정 | `msn?` | `rsn:` | 구현 완료 |
| 13 | IMU 단발 측정 | `msp?` | `rsp:` | |
| 14 | 온도 측정 | `mso?` | `rso:` | |
| 15 | 단일 채널 측정 | `mec?` | `reb:``raa:` | 테스트용 |
| 16 | 모든 채널(6) 측정 | `maa?` | `reb:`(6개) → `raa:` | |
| 17 | 전체 측정 | `mbb?` | `rbb:``reb:`(6개) → `raa:` | 배터리+IMU+온도+피에조 |
| 18 | 측정 파라미터 읽기 | `mcf?` | `rcf:` | FDS |
| 19 | 측정 파라미터 쓰기 | `mcs?` | `rcs:` | FDS |
## 디버그 로그 (RTT)
- **VS Code**: nRF Connect 패널 → Connected Devices → 보드 선택 → 터미널 아이콘
- **J-Link RTT Viewer**: Connection: USB, Target Device: NRF52840_XXAA
### 로그 태그
| 태그 | 내용 |
|------|------|
| `[BTN]` | 버튼 이벤트 (2s 홀드, 릴리스) |
| `[PWR]` | 전원 래치 ON/OFF |
| `[BOOT]` | 부팅 시퀀스 완료 |
| `[BLE]` | BLE 연결/해제/advertising/파라미터 |
| `[BLE RX]` | NUS 수신 데이터 (hex dump) |
| `[NUS TX]` | NUS 송신 |
| `[CMD]` | 커맨드 파서 처리 결과 |
| `[BATT]` | 배터리 ADC 측정값 |
| `[SYS]` | 슬립 진입 |
## prj.conf 주요 설정
| 설정 | 값 | 역할 |
|------|-----|------|
| `CONFIG_GPIO` | y | GPIO 드라이버 |
| `CONFIG_LOG` | y | 로깅 시스템 |
| `CONFIG_LOG_BACKEND_RTT` | y | RTT 로그 출력 |
| `CONFIG_SERIAL` | n | UART 비활성화 (P0.08 충돌 방지) |
| `CONFIG_BT` | y | BLE 스택 |
| `CONFIG_BT_NUS` | y | Nordic UART Service |
| `CONFIG_BT_L2CAP_TX_MTU` | 247 | MTU 크기 |
| `CONFIG_BT_CTLR_TX_PWR_PLUS_8` | y | TX power +8dBm |
| `CONFIG_ADC` | y | ADC 드라이버 (배터리, 온도) |
-97
View File
@@ -1,97 +0,0 @@
.. zephyr:code-sample:: blinky
:name: Blinky
:relevant-api: gpio_interface
Blink an LED forever using the GPIO API.
Overview
********
The Blinky sample blinks an LED forever using the :ref:`GPIO API <gpio_api>`.
The source code shows how to:
#. Get a pin specification from the :ref:`devicetree <dt-guide>` as a
:c:struct:`gpio_dt_spec`
#. Configure the GPIO pin as an output
#. Toggle the pin forever
See :zephyr:code-sample:`pwm-blinky` for a similar sample that uses the PWM API instead.
.. _blinky-sample-requirements:
Requirements
************
Your board must:
#. Have an LED connected via a GPIO pin (these are called "User LEDs" on many of
Zephyr's :ref:`boards`).
#. Have the LED configured using the ``led0`` devicetree alias.
Building and Running
********************
Build and flash Blinky as follows, changing ``reel_board`` for your board:
.. zephyr-app-commands::
:zephyr-app: samples/basic/blinky
:board: reel_board
:goals: build flash
:compact:
After flashing, the LED starts to blink and messages with the current LED state
are printed on the console. If a runtime error occurs, the sample exits without
printing to the console.
Build errors
************
You will see a build error at the source code line defining the ``struct
gpio_dt_spec led`` variable if you try to build Blinky for an unsupported
board.
On GCC-based toolchains, the error looks like this:
.. code-block:: none
error: '__device_dts_ord_DT_N_ALIAS_led_P_gpios_IDX_0_PH_ORD' undeclared here (not in a function)
Adding board support
********************
To add support for your board, add something like this to your devicetree:
.. code-block:: DTS
/ {
aliases {
led0 = &myled0;
};
leds {
compatible = "gpio-leds";
myled0: led_0 {
gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
};
};
};
The above sets your board's ``led0`` alias to use pin 13 on GPIO controller
``gpio0``. The pin flags :c:macro:`GPIO_ACTIVE_HIGH` mean the LED is on when
the pin is set to its high state, and off when the pin is in its low state.
Tips:
- See :dtcompatible:`gpio-leds` for more information on defining GPIO-based LEDs
in devicetree.
- If you're not sure what to do, check the devicetrees for supported boards which
use the same SoC as your target. See :ref:`get-devicetree-outputs` for details.
- See :zephyr_file:`include/zephyr/dt-bindings/gpio/gpio.h` for the flags you can use
in devicetree.
- If the LED is built in to your board hardware, the alias should be defined in
your :ref:`BOARD.dts file <devicetree-in-out-files>`. Otherwise, you can
define one in a :ref:`devicetree overlay <set-devicetree-overlays>`.
+90
View File
@@ -0,0 +1,90 @@
/*
* VesiScan BASIC - Custom board overlay
* 프로젝트에서 사용하는 하드웨어 핀 정의
*
* Pin mapping:
* POWER_HOLD: P0.08 (output, active high - latches power)
* POWER_BTN: P1.08 (input, active low - pressed = LOW)
* LED_BLE: P0.12 (output, active low)
* LED_ORANGE: P0.29 (output, active low)
* BATT_ADC: P0.04 (AIN2, battery voltage measurement)
*/
#include <zephyr/dt-bindings/adc/adc.h>
#include <zephyr/dt-bindings/adc/nrf-saadc.h>
/* P0.08이 DK 기본 UART0 RX로 잡혀있어서 GPIO 출력 충돌 → 비활성화 */
&uart0
{
status = "disabled";
};
/* 배터리 ADC 채널 설정 (AIN2 = P0.04) */
&adc
{
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
/* 배터리 전압 측정 채널
* 1/6 gain + 0.6V 내부 레퍼런스 → 풀스케일 3.6V
* 12bit 해상도, 4X 오버샘플링, 40us 샘플링 */
channel@2
{
reg = <2>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 40)>;
zephyr,input-positive = <NRF_SAADC_AIN2>;
zephyr,resolution = <12>;
zephyr,oversampling = <2>; /* 2^2 = 4X */
};
};
/* P0.04(AIN2)가 DK Arduino 헤더에 GPIO로 잡혀있어서 SAADC 충돌 → 비활성화 */
&arduino_adc
{
status = "disabled";
};
/ {
/* ADC 채널을 코드에서 참조하기 위한 zephyr,user 노드 */
zephyr,user
{
io-channels = <&adc 2>;
};
led
{
compatible = "gpio-leds";
LED_BLE: LED_BLE
{
gpios = <&gpio0 12 GPIO_ACTIVE_LOW>;
label = "Green LED";
};
FUNCTION_LED: FUNCTION_LED
{
gpios = <&gpio0 29 GPIO_ACTIVE_LOW>;
label = "Orange LED";
};
};
pin
{
compatible = "gpio-keys";
PWR_HOLD: PWR_HOLD
{
gpios = <&gpio0 8 GPIO_ACTIVE_HIGH>;
label = "Power Hold Latch";
};
BUTTON_CHECK: BUTTON_CHECK
{
gpios = <&gpio1 8 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
label = "Power Button";
};
};
};
-23
View File
@@ -1,23 +0,0 @@
/* SPDX-License-Identifier: Apache-2.0 */
/ {
aliases {
led0 = &led0;
};
leds {
compatible = "gpio-leds";
led0: led_0 {
gpios = <&gpio9 0 GPIO_ACTIVE_HIGH>;
label = "Green LED 0";
};
};
};
&gpiote130 {
status = "okay";
};
&gpio9 {
status = "okay";
};
@@ -1,9 +0,0 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
&led0 {
gpios = <&hpf_gpio 9 GPIO_ACTIVE_HIGH>;
};
Binary file not shown.
+51
View File
@@ -1 +1,52 @@
# VesiScan BASIC - Zephyr configuration
# Stage 1: GPIO + Power Control + LED + Debug Logging
# Stage 2: BLE (NUS)
CONFIG_GPIO=y CONFIG_GPIO=y
# Logging via RTT
CONFIG_LOG=y
CONFIG_LOG_MODE_DEFERRED=y
CONFIG_LOG_BACKEND_RTT=y
CONFIG_LOG_BACKEND_UART=n
CONFIG_USE_SEGGER_RTT=y
CONFIG_PRINTK=y
CONFIG_CONSOLE=y
CONFIG_RTT_CONSOLE=y
CONFIG_SERIAL=n
CONFIG_UART_CONSOLE=n
# BLE stack
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_DEVICE_NAME="VB026030000"
CONFIG_BT_DEVICE_NAME_DYNAMIC=y
CONFIG_BT_DEVICE_NAME_MAX=16
# BLE NUS (Nordic UART Service)
CONFIG_BT_NUS=y
# GATT / MTU
CONFIG_BT_GATT_CLIENT=y
CONFIG_BT_L2CAP_TX_MTU=247
CONFIG_BT_BUF_ACL_TX_SIZE=251
CONFIG_BT_BUF_ACL_RX_SIZE=251
# Connection parameters
CONFIG_BT_PERIPHERAL_PREF_MIN_INT=24
CONFIG_BT_PERIPHERAL_PREF_MAX_INT=24
CONFIG_BT_PERIPHERAL_PREF_LATENCY=0
CONFIG_BT_PERIPHERAL_PREF_TIMEOUT=400
# TX power
CONFIG_BT_CTLR_TX_PWR_PLUS_8=y
# Security (dev mode - disabled for now)
CONFIG_BT_SMP=n
# ADC (battery, temperature)
CONFIG_ADC=y
# System
CONFIG_HEAP_MEM_POOL_SIZE=2048
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
-12
View File
@@ -1,12 +0,0 @@
sample:
name: Blinky Sample
tests:
sample.basic.blinky:
tags:
- LED
- gpio
filter: dt_enabled_alias_with_parent_compat("led0", "gpio-leds")
depends_on: gpio
harness: led
integration_platforms:
- frdm_k64f
+229
View File
@@ -0,0 +1,229 @@
/*******************************************************************************
* @file ble_service.c
* @brief BLE NUS service module (Zephyr port)
*
* Original VesiScan-Basic BLE implementation ported to Zephyr/NCS:
* - NUS (Nordic UART Service) for data exchange
* - Advertising: 40ms interval, 3-min timeout
* - Connection: 30ms interval, 0 latency, 4s supervision timeout
* - TX power +8 dBm, 2M PHY preferred
* - Dev mode: no security / Production mode: bonding + passkey (TODO)
******************************************************************************/
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include <bluetooth/services/nus.h>
#include "ble_service.h"
#include "main.h"
#include "debug_print.h"
#include "led_control.h"
LOG_MODULE_REGISTER(ble_svc, LOG_LEVEL_INF);
/*==============================================================================
* Module variables
*============================================================================*/
static struct bt_conn *current_conn;
static ble_data_rx_cb_t rx_callback;
/* BLE API는 ISR/콜백에서 직접 호출하면 안전하지 않을 수 있으므로 k_work 사용 */
static struct k_work adv_restart_work;
static void adv_restart_handler(struct k_work *work)
{
ARG_UNUSED(work);
ble_advertising_start();
led_set_state(LED_STATE_ADVERTISING);
}
/*==============================================================================
* Advertising data
*============================================================================*/
static const struct bt_data ad[] =
{
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA(BT_DATA_NAME_COMPLETE, SERIAL_NUMBER, sizeof(SERIAL_NUMBER) - 1),
};
static const struct bt_data sd[] =
{
BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_NUS_VAL),
};
/*==============================================================================
* Connection callbacks
*============================================================================*/
static void connected(struct bt_conn *conn, uint8_t err)
{
if (err)
{
DBG_PRINTF("[BLE] Connect failed (err %d) -> re-adv\r\n", err);
k_work_submit(&adv_restart_work);
return;
}
current_conn = bt_conn_ref(conn);
ble_connection_st = true;
/* 연결 정보 출력 */
char addr_str[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr_str, sizeof(addr_str));
DBG_PRINTF("[BLE] Peer: %s\r\n", addr_str);
/* Connection parameters (30ms interval) */
struct bt_le_conn_param conn_param =
{
.interval_min = BLE_MIN_CONN_INTERVAL,
.interval_max = BLE_MAX_CONN_INTERVAL,
.latency = BLE_SLAVE_LATENCY,
.timeout = BLE_CONN_SUP_TIMEOUT,
};
bt_conn_le_param_update(conn, &conn_param);
led_set_state(LED_STATE_OFF);
DBG_PRINTF("[BLE] Connected\r\n");
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
if (current_conn)
{
bt_conn_unref(current_conn);
current_conn = NULL;
}
ble_connection_st = false;
DBG_PRINTF("[BLE] Disconnected (reason 0x%02x)\r\n", reason);
/* 워크큐에서 advertising 재시작 */
k_work_submit(&adv_restart_work);
}
static void le_param_updated(struct bt_conn *conn, uint16_t interval,
uint16_t latency, uint16_t timeout)
{
DBG_PRINTF("[BLE] Params: interval=%d latency=%d timeout=%d\r\n",
interval, latency, timeout);
}
BT_CONN_CB_DEFINE(conn_callbacks) =
{
.connected = connected,
.disconnected = disconnected,
.le_param_updated = le_param_updated,
};
/*==============================================================================
* NUS callbacks
*============================================================================*/
static void nus_received(struct bt_conn *conn, const uint8_t *data, uint16_t len)
{
if (rx_callback)
{
rx_callback(data, len);
}
}
static void nus_sent(struct bt_conn *conn)
{
DBG_PRINTF("[NUS TX] complete\r\n");
}
static struct bt_nus_cb nus_cb =
{
.received = nus_received,
.sent = nus_sent,
};
/*==============================================================================
* Public functions
*============================================================================*/
int ble_service_init(ble_data_rx_cb_t rx_cb)
{
int err;
rx_callback = rx_cb;
k_work_init(&adv_restart_work, adv_restart_handler);
/* Enable BLE stack */
err = bt_enable(NULL);
if (err)
{
DBG_PRINTF("[BLE] bt_enable failed (err %d)\r\n", err);
return err;
}
DBG_PRINTF("[BLE] Stack enabled\r\n");
/* Initialize NUS */
err = bt_nus_init(&nus_cb);
if (err)
{
DBG_PRINTF("[BLE] NUS init failed (err %d)\r\n", err);
return err;
}
DBG_PRINTF("[BLE] NUS initialized\r\n");
return 0;
}
int ble_advertising_start(void)
{
int err;
struct bt_le_adv_param adv_param = BT_LE_ADV_PARAM_INIT(
BT_LE_ADV_OPT_CONN,
APP_ADV_INTERVAL, /* min interval */
APP_ADV_INTERVAL + 16, /* max interval (slight window) */
NULL /* undirected */
);
err = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
if (err)
{
DBG_PRINTF("[BLE] Adv start failed (err %d)\r\n", err);
return err;
}
DBG_PRINTF("[BLE] Advertising started\r\n");
return 0;
}
int ble_advertising_stop(void)
{
int err = bt_le_adv_stop();
if (err)
{
DBG_PRINTF("[BLE] Adv stop failed (err %d)\r\n", err);
return err;
}
DBG_PRINTF("[BLE] Advertising stopped\r\n");
return 0;
}
int ble_data_send(const uint8_t *data, uint16_t len)
{
if (!current_conn)
{
DBG_PRINTF("[NUS TX] not connected\r\n");
return -ENOTCONN;
}
DBG_PRINTF("[NUS TX] %d bytes\r\n", len);
int err = bt_nus_send(current_conn, data, len);
if (err)
{
DBG_PRINTF("[NUS TX] failed (err %d)\r\n", err);
}
return err;
}
bool ble_is_connected(void)
{
return (current_conn != NULL);
}
+44
View File
@@ -0,0 +1,44 @@
/*******************************************************************************
* @file ble_service.h
* @brief BLE NUS service module (Zephyr port)
*
* Nordic UART Service (NUS) based BLE communication.
* Original: SoftDevice S140 + ble_nus SDK module
* Ported: Zephyr BLE + NCS bt_nus
******************************************************************************/
#ifndef BLE_SERVICE_H__
#define BLE_SERVICE_H__
#include <stdint.h>
#include <stdbool.h>
/*==============================================================================
* BLE configuration
*============================================================================*/
#define BLE_DEV_MODE 1 /* 1: Dev (no security), 0: Production */
#define APP_ADV_INTERVAL 64 /* 64 x 0.625ms = 40ms */
#define APP_ADV_DURATION 18000 /* 18000 x 10ms = 180s (3 min) */
#define BLE_MIN_CONN_INTERVAL 24 /* 30ms / 1.25ms = 24 */
#define BLE_MAX_CONN_INTERVAL 24 /* 30ms / 1.25ms = 24 */
#define BLE_SLAVE_LATENCY 0
#define BLE_CONN_SUP_TIMEOUT 400 /* 4000ms / 10ms = 400 */
#define BLE_TX_POWER 8 /* +8 dBm */
/*==============================================================================
* Callback types
*============================================================================*/
typedef void (*ble_data_rx_cb_t)(const uint8_t *data, uint16_t len);
/*==============================================================================
* Public functions
*============================================================================*/
int ble_service_init(ble_data_rx_cb_t rx_cb);
int ble_advertising_start(void);
int ble_advertising_stop(void);
int ble_data_send(const uint8_t *data, uint16_t len);
bool ble_is_connected(void);
#endif /* BLE_SERVICE_H__ */
+23
View File
@@ -0,0 +1,23 @@
/*******************************************************************************
* @file debug_print.h
* @brief Debug output macros (Zephyr LOG backend)
******************************************************************************/
#ifndef DEBUG_PRINT_H
#define DEBUG_PRINT_H
#include <zephyr/logging/log.h>
/*
* DBG_PRINTF maps to printk for RTT/UART console output.
* For module-level logging use LOG_INF/LOG_DBG etc.
*/
#define ENABLE_PRINTF 1
#if ENABLE_PRINTF
#include <zephyr/sys/printk.h>
#define DBG_PRINTF(...) printk(__VA_ARGS__)
#else
#define DBG_PRINTF(...)
#endif
#endif /* DEBUG_PRINT_H */
+196
View File
@@ -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);
}
+28
View File
@@ -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__ */
+246
View File
@@ -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 */
}
+34
View File
@@ -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__ */
+322 -30
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 * 퀀:
*/ * Phase 1: , GPIO, , LED
* Phase 2: BLE + NUS , advertising
#include <stdio.h> * Phase 3+: ( : )
*
* (5ms , k_timer):
* [OFFON] 2 P0.08 ON + LED
* 2 OFF ( )
* [ONOFF] 2 sleep_mode_enter()
******************************************************************************/
#include <zephyr/kernel.h> #include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h> #include <zephyr/drivers/gpio.h>
#include <zephyr/sys/printk.h>
/* 1000 msec = 1 sec */ #include "main.h"
#define SLEEP_TIME_MS 1000 #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. */ LOG_MODULE_REGISTER(vesiscan, LOG_LEVEL_INF);
#define LED0_NODE DT_ALIAS(led0)
/*==============================================================================
* 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. * BLE advertising
* See the sample documentation for information on how to fix this. *
* 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 main(void)
{ {
int ret; /*── Phase 1: 하드웨어 기본 초기화 ──*/
bool led_state = true; power_hold_init();
cnt_s = 0;
if (!gpio_is_ready_dt(&led)) { DBG_PRINTF("\r\n========================================\r\n");
return 0; DBG_PRINTF(" VesiScan BASIC %s (Zephyr)\r\n", FIRMWARE_VERSION);
DBG_PRINTF("========================================\r\n");
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");
/*── 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");
} }
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE); /*── Phase 3: FDS/NVS (TODO) ──*/
if (ret < 0) { /*── Phase 4: 애플리케이션 (TODO) ──*/
return 0;
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);
} }
while (1) {
ret = gpio_pin_toggle_dt(&led);
if (ret < 0) {
return 0;
}
led_state = !led_state;
printf("LED state: %s\n", led_state ? "ON" : "OFF");
k_msleep(SLEEP_TIME_MS);
}
return 0; return 0;
} }
+77
View File
@@ -0,0 +1,77 @@
/*******************************************************************************
* @file main.h
* @brief VesiScan BASIC - Zephyr port main header
******************************************************************************/
#ifndef MAIN_H__
#define MAIN_H__
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>
/*==============================================================================
* Firmware identification
*============================================================================*/
#define FIRMWARE_VERSION "VBTFW0102"
#define HARDWARE_VERSION "VB0HW0000"
#define SERIAL_NUMBER "VB026030000"
#define DEFAULT_PASSKEY "123456"
/*==============================================================================
* Data length constants
*============================================================================*/
#define SERIAL_NO_LENGTH 12
#define HW_NO_LENGTH 12
#define PASSKEY_LENGTH 6
/*==============================================================================
* Enumerations
*============================================================================*/
typedef enum
{
OFF = 0,
ON = 1
} on_off_cont_t;
typedef enum
{
CMD_BLE = 0,
CMD_UART = 1
} which_cmd_t;
typedef enum
{
BLE_DISCONNECTED_ST = 0,
BLE_CONNECTED_ST = 1
} ble_status_t;
/*==============================================================================
* Timing constants
*============================================================================*/
#define POWER_ON_DELAY 5 /* Power button poll interval (ms) */
#define POWER_OFF_DELAY 3000 /* LED display before power off (ms) */
/*==============================================================================
* Function declarations
*============================================================================*/
void sleep_mode_enter(void);
void device_power_off(void);
/*==============================================================================
* Global variables (extern)
*============================================================================*/
extern volatile bool ble_connection_st;
extern volatile bool processing;
extern char SERIAL_NO[SERIAL_NO_LENGTH];
extern char HW_NO[HW_NO_LENGTH];
extern char m_static_passkey[PASSKEY_LENGTH];
extern bool bond_data_delete;
extern uint32_t m_life_cycle;
extern uint8_t m_reset_status;
#endif /* MAIN_H__ */
+171
View File
@@ -0,0 +1,171 @@
/*******************************************************************************
* @file parser.c
* @brief BLE command parser (Zephyr port)
*
* : pc_firm/parser.c
* : [TAG 4B] [DATA NB] [CRC16 2B]
*
* : msn? ( ), mls? (LED )
******************************************************************************/
#include <zephyr/kernel.h>
#include <string.h>
#include "parser.h"
#include "main.h"
#include "debug_print.h"
#include "ble_service.h"
#include "battery_adc.h"
#include "led_control.h"
/*==============================================================================
* CRC16 (CRC-CCITT, Nordic SDK )
*============================================================================*/
static uint16_t dr_crc16_compute(const uint8_t *p_data, uint32_t size)
{
uint16_t crc = 0xFFFF;
for (uint32_t 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;
}
/*==============================================================================
*
*============================================================================*/
/* TAG(4B) + uint16 값(2B) + CRC16(2B) = 8바이트 전송 */
static void send_response_u16(const char *tag, uint16_t value)
{
uint8_t buf[8];
buf[0] = tag[0];
buf[1] = tag[1];
buf[2] = tag[2];
buf[3] = tag[3];
buf[4] = (uint8_t)(value >> 8);
buf[5] = (uint8_t)(value & 0xFF);
uint16_t crc = dr_crc16_compute(buf, 6);
buf[6] = (uint8_t)(crc & 0xFF);
buf[7] = (uint8_t)(crc >> 8);
ble_data_send(buf, 8);
}
/*==============================================================================
*
*============================================================================*/
/* msn? → 배터리 전압 측정 → rsn: + mV */
static int cmd_msn(const uint8_t *data, uint8_t data_len)
{
int mv = battery_read_mv();
if (mv < 0)
{
mv = 0;
}
send_response_u16("rsn:", (uint16_t)mv);
DBG_PRINTF("[CMD] msn -> %d mV\r\n", mv);
return 1;
}
/* mls? → LED 상태 변경 → rls: + state echo back
* : [state(2B LE)] led_state_t enum
* 0=OFF, 4=DETACH_WARNING, 5=ALIGN_SEARCHING, 6=ALIGN_COMPLETE, 7=ERROR
* : 0xFFFF= , 0xFFFE= */
static int cmd_mls(const uint8_t *data, uint8_t data_len)
{
/* 파라미터 부족 → 에러 코드 0xFFFF 에코 */
if (data_len < 2)
{
send_response_u16("rls:", 0xFFFF);
DBG_PRINTF("[CMD] mls: no data\r\n");
return 1;
}
/* 리틀엔디안으로 읽기 (앱이 LE로 전송) */
uint16_t state = (uint16_t)(data[0]) | ((uint16_t)(data[1]) << 8);
/* 범위 초과 → 에러 코드 0xFFFE 에코 */
if (state > LED_STATE_ERROR)
{
send_response_u16("rls:", 0xFFFE);
DBG_PRINTF("[CMD] mls: invalid state %d\r\n", state);
return 1;
}
led_set_state((led_state_t)state);
send_response_u16("rls:", state);
DBG_PRINTF("[CMD] mls -> LED state=%d\r\n", state);
return 1;
}
/*==============================================================================
*
*============================================================================*/
typedef struct
{
char tag[5];
int (*handler)(const uint8_t *data, uint8_t data_len);
} cmd_entry_t;
static const cmd_entry_t cmd_table[] =
{
{ "msn?", cmd_msn },
{ "mls?", cmd_mls },
};
#define CMD_TABLE_SIZE (sizeof(cmd_table) / sizeof(cmd_table[0]))
/*==============================================================================
*
*============================================================================*/
int dr_parser(const uint8_t *buf, uint16_t len)
{
/* 최소 4바이트 TAG 필요 */
if (len < 4)
{
DBG_PRINTF("[CMD] Too short (%d)\r\n", len);
return -1;
}
/* CRC16 검증 (6바이트 이상이면 마지막 2바이트가 CRC) */
if (len >= 6)
{
uint16_t calc_crc = dr_crc16_compute(buf, len - 2);
uint16_t recv_crc = (uint16_t)(buf[len - 2]) | ((uint16_t)(buf[len - 1]) << 8);
if (calc_crc != recv_crc)
{
DBG_PRINTF("[CMD] CRC fail (calc=0x%04X recv=0x%04X)\r\n", calc_crc, recv_crc);
return -1;
}
}
/* TAG 추출 (4바이트) */
char tag[5] = { buf[0], buf[1], buf[2], buf[3], '\0' };
/* 데이터 부분 (TAG 이후, CRC 이전) */
const uint8_t *data = buf + 4;
uint8_t data_len = (len >= 6) ? (len - 4 - 2) : (len - 4);
/* 테이블 검색 + 디스패치 */
for (int i = 0; i < CMD_TABLE_SIZE; i++)
{
if (memcmp(tag, cmd_table[i].tag, 4) == 0)
{
return cmd_table[i].handler(data, data_len);
}
}
DBG_PRINTF("[CMD] Unknown: %s\r\n", tag);
return 0;
}
+14
View File
@@ -0,0 +1,14 @@
/*******************************************************************************
* @file parser.h
* @brief BLE command parser (Zephyr port)
*
* 4 TAG + DATA + CRC16
******************************************************************************/
#ifndef CMD_PARSER_H__
#define CMD_PARSER_H__
#include <stdint.h>
int dr_parser(const uint8_t *buf, uint16_t len);
#endif /* CMD_PARSER_H__ */
+83
View File
@@ -0,0 +1,83 @@
/*******************************************************************************
* @file power_control.c
* @brief Device power sequence control (Zephyr port)
*
* Power-up sequence state machine with k_timer (single-shot 20ms intervals).
******************************************************************************/
#include <zephyr/kernel.h>
#include "main.h"
#include "power_control.h"
#include "debug_print.h"
#define POWER_LOOP_INTERVAL 20 /* ms */
static struct k_timer m_power_timer;
static uint8_t p_order;
static bool lock_check = false;
extern volatile bool processing;
static void power_loop_expiry(struct k_timer *timer)
{
power_loop(timer);
}
int device_sleep_mode(void)
{
k_msleep(2);
DBG_PRINTF("Device_Sleep_Mode OK!\r\n");
k_msleep(10);
processing = false;
return 0;
}
int device_activated(void)
{
p_order = 0;
lock_check = true;
power_timer_start();
return 0;
}
void power_loop(struct k_timer *timer)
{
power_timer_stop();
/* Sensor init not needed - imu_read_direct() handles it per measurement */
p_order = 2;
if (p_order < 2)
{
p_order++;
power_timer_start();
}
else
{
DBG_PRINTF("[PWR] Device Activated OK!\r\n");
}
}
int device_reactivated(void)
{
/* sw_i2c_init_once() will be added in Stage 3 */
k_msleep(10);
lock_check = true;
p_order = 0;
power_timer_start();
return 0;
}
void power_timer_start(void)
{
k_timer_start(&m_power_timer, K_MSEC(POWER_LOOP_INTERVAL), K_NO_WAIT);
}
void power_timer_stop(void)
{
k_timer_stop(&m_power_timer);
}
void power_timer_init(void)
{
k_timer_init(&m_power_timer, power_loop_expiry, NULL);
}
+18
View File
@@ -0,0 +1,18 @@
/*******************************************************************************
* @file power_control.h
* @brief Device power sequence control (Zephyr port)
******************************************************************************/
#ifndef POWER_CONTROL_H__
#define POWER_CONTROL_H__
#include "main.h"
int device_sleep_mode(void);
int device_activated(void);
int device_reactivated(void);
void power_loop(struct k_timer *timer);
void power_timer_start(void);
void power_timer_stop(void);
void power_timer_init(void);
#endif /* POWER_CONTROL_H__ */