Compare commits

...

31 Commits

Author SHA1 Message Date
jh.chun 9f252c7bef BLE 연결 해제 헬퍼 함수 및 에러별 분기 처리 2026-04-21 16:54:51 +09:00
jh.chun be1ca58bd6 버전 업데이트 2026-04-21 16:52:21 +09:00
jh.chun e20e8f5665 전원 버튼 타이머 조건 변경 2026-04-21 16:51:54 +09:00
jh.chun 2bff259fa3 코드 정리 2026-04-21 14:59:47 +09:00
jh.chun c72349cf13 NUS characteristic: MITM 인증 완료된 연결만 접근 가능하도록 수정
- SEC_OPEN -> SEC_MITM
- 기존에는 SoftDevice 레벨에서 인증 전 write를 차단하지 않음
2026-04-21 14:08:57 +09:00
jh.chun a0aa180ef1 DFU characteristic: MITM 인증 완료된 연결만 접근 가능하도록 수정
- SEC_OPEN -> SEC_MITM
- 기존에는 페어링 완료 전에도 DFU characteristic write 가능, BLE SMP 타임아웃(30초) 전에 wirte하면 부트로더로 재부팅되어 인증 없이 DFU 모드 진입 가능했음
2026-04-21 14:08:02 +09:00
jh.chun e9eb89f203 DFU 진입 시 주변 장치 및 타이머 정리 추가
- PREPARE 이벤트 시 타이머(메인, 배터리/온도) 중지 및 비동기 측정 중단 및 상태 리셋, Piezo 전원 차단
- 정리하지 않는 경우 Piezo TX 비정상 신호 출력/Flash 데이터 깨질 가능성 존재
2026-04-21 10:51:02 +09:00
jh.chun 790e3a1b82 미사용 변수 삭제 2026-04-21 09:56:24 +09:00
jh.chun 50a8e68cf4 BLE 인증 전 NUS 명령 실행 방지
- GAP 연결 시 PROD 모드에서 ble_connection_st 조기 활성화 제거(보안 인증 후 활성화)
- nus_data_handler()에 인증 상태 검증 가드 추가(ble_connection_st가 0인 경우 받지 않고 즉시 리턴)
2026-04-21 09:41:12 +09:00
jh.chun 1fa0d8b30b FW 버전 업데이트 2026-04-20 14:23:19 +09:00
jh.chun 36cd84e49c 테스트용 함수 삭제 2026-04-20 14:22:55 +09:00
jh.chun 163593c0cd 코드 정리 2026-04-20 14:22:43 +09:00
jh.chun 009897a7d8 Piezo TX/RX Active 전 딜레이 3ms -> 10ms
- ch0 데이터 안정화를 위함
2026-04-20 14:22:13 +09:00
jh.chun 93a11b7519 전원 버튼 부팅 판정 시간을 실측 기준으로 보정
- 버튼 카운터가 초기화 후부터 증가하기 때문에 체감 2초 부팅에 맞게 부팅 판정 tick을 400(2s) -> 250(1.25s)로 변경
2026-04-20 12:12:51 +09:00
jh.chun 2db6e36c08 MUX 채널 수정
ch0: A0
ch1: A1
ch2: A2
ch3: A3
ch4: B3
ch5: B2
2026-04-20 09:19:10 +09:00
jh.chun 231a000849 delta_buffer 크기 400 -> 420
- num_samples=140일 때 압축 worst case = 419바이트
2026-04-17 14:20:28 +09:00
jh.chun 0294cdb428 사용하지 않는 매크로 삭제: LESC 및 Static Passkey 관련
- ble 관련이 ble_quick_security 파일로 가면서 정의만 남은 매크로
2026-04-17 13:48:02 +09:00
jh.chun 80c846fd25 미사용 AES 버퍼 삭제 2026-04-17 12:22:28 +09:00
jh.chun 2daa8b3690 passkey 보안: 런타임 플래그
- 런타임 플래그 factory_provisioned=0인 경우에만 passkey 쓰기 가능
2026-04-17 12:15:39 +09:00
jh.chun e04e79ac31 ADC 측정 전 버퍼 클리어 2026-04-16 20:32:49 +09:00
jh.chun 32b6fb800d msi 커맨드 삭제 및 rbb 버퍼 분리 2026-04-16 19:03:27 +09:00
jh.chun 24e5b2e205 주기적 배터리 저전압 및 고온 체크
- 온도 측정 추가
2026-04-16 19:01:12 +09:00
jh.chun 143e22c2d8 전원 버튼을 통한 본딩 삭제 10초 -> 15초로 변경
- 전원이 꺼진 상태에서 전원 버튼을 15초 이상 누르는 경우 전원 켜짐 + 본딩 삭제 + 전원 꺼짐
2026-04-16 17:39:13 +09:00
jh.chun 24a4be94df 전원 버튼 BSP 제거, GPIO 제어로 전환
- 전원 OFF 로직 추가(main_s)
- processing 변수 삭제
2026-04-16 16:55:59 +09:00
jh.chun 742681554e 전원 버튼 BSP 제거, GPIO 제어로 전환
- 모든 led 및 버튼이 bsp를 사용하지 않음
- BSP 관련 include, 이벤트 등 삭제
- sdk_config.h: BSP_BTN_BLE_ENABLED=0으로 변경하여 빌드에서도 삭제
2026-04-16 16:25:19 +09:00
jh.chun 6e890b87d8 온도 측정 커맨드 변경
- 기존 mso?: Piezo TX/RX 전원 관리하지 않고 온도 측정만 담당
- 변경 mst?: Piezo TX/RX Active -> TMP235 온도 측정 -> Piezo TX/RX Sleep 자체 처리
2026-04-16 14:08:51 +09:00
jh.chun 01bdbf7cfe 버퍼 초기화 추가 2026-04-16 14:06:29 +09:00
jh.chun 2861cb9815 코드 정리
- 주석 영문으로 변경
- Allman 스타일로 통일
2026-04-16 12:01:51 +09:00
jh.chun c98d9ae14e parser 분리
- 파서 인프라/커맨드 핸들러로 분리
- 커맨드 핸들러는 기기 제어/기기 정보/센서/Piezo로 분리
2026-04-16 01:28:11 +09:00
jh.chun c11ce4ec3e 에러 커맨드 추가
- rxx: (Unknown command) - 수신 TAG가 명령 테이블에 없는 경우
- rxd: (Disabled command) - 수신 TAG가 테이블에 있지만 enabled=false로 비활성화된 경우
- rxn: (NULL handler) - 엔트리는 매칭됐지만 함수 포인터가 NULL인 경우(펌웨어 버그 방어)
- rxc: (CRC fail) - CRC16 검증 실패
- rxs: (Too short) - 패킷이 너무 짧은 경우(CRC 활성 시 7바이트 미만, 비활성 시 4바이트 미만)
2026-04-16 01:20:52 +09:00
jh.chun a826d09dfa 기기 정보 일괄 읽기 커맨드 추가
- HW Version, Serial Version, FW Version
2026-04-16 01:07:37 +09:00
48 changed files with 4123 additions and 4169 deletions
@@ -216,9 +216,9 @@ uint32_t ble_dfu_buttonless_char_add(ble_dfu_buttonless_t * p_dfu)
add_char_params.is_var_len = true; add_char_params.is_var_len = true;
add_char_params.max_len = BLE_GATT_ATT_MTU_DEFAULT; add_char_params.max_len = BLE_GATT_ATT_MTU_DEFAULT;
add_char_params.cccd_write_access = SEC_OPEN; add_char_params.cccd_write_access = SEC_MITM;
add_char_params.write_access = SEC_OPEN; add_char_params.write_access = SEC_MITM;
add_char_params.read_access = SEC_OPEN; add_char_params.read_access = SEC_MITM;
return characteristic_add(p_dfu->service_handle, &add_char_params, &p_dfu->control_point_char); return characteristic_add(p_dfu->service_handle, &add_char_params, &p_dfu->control_point_char);
} }
@@ -283,8 +283,8 @@ uint32_t ble_nus_init(ble_nus_t * p_nus, ble_nus_init_t const * p_nus_init)
add_char_params.char_props.write = 1; add_char_params.char_props.write = 1;
add_char_params.char_props.write_wo_resp = 1; add_char_params.char_props.write_wo_resp = 1;
add_char_params.read_access = SEC_OPEN; add_char_params.read_access = SEC_MITM;
add_char_params.write_access = SEC_OPEN; add_char_params.write_access = SEC_MITM;
err_code = characteristic_add(p_nus->service_handle, &add_char_params, &p_nus->rx_handles); err_code = characteristic_add(p_nus->service_handle, &add_char_params, &p_nus->rx_handles);
if (err_code != NRF_SUCCESS) if (err_code != NRF_SUCCESS)
@@ -302,9 +302,9 @@ uint32_t ble_nus_init(ble_nus_t * p_nus, ble_nus_init_t const * p_nus_init)
add_char_params.is_var_len = true; add_char_params.is_var_len = true;
add_char_params.char_props.notify = 1; add_char_params.char_props.notify = 1;
add_char_params.read_access = SEC_OPEN; add_char_params.read_access = SEC_MITM;
add_char_params.write_access = SEC_OPEN; add_char_params.write_access = SEC_MITM;
add_char_params.cccd_write_access = SEC_OPEN; add_char_params.cccd_write_access = SEC_MITM;
return characteristic_add(p_nus->service_handle, &add_char_params, &p_nus->tx_handles); return characteristic_add(p_nus->service_handle, &add_char_params, &p_nus->tx_handles);
/**@snippet [Adding proprietary characteristic to the SoftDevice] */ /**@snippet [Adding proprietary characteristic to the SoftDevice] */
@@ -0,0 +1,93 @@
/*==============================================================================
* cmd_common.h - Shared header for command handlers
*
* Bundles every include / extern declaration / macro used in common across
* the cmd_*.c modules. A handler module only needs this header plus its own
* area-specific driver headers.
*============================================================================*/
#ifndef CMD_COMMON_H
#define CMD_COMMON_H
#include "parser.h"
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include "nrf_gpio.h"
#include "nrf_delay.h"
#include "debug_print.h"
#include "dr_util.h"
#include "main.h"
#include "app_timer.h"
#include "fstorage.h" /* config_data_t */
/*------------------------------------------------------------------------------
* BLE transmission / formatting (defined in main.c)
*----------------------------------------------------------------------------*/
extern uint8_t ble_bin_buffer[];
extern void single_format_data(uint8_t *buffer, const char *tag, uint16_t value);
extern void ascii_format_data(uint8_t *buffer, const char *tag, const char *ascii, size_t length);
extern void format_data(uint8_t *buffer, const char *tag, const uint16_t *data_array, size_t length);
extern void dr_binary_tx_safe(const uint8_t *buffer, uint16_t length); /* length: word count */
extern void dr_sd_delay_ms(uint32_t ms);
extern volatile bool data_tx_in_progress;
/*------------------------------------------------------------------------------
* Device state / flags
*----------------------------------------------------------------------------*/
extern bool device_status;
extern bool con_single;
extern bool lock_check;
extern uint8_t resetCount;
/*------------------------------------------------------------------------------
* Measurement functions (defined elsewhere)
*----------------------------------------------------------------------------*/
extern void battery_level_meas(void);
extern void pressure_all_level_meas(void);
extern void tmp235_voltage_level_meas(void);
extern int imu_read_direct(void);
extern void battery_timer_stop(void);
extern void main_timer_start(void);
extern void hw_i2c_init_once(void);
/*------------------------------------------------------------------------------
* info4 mode sensor cache (used to assemble the rbb: response in mbb?)
*----------------------------------------------------------------------------*/
extern bool info4;
extern bool ble_got_new_data;
extern bool go_batt;
extern bool motion_data_once;
extern bool motion_raw_data_enabled;
extern volatile uint16_t info_batt;
extern volatile uint16_t info_temp;
extern volatile uint16_t info_imu[6];
extern volatile bool tmp235_saadc_done;
extern volatile bool battery_saadc_done;
/*------------------------------------------------------------------------------
* Device identifiers / passkey (FDS-backed)
*----------------------------------------------------------------------------*/
extern char SERIAL_NO[12];
extern char HW_NO[12];
extern char m_static_passkey[6];
extern uint32_t m_life_cycle;
/*------------------------------------------------------------------------------
* Power / reset / bonding control
*----------------------------------------------------------------------------*/
extern bool go_device_power_off;
extern bool go_NVIC_SystemReset;
extern bool bond_data_delete;
extern uint8_t m_reset_status;
extern void config_save(void);
extern config_data_t m_config;
/*------------------------------------------------------------------------------
* AGC gain switch (P0.20)
*----------------------------------------------------------------------------*/
#define GAIN_SW_PIN NRF_GPIO_PIN_MAP(0, 20)
#define AGC_GAIN_SW(x) do { if(x) nrf_gpio_pin_set(GAIN_SW_PIN); else nrf_gpio_pin_clear(GAIN_SW_PIN); } while(0)
#endif /* CMD_COMMON_H */
@@ -0,0 +1,62 @@
/*==============================================================================
* cmd_table.c - Command table definition
*
* This file is the only linkage point between parser.c and the cmd_*.c handler modules.
* It pulls in every cmd_*.h to gather handler prototypes,
* builds the CmdEntry array, and injects the table into the parser via dr_parser_init() inside cmd_table_init().
*
* Adding a new command:
* 1) Implement the handler in the appropriate cmd_*.c
* 2) Add the prototype to the matching cmd_*.h
* 3) Append { "tag?", true, Cmd_xxx } to m_cmd_table[] below
*============================================================================*/
#include "parser.h"
#include "cmd_table.h"
#include "cmd_device.h"
#include "cmd_info.h"
#include "cmd_sensor.h"
#include "cmd_piezo.h"
static const CmdEntry m_cmd_table[] = {
/* A. Device state control */
{ "msq?", true, Cmd_msq },
{ "mss?", true, Cmd_mss },
#if FEATURE_SECURE_CONNECTION
{ "msr?", true, Cmd_msr },
#endif
{ "cmd?", true, Cmd_cmd },
/* B. Device information */
{ "mfv?", true, Cmd_mfv },
{ "mid?", true, Cmd_mid },
{ "mwh?", true, Cmd_mwh },
{ "mws?", true, Cmd_mws },
{ "mrh?", true, Cmd_mrh },
{ "mrs?", true, Cmd_mrs },
{ "mpz?", true, Cmd_mpz },
{ "mqz?", true, Cmd_mqz },
/* C. Sensor measurement */
{ "msn?", true, Cmd_msn },
{ "mst?", true, Cmd_mst },
{ "msp?", true, Cmd_msp },
/* D. Piezo ultrasound */
{ "mpa?", true, Cmd_mpa },
{ "mpb?", true, Cmd_mpb },
{ "mpc?", true, Cmd_mpc },
{ "mec?", true, Cmd_mec },
{ "maa?", true, Cmd_maa },
{ "mbb?", true, Cmd_mbb },
{ "mcf?", true, Cmd_mcf },
{ "mcs?", true, Cmd_mcs },
/* E. LED control (handler in cmd_device.c) */
{ "mls?", true, Cmd_mls },
};
void cmd_table_init(void)
{
dr_parser_init(m_cmd_table, sizeof(m_cmd_table) / sizeof(m_cmd_table[0]));
}
@@ -0,0 +1,12 @@
/*==============================================================================
* cmd_table.h - Command table registration entry point
*
* main.c calls cmd_table_init() at boot to inject the command table into
* the parser.
*============================================================================*/
#ifndef CMD_TABLE_H
#define CMD_TABLE_H
void cmd_table_init(void);
#endif /* CMD_TABLE_H */
@@ -1,3 +1,11 @@
/*==============================================================================
* dr_util.c - BLE response formatting helpers
*
* Convenience wrappers used by command handlers to assemble and transmit
* short BLE responses. Each helper prepends a 4-char TAG and writes the
* supplied uint16 values in Big-Endian order.
*============================================================================*/
#include "dr_util.h" #include "dr_util.h"
#include "parser.h" #include "parser.h"
@@ -47,7 +55,7 @@ void dr_ble_return_3(const char *tag, uint16_t v1, uint16_t v2, uint16_t v3)
void dr_ble_debug(uint16_t point_id, uint16_t value) void dr_ble_debug(uint16_t point_id, uint16_t value)
{ {
/* Use dedicated buffer to avoid conflicts with ble_bin_buffer */ /* Use dedicated buffer to avoid conflicts with ble_bin_buffer */
static uint8_t dbg_buffer[8]; static uint8_t dbg_buffer[8] = {0};
dbg_buffer[0] = 'd'; dbg_buffer[0] = 'd';
dbg_buffer[1] = 'b'; dbg_buffer[1] = 'b';
@@ -65,7 +73,7 @@ void dr_ble_debug(uint16_t point_id, uint16_t value)
void dr_ble_return_piezo_1(const char *tag, uint16_t value) void dr_ble_return_piezo_1(const char *tag, uint16_t value)
{ {
/* Use dedicated buffer for piezo responses to avoid conflicts with ble_bin_buffer */ /* Use dedicated buffer for piezo responses to avoid conflicts with ble_bin_buffer */
static uint8_t piezo_buffer[8]; static uint8_t piezo_buffer[8] = {0};
piezo_buffer[0] = tag[0]; piezo_buffer[0] = tag[0];
piezo_buffer[1] = tag[1]; piezo_buffer[1] = tag[1];
@@ -1,3 +1,6 @@
/*==============================================================================
* dr_util.h - BLE response helper API
*============================================================================*/
#ifndef DR_UTIL_H #ifndef DR_UTIL_H
#define DR_UTIL_H #define DR_UTIL_H
@@ -8,11 +11,10 @@ void dr_ble_return_2(const char *tag, uint16_t v1, uint16_t v2);
void dr_ble_return_3(const char *tag, uint16_t v1, uint16_t v2, uint16_t v3); void dr_ble_return_3(const char *tag, uint16_t v1, uint16_t v2, uint16_t v3);
void dr_ble_return_3_be(const char *tag, uint16_t v1, uint16_t v2, uint16_t v3); void dr_ble_return_3_be(const char *tag, uint16_t v1, uint16_t v2, uint16_t v3);
/* Piezo dedicated BLE return - uses separate buffer to avoid conflicts */ /* Piezo dedicated BLE return - uses a separate buffer to avoid conflicts. */
void dr_ble_return_piezo_1(const char *tag, uint16_t value); void dr_ble_return_piezo_1(const char *tag, uint16_t value);
/* BLE debug output - sends "dbg:" + point_id + value */ /* BLE debug output - sends "dbg:" + point_id + value. */
void dr_ble_debug(uint16_t point_id, uint16_t value); void dr_ble_debug(uint16_t point_id, uint16_t value);
#endif /* DR_UTIL_H */ #endif /* DR_UTIL_H */
@@ -0,0 +1,177 @@
/*==============================================================================
* cmd_device.c - Device state control handlers
*
* A. msq? -> rsq: device power OFF
* B. mss? -> rss: device soft reset
* C. msr? -> rsr: BLE bond delete + reset (FEATURE_SECURE_CONNECTION)
* D. cmd? -> rmd: direct GPIO pin control (debug / test only)
* E. mls? -> rls: set LED state
*============================================================================*/
#include "cmd_common.h"
#include "cmd_device.h"
#include "fstorage.h"
#include "led_control.h"
/*==============================================================================
* msq? -> rsq: Device power OFF
*
* Request: [TAG 4B "msq?"] [val 2B BE] [CRC 2B]
* Response: [TAG 4B "rsq:"] [val 2B BE] [CRC 2B]
*
* Sends BLE response first; the actual power-off is performed from the main loop.
* Powering off immediately would prevent the response from reaching the host, so a flag + timer pattern is used instead.
*============================================================================*/
int Cmd_msq(const ParsedCmd *cmd)
{
uint16_t val = 0;
dr_get_u16(cmd, 0, &val);
if (g_plat.log)
{
g_plat.log("[Cmd_msq] Power off val=%u\r\n", val);
}
single_format_data(ble_bin_buffer, "rsq:", val);
dr_binary_tx_safe(ble_bin_buffer, 2);
go_device_power_off = true;
main_timer_start();
return 1;
}
/*==============================================================================
* mss? -> rss: Device soft reset
*
* Request: [TAG 4B "mss?"] [val 2B BE] [CRC 2B]
* Response: [TAG 4B "rss:"] [val 2B BE] [CRC 2B]
*
* Resets without erasing bond information. Reset status code is persisted to FDS so the boot path can identify the cause.
*============================================================================*/
int Cmd_mss(const ParsedCmd *cmd)
{
uint16_t val = 0;
dr_get_u16(cmd, 0, &val);
single_format_data(ble_bin_buffer, "rss:", val);
dr_binary_tx_safe(ble_bin_buffer, 2);
m_reset_status = 2;
m_config.reset_status = m_reset_status;
config_save();
nrf_delay_ms(5);
go_NVIC_SystemReset = true;
main_timer_start();
return 1;
}
/*==============================================================================
* msr? -> rsr: Delete BLE bonding info + system reset
*
* Request: [TAG 4B "msr?"] [val 2B BE] [CRC 2B]
* Response: [TAG 4B "rsr:"] [val 2B BE] [CRC 2B]
*
* 1. Send BLE response
* 2. Persist bond-delete flag and reset status to FDS
* 3. Wait 5 ms for FDS write completion
* 4. Set NVIC system reset flag
*============================================================================*/
#if FEATURE_SECURE_CONNECTION
int Cmd_msr(const ParsedCmd *cmd)
{
uint16_t val = 0;
dr_get_u16(cmd, 0, &val);
single_format_data(ble_bin_buffer, "rsr:", val);
dr_binary_tx_safe(ble_bin_buffer, 2);
bond_data_delete = true;
m_config.bond_data_delete = (uint8_t)bond_data_delete;
m_reset_status = 2;
m_config.reset_status = m_reset_status;
config_save();
nrf_delay_ms(5);
go_NVIC_SystemReset = true;
main_timer_start();
return 1;
}
#endif
/*==============================================================================
* cmd? -> rmd: Direct GPIO pin control (debug / test)
*
* Request: [TAG 4B "cmd?"] [port 2B BE] [pin 2B BE] [state 2B BE] [CRC 2B]
* Response: [TAG 4B "rmd:"] [port 2B BE] [pin 2B BE] [state 2B BE] [CRC 2B]
* Error: rmd: + 0 (insufficient data)
*
* port : GPIO port (0 or 1)
* pin : pin number (0..31)
* state: 1=HIGH, 0=LOW
*
* Misuse can damage hardware - intended for debug only.
*============================================================================*/
int Cmd_cmd(const ParsedCmd *cmd)
{
uint16_t v1, v2, v3;
uint32_t pin_number;
if (cmd->data_len < 6)
{
dr_ble_return_1("rmd:", 0);
return 1;
}
if (!dr_get_u16(cmd, 0, &v1))
{
v1 = 0;
}
if (!dr_get_u16(cmd, 1, &v2))
{
v2 = 0;
}
if (!dr_get_u16(cmd, 2, &v3))
{
v3 = 0;
}
pin_number = NRF_GPIO_PIN_MAP(v1, v2);
nrf_gpio_cfg_output(pin_number);
if (v3 == 1)
{
nrf_gpio_pin_set(pin_number);
}
else
{
nrf_gpio_pin_clear(pin_number);
}
dr_ble_return_3("rmd:", v1, v2, v3);
return 1;
}
/*==============================================================================
* mls? -> rls: Set LED state (app -> device)
*
* Request: [TAG 4B "mls?"] [state 2B BE] [CRC 2B]
* state: led_state_t enum value: 0=OFF, 4=DETACH_WARNING, 5=ALIGN_SEARCHING, 6=ALIGN_COMPLETE
* Response: [TAG 4B "rls:"] [state 2B] [CRC 2B]
* Error: rls: + 0xFFFF (insufficient data)
* rls: + 0xFFFE (state out of range)
*============================================================================*/
int Cmd_mls(const ParsedCmd *cmd)
{
if (cmd->data_len < 2)
{
dr_ble_return_1("rls:", 0xFFFF);
return 1;
}
uint16_t state;
dr_get_u16(cmd, 0, &state);
if (state > LED_STATE_ERROR)
{
dr_ble_return_1("rls:", 0xFFFE);
return 1;
}
led_set_state((led_state_t)state);
dr_ble_return_1("rls:", state);
return 1;
}
@@ -0,0 +1,17 @@
/*==============================================================================
* cmd_device.h - Device state control handlers (power / reset / GPIO / LED)
*============================================================================*/
#ifndef CMD_DEVICE_H
#define CMD_DEVICE_H
#include "parser.h"
int Cmd_msq(const ParsedCmd *cmd); /* msq? -> rsq: device power OFF */
int Cmd_mss(const ParsedCmd *cmd); /* mss? -> rss: device soft reset */
#if FEATURE_SECURE_CONNECTION
int Cmd_msr(const ParsedCmd *cmd); /* msr? -> rsr: bond delete + reset */
#endif
int Cmd_cmd(const ParsedCmd *cmd); /* cmd? -> rmd: direct GPIO control */
int Cmd_mls(const ParsedCmd *cmd); /* mls? -> rls: set LED state */
#endif /* CMD_DEVICE_H */
@@ -0,0 +1,173 @@
/*==============================================================================
* cmd_info.c - Device information read/write handlers
*
* Commands that read or write FDS-persisted data: firmware version, hardware version, serial number, BLE passkey.
* Typically written once at the factory and read back later for identification.
*============================================================================*/
#include "cmd_common.h"
#include "cmd_info.h"
#include "fstorage.h"
/*==============================================================================
* mfv? -> rfv: Read firmware version
*
* Request: [TAG 4B "mfv?"] [CRC 2B]
* Response: [TAG 4B "rfv:"] [FW_VER 12B ASCII] [CRC 2B]
*============================================================================*/
int Cmd_mfv(const ParsedCmd *cmd)
{
(void)cmd;
ascii_format_data(ble_bin_buffer, "rfv:", FIRMWARE_VERSION, 12);
dr_binary_tx_safe(ble_bin_buffer, 8); /* 16 bytes = 8 words */
return 1;
}
/*==============================================================================
* mid? -> rid: Read HW number + serial number + FW version together
*
* Request: [TAG 4B "mid?"] [CRC 2B]
* Response: [TAG 4B "rid:"] [HW 12B] [SN 12B] [FW 12B] [CRC 2B]
*============================================================================*/
int Cmd_mid(const ParsedCmd *cmd)
{
uint8_t *buf = ble_bin_buffer;
(void)cmd;
memcpy(HW_NO, m_config.hw_no, 12);
memcpy(SERIAL_NO, m_config.serial_no, 12);
buf[0] = 'r'; buf[1] = 'i'; buf[2] = 'd'; buf[3] = ':';
memcpy(&buf[4], HW_NO, 12);
memcpy(&buf[16], SERIAL_NO, 12);
memcpy(&buf[28], FIRMWARE_VERSION, 12);
dr_binary_tx_safe(buf, 20); /* 40 bytes = 20 words */
return 1;
}
/*==============================================================================
* mwh? -> rwh: Write HW number to FDS
*
* Request: [TAG 4B "mwh?"] [HW 12B ASCII] [CRC 2B]
* Response: [TAG 4B "rwh:"] [HW 12B ASCII] [CRC 2B]
* Error: rwh: + 0xFFFF (insufficient data)
*============================================================================*/
int Cmd_mwh(const ParsedCmd *cmd)
{
char buf[13];
if (cmd->data_len < 12)
{
dr_ble_return_1("rwh:", 0xFFFF);
return 1;
}
dr_get_ascii(cmd, 0, buf, 12);
memcpy(HW_NO, buf, 12);
memcpy(m_config.hw_no, buf, 12);
config_save();
ascii_format_data(ble_bin_buffer, "rwh:", buf, 12);
dr_binary_tx_safe(ble_bin_buffer, 8);
return 1;
}
/*==============================================================================
* mws? -> rws: Write serial number to FDS
*
* Request: [TAG 4B "mws?"] [SN 12B ASCII] [CRC 2B]
* Response: [TAG 4B "rws:"] [SN 12B ASCII] [CRC 2B]
* Error: rws: + 0xFFFF (insufficient data)
*============================================================================*/
int Cmd_mws(const ParsedCmd *cmd)
{
char buf[13];
if (cmd->data_len < 12)
{
dr_ble_return_1("rws:", 0xFFFF);
return 1;
}
dr_get_ascii(cmd, 0, buf, 12);
memcpy(SERIAL_NO, buf, 12);
memcpy(m_config.serial_no, buf, 12);
config_save();
ascii_format_data(ble_bin_buffer, "rws:", buf, 12);
dr_binary_tx_safe(ble_bin_buffer, 8);
return 1;
}
/*==============================================================================
* mrh? -> rrh: Read HW number from FDS
*
* Request: [TAG 4B "mrh?"] [CRC 2B]
* Response: [TAG 4B "rrh:"] [HW 12B ASCII] [CRC 2B]
*============================================================================*/
int Cmd_mrh(const ParsedCmd *cmd)
{
(void)cmd;
memcpy(HW_NO, m_config.hw_no, 12);
ascii_format_data(ble_bin_buffer, "rrh:", HW_NO, 12);
dr_binary_tx_safe(ble_bin_buffer, 8);
return 1;
}
/*==============================================================================
* mrs? -> rrs: Read serial number from FDS
*
* Request: [TAG 4B "mrs?"] [CRC 2B]
* Response: [TAG 4B "rrs:"] [SN 12B ASCII] [CRC 2B]
*============================================================================*/
int Cmd_mrs(const ParsedCmd *cmd)
{
(void)cmd;
memcpy(SERIAL_NO, m_config.serial_no, 12);
ascii_format_data(ble_bin_buffer, "rrs:", SERIAL_NO, 12);
dr_binary_tx_safe(ble_bin_buffer, 8);
return 1;
}
/*==============================================================================
* mpz? -> rpz: Write BLE passkey to FDS
*
* Request: [TAG 4B "mpz?"] [passkey 6B ASCII] [CRC 2B]
* Response: [TAG 4B "rpz:"] [passkey 6B ASCII] [CRC 2B]
*============================================================================*/
int Cmd_mpz(const ParsedCmd *cmd)
{
if (m_config.factory_provisioned != 0)
{
dr_ble_return_1("rpz:", 0xFFFF);
return 1;
}
char passkey[7] = {0};
dr_get_ascii(cmd, 0, passkey, 6);
memcpy(m_static_passkey, passkey, 6);
memcpy(m_config.static_passkey, m_static_passkey, 6);
m_config.factory_provisioned = 1;
config_save();
ascii_format_data(ble_bin_buffer, "rpz:", passkey, 6);
dr_binary_tx_safe(ble_bin_buffer, 5);
return 1;
}
/*==============================================================================
* mqz? -> rqz: Read BLE passkey from FDS
*
* Request: [TAG 4B "mqz?"] [CRC 2B]
* Response: [TAG 4B "rqz:"] [passkey 6B ASCII] [CRC 2B]
*============================================================================*/
int Cmd_mqz(const ParsedCmd *cmd)
{
(void)cmd;
memcpy(m_static_passkey, m_config.static_passkey, 6);
ascii_format_data(ble_bin_buffer, "rqz:", m_static_passkey, 6);
dr_binary_tx_safe(ble_bin_buffer, 5);
return 1;
}
@@ -0,0 +1,18 @@
/*==============================================================================
* cmd_info.h - Device information read/write handlers
*============================================================================*/
#ifndef CMD_INFO_H
#define CMD_INFO_H
#include "parser.h"
int Cmd_mfv(const ParsedCmd *cmd); /* mfv? -> rfv: read firmware version */
int Cmd_mid(const ParsedCmd *cmd); /* mid? -> rid: read HW + SN + FW together */
int Cmd_mwh(const ParsedCmd *cmd); /* mwh? -> rwh: write HW number */
int Cmd_mws(const ParsedCmd *cmd); /* mws? -> rws: write serial number */
int Cmd_mrh(const ParsedCmd *cmd); /* mrh? -> rrh: read HW number */
int Cmd_mrs(const ParsedCmd *cmd); /* mrs? -> rrs: read serial number */
int Cmd_mpz(const ParsedCmd *cmd); /* mpz? -> rpz: write BLE passkey */
int Cmd_mqz(const ParsedCmd *cmd); /* mqz? -> rqz: read BLE passkey */
#endif /* CMD_INFO_H */
@@ -0,0 +1,343 @@
/*==============================================================================
* cmd_piezo.c - Piezo ultrasound measurement handlers
*
* mpa? -> rpa: TX/RX power ON
* mpb? -> rpb: TX/RX power OFF
* mpc? -> rpc: burst generation (test)
* mec? -> reb:+raa: single-channel burst + echo capture
* maa? -> reb:+raa: 6-channel asynchronous capture
* mbb? -> rbb:+reb:+raa: bulk sensor measurement + 6-channel capture
* mcf? -> rcf: read piezo parameters
* mcs? -> rcs: write piezo parameters
*============================================================================*/
#include "cmd_common.h"
#include "cmd_piezo.h"
#include "cmd_sensor.h" /* all_sensors() */
#include "dr_piezo.h"
#include "dr_adc121s051.h"
/*==============================================================================
* mpa? -> rpa: Enable piezo TX/RX circuit
*
* Request: [TAG 4B "mpa?"] [CRC 2B]
* Response: [TAG 4B "rpa:"] [1 2B] [CRC 2B]
*============================================================================*/
int Cmd_mpa(const ParsedCmd *cmd)
{
(void)cmd;
dr_piezo_power_on();
if (g_plat.tx_bin)
{
single_format_data(ble_bin_buffer, "rpa:", 1);
dr_binary_tx_safe(ble_bin_buffer, 3);
}
return 1;
}
/*==============================================================================
* mpb? -> rpb: Disable piezo TX/RX circuit
*
* Request: [TAG 4B "mpb?"] [CRC 2B]
* Response: [TAG 4B "rpb:"] [1 2B] [CRC 2B]
*
* Power saving once measurement is complete.
*============================================================================*/
int Cmd_mpb(const ParsedCmd *cmd)
{
(void)cmd;
dr_piezo_power_off();
if (g_plat.tx_bin)
{
single_format_data(ble_bin_buffer, "rpb:", 1);
dr_binary_tx_safe(ble_bin_buffer, 3);
}
return 1;
}
/*==============================================================================
* mpc? -> rpc: Piezo burst generation (test / debug)
*
* Request: [TAG 4B "mpc?"] [cycles 2B] [freq_option 2B] [piezo_ch 2B] [CRC 2B]
* cycles : 3..7 (default 5)
* freq_option : 0=1.8MHz, 1=2.1MHz(default), 2=2.0MHz, 3=1.7MHz, 4=2.2MHz
* piezo_ch : 0..7
* Response: [TAG 4B "rpc:"] [cycles 2B] [CRC 2B]
* Error: rpc: + 2 (cycles out of range)
*
* Generates the burst only - no echo capture.
*============================================================================*/
int Cmd_mpc(const ParsedCmd *cmd)
{
uint16_t cycles = 5;
uint16_t freq_option = 1;
uint16_t piezo_ch = 0;
(void)dr_get_u16(cmd, 0, &cycles);
(void)dr_get_u16(cmd, 1, &freq_option);
(void)dr_get_u16(cmd, 2, &piezo_ch);
if (piezo_ch >= MAA_NUM_CHANNELS)
{
piezo_ch = 0;
}
if (cycles < 3 || cycles > 7)
{
dr_ble_return_1("rpc:", 2);
return 1;
}
dr_piezo_select_channel((uint8_t)piezo_ch);
switch (freq_option)
{
case 0:
dr_piezo_burst_sw_18mhz((uint8_t)cycles);
break;
case 2:
dr_piezo_burst_sw_20mhz((uint8_t)cycles);
break;
case 3:
dr_piezo_burst_sw_17mhz((uint8_t)cycles);
break;
case 4:
dr_piezo_burst_sw_22mhz((uint8_t)cycles);
break;
default:
dr_piezo_burst_sw((uint8_t)cycles);
break;
}
dr_ble_return_1("rpc:", (uint8_t)cycles);
return 1;
}
/*==============================================================================
* mec? -> reb:+raa: Piezo burst + echo capture (16-bit raw)
*
* Request: [TAG 4B "mec?"] [freq 2B] [delay_us 2B] [num_samples 2B]
* [cycles 2B] [averaging 2B] [piezo_ch 2B] [CRC 2B]
* Response: reb: [num_samples 2B] [raw_data up to 238B] (red: follow-up if needed)
* raa: [status 2B]
* Error: rer: + (0xEE00|err) + num_samples
*
* Sends raw 16-bit ADC values directly. Suited for short-range (~25cm).
*============================================================================*/
int Cmd_mec(const ParsedCmd *cmd)
{
uint16_t freq_option = 0;
uint16_t delay_us = 20;
uint16_t num_samples = 140;
uint16_t cycles = 5;
uint16_t averaging = 1;
uint16_t piezo_ch = 0;
if (!dr_piezo_is_power_on())
{
dr_piezo_power_on();
}
(void)dr_get_u16(cmd, 0, &freq_option);
(void)dr_get_u16(cmd, 1, &delay_us);
(void)dr_get_u16(cmd, 2, &num_samples);
(void)dr_get_u16(cmd, 3, &cycles);
(void)dr_get_u16(cmd, 4, &averaging);
(void)dr_get_u16(cmd, 5, &piezo_ch);
if (averaging == 0)
{
averaging = 1;
}
if (averaging > 1000)
{
averaging = 1000;
}
if (piezo_ch >= MAA_NUM_CHANNELS)
{
piezo_ch = 0;
}
dr_adc_err_t err = dr_adc_burst_capture_transmit(
(uint8_t)freq_option, delay_us, num_samples, (uint8_t)cycles,
(uint16_t)averaging, (uint8_t)piezo_ch, ble_bin_buffer, 0);
if (err != DR_ADC_OK)
{
dr_ble_return_2("rer:", 0xEE00 | (uint16_t)err, num_samples);
}
dr_piezo_power_off();
return 1;
}
/*==============================================================================
* maa? -> reb:+raa: 6-channel asynchronous full capture
*
* Request: [TAG 4B "maa?"] [CRC 2B]
* Response: per channel reb: [num_samples 2B] [raw_data...]
* final raa: [status 2B]
* Error: raa: + 0xFFFE (previous capture in progress)
* raa: + (0xFF00|err) (start failed)
*
* Asynchronous state machine - the BLE_NUS_EVT_TX_RDY callback drives the
* follow-up packet transmissions. Parameters are loaded from m_config (FDS).
*============================================================================*/
int Cmd_maa(const ParsedCmd *cmd)
{
dr_adc_err_t err;
(void)cmd;
if (maa_async_is_busy())
{
dr_ble_return_1("raa:", 0xFFFE);
return 1;
}
if (!dr_piezo_is_power_on())
{
dr_piezo_power_on();
}
err = maa_async_start(
m_config.piezo_freq_option,
m_config.piezo_delay_us,
m_config.piezo_num_samples,
m_config.piezo_cycles,
m_config.piezo_averaging,
ble_bin_buffer
);
if (err != DR_ADC_OK)
{
if (g_plat.log)
{
g_plat.log("[Cmd_maa] start failed err=%d\r\n", err);
}
single_format_data(ble_bin_buffer, "raa:", (uint16_t)(0xFF00 | err));
dr_binary_tx_safe(ble_bin_buffer, 3);
dr_piezo_power_off();
return 1;
}
return 1;
}
/*==============================================================================
* mbb? -> rbb:+reb:+raa: Bulk sensor measurement + 6-channel full capture
*
* Request: [TAG 4B "mbb?"] [CRC 2B]
* Response: rbb: [batt 2B] [IMU 12B] [temp 2B]
* reb: [num_samples 2B] [raw_data...] (per channel)
* raa: [status 2B]
* Error: raa: + 0xFFFE (previous capture in progress)
* raa: + (0xFF00|err) (start failed)
*
* 1) all_sensors() measures battery / IMU / temperature and emits rbb:
* 2) Starts 6-channel capture in pre-capture-all mode
*============================================================================*/
int Cmd_mbb(const ParsedCmd *cmd)
{
dr_adc_err_t err;
(void)cmd;
all_sensors();
if (maa_async_is_busy())
{
dr_ble_return_1("raa:", 0xFFFE);
return 1;
}
maa_async_set_pre_capture_all(true);
err = maa_async_start(
m_config.piezo_freq_option,
m_config.piezo_delay_us,
m_config.piezo_num_samples,
m_config.piezo_cycles,
m_config.piezo_averaging,
ble_bin_buffer
);
if (err != DR_ADC_OK)
{
if (g_plat.log)
{
g_plat.log("[Cmd_mbb] start failed err=%d\r\n", err);
}
single_format_data(ble_bin_buffer, "raa:", (uint16_t)(0xFF00 | err));
dr_binary_tx_safe(ble_bin_buffer, 3);
dr_piezo_power_off();
return 1;
}
return 1;
}
/*==============================================================================
* mcf? -> rcf: Read piezo parameters from FDS
*
* Request: [TAG 4B "mcf?"] [CRC 2B]
* Response: [TAG 4B "rcf:"] [freq 2B] [cycles 2B] [avg 2B] [delay_us 2B] [num_samples 2B] [CRC 2B]
*============================================================================*/
int Cmd_mcf(const ParsedCmd *cmd)
{
(void)cmd;
uint8_t *buf = ble_bin_buffer;
buf[0] = 'r'; buf[1] = 'c'; buf[2] = 'f'; buf[3] = ':';
buf[4] = 0; buf[5] = m_config.piezo_freq_option;
buf[6] = 0; buf[7] = m_config.piezo_cycles;
buf[8] = (uint8_t)(m_config.piezo_averaging >> 8); buf[9] = (uint8_t)(m_config.piezo_averaging & 0xFF);
buf[10] = (uint8_t)(m_config.piezo_delay_us >> 8); buf[11] = (uint8_t)(m_config.piezo_delay_us & 0xFF);
buf[12] = (uint8_t)(m_config.piezo_num_samples >> 8); buf[13] = (uint8_t)(m_config.piezo_num_samples & 0xFF);
dr_binary_tx_safe(buf, 7); /* 14 bytes = 7 words */
return 1;
}
/*==============================================================================
* mcs? -> rcs: Write piezo parameters to FDS
*
* Request: [TAG 4B "mcs?"] [freq 2B] [cycles 2B] [avg 2B] [delay_us 2B] [num_samples 2B] [CRC 2B]
* Response: [TAG 4B "rcs:"] [stored 5 values] [CRC 2B]
* Error: rcs: + 0xFFFF (insufficient data)
*============================================================================*/
int Cmd_mcs(const ParsedCmd *cmd)
{
if (cmd->data_len < 10)
{
if (g_plat.log)
{
g_plat.log("[Cmd_mcs] missing params (data_len=%u)\r\n", cmd->data_len);
}
dr_ble_return_1("rcs:", 0xFFFF);
return 1;
}
uint16_t freq, cycles, averaging, delay_us, num_samples;
dr_get_u16(cmd, 0, &freq);
dr_get_u16(cmd, 1, &cycles);
dr_get_u16(cmd, 2, &averaging);
dr_get_u16(cmd, 3, &delay_us);
dr_get_u16(cmd, 4, &num_samples);
m_config.piezo_freq_option = (uint8_t)freq;
m_config.piezo_cycles = (uint8_t)cycles;
m_config.piezo_averaging = averaging;
m_config.piezo_delay_us = delay_us;
m_config.piezo_num_samples = num_samples;
config_save();
uint8_t *buf = ble_bin_buffer;
buf[0] = 'r'; buf[1] = 'c'; buf[2] = 's'; buf[3] = ':';
buf[4] = 0; buf[5] = m_config.piezo_freq_option;
buf[6] = 0; buf[7] = m_config.piezo_cycles;
buf[8] = (uint8_t)(m_config.piezo_averaging >> 8); buf[9] = (uint8_t)(m_config.piezo_averaging & 0xFF);
buf[10] = (uint8_t)(m_config.piezo_delay_us >> 8); buf[11] = (uint8_t)(m_config.piezo_delay_us & 0xFF);
buf[12] = (uint8_t)(m_config.piezo_num_samples >> 8); buf[13] = (uint8_t)(m_config.piezo_num_samples & 0xFF);
dr_binary_tx_safe(buf, 7);
return 1;
}
@@ -0,0 +1,18 @@
/*==============================================================================
* cmd_piezo.h - Piezo ultrasound measurement handlers
*============================================================================*/
#ifndef CMD_PIEZO_H
#define CMD_PIEZO_H
#include "parser.h"
int Cmd_mpa(const ParsedCmd *cmd); /* mpa? -> rpa: TX/RX power ON */
int Cmd_mpb(const ParsedCmd *cmd); /* mpb? -> rpb: TX/RX power OFF */
int Cmd_mpc(const ParsedCmd *cmd); /* mpc? -> rpc: burst generation */
int Cmd_mec(const ParsedCmd *cmd); /* mec? -> reb:+raa: single-channel capture */
int Cmd_maa(const ParsedCmd *cmd); /* maa? -> reb:+raa: 6-channel async capture */
int Cmd_mbb(const ParsedCmd *cmd); /* mbb? -> rbb:+reb:+raa: sensors + capture */
int Cmd_mcf(const ParsedCmd *cmd); /* mcf? -> rcf: read piezo parameters */
int Cmd_mcs(const ParsedCmd *cmd); /* mcs? -> rcs: write piezo parameters */
#endif /* CMD_PIEZO_H */
@@ -0,0 +1,138 @@
/*==============================================================================
* cmd_sensor.c - Sensor measurement handlers
*
* msn? -> rsn: battery ADC measurement
* mso? -> rso: TMP235 temperature reading
* msp? -> rsp: IMU 6-axis single read
* all_sensors() bulk-measurement helper used by the mbb? handler
*============================================================================*/
#include "cmd_common.h"
#include "cmd_sensor.h"
#include "dr_piezo.h"
/*==============================================================================
* msn? -> rsn: Battery level ADC measurement
*
* Request: [TAG 4B "msn?"] [CRC 2B]
* Response: rsn: + battery voltage in mV (sent from battery_saadc.c callback)
*============================================================================*/
int Cmd_msn(const ParsedCmd *cmd)
{
(void)cmd;
battery_level_meas();
return 1;
}
/*==============================================================================
* mst? -> rso: Temperature with piezo power cycle
*
* Request: [TAG 4B "mst?"] [CRC 2B]
* Response: [TAG 4B "rso:"] [temp_x100 2B BE] [CRC 2B]
*
* TMP235 shares the piezo TX/RX power rail. This command handles the full
* sequence: power ON -> measure -> power OFF, so the caller doesn't need
* to send mpa?/mpb? separately.
* Response is sent from the TMP235 SAADC callback (tmp235_voltage_handler).
*============================================================================*/
int Cmd_mst(const ParsedCmd *cmd)
{
uint32_t timeout_cnt;
(void)cmd;
if (!dr_piezo_is_power_on())
{
dr_piezo_power_on();
}
tmp235_saadc_done = false;
tmp235_voltage_level_meas();
for (timeout_cnt = 0; !tmp235_saadc_done && timeout_cnt < 100; timeout_cnt++)
{
dr_sd_delay_ms(1);
}
dr_piezo_power_off();
return 1;
}
/*==============================================================================
* msp? -> rsp: IMU 6-axis single read
*
* Request: [TAG 4B "msp?"] [CRC 2B]
* Response: rsp: + accel(xyz) + gyro(xyz) (transmitted inside imu_read_direct)
*
* Reads IMU registers directly over I2C and transmits over BLE. Synchronous.
*============================================================================*/
int Cmd_msp(const ParsedCmd *cmd)
{
(void)cmd;
hw_i2c_init_once();
imu_read_direct();
return 1;
}
/*==============================================================================
* all_sensors() - Bulk-measurement helper for the mbb? handler
*
* Stores sensor values into globals via info4 mode, then transmits them as a
* single rbb: packet. SAADC measurements run asynchronously (callback), so
* dr_sd_delay_ms() is used to wait for completion.
*
* Order: battery -> IMU -> (Piezo TX/RX ON) -> temperature
* Response: rbb: [batt 2B] [IMU 6x2B] [temp 2B] = 20 bytes = 10 words
*============================================================================*/
void all_sensors(void)
{
uint8_t *buf;
uint32_t timeout_cnt;
info4 = true;
/* 1. Battery voltage -> info_batt */
battery_saadc_done = false;
battery_level_meas();
for (timeout_cnt = 0; !battery_saadc_done && timeout_cnt < 100; timeout_cnt++)
{
dr_sd_delay_ms(1);
}
/* 2. IMU 6-axis single read -> info_imu[6] */
hw_i2c_init_once();
imu_read_direct();
/* 3. Temperature -> info_temp (TMP235 needs Piezo TX/RX power) */
if (!dr_piezo_is_power_on())
{
dr_piezo_power_on();
}
tmp235_saadc_done = false;
tmp235_voltage_level_meas();
for (timeout_cnt = 0; !tmp235_saadc_done && timeout_cnt < 100; timeout_cnt++)
{
dr_sd_delay_ms(1);
}
info4 = false;
/* Assemble and transmit the rbb: packet (dedicated buffer to avoid
collision with ble_bin_buffer used by the async ADC state machine) */
static uint8_t rbb_buf[20];
buf = rbb_buf;
buf[0] = 'r'; buf[1] = 'b'; buf[2] = 'b'; buf[3] = ':';
buf[4] = (uint8_t)(info_batt >> 8);
buf[5] = (uint8_t)(info_batt & 0xFF);
for (int i = 0; i < 6; i++)
{
buf[6 + i * 2] = (uint8_t)(info_imu[i] >> 8);
buf[6 + i * 2 + 1] = (uint8_t)(info_imu[i] & 0xFF);
}
buf[18] = (uint8_t)(info_temp >> 8);
buf[19] = (uint8_t)(info_temp & 0xFF);
dr_binary_tx_safe(buf, 10); /* 20 bytes = 10 words */
}
@@ -0,0 +1,16 @@
/*==============================================================================
* cmd_sensor.h - Sensor measurement handlers (battery / temperature / IMU)
*============================================================================*/
#ifndef CMD_SENSOR_H
#define CMD_SENSOR_H
#include "parser.h"
int Cmd_msn(const ParsedCmd *cmd); /* msn? -> rsn: battery ADC measurement */
int Cmd_mst(const ParsedCmd *cmd); /* mst? -> rso: TMP235 with piezo power cycle */
int Cmd_msp(const ParsedCmd *cmd); /* msp? -> rsp: IMU 6-axis single read */
/* Helper for the mbb? handler: sequentially measures battery / IMU / temperature, then emits a single rbb: response.
* Called from Cmd_mbb() in cmd_piezo.c. */
void all_sensors(void);
#endif /* CMD_SENSOR_H */
File diff suppressed because it is too large Load Diff
@@ -1,24 +1,75 @@
/* parser.h */ /*==============================================================================
* parser.h - BLE command parser public API
*
* Parser infrastructure (parsing/CRC/dispatch) and command handlers are
* decoupled:
* - Parser: parser.c (defines the functions here)
* - Handlers: handlers/cmd_device.c / cmd_info.c / ...
* - Table: cmd_table.c (injected via cmd_table_init)
*
* Boot sequence: main.c calls cmd_table_init() -> dr_parser_init() to inject
* the command table pointer into the parser.
*============================================================================*/
#ifndef PARSER_H #ifndef PARSER_H
#define PARSER_H #define PARSER_H
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
/* Platform-dependent function pointer set */ /* Maximum payload length (excludes TAG) */
#define DR_MAX_DATA 128
/*------------------------------------------------------------------------------
* Platform interface
*----------------------------------------------------------------------------*/
typedef struct { typedef struct {
void (*log)(const char *fmt, ...); void (*log)(const char *fmt, ...);
void (*tx_bin)(const uint8_t *buf, uint16_t len); void (*tx_bin)(const uint8_t *buf, uint16_t len); /* len: word (uint16) count */
bool crc_check; bool crc_check;
} dr_platform_if_t; } dr_platform_if_t;
/* Global interface & log flag */
extern dr_platform_if_t g_plat; extern dr_platform_if_t g_plat;
extern bool g_log_enable; extern bool g_log_enable;
/* Main parser entry point */ /*------------------------------------------------------------------------------
* Parsed command - input passed to handlers
*----------------------------------------------------------------------------*/
typedef struct {
char tag[5]; /* Command TAG (e.g. "mid?") + '\0' */
uint8_t data[DR_MAX_DATA]; /* Payload bytes following the TAG */
uint8_t data_len; /* Valid length of data[] */
} ParsedCmd;
/*------------------------------------------------------------------------------
* Command table entry - defined in cmd_table.c
*----------------------------------------------------------------------------*/
typedef struct {
char tag[5]; /* TAG: 4 chars + '\0' */
bool enabled; /* false -> rxd: response */
int (*handler)(const ParsedCmd *cmd); /* 1=success, 0=failure */
} CmdEntry;
/*------------------------------------------------------------------------------
* Parser API
*----------------------------------------------------------------------------*/
/* Inject command table (called once at boot). */
void dr_parser_init(const CmdEntry *table, uint16_t count);
/* Entry point: invoked from BLE NUS / UART receive callbacks. */
int dr_cmd_parser(const uint8_t *buf, uint8_t len); int dr_cmd_parser(const uint8_t *buf, uint8_t len);
#endif /* PARSER_H */ /*------------------------------------------------------------------------------
* Public utilities for handlers
*----------------------------------------------------------------------------*/
/* Extract Big-Endian uint16. Returns false if not enough data. */
bool dr_get_u16(const ParsedCmd *cmd, uint8_t word_index, uint16_t *out);
/* Extract null-terminated ASCII string. out must hold at least max_len+1. */
void dr_get_ascii(const ParsedCmd *cmd, uint8_t offset, char *out, uint8_t max_len);
/* CRC16 (Nordic SDK CRC-CCITT variant). p_crc=NULL -> initial 0xFFFF. */
uint16_t dr_crc16_compute(const uint8_t *p_data, uint32_t size, const uint16_t *p_crc);
#endif /* PARSER_H */
@@ -1,39 +1,21 @@
/******************************************************************************* /*==============================================================================
TEST medi50 Dec 23 * fstorage.c - FDS (Flash Data Storage) configuration module
******************************************************************************/
/**
* @file fstorage.c
* @brief FDS(Flash Data Storage) 기반 설정 저장 모듈
* *
* 외부 EEPROM을 대체하여 nRF52840 내장 플래시에 장치 설정을 저장/로드한다. * Stores and loads device configuration to/from nRF52840 internal flash,
* replacing external EEPROM. Coexists safely with the SoftDevice.
* *
* [레코드 관리] * Record management:
* - CONFIG_FILE = 0x8010, CONFIG_REC_KEY = 0x7010 으로 단일 레코드를 관리한다. * CONFIG_FILE = 0x8010, CONFIG_REC_KEY = 0x7010 (single record)
* *
* [config_data_t 구조체 필드] * Magic number validation:
* - magic(4B) : 포맷 확인용 매직 넘버 (0x20231226 -> 0x20260318(기본값 재생성을 위해 매직넘버 변경)) * Data loaded from flash is checked against CONFIG_MAGIC_NUMBER_VALUE.
* - hw_no(12B) : 하드웨어 버전 (기본값: "") * Mismatch -> reinitialise with factory defaults.
* - serial_no(12B) : 시리얼 번호 (기본값: "VB026030000")
* - passkey(6B) : BLE 페어링용 정적 패스키(기본값: "123456")
* - bond_data_delete(1B) : 본딩 데이터 삭제 플래그(기본값: 1)
* - reset_status(1B) : 리셋 상태 값(기본값: 99)
* - life_cycle(4B) : 장치 사용 횟수(기본값: 0)
* - piezo_freq_option : Piezo 송신 펄스 주파수(기본값: 1=2.1MHz)
* - piezo_cycles : Piezo 송신 펄스 사이클 수 (기본값: 7)
* - piezo_averaging : Piezo 채널당 반복 측정 횟수 (기본값: 5)
* - piezo_delay_us : Piezo 송신 펄스 출력 후 ADC 시작 시까지 대기시간(us) (기본값: 10)
* - piezo_num_samples : Piezo 측정 ADC 샘플 개수 (기본값: 100)
* *
* [매직 넘버 검증] * FDS event post-processing:
* - 플래시에서 로드한 데이터의 magic 값이 0x20231226과 일치하는지 확인하여 * After a write/update completes, pending actions are executed:
* 유효한 설정인지 판별한다. 불일치 시 기본값으로 초기화한다. * power-off (go_device_power_off), sleep (go_sleep_mode_enter),
* * system reset (go_NVIC_SystemReset).
* [FDS 이벤트 후처리] *============================================================================*/
* - FDS 쓰기/업데이트 완료 이벤트 수신 후, 대기 중인 후처리를 수행한다:
* 전원 OFF (go_device_power_off), 슬립 진입 (go_sleep_mode_enter),
* 시스템 리셋 (go_NVIC_SystemReset)
*/
#include "sdk_config.h" #include "sdk_config.h"
@@ -60,29 +42,28 @@
#include "debug_print.h" #include "debug_print.h"
/* FDS 레코드 식별자: 파일 ID와 레코드 키로 단일 설정 레코드를 관리 */ /* FDS record identifiers */
#define CONFIG_FILE (0x8010) #define CONFIG_FILE (0x8010)
#define CONFIG_REC_KEY (0x7010) #define CONFIG_REC_KEY (0x7010)
/* 매직 넘버: 플래시에 저장된 데이터가 유효한 설정인지 판별하는 데 사용 */ /* Magic number used to validate stored data */
#define CONFIG_MAGIC_NUMBER_VALUE (0x20260319) #define CONFIG_MAGIC_NUMBER_VALUE (0x20260319)
/* 전역 설정 데이터 구조체 인스턴스 */ /* Global configuration instance */
config_data_t m_config; config_data_t m_config;
/* FDS 쓰기 완료 후 수행할 후처리 플래그 (main.c에서 선언) */ /* Post-processing flags (declared in main.c) */
extern bool go_device_power_off; /* 전원 OFF 요청 */ extern bool go_device_power_off;
extern bool go_sleep_mode_enter; /* 슬립 모드 진입 요청 */ extern bool go_sleep_mode_enter;
extern bool go_NVIC_SystemReset; /* 시스템 리셋 요청 */ extern bool go_NVIC_SystemReset;
/* FDS 초기화 완료 여부 플래그 (fds_evt_handler에서 true로 설정) */ /* FDS initialisation complete flag (set in fds_evt_handler) */
static bool volatile m_fds_initialized; static bool volatile m_fds_initialized;
/* FDS 쓰기 진행 중 플래그: true이면 쓰기 완료 대기 중 */ /* FDS write-in-progress flag */
bool fds_flag_write = false; bool fds_flag_write = false;
/* FDS에 기록할 레코드 템플릿 (m_config 데이터를 가리킴) */ /* FDS record template pointing to m_config */
static fds_record_t const m_dummy_record = static fds_record_t const m_dummy_record =
{ {
.file_id = CONFIG_FILE, .file_id = CONFIG_FILE,
@@ -93,30 +74,30 @@ static fds_record_t const m_dummy_record =
}; };
/* 기본 설정값 상수 */ /* Default values */
int8_t reset_status_dflt = 99; /* 리셋 상태 기본값 */ int8_t reset_status_dflt = 99;
uint8_t static_passkey_dflt[6] = DEFAULT_PASSKEY; /* BLE 패스키 기본값 */ uint8_t static_passkey_dflt[6] = DEFAULT_PASSKEY;
/** /*==============================================================================
* @brief 기본 설정값 초기화 * fds_default_value_set - Initialise m_config with factory defaults
* *
* m_config 구조체의 각 필드를 공장 초기값으로 설정한다. * Called when flash contains no valid configuration or the magic number
* 플래시에 유효한 설정이 없거나 매직 넘버가 불일치할 때 호출된다.VB0HW0000 * does not match.
*/ *============================================================================*/
void fds_default_value_set(void) void fds_default_value_set(void)
{ {
/* HW Number - default from HARDWARE_VERSION */ /* HW number */
memset(m_config.hw_no, 0, 12); memset(m_config.hw_no, 0, 12);
memcpy(m_config.hw_no, HARDWARE_VERSION, strlen(HARDWARE_VERSION)); memcpy(m_config.hw_no, HARDWARE_VERSION, strlen(HARDWARE_VERSION));
/* Serial Number - default from SERIAL_NUMBER */ /* Serial number */
memset(m_config.serial_no, 0, 12); memset(m_config.serial_no, 0, 12);
memcpy(m_config.serial_no, SERIAL_NUMBER, strlen(SERIAL_NUMBER)); memcpy(m_config.serial_no, SERIAL_NUMBER, strlen(SERIAL_NUMBER));
/* Static Passkey */ /* Static passkey */
memcpy(m_config.static_passkey, static_passkey_dflt, 6); memcpy(m_config.static_passkey, static_passkey_dflt, 6);
/* Bond data delete — 기본값: 삭제 요청 없음 (공장 초기화 경로에서만 1로 세팅) */ /* Bond delete — default: no pending delete */
m_config.bond_data_delete = 0; m_config.bond_data_delete = 0;
/* Reset status */ /* Reset status */
@@ -125,64 +106,61 @@ void fds_default_value_set(void)
/* Device usage count */ /* Device usage count */
m_config.life_cycle = 0; m_config.life_cycle = 0;
/* 피에조 측정 파라미터 기본값 */ /* Piezo measurement parameter defaults */
m_config.piezo_freq_option = 1; /* 2.1MHz */ m_config.piezo_freq_option = 1; /* 2.1 MHz */
m_config.piezo_delay_us = 10; /* 버스트 후 10us */ m_config.piezo_delay_us = 10; /* 10 us after burst */
m_config.piezo_num_samples = 100; /* 100샘플 */ m_config.piezo_num_samples = 100; /* 100 samples */
m_config.piezo_cycles = 7; /* 7사이클 */ m_config.piezo_cycles = 7; /* 7 cycles */
m_config.piezo_averaging = 3; /* 3회 평균화 */ m_config.piezo_averaging = 3; /* 3x averaging */
/* Factory provisioning — default: not provisioned */
m_config.factory_provisioned = 0;
} }
/* 마지막 FDS 이벤트 ID 저장 (디버깅용) */ /* Last FDS event ID (for debugging) */
static volatile uint8_t fds_last_evt = 0xFF; static volatile uint8_t fds_last_evt = 0xFF;
/** /*==============================================================================
* @brief FDS 이벤트 콜백 핸들러 * fds_evt_handler - FDS event callback
* *
* FDS 내부에서 비동기 작업이 완료될 때 호출된다. * FDS_EVT_INIT : initialisation complete -> set m_fds_initialized
* - FDS_EVT_INIT : FDS 초기화 완료 → m_fds_initialized 플래그 설정 * FDS_EVT_WRITE : new record written -> clear fds_flag_write
* - FDS_EVT_WRITE : 새 레코드 쓰기 완료 → fds_flag_write 해제 * FDS_EVT_UPDATE : record updated -> clear flag, then execute any
* - FDS_EVT_UPDATE : 레코드 업데이트 완료 → fds_flag_write 해제 후 * pending power-off / sleep / system reset
* 대기 중인 전원 OFF / 슬립 진입 / 시스템 리셋 수행 *============================================================================*/
* - FDS_EVT_DEL_RECORD / FDS_EVT_DEL_FILE / FDS_EVT_GC : 현재 미사용
*/
static void fds_evt_handler( fds_evt_t const *p_evt ) static void fds_evt_handler( fds_evt_t const *p_evt )
{ {
fds_last_evt = p_evt->id; fds_last_evt = p_evt->id;
switch( p_evt->id ) switch (p_evt->id)
{ {
case FDS_EVT_INIT: case FDS_EVT_INIT:
if( p_evt->result == NRF_SUCCESS ) if (p_evt->result == NRF_SUCCESS)
{ {
m_fds_initialized = true; m_fds_initialized = true;
} }
break; break;
case FDS_EVT_WRITE: case FDS_EVT_WRITE:
{
fds_flag_write = false; fds_flag_write = false;
}
break; break;
case FDS_EVT_UPDATE: case FDS_EVT_UPDATE:
{
fds_flag_write = false; fds_flag_write = false;
if(go_device_power_off == true) { if (go_device_power_off == true)
/* After flash writing completed, System Power Off */ {
device_power_off(); device_power_off();
} }
if(go_sleep_mode_enter == true) { if (go_sleep_mode_enter == true)
/* After flash writing completed, System go to Sleep Mode */ {
sleep_mode_enter(); sleep_mode_enter();
} }
if(go_NVIC_SystemReset == true) { if (go_NVIC_SystemReset == true)
/* After flash writing completed, System Reset */ {
DBG_PRINTF("Off FDS_EVENT\r\n"); DBG_PRINTF("Off FDS_EVENT\r\n");
NVIC_SystemReset(); NVIC_SystemReset();
}
} }
break; break;
@@ -201,22 +179,20 @@ static void fds_evt_handler( fds_evt_t const *p_evt )
} }
/** /*==============================================================================
* @brief FDS 초기화 완료 대기 * wait_for_fds_ready - Block until FDS initialisation completes
* *
* m_fds_initialized 플래그가 true가 될 때까지 대기한다. * Times out after 3 seconds with an error log.
* 최대 3초(3000ms) 타임아웃이 설정되어 있으며, *============================================================================*/
* 타임아웃 시 에러 로그를 출력하고 반환한다.
*/
static void wait_for_fds_ready( void ) static void wait_for_fds_ready( void )
{ {
uint32_t timeout = 0; uint32_t timeout = 0;
while( !m_fds_initialized ) while(!m_fds_initialized)
{ {
nrf_pwr_mgmt_run(); nrf_pwr_mgmt_run();
nrf_delay_ms(1); nrf_delay_ms(1);
timeout++; timeout++;
if (timeout > 3000) /* 3 second timeout */ if (timeout > 3000)
{ {
DBG_PRINTF("[FDS] TIMEOUT!\r\n"); DBG_PRINTF("[FDS] TIMEOUT!\r\n");
break; break;
@@ -225,27 +201,25 @@ static void wait_for_fds_ready( void )
} }
/** /*==============================================================================
* @brief FDS에서 설정 로드 * config_load - Load configuration from FDS
* *
* 플래시에서 CONFIG_FILE/CONFIG_REC_KEY 레코드를 검색하여 m_config에 로드한다. * Flow:
* * 1. Search for CONFIG_FILE / CONFIG_REC_KEY (retry up to 10x, 100 ms apart)
* 동작 흐름: * 2. If found:
* 1. fds_record_find()로 레코드 검색 (실패 시 최대 10회 재시도, 100ms 간격) * - Open the record (on CRC error: delete and regenerate defaults)
* 2. 레코드 발견 시: * - Copy into m_config
* - fds_record_open()으로 열기 (CRC 에러 시 삭제 후 기본값으로 재생성) * - Validate magic number; on mismatch: delete -> defaults -> rewrite
* - 데이터를 m_config로 복사 * 3. If not found:
* - 매직 넘버 불일치 시 기존 레코드 삭제 → 기본값 설정 → 재기록 * - Write factory defaults as a new record, then reload
* 3. 레코드 미발견 시: *============================================================================*/
* - 기본값으로 새 레코드 생성 후 다시 로드
*/
void config_load( void ) void config_load( void )
{ {
ret_code_t rc; ret_code_t rc;
fds_record_desc_t desc = { 0 }; fds_record_desc_t desc = { 0 };
fds_find_token_t tok = { 0 }; fds_find_token_t tok = { 0 };
uint8_t cfg_retry = 0; uint8_t cfg_retry = 0;
uint32_t fds_wait_cnt = 0; // FDS write 대기 카운터 uint32_t fds_wait_cnt = 0;
cfg_load_start: cfg_load_start:
memset((char *)&desc, 0, sizeof(desc)); memset((char *)&desc, 0, sizeof(desc));
@@ -262,12 +236,10 @@ void config_load( void )
goto cfg_load_start; goto cfg_load_start;
} }
if( rc == NRF_SUCCESS ) if (rc == NRF_SUCCESS)
{ {
/* A config file is in flash. Let's update it. */
fds_flash_record_t config = { 0 }; fds_flash_record_t config = { 0 };
/* Open the record and read its contents. */
rc = fds_record_open(&desc, &config); rc = fds_record_open(&desc, &config);
if (rc != NRF_SUCCESS) if (rc != NRF_SUCCESS)
{ {
@@ -279,28 +251,24 @@ void config_load( void )
goto cfg_load_write_new; goto cfg_load_write_new;
} }
/* Copy the configuration from flash into m_config. */
memcpy(&m_config, config.p_data, sizeof(config_data_t)); memcpy(&m_config, config.p_data, sizeof(config_data_t));
/* Close the record when done reading. */
rc = fds_record_close(&desc); rc = fds_record_close(&desc);
APP_ERROR_CHECK(rc); APP_ERROR_CHECK(rc);
DBG_PRINTF("[FDS] magic=0x%08X (expect 0x%08X)\r\n", m_config.magic_number, CONFIG_MAGIC_NUMBER_VALUE); DBG_PRINTF("[FDS] magic=0x%08X (expect 0x%08X)\r\n", m_config.magic_number, CONFIG_MAGIC_NUMBER_VALUE);
if( m_config.magic_number != (uint32_t)CONFIG_MAGIC_NUMBER_VALUE ) if (m_config.magic_number != (uint32_t)CONFIG_MAGIC_NUMBER_VALUE)
{ // first init {
DBG_PRINTF("[FDS] FORMAT! overwriting with defaults\r\n"); DBG_PRINTF("[FDS] FORMAT! overwriting with defaults\r\n");
rc = fds_record_delete(&desc); rc = fds_record_delete(&desc);
APP_ERROR_CHECK(rc); APP_ERROR_CHECK(rc);
m_config.magic_number = CONFIG_MAGIC_NUMBER_VALUE; m_config.magic_number = CONFIG_MAGIC_NUMBER_VALUE;
// default....
fds_default_value_set(); fds_default_value_set();
/* Write the updated record to flash. */
rc = fds_record_update(&desc, &m_dummy_record); rc = fds_record_update(&desc, &m_dummy_record);
if( (rc != NRF_SUCCESS) && (rc == FDS_ERR_NO_SPACE_IN_FLASH) ) if ((rc != NRF_SUCCESS) && (rc == FDS_ERR_NO_SPACE_IN_FLASH))
{ {
rc = fds_gc(); rc = fds_gc();
APP_ERROR_CHECK(rc); APP_ERROR_CHECK(rc);
@@ -317,10 +285,8 @@ void config_load( void )
{ {
cfg_load_write_new: cfg_load_write_new:
DBG_PRINTF("[FDS] New - writing defaults\r\n"); DBG_PRINTF("[FDS] New - writing defaults\r\n");
/* System config not found (or corrupt); write a new one. */
m_config.magic_number = CONFIG_MAGIC_NUMBER_VALUE; m_config.magic_number = CONFIG_MAGIC_NUMBER_VALUE;
// default....
fds_default_value_set(); fds_default_value_set();
fds_flag_write = true; fds_flag_write = true;
@@ -332,22 +298,22 @@ void config_load( void )
fds_flag_write = false; fds_flag_write = false;
} }
fds_wait_cnt = 0; // fds_wait_cnt = 0;
while(fds_flag_write && fds_wait_cnt < 3000) // FDS write 최대 3초 타임아웃 while (fds_flag_write && fds_wait_cnt < 3000) /* 3 second timeout */
{ {
nrf_pwr_mgmt_run(); nrf_pwr_mgmt_run();
nrf_delay_ms(1); nrf_delay_ms(1);
fds_wait_cnt++; fds_wait_cnt++;
} }
if(fds_flag_write) // FDS write 타임아웃 시 플래그 강제 해제 if (fds_flag_write)
{ {
DBG_PRINTF("[FDS] write TIMEOUT! forcing flag clear\r\n"); DBG_PRINTF("[FDS] write TIMEOUT! forcing flag clear\r\n");
fds_flag_write = false; fds_flag_write = false;
} }
if( (rc != NRF_SUCCESS) && (rc == FDS_ERR_NO_SPACE_IN_FLASH) ) if ((rc != NRF_SUCCESS) && (rc == FDS_ERR_NO_SPACE_IN_FLASH))
{ {
rc = fds_gc(); rc = fds_gc();
APP_ERROR_CHECK(rc); APP_ERROR_CHECK(rc);
@@ -363,21 +329,19 @@ void config_load( void )
} }
/** /*==============================================================================
* @brief 현재 설정을 FDS에 저장 * config_save - Persist current configuration to FDS
* *
* m_config의 내용을 플래시에 기록한다. * Flow:
* 1. If a previous FDS write is in progress, wait up to 3 seconds
* 2. Fix magic number if needed
* 3. If existing record found: fds_record_update()
* - On no-space: GC then retry
* 4. If not found: fds_record_write() (new record)
* *
* 동작 흐름: * Write completion is asynchronous (fds_evt_handler). Post-processing
* 1. 이전 FDS 쓰기 작업이 진행 중이면 최대 3초 대기 * (power-off / sleep / reset) may follow.
* 2. 매직 넘버가 올바르지 않으면 보정 *============================================================================*/
* 3. 기존 레코드가 있으면 fds_record_update()로 갱신
* - 플래시 공간 부족 시 GC(가비지 컬렉션) 수행 후 재시도
* 4. 기존 레코드가 없으면 fds_record_write()로 새로 생성
*
* 참고: 쓰기 완료는 fds_evt_handler()에서 비동기로 처리되며,
* 완료 후 전원 OFF/슬립/리셋 등의 후처리가 수행될 수 있다.
*/
void config_save( void ) void config_save( void )
{ {
ret_code_t rc; ret_code_t rc;
@@ -405,7 +369,7 @@ void config_save( void )
} }
} }
if( m_config.magic_number != (uint32_t)CONFIG_MAGIC_NUMBER_VALUE ) if (m_config.magic_number != (uint32_t)CONFIG_MAGIC_NUMBER_VALUE)
{ {
m_config.magic_number = CONFIG_MAGIC_NUMBER_VALUE; m_config.magic_number = CONFIG_MAGIC_NUMBER_VALUE;
} }
@@ -415,13 +379,13 @@ void config_save( void )
rc = fds_record_find(CONFIG_FILE, CONFIG_REC_KEY, &desc, &tok); rc = fds_record_find(CONFIG_FILE, CONFIG_REC_KEY, &desc, &tok);
DBG_PRINTF("[CFG_SAVE] find rc=%u\r\n", rc); DBG_PRINTF("[CFG_SAVE] find rc=%u\r\n", rc);
if( rc == NRF_SUCCESS ) if (rc == NRF_SUCCESS)
{ {
fds_flag_write = true; fds_flag_write = true;
rc = fds_record_update(&desc, &m_dummy_record); rc = fds_record_update(&desc, &m_dummy_record);
DBG_PRINTF("[CFG_SAVE] update rc=%u\r\n", rc); DBG_PRINTF("[CFG_SAVE] update rc=%u\r\n", rc);
if( rc == FDS_ERR_NO_SPACE_IN_FLASH ) if (rc == FDS_ERR_NO_SPACE_IN_FLASH)
{ {
fds_flag_write = false; fds_flag_write = false;
rc = fds_gc(); rc = fds_gc();
@@ -431,7 +395,7 @@ void config_save( void )
DBG_PRINTF("[CFG_SAVE] retry rc=%u\r\n", rc); DBG_PRINTF("[CFG_SAVE] retry rc=%u\r\n", rc);
} }
if( rc != NRF_SUCCESS ) if (rc != NRF_SUCCESS)
{ {
DBG_PRINTF("[CFG_SAVE] FAIL rc=%u\r\n", rc); DBG_PRINTF("[CFG_SAVE] FAIL rc=%u\r\n", rc);
fds_flag_write = false; fds_flag_write = false;
@@ -444,7 +408,7 @@ void config_save( void )
rc = fds_record_write(&desc, &m_dummy_record); rc = fds_record_write(&desc, &m_dummy_record);
DBG_PRINTF("[CFG_SAVE] write rc=%u\r\n", rc); DBG_PRINTF("[CFG_SAVE] write rc=%u\r\n", rc);
if( rc != NRF_SUCCESS ) if ( rc != NRF_SUCCESS )
{ {
DBG_PRINTF("[CFG_SAVE] FAIL rc=%u\r\n", rc); DBG_PRINTF("[CFG_SAVE] FAIL rc=%u\r\n", rc);
fds_flag_write = false; fds_flag_write = false;
@@ -455,37 +419,33 @@ void config_save( void )
} }
/** /*==============================================================================
* @brief config_load()의 래퍼 함수 * fs_set_value - Wrapper for config_load()
* *============================================================================*/
* 외부 모듈에서 설정 로드를 요청할 때 사용한다.
*/
void fs_set_value(void) void fs_set_value(void)
{ {
config_load(); config_load();
} }
/** /*==============================================================================
* @brief FDS 초기화 * fs_storage_init - Initialise FDS
* *
* 부팅 시 호출되어 FDS 모듈을 초기화한다. * Called once at boot:
* 1. fds_register()로 이벤트 핸들러 등록 * 1. Register event handler
* 2. fds_init()로 FDS 초기화 시작 * 2. Start FDS initialisation
* 3. wait_for_fds_ready()로 초기화 완료 대기 (최대 3초) * 3. Wait for completion (up to 3 seconds)
* 4. fds_stat()로 플래시 상태 확인 * 4. Verify flash stats
*/ *============================================================================*/
void fs_storage_init(void) void fs_storage_init(void)
{ {
ret_code_t rc; ret_code_t rc;
/* Register first to receive an event when initialization is complete. */
rc = fds_register(fds_evt_handler); rc = fds_register(fds_evt_handler);
APP_ERROR_CHECK(rc); APP_ERROR_CHECK(rc);
rc = fds_init(); rc = fds_init();
APP_ERROR_CHECK(rc); APP_ERROR_CHECK(rc);
/* Wait for fds to initialize. */
wait_for_fds_ready(); wait_for_fds_ready();
fds_stat_t stat = { 0 }; fds_stat_t stat = { 0 };
@@ -1,32 +1,26 @@
/******************************************************************************* /*==============================================================================
* @file fstorage.h * fstorage.h - FDS (Flash Data Storage) configuration module interface
* @author CandyPops Co.
* @version V1.0.0
* @date 2022-09-05
* @brief FDS(Flash Data Storage) 기반 설정 저장 모듈 인터페이스
*******************************************************************************
* *
* [헤더 개요] * Stores and loads device configuration to/from the nRF52840 internal flash
* nRF52840 내장 플래시에 디바이스 설정을 저장/로드하는 FDS 모듈의 공용 API. * via the Nordic FDS library. Replaces external EEPROM and coexists safely
* 외부 EEPROM을 대체하며, SoftDevice와 공존하여 플래시를 안전하게 관리한다. * with the SoftDevice.
* *
* [config_data_t 구조체] (45바이트, 패킹됨) * config_data_t (49 bytes, packed):
* magic_number(4B): 포맷 확인용 (0x20231226) * magic_number (4B) : format validation (0x20231226)
* hw_no(12B): 하드웨어 번호 * hw_no (12B): hardware version string
* serial_no(12B): 시리얼 번호 (BLE 디바이스 이름으로도 사용) * serial_no (12B): serial number (also used as BLE device name)
* static_passkey(6B): BLE 페어링 패스키 (숫자 6자리) * static_passkey(6B) : BLE pairing passkey (6-digit numeric)
* bond_data_delete(1B): 본딩 삭제 플래그 * bond_data_delete(1B): bond-delete flag
* reset_status(1B): 리셋 상태 코드 * reset_status (1B) : reset cause code
* pd_adc_cnt(1B): ADC 샘플링 횟수 * life_cycle (4B) : device usage count
* pd_delay_us(2B): PD 안정화 딜레이 (마이크로초) * piezo_* (8B) : piezo measurement parameters
* life_cycle(4B): 디바이스 사용 횟수 * factory_provisioned(1B): passkey provisioning lock flag
* *
* [주요 API] * API:
* fs_storage_init(): FDS 초기화 (부트 시 1회) * fs_storage_init() : initialise FDS (once at boot)
* config_load(): FDS에서 설정 로드 (없으면 기본값 생성) * config_load() : load config from FDS (creates defaults if absent)
* config_save(): 현재 설정을 FDS에 저장 * config_save() : persist current config to FDS
* *============================================================================*/
******************************************************************************/
#ifndef IHP_FSTORAGE_H_ #ifndef IHP_FSTORAGE_H_
#define IHP_FSTORAGE_H_ #define IHP_FSTORAGE_H_
@@ -36,21 +30,21 @@
#include "nordic_common.h" #include "nordic_common.h"
#include <stdint.h> #include <stdint.h>
/* ------------------------------------------------------------------------- /*------------------------------------------------------------------------------
* 기본 버전 정보 (FDS 기본값 및 빈 필드 복구 시 사용) * Default version identifiers (used for FDS defaults / empty field recovery)
* *
* 하드웨어 식별 코드 * Hardware ID:
* - VBTHW0100 = 개발(시험)용 Ver 1.00 * VBTHW0100 = development / test Ver 1.00
* - VB0HW0100 = 양산용 Ver 1.00 * VB0HW0100 = production Ver 1.00
* *
* Firmware 식별 코드 * Firmware ID:
* - VBTFW0100 = 개발(시험)용 Ver 1.00 * VBTFW0100 = development / test Ver 1.00
* - VB0FW0100 = 양산용 Ver 1.00 * VB0FW0100 = production Ver 1.00
* *
* 시리얼 넘버 식별 코드 * Serial number:
* - VBT26030001 = 개발(시험)용 26년 3월 생산 1번 * VBT26030001 = dev/test, manufactured Mar 2026, unit #1
* - VB026030001 = 양산용 26년 3월 생산 1번 * VB026030001 = production, Mar 2026, unit #1
------------------------------------------------------------------------- */ *----------------------------------------------------------------------------*/
#define HARDWARE_VERSION "VBTHW0100" #define HARDWARE_VERSION "VBTHW0100"
#define SERIAL_NUMBER "VBT26030001" #define SERIAL_NUMBER "VBT26030001"
#define DEFAULT_PASSKEY "123456" #define DEFAULT_PASSKEY "123456"
@@ -58,21 +52,24 @@
#pragma pack(1) #pragma pack(1)
typedef struct typedef struct
{ {
uint32_t magic_number; /* 4B - 포맷 확인용 매직 넘버 */ uint32_t magic_number; /* 4B - format validation magic */
char hw_no[12]; /* 12B - HW Version */ char hw_no[12]; /* 12B - HW version */
char serial_no[12]; /* 12B - Serial Number */ char serial_no[12]; /* 12B - serial number */
uint8_t static_passkey[6]; /* 6B - BLE Passkey */ uint8_t static_passkey[6]; /* 6B - BLE passkey */
uint8_t bond_data_delete; /* 1B - Bond delete flag */ uint8_t bond_data_delete; /* 1B - bond delete flag */
int8_t reset_status; /* 1B - Reset status */ int8_t reset_status; /* 1B - reset status */
uint32_t life_cycle; /* 4B - Device usage count */ uint32_t life_cycle; /* 4B - device usage count */
/* Piezo 측정 파라미터 - 8B */ /* Piezo measurement parameters - 8B */
uint8_t piezo_freq_option; /* 1B - Frequency : 송신 펄스 주파수 (0=1.8M, 1=2.1M, 2=2.0M, 3=1.7M) */ uint8_t piezo_freq_option; /* 1B - TX pulse frequency (0=1.8M, 1=2.1M, 2=2.0M, 3=1.7M) */
uint8_t piezo_cycles; /* 1B - Burst Cycle : 송신 펄스 사이클 수 (3~7) */ uint8_t piezo_cycles; /* 1B - burst pulse cycle count (3..7) */
uint16_t piezo_averaging; /* 2B - 평균화 수 : 채널당 반복 측정 횟수 (1~10) */ uint16_t piezo_averaging; /* 2B - averages per channel (1..10) */
uint16_t piezo_delay_us; /* 2B - 대기 시간(Delay) : 송신 펄스 출력 후 ADC 시작 시까지 대기시간 (us) (0~30) */ uint16_t piezo_delay_us; /* 2B - delay from TX pulse to ADC start (us) (0..30) */
uint16_t piezo_num_samples; /* 2B - 측정 ADC 샘플 개수 (80~140) */ uint16_t piezo_num_samples; /* 2B - ADC sample count (80..140) */
} config_data_t; /* Total: 48 bytes - FDS에 저장하는 디바이스 설정 */
/* Factory provisioning lock */
uint8_t factory_provisioned; /* 1B - 0=passkey not set, 1=passkey set (locked) */
} config_data_t; /* Total: 49 bytes */
extern config_data_t m_config; extern config_data_t m_config;
@@ -84,4 +81,3 @@ void fs_set_value(void);
void fs_storage_init(void); void fs_storage_init(void);
#endif /* IHP_FSTORAGE_H_ */ #endif /* IHP_FSTORAGE_H_ */
@@ -1,29 +1,18 @@
/******************************************************************************* /*==============================================================================
* @file i2c_manager.c * i2c_manager.c - HW / SW I2C mutex switching logic
* @brief Reliable HW↔SW I2C Switching Logic (with Mode Set Logging)
*******************************************************************************
* *
* [모듈 개요] * Manages mutually-exclusive HW (TWI peripheral) and SW (bit-bang) I2C modes.
* I2C 버스의 HW(하드웨어 TWI) / SW(소프트웨어 비트뱅) 모드 전환을 관리하는 모듈.
* *
* - HW I2C: nRF52840 내장 TWI 하드웨어 주변장치를 사용 (ICM42670P IMU 센서 통신용, 400kHz) * HW I2C : nRF52840 TWI hardware, 400 kHz Fast Mode (ICM42670P IMU)
* - SW I2C: GPIO 비트뱅 방식의 소프트웨어 I2C (현재 사용하지 않는 레거시 코드) * SW I2C : GPIO bit-bang (legacy, currently unused)
* *
* [핀 설정] * Pins:
* SCL = P1.14 (ICM42670_I2C_SCL_PIN) * SCL = P1.14 (ICM42670_I2C_SCL_PIN)
* SDA = P1.15 (ICM42670_I2C_SDA_PIN) * SDA = P1.15 (ICM42670_I2C_SDA_PIN)
* *
* [주요 함수] * The two bool flags HW_I2C_FRQ and SW_I2C_FRQ track the current mode.
* hw_i2c_init_once() : SW→HW 전환 또는 HW 초기화 (이미 HW 모드면 중복 초기화 방지) * Switching releases the old mode's resources before initialising the new one.
* sw_i2c_init_once() : HW→SW 전환 (TWI 해제 후 SW 모드 진입, 레거시) *============================================================================*/
* i2c_reset_state() : 모든 I2C 모드 플래그를 초기화 (HW/SW 모두 false)
*
* [동작 원리]
* HW_I2C_FRQ, SW_I2C_FRQ 두 개의 bool 플래그로 현재 모드를 추적하며,
* 한 번에 하나의 모드만 활성화되도록 상호 배제(mutex) 방식으로 관리한다.
* 모드 전환 시 기존 모드의 리소스를 먼저 해제한 후 새 모드를 초기화한다.
*
******************************************************************************/
#include "i2c_manager.h" #include "i2c_manager.h"
#include "debug_print.h" #include "debug_print.h"
@@ -34,137 +23,103 @@
#include "boards.h" #include "boards.h"
#include "system_interface.h" #include "system_interface.h"
/* 현재 I2C 모드 상태 플래그 (true = 해당 모드 활성화) */ /* Current I2C mode flags */
bool HW_I2C_FRQ = true; /* HW TWI 모드 활성 여부 (기본값: true, 초기 상태는 HW) */ bool HW_I2C_FRQ = true;
bool SW_I2C_FRQ = false; /* SW 비트뱅 모드 활성 여부 */ bool SW_I2C_FRQ = false;
/* TWI 인스턴스 번호 (nRF52840 TWI0, TWI1 두 개 지원) */ /* TWI instance (nRF52840 supports TWI0 and TWI1) */
#define TWI_INSTANCE 0 #define TWI_INSTANCE 0
/* TWI (I2C) 하드웨어 인스턴스 : IMU 드라이버에서 사용 - jhChun 26.03.16 */
const nrfx_twi_t m_twi = NRFX_TWI_INSTANCE(TWI_INSTANCE); const nrfx_twi_t m_twi = NRFX_TWI_INSTANCE(TWI_INSTANCE);
/* TWI (I2C) 해제 - jhChun 26.03.16 */ /* Disable and uninitialise the TWI peripheral, releasing GPIO pins. */
/* TWI 하드웨어를 비활성화하고 초기화 해제하여 GPIO 핀을 반환한다 */ static void twi_uninitialize(void)
static void twi_uninitialize(void){ {
nrfx_twi_disable(&m_twi); /* TWI 주변장치 비활성화 */ nrfx_twi_disable(&m_twi);
nrfx_twi_uninit(&m_twi); /* TWI 초기화 해제 (핀 리소스 반환) */ nrfx_twi_uninit(&m_twi);
} }
/* TWI (I2C) 하드웨어 초기화 (SCL/SDA, 400kHz) - jhChun 26.03.16 */ /* Initialise the TWI peripheral (SCL/SDA pins, 400 kHz, blocking mode). */
/* SCL, SDA 핀을 설정하고 400kHz Fast Mode로 TWI를 초기화 및 활성화한다 */ static void twi_initialize(void)
static void twi_initialize(void){ {
ret_code_t err_code; ret_code_t err_code;
/* TWI 설정 구조체: 핀 번호, 클럭 속도, 인터럽트 우선순위 지정 */ const nrfx_twi_config_t twi_config =
const nrfx_twi_config_t twi_config = { {
.scl = ICM42670_I2C_SCL_PIN, /* SCL 핀 (P1.14) */ .scl = ICM42670_I2C_SCL_PIN,
.sda = ICM42670_I2C_SDA_PIN, /* SDA 핀 (P1.15) */ .sda = ICM42670_I2C_SDA_PIN,
.frequency = NRF_TWI_FREQ_400K, /* 400kHz Fast Mode */ .frequency = NRF_TWI_FREQ_400K,
.interrupt_priority = APP_IRQ_PRIORITY_HIGH, /* 높은 인터럽트 우선순위 */ .interrupt_priority = APP_IRQ_PRIORITY_HIGH,
}; };
/* TWI 초기화 (이벤트 핸들러 NULL = 블로킹 모드) */
err_code = nrfx_twi_init(&m_twi, &twi_config, NULL, NULL); err_code = nrfx_twi_init(&m_twi, &twi_config, NULL, NULL);
APP_ERROR_CHECK(err_code); APP_ERROR_CHECK(err_code);
nrfx_twi_enable(&m_twi); /* TWI 주변장치 활성화 → I2C 통신 가능 */ nrfx_twi_enable(&m_twi);
} }
/* -------------------------------------------------------------------------- */ /*==============================================================================
/* HW (TWI) 초기화 */ * hw_i2c_init_once - Switch to or initialise HW TWI mode
/* -------------------------------------------------------------------------- */ *
/* * If SW mode is active, its flag is cleared first.
* HW I2C 모드로 전환하거나 초기화하는 함수. * If HW mode is already active, returns immediately (no re-init).
* - SW 모드가 활성화되어 있으면 SW 플래그를 해제하고 HW로 전환 *============================================================================*/
* - 이미 HW 모드이면 중복 초기화를 방지하여 바로 리턴
* - 최초 HW 초기화 시 twi_initialize()를 호출하여 TWI 하드웨어 설정
*/
void hw_i2c_init_once(void) void hw_i2c_init_once(void)
{ {
// SW 모드일 경우 강제 해제 후 HW 전환
if (SW_I2C_FRQ) if (SW_I2C_FRQ)
{ {
//DBG_PRINTF("[I2C]SW→HW\r\n");
// SW 리소스 해제 (필요 시 추가)
SW_I2C_FRQ = false; SW_I2C_FRQ = false;
nrf_delay_ms(2); /* 모드 전환 안정화 대기 (2ms) */ nrf_delay_ms(2); /* mode-switch settling */
} }
/* 이미 HW 모드가 활성화되어 있으면 중복 초기화 방지를 위해 즉시 리턴 */
// 이미 HW면 스킵
if (HW_I2C_FRQ) if (HW_I2C_FRQ)
{ {
// DBG_PRINTF("[I2C] HW I2C set\r\n");
return; return;
} }
/* HW TWI 하드웨어 초기화 수행 (SCL/SDA 핀 설정, 400kHz, 활성화) */
// 실제 HW 초기화
twi_initialize(); twi_initialize();
nrf_delay_ms(2); /* 초기화 후 안정화 대기 (2ms) */ nrf_delay_ms(2);
/* 모드 플래그 갱신: HW 활성, SW 비활성 */
HW_I2C_FRQ = true; HW_I2C_FRQ = true;
SW_I2C_FRQ = false; SW_I2C_FRQ = false;
// DBG_PRINTF("[I2C] HW I2C Mode set!\r\n");
} }
/* -------------------------------------------------------------------------- */ /*==============================================================================
/* SW (Port Bang-Bang) 초기화 */ * sw_i2c_init_once - Switch to SW bit-bang mode (legacy, unused)
/* -------------------------------------------------------------------------- */ *
/* * If HW mode is active, TWI is released first.
* SW I2C(비트뱅) 모드로 전환하는 함수. (현재 레거시, 사용하지 않음) * If SW mode is already active, returns immediately.
* - HW 모드가 활성화되어 있으면 TWI를 해제하고 SW로 전환 *============================================================================*/
* - 이미 SW 모드이면 중복 초기화를 방지하여 바로 리턴
* - power_control.c의 power_loop()에서 Step 0에서 호출됨
*/
void sw_i2c_init_once(void) void sw_i2c_init_once(void)
{ {
// HW 모드일 경우 강제 해제 후 SW 전환
if (HW_I2C_FRQ) if (HW_I2C_FRQ)
{ {
//DBG_PRINTF("[I2C]HW→SW\r\n"); nrfx_twi_disable(&m_twi);
nrfx_twi_uninit(&m_twi);
nrfx_twi_disable(&m_twi); /* TWI 비활성화 */ nrf_delay_ms(2);
nrfx_twi_uninit(&m_twi); /* TWI 초기화 해제 */
nrf_delay_ms(2); /* 모드 전환 안정화 대기 (2ms) */
HW_I2C_FRQ = false; HW_I2C_FRQ = false;
} }
// 이미 SW 모드면 재실행 금지
if (SW_I2C_FRQ) if (SW_I2C_FRQ)
{ {
// DBG_PRINTF("[I2C] SWI2C already initialized\r\n");
return; return;
} }
/* TWI 라인 완전 해제 후 SW 비트뱅 모드 진입 */ twi_uninitialize();
// 실제 SW 초기화 nrf_delay_ms(1);
twi_uninitialize(); // TWI 라인 해제
nrf_delay_ms(1); /* 해제 후 안정화 대기 (1ms) */
/* 모드 플래그 갱신: SW 활성, HW 비활성 */
SW_I2C_FRQ = true; SW_I2C_FRQ = true;
HW_I2C_FRQ = false; HW_I2C_FRQ = false;
// DBG_PRINTF("[I2C] SW I2C Mode set!\r\n");
} }
/* -------------------------------------------------------------------------- */ /*==============================================================================
/* 전체 리셋 */ * i2c_reset_state - Clear all mode flags
/* -------------------------------------------------------------------------- */ *
/* * Forces re-initialisation on the next init call. Used for system reset
* 모든 I2C 모드 플래그를 초기화하는 함수. * or error recovery.
* HW/SW 모두 비활성 상태로 만들어, 다음 init 호출 시 강제로 재초기화되도록 한다. *============================================================================*/
* 주로 시스템 리셋이나 에러 복구 시 사용.
*/
void i2c_reset_state(void) void i2c_reset_state(void)
{ {
HW_I2C_FRQ = false; /* HW 모드 플래그 초기화 */ HW_I2C_FRQ = false;
SW_I2C_FRQ = false; /* SW 모드 플래그 초기화 */ SW_I2C_FRQ = false;
DBG_PRINTF("Flags reset\r\n"); DBG_PRINTF("Flags reset\r\n");
} }
@@ -1,50 +1,31 @@
/******************************************************************************* /*==============================================================================
* @file i2c_manager.h * i2c_manager.h - HW / SW I2C mutex control
* @brief Common header for HW/SW I2C mutex control
*******************************************************************************
* *
* [헤더 개요] * Manages the mutually-exclusive HW TWI and SW bit-bang I2C modes.
* I2C 버스 HW/SW 모드 전환 관리자의 공용 인터페이스 헤더. * Only one mode may be active at a time.
* *
* - HW_I2C_FRQ: HW TWI 모드 활성 여부 (true = HW I2C 사용 중) * Flags:
* - SW_I2C_FRQ: SW 비트뱅 모드 활성 여부 (true = SW I2C 사용 중) * HW_I2C_FRQ : true when HW TWI mode is active
* * SW_I2C_FRQ : true when SW bit-bang mode is active
* 두 플래그는 상호 배제적으로 동작하며, 동시에 true가 되지 않도록 관리된다. *============================================================================*/
*
******************************************************************************/
#ifndef __I2C_MANAGER_H__ #ifndef __I2C_MANAGER_H__
#define __I2C_MANAGER_H__ #define __I2C_MANAGER_H__
#include <stdbool.h> #include <stdbool.h>
#include "app_error.h" #include "app_error.h"
/* I2C 모드 상태 플래그 (외부 참조용) */ extern bool HW_I2C_FRQ;
extern bool HW_I2C_FRQ; /* HW TWI 모드 활성 여부 */ extern bool SW_I2C_FRQ;
extern bool SW_I2C_FRQ; /* SW 비트뱅 모드 활성 여부 */
/** /* Initialise HW I2C (TWI) mode. If SW mode is active it is released first.
* @brief HW I2C(TWI) 모드 초기화 (중복 초기화 방지) * No-op if HW mode is already active. Call before ICM42670P IMU access. */
*
* SW 모드가 활성화되어 있으면 해제 후 HW로 전환한다.
* 이미 HW 모드이면 아무 동작 없이 리턴한다.
* ICM42670P IMU 센서 통신 전에 호출하여 HW I2C를 준비한다.
*/
void hw_i2c_init_once(void); void hw_i2c_init_once(void);
/** /* Initialise SW I2C (bit-bang) mode (legacy, currently unused).
* @brief SW I2C(비트뱅) 모드 초기화 (레거시, 현재 미사용) * If HW mode is active it is released first. */
*
* HW 모드가 활성화되어 있으면 TWI를 해제한 후 SW로 전환한다.
* 이미 SW 모드이면 아무 동작 없이 리턴한다.
*/
void sw_i2c_init_once(void); void sw_i2c_init_once(void);
/** /* Reset both mode flags to false, forcing re-initialisation on next call. */
* @brief I2C 모드 플래그 전체 초기화
*
* HW_I2C_FRQ, SW_I2C_FRQ를 모두 false로 리셋한다.
* 다음 init 호출 시 강제로 재초기화가 수행된다.
*/
void i2c_reset_state(void); void i2c_reset_state(void);
#endif #endif
File diff suppressed because it is too large Load Diff
@@ -3,43 +3,49 @@
* @author CandyPops Co. * @author CandyPops Co.
* @version V1.0.0 * @version V1.0.0
* @date 2022-09-05 * @date 2022-09-05
* @brief VesiScan BASIC 메인 헤더 파일 * @brief VesiScan BASIC main header file
* *
* [시스템 개요] * [System Overview]
* VesiScan BASIC nRF52840 기반 BLE 방광 모니터링 패치 디바이스이다. * VesiScan BASIC is an nRF52840-based BLE bladder monitoring patch device.
* 본 헤더는 시스템 전역에서 사용하는 열거형, 함수 선언, 전역 변수를 정의한다. * This header defines enums, function declarations, and global variables used system-wide.
* *
* [통신 방식] * [Communication]
* - BLE NUS (Nordic UART Service): 스마트폰 앱과 바이너리 프로토콜 통신 * - BLE NUS (Nordic UART Service): binary protocol with smartphone app
* - 물리 UART (1Mbps): 디버그 및 공장 테스트용 * - Physical UART (1Mbps): debug and factory testing
* *
* [데이터 전송 흐름] * [Data Transmission Flow]
* 1. 앱/UART에서 명령 수신 → received_command_process() * 1. Receive command from app/UART -> received_command_process()
* 2. 센서 데이터 수집 (배터리, 온도, IMU, 압력) * 2. Collect sensor data (battery, temperature, IMU, pressure)
* 3. format_data() 계열 함수로 바이너리 패킷 생성 * 3. Build binary packets via format_data() family
* 4. dr_binary_tx_safe()로 CRC16 추가 후 BLE 전송 * 4. Transmit over BLE with CRC16 appended via dr_binary_tx_safe()
******************************************************************************/ ******************************************************************************/
#ifndef MAIN_H__ #ifndef MAIN_H__
#define MAIN_H__ #define MAIN_H__
/* ------------------------------------------------------------------------- /* -------------------------------------------------------------------------
* Firmware 식별 코드 * Firmware Identification Code
* - VBTFW0100 = 개발(시험)용 Ver 1.00 * - VBTFW0100 = Development (test) build Ver 1.00
* - VB0FW0100 = 양산용 Ver 1.00 * - VB0FW0100 = Production build Ver 1.00
* *
* Firmware Version Update History * Firmware Version Update History
* - VBTFW0101 : reb+red 패킷 병합 (채널당 단일 패킷), 260330 jhChun * - VBTFW0101 : Merged reb+red packets (single packet per channel), 260330 jhChun
* - VBTFW0102 : LED 상태 설정 명령(msl) 추가 및 재페어링 허용 등 260331 jhChun * - VBTFW0102 : Added LED state command (msl) and re-pairing support, 260331 jhChun
* - VBTFW0103 260416 jhChun
* : Stabilized ADC measurement by clearing buffers before sampling.
* : Improved low-battery detection and automatic power-off handling.
* : Updated BLE security, bonding, and advertising timeout behavior.
* : Cleaned up command parsing and removed unused project files.
* - VBTFW0111 260421 jhChun
------------------------------------------------------------------------- */ ------------------------------------------------------------------------- */
#define FIRMWARE_VERSION "VBTFW0102" #define FIRMWARE_VERSION "VBTFW0111"
/*============================================================================== /*==============================================================================
* 데이터 길이 상수 * Data Length Constants
*============================================================================*/ *============================================================================*/
#define SERIAL_NO_LENGTH 12 /* 시리얼 번호 길이 (예: "VB026030000") */ #define SERIAL_NO_LENGTH 12 /* Serial number length (e.g. "VB026030000") */
#define HW_NO_LENGTH 12 /* 하드웨어 번호(버전) 길이 */ #define HW_NO_LENGTH 12 /* Hardware number (version) length */
#define PASSKEY_LENGTH 6 /* BLE 페어링 패스키 길이 (숫자 6자리) */ #define PASSKEY_LENGTH 6 /* BLE pairing passkey length (6 digits) */
#include <stdio.h> #include <stdio.h>
#include <stdint.h> #include <stdint.h>
@@ -49,128 +55,127 @@
#include "boards.h" #include "boards.h"
/*============================================================================== /*==============================================================================
* 열거형 정의 * Enum Definitions
*============================================================================*/ *============================================================================*/
/* 디바이스 ON/OFF 제어용 열거형 (EEPROM, 전원 등) */ /* Device ON/OFF control enum (EEPROM, power, etc.) */
typedef enum typedef enum
{ {
OFF = 0, /* 꺼짐 */ OFF = 0, /* Off */
ON = 1 /* 켜짐 */ ON = 1 /* On */
}on_off_cont_t; }on_off_cont_t;
/* 명령 수신 경로 구분 (BLE 또는 UART) */ /* Command source identifier (BLE or UART) */
typedef enum typedef enum
{ {
CMD_BLE = 0, /* BLE NUS를 통해 수신된 명령 */ CMD_BLE = 0, /* Command received via BLE NUS */
CMD_UART = 1 /* 물리 UART를 통해 수신된 명령 */ CMD_UART = 1 /* Command received via physical UART */
}which_cmd_t; }which_cmd_t;
/* 챔버 자동 테스트 모드 (FEATURE_CHAMBER_AUTO_TEST 활성 시) */ /* Chamber auto-test mode (when FEATURE_CHAMBER_AUTO_TEST enabled) */
#if FEATURE_CHAMBER_AUTO_TEST #if FEATURE_CHAMBER_AUTO_TEST
typedef enum typedef enum
{ {
SIMPLE_AUTO_MODE = 0, /* 간단 자동 모드 */ SIMPLE_AUTO_MODE = 0, /* Simple auto mode */
HALF_AUTO_MODE = 1, /* 반자동 모드 */ HALF_AUTO_MODE = 1, /* Semi-auto mode */
FULL_AUTO_MODE = 2, /* 전체 자동 모드 */ FULL_AUTO_MODE = 2, /* Full auto mode */
NONE_AUTO_MODE = 3 /* 자동 모드 없음 */ NONE_AUTO_MODE = 3 /* No auto mode */
}auto_meas_mode_t; }auto_meas_mode_t;
#endif #endif
/* BLE 연결 상태 */ /* BLE connection state */
typedef enum typedef enum
{ {
BLE_DISCONNECTED_ST = 0, /* BLE 미연결 */ BLE_DISCONNECTED_ST = 0, /* BLE disconnected */
BLE_CONNECTED_ST = 1 /* BLE 연결됨 */ BLE_CONNECTED_ST = 1 /* BLE connected */
}ble_status_t; }ble_status_t;
/*============================================================================== /*==============================================================================
* 함수 선언 * Function Declarations
*============================================================================*/ *============================================================================*/
#if FEATURE_SECURE_CONNECTION #if FEATURE_SECURE_CONNECTION
/* BLE 광고 시작 (erase_bonds=true이면 본딩 정보 삭제 후 시작) */ /* Start BLE advertising (if erase_bonds=true, delete bond info first) */
static void advertising_start(bool erase_bonds); static void advertising_start(bool erase_bonds);
#endif #endif
/* 슬립 모드 진입: LED 표시 후 POWER_OFF_DELAY(3초) 후 전원 차단 */ /* Enter sleep mode: show LED, then power off after POWER_OFF_DELAY (3s) */
void sleep_mode_enter(void); void sleep_mode_enter(void);
/* 전원 OFF 타이머 콜백: POWER_OFF_DELAY 경과 후 실제 전원 차단 */ /* Power-off timer callback: physically cut power after POWER_OFF_DELAY */
static void t_power_off_timeout_handler(void * p_context); static void t_power_off_timeout_handler(void * p_context);
/* 디바이스 전원 OFF: LED 표시 후 타이머로 지연 전원 차단 */ /* Device power off: show LED, then delayed power cut via timer */
void device_power_off(void); void device_power_off(void);
/* 전원 제어 핸들러: POWER_HOLD 핀으로 물리적 전원 ON/OFF */ /* Power control handler: physical power ON/OFF via POWER_HOLD pin */
static void power_control_handler(on_off_cont_t device_power_st); static void power_control_handler(on_off_cont_t device_power_st);
/* 전원 버튼 상태머신 (타이머 콜백, 5ms 간격): /* Power button state machine (timer callback, 5ms interval):
* - 짧은 눌림(<1.5): 전원 OFF * - Short press (<1.5s): power OFF
* - 중간 눌림(1.5~10): 부팅 시퀀스 시작 * - Medium press (1.5s~10s): start boot sequence
* - 긴 눌림(>10): 공장 초기화 (패스키 리셋 + 전원 OFF) */ * - Long press (>10s): factory reset (passkey reset + power OFF) */
static void main_s(void * p_context); static void main_s(void * p_context);
/* Peer Manager 타이머 콜백: reset_status==5이면 BLE 연결 강제 해제 */ /* Peer Manager timer callback: force BLE disconnect if reset_status==5 */
static void PM_s(void * p_context); static void PM_s(void * p_context);
//static void main_re(void * p_context); //static void main_re(void * p_context);
/* 메인 루틴 핸들러 (레거시, 현재 미사용) */ /* Main routine handler (legacy, currently unused) */
static void main_routine_handler(void * p_context); static void main_routine_handler(void * p_context);
/*------------------------------------------------------------------------------ /*------------------------------------------------------------------------------
* 데이터 전송 함수 * Data Transmission Functions
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
/* ASCII 텍스트 BLE 전송 ('\r'까지 전송, CRC16 자동 추가) */ /* Send ASCII text over BLE (up to '\r', CRC16 appended automatically) */
void data_tx_handler(char const *p_data_to_send); void data_tx_handler(char const *p_data_to_send);
/* 바이너리 데이터 BLE 안전 전송 (CRC16 자동 추가, 재시도 로직 포함) /* Safe binary data BLE transmission (CRC16 appended, with retry logic)
* @param ble_bin_buff 전송할 바이너리 버퍼 * @param ble_bin_buff Binary buffer to transmit
* @param length 데이터 길이 (uint16_t 워드 단위, 실제 바이트 = length × 2) */ * @param length Data length in uint16_t words (actual bytes = length x 2) */
void dr_binary_tx_safe(uint8_t const *ble_bin_buff, uint16_t length); void dr_binary_tx_safe(uint8_t const *ble_bin_buff, uint16_t length);
/* SoftDevice 호환 딜레이 (nrf_delay_ms 래퍼) */ /* SoftDevice-compatible delay (nrf_delay_ms wrapper) */
void dr_sd_delay_ms(uint32_t ms); void dr_sd_delay_ms(uint32_t ms);
/*------------------------------------------------------------------------------ /*------------------------------------------------------------------------------
* 바이너리 패킷 포맷 함수 * Binary Packet Format Functions
* 패킷 구조: [4바이트 태그][데이터][2바이트 CRC16] * Packet structure: [4-byte tag][data][2-byte CRC16]
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
/* 단일 uint16_t 값 포맷: [tag 4B][value 2B] */ /* Format single uint16_t value: [tag 4B][value 2B] */
void single_format_data(uint8_t *buffer, const char *tag, const uint16_t value) ; void single_format_data(uint8_t *buffer, const char *tag, const uint16_t value) ;
/* uint16_t 배열 포맷: [tag 4B][data0 2B][data1 2B]... */ /* Format uint16_t array: [tag 4B][data0 2B][data1 2B]... */
void format_data(uint8_t *buffer, const char *tag, const uint16_t *data_array, size_t length); void format_data(uint8_t *buffer, const char *tag, const uint16_t *data_array, size_t length);
/* uint8_t 바이트 배열 포맷: [tag 4B][byte0][byte1]... */ /* Format uint8_t byte array: [tag 4B][byte0][byte1]... */
void format_data_byte(uint8_t *buffer, const char *tag, const uint8_t *data_array, size_t length); void format_data_byte(uint8_t *buffer, const char *tag, const uint8_t *data_array, size_t length);
/* ASCII 문자열 포맷: [tag 4B][char0][char1]... */ /* Format ASCII string: [tag 4B][char0][char1]... */
void ascii_format_data(uint8_t *buffer, const char *tag, const char *data_ascii, size_t length); void ascii_format_data(uint8_t *buffer, const char *tag, const char *data_ascii, size_t length);
/*============================================================================== /*==============================================================================
* 전역 변수 (extern) * Global Variables (extern)
*============================================================================*/ *============================================================================*/
extern volatile bool data_tx_in_progress; /* BLE TX 전송 진행 중 플래그 */ extern volatile bool data_tx_in_progress; /* BLE TX in progress flag */
extern volatile bool ble_connection_st; /* BLE 연결 상태 (0=미연결, 1=연결) */ extern volatile bool ble_connection_st; /* BLE connection state (0=disconnected, 1=connected) */
extern volatile bool processing; /* 센서 데이터 처리 중 플래그 (중복 명령 방지) */
/* 2026-03-17: cmd_parse.c에서 main.c로 이동한 전역변수 */ /* 2026-03-17: Global variables moved from cmd_parse.c to main.c */
extern char SERIAL_NO[SERIAL_NO_LENGTH]; /* 시리얼 번호 */ extern char SERIAL_NO[SERIAL_NO_LENGTH]; /* Serial number */
extern char HW_NO[HW_NO_LENGTH]; /* 하드웨어 번호 */ extern char HW_NO[HW_NO_LENGTH]; /* Hardware number */
extern char m_static_passkey[PASSKEY_LENGTH]; /* BLE 정적 패스키 */ extern char m_static_passkey[PASSKEY_LENGTH]; /* BLE static passkey */
extern bool bond_data_delete; /* 본딩 데이터 삭제 요청 플래그 */ extern bool bond_data_delete; /* Bond data delete request flag */
extern uint32_t m_life_cycle; /* 디바이스 수명 사이클 카운터 */ extern uint32_t m_life_cycle; /* Device life cycle counter */
extern uint8_t resetCount; /* 통신 타임아웃 카운터 */ extern uint8_t resetCount; /* Communication timeout counter */
extern bool info4; /* 추가 정보 포함 측정 플래그 */ extern bool info4; /* Measurement with extra info flag */
extern uint8_t m_reset_status; /* 리셋 상태 코드 */ extern uint8_t m_reset_status; /* Reset status code */
extern uint8_t ble_bin_buffer[]; /* BLE 바이너리 응답 버퍼 */ extern uint8_t ble_bin_buffer[]; /* BLE binary response buffer */
/* 에러 응답 전송 */ /* Send error response */
void param_error(const char *cmd); void param_error(const char *cmd);
#endif //MAIN_H__ #endif //MAIN_H__
File diff suppressed because it is too large Load Diff
@@ -263,19 +263,6 @@ uint32_t dr_adc_get_vref(void);
* DEBUG FUNCTIONS * DEBUG FUNCTIONS
*============================================================================*/ *============================================================================*/
/**
* @brief Test ADC communication
* @return true if OK
*/
bool dr_adc_test(void);
/**
* @brief Print echo buffer to debug output
* @param buffer Sample buffer
* @param num_samples Number of samples
*/
void dr_adc_print_buffer(const uint16_t *buffer, uint16_t num_samples);
/*============================================================================== /*==============================================================================
* POWER CONTROL * POWER CONTROL
*============================================================================*/ *============================================================================*/
@@ -450,8 +437,8 @@ typedef struct {
uint16_t averaging; /**< Averaging count */ uint16_t averaging; /**< Averaging count */
uint8_t *ble_buffer; /**< Working buffer for BLE packets */ uint8_t *ble_buffer; /**< Working buffer for BLE packets */
dr_maa_channel_t channels[MAA_NUM_CHANNELS]; /**< Captured data for each channel */ dr_maa_channel_t channels[MAA_NUM_CHANNELS]; /**< Captured data for each channel */
bool pre_capture_all; /**< true: 전채널 캡처 완료 후 일괄 전송 (mbb) */ bool pre_capture_all; /**< true: capture all channels before transmitting (mbb) */
void (*on_complete_cb)(void); /**< 비동기 캡처 완료 후 호출될 콜백 (NULL이면 미사용) */ void (*on_complete_cb)(void); /**< callback after async capture completes (NULL = none) */
} maa_async_ctx_t; } maa_async_ctx_t;
/** /**
@@ -500,15 +487,14 @@ maa_async_state_t maa_async_get_state(void);
void maa_async_abort(void); void maa_async_abort(void);
/** /**
* @brief 자동 전원 플래그 설정 (완료 후 자동 power off) * @brief Set auto power-off flag (power off after completion)
*/ */
void maa_async_set_auto_power(bool on); void maa_async_set_auto_power(bool on);
void maa_async_set_pre_capture_all(bool on); void maa_async_set_pre_capture_all(bool on);
/** /**
* @brief 비동기 캡처 완료 콜백 설정 * @brief Set async capture completion callback
* raa: 전송 + 전원 OFF 이후 호출된다. NULL이면 콜백 없음. * Called after raa: is transmitted and power-off. NULL = no callback.
*/ */
void maa_async_set_on_complete(void (*cb)(void)); void maa_async_set_on_complete(void (*cb)(void));
@@ -1,25 +1,16 @@
/******************************************************************************* /*==============================================================================
* @file battery_saadc.c * battery_saadc.c - Battery voltage ADC measurement
* @author CandyPops Co.
* @version V1.0.0
* @date 2022-09-05
* @brief
******************************************************************************/
/*******************************************************************************
* [모듈 개요] 배터리 전압 ADC 측정 모듈
* *
* nRF52840 SAADC(Successive Approximation ADC)를 사용하여 배터리 전압을 측정: * Measures battery voltage via nRF52840 SAADC on AIN2:
* - AIN2 채널, 10bit 해상도 * - 12-bit resolution, 4x oversampling
* - 5초 주기 타이머(battery_loop)로 반복 측정 * - Periodic safety check via battery_loop timer (60 s interval)
* - 저전압(3500mV 이하) 10회 연속 감지 시 자동 전원 OFF * - Sequential: battery -> temperature measurement
* - info4 모드(전체 센서 수집)에서는 info_batt에 저장 후 온도 측정으로 전환 * - Auto power-off after 5 consecutive readings below 3500 mV or above 40 C
* - In info4 mode (bulk sensor collection): stores to info_batt
* *
* 배터리 전압 변환 공식: * Voltage conversion:
* 전압(mV) = ADC값 x (600mV / 1023) x 6 x 1.42 (분압 저항 보정 계수) * mV = ADC_VALUE * (600 / 4095) * 6 * 1.42 (resistor divider correction)
* *============================================================================*/
* info4 모드 순서: 배터리 -> 온도(go_temp) -> IMU(motion_raw_data_enabled)
******************************************************************************/
#include "sdk_common.h" #include "sdk_common.h"
@@ -34,156 +25,176 @@
#include "nrf_log.h" #include "nrf_log.h"
#include "main.h" #include "main.h"
#include "app_timer.h" #include "app_timer.h"
//#include "fstorage.h"
#include "battery_saadc.h" #include "battery_saadc.h"
#include "main_timer.h" #include "main_timer.h"
#include "main.h" #include "tmp235_q1.h"
#include "dr_piezo.h"
#include "debug_print.h" #include "debug_print.h"
/* SAADC 내부 기준전압 600mV (부동소수점) */ /* SAADC internal reference voltage (mV, float) */
#define BATTERY_REF_VOLTAGE_IN_MILLIVOLTS 600.0f /**< Reference voltage (in milli volts) used by ADC while doing conversion. */ #define BATTERY_REF_VOLTAGE_IN_MILLIVOLTS 600.0f
/* 1/3 프리스케일링 보상 계수 (입력 전압을 1/3로 분압하므로 x3, 추가 x2 = 총 x6) (부동소수점) */ /* 1/3 prescaling compensation (input divided by 3, then x2 = total x6) */
#define BATTERY_PRE_SCALING_COMPENSATION 6.0f /**< The ADC is configured to use VDD with 1/3 prescaling as input. And hence the result of conversion is to be multiplied by 3 to get the actual value of the battery voltage.*/ #define BATTERY_PRE_SCALING_COMPENSATION 6.0f
/* 12비트 ADC 최대 디지털 값 (부동소수점) */ /* 12-bit ADC maximum digital value */
#define BATTERY_ADC_RES_12BITS 4095.0f /**< Maximum digital value for 12-bit ADC conversion. */ #define BATTERY_ADC_RES_12BITS 4095.0f
/**@brief Macro to convert the result of ADC conversion in millivolts. /* Convert raw ADC value to millivolts */
*
* @param[in] ADC_VALUE ADC result.
*
* @retval Result converted to millivolts.
*/
#define BATTERY_RESULT_IN_MILLI_VOLTS(ADC_VALUE)\ #define BATTERY_RESULT_IN_MILLI_VOLTS(ADC_VALUE)\
((((ADC_VALUE) * BATTERY_REF_VOLTAGE_IN_MILLIVOLTS) / BATTERY_ADC_RES_12BITS) * BATTERY_PRE_SCALING_COMPENSATION) ((((ADC_VALUE) * BATTERY_REF_VOLTAGE_IN_MILLIVOLTS) / BATTERY_ADC_RES_12BITS) * BATTERY_PRE_SCALING_COMPENSATION)
/* 배터리 측정용 싱글 버퍼 (1회 측정 후 uninit하므로 더블 버퍼 불필요) */ /* Single ADC buffer (uninit after each measurement, no double-buffer needed) */
static nrf_saadc_value_t adc_buf; static nrf_saadc_value_t adc_buf;
//static nrf_saadc_value_t adc_bufs[2]; // 이전: 더블 버퍼 (연속 측정용)
/* Battery monitoring repeat timer */
/* 배터리 모니터링 반복 타이머 정의 */
APP_TIMER_DEF(m_battery_loop_timer_id); APP_TIMER_DEF(m_battery_loop_timer_id);
/* 배터리 측정 주기: 5초 (밀리초 단위) */ /* Battery monitoring interval (ms) */
#define BATTERY_LOOP_INTERVAL 60000 #define BATTERY_LOOP_INTERVAL 60000
/* 저전압 체크 플래그 — battery_loop에서 true로 설정, 핸들러에서 소비 */ /* Safety check consecutive count threshold */
#define SAFETY_CHECK_COUNT 5
/* Low-battery check flag — set by battery_loop, consumed by handler */
bool low_battery_check = false; bool low_battery_check = false;
/* SAADC 콜백 완료 플래그 — all_sensors()에서 배터리 측정 완료 대기용 */ /* Safety check mode flag — set by battery handler, consumed by tmp235 handler */
bool safety_check_mode = false;
/* SAADC callback completion flag — used by all_sensors() to wait */
volatile bool battery_saadc_done = false; volatile bool battery_saadc_done = false;
/* info4: 전체 센서 데이터 수집 모드 플래그 */ /* Safety check: cached battery voltage for use in safety_check_complete() */
extern bool info4; // main.c static float safety_batt_mv = 0;
/* Safety check: consecutive counters */
static uint8_t low_battery_cnt = 0;
static uint8_t over_temp_cnt = 0;
/* info4: bulk sensor collection mode flag */
extern bool info4;
extern char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN]; extern char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN];
/* true가 되면 main_timer에서 전원 OFF 시퀀스 실행 */
extern bool go_device_power_off; extern bool go_device_power_off;
/* 다른 작업(IMU 등) 처리 중이면 true — 배터리 측정 스킵용 */
extern volatile bool processing;
/* 현재 명령 소스: CMD_UART 또는 CMD_BLE */
extern which_cmd_t cmd_type_t; extern which_cmd_t cmd_type_t;
extern uint8_t ble_bin_buffer[BLE_NUS_MAX_DATA_LEN];
extern uint8_t ble_bin_buffer[BLE_NUS_MAX_DATA_LEN] ; /* info4 mode: cached battery voltage (mV) */
volatile uint16_t info_batt;
/* info4 모드에서 배터리 전압을 임시 저장 (mV 단위) */ /* info4 sequential measurement control flags */
volatile uint16_t info_batt; //48_c extern bool go_temp;
extern bool go_batt;
/* info4 순차 측정 제어 플래그: go_batt→ go_temp → motion */ extern bool motion_raw_data_enabled;
extern bool go_temp; //
extern bool go_batt; //cmd_parse
extern bool motion_raw_data_enabled ;
extern bool ble_got_new_data; extern bool ble_got_new_data;
extern bool motion_data_once ; extern bool motion_data_once;
/**@brief Function for handling the ADC interrupt.
*
* @details This function will fetch the conversion result from the ADC, convert the value into
* percentage and send it to peer.
*/
/*==============================================================================
/** * safety_check_complete - Called by tmp235 handler after temperature measurement
* @brief 배터리 전압 ADC 완료 콜백
* *
* SAADC 변환 완료 시 호출된다. * Checks both battery voltage and temperature against thresholds.
* ADC 값을 실제 배터리 전압(mV)으로 변환하고, 동작 모드에 따라: * 5 consecutive readings exceeding either threshold triggers power OFF.
* - 저전압 체크 모드: 3500mV 이하 10회 연속이면 자동 전원 OFF *============================================================================*/
* - info4 모드: info_batt에 저장 후 온도 측정(go_temp)으로 전환 void safety_check_complete(float temp_c)
* - 일반 모드: BLE 또는 UART로 즉시 전송
*
* 전압 변환: ADC값 x (600/1023) x 6 = 기본 전압, x 1.42 = 분압 보정 후 실제 전압
*/
void battery_event_handler( nrf_drv_saadc_evt_t const * p_event )
{ {
/* 저전압 연속 감지 카운터 (static으로 호출 간 유지) */ //DBG_PRINTF("[SAFETY] Batt=%d mV, Temp=%d.%d C\r\n",
static uint8_t low_battery_cnt = 0; // (int)safety_batt_mv, (int)temp_c, ((int)(temp_c * 10)) % 10);
if (p_event->type == NRF_DRV_SAADC_EVT_DONE) /* Battery check */
if (safety_batt_mv <= LOW_BATTERY_VOLTAGE)
{ {
nrf_saadc_value_t register_val = 0; low_battery_cnt++;
float batt_lvl_in_milli_volt_0 = 0; /* 보정 전 전압 (부동소수점) */ DBG_PRINTF("[SAFETY] Low batt cnt=%d\r\n", low_battery_cnt);
float batt_lvl_in_milli_volt_1 = 0; /* 분압 보정 후 최종 전압 (부동소수점) */
/* ADC 변환 결과 읽기 */
register_val = p_event->data.done.p_buffer[0];
//err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, 1); // 이전: 다음 변환을 위해 버퍼 재등록
//APP_ERROR_CHECK(err_code);
/* SAADC 해제 — 다른 ADC 측정(온도, 압력)과 하드웨어 공유 */
/* 1회 측정 후 해제이므로 buffer_convert(다음 버퍼 등록) 불필요 */
nrf_drv_saadc_channel_uninit(0);
nrf_drv_saadc_uninit();
/* 콜백 완료 알림 (all_sensors 대기 해제용) */
battery_saadc_done = true;
/* ADC값 → mV 변환 (매크로: ADC x 600/1023 x 6) */
batt_lvl_in_milli_volt_0 = BATTERY_RESULT_IN_MILLI_VOLTS(register_val);
/* 분압 저항 보정 계수 1.42 적용 → 실제 배터리 전압 */
batt_lvl_in_milli_volt_1 = (batt_lvl_in_milli_volt_0) *1.42f;
/* === 저전압 체크 모드 (battery_loop 타이머에서 설정) === */
if(low_battery_check == true)
{
low_battery_check = false;
/* 배터리 전압이 LOW_BATTERY_VOLTAGE(3500mV) 이하인지 확인 */
if(batt_lvl_in_milli_volt_1 <= LOW_BATTERY_VOLTAGE)
{
/* 10회 연속 저전압 감지 시 전원 OFF 시퀀스 시작 */
if(low_battery_cnt >= 10)
{
low_battery_cnt = 0;
/*go to power off and fds save */
DBG_PRINTF("Save FDS parameters and then Power OFF\r\n");
go_device_power_off = true;
main_timer_start();
} }
else else
{ {
/* 아직 10회 미만 — 카운터 증가 후 경고 출력 */ low_battery_cnt = 0;
low_battery_cnt++;
DBG_PRINTF("WARNING!!! low_battery cnt = %d, Batt = %d(mV)\r\n", low_battery_cnt, (int)batt_lvl_in_milli_volt_1);
}
}
} }
/* === info4 모드: 전체 센서 수집(mbb) 중 배터리 값 저장 === */ /* Temperature check */
if (temp_c >= OVER_TEMPERATURE_THRESHOLD)
{
over_temp_cnt++;
DBG_PRINTF("[SAFETY] Over temp cnt=%d\r\n", over_temp_cnt);
}
else
{
over_temp_cnt = 0;
}
/* Power OFF if either threshold exceeded 5 consecutive times */
if (low_battery_cnt >= SAFETY_CHECK_COUNT)
{
low_battery_cnt = 0;
DBG_PRINTF("[SAFETY] Low battery -> Power OFF\r\n");
go_device_power_off = true;
main_timer_start();
}
else if (over_temp_cnt >= SAFETY_CHECK_COUNT)
{
over_temp_cnt = 0;
DBG_PRINTF("[SAFETY] Over temperature -> Power OFF\r\n");
go_device_power_off = true;
main_timer_start();
}
dr_piezo_power_off();
}
/*==============================================================================
* battery_event_handler - SAADC conversion complete callback
*
* Converts the raw ADC value to battery voltage (mV) and then:
* - Low-battery check mode: store voltage, chain temperature measurement
* - info4 mode: store to info_batt (no BLE send)
* - Normal mode: send rsn: response over BLE or UART
*============================================================================*/
void battery_event_handler(nrf_drv_saadc_evt_t const * p_event)
{
if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
{
nrf_saadc_value_t register_val = 0;
float batt_lvl_in_milli_volt_0 = 0;
float batt_lvl_in_milli_volt_1 = 0;
register_val = p_event->data.done.p_buffer[0];
/* Release SAADC — shared with temperature / pressure ADC */
nrf_drv_saadc_channel_uninit(0);
nrf_drv_saadc_uninit();
battery_saadc_done = true;
/* ADC -> mV conversion */
batt_lvl_in_milli_volt_0 = BATTERY_RESULT_IN_MILLI_VOLTS(register_val);
/* Resistor divider correction factor 1.42 */
batt_lvl_in_milli_volt_1 = batt_lvl_in_milli_volt_0 * 1.42f;
/* --- Safety check mode: store voltage, chain temperature measurement --- */
if (low_battery_check == true)
{
low_battery_check = false;
safety_batt_mv = batt_lvl_in_milli_volt_1;
safety_check_mode = true;
/* TMP235 shares piezo TX/RX power rail */
if (!dr_piezo_is_power_on())
{
dr_piezo_power_on();
}
tmp235_voltage_level_meas();
}
/* --- info4 mode: store value for mbb? bulk response --- */
else if (info4 == true) else if (info4 == true)
{ {
info_batt = batt_lvl_in_milli_volt_1; info_batt = batt_lvl_in_milli_volt_1;
} }
/* === 일반 모드: 단독 배터리 측정 요청(msn)에 대한 응답 전송 === */ /* --- Normal mode: send rsn: BLE response --- */
else else
{ {
if (cmd_type_t == CMD_UART) if (cmd_type_t == CMD_UART)
@@ -192,105 +203,90 @@ void battery_event_handler( nrf_drv_saadc_evt_t const * p_event )
} }
else if (cmd_type_t == CMD_BLE) else if (cmd_type_t == CMD_BLE)
{ {
/* "rsn:" 헤더와 함께 배터리 전압을 바이너리로 BLE 전송 */
single_format_data(ble_bin_buffer, "rsn:", batt_lvl_in_milli_volt_1); single_format_data(ble_bin_buffer, "rsn:", batt_lvl_in_milli_volt_1);
dr_binary_tx_safe(ble_bin_buffer,3); dr_binary_tx_safe(ble_bin_buffer, 3);
//data_tx_handler(ble_tx_buffer);
} }
} }
} }
} }
/** /*==============================================================================
* @brief SAADC를 배터리 전압 측정용으로 설정 * battery_configure - Set up SAADC for battery voltage measurement
* *
* AIN2 채널을 싱글엔드(SE) 모드, 1/3 프리스케일링으로 초기화한다. * AIN2, single-ended, 1/6 gain, 12-bit, 4x oversampling, burst enabled.
* 더블 버퍼(adc_bufs[0], [1])를 등록하여 연속 측정이 가능하도록 한다. * Registers a single buffer (uninit after one conversion).
* 콜백: battery_event_handler *============================================================================*/
*/
static void battery_configure(void) static void battery_configure(void)
{ {
/* SAADC 드라이버 초기화 (4x 오버샘플링으로 노이즈 저감) */
nrf_drv_saadc_config_t saadc_config = NRF_DRV_SAADC_DEFAULT_CONFIG; nrf_drv_saadc_config_t saadc_config = NRF_DRV_SAADC_DEFAULT_CONFIG;
saadc_config.resolution = NRF_SAADC_RESOLUTION_12BIT; // 10 -> 12bit saadc_config.resolution = NRF_SAADC_RESOLUTION_12BIT;
saadc_config.oversample = NRF_SAADC_OVERSAMPLE_4X; saadc_config.oversample = NRF_SAADC_OVERSAMPLE_4X;
ret_code_t err_code = nrf_drv_saadc_init(&saadc_config, battery_event_handler); ret_code_t err_code = nrf_drv_saadc_init(&saadc_config, battery_event_handler);
if (err_code != NRF_SUCCESS) { if (err_code != NRF_SUCCESS)
return; /* SAADC 사용 중 → 이번 측정 스킵, 다음 주기에 재시도 */ {
return; /* SAADC busy — skip this cycle, retry next */
} }
/* AIN2 채널 설정: 싱글엔드 입력, 1/6 gain, burst + TACQ 20μs */
nrf_saadc_channel_config_t config = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN2); nrf_saadc_channel_config_t config = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN2);
config.burst = NRF_SAADC_BURST_ENABLED; config.burst = NRF_SAADC_BURST_ENABLED;
config.acq_time = NRF_SAADC_ACQTIME_10US; config.acq_time = NRF_SAADC_ACQTIME_10US;
err_code = nrf_drv_saadc_channel_init(0, &config); err_code = nrf_drv_saadc_channel_init(0, &config);
APP_ERROR_CHECK(err_code); APP_ERROR_CHECK(err_code);
/* 싱글 버퍼 등록 (1회 측정 후 uninit) */
err_code = nrf_drv_saadc_buffer_convert(&adc_buf, 1); err_code = nrf_drv_saadc_buffer_convert(&adc_buf, 1);
APP_ERROR_CHECK(err_code); APP_ERROR_CHECK(err_code);
//err_code = nrf_drv_saadc_buffer_convert(&adc_bufs[0], 1); // 이전: 더블 버퍼
//APP_ERROR_CHECK(err_code);
//err_code = nrf_drv_saadc_buffer_convert(&adc_bufs[1], 1);
//APP_ERROR_CHECK(err_code);
} }
/** /*==============================================================================
* @brief 배터리 전압 1회 측정 시작 * battery_level_meas - Start a single battery voltage measurement
* *
* SAADC를 배터리용으로 설정 후 샘플링을 트리거한다. * Configures SAADC and triggers sampling. Result arrives asynchronously
* 결과는 battery_event_handler 콜백에서 비동기로 처리된다. * via battery_event_handler.
*/ *============================================================================*/
void battery_level_meas(void) void battery_level_meas(void)
{ {
ret_code_t err_code; ret_code_t err_code;
battery_configure(); /* SAADC 배터리용 초기화 */ battery_configure();
err_code = nrf_drv_saadc_sample(); /* ADC 샘플링 트리거 (비동기) */ err_code = nrf_drv_saadc_sample();
APP_ERROR_CHECK(err_code); APP_ERROR_CHECK(err_code);
} }
/** /*==============================================================================
* @brief 배터리 모니터링 타이머 콜백 (5초 주기) - 주기 확인 필요(너무 짧음) * battery_loop - Periodic battery monitoring timer callback
* *
* 저전압 체크 플래그를 설정하고 배터리 측정을 시작한다. * Sets the low-battery check flag and starts a measurement.
* 다른 작업(IMU 등) 처리 중이면 측정을 건너뛴다. * Skips if info4 mode is active (SAADC conflict).
*/ *============================================================================*/
void battery_loop(void * p_context) /* For 1sec */ void battery_loop(void * p_context)
{ {
UNUSED_PARAMETER(p_context); UNUSED_PARAMETER(p_context);
/* 다른 센서 처리 중 또는 MBB 센서 수집 중이면 배터리 측정 스킵 (SAADC 충돌 방지) */ if (info4 == true)
if (processing == true || info4 == true)
{ {
processing = false ; // add 20241218
//low_battery_check = true;
return; return;
} }
else
{ low_battery_check = true;
low_battery_check = true; /* 저전압 감지 모드로 측정 */ battery_level_meas();
battery_level_meas(); /* 배터리 ADC 1회 측정 시작 */
}
} }
/** @brief 배터리 모니터링 타이머 시작 (5초 반복) */ /* Start the periodic battery monitoring timer. */
void battery_timer_start(void) void battery_timer_start(void)
{ {
APP_ERROR_CHECK(app_timer_start(m_battery_loop_timer_id, APP_TIMER_TICKS(BATTERY_LOOP_INTERVAL), NULL)); APP_ERROR_CHECK(app_timer_start(m_battery_loop_timer_id, APP_TIMER_TICKS(BATTERY_LOOP_INTERVAL), NULL));
} }
/** @brief 배터리 모니터링 타이머 정지 */ /* Stop the battery monitoring timer. */
void battery_timer_stop(void) void battery_timer_stop(void)
{ {
APP_ERROR_CHECK(app_timer_stop(m_battery_loop_timer_id)); APP_ERROR_CHECK(app_timer_stop(m_battery_loop_timer_id));
} }
/** @brief 배터리 모니터링 타이머 초기화 (반복 모드, 콜백: battery_loop) */ /* Initialise the battery monitoring timer (repeated mode). */
void battery_timer_init(void) void battery_timer_init(void)
{ {
APP_ERROR_CHECK(app_timer_create(&m_battery_loop_timer_id, APP_TIMER_MODE_REPEATED, battery_loop)); APP_ERROR_CHECK(app_timer_create(&m_battery_loop_timer_id, APP_TIMER_MODE_REPEATED, battery_loop));
} }
@@ -1,41 +1,43 @@
/******************************************************************************* /*==============================================================================
* @file battery_saadc.h * battery_saadc.h - Battery voltage SAADC measurement interface
* @author CandyPops Co.
* @version V1.0.0
* @date 2022-09-05
* @brief
******************************************************************************/
/*******************************************************************************
* [헤더 개요] 배터리 전압 SAADC 측정 인터페이스
* *
* nRF52840 SAADC를 이용한 배터리 전압 측정 API를 선언한다. * Uses the nRF52840 SAADC to measure battery voltage on AIN2.
* *
* 주요 API: * API:
* - battery_level_meas() : 배터리 전압 1회 측정 (AIN2) * battery_level_meas() : one-shot measurement (async, result via callback)
* - battery_timer_init/start/stop() : 5초 주기 배터리 모니터링 타이머 제어 * battery_timer_init/start/stop() : 60-second periodic monitoring timer
* *
* LOW_BATTERY_VOLTAGE(3500mV) 이하가 10회 연속 감지되면 자동 전원 OFF * Periodic safety check (every 60 s):
******************************************************************************/ * Battery -> Temperature sequential measurement via SAADC.
* Auto power-off after 5 consecutive readings below LOW_BATTERY_VOLTAGE (3500 mV)
* or above OVER_TEMPERATURE_THRESHOLD (40 C).
*============================================================================*/
#ifndef _BATTERY_SAADC_H_ #ifndef _BATTERY_SAADC_H_
#define _BATTERY_SAADC_H_ #define _BATTERY_SAADC_H_
/* 저전압 판정 임계값 (mV) — 이 값 이하가 10회 연속이면 자동 전원 OFF */ /* Low-battery threshold (mV) — 5 consecutive readings below this -> power OFF */
#define LOW_BATTERY_VOLTAGE 3500 /* Low Battery 임계값 */ #define LOW_BATTERY_VOLTAGE 3500
/* Over-temperature threshold (deg C) — 5 consecutive readings above this -> power OFF */
#define OVER_TEMPERATURE_THRESHOLD 40.0f
/** @brief 배터리 SAADC 콜백 완료 플래그 (all_sensors 대기용) */ /* SAADC callback completion flag (used by all_sensors() to wait) */
extern volatile bool battery_saadc_done; extern volatile bool battery_saadc_done;
/** @brief 배터리 전압 1회 측정 시작 (비동기, 결과는 콜백에서 처리) */ /* Safety check mode flag — set by battery_loop, consumed by tmp235 handler */
extern bool safety_check_mode;
/* Called by tmp235 handler when safety check temperature measurement completes */
void safety_check_complete(float temp_c);
/* Start a single async battery measurement. Result handled in callback. */
void battery_level_meas(void); void battery_level_meas(void);
/** @brief 배터리 모니터링 5초 반복 타이머 시작 */ /* Start the 60-second periodic battery monitoring timer. */
void battery_timer_start(void); void battery_timer_start(void);
/** @brief 배터리 모니터링 타이머 정지 */ /* Stop the battery monitoring timer. */
void battery_timer_stop(void); void battery_timer_stop(void);
/** @brief 배터리 모니터링 타이머 초기화 (앱 시작 시 1회 호출) */ /* Initialise the battery monitoring timer (call once at app start). */
void battery_timer_init(void); void battery_timer_init(void);
#endif //_BATTERY_SAADC_H_ #endif //_BATTERY_SAADC_H_
@@ -7,28 +7,29 @@
******************************************************************************/ ******************************************************************************/
/******************************************************************************* /*******************************************************************************
* [모듈 개요] ICM42670P IMU 드라이버 상위 레이어 * [Module overview] ICM42670P IMU driver application layer
* *
* ICM42670P IMU 센서의 초기화, 설정, 데이터 읽기를 담당하는 애플리케이션 레이어 모듈 * Application layer module responsible for initialization, configuration,
* InvenSense 드라이버 API를 래핑하여 사용 * and data reading of the ICM42670P IMU sensor. Wraps InvenSense driver API.
* *
* 주요 기능: * Key functions:
* 1) setup_imu_device() - IMU 초기화 및 WHOAMI 확인 (0x67 = ICM42670P) * 1) setup_imu_device() - IMU init and WHOAMI verification (0x67 = ICM42670P)
* 2) configure_imu_device() - 센서 파라미터 설정 * 2) configure_imu_device() - Sensor parameter configuration
* - 가속도계: ±4g FSR, 100Hz(저전력) 또는 800Hz(저잡음) * - Accelerometer: +/-4g FSR, 100Hz (low-power) or 800Hz (low-noise)
* - 자이로: ±2000dps FSR, 100Hz 또는 800Hz * - Gyroscope: +/-2000dps FSR, 100Hz or 800Hz
* - FIFO 비활성화 (레지스터 직접 읽기 모드) * - FIFO disabled (direct register read mode)
* 3) get_imu_data() - FIFO 또는 레지스터에서 센서 데이터 읽기 * 3) get_imu_data() - Read sensor data from FIFO or registers
* 4) imu_callback() - 센서 데이터 수신 콜백 * 4) imu_callback() - Sensor data receive callback
* - 마운팅 매트릭스 적용 (보드 방향 보정) * - Applies mounting matrix (board orientation correction)
* - info4 모드: info_imu[6]에 데이터 저장 * - info4 mode: stores data in info_imu[6]
* - BLE 모드: "rsp:" 태그로 6축 데이터 BLE 전송 * - BLE mode: sends 6-axis data via BLE with "rsp:" tag
* - UART 모드: 텍스트 형식으로 시리얼 출력 * - UART mode: outputs text format to serial
* 5) imu_read_direct() - 드라이버 API를 우회한 직접 I2C 레지스터 읽기 * 5) imu_read_direct() - Direct I2C register read bypassing driver API
* - 센서 설정 → 전원 ON → 80ms 대기 → 12바이트 읽기 → 슬립 * - Configure sensor -> power ON -> wait 80ms -> read 12 bytes -> sleep
* *
* 마운팅 매트릭스: * Mounting matrix:
* Q30 고정소수점 형식의 3x3 회전 매트릭스로, 보드에 장착된 센서의물리적 방향을 소프트웨어 좌표계에 맞춰 보정 * 3x3 rotation matrix in Q30 fixed-point format, correcting the sensor's
* physical mounting orientation to match the software coordinate system.
******************************************************************************/ ******************************************************************************/
#include "sdk_config.h" #include "sdk_config.h"
@@ -47,9 +48,9 @@
/* /*
* 데이터 출력 형식 선택 * Data output format selection
* 0 : 원시 데이터 (raw accel, gyro, temp) 출력 * 0 : Raw data output (raw accel, gyro, temp)
* 1 : 스케일링된 데이터 (g, dps, 섭씨) 출력 * 1 : Scaled data output (g, dps, Celsius)
*/ */
#define SCALED_DATA_G_DPS 0 #define SCALED_DATA_G_DPS 0
@@ -58,20 +59,20 @@
* Static and extern variables * Static and extern variables
* -------------------------------------------------------------------------------------- */ * -------------------------------------------------------------------------------------- */
/* IMU 드라이버 객체 — 드라이버 API 호출 시 항상 이 구조체 전달 */ /* IMU driver object — always passed to driver API calls */
static struct inv_imu_device icm_driver; static struct inv_imu_device icm_driver;
/* BLE 전송용 바이너리 버퍼 */ /* Binary buffer for BLE transmission */
uint8_t imu_bin_buffer[BLE_NUS_MAX_DATA_LEN]; uint8_t imu_bin_buffer[BLE_NUS_MAX_DATA_LEN] = {0};
/* /*
* ICM42670P 마운팅 매트릭스 (Q30 고정소수점) * ICM42670P mounting matrix (Q30 fixed-point)
* *
* 센서가 보드에 장착된 물리적 방향에 따라 좌표 변환 * Coordinate transform based on the sensor's physical mounting orientation.
* Q30 형식: 1.0 = (1 << 30) = 0x40000000 * Q30 format: 1.0 = (1 << 30) = 0x40000000
* *
* SM_REVB_DB (개발보드): X-Y, Y→X 변환 (90도 회전) * SM_REVB_DB (dev board): X->-Y, Y->X transform (90-degree rotation)
* 기본 (SmartMotion): 단위 행렬 (변환 없음) * Default (SmartMotion): identity matrix (no transform)
*/ */
#if (SM_BOARD_REV == SM_REVB_DB) /* when DB or EVB are used */ #if (SM_BOARD_REV == SM_REVB_DB) /* when DB or EVB are used */
static int32_t icm_mounting_matrix[9] = { 0, -(1<<30), 0, static int32_t icm_mounting_matrix[9] = { 0, -(1<<30), 0,
@@ -83,13 +84,13 @@ static int32_t icm_mounting_matrix[9] = {(1<<30), 0, 0,
0, 0, (1<<30)}; 0, 0, (1<<30)};
#endif #endif
bool custom_add_data; /* 커스텀 데이터 추가 플래그 (BLE 전송 제어) */ bool custom_add_data; /* Custom data append flag (BLE transmission control) */
extern bool motion_raw_data_enabled; /* 외부에서 원시 데이터 읽기를 요청하는 플래그 */ extern bool motion_raw_data_enabled; /* Flag requesting raw data read from external module */
extern char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN]; /* BLE 텍스트 전송 버퍼 */ extern char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN]; /* BLE text transmit buffer */
extern which_cmd_t cmd_type_t; /* 현재 명령 소스 (BLE 또는 UART) */ extern which_cmd_t cmd_type_t; /* Current command source (BLE or UART) */
uint16_t ssp_data[6]={0,}; /* BLE 전송용 6축 데이터 배열 (accel XYZ + gyro XYZ) */ uint16_t ssp_data[6]={0,}; /* 6-axis data array for BLE (accel XYZ + gyro XYZ) */
extern bool info4; /* info4 모드 플래그 (cmd_parse에서 설정) */ extern bool info4; /* info4 mode flag (set by cmd_parse) */
volatile uint16_t info_imu[6]; /* info4 모드에서 IMU 데이터를 저장하는 전역 배열 */ volatile uint16_t info_imu[6]; /* Global array storing IMU data in info4 mode */
/* -------------------------------------------------------------------------------------- /* --------------------------------------------------------------------------------------
* static function declaration * static function declaration
@@ -102,35 +103,35 @@ static void apply_mounting_matrix(const int32_t matrix[9], int32_t raw[3]);
/* /*
* setup_imu_device() * setup_imu_device()
* IMU 디바이스 초기화 및 식별 확인 * IMU device initialization and identification verification.
* *
* 처리 흐름: * Flow:
* 1) inv_imu_init()으로 드라이버 초기화 (시리얼 인터페이스 + 콜백 등록) * 1) Initialize driver via inv_imu_init() (serial interface + callback registration)
* 2) WHOAMI 레지스터 읽기로 디바이스 확인 * 2) Read WHOAMI register for device identification
* 3) WHOAMI 값이 ICM_WHOAMI(0x67)와 일치하는지 검증 * 3) Verify WHOAMI value matches ICM_WHOAMI (0x67)
* *
* 반환값: 0=성공, 음수=에러 * Returns: 0=success, negative=error
*/ */
int setup_imu_device(struct inv_imu_serif *icm_serif) int setup_imu_device(struct inv_imu_serif *icm_serif)
{ {
int rc = 0; int rc = 0;
uint8_t who_am_i; uint8_t who_am_i;
/* IMU 드라이버 초기화 — 시리얼 인터페이스 연결 및 콜백 함수 등록 */ /* Initialize IMU driver — connect serial interface and register callback */
rc = inv_imu_init(&icm_driver, icm_serif, imu_callback); rc = inv_imu_init(&icm_driver, icm_serif, imu_callback);
if (rc != INV_ERROR_SUCCESS) { if (rc != INV_ERROR_SUCCESS) {
DBG_PRINTF("!!! ERROR : Failed to initialize IMU!\r\n"); DBG_PRINTF("!!! ERROR : Failed to initialize IMU!\r\n");
return rc; return rc;
} }
/* WHOAMI 레지스터 읽기 — 디바이스 존재 및 통신 확인 */ /* Read WHOAMI register — verify device presence and communication */
rc = inv_imu_get_who_am_i(&icm_driver, &who_am_i); rc = inv_imu_get_who_am_i(&icm_driver, &who_am_i);
if (rc != INV_ERROR_SUCCESS) { if (rc != INV_ERROR_SUCCESS) {
DBG_PRINTF("!!! ERROR : Failed to read whoami!\r\n"); DBG_PRINTF("!!! ERROR : Failed to read whoami!\r\n");
return rc; return rc;
} }
/* WHOAMI 값 검증 — ICM42670P의 경우 0x67이어야 함 */ /* Verify WHOAMI value — must be 0x67 for ICM42670P */
if (who_am_i != ICM_WHOAMI) { if (who_am_i != ICM_WHOAMI) {
DBG_PRINTF("!!! ERROR : Bad WHOAMI value! Read 0x%02x, expected 0x%02x\r\n", who_am_i, ICM_WHOAMI); DBG_PRINTF("!!! ERROR : Bad WHOAMI value! Read 0x%02x, expected 0x%02x\r\n", who_am_i, ICM_WHOAMI);
return INV_ERROR; return INV_ERROR;
@@ -141,53 +142,53 @@ int setup_imu_device(struct inv_imu_serif *icm_serif)
/* /*
* configure_imu_device() * configure_imu_device()
* IMU 센서 동작 파라미터 설정 * Configures IMU sensor operating parameters.
* *
* 설정 항목: * Settings:
* - FIFO: 비활성화 (USE_FIFO=0일 때, 레지스터 직접 읽기 모드) * - FIFO: disabled (when USE_FIFO=0, direct register read mode)
* - 가속도계 FSR: ±4g (USE_HIGH_RES_MODE=0일 때) * - Accel FSR: +/-4g (when USE_HIGH_RES_MODE=0)
* - 자이로 FSR: ±2000dps * - Gyro FSR: +/-2000dps
* - ODR(출력 데이터율): * - ODR (output data rate):
* - 저잡음 모드(USE_LOW_NOISE_MODE=1): 800Hz * - Low-noise mode (USE_LOW_NOISE_MODE=1): 800Hz
* - 저전력 모드(USE_LOW_NOISE_MODE=0): 100Hz * - Low-power mode (USE_LOW_NOISE_MODE=0): 100Hz
* - 자이로는 항상 저잡음 모드로 동작 * - Gyro always operates in low-noise mode
* - FIFO 미사용 시 자이로 스타트업 시간만큼 대기 * - Waits for gyro startup time when FIFO is not used
* *
* 반환값: 0=성공, 음수=에러 * Returns: 0=success, negative=error
*/ */
int configure_imu_device(void) int configure_imu_device(void)
{ {
int rc = 0; int rc = 0;
/* FIFO 비활성화 — 레지스터에서 직접 데이터를 읽기 */ /* Disable FIFO — read data directly from registers */
if (!USE_FIFO) if (!USE_FIFO)
rc |= inv_imu_configure_fifo(&icm_driver, INV_IMU_FIFO_DISABLED); rc |= inv_imu_configure_fifo(&icm_driver, INV_IMU_FIFO_DISABLED);
if (USE_HIGH_RES_MODE) { if (USE_HIGH_RES_MODE) {
/* 고해상도 FIFO 모드: 20비트 데이터, FSR은 16g/2000dps로 고정됨 */ /* High-resolution FIFO mode: 20-bit data, FSR locked to 16g/2000dps */
rc |= inv_imu_enable_high_resolution_fifo(&icm_driver); rc |= inv_imu_enable_high_resolution_fifo(&icm_driver);
} else { } else {
/* 표준 모드: 가속도계 ±4g, 자이로 ±2000dps FSR 설정 */ /* Standard mode: accel +/-4g, gyro +/-2000dps FSR */
rc |= inv_imu_set_accel_fsr(&icm_driver, ACCEL_CONFIG0_FS_SEL_4g); rc |= inv_imu_set_accel_fsr(&icm_driver, ACCEL_CONFIG0_FS_SEL_4g);
rc |= inv_imu_set_gyro_fsr(&icm_driver, GYRO_CONFIG0_FS_SEL_2000dps); rc |= inv_imu_set_gyro_fsr(&icm_driver, GYRO_CONFIG0_FS_SEL_2000dps);
} }
if (USE_LOW_NOISE_MODE) { if (USE_LOW_NOISE_MODE) {
/* 저잡음 모드: 800Hz ODR, 가속도계 저잡음 모드 활성화 */ /* Low-noise mode: 800Hz ODR, enable accel low-noise mode */
rc |= inv_imu_set_accel_frequency(&icm_driver, ACCEL_CONFIG0_ODR_800_HZ); rc |= inv_imu_set_accel_frequency(&icm_driver, ACCEL_CONFIG0_ODR_800_HZ);
rc |= inv_imu_set_gyro_frequency(&icm_driver, GYRO_CONFIG0_ODR_800_HZ); rc |= inv_imu_set_gyro_frequency(&icm_driver, GYRO_CONFIG0_ODR_800_HZ);
rc |= inv_imu_enable_accel_low_noise_mode(&icm_driver); rc |= inv_imu_enable_accel_low_noise_mode(&icm_driver);
} else { } else {
/* 저전력 모드: 100Hz ODR, 가속도계 저전력 모드 활성화 */ /* Low-power mode: 100Hz ODR, enable accel low-power mode */
rc |= inv_imu_set_accel_frequency(&icm_driver, ACCEL_CONFIG0_ODR_100_HZ); rc |= inv_imu_set_accel_frequency(&icm_driver, ACCEL_CONFIG0_ODR_100_HZ);
rc |= inv_imu_set_gyro_frequency(&icm_driver, GYRO_CONFIG0_ODR_100_HZ); rc |= inv_imu_set_gyro_frequency(&icm_driver, GYRO_CONFIG0_ODR_100_HZ);
rc |= inv_imu_enable_accel_low_power_mode(&icm_driver); rc |= inv_imu_enable_accel_low_power_mode(&icm_driver);
} }
/* 자이로는 모드에 관계없이 항상 저잡음 모드로 동작 */ /* Gyro always operates in low-noise mode regardless of setting */
rc |= inv_imu_enable_gyro_low_noise_mode(&icm_driver); rc |= inv_imu_enable_gyro_low_noise_mode(&icm_driver);
/* FIFO 미사용 시 자이로 스타트업 시간만큼 대기 (첫 유효 데이터까지의 지연) */ /* When FIFO is not used, wait for gyro startup time (delay until first valid data) */
if (!USE_FIFO) if (!USE_FIFO)
inv_imu_sleep_us(GYR_STARTUP_TIME_US); inv_imu_sleep_us(GYR_STARTUP_TIME_US);
@@ -197,9 +198,9 @@ int configure_imu_device(void)
/* /*
* get_imu_data() * get_imu_data()
* IMU에서 센서 데이터 읽기 * Reads sensor data from the IMU.
* USE_FIFO 설정에 따라 FIFO 또는 레지스터에서 데이터를 가져옴 * Fetches data from FIFO or registers depending on USE_FIFO setting.
* 읽은 데이터는 imu_callback()을 통해 처리 * Read data is processed via imu_callback().
*/ */
int get_imu_data(void) int get_imu_data(void)
{ {
@@ -214,8 +215,8 @@ int get_imu_data(void)
#if SCALED_DATA_G_DPS #if SCALED_DATA_G_DPS
/* /*
* get_accel_and_gyr_fsr() * get_accel_and_gyr_fsr()
* 현재 설정된 가속도계와 자이로의 FSR(Full Scale Range) 값을 가져온다. * Retrieves the currently configured FSR (Full Scale Range) for accel and gyro.
* 스케일링된 데이터(g, dps) 변환에 사용된다. * Used for converting to scaled data (g, dps).
*/ */
static void get_accel_and_gyr_fsr(int16_t * accel_fsr_g, int16_t * gyro_fsr_dps) static void get_accel_and_gyr_fsr(int16_t * accel_fsr_g, int16_t * gyro_fsr_dps)
{ {
@@ -253,17 +254,17 @@ static void get_accel_and_gyr_fsr(int16_t * accel_fsr_g, int16_t * gyro_fsr_dps)
/* /*
* imu_callback() * imu_callback()
* IMU 드라이버가 새 센서 데이터를 읽을 때마다 호출되는 콜백 함수 * Callback invoked each time the IMU driver reads new sensor data.
* *
* 처리 흐름: * Flow:
* 1) 이벤트에서 가속도/자이로 원시 데이터 추출 * 1) Extract raw accel/gyro data from event
* - FIFO 모드: 타임스탬프 롤오버 처리, 고해상도(20비트) 지원 * - FIFO mode: handles timestamp rollover, supports high-res (20-bit)
* - 레지스터 모드: 16비트 데이터 직접 사용 * - Register mode: uses 16-bit data directly
* 2) 마운팅 매트릭스 적용 (보드 장착 방향 보정) * 2) Apply mounting matrix (board orientation correction)
* 3) 데이터 출력 (모드에 따라 분기): * 3) Output data (branches by mode):
* - info4 모드: info_imu[6] 전역 배열에 저장 (외부에서 폴링) * - info4 mode: stores in info_imu[6] global array (polled externally)
* - UART 모드: "Tp" 접두사로 6축 데이터 텍스트 출력 * - UART mode: text output with "Tp" prefix for 6-axis data
* - BLE 모드: "rsp:" 태그로 바이너리 패킷 전송 + UART 텍스트 동시 출력 * - BLE mode: binary packet with "rsp:" tag + simultaneous UART output
*/ */
void imu_callback(inv_imu_sensor_event_t *event) void imu_callback(inv_imu_sensor_event_t *event)
{ {
@@ -280,18 +281,18 @@ void imu_callback(inv_imu_sensor_event_t *event)
static uint64_t last_fifo_timestamp = 0; static uint64_t last_fifo_timestamp = 0;
static uint32_t rollover_num = 0; static uint32_t rollover_num = 0;
/* FIFO 타임스탬프 롤오버 처리 (16비트 → 64비트 확장) */ /* FIFO timestamp rollover handling (16-bit -> 64-bit extension) */
if (last_fifo_timestamp > event->timestamp_fsync) if (last_fifo_timestamp > event->timestamp_fsync)
rollover_num++; rollover_num++;
last_fifo_timestamp = event->timestamp_fsync; last_fifo_timestamp = event->timestamp_fsync;
/* 타임스탬프를 마이크로초 단위로 변환 (Q24 해상도 적용) */ /* Convert timestamp to microseconds (apply Q24 resolution) */
timestamp = event->timestamp_fsync + rollover_num * UINT16_MAX; timestamp = event->timestamp_fsync + rollover_num * UINT16_MAX;
timestamp *= inv_imu_get_fifo_timestamp_resolution_us_q24(&icm_driver); timestamp *= inv_imu_get_fifo_timestamp_resolution_us_q24(&icm_driver);
timestamp /= (1UL << 24); timestamp /= (1UL << 24);
if (icm_driver.fifo_highres_enabled) { if (icm_driver.fifo_highres_enabled) {
/* 고해상도 모드: 16비트 데이터를 4비트 좌측 시프트 + 하위 4비트 추가 → 20비트 */ /* High-res mode: left-shift 16-bit data by 4 + add lower 4 bits -> 20-bit */
accel[0] = (((int32_t)event->accel[0] << 4)) | event->accel_high_res[0]; accel[0] = (((int32_t)event->accel[0] << 4)) | event->accel_high_res[0];
accel[1] = (((int32_t)event->accel[1] << 4)) | event->accel_high_res[1]; accel[1] = (((int32_t)event->accel[1] << 4)) | event->accel_high_res[1];
accel[2] = (((int32_t)event->accel[2] << 4)) | event->accel_high_res[2]; accel[2] = (((int32_t)event->accel[2] << 4)) | event->accel_high_res[2];
@@ -301,7 +302,7 @@ void imu_callback(inv_imu_sensor_event_t *event)
gyro[2] = (((int32_t)event->gyro[2] << 4)) | event->gyro_high_res[2]; gyro[2] = (((int32_t)event->gyro[2] << 4)) | event->gyro_high_res[2];
} else { } else {
/* 표준 해상도: 16비트 데이터 그대로 사용 */ /* Standard resolution: use 16-bit data as-is */
accel[0] = event->accel[0]; accel[0] = event->accel[0];
accel[1] = event->accel[1]; accel[1] = event->accel[1];
accel[2] = event->accel[2]; accel[2] = event->accel[2];
@@ -312,7 +313,7 @@ void imu_callback(inv_imu_sensor_event_t *event)
} }
#else #else
/* 레지스터 직접 읽기 모드: 16비트 원시 데이터 추출 */ /* Direct register read mode: extract 16-bit raw data */
accel[0] = event->accel[0]; accel[0] = event->accel[0];
accel[1] = event->accel[1]; accel[1] = event->accel[1];
accel[2] = event->accel[2]; accel[2] = event->accel[2];
@@ -321,20 +322,20 @@ void imu_callback(inv_imu_sensor_event_t *event)
gyro[1] = event->gyro[1]; gyro[1] = event->gyro[1];
gyro[2] = event->gyro[2]; gyro[2] = event->gyro[2];
/* 레지스터 모드에서는 센서 마스크를 강제 설정하여 아래 출력 로직이 동작하도록 함 */ /* In register mode, force sensor mask so the output logic below works */
event->sensor_mask |= (1 << INV_SENSOR_TEMPERATURE); event->sensor_mask |= (1 << INV_SENSOR_TEMPERATURE);
event->sensor_mask |= (1 << INV_SENSOR_ACCEL); event->sensor_mask |= (1 << INV_SENSOR_ACCEL);
event->sensor_mask |= (1 << INV_SENSOR_GYRO); event->sensor_mask |= (1 << INV_SENSOR_GYRO);
#endif #endif
/* 마운팅 매트릭스 적용 — 센서의 물리적 장착 방향을 소프트웨어 좌표계로 보정 */ /* Apply mounting matrix — correct sensor physical orientation to software coordinates */
apply_mounting_matrix(icm_mounting_matrix, accel); apply_mounting_matrix(icm_mounting_matrix, accel);
apply_mounting_matrix(icm_mounting_matrix, gyro); apply_mounting_matrix(icm_mounting_matrix, gyro);
#if SCALED_DATA_G_DPS #if SCALED_DATA_G_DPS
/* /*
* 원시 데이터를 물리 단위(g, dps)로 변환 * Convert raw data to physical units (g, dps)
* 변환 공식: 물리값 = 원시값 * FSR / INT16_MAX * Formula: physical_value = raw_value * FSR / INT16_MAX
*/ */
get_accel_and_gyr_fsr(&accel_fsr_g, &gyro_fsr_dps); get_accel_and_gyr_fsr(&accel_fsr_g, &gyro_fsr_dps);
accel_g[0] = (float)(accel[0] * accel_fsr_g) / INT16_MAX; accel_g[0] = (float)(accel[0] * accel_fsr_g) / INT16_MAX;
@@ -344,14 +345,14 @@ void imu_callback(inv_imu_sensor_event_t *event)
gyro_dps[1] = (float)(gyro[1] * gyro_fsr_dps) / INT16_MAX; gyro_dps[1] = (float)(gyro[1] * gyro_fsr_dps) / INT16_MAX;
gyro_dps[2] = (float)(gyro[2] * gyro_fsr_dps) / INT16_MAX; gyro_dps[2] = (float)(gyro[2] * gyro_fsr_dps) / INT16_MAX;
/* 온도 변환: 고해상도/레지스터 모드는 /128, FIFO 표준 모드는 /2 */ /* Temperature conversion: high-res/register mode uses /128, FIFO standard mode uses /2 */
if (USE_HIGH_RES_MODE || !USE_FIFO) if (USE_HIGH_RES_MODE || !USE_FIFO)
temp_degc = 25 + ((float)event->temperature / 128); temp_degc = 25 + ((float)event->temperature / 128);
else else
temp_degc = 25 + ((float)event->temperature / 2); temp_degc = 25 + ((float)event->temperature / 2);
/* /*
* 스케일링된 데이터를 UART로 출력 * Output scaled data via UART
*/ */
if (event->sensor_mask & (1 << INV_SENSOR_ACCEL) && event->sensor_mask & (1 << INV_SENSOR_GYRO)) if (event->sensor_mask & (1 << INV_SENSOR_ACCEL) && event->sensor_mask & (1 << INV_SENSOR_GYRO))
DBG_PRINTF("%u: %.3f, \t%.3f, \t%.3f, \t%.3f, \t%.3f, \t%.3f, \t%.3f\r\n", DBG_PRINTF("%u: %.3f, \t%.3f, \t%.3f, \t%.3f, \t%.3f, \t%.3f, \t%.3f\r\n",
@@ -362,13 +363,13 @@ void imu_callback(inv_imu_sensor_event_t *event)
#else #else
/* /*
* 원시 데이터 출력 — 명령 소스(info4/UART/BLE)에 따라 분기 * Raw data output — branches by command source (info4/UART/BLE)
*/ */
if (event->sensor_mask & (1 << INV_SENSOR_ACCEL) && event->sensor_mask & (1 << INV_SENSOR_GYRO) || motion_raw_data_enabled) if (event->sensor_mask & (1 << INV_SENSOR_ACCEL) && event->sensor_mask & (1 << INV_SENSOR_GYRO) || motion_raw_data_enabled)
{ {
motion_raw_data_enabled = false; motion_raw_data_enabled = false;
/* info4 모드: 전역 배열 info_imu[6]에 데이터 저장, 외부 모듈에서 이 배열을 폴링하여 데이터 사용 */ /* info4 mode: store data in global array info_imu[6], polled by external modules */
if (info4 == true) if (info4 == true)
{ {
info_imu[0] = (uint16_t)accel[0]; info_imu[0] = (uint16_t)accel[0];
@@ -379,16 +380,16 @@ void imu_callback(inv_imu_sensor_event_t *event)
info_imu[5] = (uint16_t)gyro[2]; info_imu[5] = (uint16_t)gyro[2];
} }
/* UART 모드: "Tp" 접두사로 6축 데이터를 텍스트 형식으로 출력 */ /* UART mode: output 6-axis data in text format with "Tp" prefix */
else if(cmd_type_t == CMD_UART) { else if(cmd_type_t == CMD_UART) {
//DBG_PRINTF("Tp%d,%d,%d,%d,%d,%d\r\n\r\n", accel[0], accel[1], accel[2], gyro[0], gyro[1], gyro[2]); //DBG_PRINTF("Tp%d,%d,%d,%d,%d,%d\r\n\r\n", accel[0], accel[1], accel[2], gyro[0], gyro[1], gyro[2]);
} }
/* /*
* BLE 모드: 6축 데이터를 바이너리 패킷으로 BLE 전송 * BLE mode: send 6-axis data as binary packet via BLE
* ssp_data[0~2] = 가속도 XYZ, ssp_data[3~5] = 자이로 XYZ * ssp_data[0..2] = accel XYZ, ssp_data[3..5] = gyro XYZ
* format_data() "rsp:" 태그 + 12바이트 데이터를 패킷화 * format_data() packs "rsp:" tag + 12-byte data
* dr_binary_tx_safe()로 8바이트 BLE 전송 * dr_binary_tx_safe() sends 8 bytes via BLE
*/ */
else if(cmd_type_t == CMD_BLE) { else if(cmd_type_t == CMD_BLE) {
ssp_data[0] = (uint16_t)accel[0]; ssp_data[0] = (uint16_t)accel[0];
@@ -419,13 +420,13 @@ void imu_callback(inv_imu_sensor_event_t *event)
/* /*
* apply_mounting_matrix() * apply_mounting_matrix()
* Q30 고정소수점 회전 매트릭스를 3축 벡터에 적용한다. * Applies a Q30 fixed-point rotation matrix to a 3-axis vector.
* *
* 계산 방식: * Calculation:
* result[i] = matrix[i*3+0]*raw[0] + matrix[i*3+1]*raw[1] + matrix[i*3+2]*raw[2] * result[i] = matrix[i*3+0]*raw[0] + matrix[i*3+1]*raw[1] + matrix[i*3+2]*raw[2]
* 결과를 30비트 우측 시프트하여 Q30 → 정수 변환 * Right-shift result by 30 bits for Q30 -> integer conversion.
* *
* 이를 통해 센서의 물리적 장착 방향에 관계없이 일관된 좌표계를 사용할 수 있다. * Ensures a consistent coordinate system regardless of physical sensor orientation.
*/ */
static void apply_mounting_matrix(const int32_t matrix[9], int32_t raw[3]) static void apply_mounting_matrix(const int32_t matrix[9], int32_t raw[3])
{ {
@@ -437,7 +438,7 @@ static void apply_mounting_matrix(const int32_t matrix[9], int32_t raw[3])
data_q30[i] += ((int64_t)matrix[3*i+1] * raw[1]); data_q30[i] += ((int64_t)matrix[3*i+1] * raw[1]);
data_q30[i] += ((int64_t)matrix[3*i+2] * raw[2]); data_q30[i] += ((int64_t)matrix[3*i+2] * raw[2]);
} }
/* Q30 → 정수 변환: 30비트 우측 시프트 */ /* Q30 -> integer conversion: right-shift by 30 bits */
raw[0] = (int32_t)(data_q30[0]>>30); raw[0] = (int32_t)(data_q30[0]>>30);
raw[1] = (int32_t)(data_q30[1]>>30); raw[1] = (int32_t)(data_q30[1]>>30);
raw[2] = (int32_t)(data_q30[2]>>30); raw[2] = (int32_t)(data_q30[2]>>30);
@@ -446,22 +447,22 @@ static void apply_mounting_matrix(const int32_t matrix[9], int32_t raw[3])
/* /*
* imu_read_direct() * imu_read_direct()
* 드라이버 API를 우회하여 직접 I2C 레지스터를 읽는 함수. * Reads IMU registers directly via I2C, bypassing the driver API.
* DRDY(데이터 준비) 인터럽트를 기다리지 않고 즉시 데이터를 읽는다. * Reads data immediately without waiting for DRDY interrupt.
* *
* 처리 흐름: * Flow:
* 1) TWI 초기화 확인 (최초 1회만) * 1) Check TWI initialization (first call only)
* 2) 자이로 설정: ±2000dps, 100Hz ODR (GYRO_CONFIG0 = 0x09) * 2) Gyro config: +/-2000dps, 100Hz ODR (GYRO_CONFIG0 = 0x09)
* 3) 가속도 설정: ±4g, 100Hz ODR (ACCEL_CONFIG0 = 0x29) * 3) Accel config: +/-4g, 100Hz ODR (ACCEL_CONFIG0 = 0x29)
* 4) 전원 ON: 가속도+자이로 저잡음 모드 (PWR_MGMT0 = 0x0F) * 4) Power ON: accel+gyro low-noise mode (PWR_MGMT0 = 0x0F)
* 5) 80ms 대기 (자이로 스타트업: 최소 45ms + 여유) * 5) Wait 80ms (gyro startup: min 45ms + margin)
* 6) ACCEL_DATA_X1(0x0B)부터 12바이트 연속 읽기 (accel 6 + gyro 6) * 6) Read 12 consecutive bytes from ACCEL_DATA_X1 (0x0B) (accel 6 + gyro 6)
* 7) 빅엔디안 → int16_t 변환 * 7) Big-endian -> int16_t conversion
* 8) 마운팅 매트릭스 적용 * 8) Apply mounting matrix
* 9) "rsp:" 태그로 BLE 전송 * 9) Send via BLE with "rsp:" tag
* 10) IMU 슬립 모드로 전환 (전력 절감) * 10) Switch IMU to sleep mode (power saving)
* *
* 반환값: 0=성공, -1=TX 실패, -2=RX 실패 * Returns: 0=success, -1=TX failure, -2=RX failure
*/ */
/* Raw I2C read from ICM42670P — bypasses driver API entirely */ /* Raw I2C read from ICM42670P — bypasses driver API entirely */
#include "system_interface.h" #include "system_interface.h"
@@ -470,51 +471,51 @@ static void apply_mounting_matrix(const int32_t matrix[9], int32_t raw[3])
extern const nrfx_twi_t m_twi_icm42670; extern const nrfx_twi_t m_twi_icm42670;
#define IMU_I2C_ADDR 0x68 #define IMU_I2C_ADDR 0x68
#define REG_ACCEL_X1 0x0B /* ACCEL_DATA_X1 — 가속도 X축 상위 바이트 레지스터 */ #define REG_ACCEL_X1 0x0B /* ACCEL_DATA_X1 — accel X-axis upper byte register */
/* -------------------------------------------------------------------------------------- /* --------------------------------------------------------------------------------------
* Direct IMU register read — raw I2C, no DRDY, sends rsp: via BLE * Direct IMU register read — raw I2C, no DRDY, sends rsp: via BLE
* 직접 I2C로 레지스터 읽는 방식 (인터럽트 X, IMU 드라이버 API X) * Direct I2C register read (no interrupt, no IMU driver API)
* -------------------------------------------------------------------------------------- */ * -------------------------------------------------------------------------------------- */
int imu_read_direct(void) int imu_read_direct(void)
{ {
uint8_t raw[12]; /* 가속도 6바이트 + 자이로 6바이트 */ uint8_t raw[12]; /* accel 6 bytes + gyro 6 bytes */
int32_t accel[3], gyro[3]; int32_t accel[3], gyro[3];
uint8_t reg; uint8_t reg;
uint32_t ret; uint32_t ret;
static bool twi_ready = false; static bool twi_ready = false;
/* TWI(I2C) 초기화 — 최초 1회만 수행 (재초기화로 클린 상태 보장) */ /* TWI (I2C) init — performed only once (re-init ensures clean state) */
if (!twi_ready) { if (!twi_ready) {
inv_i2c_master_uninitialize(); inv_i2c_master_uninitialize();
inv_i2c_master_initialize(); inv_i2c_master_initialize();
twi_ready = true; twi_ready = true;
} }
/* 자이로 설정: GYRO_CONFIG0(0x20) = 0x09 → ±2000dps FSR, 100Hz ODR */ /* Gyro config: GYRO_CONFIG0(0x20) = 0x09 -> +/-2000dps FSR, 100Hz ODR */
{ {
uint8_t gyro_cfg[2] = { 0x20, 0x09 }; uint8_t gyro_cfg[2] = { 0x20, 0x09 };
icm42670_twi_tx(IMU_I2C_ADDR, gyro_cfg, 2, false); icm42670_twi_tx(IMU_I2C_ADDR, gyro_cfg, 2, false);
} }
/* 가속도 설정: ACCEL_CONFIG0(0x21) = 0x29 → ±4g FSR, 100Hz ODR */ /* Accel config: ACCEL_CONFIG0(0x21) = 0x29 -> +/-4g FSR, 100Hz ODR */
{ {
uint8_t accel_cfg[2] = { 0x21, 0x29 }; uint8_t accel_cfg[2] = { 0x21, 0x29 };
icm42670_twi_tx(IMU_I2C_ADDR, accel_cfg, 2, false); icm42670_twi_tx(IMU_I2C_ADDR, accel_cfg, 2, false);
} }
/* 전원 ON: PWR_MGMT0(0x1F) = 0x0F → 가속도(저잡음) + 자이로(저잡음) 활성화 */ /* Power ON: PWR_MGMT0(0x1F) = 0x0F -> accel (low-noise) + gyro (low-noise) enabled */
{ {
uint8_t pwr_cmd[2] = { 0x1F, 0x0F }; /* reg=0x1F, val=0x0F */ uint8_t pwr_cmd[2] = { 0x1F, 0x0F }; /* reg=0x1F, val=0x0F */
icm42670_twi_tx(IMU_I2C_ADDR, pwr_cmd, 2, false); icm42670_twi_tx(IMU_I2C_ADDR, pwr_cmd, 2, false);
//nrf_delay_ms(80); /* 자이로 스타트업: 최소 45ms + 안전 마진 */ //nrf_delay_ms(80); /* Gyro startup: min 45ms + safety margin */
dr_sd_delay_ms(80); dr_sd_delay_ms(80);
} }
/* ACCEL_DATA_X1(0x0B)부터 12바이트 연속 읽기 (0x0B~0x16) */ /* Read 12 consecutive bytes from ACCEL_DATA_X1 (0x0B~0x16) */
reg = REG_ACCEL_X1; reg = REG_ACCEL_X1;
ret = icm42670_twi_tx(IMU_I2C_ADDR, &reg, 1, true); /* 레지스터 주소 전송 (STOP 없음) */ ret = icm42670_twi_tx(IMU_I2C_ADDR, &reg, 1, true); /* Send register address (no STOP) */
if (ret) if (ret)
{ {
@@ -522,7 +523,7 @@ int imu_read_direct(void)
return -1; return -1;
} }
ret = icm42670_twi_rx(IMU_I2C_ADDR, raw, 12); /* 12바이트 데이터 수신 */ ret = icm42670_twi_rx(IMU_I2C_ADDR, raw, 12); /* Receive 12 bytes of data */
if (ret) if (ret)
{ {
@@ -531,9 +532,9 @@ int imu_read_direct(void)
} }
/* /*
* 빅엔디안 레지스터 레이아웃을 int16_t로 변환 * Convert big-endian register layout to int16_t
* raw[0..5] = 가속도 X,Y,Z (각 2바이트, MSB first) * raw[0..5] = accel X,Y,Z (2 bytes each, MSB first)
* raw[6..11] = 자이로 X,Y,Z (각 2바이트, MSB first) * raw[6..11] = gyro X,Y,Z (2 bytes each, MSB first)
*/ */
accel[0] = (int16_t)((raw[0] << 8) | raw[1]); accel[0] = (int16_t)((raw[0] << 8) | raw[1]);
accel[1] = (int16_t)((raw[2] << 8) | raw[3]); accel[1] = (int16_t)((raw[2] << 8) | raw[3]);
@@ -542,11 +543,11 @@ int imu_read_direct(void)
gyro[1] = (int16_t)((raw[8] << 8) | raw[9]); gyro[1] = (int16_t)((raw[8] << 8) | raw[9]);
gyro[2] = (int16_t)((raw[10] << 8) | raw[11]); gyro[2] = (int16_t)((raw[10] << 8) | raw[11]);
/* 마운팅 매트릭스 적용 — 보드 장착 방향 보정 */ /* Apply mounting matrix — board orientation correction */
apply_mounting_matrix(icm_mounting_matrix, accel); apply_mounting_matrix(icm_mounting_matrix, accel);
apply_mounting_matrix(icm_mounting_matrix, gyro); apply_mounting_matrix(icm_mounting_matrix, gyro);
/* 데이터 패킹 */ /* Pack data */
ssp_data[0] = (uint16_t)accel[0]; ssp_data[0] = (uint16_t)accel[0];
ssp_data[1] = (uint16_t)accel[1]; ssp_data[1] = (uint16_t)accel[1];
ssp_data[2] = (uint16_t)accel[2]; ssp_data[2] = (uint16_t)accel[2];
@@ -556,7 +557,7 @@ int imu_read_direct(void)
if (info4 == true) if (info4 == true)
{ {
/* info4 모드: 전역 배열에 저장 (mbb?에서 rbb: 패킷으로 일괄 전송) */ /* info4 mode: store in global array (sent as rbb: packet by mbb?) */
info_imu[0] = ssp_data[0]; info_imu[0] = ssp_data[0];
info_imu[1] = ssp_data[1]; info_imu[1] = ssp_data[1];
info_imu[2] = ssp_data[2]; info_imu[2] = ssp_data[2];
@@ -566,12 +567,12 @@ int imu_read_direct(void)
} }
else else
{ {
/* 일반 모드: "rsp:" 태그로 BLE 즉시 전송 */ /* Normal mode: send immediately via BLE with "rsp:" tag */
format_data(imu_bin_buffer, "rsp:", ssp_data, 12); format_data(imu_bin_buffer, "rsp:", ssp_data, 12);
dr_binary_tx_safe(imu_bin_buffer, 8); dr_binary_tx_safe(imu_bin_buffer, 8);
} }
/* IMU 슬립 모드: PWR_MGMT0 = 0x00 → 가속도/자이로 모두 OFF (전력 절감) */ /* IMU sleep mode: PWR_MGMT0 = 0x00 -> accel/gyro both OFF (power saving) */
{ {
uint8_t pwr_off[2] = { 0x1F, 0x00 }; /* reg=PWR_MGMT0, val=0x00 */ uint8_t pwr_off[2] = { 0x1F, 0x00 }; /* reg=PWR_MGMT0, val=0x00 */
icm42670_twi_tx(IMU_I2C_ADDR, pwr_off, 2, false); icm42670_twi_tx(IMU_I2C_ADDR, pwr_off, 2, false);
@@ -7,16 +7,16 @@
******************************************************************************/ ******************************************************************************/
/******************************************************************************* /*******************************************************************************
* [헤더 개요] ICM42670P IMU 드라이버 상위 레이어 선언 * [Header overview] ICM42670P IMU driver application layer declarations
* *
* IMU 센서의 초기화, 설정, 데이터 읽기를 위한 함수 프로토타입과 * Function prototypes and operating mode configuration macros for
* 동작 모드 설정 매크로를 정의한다. * IMU sensor initialization, configuration, and data reading.
* *
* 주요 설정 매크로: * Key configuration macros:
* SERIF_TYPE - 통신 인터페이스 (UI_I2C) * SERIF_TYPE - Communication interface (UI_I2C)
* USE_LOW_NOISE_MODE - 1:저잡음(800Hz), 0:저전력(100Hz) * USE_LOW_NOISE_MODE - 1: low-noise (800Hz), 0: low-power (100Hz)
* USE_HIGH_RES_MODE - 1:20비트 고해상도, 0:16비트 표준 * USE_HIGH_RES_MODE - 1: 20-bit high-res, 0: 16-bit standard
* USE_FIFO - 1:FIFO 사용, 0:레지스터 직접 읽기 * USE_FIFO - 1: use FIFO, 0: direct register read
******************************************************************************/ ******************************************************************************/
#ifndef _APP_RAW_H_ #ifndef _APP_RAW_H_
@@ -29,71 +29,71 @@
#include "inv_imu_driver.h" #include "inv_imu_driver.h"
/*** 설정 매크로 ***/ /*** Configuration macros ***/
/* /*
* MCUIMU 간 통신 인터페이스 선택 * MCU-IMU communication interface selection
* UI_I2C: I2C 통신 사용 (기본) * UI_I2C: use I2C communication (default)
*/ */
#define SERIF_TYPE UI_I2C #define SERIF_TYPE UI_I2C
/* /*
* 전원 모드 설정 * Power mode selection
* 1: 저잡음 모드 — 800Hz ODR, 높은 정밀도, 높은 전력 소모 * 1: Low-noise mode — 800Hz ODR, high precision, higher power consumption
* 0: 저전력 모드 — 100Hz ODR, 낮은 전력 소모 * 0: Low-power mode — 100Hz ODR, lower power consumption
* 주의: 12.5Hz 미만 ODR에서는 저잡음 모드 사용 불가 * Note: Low-noise mode cannot be used with ODR below 12.5Hz
*/ */
#define USE_LOW_NOISE_MODE 1 #define USE_LOW_NOISE_MODE 1
/* /*
* FIFO 해상도 모드 선택 * FIFO resolution mode selection
* 0: 저해상도 — 16비트 데이터 (기본) * 0: Low resolution — 16-bit data (default)
* 1: 고해상도 — 20비트 데이터 (FSR이 16g/2000dps로 강제 고정됨) * 1: High resolution — 20-bit data (FSR locked to 16g/2000dps)
*/ */
#define USE_HIGH_RES_MODE 0 #define USE_HIGH_RES_MODE 0
/* /*
* 데이터 읽기 방식 선택 * Data read method selection
* 0: 레지스터 직접 읽기 (현재 사용 중) * 0: Direct register read (currently in use)
* 1: FIFO에서 읽기 * 1: Read from FIFO
*/ */
#define USE_FIFO 0 #define USE_FIFO 0
/** /**
* \brief IMU 디바이스를 리셋하고 초기화한다. WHOAMI 확인 포함. * \brief Resets and initializes the IMU device. Includes WHOAMI verification.
* 다른 IMU 접근 함수 호출 전에 반드시 성공적으로 실행되어야 한다. * Must complete successfully before calling any other IMU access functions.
* *
* \return 0=성공, 음수=에러 * \return 0=success, negative=error
*/ */
int setup_imu_device(struct inv_imu_serif *icm_serif); int setup_imu_device(struct inv_imu_serif *icm_serif);
/** /**
* \brief 자이로 및 가속도계 출력을 위한 디바이스 설정을 수행한다. * \brief Configures the device for gyro and accel output.
* FSR, ODR, 전원 모드, FIFO 설정 등을 적용한다. * Applies FSR, ODR, power mode, and FIFO settings.
* \return 0=성공, 음수=에러 * \return 0=success, negative=error
*/ */
int configure_imu_device(void); int configure_imu_device(void);
/** /**
* \brief FIFO 또는 레지스터에서 IMU 데이터를 추출한다. * \brief Retrieves IMU data from FIFO or registers.
* 내부적으로 imu_callback()이 호출되어 데이터를 처리한다. * Internally triggers imu_callback() for data processing.
* \return 0=성공, 음수=에러 * \return 0=success, negative=error
*/ */
int get_imu_data(void); int get_imu_data(void);
/** /**
* \brief 센서 데이터 수신 콜백. 마운팅 매트릭스 적용 후 * \brief Sensor data receive callback. Applies mounting matrix then
* info4/BLE/UART 모드에 따라 데이터를 출력한다. * outputs data according to info4/BLE/UART mode.
* \param[in] event 하나의 센서 데이터 패킷을 담은 구조체 * \param[in] event Structure containing one sensor data packet
*/ */
void imu_callback(inv_imu_sensor_event_t *event); void imu_callback(inv_imu_sensor_event_t *event);
/** /**
* \brief 드라이버 API를 우회한 직접 I2C 레지스터 읽기. * \brief Direct I2C register read bypassing the driver API.
* DRDY 인터럽트 없이 즉시 센서 데이터를 읽어 BLE로 전송한다. * Reads sensor data immediately without DRDY interrupt and sends via BLE.
* 읽기 후 IMU를 슬립 모드로 전환하여 전력을 절감한다. * Switches IMU to sleep mode after reading to save power.
* \return 0=성공, 음수=에러 * \return 0=success, negative=error
*/ */
int imu_read_direct(void); int imu_read_direct(void);
@@ -8,30 +8,31 @@
/******************************************************************************* /*******************************************************************************
* 2026.03.26 jhChun * 2026.03.26 jhChun
* 현재 이 파일은 실제 런타임에 실행되지 않고 있음 * This file is currently not executed at runtime.
* 인터럽트 방식 대신 app_raw.c imu_read_direct()에서 직접 레지스터 읽는 방식 사용 중 * Instead of interrupt-driven reads, imu_read_direct() in app_raw.c
* 추후 필요 여부에 따라 정리 예정 * reads registers directly. May be cleaned up later as needed.
******************************************************************************/ ******************************************************************************/
/******************************************************************************* /*******************************************************************************
* [모듈 개요] ICM42670P 메인 초기화 및 폴링 루프 * [Module overview] ICM42670P main initialization and polling loop
* *
* ICM42670P IMU 센서의 전체 초기화 시퀀스와 메인 루프를 담당한다. * Handles the full initialization sequence and main loop for the ICM42670P
* IMU sensor.
* *
* 초기화 흐름 (icm42670_init): * Init flow (icm42670_init):
* 1) setup_mcu() - I2C 시리얼 인터페이스 구조체 설정 및 TWI 초기화 * 1) setup_mcu() - Configure I2C serial interface struct and TWI init
* 2) setup_imu_device() - IMU 드라이버 초기화 + WHOAMI 확인 * 2) setup_imu_device() - IMU driver init + WHOAMI verification
* 3) configure_imu_device() - 센서 파라미터 설정 (FSR, ODR, 전원 모드) * 3) configure_imu_device() - Sensor parameter config (FSR, ODR, power mode)
* 4) inv_gpio_sensor_irq_init() - INT1(P1.13) GPIO 인터럽트 설정 * 4) inv_gpio_sensor_irq_init() - INT1 (P1.13) GPIO interrupt setup
* *
* 메인 루프 (icm42670_main): * Main loop (icm42670_main):
* - INT1 인터럽트 발생 시 irq_from_device 플래그가 세팅됨 * - irq_from_device flag is set when INT1 interrupt fires
* - 메인 루프에서 플래그를 확인하고, 세팅되어 있으면 센서 데이터를 읽음 * - Main loop checks the flag and reads sensor data when set
* - 인터럽트는 하강 에지(HITOLO)에서 발생 (INT1 핀 풀업 설정) * - Interrupt triggers on falling edge (HITOLO) with INT1 pin pulled up
* *
* 보조 함수: * Helper functions:
* - inv_imu_sleep_us() - nrf_delay_us 래퍼 (IMU 드라이버가 사용) * - inv_imu_sleep_us() - nrf_delay_us wrapper (used by IMU driver)
* - inv_imu_get_time_us() - RTC1 카운터로 타임스탬프 제공 * - inv_imu_get_time_us() - Provides timestamp via RTC1 counter
******************************************************************************/ ******************************************************************************/
#include "sdk_config.h" #include "sdk_config.h"
#include "app_raw.h" #include "app_raw.h"
@@ -50,7 +51,7 @@
#include "nrf_delay.h" #include "nrf_delay.h"
#include "app_util_platform.h" #include "app_util_platform.h"
#include "main.h" /* 2026-03-17: cmd_parse.h 삭제 → main.h */ #include "main.h" /* 2026-03-17: removed cmd_parse.h, using main.h */
#include "i2c_manager.h" #include "i2c_manager.h"
/* -------------------------------------------------------------------------------------- /* --------------------------------------------------------------------------------------
* Global variables * Global variables
@@ -61,10 +62,10 @@
* -------------------------------------------------------------------------------------- */ * -------------------------------------------------------------------------------------- */
/* /*
* IMU 인터럽트 플래그 * IMU interrupt flag
* INT1 핀의 하강 에지 인터럽트 발생 시 1로 세팅된다. * Set to 1 on falling-edge interrupt of INT1 pin.
* 메인 루프에서 이 플래그를 확인 후 데이터를 읽고 0으로 클리어한다. * Main loop checks this flag, reads data, and clears it to 0.
* volatile: ISR에서 변경되므로 컴파일러 최적화 방지 * volatile: modified by ISR, prevents compiler optimization.
*/ */
static volatile int irq_from_device; static volatile int irq_from_device;
@@ -90,9 +91,9 @@ static int setup_mcu(struct inv_imu_serif *icm_serif);
*/ */
/* /*
* inv_gpio_sensor_interrupt_handler() * inv_gpio_sensor_interrupt_handler()
* INT1 핀 인터럽트 핸들러 (ISR). * INT1 pin interrupt handler (ISR).
* 센서가 새 데이터를 준비했을 때 호출되며, 플래그만 세팅하고 즉시 반환한다. * Called when the sensor has new data ready; sets a flag and returns immediately.
* 실제 데이터 처리는 메인 루프(icm42670_main)에서 수행한다. * Actual data processing is done in the main loop (icm42670_main).
*/ */
static void inv_gpio_sensor_interrupt_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action) static void inv_gpio_sensor_interrupt_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{ {
@@ -102,52 +103,52 @@ static void inv_gpio_sensor_interrupt_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_
/* /*
* inv_gpio_sensor_irq_init() * inv_gpio_sensor_irq_init()
* INT1(P1.13) GPIO 인터럽트를 초기화한다. * Initializes INT1 (P1.13) GPIO interrupt.
* *
* 설정: * Configuration:
* - 트리거: 하강 에지 (HITOLO) — 센서가 INT를 Low로 끌어내릴 때 * - Trigger: falling edge (HITOLO) — when the sensor pulls INT low
* - 풀업 저항: 내부 풀업 활성화 * - Pull-up: internal pull-up enabled
* - 핸들러: inv_gpio_sensor_interrupt_handler * - Handler: inv_gpio_sensor_interrupt_handler
* - GPIOTE 모듈이 미초기화 상태이면 먼저 초기화 * - Initializes GPIOTE module first if not already initialized
*/ */
void inv_gpio_sensor_irq_init(void) void inv_gpio_sensor_irq_init(void)
{ {
ret_code_t err_code; ret_code_t err_code;
/* GPIOTE 모듈 초기화 (이미 초기화되어 있으면 건너뜀) */ /* Initialize GPIOTE module (skip if already initialized) */
if (!nrfx_gpiote_is_init()) if (!nrfx_gpiote_is_init())
{ {
err_code = nrfx_gpiote_init(); err_code = nrfx_gpiote_init();
APP_ERROR_CHECK(err_code); APP_ERROR_CHECK(err_code);
} }
/* 하강 에지 인터럽트 설정: HighLow 전환 시 트리거, 내부 풀업 사용 */ /* Falling-edge interrupt: trigger on High->Low transition, internal pull-up */
nrfx_gpiote_in_config_t in_config = NRFX_GPIOTE_CONFIG_IN_SENSE_HITOLO(true); nrfx_gpiote_in_config_t in_config = NRFX_GPIOTE_CONFIG_IN_SENSE_HITOLO(true);
in_config.pull = NRF_GPIO_PIN_PULLUP; in_config.pull = NRF_GPIO_PIN_PULLUP;
/* INT1 핀에 인터럽트 핸들러 등록 */ /* Register interrupt handler for INT1 pin */
err_code = nrfx_gpiote_in_init(ICM42670_INT1_PIN, &in_config, inv_gpio_sensor_interrupt_handler); err_code = nrfx_gpiote_in_init(ICM42670_INT1_PIN, &in_config, inv_gpio_sensor_interrupt_handler);
APP_ERROR_CHECK(err_code); APP_ERROR_CHECK(err_code);
/* 인터럽트 이벤트 활성화 */ /* Enable interrupt event */
nrfx_gpiote_in_event_enable(ICM42670_INT1_PIN, true); nrfx_gpiote_in_event_enable(ICM42670_INT1_PIN, true);
} }
/* /*
* inv_gpio_sensor_irq_uninit() * inv_gpio_sensor_irq_uninit()
* INT1 GPIO 인터럽트를 비활성화하고 해제한다. * Disables and releases the INT1 GPIO interrupt.
* 센서 비활성화 시 또는 재초기화 전에 호출된다. * Called when deactivating the sensor or before re-initialization.
*/ */
void inv_gpio_sensor_irq_uninit(void) void inv_gpio_sensor_irq_uninit(void)
{ {
/* 인터럽트 이벤트 비활성화 */ /* Disable interrupt event */
nrfx_gpiote_in_event_disable(ICM42670_INT1_PIN); nrfx_gpiote_in_event_disable(ICM42670_INT1_PIN);
/* INT1 핀 인터럽트 설정 해제 */ /* Release INT1 pin interrupt configuration */
nrfx_gpiote_in_uninit(ICM42670_INT1_PIN); nrfx_gpiote_in_uninit(ICM42670_INT1_PIN);
/* GPIOTE 모듈 해제 (초기화된 경우에만) */ /* Release GPIOTE module (only if initialized) */
if (nrfx_gpiote_is_init()) if (nrfx_gpiote_is_init())
{ {
nrfx_gpiote_uninit(); nrfx_gpiote_uninit();
@@ -161,15 +162,15 @@ void inv_gpio_sensor_irq_uninit(void)
/* /*
* icm42670_init() * icm42670_init()
* ICM42670P 전체 초기화 시퀀스를 수행한다. * Performs the full ICM42670P initialization sequence.
* *
* 초기화 순서: * Init order:
* 1) setup_mcu() - I2C 인터페이스 구조체 설정 및 TWI 하드웨어 초기화 * 1) setup_mcu() - Configure I2C interface struct and TWI hardware init
* 2) setup_imu_device() - IMU 드라이버 초기화, WHOAMI(0x67) 확인 * 2) setup_imu_device() - IMU driver init, WHOAMI (0x67) verification
* 3) configure_imu_device() - FSR, ODR, 전원 모드 설정 * 3) configure_imu_device() - FSR, ODR, power mode configuration
* 4) inv_gpio_sensor_irq_init() - INT1 인터럽트 활성화 (데이터 준비 알림) * 4) inv_gpio_sensor_irq_init() - Enable INT1 interrupt (data-ready notification)
* *
* 반환값: 0=성공, -1=초기화 실패 * Returns: 0=success, -1=initialization failure
*/ */
int icm42670_init(void) int icm42670_init(void)
{ {
@@ -185,7 +186,7 @@ int icm42670_init(void)
return -1; return -1;
} }
/* 초기화 성공 후 INT1 인터럽트 활성화 — 이후 데이터 준비 시 ISR이 호출됨 */ /* Enable INT1 interrupt after successful init — ISR fires on data ready */
inv_gpio_sensor_irq_init(); inv_gpio_sensor_irq_init();
return rc; return rc;
@@ -194,22 +195,23 @@ int icm42670_init(void)
/* /*
* icm42670_main() * icm42670_main()
* ICM42670P 메인 폴링 루프. * ICM42670P main polling loop.
* 메인 애플리케이션 루프에서 주기적으로 호출되어야 한다. * Must be called periodically from the main application loop.
* *
* 동작: * Operation:
* 1) I2C 하드웨어가 초기화되었는지 확인 (hw_i2c_init_once) * 1) Check if I2C hardware is initialized (hw_i2c_init_once)
* 2) irq_from_device 플래그 확인 (ISR에서 세팅됨) * 2) Check irq_from_device flag (set by ISR)
* 3) 플래그가 세팅되어 있으면 센서 데이터 읽기 (get_imu_data) * 3) If flag is set, read sensor data (get_imu_data)
* 4) 데이터 읽기 완료 후 플래그 클리어 * 4) Clear flag after data read completes
* *
* 참고: 인터럽트 기반 폴링 방식으로, ISR에서는 플래그만 세팅하고 실제 I2C 통신은 메인 컨텍스트에서 수행한다. * Note: Interrupt-based polling — ISR only sets the flag; actual I2C
* communication is done in the main context.
*/ */
void icm42670_main(void) void icm42670_main(void)
{ {
int rc = 0; int rc = 0;
hw_i2c_init_once(); hw_i2c_init_once();
/* 인터럽트 발생 여부 확인 후 데이터 읽기 */ /* Check for interrupt and read data */
if (irq_from_device) { if (irq_from_device) {
rc = get_imu_data(); rc = get_imu_data();
@@ -218,7 +220,7 @@ void icm42670_main(void)
printf("error while getting data\r\n"); printf("error while getting data\r\n");
} }
/* 플래그 클리어 — 다음 인터럽트까지 대기 */ /* Clear flag — wait for next interrupt */
irq_from_device = 0; irq_from_device = 0;
} }
@@ -231,28 +233,28 @@ void icm42670_main(void)
/* /*
* setup_mcu() * setup_mcu()
* MCU 측 시리얼 인터페이스를 설정한다. * Configures the MCU-side serial interface.
* *
* inv_imu_serif 구조체에 다음을 등록: * Registers the following in the inv_imu_serif struct:
* - read_reg / write_reg : I2C 읽기/쓰기 콜백 함수 (system_interface.c에서 구현) * - read_reg / write_reg : I2C read/write callbacks (implemented in system_interface.c)
* - max_read / max_write : 최대 전송 크기 (32KB) * - max_read / max_write : Max transfer size (32KB)
* - serif_type : 통신 타입 (UI_I2C) * - serif_type : Communication type (UI_I2C)
* *
* 설정 후 inv_io_hal_init()을 호출하여 실제 TWI 하드웨어를 초기화한다. * After configuration, calls inv_io_hal_init() to initialize the TWI hardware.
*/ */
static int setup_mcu(struct inv_imu_serif *icm_serif) static int setup_mcu(struct inv_imu_serif *icm_serif)
{ {
int rc = 0; int rc = 0;
/* IMU 드라이버용 시리얼 인터페이스 구조체 설정 */ /* Configure serial interface struct for IMU driver */
icm_serif->context = 0; /* 컨텍스트 미사용 */ icm_serif->context = 0; /* Context unused */
icm_serif->read_reg = inv_io_hal_read_reg; /* 레지스터 읽기 콜백 */ icm_serif->read_reg = inv_io_hal_read_reg; /* Register read callback */
icm_serif->write_reg = inv_io_hal_write_reg; /* 레지스터 쓰기 콜백 */ icm_serif->write_reg = inv_io_hal_write_reg; /* Register write callback */
icm_serif->max_read = 1024*32; /* 1회 읽기 최대 바이트 수 */ icm_serif->max_read = 1024*32; /* Max bytes per read */
icm_serif->max_write = 1024*32; /* 1회 쓰기 최대 바이트 수 */ icm_serif->max_write = 1024*32; /* Max bytes per write */
icm_serif->serif_type = SERIF_TYPE; /* UI_I2C (app_raw.h에서 정의) */ icm_serif->serif_type = SERIF_TYPE; /* UI_I2C (defined in app_raw.h) */
/* TWI 하드웨어 초기화 */ /* Initialize TWI hardware */
rc |= inv_io_hal_init(icm_serif); rc |= inv_io_hal_init(icm_serif);
return rc; return rc;
@@ -265,9 +267,9 @@ static int setup_mcu(struct inv_imu_serif *icm_serif)
/* /*
* inv_imu_sleep_us() * inv_imu_sleep_us()
* IMU 드라이버가 사용하는 마이크로초 단위 슬립 함수. * Microsecond sleep function used by the IMU driver.
* nrf_delay_us()를 래핑하여 플랫폼 독립적 인터페이스를 제공한다. * Wraps nrf_delay_us() to provide a platform-independent interface.
* 예: 자이로 스타트업 대기(GYR_STARTUP_TIME_US) 시 사용 * Example: used for gyro startup delay (GYR_STARTUP_TIME_US).
*/ */
void inv_imu_sleep_us(uint32_t us) void inv_imu_sleep_us(uint32_t us)
{ {
@@ -277,12 +279,12 @@ void inv_imu_sleep_us(uint32_t us)
/* /*
* inv_imu_get_time_us() * inv_imu_get_time_us()
* IMU 드라이버가 사용하는 타임스탬프 함수. * Timestamp function used by the IMU driver.
* nRF52840 RTC1 카운터 값을 반환한다. * Returns the nRF52840 RTC1 counter value.
* *
* 주의: RTC1 32.768kHz로 동작하므로, 반환값의 단위는 엄밀히 * Note: RTC1 runs at 32.768kHz, so the returned value is technically
* 마이크로초가 아닌 RTC 틱(약 30.5us/tick)이다. * in RTC ticks (~30.5us/tick), not microseconds.
* 드라이버 내부에서 상대적 시간 비교 용도로 사용된다. * Used for relative time comparisons within the driver.
*/ */
uint64_t inv_imu_get_time_us(void) uint64_t inv_imu_get_time_us(void)
{ {
@@ -7,25 +7,26 @@
******************************************************************************/ ******************************************************************************/
/******************************************************************************* /*******************************************************************************
* [헤더 개요] ICM42670P 메인 초기화/폴링 루프 선언 * [Header overview] ICM42670P main initialization/polling loop declarations
* *
* ICM42670P IMU 센서의 전체 초기화 및 메인 루프 함수를 선언한다. * Declares the full initialization and main loop functions for the
* - icm42670_init() : 전체 초기화 (MCU설정 → IMU초기화 → 센서설정 → 인터럽트 활성화) * ICM42670P IMU sensor.
* - icm42670_main() : 메인 폴링 루프 (INT1 인터럽트 확인 → 데이터 읽기) * - icm42670_init() : Full init (MCU config -> IMU init -> sensor config -> enable IRQ)
* - icm42670_uninit() : 해제 (프로토타입만 선언, 구현은 별도) * - icm42670_main() : Main polling loop (check INT1 interrupt -> read data)
* - icm42670_uninit() : Release (prototype only, implementation elsewhere)
******************************************************************************/ ******************************************************************************/
#ifndef _APP_RAW_MAIN_H_ #ifndef _APP_RAW_MAIN_H_
#define _APP_RAW_MAIN_H_ #define _APP_RAW_MAIN_H_
#include "sdk_config.h" #include "sdk_config.h"
/* ICM42670P 전체 초기화 — MCU I2C 설정 → IMU 드라이버 초기화 → 센서 설정 → 인터럽트 활성화 */ /* ICM42670P full init — MCU I2C config -> IMU driver init -> sensor config -> enable IRQ */
int icm42670_init(void); int icm42670_init(void);
/* ICM42670P 메인 폴링 루프 — INT1 인터럽트 플래그 확인 후 센서 데이터 읽기 */ /* ICM42670P main polling loop — check INT1 interrupt flag, then read sensor data */
void icm42670_main(void); void icm42670_main(void);
/* ICM42670P 해제 (프로토타입 선언) */ /* ICM42670P release (prototype declaration) */
int icm42670_uninit(void); int icm42670_uninit(void);
#endif /* !_APP_RAW_MAIN_H_ */ #endif /* !_APP_RAW_MAIN_H_ */
@@ -7,25 +7,25 @@
******************************************************************************/ ******************************************************************************/
/******************************************************************************* /*******************************************************************************
* [모듈 개요] ICM42670P IMU 센서 I2C 통신 인터페이스 * [Module overview] ICM42670P IMU sensor I2C communication interface
* *
* nRF52840의 TWI(I2C) 하드웨어를 사용하여 ICM42670P IMU 센서와 통신하는 * Low-level interface module for communicating with the ICM42670P IMU sensor
* 저수준 인터페이스 모듈이다. * via the nRF52840 TWI (I2C) hardware.
* *
* - I2C 슬레이브 주소: 0x68 (ICM42670P 기본 주소) * - I2C slave address: 0x68 (ICM42670P default)
* - I2C 핀 설정: SCL=P1.14, SDA=P1.15 (system_interface.h에서 정의) * - I2C pin config: SCL=P1.14, SDA=P1.15 (defined in system_interface.h)
* - TWI 인스턴스: NRFX_TWI_INSTANCE(0) 사용 * - TWI instance: NRFX_TWI_INSTANCE(0)
* - 통신 속도: 100kHz (NRF_TWI_FREQ_100K) * - Bus speed: 100kHz (NRF_TWI_FREQ_100K)
* *
* 주요 함수 흐름: * Main function flow:
* inv_io_hal_init() I2C 또는 SPI 초기화 (현재 I2C만 구현) * inv_io_hal_init() -> Initialize I2C or SPI (only I2C implemented)
* inv_io_hal_read_reg() → 레지스터 읽기 (TX로 주소 전송 → RX로 데이터 수신) * inv_io_hal_read_reg() -> Register read (TX address -> RX data)
* inv_io_hal_write_reg() → 레지스터 쓰기 (주소+데이터를 한번에 TX) * inv_io_hal_write_reg() -> Register write (TX address+data at once)
* *
* 에러 처리: 모든 I2C 읽기/쓰기에서 실패 시 1회 재시도 후 에러 출력 * Error handling: All I2C read/write operations retry once on failure
* *
* 참고: SPI4 인터페이스 코드 경로도 존재하지만 현재 미구현 상태이며, * Note: SPI4 code path exists but is not implemented;
* 실제 운용에서는 I2C(UI_I2C)만 사용한다. * only I2C (UI_I2C) is used in production.
******************************************************************************/ ******************************************************************************/
/* board driver */ /* board driver */
@@ -43,17 +43,17 @@
#include "system_interface.h" #include "system_interface.h"
#include "nrf_delay.h" #include "nrf_delay.h"
/* ICM42670P I2C 슬레이브 주소 및 직렬 쓰기 최대 바이트 수 */ /* ICM42670P I2C slave address and max serial write byte count */
#define ICM_I2C_ADDR 0x68 #define ICM_I2C_ADDR 0x68
#define INV_MAX_SERIAL_WRITE 16 #define INV_MAX_SERIAL_WRITE 16
/* TWI(I2C) 인스턴스 생성 — system_interface.h의 ICM42670_I2C_INSTANCE(0)을 사용 */ /* TWI (I2C) instance — uses ICM42670_I2C_INSTANCE(0) from system_interface.h */
const nrfx_twi_t m_twi_icm42670 = NRFX_TWI_INSTANCE(ICM42670_I2C_INSTANCE); const nrfx_twi_t m_twi_icm42670 = NRFX_TWI_INSTANCE(ICM42670_I2C_INSTANCE);
/* /*
* inv_i2c_master_uninitialize() * inv_i2c_master_uninitialize()
* I2C 버스를 비활성화하고 TWI 인스턴스를 해제한다. * Disables the I2C bus and releases the TWI instance.
* 슬립 모드 진입 전 또는 재초기화 전에 호출된다. * Called before entering sleep mode or before re-initialization.
*/ */
void inv_i2c_master_uninitialize(void){ void inv_i2c_master_uninitialize(void){
nrfx_twi_disable(&m_twi_icm42670); nrfx_twi_disable(&m_twi_icm42670);
@@ -62,11 +62,11 @@ void inv_i2c_master_uninitialize(void){
/* /*
* inv_i2c_master_initialize() * inv_i2c_master_initialize()
* nRF52840 TWI 하드웨어를 초기화하고 활성화한다. * Initializes and enables the nRF52840 TWI hardware.
* - SCL: P1.14, SDA: P1.15 * - SCL: P1.14, SDA: P1.15
* - 속도: 100kHz * - Speed: 100kHz
* - 인터럽트 우선순위: 최고 (APP_IRQ_PRIORITY_HIGH) * - Interrupt priority: highest (APP_IRQ_PRIORITY_HIGH)
* - 이벤트 핸들러 없음 (블로킹 모드로 동작) * - No event handler (blocking mode)
*/ */
void inv_i2c_master_initialize(void){ void inv_i2c_master_initialize(void){
ret_code_t err_code; ret_code_t err_code;
@@ -78,19 +78,19 @@ void inv_i2c_master_initialize(void){
.interrupt_priority = APP_IRQ_PRIORITY_HIGH, .interrupt_priority = APP_IRQ_PRIORITY_HIGH,
}; };
/* TWI 드라이버 초기화 (이벤트 핸들러=NULL → 블로킹 모드) */ /* Initialize TWI driver (event handler=NULL -> blocking mode) */
err_code = nrfx_twi_init(&m_twi_icm42670, &twi_icm42670_config, NULL, NULL); err_code = nrfx_twi_init(&m_twi_icm42670, &twi_icm42670_config, NULL, NULL);
APP_ERROR_CHECK(err_code); APP_ERROR_CHECK(err_code);
/* TWI 하드웨어 활성화 — 이후 tx/rx 가능 */ /* Enable TWI hardware — tx/rx available after this */
nrfx_twi_enable(&m_twi_icm42670); nrfx_twi_enable(&m_twi_icm42670);
} }
/* /*
* icm42670_twi_tx() * icm42670_twi_tx()
* I2C 전송 래퍼 함수. nrfx_twi_tx를 호출하여 데이터를 송신한다. * I2C transmit wrapper. Calls nrfx_twi_tx to send data.
* no_stop=true이면 STOP 컨디션을 보내지 않음 (Repeated START를 위해 사용) * If no_stop=true, STOP condition is omitted (used for Repeated START).
*/ */
uint32_t icm42670_twi_tx( uint8_t device_id, uint32_t icm42670_twi_tx( uint8_t device_id,
uint8_t const * p_data, uint8_t const * p_data,
@@ -105,7 +105,7 @@ uint32_t icm42670_twi_tx( uint8_t device_id,
/* /*
* icm42670_twi_rx() * icm42670_twi_rx()
* I2C 수신 래퍼 함수. nrfx_twi_rx를 호출하여 데이터를 수신한다. * I2C receive wrapper. Calls nrfx_twi_rx to receive data.
*/ */
uint32_t icm42670_twi_rx( uint8_t device_id, uint32_t icm42670_twi_rx( uint8_t device_id,
uint8_t * p_data, uint8_t * p_data,
@@ -119,33 +119,33 @@ uint32_t icm42670_twi_rx( uint8_t device_id,
/* /*
* inv_i2c_master_read_register() * inv_i2c_master_read_register()
* ICM42670P의 특정 레지스터에서 데이터를 읽는다. * Reads data from a specific ICM42670P register.
* *
* 동작 순서: * Sequence:
* 1) TX: 레지스터 주소 1바이트 전송 (no_stop=true Repeated START 준비) * 1) TX: Send 1-byte register address (no_stop=true -> prepare Repeated START)
* 2) RX: 지정된 길이만큼 데이터 수신 * 2) RX: Receive data of specified length
* *
* 에러 처리: TX, RX 각각 실패 시 1회 재시도한다. * Error handling: Retries once on TX or RX failure.
*/ */
static unsigned long inv_i2c_master_read_register(unsigned char Address, unsigned char RegisterAddr, unsigned short RegisterLen, unsigned char *RegisterValue){ static unsigned long inv_i2c_master_read_register(unsigned char Address, unsigned char RegisterAddr, unsigned short RegisterLen, unsigned char *RegisterValue){
//ret_code_t ret; //ret_code_t ret;
uint32_t ret; uint32_t ret;
uint8_t addr8 = (uint8_t)RegisterAddr; uint8_t addr8 = (uint8_t)RegisterAddr;
/* 1단계: 읽을 레지스터 주소를 전송 (STOP 없이 → Repeated START 사용) */ /* Step 1: Send register address to read (no STOP -> uses Repeated START) */
ret = icm42670_twi_tx(Address, &addr8, 1, true); ret = icm42670_twi_tx(Address, &addr8, 1, true);
if(ret != NRF_SUCCESS) { if(ret != NRF_SUCCESS) {
/* 실패 시 1회 재시도 */ /* Retry once on failure */
ret = icm42670_twi_tx(Address, &addr8, 1, true); ret = icm42670_twi_tx(Address, &addr8, 1, true);
if(ret != NRF_SUCCESS) { if(ret != NRF_SUCCESS) {
printf("ERR! i2c read-1\r\n"); printf("ERR! i2c read-1\r\n");
} }
} }
/* 2단계: 해당 레지스터에서 데이터 수신 */ /* Step 2: Receive data from the register */
ret = icm42670_twi_rx(Address, RegisterValue, RegisterLen); ret = icm42670_twi_rx(Address, RegisterValue, RegisterLen);
if(ret != NRF_SUCCESS) { if(ret != NRF_SUCCESS) {
/* 실패 시 1회 재시도 */ /* Retry once on failure */
ret = icm42670_twi_rx(Address, RegisterValue, RegisterLen); ret = icm42670_twi_rx(Address, RegisterValue, RegisterLen);
if(ret != NRF_SUCCESS) { if(ret != NRF_SUCCESS) {
printf("ERR! i2c read-2\r\n"); printf("ERR! i2c read-2\r\n");
@@ -157,26 +157,26 @@ static unsigned long inv_i2c_master_read_register(unsigned char Address, unsigne
/* /*
* inv_i2c_master_write_register() * inv_i2c_master_write_register()
* ICM42670P의 특정 레지스터에 데이터를 쓴다. * Writes data to a specific ICM42670P register.
* *
* 동작 순서: * Sequence:
* 1) 버퍼[0]에 레지스터 주소, 버퍼[1~N]에 쓸 데이터를 배치 * 1) Place register address in buffer[0], data in buffer[1..N]
* 2) TX: 주소+데이터를 한번에 전송 (no_stop=false → STOP 컨디션 포함) * 2) TX: Send address+data at once (no_stop=false -> includes STOP condition)
* *
* 에러 처리: 실패 시 1회 재시도한다. * Error handling: Retries once on failure.
*/ */
static unsigned long inv_i2c_master_write_register(unsigned char Address, unsigned char RegisterAddr, unsigned short RegisterLen, const unsigned char *RegisterValue){ static unsigned long inv_i2c_master_write_register(unsigned char Address, unsigned char RegisterAddr, unsigned short RegisterLen, const unsigned char *RegisterValue){
uint32_t ret; uint32_t ret;
uint8_t buffer[1 + INV_MAX_SERIAL_WRITE]; /* 레지스터 주소(1) + 데이터(최대 16바이트) */ uint8_t buffer[1 + INV_MAX_SERIAL_WRITE]; /* register address (1) + data (max 16 bytes) */
/* 버퍼 구성: [레지스터 주소][데이터 바이트들] */ /* Buffer layout: [register address][data bytes] */
buffer[0] = (uint8_t)RegisterAddr; buffer[0] = (uint8_t)RegisterAddr;
memcpy(buffer+1, RegisterValue, RegisterLen); memcpy(buffer+1, RegisterValue, RegisterLen);
/* 주소+데이터를 한번에 전송 */ /* Send address+data at once */
ret = icm42670_twi_tx(Address, buffer, RegisterLen+1, false); ret = icm42670_twi_tx(Address, buffer, RegisterLen+1, false);
if(ret != NRF_SUCCESS) { if(ret != NRF_SUCCESS) {
/* 실패 시 1회 재시도 */ /* Retry once on failure */
ret = icm42670_twi_tx(Address, buffer, RegisterLen+1, false); ret = icm42670_twi_tx(Address, buffer, RegisterLen+1, false);
if(ret != NRF_SUCCESS) { if(ret != NRF_SUCCESS) {
printf("ERR! i2c write\r\n"); printf("ERR! i2c write\r\n");
@@ -190,9 +190,9 @@ static unsigned long inv_i2c_master_write_register(unsigned char Address, unsign
/* /*
* inv_io_hal_init() * inv_io_hal_init()
* IMU 드라이버가 사용하는 시리얼 인터페이스(I2C 또는 SPI)를 초기화한다. * Initializes the serial interface (I2C or SPI) used by the IMU driver.
* serif->serif_type에 따라 분기하며, 현재는 I2C만 구현되어 있다. * Branches based on serif->serif_type; only I2C is currently implemented.
* 반환값: 0=성공, -1=지원하지 않는 인터페이스 타입 * Returns: 0=success, -1=unsupported interface type
*/ */
int inv_io_hal_init(struct inv_imu_serif *serif) int inv_io_hal_init(struct inv_imu_serif *serif)
@@ -201,7 +201,7 @@ int inv_io_hal_init(struct inv_imu_serif *serif)
switch (serif->serif_type) { switch (serif->serif_type) {
case UI_SPI4: case UI_SPI4:
{ {
/* SPI4 초기화 — 현재 미구현 (I2C만 사용) */ /* SPI4 init — not implemented (only I2C is used) */
break; break;
} }
@@ -219,8 +219,8 @@ int inv_io_hal_init(struct inv_imu_serif *serif)
/* /*
* inv_io_hal_read_reg() * inv_io_hal_read_reg()
* IMU 드라이버 콜백: 지정된 레지스터에서 데이터를 읽는다. * IMU driver callback: reads data from the specified register.
* 시리얼 타입에 따라 I2C 또는 SPI 읽기를 수행한다. * Performs I2C or SPI read depending on the serial type.
*/ */
int inv_io_hal_read_reg(struct inv_imu_serif *serif, uint8_t reg, uint8_t * rbuffer, uint32_t rlen) int inv_io_hal_read_reg(struct inv_imu_serif *serif, uint8_t reg, uint8_t * rbuffer, uint32_t rlen)
{ {
@@ -238,8 +238,8 @@ int inv_io_hal_read_reg(struct inv_imu_serif *serif, uint8_t reg, uint8_t * rbuf
/* /*
* inv_io_hal_write_reg() * inv_io_hal_write_reg()
* IMU 드라이버 콜백: 지정된 레지스터에 데이터를 쓴다. * IMU driver callback: writes data to the specified register.
* 시리얼 타입에 따라 I2C 또는 SPI 쓰기를 수행한다. * Performs I2C or SPI write depending on the serial type.
*/ */
int inv_io_hal_write_reg(struct inv_imu_serif *serif, uint8_t reg, const uint8_t * wbuffer, uint32_t wlen) int inv_io_hal_write_reg(struct inv_imu_serif *serif, uint8_t reg, const uint8_t * wbuffer, uint32_t wlen)
{ {
@@ -257,9 +257,9 @@ int inv_io_hal_write_reg(struct inv_imu_serif *serif, uint8_t reg, const uint8_t
/* /*
* cat_read() * cat_read()
* 범용 I2C 읽기 함수 (디버그/레거시용). * Generic I2C read function (debug/legacy).
* 8바이트를 읽어 첫 번째 바이트를 반환하고, 읽은 데이터를 콘솔에 출력한다. * Reads 8 bytes, returns the first byte, and prints the data to console.
* 참고: 현재 실제 운용에서는 사용되지 않으며, 디버그 목적으로 남아 있다. * Note: Not used in production; kept for debug purposes.
*/ */
uint8_t cat_read(uint8_t device_id, uint8_t address, uint8_t *data) uint8_t cat_read(uint8_t device_id, uint8_t address, uint8_t *data)
{ {
@@ -270,14 +270,14 @@ uint8_t cat_read(uint8_t device_id, uint8_t address, uint8_t *data)
//address = 1|(address<<1); //address = 1|(address<<1);
address = (address & 0xFF); address = (address & 0xFF);
/* 레지스터 주소 전송 (STOP 없이, Repeated START 준비) */ /* Send register address (no STOP, prepare Repeated START) */
err_code = nrfx_twi_tx(&m_twi_icm42670, device_id, &address, 1, true); err_code = nrfx_twi_tx(&m_twi_icm42670, device_id, &address, 1, true);
if (err_code != NRF_SUCCESS) { if (err_code != NRF_SUCCESS) {
// Handle error // Handle error
// return; // return;
} }
/* 8바이트 데이터 수신 */ /* Receive 8 bytes of data */
err_code = nrfx_twi_rx(&m_twi_icm42670, device_id, data, 8); err_code = nrfx_twi_rx(&m_twi_icm42670, device_id, data, 8);
if (err_code != NRF_SUCCESS) { if (err_code != NRF_SUCCESS) {
// Handle error // Handle error
@@ -298,9 +298,9 @@ uint8_t cat_read(uint8_t device_id, uint8_t address, uint8_t *data)
/* /*
* cat_write() * cat_write()
* 범용 I2C 쓰기 함수 (디버그/레거시용). * Generic I2C write function (debug/legacy).
* 주소 1바이트 + 데이터 1바이트를 전송한다. * Sends 1 byte address + 1 byte data.
* 참고: buffer에 6바이트를 복사하지만, 실제 전송은 2바이트만 수행한다. * Note: Copies 6 bytes into buffer, but only transmits 2 bytes.
*/ */
void cat_write(uint8_t device_id, uint8_t address, uint8_t *data){ void cat_write(uint8_t device_id, uint8_t address, uint8_t *data){
@@ -314,7 +314,7 @@ void cat_write(uint8_t device_id, uint8_t address, uint8_t *data){
ret_code_t err_code; ret_code_t err_code;
//err_code = nrf_drv_twi_tx(&m_twi_ir, device_id, 0x00, 1, false); //err_code = nrf_drv_twi_tx(&m_twi_ir, device_id, 0x00, 1, false);
/* 주소(1바이트) + 데이터(1바이트) = 2바이트 전송 */ /* Address (1 byte) + data (1 byte) = 2 bytes transmitted */
err_code = nrfx_twi_tx(&m_twi_icm42670, device_id, buffer, 2, false); err_code = nrfx_twi_tx(&m_twi_icm42670, device_id, buffer, 2, false);
// err_code = nrf_drv_twi_tx(&m_twi_ir, device_id, buffer, 2, false); // err_code = nrf_drv_twi_tx(&m_twi_ir, device_id, buffer, 2, false);
// nrfx_twi_rx(&m_twi_icm42670, device_id, p_data, length); // nrfx_twi_rx(&m_twi_icm42670, device_id, p_data, length);
@@ -7,17 +7,18 @@
******************************************************************************/ ******************************************************************************/
/******************************************************************************* /*******************************************************************************
* [헤더 개요] ICM42670P I2C 통신 인터페이스 선언 * [Header overview] ICM42670P I2C communication interface declarations
* *
* nRF52840 TWI 하드웨어를 통해 ICM42670P IMU 센서와 통신하기 위한 핀 정의, 함수 프로토타입 선언 * Pin definitions and function prototypes for communicating with the
* ICM42670P IMU sensor via nRF52840 TWI hardware.
* *
* 핀 배치: * Pin assignment:
* - I2C SCL : P1.14 * - I2C SCL : P1.14
* - I2C SDA : P1.15 * - I2C SDA : P1.15
* - INT1 : P1.13 (데이터 준비 인터럽트) * - INT1 : P1.13 (data-ready interrupt)
* - INT2 : P0.26 (보조 인터럽트, 현재 미사용) * - INT2 : P0.26 (auxiliary interrupt, currently unused)
* *
* TWI 인스턴스: 0번 사용 * TWI instance: 0
******************************************************************************/ ******************************************************************************/
#ifndef _SYSTEM_INTERFACE_H_ #ifndef _SYSTEM_INTERFACE_H_
@@ -31,42 +32,42 @@
#endif #endif
#define ICM42670_I2C_INSTANCE 0 /**< I2C(TWI) 인스턴스 인덱스 */ #define ICM42670_I2C_INSTANCE 0 /**< I2C (TWI) instance index */
#define ICM42670_I2C_SDA_PIN NRF_GPIO_PIN_MAP(1,15) /**< SDA : P1.15 */ #define ICM42670_I2C_SDA_PIN NRF_GPIO_PIN_MAP(1,15) /**< SDA pin: P1.15 */
#define ICM42670_I2C_SCL_PIN NRF_GPIO_PIN_MAP(1,14) /**< SCL : P1.14 */ #define ICM42670_I2C_SCL_PIN NRF_GPIO_PIN_MAP(1,14) /**< SCL pin: P1.14 */
#define ICM42670_INT1_PIN NRF_GPIO_PIN_MAP(1,13) /**< INT1 : P1.13 (데이터 준비 인터럽트) */ #define ICM42670_INT1_PIN NRF_GPIO_PIN_MAP(1,13) /**< INT1 pin: P1.13 (data-ready interrupt) */
#define ICM42670_INT2_PIN NRF_GPIO_PIN_MAP(0,26) /**< INT2 : P0.26 (보조, 현재 미사용) */ #define ICM42670_INT2_PIN NRF_GPIO_PIN_MAP(0,26) /**< INT2 pin: P0.26 (auxiliary, currently unused) */
/* I2C 전송 래퍼 — no_stop=true이면 Repeated START를 위해 STOP 컨디션 생략 */ /* I2C transmit wrapper — if no_stop=true, STOP condition is omitted for Repeated START */
uint32_t icm42670_twi_tx( uint8_t device_id, uint32_t icm42670_twi_tx( uint8_t device_id,
uint8_t const * p_data, uint8_t const * p_data,
uint8_t length, uint8_t length,
bool no_stop); bool no_stop);
/* I2C 수신 래퍼 */ /* I2C receive wrapper */
uint32_t icm42670_twi_rx( uint8_t device_id, uint32_t icm42670_twi_rx( uint8_t device_id,
uint8_t * p_data, uint8_t * p_data,
uint8_t length); uint8_t length);
/* 범용 I2C 읽기 (디버그/레거시용) — 8바이트를 읽어 첫 바이트 반환 */ /* Generic I2C read (debug/legacy) — reads 8 bytes and returns the first byte */
uint8_t cat_read (uint8_t device_id, uint8_t address, uint8_t *data); uint8_t cat_read (uint8_t device_id, uint8_t address, uint8_t *data);
/* 범용 I2C 쓰기 (디버그/레거시용) — 주소+데이터 2바이트 전송 */ /* Generic I2C write (debug/legacy) — sends address+data 2 bytes */
void cat_write (uint8_t device_id, uint8_t address, uint8_t *data); void cat_write (uint8_t device_id, uint8_t address, uint8_t *data);
/* I2C 하드웨어 해제 (슬립 또는 재초기화 전 호출) */ /* Release I2C hardware (called before sleep or re-initialization) */
void inv_i2c_master_uninitialize(void); void inv_i2c_master_uninitialize(void);
/* I2C 하드웨어 초기화 (100kHz, 블로킹 모드) */ /* Initialize I2C hardware (100kHz, blocking mode) */
void inv_i2c_master_initialize(void); void inv_i2c_master_initialize(void);
/* IMU 드라이버용 시리얼 인터페이스 초기화 (I2C/SPI 분기) */ /* Initialize serial interface for IMU driver (I2C/SPI branch) */
int inv_io_hal_init(struct inv_imu_serif *serif); int inv_io_hal_init(struct inv_imu_serif *serif);
/* IMU 드라이버 콜백: 레지스터 읽기 */ /* IMU driver callback: register read */
int inv_io_hal_read_reg(struct inv_imu_serif *serif, uint8_t reg, uint8_t * rbuffer, uint32_t rlen); int inv_io_hal_read_reg(struct inv_imu_serif *serif, uint8_t reg, uint8_t * rbuffer, uint32_t rlen);
/* IMU 드라이버 콜백: 레지스터 쓰기 */ /* IMU driver callback: register write */
int inv_io_hal_write_reg(struct inv_imu_serif *serif, uint8_t reg, const uint8_t * wbuffer, uint32_t wlen); int inv_io_hal_write_reg(struct inv_imu_serif *serif, uint8_t reg, const uint8_t * wbuffer, uint32_t wlen);
#endif /* !_SYSTEM_INTERFACE_H_ */ #endif /* !_SYSTEM_INTERFACE_H_ */
@@ -6,16 +6,16 @@
* *
* @details Uses Timer2 + GPIOTE + PPI for CPU-free 2MHz waveform generation * @details Uses Timer2 + GPIOTE + PPI for CPU-free 2MHz waveform generation
* *
* Timing Diagram (???): * Timing Diagram (TX sequence):
* *
* |<----------- PE HIGH ----------->| * |<----------- PE HIGH ----------->|
* PE ___/?????????????????????????????????\___ * PE ___/-------------------------------------\___
* P_OUT ___/?\_/?\_/?\_/?\_/?\________________\___ * P_OUT ___/-\_/-\_/-\_/-\_/-\________________\___
* N_OUT ___\_/?\_/?\_/?\_/?\_/________________\___ * N_OUT ___\_/-\_/-\_/-\_/-\_/________________\___
* DMP _________________________/?????\_________ * DMP _________________________/-----\_________
* |<-- 3~5 cycles -->| |<DMP>| * |<-- 3~5 cycles -->| |<DMP>|
* *
* P_OUT? N_OUT? ?? ?? ?? (???) * P_OUT and N_OUT toggle in anti-phase (complementary)
* *
* 2MHz = 500ns period = 250ns half-period * 2MHz = 500ns period = 250ns half-period
* Timer @ 16MHz: 1 tick = 62.5ns * Timer @ 16MHz: 1 tick = 62.5ns
@@ -23,68 +23,47 @@
* Full-period = 500ns = 8 ticks * Full-period = 500ns = 8 ticks
******************************************************************************/ ******************************************************************************/
/******************************************************************************* /*==============================================================================
* [한국어 설명] 피에조 초음파 트랜스듀서 드라이버 * Module overview — Piezo ultrasound transducer driver
* *
* === 개요 === * Generates 2 MHz ultrasound TX signals for bladder measurement using
* 방광 측정용 2MHz 초음파 송신 신호를 생성하는 드라이버. * nRF52840 hardware peripherals (Timer2 + GPIOTE + PPI) for CPU-free
* nRF52840의 하드웨어 주변장치(Timer2 + GPIOTE + PPI)를 활용하여 * precise waveform generation.
* CPU 개입 없이 정밀한 2MHz 파형을 자동으로 생성한다.
* *
* === 초음파 TX 시퀀스 === * TX sequence:
* 1단계: PE(Pulse Enable) = HIGH -> MOSFET 드라이버 활성화 * 1. PE = HIGH -> enable MOSFET driver
* 2단계: P_OUT/N_OUT 교번 펄스 생성 (2MHz, 3~7 사이클) * 2. P_OUT / N_OUT alternating pulses (2 MHz, 3..7 cycles)
* - P_OUT N_OUT은 역상(반대 위상)으로 동작 * P_OUT and N_OUT are anti-phase, applying +/-20V across the piezo
* - 피에조 소자 양단에 +/-20V 교번 전압 인가 * 3. DMP = HIGH -> dump residual piezo energy
* 3단계: DMP(Dump) = HIGH -> 펄스 완료 후 피에조에 남은 잔류 에너지 방전 * 4. DMP = LOW -> discharge complete
* 4단계: DMP = LOW -> 방전 완료 * 5. PE = LOW -> return to idle
* 5단계: PE = LOW -> MOSFET 드라이버 비활성화, 유휴 상태 복귀
* *
* === 하드웨어 아키텍처 === * HW architecture:
* [Timer2] --- CC[0](반주기=4틱) ---> [PPI CH8,9] ---> [GPIOTE CH4,5] -> P_OUT/N_OUT 토글 * Timer2 CC[0] (half-period = 4 ticks) -> PPI CH8,9 -> GPIOTE CH4,5 toggle
* |-- CC[1](전체주기=8틱) --> [PPI CH10,11] --> [GPIOTE CH4,5] -> P_OUT/N_OUT 토글 * Timer2 CC[1] (full-period = 8 ticks) -> PPI CH10,11 -> GPIOTE CH4,5 toggle
* |-- CC[2](전체주기=8틱) --> 타이머 자동 클리어 + 인터럽트(잔여 사이클 카운트) * Timer2 CC[2] (full-period = 8 ticks) -> auto-clear + IRQ (cycle counter)
* *
* - Timer2: 16MHz 클럭, 16비트 모드 * Power: DR_PIEZO_PWR_EN (P1.9) -> DC/DC -> +/-20V
* - CC[0] = 4틱(250ns) -> 반주기 시점에서 P_OUT/N_OUT 토글
* - CC[1] = 8틱(500ns) -> 전체주기 시점에서 P_OUT/N_OUT 토글
* - CC[2] = 8틱(500ns) -> 인터럽트 발생 + 타이머 자동 클리어(SHORT)
* *
* - GPIOTE CH4: P_OUT 핀 토글 모드 * SW burst mode (dr_piezo_burst_sw_* family):
* - GPIOTE CH5: N_OUT 핀 토글 모드 * Direct GPIO via port register (NRF_P1->OUT), interrupts disabled,
* * NOP-based timing. Per-frequency NOP count differs.
* - PPI CH8: CC[0] 이벤트 -> P_OUT 토글 태스크 * NOP timing: CPU 64 MHz -> 1 NOP = 15.625 ns
* - PPI CH9: CC[0] 이벤트 -> N_OUT 토글 태스크 * half_period_ns = 1 000 000 / (freq_MHz * 2)
* - PPI CH10: CC[1] 이벤트 -> P_OUT 토글 태스크 * NOP_count = (half_period - register_write ~30 ns) / 15.625
* - PPI CH11: CC[1] 이벤트 -> N_OUT 토글 태스크 * loop overhead (~47 ns = 3 NOP) subtracted from second half-period
* *============================================================================*/
* === 전원 ===
* DR_PIEZO_PWR_EN(P1.9) -> DC/DC 컨버터 활성화 -> +/-20V 고전압 생성
*
* === 소프트웨어 버스트 모드 (dr_piezo_burst_sw 계열) ===
* Timer/PPI 대신 CPU에서 직접 GPIO를 제어하는 방식.
* 인터럽트를 비활성화(__disable_irq)하고 NOP 명령어로 정밀 타이밍 생성.
* 포트 레지스터(NRF_P1->OUT)에 직접 접근하여 여러 핀을 동시에 제어.
* 주파수별(1.7/1.8/1.9/2.0/2.1/2.2 MHz) NOP 개수가 다름.
*
* NOP 타이밍 계산법:
* CPU 클럭 64MHz -> 1 NOP = 15.625ns
* 목표 반주기(ns) = 1,000,000 / (목표주파수MHz * 2)
* 필요 NOP 수 = (목표 반주기 - 레지스터 쓰기 시간(~30ns)) / 15.625
* 루프 오버헤드(~47ns = 3 NOP)는 두 번째 반주기에서 차감
******************************************************************************/
/* 헤더 포함 */
#include "dr_piezo.h" #include "dr_piezo.h"
#include "nrf_gpio.h" /* GPIO 제어 (핀 설정, 출력) */ #include "nrf_gpio.h"
#include "nrf_timer.h" /* 타이머 주변장치 제어 */ #include "nrf_timer.h"
#include "nrf_gpiote.h" /* GPIOTE (GPIO Tasks and Events) 제어 */ #include "nrf_gpiote.h"
#include "nrf_ppi.h" /* PPI (Programmable Peripheral Interconnect) 제어 */ #include "nrf_ppi.h"
#include "nrf_delay.h" /* 지연(딜레이) 함수 */ #include "nrf_delay.h"
#include "power_control.h" /* 전원 관리 */ #include "power_control.h"
#include "app_util_platform.h" /* 인터럽트 우선순위 등 플랫폼 유틸리티 */ #include "app_util_platform.h"
/* 조건부 디버그 출력: FEATURE_PRINTF 정의 시 SEGGER RTT로 출력 */ /* Debug output: enabled when FEATURE_PRINTF is defined */
#ifdef FEATURE_PRINTF #ifdef FEATURE_PRINTF
#include "debug_print.h" #include "debug_print.h"
#else #else
@@ -92,76 +71,72 @@
#endif #endif
/*============================================================================== /*==============================================================================
* 하드웨어 리소스 할당 * Hardware resource allocation
* - Timer2: 2MHz 파형 생성의 시간 기준 (16MHz 클럭) * Timer2: 16 MHz clock, time base for 2 MHz waveform
* - GPIOTE CH4/5: P_OUT/N_OUT 핀의 하드웨어 토글 * GPIOTE CH4/5: P_OUT / N_OUT hardware toggle
* - PPI CH8~11: 타이머 비교 이벤트 -> GPIOTE 토글 태스크 자동 연결 * PPI CH8..11: timer compare events -> GPIOTE toggle tasks
*============================================================================*/ *============================================================================*/
#define PIEZO_TIMER NRF_TIMER2 /* 사용할 타이머 인스턴스 */ #define PIEZO_TIMER NRF_TIMER2
#define PIEZO_TIMER_IRQn TIMER2_IRQn /* 타이머2 인터럽트 번호 */ #define PIEZO_TIMER_IRQn TIMER2_IRQn
#define PIEZO_TIMER_IRQ_PRIORITY 6 /* 인터럽트 우선순위 (6 = 중간) */ #define PIEZO_TIMER_IRQ_PRIORITY 6
/* GPIOTE 채널 할당 - 핀 토글 제어용 */ #define GPIOTE_CH_P_OUT 4 /* P_OUT toggle */
#define GPIOTE_CH_P_OUT 4 /* P_OUT(양극 출력) 토글용 GPIOTE 채널 */ #define GPIOTE_CH_N_OUT 5 /* N_OUT toggle */
#define GPIOTE_CH_N_OUT 5 /* N_OUT(음극 출력) 토글용 GPIOTE 채널 */
/* PPI 채널 할당 - 타이머 이벤트 -> GPIOTE 태스크 연결 */ #define PPI_CH_P_OUT_TOGGLE_0 8 /* CC[0] (half-period) -> P_OUT */
#define PPI_CH_P_OUT_TOGGLE_0 8 /* CC[0](반주기) -> P_OUT 토글 */ #define PPI_CH_N_OUT_TOGGLE_0 9 /* CC[0] (half-period) -> N_OUT */
#define PPI_CH_N_OUT_TOGGLE_0 9 /* CC[0](반주기) -> N_OUT 토글 */ #define PPI_CH_P_OUT_TOGGLE_1 10 /* CC[1] (full-period) -> P_OUT */
#define PPI_CH_P_OUT_TOGGLE_1 10 /* CC[1](전체주기) -> P_OUT 토글 */ #define PPI_CH_N_OUT_TOGGLE_1 11 /* CC[1] (full-period) -> N_OUT */
#define PPI_CH_N_OUT_TOGGLE_1 11 /* CC[1](전체주기) -> N_OUT 토글 */
/*============================================================================== /*==============================================================================
* 타이밍 상수 * Timing constants
* Timer2 클럭: 16MHz -> 1틱 = 62.5ns * Timer2 @ 16 MHz: 1 tick = 62.5 ns
* 2MHz 신호: 주기 500ns(8틱), 반주기 250ns(4) * 2 MHz signal: period 500 ns (8 ticks), half-period 250 ns (4 ticks)
*============================================================================*/ *============================================================================*/
#define TIMER_FREQ_MHZ 16 /* 타이머 클럭 주파수 (MHz) */ #define TIMER_FREQ_MHZ 16
#define TICK_NS (1000 / TIMER_FREQ_MHZ) /* 1틱 = 62.5ns */ #define TICK_NS (1000 / TIMER_FREQ_MHZ) /* 62.5 ns */
/* 2MHz 기준 타이밍: 주기 = 500ns, 반주기 = 250ns */ /* 2 MHz reference timing: period = 500 ns, half-period = 250 ns */
#define PERIOD_TICKS_2MHZ 8 /* 전체 주기: 500ns / 62.5ns = 8 */ #define PERIOD_TICKS_2MHZ 8 /* full period: 500 ns / 62.5 ns = 8 ticks */
#define HALF_PERIOD_TICKS 4 /* 반주기: 250ns / 62.5ns = 4 */ #define HALF_PERIOD_TICKS 4 /* half period: 250 ns / 62.5 ns = 4 ticks */
/*============================================================================== /*==============================================================================
* 피에조 동작 주파수 설정 * SW burst port register bitmasks
*============================================================================*/
/*
* 목표 피에조 주파수: 2.1 MHz (하드웨어 버스트 모드용)
* *
* 소프트웨어 버스트 모드의 타이밍은 각 주파수별 함수에 NOP 개수로 하드코딩됨. * Pre-calculated from pin definitions in dr_piezo.h.
* 안정적인 파형 생성을 위해 컴파일 타임에 고정. * Used for direct writes to NRF_P1->OUT, enabling simultaneous multi-pin control.
* * PE is on P0 port, controlled separately via OUTSET/OUTCLR.
* NOP 기반 타이밍 계산 (CPU 64MHz, 1 NOP = 15.625ns):
* 1.7 MHz: 반주기 294ns -> 첫 반주기 18 NOP, 둘째 반주기 10 NOP + 루프 오버헤드
* 1.8 MHz: 반주기 278ns -> 첫 반주기 17 NOP, 둘째 반주기 11 NOP + 루프 오버헤드
* 1.9 MHz: 반주기 263ns -> 첫 반주기 15 NOP, 둘째 반주기 9 NOP + 루프 오버헤드
* 2.0 MHz: 반주기 250ns -> 첫 반주기 15 NOP, 둘째 반주기 10 NOP + 루프 오버헤드
* 2.1 MHz: 반주기 238ns -> 첫 반주기 14 NOP, 둘째 반주기 9 NOP + 루프 오버헤드
* 2.2 MHz: 반주기 227ns -> 첫 반주기 13 NOP, 둘째 반주기 8 NOP + 루프 오버헤드
*
* 주파수를 변경하려면 dr_piezo_burst_sw_XXmhz() 함수의 NOP 수를 수정할 것.
*/
#define PIEZO_FREQ_MHZ 2.1f /* 기본 동작 주파수 (MHz) */
/*==============================================================================
* 정적 변수
*============================================================================*/ *============================================================================*/
static volatile bool m_tx_active = false; /* TX 송신 중 플래그 (인터럽트에서 변경) */ /* Extract bit position within port from pin number (lower 5 bits = 0~31) */
static volatile uint8_t m_remaining_cycles = 0; /* 남은 펄스 사이클 수 (인터럽트에서 감소) */ #define PIN_NUM(pin) ((pin) & 0x1F)
static uint32_t m_period_ticks = PERIOD_TICKS_2MHZ; /* 현재 주기 (타이머 틱 단위) */
static bool m_power_enabled = false; /* DC/DC 컨버터 전원 상태 */ #define P_OUT_MASK (1UL << PIN_NUM(DR_PIEZO_PIN_P_OUT)) /* P1.07 positive output */
static bool m_initialized = false; /* 드라이버 초기화 완료 여부 */ #define N_OUT_MASK (1UL << PIN_NUM(DR_PIEZO_PIN_N_OUT)) /* P1.06 negative output */
#define PE_MASK (1UL << PIN_NUM(DR_PIEZO_PIN_PE)) /* P0.25 pulse enable */
#define DMP_MASK (1UL << PIN_NUM(DR_PIEZO_PIN_DMP)) /* P1.00 dump control */
/* Combined mask of piezo control pins on P1 port (excludes channel select pins) */
#define P1_CTRL_MASK (P_OUT_MASK | N_OUT_MASK | DMP_MASK)
/*============================================================================== /*==============================================================================
* 타이머2 인터럽트 핸들러 * Static variables
* 매 주기(CC[2])마다 호출되어 잔여 사이클을 감소시킨다. *============================================================================*/
* 잔여 사이클이 0이 되면: static volatile bool m_tx_active = false; /* TX in progress (modified in IRQ) */
* 1) 타이머 정지 및 클리어 static volatile uint8_t m_remaining_cycles = 0; /* remaining pulse cycles (decremented in IRQ) */
* 2) GPIOTE 비활성화 (P_OUT/N_OUT 토글 중단) static uint32_t m_period_ticks = PERIOD_TICKS_2MHZ; /* current period (timer ticks) */
* 3) GPIO를 출력 모드로 재설정 후 LOW로 초기화 static bool m_power_enabled = false; /* DC/DC converter state */
* 4) DMP 펄스 발생 (피에조 잔류 에너지 방전) static bool m_initialized = false; /* driver initialised flag */
* 5) PE = LOW (MOSFET 드라이버 비활성화)
/*==============================================================================
* Timer2 IRQ handler
*
* Called every period (CC[2]) to decrement the remaining cycle count.
* When zero:
* 1) Stop and clear timer
* 2) Disable GPIOTE (stop P_OUT/N_OUT toggling)
* 3) Reconfigure GPIOs as push-pull output, drive LOW
* 4) Generate DMP pulse (discharge residual piezo energy)
* 5) PE = LOW (disable MOSFET driver)
*============================================================================*/ *============================================================================*/
void TIMER2_IRQHandler(void) void TIMER2_IRQHandler(void)
{ {
@@ -180,11 +155,11 @@ void TIMER2_IRQHandler(void)
nrf_timer_task_trigger(PIEZO_TIMER, NRF_TIMER_TASK_STOP); nrf_timer_task_trigger(PIEZO_TIMER, NRF_TIMER_TASK_STOP);
nrf_timer_task_trigger(PIEZO_TIMER, NRF_TIMER_TASK_CLEAR); nrf_timer_task_trigger(PIEZO_TIMER, NRF_TIMER_TASK_CLEAR);
/* Step 2: Disable GPIOTE (GPIO ??? ??) */ /* Step 2: Disable GPIOTE (return GPIO to SW control) */
nrf_gpiote_task_disable(GPIOTE_CH_P_OUT); nrf_gpiote_task_disable(GPIOTE_CH_P_OUT);
nrf_gpiote_task_disable(GPIOTE_CH_N_OUT); nrf_gpiote_task_disable(GPIOTE_CH_N_OUT);
/* Step 3: GPIO? ?? ? idle ?? */ /* Step 3: Reconfigure GPIOs and set idle state */
nrf_gpio_cfg_output(DR_PIEZO_PIN_P_OUT); nrf_gpio_cfg_output(DR_PIEZO_PIN_P_OUT);
nrf_gpio_cfg_output(DR_PIEZO_PIN_N_OUT); nrf_gpio_cfg_output(DR_PIEZO_PIN_N_OUT);
nrf_gpio_pin_clear(DR_PIEZO_PIN_P_OUT); nrf_gpio_pin_clear(DR_PIEZO_PIN_P_OUT);
@@ -207,12 +182,10 @@ void TIMER2_IRQHandler(void)
} }
/*============================================================================== /*==============================================================================
* 전원 제어 함수 * Power control
* DC/DC 컨버터(±20V)를 ON/OFF하여 피에조 구동 전압을 제어한다. * Switches the DC/DC converter (+/-20V) for piezo driving voltage.
* 전원 안정화에 약 10ms 필요. * ~10 ms needed for power stabilisation.
*============================================================================*/ *============================================================================*/
/* 피에조 전원 ON: DC/DC 컨버터 활성화 → ±20V 생성 */
void dr_piezo_power_on(void) void dr_piezo_power_on(void)
{ {
//nrf_delay_ms(20); //nrf_delay_ms(20);
@@ -221,19 +194,19 @@ void dr_piezo_power_on(void)
nrf_gpio_pin_set(DR_PIEZO_PWR_EN); nrf_gpio_pin_set(DR_PIEZO_PWR_EN);
/* Wait for power stabilization */ /* Wait for power stabilization */
nrf_delay_ms(3); nrf_delay_ms(10);
m_power_enabled = true; m_power_enabled = true;
//DBG_PRINTF("[PIEZO] TX/RX Active: +/-20V ready\r\n"); //DBG_PRINTF("[PIEZO] TX/RX Active: +/-20V ready\r\n");
} }
/* 피에조 전원 OFF: TX 비활성화 → MUX 비활성화 → DC/DC 컨버터 차단 */ /* Power OFF: disable TX -> disable MUX -> shut down DC/DC */
void dr_piezo_power_off(void) void dr_piezo_power_off(void)
{ {
dr_piezo_disable(); dr_piezo_disable();
/* MUX enable 핀 클리어: select_channel() 이후 HIGH로 남은 핀 해제 */ /* clear MUX enable pins left HIGH by select_channel() */
nrf_gpio_pin_clear(DR_PIEZO_EN_MUXA); nrf_gpio_pin_clear(DR_PIEZO_EN_MUXA);
nrf_gpio_pin_clear(DR_PIEZO_EN_MUXB); nrf_gpio_pin_clear(DR_PIEZO_EN_MUXB);
nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL0);
@@ -246,17 +219,17 @@ void dr_piezo_power_off(void)
//DBG_PRINTF("[PIEZO] Power OFF\r\n"); //DBG_PRINTF("[PIEZO] Power OFF\r\n");
} }
/* 피에조 전원 상태 확인 */ /* Check piezo power state */
bool dr_piezo_is_power_on(void) bool dr_piezo_is_power_on(void)
{ {
return m_power_enabled; return m_power_enabled;
} }
/*============================================================================== /*==============================================================================
* 내부(private) 초기화 함수 * Internal (private) initialisation
*============================================================================*/ *============================================================================*/
/* GPIO 초기화: 모든 신호 핀을 출력 모드로 설정하고 LOW(유휴)로 초기화 */ /* Configure all signal pins as push-pull output, drive LOW (idle) */
static void dr_piezo_gpio_init(void) static void dr_piezo_gpio_init(void)
{ {
nrf_gpio_cfg_output(DR_PIEZO_PIN_PE); nrf_gpio_cfg_output(DR_PIEZO_PIN_PE);
@@ -274,7 +247,7 @@ static void dr_piezo_gpio_init(void)
dr_piezo_mux_init(); dr_piezo_mux_init();
} }
/* GPIOTE 초기화: P_OUT/N_OUT을 토글 모드로 설정 (PPI 연결 대상) */ /* GPIOTE init: configure P_OUT/N_OUT in toggle mode (PPI targets) */
static void dr_piezo_gpiote_init(void) static void dr_piezo_gpiote_init(void)
{ {
/* P_OUT: Toggle mode, initial LOW */ /* P_OUT: Toggle mode, initial LOW */
@@ -297,10 +270,10 @@ static void dr_piezo_gpiote_init(void)
} }
/* /*
* 타이머 초기화: 16MHz 클럭, 16비트 모드 * Timer init: 16 MHz clock, 16-bit mode
* CC[0]=반주기(4틱): 반주기 시점 토글 이벤트 * CC[0] = half-period (4 ticks): toggle event at half-period
* CC[1]=전체주기(8틱): 전체주기 시점 토글 이벤트 * CC[1] = full-period (8 ticks): toggle event at full-period
* CC[2]=전체주기(8틱): 인터럽트 발생 + 타이머 자동 클리어(SHORT) * CC[2] = full-period (8 ticks): IRQ + auto-clear (SHORT)
*/ */
static void dr_piezo_timer_init(void) static void dr_piezo_timer_init(void)
{ {
@@ -312,31 +285,31 @@ static void dr_piezo_timer_init(void)
nrf_timer_frequency_set(PIEZO_TIMER, NRF_TIMER_FREQ_16MHz); nrf_timer_frequency_set(PIEZO_TIMER, NRF_TIMER_FREQ_16MHz);
/* /*
* ??? ??? ?? ???: * Compare channel assignment:
* - CC[0] = 4 (half period): P_OUT ??, N_OUT ?? * - CC[0] = 4 (half period): toggle P_OUT, toggle N_OUT
* - CC[1] = 8 (full period): P_OUT ??, N_OUT ?? * - CC[1] = 8 (full period): toggle P_OUT, toggle N_OUT
* - CC[2] = 8 (full period): ??? ??? + CLEAR * - CC[2] = 8 (full period): cycle count IRQ + CLEAR
* *
* ?? ??: P=HIGH, N=LOW * Initial state: P=HIGH, N=LOW
* t=4: ? ? ?? -> P=LOW, N=HIGH * t=4: 1st toggle -> P=LOW, N=HIGH
* t=8: ? ? ?? -> P=HIGH, N=LOW (+ CLEAR) * t=8: 2nd toggle -> P=HIGH, N=LOW (+ CLEAR)
* t=12: ? ? ?? -> P=LOW, N=HIGH * t=12: 1st toggle -> P=LOW, N=HIGH
* ... * ...
*/ */
/* CC[0]: ??? ?? */ /* CC[0]: half-period toggle */
nrf_timer_cc_write(PIEZO_TIMER, NRF_TIMER_CC_CHANNEL0, HALF_PERIOD_TICKS); // 4 nrf_timer_cc_write(PIEZO_TIMER, NRF_TIMER_CC_CHANNEL0, HALF_PERIOD_TICKS); // 4
/* CC[1]: ? ?? ?? */ /* CC[1]: full-period toggle */
nrf_timer_cc_write(PIEZO_TIMER, NRF_TIMER_CC_CHANNEL1, m_period_ticks); // 8 nrf_timer_cc_write(PIEZO_TIMER, NRF_TIMER_CC_CHANNEL1, m_period_ticks); // 8
/* CC[2]: ? ?? - ??? ???? */ /* CC[2]: full-period - cycle count IRQ source */
nrf_timer_cc_write(PIEZO_TIMER, NRF_TIMER_CC_CHANNEL2, m_period_ticks); // 8 nrf_timer_cc_write(PIEZO_TIMER, NRF_TIMER_CC_CHANNEL2, m_period_ticks); // 8
/* CC[2]?? ?? CLEAR */ /* Auto-CLEAR on CC[2] match */
nrf_timer_shorts_enable(PIEZO_TIMER, NRF_TIMER_SHORT_COMPARE2_CLEAR_MASK); nrf_timer_shorts_enable(PIEZO_TIMER, NRF_TIMER_SHORT_COMPARE2_CLEAR_MASK);
/* CC[2] ???? (??? ???) */ /* CC[2] interrupt enable (cycle counter) */
nrf_timer_int_enable(PIEZO_TIMER, NRF_TIMER_INT_COMPARE2_MASK); nrf_timer_int_enable(PIEZO_TIMER, NRF_TIMER_INT_COMPARE2_MASK);
NVIC_SetPriority(PIEZO_TIMER_IRQn, PIEZO_TIMER_IRQ_PRIORITY); NVIC_SetPriority(PIEZO_TIMER_IRQn, PIEZO_TIMER_IRQ_PRIORITY);
@@ -344,17 +317,17 @@ static void dr_piezo_timer_init(void)
} }
/* /*
* PPI 초기화: 타이머 비교 이벤트 → GPIOTE 토글 태스크 연결 (4채널) * PPI init: connect timer compare events to GPIOTE toggle tasks (4 channels)
* CC[0] 이벤트(반주기) → P_OUT 토글 + N_OUT 토글 * CC[0] event (half-period) -> P_OUT toggle + N_OUT toggle
* CC[1] 이벤트(전체주기) → P_OUT 토글 + N_OUT 토글 * CC[1] event (full-period) -> P_OUT toggle + N_OUT toggle
* 이로써 P_OUT N_OUT은 항상 역상으로 동작함. * This ensures P_OUT and N_OUT always operate in anti-phase.
*/ */
static void dr_piezo_ppi_init(void) static void dr_piezo_ppi_init(void)
{ {
/* /*
* ??? ??: * Connection map:
* CC[0] (t=4): P_OUT ??, N_OUT ?? * CC[0] (t=4): P_OUT toggle, N_OUT toggle
* CC[1] (t=8): P_OUT ??, N_OUT ?? * CC[1] (t=8): P_OUT toggle, N_OUT toggle
*/ */
/* CC[0] -> P_OUT toggle */ /* CC[0] -> P_OUT toggle */
@@ -391,10 +364,10 @@ static void dr_piezo_ppi_init(void)
} }
/*============================================================================== /*==============================================================================
* TX 드라이버 공개 함수 * TX driver public functions
*============================================================================*/ *============================================================================*/
/* TX 드라이버 초기화: GPIO GPIOTE Timer PPI 순서로 설정 */ /* TX driver init: configure GPIO -> GPIOTE -> Timer -> PPI in order */
void dr_piezo_init(void) void dr_piezo_init(void)
{ {
dr_piezo_gpio_init(); dr_piezo_gpio_init();
@@ -407,7 +380,7 @@ void dr_piezo_init(void)
m_initialized = true; m_initialized = true;
} }
/* TX 드라이버 해제: 타이머 정지, PPI/GPIOTE 비활성화, 모든 핀 LOW */ /* TX driver deinit: stop timer, disable PPI/GPIOTE, all pins LOW */
void dr_piezo_uninit(void) void dr_piezo_uninit(void)
{ {
nrf_timer_task_trigger(PIEZO_TIMER, NRF_TIMER_TASK_STOP); nrf_timer_task_trigger(PIEZO_TIMER, NRF_TIMER_TASK_STOP);
@@ -432,11 +405,11 @@ void dr_piezo_uninit(void)
} }
/* /*
* 하드웨어 기반 버스트 송신 (Timer + PPI + GPIOTE 사용) * Hardware-based burst transmit (using Timer + PPI + GPIOTE)
* 1) GPIOTE를 재설정: P_OUT=HIGH 시작, N_OUT=LOW 시작 (역상) * 1) Reconfigure GPIOTE: P_OUT starts HIGH, N_OUT starts LOW (anti-phase)
* 2) PE = HIGH → MOSFET 드라이버 활성화 * 2) PE = HIGH -> enable MOSFET driver
* 3) 타이머 시작 → PPI가 자동으로 P_OUT/N_OUT 토글 * 3) Start timer -> PPI automatically toggles P_OUT/N_OUT
* 4) 인터럽트 핸들러에서 잔여 사이클 관리 및 종료 처리 * 4) IRQ handler manages remaining cycles and shutdown sequence
*/ */
void dr_piezo_burst(uint8_t cycles) void dr_piezo_burst(uint8_t cycles)
{ {
@@ -460,7 +433,7 @@ void dr_piezo_burst(uint8_t cycles)
m_remaining_cycles = cycles; m_remaining_cycles = cycles;
m_tx_active = true; m_tx_active = true;
/* GPIOTE ??? */ /* Reconfigure GPIOTE */
nrf_gpiote_task_disable(GPIOTE_CH_P_OUT); nrf_gpiote_task_disable(GPIOTE_CH_P_OUT);
nrf_gpiote_task_disable(GPIOTE_CH_N_OUT); nrf_gpiote_task_disable(GPIOTE_CH_N_OUT);
@@ -491,23 +464,23 @@ void dr_piezo_burst(uint8_t cycles)
nrf_timer_event_clear(PIEZO_TIMER, NRF_TIMER_EVENT_COMPARE1); nrf_timer_event_clear(PIEZO_TIMER, NRF_TIMER_EVENT_COMPARE1);
nrf_timer_event_clear(PIEZO_TIMER, NRF_TIMER_EVENT_COMPARE2); nrf_timer_event_clear(PIEZO_TIMER, NRF_TIMER_EVENT_COMPARE2);
/* PE = HIGH (??? ??) */ /* PE = HIGH (enable driver) */
nrf_gpio_pin_set(DR_PIEZO_PIN_PE); nrf_gpio_pin_set(DR_PIEZO_PIN_PE);
/* ??? ?? ? ??? ?? */ /* Settling delay before starting timer */
__NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP();
/* Timer START -> PPI? ???? P_OUT/N_OUT ?? */ /* Timer START -> PPI automatically toggles P_OUT/N_OUT */
nrf_timer_task_trigger(PIEZO_TIMER, NRF_TIMER_TASK_START); nrf_timer_task_trigger(PIEZO_TIMER, NRF_TIMER_TASK_START);
} }
/* 기본 사이클 수(5)로 버스트 송신 */ /* Burst transmit with default cycle count (5) */
void dr_piezo_pulse(void) void dr_piezo_pulse(void)
{ {
dr_piezo_burst(DR_PIEZO_DEFAULT_CYCLES); dr_piezo_burst(DR_PIEZO_DEFAULT_CYCLES);
} }
/* TX 출력 활성화: 수동으로 초기 상태 설정 (PE=HIGH, P_OUT=HIGH, N_OUT=LOW) */ /* Enable TX output: manually set initial state (PE=HIGH, P_OUT=HIGH, N_OUT=LOW) */
void dr_piezo_enable(void) void dr_piezo_enable(void)
{ {
nrf_gpio_pin_set(DR_PIEZO_PIN_P_OUT); nrf_gpio_pin_set(DR_PIEZO_PIN_P_OUT);
@@ -517,7 +490,7 @@ void dr_piezo_enable(void)
DBG_PRINTF("[DR_PIEZO] TX enabled\r\n"); DBG_PRINTF("[DR_PIEZO] TX enabled\r\n");
} }
/* TX 출력 비활성화: 모든 신호 핀 LOW로 복귀 (유휴 상태) */ /* Disable TX output: all signal pins return to LOW (idle) */
void dr_piezo_disable(void) void dr_piezo_disable(void)
{ {
nrf_gpio_pin_clear(DR_PIEZO_PIN_DMP); nrf_gpio_pin_clear(DR_PIEZO_PIN_DMP);
@@ -526,13 +499,13 @@ void dr_piezo_disable(void)
nrf_gpio_pin_clear(DR_PIEZO_PIN_N_OUT); nrf_gpio_pin_clear(DR_PIEZO_PIN_N_OUT);
} }
/* TX 송신 중 여부 확인 (인터럽트 핸들러에서 false로 전환) */ /* Check if TX is in progress (cleared to false in IRQ handler) */
bool dr_piezo_is_busy(void) bool dr_piezo_is_busy(void)
{ {
return m_tx_active; return m_tx_active;
} }
/* 동작 주파수 변경 (100kHz~4MHz): 타이머 CC 레지스터 재설정 */ /* Change operating frequency (100kHz~4MHz): reconfigure timer CC registers */
void dr_piezo_set_frequency(uint32_t freq_hz) void dr_piezo_set_frequency(uint32_t freq_hz)
{ {
if (freq_hz < 100000 || freq_hz > 4000000) if (freq_hz < 100000 || freq_hz > 4000000)
@@ -552,14 +525,14 @@ void dr_piezo_set_frequency(uint32_t freq_hz)
} }
/*============================================================================== /*==============================================================================
* MUX 제어 함수 * MUX control functions
* 8채널 아날로그 MUX로 피에조 에코 신호 경로를 선택한다. * 8-channel analog MUX selects piezo echo signal path.
* MUXA: CH0~CH3 담당, MUXB: CH4~CH7 담당 * MUXA: CH0~CH3, MUXB: CH4~CH7
* SEL0, SEL1: MUX 내부 채널 주소 선택 * SEL0, SEL1: MUX internal channel address selection
* High Drive(H0H1) 모드: MUX IC의 빠른 스위칭을 위해 강한 출력 구동력 사용 * High Drive (H0H1) mode: strong output drive for fast MUX IC switching
*============================================================================*/ *============================================================================*/
/* MUX 제어 핀 초기화: 4개 핀 모두 High Drive 출력으로 설정, 기본값 LOW */ /* MUX control pin init: all 4 pins as High Drive output, default LOW */
void dr_piezo_mux_init(void) void dr_piezo_mux_init(void)
{ {
/* Configure pins as output with high drive strength */ /* Configure pins as output with high drive strength */
@@ -571,6 +544,7 @@ void dr_piezo_mux_init(void)
NRF_GPIO_PIN_H0H1, /* High drive */ NRF_GPIO_PIN_H0H1, /* High drive */
NRF_GPIO_PIN_NOSENSE NRF_GPIO_PIN_NOSENSE
); );
nrf_gpio_cfg( nrf_gpio_cfg(
DR_PIEZO_MUX_SEL1, DR_PIEZO_MUX_SEL1,
NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_DIR_OUTPUT,
@@ -580,7 +554,7 @@ void dr_piezo_mux_init(void)
NRF_GPIO_PIN_NOSENSE NRF_GPIO_PIN_NOSENSE
); );
/* GPIO PIN Setting jhChun 0129 */ /* GPIO PIN Setting */
nrf_gpio_cfg( nrf_gpio_cfg(
DR_PIEZO_EN_MUXA, // PIN DR_PIEZO_EN_MUXA, // PIN
NRF_GPIO_PIN_DIR_OUTPUT, // DIR : OUTPUT NRF_GPIO_PIN_DIR_OUTPUT, // DIR : OUTPUT
@@ -607,59 +581,59 @@ void dr_piezo_mux_init(void)
/* /*
* 피에조 채널 선택 (0~7) * Select piezo channel (0~7)
* 채널 매핑 (EN_MUXA, EN_MUXB, SEL0, SEL1): * Channel mapping (EN_MUXA, EN_MUXB, SEL0, SEL1):
* CH0 = MUXA 입력0 (1,0,0,0) CH4 = MUXB 입력0 (0,1,1,1) * CH0 = MUXA input0 (1,0,0,0) CH4 = MUXB input0 (0,1,1,1)
* CH1 = MUXA 입력2 (1,0,1,0) CH5 = MUXB 입력1 (0,1,0,1) * CH1 = MUXA input2 (1,0,1,0) CH5 = MUXB input1 (0,1,0,1)
* CH2 = MUXA 입력1 (1,0,0,1) CH6 = MUXB 입력2 (0,1,1,0) * CH2 = MUXA input1 (1,0,0,1) CH6 = MUXB input2 (0,1,1,0)
* CH3 = MUXA 입력3 (1,0,1,1) CH7 = MUXB 입력3 (0,1,0,0) * CH3 = MUXA input3 (1,0,1,1) CH7 = MUXB input3 (0,1,0,0)
* 채널 전환 후 MUX 안정화 대기 시간(1.3ms) 필요. * MUX settling time (~1.3ms) required after channel switch.
*/ */
void dr_piezo_select_channel(uint8_t channel) void dr_piezo_select_channel(uint8_t channel)
{ {
channel = channel & 0x07; /* 0~7 범위로 마스킹 */ channel = channel & 0x07; /* Mask to 0~7 range */
switch (channel) { switch (channel)
// EN_A EN_B SEL0 SEL1 {
case 0: // A0: 1 0 0 0 case 0: // A0: EN_MUXA=1, EN_MUXB=0, SEL0=0, SEL1=0
nrf_gpio_pin_clear(DR_PIEZO_EN_MUXB); nrf_gpio_pin_set(DR_PIEZO_EN_MUXA); nrf_gpio_pin_clear(DR_PIEZO_EN_MUXB); nrf_gpio_pin_set(DR_PIEZO_EN_MUXA);
nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL1); nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL1);
break; break;
case 1: // A2: 1 0 1 0 case 1: // A1: EN_MUXA=1, EN_MUXB=0, SEL0=1, SEL1=0
nrf_gpio_pin_clear(DR_PIEZO_EN_MUXB); nrf_gpio_pin_set(DR_PIEZO_EN_MUXA); nrf_gpio_pin_clear(DR_PIEZO_EN_MUXB); nrf_gpio_pin_set(DR_PIEZO_EN_MUXA);
nrf_gpio_pin_set(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL1); nrf_gpio_pin_set(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL1);
break; break;
case 2: // A1: 1 0 0 1 case 2: // A2: EN_MUXA=1, EN_MUXB=0, SEL0=0, SEL1=1
nrf_gpio_pin_clear(DR_PIEZO_EN_MUXB); nrf_gpio_pin_set(DR_PIEZO_EN_MUXA); nrf_gpio_pin_clear(DR_PIEZO_EN_MUXB); nrf_gpio_pin_set(DR_PIEZO_EN_MUXA);
nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_set(DR_PIEZO_MUX_SEL1); nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_set(DR_PIEZO_MUX_SEL1);
break; break;
case 3: // A3: 1 0 1 1 case 3: // A3: EN_MUXA=1, EN_MUXB=0, SEL0=1, SEL1=1
nrf_gpio_pin_clear(DR_PIEZO_EN_MUXB); nrf_gpio_pin_set(DR_PIEZO_EN_MUXA); nrf_gpio_pin_clear(DR_PIEZO_EN_MUXB); nrf_gpio_pin_set(DR_PIEZO_EN_MUXA);
nrf_gpio_pin_set(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_set(DR_PIEZO_MUX_SEL1); nrf_gpio_pin_set(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_set(DR_PIEZO_MUX_SEL1);
break; break;
case 4: // B0: 0 1 1 1 /*case 4: // B0: EN_MUXA=0, EN_MUXB=1, SEL0=1, SEL1=1
nrf_gpio_pin_clear(DR_PIEZO_EN_MUXA); nrf_gpio_pin_set(DR_PIEZO_EN_MUXB); nrf_gpio_pin_clear(DR_PIEZO_EN_MUXA); nrf_gpio_pin_set(DR_PIEZO_EN_MUXB);
nrf_gpio_pin_set(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_set(DR_PIEZO_MUX_SEL1); nrf_gpio_pin_set(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_set(DR_PIEZO_MUX_SEL1);
break; break;
case 5: // B1: 0 1 1 0 case 5: // B1: EN_MUXA=0, EN_MUXB=1, SEL0=0, SEL1=1
nrf_gpio_pin_clear(DR_PIEZO_EN_MUXA); nrf_gpio_pin_set(DR_PIEZO_EN_MUXB); nrf_gpio_pin_clear(DR_PIEZO_EN_MUXA); nrf_gpio_pin_set(DR_PIEZO_EN_MUXB);
nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_set(DR_PIEZO_MUX_SEL1); nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_set(DR_PIEZO_MUX_SEL1);
break; break;*/
case 6: // B2: 0 1 0 1 case 5: // B2: EN_MUXA=0, EN_MUXB=1, SEL0=1, SEL1=0
nrf_gpio_pin_clear(DR_PIEZO_EN_MUXA); nrf_gpio_pin_set(DR_PIEZO_EN_MUXB); nrf_gpio_pin_clear(DR_PIEZO_EN_MUXA); nrf_gpio_pin_set(DR_PIEZO_EN_MUXB);
nrf_gpio_pin_set(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL1); nrf_gpio_pin_set(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL1);
break; break;
case 7: // B3: 0 1 0 0 case 4: // B3: EN_MUXA=0, EN_MUXB=1, SEL0=0, SEL1=0
nrf_gpio_pin_clear(DR_PIEZO_EN_MUXA); nrf_gpio_pin_set(DR_PIEZO_EN_MUXB); nrf_gpio_pin_clear(DR_PIEZO_EN_MUXA); nrf_gpio_pin_set(DR_PIEZO_EN_MUXB);
nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL1); nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL0); nrf_gpio_pin_clear(DR_PIEZO_MUX_SEL1);
break; break;
} }
/* 채널 변경 시 MUX 안정화 시간 필요 (> 1.2ms) */ /* MUX settling time required after channel change (> 1.2ms) */
nrf_delay_us(DR_PIEZO_MUX_SETTLING_US); nrf_delay_us(DR_PIEZO_MUX_SETTLING_US);
} }
/* 핀 테스트: 각 신호 핀을 순서대로 HIGH/LOW 토글 (오실로스코프 확인용) */ /* Pin test: toggle each signal pin HIGH/LOW in sequence (for oscilloscope verification) */
void dr_piezo_test_pins(void) void dr_piezo_test_pins(void)
{ {
DBG_PRINTF("[DR_PIEZO] Pin test...\r\n"); DBG_PRINTF("[DR_PIEZO] Pin test...\r\n");
@@ -700,24 +674,24 @@ void dr_piezo_test_pins(void)
} }
/*============================================================================== /*==============================================================================
* 시스템 함수 (전원 + TX 드라이버 통합 제어) * System functions (power + TX driver unified control)
*============================================================================*/ *============================================================================*/
/* 시스템 전체 초기화: TX 드라이버 초기화 → 전원 ON (부팅 시 1회만 호출) */ /* Full system init: TX driver init -> power ON (call once at boot) */
void dr_piezo_system_init(void) void dr_piezo_system_init(void)
{ {
dr_piezo_init(); /* GPIO/GPIOTE/Timer/PPI 먼저 안전 상태로 설정 */ dr_piezo_init(); /* Set GPIO/GPIOTE/Timer/PPI to safe state first */
dr_piezo_power_on(); /* 그 다음 DC/DC 전원 인가 */ dr_piezo_power_on(); /* Then apply DC/DC power */
} }
/* 시스템 전체 종료: TX 드라이버 해제 → 전원 OFF */ /* Full system shutdown: deinit TX driver -> power OFF */
void dr_piezo_system_uninit(void) void dr_piezo_system_uninit(void)
{ {
dr_piezo_uninit(); dr_piezo_uninit();
dr_piezo_power_off(); dr_piezo_power_off();
} }
/* 전원 확인 후 버스트 송신 (블로킹: 송신 완료까지 대기) */ /* Burst transmit with power check (blocking: waits until TX complete) */
void dr_piezo_transmit(uint8_t cycles) void dr_piezo_transmit(uint8_t cycles)
{ {
if (!m_power_enabled) if (!m_power_enabled)
@@ -732,73 +706,51 @@ void dr_piezo_transmit(uint8_t cycles)
} }
/*============================================================================== /*==============================================================================
* 소프트웨어 기반 버스트 모드 * Software-based burst mode
* 2025-12-11 Charles KWON * 2025-12-11 Charles KWON
*============================================================================== *==============================================================================
* *
* Timer/PPI/GPIOTE 하드웨어 대신 CPU에서 직접 GPIO 레지스터를 조작하여 * Generates ultrasound pulses by directly manipulating GPIO registers from
* 초음파 펄스를 생성하는 방식. 인터럽트를 비활성화하여 정확한 타이밍 보장. * the CPU, instead of using Timer/PPI/GPIOTE hardware. Interrupts are
* disabled to guarantee precise timing.
* *
* === 타이밍 다이어그램 === * === Timing diagram ===
* *
* |<-마진->|<----- 펄스들 ----->|<-- DMP -->|<-마진->| * |<-margin->|<---- pulses ----->|<-- DMP -->|<-margin->|
* *
* PE ___/--------------------------------------------------\___ * PE ___/--------------------------------------------------\___
* P_OUT ___________/-\_/-\_/-\_/-\_/-\____________________\_______ * P_OUT ___________/-\_/-\_/-\_/-\_/-\____________________\_______
* N_OUT ___________\_/-\_/-\_/-\_/-\_/____________________\_______ * N_OUT ___________\_/-\_/-\_/-\_/-\_/____________________\_______
* DMP __________________________________/----------\____________ * DMP __________________________________/----------\____________
* *
* === 신호 설명 === * === Signal description ===
* - PE (Pulse Enable): 전체 시퀀스를 감싸는 활성화 신호 (전후 마진 포함) * - PE (Pulse Enable): activation signal wrapping the entire sequence (with margins)
* - P_OUT: 양극 출력, N_OUT과 역상으로 2MHz 토글 * - P_OUT: positive output, toggles at 2MHz in anti-phase with N_OUT
* - N_OUT: 음극 출력, P_OUT과 역상으로 2MHz 토글 * - N_OUT: negative output, toggles at 2MHz in anti-phase with P_OUT
* - DMP (Dump): 펄스 완료 후 피에조 잔류 에너지 방전 * - DMP (Dump): discharges residual piezo energy after pulse completion
* *
* === 동작 원리 === * === Operating principle ===
* 1) __disable_irq()로 인터럽트 차단 → 타이밍 흔들림 방지 * 1) __disable_irq() blocks interrupts -> prevents timing jitter
* 2) PE ON (P0.25 OUTSET 레지스터 사용 → 다른 P0 핀 영향 없음) * 2) PE ON (via P0.25 OUTSET register -> no effect on other P0 pins)
* 3) NOP 마진 후 for 루프로 P_OUT/N_OUT 교번 출력 * 3) NOP margin, then for-loop alternates P_OUT/N_OUT
* 4) DMP 펄스 (32 NOP 500ns) * 4) DMP pulse (32 NOP ~ 500ns)
* 5) PE OFF __enable_irq()로 인터럽트 복원 * 5) PE OFF, then __enable_irq() restores interrupts
* *
* === 포트 레지스터 직접 접근 === * === Direct port register access ===
* NRF_P1->OUT 레지스터에 미리 계산된 비트 마스크를 직접 기록. * Pre-calculated bitmasks are written directly to NRF_P1->OUT register.
* 이 방식으로 P_OUT, N_OUT, DMP를 동시에 제어하면서도 * This allows simultaneous control of P_OUT, N_OUT, DMP while preserving
* 채널 선택 핀(MUX SEL)은 보존한다 (P1_CTRL_MASK로 제어 핀만 변경). * channel select pins (MUX SEL) via P1_CTRL_MASK (only control pins change).
* PE는 P0 포트에 있으므로 OUTSET/OUTCLR 레지스터로 별도 제어. * PE is on P0 port, so it is controlled separately via OUTSET/OUTCLR registers.
*============================================================================*/ *============================================================================*/
/* 핀 번호에서 포트 내 비트 위치 추출 (하위 5비트 = 0~31) */
#define PIN_NUM(pin) ((pin) & 0x1F)
/* P1 포트 핀의 비트 마스크 - dr_piezo.h의 핀 정의에서 자동 생성
*
* 경고: 핀 번호를 절대 하드코딩하지 말 것!
* 하드코딩은 개발자의 시간을 일시적으로 절약해줄 수 있지만,
* 동시에 개발자의 수명을 단축시킬 것이다.
* - Charles KWON
*
* 각 마스크는 해당 핀의 포트 레지스터 내 비트 위치를 나타낸다.
* NRF_P1->OUT에 직접 쓸 때 사용되며, 여러 핀을 동시에 제어 가능.
*/
#define P_OUT_MASK (1UL << PIN_NUM(DR_PIEZO_PIN_P_OUT)) /* P1.07 양극 출력 */
#define N_OUT_MASK (1UL << PIN_NUM(DR_PIEZO_PIN_N_OUT)) /* P1.06 음극 출력 */
#define PE_MASK (1UL << PIN_NUM(DR_PIEZO_PIN_PE)) /* P0.25 펄스 활성화 */
#define DMP_MASK (1UL << PIN_NUM(DR_PIEZO_PIN_DMP)) /* P1.00 방전 제어 */
/* P1 포트에서 피에조 제어에 사용하는 핀들의 결합 마스크 (채널 선택 핀 제외) */
#define P1_CTRL_MASK (P_OUT_MASK | N_OUT_MASK | DMP_MASK)
/* /*
* 소프트웨어 버스트 - 기본 주파수 2.1MHz * Software burst - default frequency 2.1MHz
* *
* NOP 타이밍 계산: * NOP timing calculation:
* 2.1MHz → 주기 476ns, 반주기 238ns * 2.1MHz -> period 476ns, half-period 238ns
* CPU 64MHz 1 NOP = 15.625ns * CPU 64MHz -> 1 NOP = 15.625ns
* 첫 반주기: 14 NOP(≒219ns) + 레지스터 쓰기(≒30ns) = 249ns * 1st half: 14 NOP (~219ns) + register write (~30ns) = ~249ns
* 둘째 반주기: 9 NOP(≒141ns) + 루프 오버헤드(≒47ns) + 레지스터 쓰기(≒30ns) = 218ns * 2nd half: 9 NOP (~141ns) + loop overhead (~47ns) + register write (~30ns) = ~218ns
* 합계: 436~476ns (2.1MHz) * Total: ~436-476ns (~2.1MHz)
*/ */
void dr_piezo_burst_sw(uint8_t cycles) void dr_piezo_burst_sw(uint8_t cycles)
{ {
@@ -839,7 +791,7 @@ void dr_piezo_burst_sw(uint8_t cycles)
* IMPORTANT: Channel select pins (P1.11, P1.12) are preserved in all states * IMPORTANT: Channel select pins (P1.11, P1.12) are preserved in all states
*------------------------------------------------------------------------*/ *------------------------------------------------------------------------*/
// PE OUTSET/OUTCLR 사용 (다른 P0 핀 영향 방지) // PE uses OUTSET/OUTCLR (prevents affecting other P0 pins)
uint32_t p1_all_low = saved_p1_out & ~P1_CTRL_MASK; uint32_t p1_all_low = saved_p1_out & ~P1_CTRL_MASK;
uint32_t p1_P_high_N_low = p1_all_low | P_OUT_MASK; uint32_t p1_P_high_N_low = p1_all_low | P_OUT_MASK;
@@ -852,11 +804,11 @@ void dr_piezo_burst_sw(uint8_t cycles)
__disable_irq(); __disable_irq();
/* Initialize: Set all signals to LOW */ /* Initialize: Set all signals to LOW */
NRF_P0->OUTCLR = PE_MASK; // PE OFF (OUTCLR로 다른 핀 영향 없음) NRF_P0->OUTCLR = PE_MASK; // PE OFF (OUTCLR does not affect other pins)
NRF_P1->OUT = p1_all_low; NRF_P1->OUT = p1_all_low;
/* PE rises first with margin before pulses start */ /* PE rises first with margin before pulses start */
NRF_P0->OUTSET = PE_MASK; // PE ON (OUTSET로 다른 핀 영향 없음) NRF_P0->OUTSET = PE_MASK; // PE ON (OUTSET does not affect other pins)
__NOP(); __NOP(); __NOP(); /* ~47ns margin */ __NOP(); __NOP(); __NOP(); /* ~47ns margin */
__NOP(); __NOP(); __NOP(); /* ~47ns margin */ __NOP(); __NOP(); __NOP(); /* ~47ns margin */
@@ -947,11 +899,11 @@ void dr_piezo_burst_sw(uint8_t cycles)
* Second half: 12 NOPs (~188ns) + loop overhead * Second half: 12 NOPs (~188ns) + loop overhead
*/ */
/* /*
* 소프트웨어 버스트 - 1.8MHz * Software burst - 1.8MHz
* NOP 타이밍: 반주기 278ns * NOP timing: half-period 278ns
* 첫 반주기: 17 NOP(≒266ns) + 레지스터 쓰기(≒30ns) = 296ns * 1st half: 17 NOP (~266ns) + register write (~30ns) = ~296ns
* 둘째 반주기: 11 NOP(≒172ns) + 루프 오버헤드(≒47ns) + 레지스터 쓰기(≒30ns) = 249ns * 2nd half: 11 NOP (~172ns) + loop overhead (~47ns) + register write (~30ns) = ~249ns
* 합계: 499~556ns (1.8MHz) * Total: ~499-556ns (~1.8MHz)
*/ */
void dr_piezo_burst_sw_18mhz(uint8_t cycles) void dr_piezo_burst_sw_18mhz(uint8_t cycles)
{ {
@@ -979,7 +931,7 @@ void dr_piezo_burst_sw_18mhz(uint8_t cycles)
//NRF_P0->OUT = saved_p0_out; //NRF_P0->OUT = saved_p0_out;
/* Pre-calculate P1 output states (PE P0.25 - OUTSET/OUTCLR로 제어) */ /* Pre-calculate P1 output states (PE on P0.25 - controlled via OUTSET/OUTCLR) */
uint32_t p1_all_low = saved_p1_out & ~P1_CTRL_MASK; uint32_t p1_all_low = saved_p1_out & ~P1_CTRL_MASK;
uint32_t p1_P_high_N_low = p1_all_low | P_OUT_MASK; uint32_t p1_P_high_N_low = p1_all_low | P_OUT_MASK;
uint32_t p1_P_low_N_high = p1_all_low | N_OUT_MASK; uint32_t p1_P_low_N_high = p1_all_low | N_OUT_MASK;
@@ -1076,11 +1028,11 @@ void dr_piezo_burst_sw_18mhz(uint8_t cycles)
* Total: ~468-500ns per cycle * Total: ~468-500ns per cycle
*/ */
/* /*
* 소프트웨어 버스트 - 2.0MHz * Software burst - 2.0MHz
* NOP 타이밍: 반주기 250ns * NOP timing: half-period 250ns
* 첫 반주기: 15 NOP(≒234ns) + 레지스터 쓰기(≒30ns) = 264ns * 1st half: 15 NOP (~234ns) + register write (~30ns) = ~264ns
* 둘째 반주기: 10 NOP(≒156ns) + 루프 오버헤드(≒47ns) + 레지스터 쓰기(≒30ns) = 233ns * 2nd half: 10 NOP (~156ns) + loop overhead (~47ns) + register write (~30ns) = ~233ns
* 합계: 468~500ns (2.0MHz) * Total: ~468-500ns (~2.0MHz)
*/ */
void dr_piezo_burst_sw_20mhz(uint8_t cycles) void dr_piezo_burst_sw_20mhz(uint8_t cycles)
{ {
@@ -1107,7 +1059,7 @@ void dr_piezo_burst_sw_20mhz(uint8_t cycles)
//NRF_P0->OUT = saved_p0_out; //NRF_P0->OUT = saved_p0_out;
NRF_P0->OUT = (NRF_P0->OUT & ~PE_MASK) | (saved_p0_out & PE_MASK); NRF_P0->OUT = (NRF_P0->OUT & ~PE_MASK) | (saved_p0_out & PE_MASK);
/* Pre-calculate P1 output states (PE P0.25 - OUTSET/OUTCLR로 제어) */ /* Pre-calculate P1 output states (PE on P0.25 - controlled via OUTSET/OUTCLR) */
uint32_t p1_all_low = saved_p1_out & ~P1_CTRL_MASK; uint32_t p1_all_low = saved_p1_out & ~P1_CTRL_MASK;
uint32_t p1_P_high_N_low = p1_all_low | P_OUT_MASK; uint32_t p1_P_high_N_low = p1_all_low | P_OUT_MASK;
uint32_t p1_P_low_N_high = p1_all_low | N_OUT_MASK; uint32_t p1_P_low_N_high = p1_all_low | N_OUT_MASK;
@@ -1206,11 +1158,11 @@ void dr_piezo_burst_sw_20mhz(uint8_t cycles)
* Total: ~468-500ns per cycle * Total: ~468-500ns per cycle
*/ */
/* /*
* 소프트웨어 버스트 - 1.9MHz * Software burst - 1.9MHz
* NOP 타이밍: 반주기 263ns * NOP timing: half-period 263ns
* 첫 반주기: 15 NOP(≒234ns) + 레지스터 쓰기(≒30ns) = 264ns * 1st half: 15 NOP (~234ns) + register write (~30ns) = ~264ns
* 둘째 반주기: 9 NOP(≒141ns) + 루프 오버헤드(≒47ns) + 레지스터 쓰기(≒30ns) = 218ns * 2nd half: 9 NOP (~141ns) + loop overhead (~47ns) + register write (~30ns) = ~218ns
* 합계: 468~500ns (1.9MHz) * Total: ~468-500ns (~1.9MHz)
*/ */
void dr_piezo_burst_sw_19mhz(uint8_t cycles) void dr_piezo_burst_sw_19mhz(uint8_t cycles)
{ {
@@ -1237,7 +1189,7 @@ void dr_piezo_burst_sw_19mhz(uint8_t cycles)
//NRF_P0->OUT = saved_p0_out; //NRF_P0->OUT = saved_p0_out;
NRF_P0->OUT = (NRF_P0->OUT & ~PE_MASK) | (saved_p0_out & PE_MASK); NRF_P0->OUT = (NRF_P0->OUT & ~PE_MASK) | (saved_p0_out & PE_MASK);
/* Pre-calculate P1 output states (PE P0.25 - OUTSET/OUTCLR로 제어) */ /* Pre-calculate P1 output states (PE on P0.25 - controlled via OUTSET/OUTCLR) */
uint32_t p1_all_low = saved_p1_out & ~P1_CTRL_MASK; uint32_t p1_all_low = saved_p1_out & ~P1_CTRL_MASK;
uint32_t p1_P_high_N_low = p1_all_low | P_OUT_MASK; uint32_t p1_P_high_N_low = p1_all_low | P_OUT_MASK;
uint32_t p1_P_low_N_high = p1_all_low | N_OUT_MASK; uint32_t p1_P_low_N_high = p1_all_low | N_OUT_MASK;
@@ -1338,11 +1290,11 @@ void dr_piezo_burst_sw_19mhz(uint8_t cycles)
* Total: ~452ns per cycle (~2.21 MHz) * Total: ~452ns per cycle (~2.21 MHz)
*/ */
/* /*
* 소프트웨어 버스트 - 2.2MHz * Software burst - 2.2MHz
* NOP 타이밍: 반주기 227ns * NOP timing: half-period 227ns
* 첫 반주기: 13 NOP(≒203ns) + 레지스터 쓰기(≒30ns) = 233ns * 1st half: 13 NOP (~203ns) + register write (~30ns) = ~233ns
* 둘째 반주기: 8 NOP(≒125ns) + 루프 오버헤드(≒47ns) + 레지스터 쓰기(≒30ns) = 202ns * 2nd half: 8 NOP (~125ns) + loop overhead (~47ns) + register write (~30ns) = ~202ns
* 합계: 435~454ns (2.2MHz) * Total: ~435-454ns (~2.2MHz)
*/ */
void dr_piezo_burst_sw_22mhz(uint8_t cycles) void dr_piezo_burst_sw_22mhz(uint8_t cycles)
{ {
@@ -1464,11 +1416,11 @@ void dr_piezo_burst_sw_22mhz(uint8_t cycles)
* Total: ~532-588ns per cycle * Total: ~532-588ns per cycle
*/ */
/* /*
* 소프트웨어 버스트 - 1.7MHz * Software burst - 1.7MHz
* NOP 타이밍: 반주기 294ns * NOP timing: half-period 294ns
* 첫 반주기: 18 NOP(≒281ns) + 레지스터 쓰기(≒30ns) = 311ns * 1st half: 18 NOP (~281ns) + register write (~30ns) = ~311ns
* 둘째 반주기: 10 NOP(≒156ns) + 루프 오버헤드(≒47ns) + 레지스터 쓰기(≒30ns) = 233ns * 2nd half: 10 NOP (~156ns) + loop overhead (~47ns) + register write (~30ns) = ~233ns
* 합계: 532~588ns (1.7MHz) * Total: ~532-588ns (~1.7MHz)
*/ */
void dr_piezo_burst_sw_17mhz(uint8_t cycles) void dr_piezo_burst_sw_17mhz(uint8_t cycles)
{ {
@@ -1494,7 +1446,7 @@ void dr_piezo_burst_sw_17mhz(uint8_t cycles)
NRF_P1->OUT = saved_p1_out; NRF_P1->OUT = saved_p1_out;
NRF_P0->OUT = (NRF_P0->OUT & ~PE_MASK) | (saved_p0_out & PE_MASK); NRF_P0->OUT = (NRF_P0->OUT & ~PE_MASK) | (saved_p0_out & PE_MASK);
/* Pre-calculate P1 output states (PE는 P0에서 OUTSET/OUTCLR로 제어) */ /* Pre-calculate P1 output states (PE on P0 - controlled via OUTSET/OUTCLR) */
uint32_t p1_all_low = saved_p1_out & ~P1_CTRL_MASK; uint32_t p1_all_low = saved_p1_out & ~P1_CTRL_MASK;
uint32_t p1_P_high_N_low = p1_all_low | P_OUT_MASK; uint32_t p1_P_high_N_low = p1_all_low | P_OUT_MASK;
uint32_t p1_P_low_N_high = p1_all_low | N_OUT_MASK; uint32_t p1_P_low_N_high = p1_all_low | N_OUT_MASK;
@@ -1,61 +1,30 @@
/******************************************************************************* /*==============================================================================
* @file dr_piezo.h * dr_piezo.h - Piezo Transducer Driver (2 MHz Signal Generator)
* @brief Piezo Transducer Driver (2MHz Signal Generator)
* @author Charles KWON
* @date 2025-12-09
* *
* @note Hardware: nRF52840 + MD1822K6-G MOSFET Driver + TC7920K6-G MOSFET * Hardware: nRF52840 + MD1822K6-G MOSFET Driver + TC7920K6-G MOSFET
* Output: +/-20V at 2MHz, 3~5 cycles * Output: +/-20V at 2 MHz, 3..7 cycles burst
* *
* @details Timing Sequence: * Timing Sequence:
* 1. PE = HIGH (enable) * 1. PE = HIGH (enable)
* 2. P_OUT/N_OUT = 2MHz pulses (3~5 cycles) * 2. P_OUT/N_OUT = 2 MHz pulses (3..7 cycles)
* 3. DMP = HIGH (dump) * 3. DMP = HIGH (dump residual energy)
* 4. DMP = LOW * 4. DMP = LOW
* 5. PE = LOW (disable) * 5. PE = LOW (disable)
* *
* All signals (P_OUT, N_OUT, DMP) operate within PE HIGH period. * Pin assignment:
******************************************************************************/ * Power: DR_PIEZO_PWR_EN (P1.9) — DC/DC +/-20V enable
* TX: PE (P0.25), DMP (P1.0), P_OUT (P1.7), N_OUT (P1.6)
/******************************************************************************* * MUX: EN_MUXA (P0.21), EN_MUXB (P0.23), SEL0 (P1.10), SEL1 (P0.28)
* [한국어 설명] 피에조 초음파 트랜스듀서 드라이버 헤더
* *
* === 개요 === * MUX channel mapping (8ch):
* 방광 측정용 초음파 송신기의 핀 할당, 설정값, 함수 선언을 정의. * CH0=A0(1,0,0,0) CH1=A2(1,0,1,0) CH2=A1(1,0,0,1) CH3=A3(1,0,1,1)
* nRF52840 + MD1822K6-G(MOSFET 드라이버) + TC7920K6-G(MOSFET) 하드웨어 구성. * CH4=B0(0,1,1,1) CH5=B1(0,1,0,1) CH6=B2(0,1,1,0) CH7=B3(0,1,0,0)
* 출력: +/-20V, 2MHz, 3~7 사이클 버스트.
* *
* === 핀 할당 === * Two burst modes:
* 전원 제어: * 1) HW burst (dr_piezo_burst): Timer2 + PPI + GPIOTE, CPU-independent
* - DR_PIEZO_PWR_EN (P1.9): DC/DC 컨버터 활성화 -> +/-20V 고전압 생성 * 2) SW burst (dr_piezo_burst_sw_XXmhz): CPU NOP-based precise timing
* * Per-frequency functions: 1.7 / 1.8 / 1.9 / 2.0 / 2.1 / 2.2 MHz
* TX 신호 핀 (MOSFET 드라이버 제어): *============================================================================*/
* - PE (P0.25): Pulse Enable - 전체 시퀀스 활성화/비활성화
* - DMP (P1.0): Dump - 펄스 후 피에조 잔류 에너지 방전
* - P_OUT (P1.7): Positive Output - 피에조 양극 구동
* - N_OUT (P1.6): Negative Output - 피에조 음극 구동 (P_OUT과 역상)
*
* MUX 제어 핀 (8채널 에코 신호 경로 선택):
* - EN_MUXA (P0.21): MUXA 활성화 (CH0~CH3 담당)
* - EN_MUXB (P0.23): MUXB 활성화 (CH4~CH7 담당)
* - SEL0 (P1.10): MUX 내부 채널 주소 비트 0
* - SEL1 (P0.28): MUX 내부 채널 주소 비트 1
*
* === MUX 채널 매핑 (8채널) ===
* CH0 = MUXA 입력0: EN_A=1, EN_B=0, SEL0=0, SEL1=0
* CH1 = MUXA 입력2: EN_A=1, EN_B=0, SEL0=1, SEL1=0
* CH2 = MUXA 입력1: EN_A=1, EN_B=0, SEL0=0, SEL1=1
* CH3 = MUXA 입력3: EN_A=1, EN_B=0, SEL0=1, SEL1=1
* CH4 = MUXB 입력0: EN_A=0, EN_B=1, SEL0=1, SEL1=1
* CH5 = MUXB 입력1: EN_A=0, EN_B=1, SEL0=0, SEL1=1
* CH6 = MUXB 입력2: EN_A=0, EN_B=1, SEL0=1, SEL1=0
* CH7 = MUXB 입력3: EN_A=0, EN_B=1, SEL0=0, SEL1=0
*
* === 두 가지 버스트 모드 ===
* 1) 하드웨어 버스트 (dr_piezo_burst): Timer2 + PPI + GPIOTE 사용, CPU 비의존적
* 2) 소프트웨어 버스트 (dr_piezo_burst_sw_XXmhz): CPU NOP 기반 정밀 타이밍
* - 주파수별 전용 함수: 1.7/1.8/1.9/2.0/2.1/2.2 MHz
******************************************************************************/
#ifndef DR_PIEZO_H #ifndef DR_PIEZO_H
#define DR_PIEZO_H #define DR_PIEZO_H
@@ -65,212 +34,92 @@
#include "nrf_gpio.h" #include "nrf_gpio.h"
/*============================================================================== /*==============================================================================
* 전원 제어 핀 (DC/DC 컨버터 +/-20V) * Power control pin (+/-20V DC/DC converter)
* DR_PIEZO_PWR_EN: HIGH로 설정 시 DC/DC 컨버터가 +/-20V 고전압 생성
*============================================================================*/ *============================================================================*/
#define DR_PIEZO_PWR_EN NRF_GPIO_PIN_MAP(1, 9) /** Power Enable jhChun 0128 */ #define DR_PIEZO_PWR_EN NRF_GPIO_PIN_MAP(1, 9)
/*============================================================================== /*==============================================================================
* TX 신호 핀 (MOSFET 드라이버 제어) * TX signal pins (MOSFET driver)
* PE: Pulse Enable - 전체 TX 시퀀스 활성화/비활성화 * PE: Pulse Enable — activates the entire TX sequence
* DMP: Dump - 펄스 후 피에조 잔류 에너지 방전용 * DMP: Dump — discharges residual piezo energy after burst
* P_OUT: Positive Output - 피에조 양극 구동 (N_OUT과 역상) * P_OUT: Positive output — drives piezo positive terminal
* N_OUT: Negative Output - 피에조 음극 구동 (P_OUT과 역상) * N_OUT: Negative output — drives piezo negative terminal (inverted P_OUT)
* 주의: 이전 핀 할당(주석 처리)에서 새 보드 레이아웃으로 변경됨 (jhChun 0128)
*============================================================================*/ *============================================================================*/
#define DR_PIEZO_PIN_PE NRF_GPIO_PIN_MAP(0, 25) /**< Pulse Enable */ // P1.05 -> P0.25 #define DR_PIEZO_PIN_PE NRF_GPIO_PIN_MAP(0, 25) /**< Pulse Enable */
#define DR_PIEZO_PIN_DMP NRF_GPIO_PIN_MAP(1, 0) /**< Dump control */ // P1.9 -> P1.0 #define DR_PIEZO_PIN_DMP NRF_GPIO_PIN_MAP(1, 0) /**< Dump control */
#define DR_PIEZO_PIN_P_OUT NRF_GPIO_PIN_MAP(1, 7) /**< Positive output */ // P1.3 -> P1.7 #define DR_PIEZO_PIN_P_OUT NRF_GPIO_PIN_MAP(1, 7) /**< Positive output */
#define DR_PIEZO_PIN_N_OUT NRF_GPIO_PIN_MAP(1, 6) /**< Negative output */ // P1.2 -> P1.6 jhChun 0128 #define DR_PIEZO_PIN_N_OUT NRF_GPIO_PIN_MAP(1, 6) /**< Negative output */
/*============================================================================== /*==============================================================================
* MUX 제어 핀 (에코 신호 경로 선택) * MUX control pins (echo signal path selection, 8 channels)
* 8채널 아날로그 MUX로 피에조 센서 채널을 선택한다. * MUXA handles CH0..CH3, MUXB handles CH4..CH7.
* MUXA(CH0~CH3)와 MUXB(CH4~CH7) 두 개의 4채널 MUX 사용. * Only one MUX is enabled at a time.
* EN_MUXA/EN_MUXB: 각 MUX 활성화 (동시에 하나만 HIGH)
* SEL0/SEL1: MUX 내부 4채널 중 하나를 선택하는 주소 비트
*============================================================================*/ *============================================================================*/
/* Piezo MUX pins (8ch) jhChun 0129 */
#define DR_PIEZO_EN_MUXA NRF_GPIO_PIN_MAP(0, 21) /**< MUXA Enable */ #define DR_PIEZO_EN_MUXA NRF_GPIO_PIN_MAP(0, 21) /**< MUXA Enable */
#define DR_PIEZO_EN_MUXB NRF_GPIO_PIN_MAP(0, 23) /**< MUXB Enable */ #define DR_PIEZO_EN_MUXB NRF_GPIO_PIN_MAP(0, 23) /**< MUXB Enable */
#define DR_PIEZO_MUX_SEL0 NRF_GPIO_PIN_MAP(1, 10) /**< MUX Select 0 */ #define DR_PIEZO_MUX_SEL0 NRF_GPIO_PIN_MAP(1, 10) /**< MUX Select 0 */
#define DR_PIEZO_MUX_SEL1 NRF_GPIO_PIN_MAP(0, 28) /**< MUX Select 1 */ #define DR_PIEZO_MUX_SEL1 NRF_GPIO_PIN_MAP(0, 28) /**< MUX Select 1 */
/*============================================================================== /*==============================================================================
* 설정값 * Configuration
* DR_PIEZO_FREQ_HZ: 목표 주파수 (실제 동작 주파수는 dr_piezo.c에서 결정)
* DR_PIEZO_DEFAULT_CYCLES: 기본 버스트 사이클 수 (5)
* DR_PIEZO_MIN/MAX_CYCLES: 허용 사이클 범위 (3~7)
* DR_PIEZO_MUX_SETTLING_US: MUX 채널 전환 후 아날로그 경로 안정화 대기 시간
*============================================================================*/ *============================================================================*/
/**
* @note Actual operating frequency is defined in dr_piezo.c as PIEZO_FREQ_MHZ.
* Change PIEZO_FREQ_MHZ in dr_piezo.c to adjust the burst frequency.
* Current setting: 2.1 MHz
*/
#define DR_PIEZO_FREQ_HZ 2100000 /**< Target frequency (set PIEZO_FREQ_MHZ in .c) */ #define DR_PIEZO_FREQ_HZ 2100000 /**< Target frequency (set PIEZO_FREQ_MHZ in .c) */
#define DR_PIEZO_DEFAULT_CYCLES 5 /**< Default burst cycles (3~5) */ #define DR_PIEZO_DEFAULT_CYCLES 5 /**< Default burst cycles */
#define DR_PIEZO_MIN_CYCLES 3 #define DR_PIEZO_MIN_CYCLES 3
#define DR_PIEZO_MAX_CYCLES 7 #define DR_PIEZO_MAX_CYCLES 7
#define DR_PIEZO_MUX_SETTLING_US 1300 /**< MUX settling delay (us) */ #define DR_PIEZO_MUX_SETTLING_US 1300 /**< MUX settling delay (us) */
/*============================================================================== /*==============================================================================
* 전원 제어 함수 * Power control
* DC/DC 컨버터(+/-20V)를 ON/OFF하여 피에조 구동 고전압을 제어한다.
*============================================================================*/ *============================================================================*/
/**
* @brief Power ON piezo system (+/-20V DC/DC converter)
*/
void dr_piezo_power_on(void); void dr_piezo_power_on(void);
/**
* @brief Power OFF piezo system
*/
void dr_piezo_power_off(void); void dr_piezo_power_off(void);
/** /** @return true if power is ON */
* @brief 피에조 전원 상태 확인
* @return true: 전원 ON, false: 전원 OFF
*/
bool dr_piezo_is_power_on(void); bool dr_piezo_is_power_on(void);
/*============================================================================== /*==============================================================================
* TX 드라이버 함수 * TX driver
* 초음파 송신 관련: 초기화, 버스트 송신, 활성화/비활성화, 주파수 설정
*============================================================================*/ *============================================================================*/
/**
* @brief Initialize piezo TX driver (Timer + PPI + GPIOTE)
*/
void dr_piezo_init(void); void dr_piezo_init(void);
/**
* @brief Uninitialize piezo TX driver
*/
void dr_piezo_uninit(void); void dr_piezo_uninit(void);
/**
* @brief Transmit a burst of 2MHz pulses
* @param cycles Number of cycles to transmit (3~10)
*/
void dr_piezo_burst(uint8_t cycles); void dr_piezo_burst(uint8_t cycles);
/**
* @brief Transmit default burst (5 cycles)
*/
void dr_piezo_pulse(void); void dr_piezo_pulse(void);
/**
* @brief Enable TX output (prepare for transmission)
*/
void dr_piezo_enable(void); void dr_piezo_enable(void);
/**
* @brief Disable TX output (return to idle state)
*/
void dr_piezo_disable(void); void dr_piezo_disable(void);
/**
* @brief Check if TX is currently active
* @return true if transmitting
*/
bool dr_piezo_is_busy(void); bool dr_piezo_is_busy(void);
/**
* @brief Set TX frequency (for testing)
* @param freq_hz Frequency in Hz (100kHz ~ 4MHz)
*/
void dr_piezo_set_frequency(uint32_t freq_hz); void dr_piezo_set_frequency(uint32_t freq_hz);
/**
* @brief Test all pins manually (for debugging with oscilloscope)
*/
void dr_piezo_test_pins(void); void dr_piezo_test_pins(void);
/**
* @brief Initialize MUX control pins for echo signal path
*/
void dr_piezo_mux_init(void); void dr_piezo_mux_init(void);
/** /**
* @brief Select piezo channel (0~7) via 8ch MUX * @brief Select piezo channel (0..7) via 8ch MUX
* @param channel Piezo channel number (0~7) * @note MUX settling time: ~1.3 ms delay after switching
*
* Channel mapping (EN_MUXA, EN_MUXB, SEL0, SEL1):
* CH0=A0(1,0,0,0) CH1=A2(1,0,1,0) CH2=A1(1,0,0,1) CH3=A3(1,0,1,1)
* CH4=B0(0,1,1,1) CH5=B1(0,1,0,1) CH6=B2(0,1,1,0) CH7=B3(0,1,0,0)
*
* @note MUX settling time: 1.3ms delay after switching
*/ */
void dr_piezo_select_channel(uint8_t channel); void dr_piezo_select_channel(uint8_t channel);
/*============================================================================== /*==============================================================================
* 시스템 함수 (전원 + TX 통합 제어) * System functions (power + TX combined)
* 전원 ON/OFF와 TX 드라이버 초기화/해제를 한 번에 수행하는 편의 함수.
* 소프트웨어 버스트(burst_sw) 계열: CPU NOP 기반 정밀 타이밍.
* - Timer/PPI 없이 CPU에서 직접 GPIO를 제어
* - 인터럽트 비활성화 상태에서 동작하여 타이밍 정확도 보장
* - 주파수별 전용 함수 제공 (NOP 개수가 다름)
*============================================================================*/ *============================================================================*/
/**
* @brief Full system initialization (power + TX driver)
*/
void dr_piezo_system_init(void); void dr_piezo_system_init(void);
/**
* @brief Full system shutdown
*/
void dr_piezo_system_uninit(void); void dr_piezo_system_uninit(void);
/**
* @brief Transmit with power check
* @param cycles Number of cycles
*/
void dr_piezo_transmit(uint8_t cycles); void dr_piezo_transmit(uint8_t cycles);
/** /*==============================================================================
* @brief Software-based burst (CPU-controlled, no Timer/PPI) * Software burst CPU NOP-based precise timing, no Timer/PPI
* @param cycles Number of cycles (1~20) *
* @note Default frequency: 2.1 MHz * Interrupts are disabled during burst for timing accuracy.
*/ * Per-frequency functions (NOP count varies):
void dr_piezo_burst_sw(uint8_t cycles); *============================================================================*/
/** void dr_piezo_burst_sw(uint8_t cycles); /**< 2.1 MHz (default) */
* @brief Software-based burst at 1.8 MHz void dr_piezo_burst_sw_18mhz(uint8_t cycles); /**< 1.8 MHz */
* @param cycles Number of cycles (1~20) void dr_piezo_burst_sw_20mhz(uint8_t cycles); /**< 2.0 MHz */
* @note Fixed frequency: 1.8 MHz void dr_piezo_burst_sw_22mhz(uint8_t cycles); /**< 2.2 MHz */
*/ void dr_piezo_burst_sw_17mhz(uint8_t cycles); /**< 1.7 MHz */
void dr_piezo_burst_sw_18mhz(uint8_t cycles); void dr_piezo_burst_sw_19mhz(uint8_t cycles); /**< 1.9 MHz */
/**
* @brief Software-based burst at 2.0 MHz
* @param cycles Number of cycles (1~20)
* @note Fixed frequency: 2.0 MHz
*/
void dr_piezo_burst_sw_20mhz(uint8_t cycles);
/**
* @brief Software-based burst at 2.2 MHz
* @param cycles Number of cycles (1~20)
* @note Fixed frequency: 2.2 MHz
*/
void dr_piezo_burst_sw_22mhz(uint8_t cycles);
/**
* @brief Software-based burst at 1.7 MHz
* @param cycles Number of cycles (1~20)
* @note Fixed frequency: 1.7 MHz
*/
void dr_piezo_burst_sw_17mhz(uint8_t cycles);
/**
* @brief Software-based burst at 1.9 MHz
* @param cycles Number of cycles (1~20)
* @note Fixed frequency: 1.9 MHz
*/
void dr_piezo_burst_sw_19mhz(uint8_t cycles);
#endif /* DR_PIEZO_H */ #endif /* DR_PIEZO_H */
@@ -1,27 +1,14 @@
/******************************************************************************* /*==============================================================================
* @file tmp235_q1.c * tmp235_q1.c - TMP235-Q1 analogue temperature sensor driver
* @author CandyPops Co.
* @version V1.0.0
* @date 2022-09-05
* @brief
******************************************************************************/
/*******************************************************************************
* [모듈 개요] TMP235-Q1 아날로그 온도센서 드라이버
* *
* TMP235-Q1은 온도에 비례하는 아날로그 전압(Vout)을 출력하는 센서 * Reads the TMP235-Q1 analogue output via SAADC AIN3 and converts to deg C.
* nRF52840 SAADC의 AIN3 채널로 Vout을 읽고, mV로 변환한 뒤 온도(°C)로 계산
* *
* 온도 계산 공식 (구간별 선형 보간): * Temperature conversion (piecewise linear, per datasheet):
* - Vout <= 1500mV (0~100°C): Ta = (Vout - 500) / 10.0 * Vout <= 1500 mV (0..100 C) : Ta = (Vout - 500) / 10.0
* - Vout <= 1750mV (100~125°C): Ta = (Vout - 1500) / 10.1 + 100 * Vout <= 1750 mV (100..125 C): Ta = (Vout - 1500) / 10.1 + 100
* - Vout <= 2000mV (125~150°C): Ta = (Vout - 1752.5) / 10.6 + 125 * Vout <= 2000 mV (125..150 C): Ta = (Vout - 1752.5) / 10.6 + 125
* - Vout > 2000mV: 오류 (150°C 초과) * Vout > 2000 mV : out of sensor range
* *============================================================================*/
* info4 모드(전체 센서 수집) 동작 순서:
* 배터리(go_batt) → 온도(go_temp) → IMU(motion_raw_data_enabled)
* 온도 측정 완료 시 go_temp=false, motion_raw_data_enabled=true 로 전환
******************************************************************************/
#include "sdk_common.h" #include "sdk_common.h"
@@ -36,181 +23,146 @@
#include "ble_nus.h" #include "ble_nus.h"
#include "tmp235_q1.h" #include "tmp235_q1.h"
#include "main.h" #include "main.h"
/* 2026-03-17: cmd_parse.h 삭제 — main.h는 이미 포함됨 */
#include "main_timer.h" #include "main_timer.h"
#include "battery_saadc.h"
#include "debug_print.h" #include "debug_print.h"
/* SAADC 내부 기준전압 600mV (부동소수점) */
#define TMP235_REF_VOLTAGE_IN_MILLIVOLTS 600.0f /**< Reference voltage (in milli volts) used by ADC while doing conversion. */
/* 1/3 프리스케일링 보상 계수 x6 (부동소수점) */
#define TMP235_PRE_SCALING_COMPENSATION 6.0f /**< The ADC is configured to use VDD with 1/3 prescaling as input. And hence the result of conversion is to be multiplied by 3 to get the actual value of the battery voltage.*/
/* 12비트 ADC 최대값 4096 (부동소수점, 분해능 기준) */
#define TMP235_ADC_RES_12BITS 4096.0f /**< Maximum digital value for 12-bit ADC conversion. */
/**@brief Macro to convert the result of ADC conversion in millivolts. /* SAADC internal reference (mV) */
* #define TMP235_REF_VOLTAGE_IN_MILLIVOLTS 600.0f
* @param[in] ADC_VALUE ADC result. /* 1/3 prescaling compensation (x6) */
* #define TMP235_PRE_SCALING_COMPENSATION 6.0f
* @retval Result converted to millivolts. /* 12-bit ADC full scale */
*/ #define TMP235_ADC_RES_12BITS 4096.0f
/* ADC 원시값 → TMP235 출력전압(mV) 변환 매크로: ADC x (600/4096) x 6 */
/* Convert raw ADC value to TMP235 output voltage (mV) */
#define TMP235_VOUT_IN_MILLI_VOLTS(ADC_VALUE)\ #define TMP235_VOUT_IN_MILLI_VOLTS(ADC_VALUE)\
((((ADC_VALUE) * TMP235_REF_VOLTAGE_IN_MILLIVOLTS) / TMP235_ADC_RES_12BITS) * TMP235_PRE_SCALING_COMPENSATION) ((((ADC_VALUE) * TMP235_REF_VOLTAGE_IN_MILLIVOLTS) / TMP235_ADC_RES_12BITS) * TMP235_PRE_SCALING_COMPENSATION)
/* SAADC 변환 결과 저장 버퍼 (1채널) */
static nrf_saadc_value_t adc_buf; static nrf_saadc_value_t adc_buf;
extern char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN]; extern char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN];
extern uint8_t ble_bin_buffer[BLE_NUS_MAX_DATA_LEN] ; extern uint8_t ble_bin_buffer[BLE_NUS_MAX_DATA_LEN];
/* 현재 명령 소스: CMD_UART 또는 CMD_BLE */
extern which_cmd_t cmd_type_t; extern which_cmd_t cmd_type_t;
extern bool info4;
extern bool go_temp;
/* info4: 전체 센서 데이터 수집 모드 플래그 */ /* info4 mode: cached temperature (deg C x 100, integer) */
extern bool info4; // main.c volatile uint16_t info_temp;
/* 온도 측정 순서 제어 플래그 */
extern bool go_temp; // main_timer.c
/* info4 모드에서 온도값 임시 저장 (°C x 100, 정수 표현) */
volatile uint16_t info_temp; //48_C
extern bool motion_raw_data_enabled; extern bool motion_raw_data_enabled;
/* SAADC 완료 플래그 — all_sensors()에서 콜백 완료 대기용 */ /* SAADC completion flag — used by all_sensors() to wait */
volatile bool tmp235_saadc_done = false; volatile bool tmp235_saadc_done = false;
/**@brief Function for handling the ADC interrupt.
*
* @details This function will fetch the conversion result from the ADC, convert the value into
* percentage and send it to peer.
*/
/** /*==============================================================================
* @brief TMP235 온도센서 ADC 완료 콜백 * tmp235_voltage_handler - SAADC conversion complete callback
* *
* SAADC 변환 완료 시 호출된다. * ADC value -> Vout (mV) -> temperature (deg C), then:
* ADC값 → Vout(mV) → 온도(°C) 순으로 변환하고, 동작 모드에 따라: * - info4 mode: store to info_temp (C x 100 integer)
* - info4 모드: info_temp에 저장 (°C x 100 정수), 이후 IMU 측정으로 전환 * - Normal mode: send rso: response over BLE or UART
* - 일반 모드: BLE("rso:" 바이너리) 또는 UART로 온도값 전송 *============================================================================*/
*/ void tmp235_voltage_handler(nrf_drv_saadc_evt_t const * p_event)
void tmp235_voltage_handler(nrf_drv_saadc_evt_t const * p_event) /* TMP325 Vout reading */
{ {
float led_temp; /* 계산된 온도 (°C, 부동소수점) */ float led_temp;
float led_temp_16; /* BLE 전송용 온도 (°C x 100, 부동소수점) */ float led_temp_16;
if (p_event->type == NRF_DRV_SAADC_EVT_DONE) if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
{ {
nrf_saadc_value_t adc_result; nrf_saadc_value_t adc_result;
float tmp235_voltage_in_milli_volts = 0; float tmp235_voltage_in_milli_volts = 0;
/* ADC 변환 결과 읽기 */
adc_result = p_event->data.done.p_buffer[0]; adc_result = p_event->data.done.p_buffer[0];
//DBG_PRINTF("[TMP] adc=%d\r\n", adc_result);
/* SAADC 해제 — 배터리/압력센서 측정과 하드웨어 공유 */ /* Release SAADC — shared with battery / pressure ADC */
nrf_drv_saadc_channel_uninit(0); // 채널 먼저 해제 nrf_drv_saadc_channel_uninit(0);
nrf_drv_saadc_uninit(); nrf_drv_saadc_uninit();
//nrf_drv_saadc_uninit(); // 이전: 드라이버 먼저 해제
//nrf_drv_saadc_channel_uninit(0);
/* ADC값 → TMP235 출력전압(mV) 변환 */ /* ADC -> TMP235 output voltage (mV) */
tmp235_voltage_in_milli_volts = TMP235_VOUT_IN_MILLI_VOLTS(adc_result); tmp235_voltage_in_milli_volts = TMP235_VOUT_IN_MILLI_VOLTS(adc_result);
/* /* Vout -> temperature (piecewise linear per datasheet) */
* Vout → 온도(°C) 변환 (구간별 선형 보간)
* TMP235 데이터시트 기반:
* 0~100°C 구간: 기울기 10.0 mV/°C, 오프셋 500mV
* 100~125°C 구간: 기울기 10.1 mV/°C
* 125~150°C 구간: 기울기 10.6 mV/°C
*/
if(tmp235_voltage_in_milli_volts <= 1500) if(tmp235_voltage_in_milli_volts <= 1500)
{ {
/* 0~100°C: Ta = (Vout - 500mV) / 10.0 mV/°C */ /* 0..100 C: slope 10.0 mV/C, offset 500 mV */
led_temp = (tmp235_voltage_in_milli_volts - 500.0f) / 10.0f + 0.0f; led_temp = (tmp235_voltage_in_milli_volts - 500.0f) / 10.0f + 0.0f;
} }
else if(tmp235_voltage_in_milli_volts <= 1750) else if(tmp235_voltage_in_milli_volts <= 1750)
{ {
/* 100~125°C: 기울기가 10.1로 약간 증가 */ /* 100..125 C: slope 10.1 mV/C */
led_temp = (tmp235_voltage_in_milli_volts - 1500.0f) / 10.1f + 100.0f; led_temp = (tmp235_voltage_in_milli_volts - 1500.0f) / 10.1f + 100.0f;
} }
else if(tmp235_voltage_in_milli_volts <= 2000) else if(tmp235_voltage_in_milli_volts <= 2000)
{ {
/* 125~150°C: 기울기가 10.6으로 더 증가 */ /* 125..150 C: slope 10.6 mV/C */
led_temp = (tmp235_voltage_in_milli_volts - 1752.5f) / 10.6f + 125.0f; led_temp = (tmp235_voltage_in_milli_volts - 1752.5f) / 10.6f + 125.0f;
} }
else else
{ {
/* 150°C 초과 — 센서 측정 범위 벗어남 */ /* Out of sensor range (>150 C) */
DBG_PRINTF("ERR!!! Temprature is over 150c\r\n"); DBG_PRINTF("ERR!!! Temperature is over 150c\r\n");
} }
/* info4 모드: 온도값을 정수(°C x 100)로 저장 (예: 36.50°C → 3650) */ /* --- Safety check mode: pass temperature to battery module for judgment --- */
if (info4 == true) if (safety_check_mode == true)
{
safety_check_mode = false;
safety_check_complete(led_temp);
}
/* --- info4 mode: store value for mbb? bulk response --- */
else if (info4 == true)
{ {
info_temp = (uint16_t)(led_temp * 100); info_temp = (uint16_t)(led_temp * 100);
} }
/* UART 모드: 소수점 2자리까지 텍스트로 출력 */ else if (cmd_type_t == CMD_UART)
else if(cmd_type_t == CMD_UART)
{ {
DBG_PRINTF("To%.2f\r\n\r\n",led_temp); DBG_PRINTF("To%.2f\r\n\r\n", led_temp);
} }
/* BLE 모드: °C x 100 정수를 "rso:" 헤더로 바이너리 전송 */ else if (cmd_type_t == CMD_BLE)
else if(cmd_type_t == CMD_BLE)
{ {
led_temp_16 = led_temp * 100; led_temp_16 = led_temp * 100;
single_format_data(ble_bin_buffer, "rso:", (uint16_t)led_temp_16); single_format_data(ble_bin_buffer, "rso:", (uint16_t)led_temp_16);
dr_binary_tx_safe(ble_bin_buffer, 3);
dr_binary_tx_safe(ble_bin_buffer,3);
// sprintf(ble_tx_buffer, "To%.2f\r\n",led_temp);
// data_tx_handler(ble_tx_buffer);
} }
tmp235_saadc_done = true; tmp235_saadc_done = true;
} }
} }
/** /*==============================================================================
* @brief TMP235 온도센서 SAADC 초기화 및 측정 시작 * tmp235_init - Initialise SAADC for TMP235 and start measurement
* *
* AIN3 채널을 싱글엔드 모드로 설정하고 즉시 샘플링을 트리거한다. * AIN3, single-ended, 12-bit, 4x oversampling, burst enabled.
* 결과는 tmp235_voltage_handler 콜백에서 비동기로 처리된다. * Triggers sampling immediately; result arrives via tmp235_voltage_handler.
*/ *============================================================================*/
void tmp235_init(void) void tmp235_init(void)
{ {
/* SAADC 드라이버 초기화 (4x 오버샘플링으로 노이즈 저감) */
nrf_drv_saadc_config_t saadc_config = NRF_DRV_SAADC_DEFAULT_CONFIG; nrf_drv_saadc_config_t saadc_config = NRF_DRV_SAADC_DEFAULT_CONFIG;
saadc_config.resolution = NRF_SAADC_RESOLUTION_12BIT; saadc_config.resolution = NRF_SAADC_RESOLUTION_12BIT;
saadc_config.oversample = NRF_SAADC_OVERSAMPLE_4X; saadc_config.oversample = NRF_SAADC_OVERSAMPLE_4X;
ret_code_t err_code = nrf_drv_saadc_init(&saadc_config, tmp235_voltage_handler); ret_code_t err_code = nrf_drv_saadc_init(&saadc_config, tmp235_voltage_handler);
APP_ERROR_CHECK(err_code); APP_ERROR_CHECK(err_code);
/* AIN3 채널 설정: TMP235-Q1 Vout 핀 (싱글엔드 입력, burst 활성화) */
nrf_saadc_channel_config_t config = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN3); nrf_saadc_channel_config_t config = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN3);
config.burst = NRF_SAADC_BURST_ENABLED; config.burst = NRF_SAADC_BURST_ENABLED;
err_code = nrf_drv_saadc_channel_init(0, &config); err_code = nrf_drv_saadc_channel_init(0, &config);
APP_ERROR_CHECK(err_code); APP_ERROR_CHECK(err_code);
/* ADC 버퍼 등록 (1채널, 1샘플) */
err_code = nrf_drv_saadc_buffer_convert(&adc_buf, 1); err_code = nrf_drv_saadc_buffer_convert(&adc_buf, 1);
APP_ERROR_CHECK(err_code); APP_ERROR_CHECK(err_code);
/* 즉시 ADC 샘플링 시작 (비동기) */
err_code = nrf_drv_saadc_sample(); err_code = nrf_drv_saadc_sample();
APP_ERROR_CHECK(err_code); APP_ERROR_CHECK(err_code);
} }
/* Ta = (Vout Voffs ) / Tc + Tinfl */ /*==============================================================================
/** * tmp235_voltage_level_meas - External entry point for one-shot reading
* @brief 온도 측정 외부 호출 함수
* *
* tmp235_init()을 호출하여 SAADC 초기화 + 측정을 일괄 수행한다. * Calls tmp235_init() which both initialises and triggers sampling.
* 내부적으로 init 시 바로 샘플링이 시작되므로 별도 sample 호출 불필요. *============================================================================*/
*/
void tmp235_voltage_level_meas(void) void tmp235_voltage_level_meas(void)
{ {
tmp235_init(); // init 함수에 있는 걸 그냥 여기 넣어도 tmp235_init();
//tmp235_uninit();
} }
@@ -1,31 +1,22 @@
/******************************************************************************* /*==============================================================================
* @file tmp235_q1.h * tmp235_q1.h - TMP235-Q1 analogue temperature sensor driver interface
* @author CandyPops Co.
* @version V1.0.0
* @date 2022-09-05
* @brief
******************************************************************************/
/*******************************************************************************
* [헤더 개요] TMP235-Q1 아날로그 온도센서 드라이버 인터페이스
* *
* TMP235-Q1의 아날로그 전압 출력을 SAADC(AIN3)로 읽어 * Reads the TMP235-Q1 analogue voltage output via SAADC (AIN3) and converts
* 온도(°C)로 변환하는 기능의 외부 호출용 API를 선언한다. * it to temperature (deg C).
* *
* 주요 API: * Conversion: Ta(C) = (Vout_mV - 500) / 10.0 (valid 0..100 C)
* - tmp235_init() : SAADC 초기화 + 즉시 측정 시작 (내부 사용)
* - tmp235_voltage_level_meas() : 온도 1회 측정 (외부 호출용 래퍼)
* *
* 온도 변환: Vout(mV) → Ta(°C) = (Vout - 500) / 10.0 (0~100°C 구간) * API:
******************************************************************************/ * tmp235_init() : initialise SAADC + start measurement (internal)
* tmp235_voltage_level_meas() : one-shot temperature reading (external wrapper)
*============================================================================*/
#ifndef _TMP235_Q1_H_ #ifndef _TMP235_Q1_H_
#define _TMP235_Q1_H_ #define _TMP235_Q1_H_
/** @brief TMP235 SAADC 초기화 및 측정 시작 (AIN3 채널) */ /* Initialise SAADC for TMP235 and start measurement (AIN3). */
void tmp235_init(void); void tmp235_init(void);
/** @brief 온도 1회 측정 외부 호출 함수 (내부적으로 tmp235_init 호출) */ /* External entry point for a single temperature reading. */
void tmp235_voltage_level_meas(void); void tmp235_voltage_level_meas(void);
#endif /* !_TMP235_Q1_H_ */ #endif /* !_TMP235_Q1_H_ */
File diff suppressed because one or more lines are too long
@@ -107,7 +107,7 @@
#ifndef BSP_BTN_BLE_ENABLED #ifndef BSP_BTN_BLE_ENABLED
#define BSP_BTN_BLE_ENABLED 1 #define BSP_BTN_BLE_ENABLED 0
#endif #endif
// </h> // </h>
@@ -1,32 +1,32 @@
// file: debug_print.h // file: debug_print.h
/******************************************************************************* /*******************************************************************************
* [한국어 설명] 디버그 출력 매크로 (조건부 컴파일) * Debug print macros (conditional compilation)
* *
* SEGGER RTT(Real Time Transfer)를 이용한 디버그 출력 매크로. * Debug output macros using SEGGER RTT (Real Time Transfer).
* J-Link 디버거를 통해 실시간으로 로그를 PC에 전송한다. * Sends logs to the PC in real time via J-Link debugger.
* UART를 사용하지 않으므로 시스템 타이밍에 미치는 영향이 적다. * Minimal impact on system timing since UART is not used.
* *
* === 조건부 컴파일 === * === Conditional compilation ===
* ENABLE_PRINTF = 1: DBG_PRINTF SEGGER_RTT_printf(채널0)로 치환됨 * ENABLE_PRINTF = 1: DBG_PRINTF maps to SEGGER_RTT_printf (channel 0)
* -> 실제 로그 출력 (디버깅 시 사용) * -> Actual log output (for debugging)
* ENABLE_PRINTF = 0: DBG_PRINTF가 빈 매크로로 치환됨 * ENABLE_PRINTF = 0: DBG_PRINTF maps to an empty macro
* -> 코드에서 완전히 제거 (릴리스 빌드 시 사용) * -> Completely removed from code (for release builds)
* *
* === 사용법 === * === Usage ===
* DBG_PRINTF(": %d\r\n", value); // printf와 동일한 포맷 문자열 * DBG_PRINTF("val: %d\r\n", value); // Same format string as printf
* 출력은 SEGGER RTT Viewer 또는 J-Link RTT Client에서 확인. * View output in SEGGER RTT Viewer or J-Link RTT Client.
******************************************************************************/ ******************************************************************************/
#ifndef DEBUG_PRINT_H #ifndef DEBUG_PRINT_H
#define DEBUG_PRINT_H #define DEBUG_PRINT_H
#define ENABLE_PRINTF 1 /* 1=디버그 출력 활성화, 0=전역 비활성화 */ #define ENABLE_PRINTF 1 /* 1=Enable debug output, 0=Disable globally */
#if ENABLE_PRINTF #if ENABLE_PRINTF
#include "SEGGER_RTT.h" #include "SEGGER_RTT.h"
/* SEGGER RTT 채널 0으로 포맷 문자열 출력 */ /* Print formatted string to SEGGER RTT channel 0 */
#define DBG_PRINTF(...) SEGGER_RTT_printf(0, __VA_ARGS__) #define DBG_PRINTF(...) SEGGER_RTT_printf(0, __VA_ARGS__)
#else #else
/* 빈 매크로: 컴파일러가 호출 코드를 완전히 제거 */ /* Empty macro: compiler removes all call-site code */
#define DBG_PRINTF(...) // Do nothing #define DBG_PRINTF(...) // Do nothing
#endif #endif
@@ -1,11 +1,11 @@
/******************************************************************************* /*******************************************************************************
* @file led_control.c * @file led_control.c
* @brief LED 직접 제어 드라이버 (BSP 미사용) * @brief Direct LED control driver
* @date 2026-03-30 * @date 2026-03-30
* *
* app_timer 1개로 2색 LED(녹색/주황)의 복합 blink 패턴 구현 * Implements dual-color LED (green/orange) blink patterns with a single app_timer.
* 단순 on/off 상태는 타이머 없이 즉시 GPIO 제어 * Simple on/off states use immediate GPIO control without a timer.
* 복합 패턴(에러 등)은 phase 기반 state machine으로 처리 * Complex patterns (e.g. error) are handled by a phase-based state machine.
******************************************************************************/ ******************************************************************************/
#include "led_control.h" #include "led_control.h"
@@ -13,20 +13,20 @@
#include "app_timer.h" #include "app_timer.h"
/*============================================================================== /*==============================================================================
* 내부 상수 * Internal constants
*============================================================================*/ *============================================================================*/
#define MS_TO_TICKS(ms) APP_TIMER_TICKS(ms) #define MS_TO_TICKS(ms) APP_TIMER_TICKS(ms)
/*============================================================================== /*==============================================================================
* 색상 * Colors
*============================================================================*/ *============================================================================*/
#define COLOR_NONE 0 #define COLOR_NONE 0
#define COLOR_GREEN 1 #define COLOR_GREEN 1
#define COLOR_ORANGE 2 #define COLOR_ORANGE 2
/*============================================================================== /*==============================================================================
* 에러 패턴 상수 (No.7) * Error pattern constants (No.7)
* 3Hz 깜빡 3회 = 166ms on + 166ms off × 3 = ~1, 이후 꺼짐 1 * 3Hz blink x3 = 166ms on + 166ms off x 3 = ~1s, then off for 1s
*============================================================================*/ *============================================================================*/
#define ERROR_BLINK_ON_MS 166 #define ERROR_BLINK_ON_MS 166
#define ERROR_BLINK_OFF_MS 166 #define ERROR_BLINK_OFF_MS 166
@@ -34,42 +34,42 @@
#define ERROR_PAUSE_MS 1000 #define ERROR_PAUSE_MS 1000
/*============================================================================== /*==============================================================================
* 패턴 테이블 (단순 blink) * Pattern table (for simple blink)
*============================================================================*/ *============================================================================*/
typedef struct typedef struct
{ {
uint32_t on_ms; /* LED 켜짐 시간 (ms) */ uint32_t on_ms; /* LED on duration (ms) */
uint32_t off_ms; /* LED 꺼짐 시간 (ms) */ uint32_t off_ms; /* LED off duration (ms) */
uint8_t color; /* COLOR_GREEN 또는 COLOR_ORANGE */ uint8_t color; /* COLOR_GREEN or COLOR_ORANGE */
bool repeat; /* true: 무한 반복, false: 1회 후 OFF */ bool repeat; /* true: repeat forever, false: once then OFF */
} led_pattern_t; } led_pattern_t;
static const led_pattern_t m_patterns[LED_STATE_COUNT] = static const led_pattern_t m_patterns[LED_STATE_COUNT] =
{ {
[LED_STATE_OFF] = { 0, 0, COLOR_NONE, false }, [LED_STATE_OFF] = { 0, 0, COLOR_NONE, false },
[LED_STATE_POWER_ON] = { 2000, 0, COLOR_GREEN, false }, /* 초록 점등 2초 → 유지 */ [LED_STATE_POWER_ON] = { 2000, 0, COLOR_GREEN, false }, /* Green on 2s, stay on */
[LED_STATE_POWER_OFF] = { 2000, 0, COLOR_GREEN, false }, /* 초록 점등 2초 → OFF */ [LED_STATE_POWER_OFF] = { 2000, 0, COLOR_GREEN, false }, /* Green on 2s, then OFF */
[LED_STATE_ADVERTISING] = { 500, 500, COLOR_GREEN, true }, /* 초록 점멸 1초 */ [LED_STATE_ADVERTISING] = { 500, 500, COLOR_GREEN, true }, /* Green blink 1s interval */
[LED_STATE_DETACH_WARNING] = { 1000, 3000, COLOR_GREEN, true }, /* 초록 1 on / 3 off */ [LED_STATE_DETACH_WARNING] = { 1000, 3000, COLOR_GREEN, true }, /* Green 1s on / 3s off */
[LED_STATE_ALIGN_SEARCHING] = { 1000, 1000, COLOR_ORANGE, true }, /* 주황 점멸 1초 */ [LED_STATE_ALIGN_SEARCHING] = { 1000, 1000, COLOR_ORANGE, true }, /* Orange blink 1s interval */
[LED_STATE_ALIGN_COMPLETE] = { 3000, 1000, COLOR_GREEN, true }, /* 초록 3 on / 1 off */ [LED_STATE_ALIGN_COMPLETE] = { 3000, 1000, COLOR_GREEN, true }, /* Green 3s on / 1s off */
[LED_STATE_ERROR] = { 0, 0, COLOR_ORANGE, true } /* 별도 state machine */ [LED_STATE_ERROR] = { 0, 0, COLOR_ORANGE, true } /* Separate state machine */
}; };
/*============================================================================== /*==============================================================================
* 모듈 변수 * Module variables
*============================================================================*/ *============================================================================*/
APP_TIMER_DEF(m_led_timer); APP_TIMER_DEF(m_led_timer);
static led_state_t m_current_state = LED_STATE_OFF; static led_state_t m_current_state = LED_STATE_OFF;
static bool m_phase_on; /* true: LED 켜진 구간, false: 꺼진 구간 */ static bool m_phase_on; /* true: LED on phase, false: off phase */
/* 에러 패턴 전용 */ /* Error pattern only */
static uint8_t m_error_blink_cnt; /* 현재까지 깜빡인 횟수 */ static uint8_t m_error_blink_cnt; /* Blink count so far */
static uint8_t m_error_phase; /* 0: blink-on, 1: blink-off, 2: pause */ static uint8_t m_error_phase; /* 0: blink-on, 1: blink-off, 2: pause */
/*============================================================================== /*==============================================================================
* GPIO 헬퍼 * GPIO helpers
*============================================================================*/ *============================================================================*/
static inline void led_green_on(void) static inline void led_green_on(void)
@@ -122,7 +122,7 @@ static void led_color_on(uint8_t color)
} }
/*============================================================================== /*==============================================================================
* 타이머 시작 헬퍼 * Timer start helper
*============================================================================*/ *============================================================================*/
static void timer_start_ms(uint32_t ms) static void timer_start_ms(uint32_t ms)
{ {
@@ -131,7 +131,7 @@ static void timer_start_ms(uint32_t ms)
} }
/*============================================================================== /*==============================================================================
* 에러 패턴 state machine (No.7) * Error pattern state machine (No.7)
* phase 0: LED ON (166ms) → phase 1 * phase 0: LED ON (166ms) → phase 1
* phase 1: LED OFF (166ms) → cnt++ → cnt<3 ? phase 0 : phase 2 * phase 1: LED OFF (166ms) → cnt++ → cnt<3 ? phase 0 : phase 2
* phase 2: PAUSE (1000ms) → cnt=0, phase 0 * phase 2: PAUSE (1000ms) → cnt=0, phase 0
@@ -148,30 +148,30 @@ static void error_pattern_tick(void)
{ {
switch (m_error_phase) switch (m_error_phase)
{ {
case 0: /* ON 구간 끝 → OFF */ case 0: /* ON phase done -> OFF */
led_all_off(); led_all_off();
m_error_phase = 1; m_error_phase = 1;
timer_start_ms(ERROR_BLINK_OFF_MS); timer_start_ms(ERROR_BLINK_OFF_MS);
break; break;
case 1: /* OFF 구간 끝 */ case 1: /* OFF phase done */
m_error_blink_cnt++; m_error_blink_cnt++;
if (m_error_blink_cnt < ERROR_BLINK_COUNT) if (m_error_blink_cnt < ERROR_BLINK_COUNT)
{ {
/* 다시 ON */ /* Back to ON */
m_error_phase = 0; m_error_phase = 0;
led_color_on(COLOR_ORANGE); led_color_on(COLOR_ORANGE);
timer_start_ms(ERROR_BLINK_ON_MS); timer_start_ms(ERROR_BLINK_ON_MS);
} }
else else
{ {
/* 3회 완료 → pause */ /* 3 blinks done -> pause */
m_error_phase = 2; m_error_phase = 2;
timer_start_ms(ERROR_PAUSE_MS); timer_start_ms(ERROR_PAUSE_MS);
} }
break; break;
case 2: /* pause 끝 → 처음부터 */ case 2: /* Pause done -> restart */
m_error_blink_cnt = 0; m_error_blink_cnt = 0;
m_error_phase = 0; m_error_phase = 0;
led_color_on(COLOR_ORANGE); led_color_on(COLOR_ORANGE);
@@ -184,13 +184,13 @@ static void error_pattern_tick(void)
} }
/*============================================================================== /*==============================================================================
* 타이머 콜백 * Timer callback
*============================================================================*/ *============================================================================*/
static void led_timer_handler(void * p_context) static void led_timer_handler(void * p_context)
{ {
(void)p_context; (void)p_context;
/* 에러 상태는 별도 처리 */ /* Error state handled separately */
if (m_current_state == LED_STATE_ERROR) if (m_current_state == LED_STATE_ERROR)
{ {
error_pattern_tick(); error_pattern_tick();
@@ -201,7 +201,7 @@ static void led_timer_handler(void * p_context)
if (m_phase_on) if (m_phase_on)
{ {
/* ON→OFF 전환 */ /* ON -> OFF transition */
led_all_off(); led_all_off();
m_phase_on = false; m_phase_on = false;
@@ -211,40 +211,40 @@ static void led_timer_handler(void * p_context)
} }
else if (!p->repeat) else if (!p->repeat)
{ {
/* 1회성: off_ms == 0 이면 그냥 유지 (POWER_ON) 또는 끄기 (POWER_OFF) */ /* One-shot: if off_ms == 0, stay on (POWER_ON) or turn off (POWER_OFF) */
if (m_current_state == LED_STATE_POWER_OFF) if (m_current_state == LED_STATE_POWER_OFF)
{ {
led_all_off(); led_all_off();
m_current_state = LED_STATE_OFF; m_current_state = LED_STATE_OFF;
} }
/* POWER_ON: 점등 유지 상태 → 타이머 x */ /* POWER_ON: stay lit, no timer */
} }
} }
else else
{ {
/* OFF ON 전환 */ /* OFF -> ON transition */
if (p->repeat) if (p->repeat)
{ {
led_color_on(p->color); led_color_on(p->color);
m_phase_on = true; m_phase_on = true;
timer_start_ms(p->on_ms); timer_start_ms(p->on_ms);
} }
/* repeat == false && off_ms > 0 인 경우는 현재 없음 */ /* No current case where repeat == false && off_ms > 0 */
} }
} }
/*============================================================================== /*==============================================================================
* 공개 함수 * Public functions
*============================================================================*/ *============================================================================*/
void led_init(void) void led_init(void)
{ {
/* GPIO 출력 설정 */ /* Configure GPIO outputs */
nrf_gpio_cfg_output(LED_PIN_GREEN); nrf_gpio_cfg_output(LED_PIN_GREEN);
nrf_gpio_cfg_output(LED_PIN_ORANGE); nrf_gpio_cfg_output(LED_PIN_ORANGE);
led_all_off(); led_all_off();
/* 타이머 생성 (single-shot) */ /* Create timer (single-shot) */
app_timer_create(&m_led_timer, APP_TIMER_MODE_SINGLE_SHOT, led_timer_handler); app_timer_create(&m_led_timer, APP_TIMER_MODE_SINGLE_SHOT, led_timer_handler);
m_current_state = LED_STATE_OFF; m_current_state = LED_STATE_OFF;
@@ -254,7 +254,7 @@ void led_set_state(led_state_t state)
{ {
if (state >= LED_STATE_COUNT) return; if (state >= LED_STATE_COUNT) return;
/* 이전 패턴 중단 */ /* Stop previous pattern */
app_timer_stop(m_led_timer); app_timer_stop(m_led_timer);
led_all_off(); led_all_off();
@@ -266,15 +266,15 @@ void led_set_state(led_state_t state)
switch (state) switch (state)
{ {
case LED_STATE_OFF: case LED_STATE_OFF:
/* 이미 all off */ /* Already all off */
break; break;
/* 에러 패턴: 별도 state machine */ /* Error pattern: separate state machine */
case LED_STATE_ERROR: case LED_STATE_ERROR:
error_pattern_start(); error_pattern_start();
break; break;
/* 그 외: on 구간부터 시작 */ /* All others: start from ON phase */
default: default:
led_color_on(p->color); led_color_on(p->color);
m_phase_on = true; m_phase_on = true;
@@ -1,10 +1,10 @@
/******************************************************************************* /*******************************************************************************
* @file led_control.h * @file led_control.h
* @brief LED 직접 제어 드라이버 (BSP 미사용) * @brief Direct LED control driver
* @date 2026-03-30 * @date 2026-03-30
* *
* 녹색(P0.12) + 주황(P0.29) 2색 LED를 app_timer 기반으로 제어 * Controls dual-color LED green(P0.12) + orange(P0.29) via app_timer.
* 각 상태별 on/off 시간, 색상, 반복 패턴을 테이블로 관리 * On/off timing, color, and repeat pattern managed per state in a table.
******************************************************************************/ ******************************************************************************/
#ifndef LED_CONTROL_H__ #ifndef LED_CONTROL_H__
@@ -14,39 +14,39 @@
#include <stdbool.h> #include <stdbool.h>
/*============================================================================== /*==============================================================================
* LED 핀 정의 * LED pin definitions
*============================================================================*/ *============================================================================*/
#define LED_PIN_GREEN NRF_GPIO_PIN_MAP(0, 12) /* 녹색 LED */ #define LED_PIN_GREEN NRF_GPIO_PIN_MAP(0, 12) /* Green LED */
#define LED_PIN_ORANGE NRF_GPIO_PIN_MAP(0, 29) /* 주황 LED */ #define LED_PIN_ORANGE NRF_GPIO_PIN_MAP(0, 29) /* Orange LED */
#define LED_ACTIVE_LOW 1 /* 1 = Active Low (GPIO LOW에서 LED 켜짐) */ #define LED_ACTIVE_LOW 1 /* 1 = Active Low (LED on when GPIO LOW) */
/*============================================================================== /*==============================================================================
* LED 상태 열거형 * LED state enumeration
*============================================================================*/ *============================================================================*/
typedef enum typedef enum
{ {
LED_STATE_OFF = 0, /* 모든 LED 소등 */ LED_STATE_OFF = 0, /* All LEDs off */
LED_STATE_POWER_ON, /* 1: 전원 ON - 초록 소등 → 녹색 점등 2 */ LED_STATE_POWER_ON, /* 1: Power ON - green on 2s */
LED_STATE_POWER_OFF, /* 2: 전원 OFF - 초록 점등 2초 → 소등 */ LED_STATE_POWER_OFF, /* 2: Power OFF - green on 2s then off */
LED_STATE_ADVERTISING, /* 3: 블루투스 스캐닝 - 초록 점멸 1초 간격(반복) */ LED_STATE_ADVERTISING, /* 3: BLE advertising - green blink 1s interval (repeat) */
LED_STATE_DETACH_WARNING, /* 4: 정상 작동중(미부착 시) - 초록 점등 1초 / 소등 3초 (반복) */ LED_STATE_DETACH_WARNING, /* 4: Normal operation (detached) - green 1s on / 3s off (repeat) */
LED_STATE_ALIGN_SEARCHING, /* 5: 정렬모드(탐지중) - 주황 점멸 1초 간격 (반복) */ LED_STATE_ALIGN_SEARCHING, /* 5: Alignment mode (searching) - orange blink 1s interval (repeat) */
LED_STATE_ALIGN_COMPLETE, /* 6: 정렬모드(탐지 완료) - 초록 점등 3초 / 소등 1초 */ LED_STATE_ALIGN_COMPLETE, /* 6: Alignment mode (complete) - green 3s on / 1s off */
LED_STATE_ERROR, /* 7: 오류 발생 - 주황 3Hz 깜빡 3회 / 꺼짐 1 (반복) */ LED_STATE_ERROR, /* 7: Error - orange 3Hz blink x3 / off 1s (repeat) */
LED_STATE_COUNT /* 배열 크기 */ LED_STATE_COUNT /* Array size */
} led_state_t; } led_state_t;
/*============================================================================== /*==============================================================================
* 공개 함수 * Public functions
*============================================================================*/ *============================================================================*/
/** @brief LED GPIO 초기화 + 타이머 생성 - main()에서 1회 호출 */ /** @brief Initialize LED GPIO + create timer - call once from main() */
void led_init(void); void led_init(void);
/** @brief LED 상태 변경 - 이전 패턴을 즉시 중단하고 새 패턴 시작 */ /** @brief Change LED state - immediately stops previous pattern and starts new one */
void led_set_state(led_state_t state); void led_set_state(led_state_t state);
/** @brief 현재 LED 상태 조회 */ /** @brief Get current LED state */
led_state_t led_get_state(void); led_state_t led_get_state(void);
#endif /* LED_CONTROL_H__ */ #endif /* LED_CONTROL_H__ */
@@ -2,30 +2,30 @@
TEST medi50 Dec 23 TEST medi50 Dec 23
******************************************************************************* *******************************************************************************
* *
* [모듈 개요] * [Module overview]
* 메인 이벤트 루프 타이머 모듈 (10ms 간격, 싱글샷 모드). * Main event loop timer module (10ms interval, single-shot mode).
* *
* 센서 데이터 수집 및 시스템 제어 이벤트를 플래그 기반으로 디스패치한다. * Dispatches sensor data collection and system control events via flags.
* app_timer 싱글샷 모드를 사용하므로, 이벤트 처리 완료 후 * Uses app_timer single-shot mode; after processing, call main_timer_start()
* 필요 시 main_timer_start()로 수동 재시작해야 한다. * manually to restart if needed.
* *
* [이벤트 플래그 및 처리 순서] * [Event flags and processing order]
* motion_raw_data_enabled IMU 데이터 읽기 (icm42670_main 호출) * motion_raw_data_enabled -> IMU data read (calls icm42670_main)
* - motion_data_once == true: 단발성 읽기 (HW I2C 초기화 후 1회) * - motion_data_once == true: one-shot read (after HW I2C init)
* - motion_data_once == false: 연속 읽기 (BLE 전송 대기 중이 아닐 때) * - motion_data_once == false: continuous read (when not waiting for BLE TX)
* go_batt → 배터리 전압 측정 (battery_level_meas) * go_batt -> Battery voltage measurement (battery_level_meas)
* go_temp → 온도 측정 (tmp235_voltage_level_meas) * go_temp -> Temperature measurement (tmp235_voltage_level_meas)
* go_device_power_off → 디바이스 전원 OFF (device_power_off) * go_device_power_off -> Device power OFF (device_power_off)
* go_sleep_mode_enter → 슬립 모드 진입 (sleep_mode_enter) * go_sleep_mode_enter -> Enter sleep mode (sleep_mode_enter)
* go_NVIC_SystemReset NVIC 시스템 리셋 * go_NVIC_SystemReset -> NVIC system reset
* *
* [info4 모드 측정 순서] * [info4 mode measurement order]
* IMU 연속 읽기 → go_batt(배터리) → go_temp(온도) → motion_data_once(IMU 단발) * IMU continuous read -> go_batt (battery) -> go_temp (temp) -> motion_data_once (IMU one-shot)
* 온도 측정 완료 시 motion_data_once=true로 설정하여 다시 IMU로 돌아간다. * After temperature measurement, motion_data_once=true to return to IMU.
* *
* [타이머 설정] * [Timer settings]
* - 일반 모드: 10ms 간격 (MAIN_LOOP_INTERVAL) * - Normal mode: 10ms interval (MAIN_LOOP_INTERVAL)
* - FEATURE_DETAIL_VALUE_FULL 모드: 80ms 간격 (디테일 프린트아웃용) * - FEATURE_DETAIL_VALUE_FULL mode: 80ms interval (for detail printout)
* *
******************************************************************************/ ******************************************************************************/
@@ -56,16 +56,16 @@
#include "tmp235_q1.h" #include "tmp235_q1.h"
//#include "fstorage.h" //#include "fstorage.h"
#include "power_control.h" #include "power_control.h"
#include "main.h" /* 2026-03-17: cmd_parse.h 삭제 → main.h */ #include "main.h" /* 2026-03-17: cmd_parse.h removed, use main.h */
#include "debug_print.h" #include "debug_print.h"
#include "i2c_manager.h" //add cj #include "i2c_manager.h" //add cj
/* 메인 루프 싱글샷 타이머 인스턴스 */ /* Main loop single-shot timer instance */
APP_TIMER_DEF(m_main_loop_timer_id); APP_TIMER_DEF(m_main_loop_timer_id);
#if FEATURE_DETAIL_VALUE_FULL #if FEATURE_DETAIL_VALUE_FULL
/* 디테일 프린트아웃 모드: 80ms 간격으로 메인 루프 실행 */ /* Detail printout mode: run main loop at 80ms interval */
#define MAIN_LOOP_INTERVAL 80 /* 디테일 프린트아웃이 있을경우 Full_Mode Main Prosessing 수행하는 타이머 */ #define MAIN_LOOP_INTERVAL 80 /* Timer for Full_Mode main processing when detail printout is enabled */
//extern bool pd_adc_full_a_start; //extern bool pd_adc_full_a_start;
//extern bool pd_adc_full_b_start; //extern bool pd_adc_full_b_start;
@@ -75,36 +75,36 @@ APP_TIMER_DEF(m_main_loop_timer_id);
extern which_cmd_t cmd_type_t; extern which_cmd_t cmd_type_t;
#else #else
/* 일반 모드: 10ms 간격으로 메인 루프 실행 */ /* Normal mode: run main loop at 10ms interval */
#define MAIN_LOOP_INTERVAL 10 #define MAIN_LOOP_INTERVAL 10
#endif #endif
/* ========================================================================== */ /* ========================================================================== */
/* 이벤트 플래그 (외부 모듈에서 설정, main_loop에서 처리) */ /* Event flags (set by external modules, processed in main_loop) */
/* ========================================================================== */ /* ========================================================================== */
bool go_batt= false; /* 배터리 측정 요청 플래그 */ bool go_batt= false; /* Battery measurement request flag */
bool go_temp= false; /* 온도 측정 요청 플래그 */ bool go_temp= false; /* Temperature measurement request flag */
bool go_device_power_off = false; /* 디바이스 전원 OFF 요청 플래그 */ bool go_device_power_off = false; /* Device power OFF request flag */
bool go_sleep_mode_enter = false; /* 슬립 모드 진입 요청 플래그 */ bool go_sleep_mode_enter = false; /* Sleep mode entry request flag */
bool go_NVIC_SystemReset = false; /* 시스템 리셋 요청 플래그 */ bool go_NVIC_SystemReset = false; /* System reset request flag */
bool motion_raw_data_enabled = false; /* IMU 모션 데이터 읽기 활성화 플래그 */ bool motion_raw_data_enabled = false; /* IMU motion data read enable flag */
bool ble_got_new_data = false; /* BLE로 새 데이터 전송 완료 여부 */ bool ble_got_new_data = false; /* BLE new data transmission complete */
bool motion_data_once = false; /* IMU 단발성 읽기 모드 (true: 1회만 읽기) */ bool motion_data_once = false; /* IMU one-shot read mode (true: read once) */
/** /**
* @brief 메인 이벤트 루프 (싱글샷 타이머 콜백) * @brief Main event loop (single-shot timer callback)
* *
* 플래그 기반 이벤트 디스패처로, 설정된 플래그에 따라 해당 처리를 수행한다. * Flag-based event dispatcher; processes actions based on set flags.
* 싱글샷 타이머이므로 연속 실행이 필요한 경우 처리 내부에서 main_timer_start() * Since the timer is single-shot, call main_timer_start() again from
* 다시 호출하여 타이머를 재시작해야 한다. * within the handler to continue execution.
* *
* [처리 우선순위] (코드 순서대로 검사) * [Processing priority] (checked in code order)
* 1. IMU 모션 데이터 (motion_raw_data_enabled) * 1. IMU motion data (motion_raw_data_enabled)
* 2. 배터리 측정 (go_batt) * 2. Battery measurement (go_batt)
* 3. 온도 측정 (go_temp) * 3. Temperature measurement (go_temp)
* 4. 전원 OFF (go_device_power_off) * 4. Power OFF (go_device_power_off)
* 5. 슬립 모드 (go_sleep_mode_enter) * 5. Sleep mode (go_sleep_mode_enter)
* 6. 시스템 리셋 (go_NVIC_SystemReset) * 6. System reset (go_NVIC_SystemReset)
*/ */
void main_loop(void * p_context) /* For x ms */ void main_loop(void * p_context) /* For x ms */
{ {
@@ -140,109 +140,117 @@ void main_loop(void * p_context) /* For x ms */
// For Motion Data Sampling // For Motion Data Sampling
/* ---- IMU 모션 데이터 읽기 ---- */ /* ---- IMU motion data read ---- */
/* /*
* motion_raw_data_enabled true이면 IMU(ICM42670P) 데이터를 읽는다. * If motion_raw_data_enabled is true, read IMU (ICM42670P) data.
* *
* motion_data_once == true: * motion_data_once == true:
* 단발성 읽기 모드. HW I2C를 초기화한 후 icm42670_main()을 1회 호출. * One-shot read mode. Initialize HW I2C then call icm42670_main() once.
* info4 모드에서 배터리/온도 측정 후 다시 IMU로 돌아올 때 사용. * Used in info4 mode to return to IMU after battery/temp measurement.
* *
* motion_data_once == false: * motion_data_once == false:
* 연속 읽기 모드. BLE 전송 대기 중(ble_got_new_data==false)이면 * Continuous read mode. If not waiting for BLE TX (ble_got_new_data==false),
* icm42670_main()을 호출하고 10ms 후 타이머를 재시작하여 반복 실행. * call icm42670_main() and restart timer after 10ms for repeated execution.
*/ */
if(motion_raw_data_enabled == true) { if (motion_raw_data_enabled == true)
main_timer_stop(); /* 타이머 정지 (재진입 방지) */
if(motion_data_once == true)
{ {
/* 단발성 모드: HW I2C 초기화 후 IMU 데이터 1회 읽기 */ main_timer_stop(); /* Stop timer (prevent re-entry) */
hw_i2c_init_once(); /* HW TWI 모드로 전환 (400kHz) */ if (motion_data_once == true)
icm42670_main(); /* IMU 데이터 읽기 및 처리 */ {
/* One-shot mode: init HW I2C then read IMU data once */
hw_i2c_init_once(); /* Switch to HW TWI mode (400kHz) */
icm42670_main(); /* Read and process IMU data */
} }
else{ else
/* 연속 모드: BLE 전송 대기 중이 아니면 반복 읽기 */ {
if(ble_got_new_data==false){ /* Continuous mode: repeat read if not waiting for BLE TX */
if (ble_got_new_data==false)
{
//for(uint16_t i=0 ; i<60 ;i++) //for(uint16_t i=0 ; i<60 ;i++)
//{ //{
DBG_PRINTF("IMU \r\n"); DBG_PRINTF("IMU \r\n");
icm42670_main(); /* IMU 데이터 읽기 */ icm42670_main(); /* Read IMU data */
motion_raw_data_enabled = true; /* 플래그 유지 (연속 읽기) */ motion_raw_data_enabled = true; /* Keep flag set (continuous read) */
main_timer_start_ms(1000); /* 1초 후 다음 IMU 읽기 */ main_timer_start_ms(1000); /* Next IMU read after 1s */
} }
// else if(ble_got_new_data==true){ // else if(ble_got_new_data==true){
// motion_data_once = true; // motion_data_once = true;
// } // }
} }
} }
/* ---- 배터리 전압 측정 ---- */ /* ---- Battery voltage measurement ---- */
/* /*
* go_batt 플래그가 true이면 배터리 레벨을 측정한다. * If go_batt is true, measure battery level.
* info4 모드에서 IMU 연속 읽기 이후 호출되는 단계. * Called after IMU continuous read in info4 mode.
* 측정 완료 후 타이머가 정지된 상태로 유지된다. * Timer remains stopped after measurement.
*/ */
if(go_batt == true) { if (go_batt == true)
{
DBG_PRINTF("IMU BATT\r\n"); DBG_PRINTF("IMU BATT\r\n");
main_timer_stop(); /* 타이머 정지 */ main_timer_stop(); /* Stop timer */
go_batt = false; /* 플래그 소비 (1회 실행) */ go_batt = false; /* Consume flag (one-shot) */
// go_temp = true; // go_temp = true;
battery_level_meas(); /* SAADC를 이용한 배터리 전압 측정 */ battery_level_meas(); /* Measure battery voltage via SAADC */
// nrf_delay_ms(20); // nrf_delay_ms(20);
// m48_adc_start_init(); // m48_adc_start_init();
// main_timer_start(); // main_timer_start();
} }
/* ---- 온도 측정 ---- */ /* ---- Temperature measurement ---- */
/* /*
* go_temp 플래그가 true이면 TMP235-Q1 센서로 온도를 측정한다. * If go_temp is true, measure temperature via TMP235-Q1 sensor.
* info4 모드에서 배터리 측정 이후 호출되는 단계. * Called after battery measurement in info4 mode.
* 측정 완료 후 motion_data_once=true로 설정하여 * After completion, sets motion_data_once=true so the next IMU read
* 다음 IMU 읽기는 단발성 모드(HW I2C 재초기화)로 전환된다. * uses one-shot mode (with HW I2C re-init).
*/ */
if(go_temp == true) { if (go_temp == true)
{
DBG_PRINTF("IMU Temp\r\n"); DBG_PRINTF("IMU Temp\r\n");
main_timer_stop(); /* 타이머 정지 */ main_timer_stop(); /* Stop timer */
// go_batt = false; // go_batt = false;
go_temp = false; /* 플래그 소비 (1회 실행) */ go_temp = false; /* Consume flag (one-shot) */
motion_data_once = true; /* 다음 IMU 읽기를 단발성 모드로 전환 */ motion_data_once = true; /* Switch next IMU read to one-shot mode */
tmp235_voltage_level_meas(); /* TMP235-Q1 온도 센서 전압 측정 */ tmp235_voltage_level_meas(); /* Measure TMP235-Q1 temperature sensor voltage */
// motion_raw_data_enabled = true; // motion_raw_data_enabled = true;
// main_timer_start(); // main_timer_start();
} }
/* ---- 시스템 제어 이벤트 처리 ---- */ /* ---- System control event handling ---- */
/* 디바이스 전원 OFF 처리 */ /* Device power OFF handling */
if(go_device_power_off == true){ if (go_device_power_off == true)
main_timer_stop(); /* 타이머 정지 */ {
main_timer_stop(); /* Stop timer */
DBG_PRINTF("Off main_timer\r\n"); DBG_PRINTF("Off main_timer\r\n");
device_power_off(); /* 디바이스 전원 OFF 실행 */ device_power_off(); /* Execute device power OFF */
} }
/* 슬립 모드 진입 처리 */ /* Sleep mode entry handling */
if(go_sleep_mode_enter == true){ if (go_sleep_mode_enter == true)
main_timer_stop(); /* 타이머 정지 */ {
main_timer_stop(); /* Stop timer */
DBG_PRINTF("sleep main timer\r\n"); DBG_PRINTF("sleep main timer\r\n");
sleep_mode_enter(); /* 슬립 모드 진입 실행 */ sleep_mode_enter(); /* Execute sleep mode entry */
} }
/* NVIC 시스템 리셋 처리 */ /* NVIC system reset handling */
if(go_NVIC_SystemReset == true) { if (go_NVIC_SystemReset == true)
main_timer_stop(); /* 타이머 정지 */ {
NVIC_SystemReset(); /* ARM Cortex-M4 시스템 리셋 */ main_timer_stop(); /* Stop timer */
NVIC_SystemReset(); /* ARM Cortex-M4 system reset */
} }
} }
/** /**
* @brief 메인 루프 타이머 시작 * @brief Start main loop timer
* *
* 싱글샷 모드로 MAIN_LOOP_INTERVAL(10ms 또는 80ms) 후 main_loop()를 호출 * Single-shot mode; calls main_loop() after MAIN_LOOP_INTERVAL (10ms or 80ms)
*/ */
void main_timer_start(void) void main_timer_start(void)
{ {
@@ -250,9 +258,9 @@ void main_timer_start(void)
} }
/** /**
* @brief 지정된 간격(ms)으로 메인 루프 타이머 시작 * @brief Start main loop timer with specified interval (ms)
* *
* IMU 연속 스트리밍 등 기본 간격과 다른 주기가 필요할 때 사용 * Used when a different period than the default is needed (e.g. IMU streaming)
*/ */
void main_timer_start_ms(uint32_t interval_ms) void main_timer_start_ms(uint32_t interval_ms)
{ {
@@ -260,7 +268,7 @@ void main_timer_start_ms(uint32_t interval_ms)
} }
/** /**
* @brief 메인 루프 타이머 정지 * @brief Stop main loop timer
*/ */
void main_timer_stop(void) void main_timer_stop(void)
{ {
@@ -268,10 +276,10 @@ void main_timer_stop(void)
} }
/** /**
* @brief 메인 루프 타이머 초기화 (앱 시작 시 1회 호출) * @brief Initialize main loop timer (call once at app startup)
* *
* 싱글샷 모드 타이머를 생성하고, 콜백으로 main_loop()를 등록 * Creates a single-shot timer with main_loop() as callback.
* 싱글샷이므로 매 호출마다 main_timer_start()로 수동 재시작해야 함 * Must manually restart via main_timer_start() after each firing.
*/ */
void main_timer_init(void) void main_timer_init(void)
{ {
@@ -6,32 +6,32 @@
* @brief * @brief
******************************************************************************* *******************************************************************************
* *
* [헤더 개요] * [Header overview]
* 메인 이벤트 루프 타이머의 공용 인터페이스 헤더. * Public interface header for the main event loop timer.
* *
* 10ms(일반) 또는 80ms(디테일) 간격의 싱글샷 타이머를 사용하여 * Uses a single-shot timer at 10ms (normal) or 80ms (detail) interval.
* main_loop() 콜백에서 센서 데이터 수집 및 시스템 제어를 수행한다. * The main_loop() callback handles sensor data collection and system control.
* *
* [주요 함수] * [Key functions]
* main_timer_start() : 타이머 시작 (싱글샷, 수동 재시작 필요) * main_timer_start() : Start timer (single-shot, manual restart required)
* main_timer_stop() : 타이머 정지 * main_timer_stop() : Stop timer
* main_timer_init() : 타이머 초기화 (앱 시작 시 1회 호출) * main_timer_init() : Initialize timer (call once at app startup)
* *
******************************************************************************/ ******************************************************************************/
#ifndef TIMER_ROUTINE_H__ #ifndef TIMER_ROUTINE_H__
#define TIMER_ROUTINE_H__ #define TIMER_ROUTINE_H__
/** @brief 메인 루프 타이머 시작 (싱글샷, MAIN_LOOP_INTERVAL 후 main_loop 호출) */ /** @brief Start main loop timer (single-shot, calls main_loop after MAIN_LOOP_INTERVAL) */
void main_timer_start(void); void main_timer_start(void);
/** @brief 지정된 간격(ms)으로 메인 루프 타이머 시작 */ /** @brief Start main loop timer with specified interval (ms) */
void main_timer_start_ms(uint32_t interval_ms); void main_timer_start_ms(uint32_t interval_ms);
/** @brief 메인 루프 타이머 정지 */ /** @brief Stop main loop timer */
void main_timer_stop(void); void main_timer_stop(void);
/** @brief 메인 루프 타이머 초기화 (앱 시작 시 1회, 싱글샷 모드로 생성) */ /** @brief Initialize main loop timer (call once at startup, creates single-shot timer) */
void main_timer_init(void); void main_timer_init(void);
#endif //TIMER_ROUTINE_H__ #endif //TIMER_ROUTINE_H__
@@ -3,24 +3,24 @@
//=========power_control.c==================== //=========power_control.c====================
******************************************************************************* *******************************************************************************
* *
* [모듈 개요] * [Module overview]
* 디바이스 전원 시퀀스를 관리하는 모듈 (전원 켜기 / 끄기 / 슬립). * Device power sequence manager (power on / off / sleep).
* *
* [전원 켜기 흐름] * [Power-on flow]
* device_activated() * device_activated()
* power_loop 타이머 시작 → 즉시 완료 (센서 초기화 불필요) * -> Start power_loop timer -> completes immediately (no sensor init needed)
* → 센서(IMU)는 측정 명령 시 imu_read_direct()가 자체 처리 * -> Sensor (IMU) is handled by imu_read_direct() at measurement time
* *
* [슬립 모드] * [Sleep mode]
* device_sleep_mode() * device_sleep_mode()
* processing 플래그 해제 * -> Clear processing flag
* *
* [재활성화] * [Reactivation]
* device_reactivated() * device_reactivated()
* → I2C 재초기화 후 전원 시퀀스를 처음부터 다시 시작 * -> Re-init I2C then restart power sequence from the beginning
* *
* [타이머] * [Timer]
* 싱글샷 모드 app_timer, 20ms 간격으로 power_loop 호출 * Single-shot app_timer, calls power_loop at 20ms interval
* *
******************************************************************************/ ******************************************************************************/
#include <stdbool.h> #include <stdbool.h>
@@ -38,53 +38,48 @@
#include "i2c_manager.h" #include "i2c_manager.h"
/* 전원 시퀀스용 싱글샷 타이머 인스턴스 */ /* Single-shot timer instance for power sequence */
APP_TIMER_DEF(m_power_timer_id); APP_TIMER_DEF(m_power_timer_id);
/* 전원 시퀀스 상태머신의 타이머 간격 (20ms) */ /* Power sequence state machine timer interval (20ms) */
// 2025-12-08 change to #define POWER_LOOP_INTERVAL 30 -> 20 // 2025-12-08 change to #define POWER_LOOP_INTERVAL 30 -> 20
#define POWER_LOOP_INTERVAL 20 #define POWER_LOOP_INTERVAL 20
/* 전원 시퀀스 현재 단계 (0: I2C 초기화, 1: 예약, 2: 완료) */ /* Power sequence current step (0: I2C init, 1: reserved, 2: complete) */
static uint8_t p_order; static uint8_t p_order;
/* 데이터 처리 중 플래그 (외부 모듈에서 선언) */ /* Power sequence lock flag (true = sequence in progress) */
extern volatile bool processing;
/* 전원 시퀀스 잠금 플래그 (true = 전원 시퀀스 진행 중) */
bool lock_check = false; bool lock_check = false;
/** /**
* @brief 디바이스 슬립 모드 진입 * @brief Enter device sleep mode
* *
* 데이터 처리 플래그(processing)를 해제하여 * @return 0 (always succeeds)
* 메인 루프가 더 이상 센서 데이터를 처리하지 않도록 한다.
*
* @return 0 (항상 성공)
*/ */
int device_sleep_mode(void){ int device_sleep_mode(void)
{
int rc = 0; int rc = 0;
nrf_delay_ms(2); nrf_delay_ms(2);
DBG_PRINTF("Device_Sleep_Mode OK!\r\n"); DBG_PRINTF("Device_Sleep_Mode OK!\r\n");
nrf_delay_ms(10); nrf_delay_ms(10);
processing = false; /* 데이터 처리 플래그 해제 → 센서 처리 중단 */
return rc; return rc;
} }
/** /**
* @brief 디바이스 전원 켜기 (전원 시퀀스 시작) * @brief Power on device (start power sequence)
* *
* 전원 시퀀스 단계를 0으로 초기화하고, 잠금 플래그를 설정한 뒤 * Resets the sequence step to 0, sets the lock flag, and starts
* 타이머를 시작하여 power_loop() 상태머신을 구동한다. * the timer to drive the power_loop() state machine.
* *
* @return 0 (항상 성공) * @return 0 (always succeeds)
*/ */
int device_activated(void){ int device_activated(void)
{
int rc = 0; int rc = 0;
p_order = 0; /* 상태머신 시작 단계 (Step 0: I2C 초기화) */ p_order = 0; /* State machine start step (Step 0: I2C init) */
lock_check =true; /* 전원 시퀀스 진행 중 잠금 */ lock_check =true; /* Lock: power sequence in progress */
power_timer_start(); /* 20ms 후 power_loop() 첫 호출 */ power_timer_start(); /* First power_loop() call after 20ms */
return rc; return rc;
} }
@@ -101,60 +96,64 @@ int device_activated(void){
* 2: Complete * 2: Complete
*/ */
/* /*
* 전원 시퀀스 상태머신 (20ms 싱글샷 타이머 콜백). * Power sequence state machine (20ms single-shot timer callback).
* *
* [실행 흐름] * [Execution flow]
* 1) 타이머 콜백 진입 → 타이머 정지 (싱글샷이므로) * 1) Timer callback entry -> stop timer (single-shot)
* 2) 현재 p_order에 해당하는 초기화 단계 실행 * 2) Execute init step for current p_order
* 3) p_order < 2이면 다음 단계로 진행하고 타이머 재시작 * 3) If p_order < 2, advance to next step and restart timer
* 4) p_order == 2이면 전원 시퀀스 완료 * 4) If p_order == 2, power sequence complete
* *
* 센서 초기화 불필요 — imu_read_direct()가 매 측정 시 자체 처리 * No sensor init needed -- imu_read_direct() handles it per measurement.
*/ */
void power_loop(void *p_context) void power_loop(void *p_context)
{ {
UNUSED_PARAMETER(p_context); UNUSED_PARAMETER(p_context);
power_timer_stop(); /* 현재 타이머 정지 (싱글샷 → 수동 재시작 필요) */ power_timer_stop(); /* Stop current timer (single-shot, manual restart needed) */
/* 센서 초기화 불필요 — imu_read_direct()가 매 측정 시 자체 처리 */ /* No sensor init needed -- imu_read_direct() handles it per measurement */
/* 추후 전원 시퀀스 단계가 필요하면 여기에 switch(p_order) 추가 */ /* Add switch(p_order) here if future power sequence steps are needed */
p_order = 2; p_order = 2;
/* Advance to next step or finish */ /* Advance to next step or finish */
/* 다음 단계로 진행하거나, 시퀀스 완료 처리 */ /* Advance to next step or finish sequence */
if (p_order < 2) { if (p_order < 2)
p_order++; /* 다음 단계로 이동 */ {
power_timer_start(); /* 20ms 후 다음 단계 실행 */ p_order++; /* Move to next step */
} else { power_timer_start(); /* Execute next step after 20ms */
/* 전원 시퀀스 전체 완료 */ }
else
{
/* Power sequence fully complete */
DBG_PRINTF("[PWR] Device Activated OK!\r\n"); DBG_PRINTF("[PWR] Device Activated OK!\r\n");
} }
} }
/** /**
* @brief 디바이스 재활성화 (슬립 복귀 시) * @brief Reactivate device (on wake from sleep)
* *
* I2C를 재초기화하고, 전원 시퀀스를 처음부터 다시 시작한다. * Re-initializes I2C and restarts the power sequence from the beginning.
* 슬립 모드에서 깨어날 때 호출된다. * Called when waking from sleep mode.
* *
* @return 0 (항상 성공) * @return 0 (always succeeds)
*/ */
int device_reactivated(void){ int device_reactivated(void)
{
int rc = 0; int rc = 0;
sw_i2c_init_once(); /* I2C 버스 재초기화 */ sw_i2c_init_once(); /* Re-initialize I2C bus */
nrf_delay_ms(10); /* 안정화 대기 */ nrf_delay_ms(10); /* Stabilization delay */
lock_check = true; /* 전원 시퀀스 진행 중 잠금 */ lock_check = true; /* Lock: power sequence in progress */
p_order = 0; /* 상태머신을 Step 0부터 재시작 */ p_order = 0; /* Restart state machine from Step 0 */
power_timer_start(); /* 20ms 후 power_loop() 시작 */ power_timer_start(); /* Start power_loop() after 20ms */
return rc; return rc;
} }
/** /**
* @brief 전원 시퀀스 타이머 시작 * @brief Start power sequence timer
* *
* 싱글샷 모드로 POWER_LOOP_INTERVAL(20ms) 후 power_loop()를 호출한다. * Single-shot mode; calls power_loop() after POWER_LOOP_INTERVAL (20ms).
*/ */
void power_timer_start(void) void power_timer_start(void)
{ {
@@ -163,7 +162,7 @@ void power_timer_start(void)
/** /**
* @brief 전원 시퀀스 타이머 정지 * @brief Stop power sequence timer
*/ */
void power_timer_stop(void) void power_timer_stop(void)
{ {
@@ -173,10 +172,10 @@ void power_timer_stop(void)
/** /**
* @brief 전원 시퀀스 타이머 초기화 (앱 시작 시 1회 호출) * @brief Initialize power sequence timer (call once at app startup)
* *
* 싱글샷 모드 타이머를 생성하고, 콜백으로 power_loop()를 등록한다. * Creates a single-shot timer with power_loop() as callback.
* 싱글샷이므로 매 단계마다 power_timer_start()로 수동 재시작해야 한다. * Must manually restart via power_timer_start() after each step.
*/ */
void power_timer_init(void) //active start void power_timer_init(void) //active start
{ {
@@ -6,16 +6,16 @@
* @brief * @brief
******************************************************************************* *******************************************************************************
* *
* [헤더 개요] * [Header overview]
* 디바이스 전원 시퀀스 관리 모듈의 공용 인터페이스 헤더. * Public interface header for the device power sequence manager.
* *
* [주요 함수 요약] * [Key functions]
* device_activated() : 전원 켜기 → power_loop 상태머신 시작 * device_activated() : Power on -> start power_loop state machine
* device_sleep_mode() : 슬립 모드 진입 (처리 중단) * device_sleep_mode() : Enter sleep mode (stop processing)
* device_reactivated() : 슬립 복귀 → 전원 시퀀스 재시작 * device_reactivated() : Wake from sleep -> restart power sequence
* power_loop() : 20ms 간격 전원 시퀀스 상태머신 콜백 * power_loop() : 20ms power sequence state machine callback
* power_timer_start/stop : 전원 시퀀스 타이머 제어 * power_timer_start/stop : Power sequence timer control
* power_timer_init() : 전원 시퀀스 타이머 초기화 (앱 시작 시 1회) * power_timer_init() : Initialize power sequence timer (call once at startup)
* *
******************************************************************************/ ******************************************************************************/
@@ -24,25 +24,25 @@
#include "main.h" #include "main.h"
/** @brief 디바이스 슬립 모드 진입 (processing 해제) */ /** @brief Enter device sleep mode (clears processing flag) */
int device_sleep_mode(void); int device_sleep_mode(void);
/** @brief 디바이스 전원 켜기 (전원 시퀀스 상태머신 시작) */ /** @brief Power on device (start power sequence state machine) */
int device_activated(void); int device_activated(void);
/** @brief 디바이스 재활성화 (슬립 복귀 시 I2C 재초기화 후 시퀀스 재시작) */ /** @brief Reactivate device (re-init I2C and restart sequence on wake from sleep) */
int device_reactivated(void); int device_reactivated(void);
/** @brief 전원 시퀀스 상태머신 (20ms 타이머 콜백, Step 0→1→2) */ /** @brief Power sequence state machine (20ms timer callback, Step 0->1->2) */
void power_loop(void * p_context); /* For x ms */ void power_loop(void * p_context); /* For x ms */
/** @brief 전원 시퀀스 타이머 시작 (20ms 싱글샷) */ /** @brief Start power sequence timer (20ms single-shot) */
void power_timer_start(void); void power_timer_start(void);
/** @brief 전원 시퀀스 타이머 정지 */ /** @brief Stop power sequence timer */
void power_timer_stop(void);; void power_timer_stop(void);;
/** @brief 전원 시퀀스 타이머 초기화 (앱 시작 시 1회 호출) */ /** @brief Initialize power sequence timer (call once at app startup) */
void power_timer_init(void); void power_timer_init(void);
#endif //_POWER_CONTROL_H_ #endif //_POWER_CONTROL_H_
@@ -20,6 +20,22 @@ static struct {
bool bonds_delete_pending; bool bonds_delete_pending;
} m_state = {0}; } m_state = {0};
static const char * sec_error_name(pm_sec_error_code_t error)
{
switch (error) {
case PM_CONN_SEC_ERROR_PIN_OR_KEY_MISSING:
return "PIN_OR_KEY_MISSING";
case PM_CONN_SEC_ERROR_MIC_FAILURE:
return "MIC_FAILURE";
case PM_CONN_SEC_ERROR_DISCONNECT:
return "DISCONNECT";
case PM_CONN_SEC_ERROR_SMP_TIMEOUT:
return "SMP_TIMEOUT";
default:
return "UNKNOWN";
}
}
/** /**
* @brief Initialize BLE security * @brief Initialize BLE security
*/ */
@@ -100,10 +116,11 @@ void ble_security_quick_pm_handler(pm_evt_t const *p_evt)
{ {
ret_code_t err_code; ret_code_t err_code;
// DEV 모드: 보안 실패 이벤트는 SDK 핸들러에 전달하지 않음 (disconnect 방지) // DEV mode: do not forward security failure events to SDK handler (prevent disconnect)
if (m_state.dev_mode && p_evt->evt_id == PM_EVT_CONN_SEC_FAILED) { if (m_state.dev_mode && p_evt->evt_id == PM_EVT_CONN_SEC_FAILED) {
DBG_PRINTF("Security failed: error=%d\r\n", DBG_PRINTF("Security failed: error=%d (%s)\r\n",
p_evt->params.conn_sec_failed.error); p_evt->params.conn_sec_failed.error,
sec_error_name(p_evt->params.conn_sec_failed.error));
DBG_PRINTF("DEV: Ignoring sec failure, keeping connection\r\n"); DBG_PRINTF("DEV: Ignoring sec failure, keeping connection\r\n");
return; return;
} }
@@ -129,17 +146,32 @@ void ble_security_quick_pm_handler(pm_evt_t const *p_evt)
break; break;
case PM_EVT_CONN_SEC_FAILED: case PM_EVT_CONN_SEC_FAILED:
DBG_PRINTF("Security failed: error=%d\r\n", DBG_PRINTF("Security failed: error=%d (%s)\r\n",
p_evt->params.conn_sec_failed.error); p_evt->params.conn_sec_failed.error,
sec_error_name(p_evt->params.conn_sec_failed.error));
if (m_state.dev_mode) { if (m_state.dev_mode) {
// DEV 모드: 보안 실패 무시 — 연결 유지 // DEV mode: ignore security failure, keep connection
DBG_PRINTF("DEV: Ignoring sec failure, keeping connection\r\n"); DBG_PRINTF("DEV: Ignoring sec failure, keeping connection\r\n");
break; break;
} }
if (p_evt->params.conn_sec_failed.error == PM_CONN_SEC_ERROR_DISCONNECT) {
// The peer/link already disconnected before security finished.
// There is no live connection to repair; BLE_GAP_EVT_DISCONNECTED
// will restart advertising.
DBG_PRINTF("Security ended by disconnect; waiting for reconnect\r\n");
break;
}
if (p_evt->params.conn_sec_failed.error == PM_CONN_SEC_ERROR_SMP_TIMEOUT) {
// The SDK cannot start another SMP procedure on this link.
pm_handler_disconnect_on_sec_failure(p_evt);
break;
}
if (p_evt->params.conn_sec_failed.error == PM_CONN_SEC_ERROR_PIN_OR_KEY_MISSING) { if (p_evt->params.conn_sec_failed.error == PM_CONN_SEC_ERROR_PIN_OR_KEY_MISSING) {
// Key missing: 재페어링 시도, 실패 시 disconnect로 폴백 // Key missing: attempt re-pairing, fall back to disconnect on failure
err_code = pm_conn_secure(p_evt->conn_handle, true); err_code = pm_conn_secure(p_evt->conn_handle, true);
if (err_code != NRF_ERROR_INVALID_STATE && if (err_code != NRF_ERROR_INVALID_STATE &&
err_code != NRF_ERROR_BUSY && err_code != NRF_ERROR_BUSY &&
@@ -147,11 +179,11 @@ void ble_security_quick_pm_handler(pm_evt_t const *p_evt)
APP_ERROR_CHECK(err_code); APP_ERROR_CHECK(err_code);
} }
if (err_code != NRF_SUCCESS) { if (err_code != NRF_SUCCESS) {
// 재페어링 불가 → disconnect // Re-pairing not possible -> disconnect
pm_handler_disconnect_on_sec_failure(p_evt); pm_handler_disconnect_on_sec_failure(p_evt);
} }
} else { } else {
// 기타 보안 실패 → bond 삭제 후 재페어링 시도 // Other security failure -> delete bond then attempt re-pairing
pm_peer_id_t peer_id; pm_peer_id_t peer_id;
if (pm_peer_id_get(p_evt->conn_handle, &peer_id) == NRF_SUCCESS if (pm_peer_id_get(p_evt->conn_handle, &peer_id) == NRF_SUCCESS
&& peer_id != PM_PEER_ID_INVALID) { && peer_id != PM_PEER_ID_INVALID) {