maa samples 140->100 변경, mec/maa 수신 시 Piezo 자동 Active/Sleep

- MAA_NUM_SAMPLES 140에서 100으로 변경 (DR_ADC_ECHO_SAMPLES_MAX 제한)
- 단일(mec) 및 모든 채널(maa) 에코 캡처 명령 수신 시 Piezo 자동 Active, 응답 송신 후 Sleep
- Cmd_mpa 중복 호출 정리 (dr_piezo_system_init 내부에 power_on 포함)
- 채널별 디버그 로그 추가
- 코드 리뷰 주석 정리 및 기타 파일 업데이트

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
jhChun
2026-03-17 18:03:04 +09:00
parent 36c17d79c6
commit a0c96c6677
29 changed files with 1474 additions and 501 deletions

View File

@@ -165,6 +165,75 @@ cat_interface.c 삭제(EEPROM/AES 코드 전체 삭제). TWI 인스턴스(m_twi)
| c_addr[6] | main.c:187 | 삭제 | **미완료** | | c_addr[6] | main.c:187 | 삭제 | **미완료** |
| led_pd_dac_v[LED_NUM] | 참조 다수 | 삭제 | 확인 필요 | | led_pd_dac_v[LED_NUM] | 참조 다수 | 삭제 | 확인 필요 |
### 2.3.1 cmd_parse.c → parser.c 통합 현황
현재 구조: main.c → `received_command_process()` [cmd_parse.c] → `dr_cmd_parser()` [parser.c] → 실패 시 레거시 if-else 체인 [cmd_parse.c]
#### parser.c 명령어 테이블 (g_cmd_table)
| 태그 | 핸들러 | enabled | 기능 | 비고 |
|------|--------|---------|------|------|
| `cmd?` | Cmd_cmd | true | 핀 테스트 (sudo) | parser.c 신규 |
| `mpa?` | Cmd_mpa | true | Piezo 활성화 | parser.c 신규 |
| `mpb?` | Cmd_mpb | true | Piezo 비활성화 | parser.c 신규 |
| `mpc?` | Cmd_mpc | true | Piezo Cycles 제어 | parser.c 신규 |
| `mdc?` | Cmd_mdc | true | Piezo burst + Echo (12-bit) | parser.c 신규 |
| `mec?` | Cmd_mec | true | Piezo burst + Echo (16-bit) | parser.c 신규 |
| `maa?` | Cmd_maa | true | 8채널 전체 캡처 | parser.c 신규 |
| `msp?` | Cmd_msp | true | IMU 6축 raw data | parser.c 신규 |
| `mwh?` | Cmd_mwh | true | HW번호 쓰기 (FDS) | shz? 대체 |
| `mws?` | Cmd_mws | true | 시리얼번호 쓰기 (FDS) | ssz? 대체 |
| `mrh?` | Cmd_mrh | true | HW번호 읽기 (FDS) | siz? 대체 |
| `mrs?` | Cmd_mrs | true | 시리얼번호 읽기 (FDS) | srz? 대체 |
| `mpz?` | Cmd_mpz | true | 패스키 쓰기 (FDS) | **신규 추가** (spz? 대체) |
| `mqz?` | Cmd_mqz | true | 패스키 읽기 | **신규 추가** (sqz? 대체) |
| `mxz?` | Cmd_mxz | true | life_cycle 쓰기 (FDS) | **신규 추가** (sxz? 대체) |
| `myz?` | Cmd_myz | true | life_cycle 읽기 | **신규 추가** (syz? 대체) |
| `mta?` | Cmd_mta | true | 디바이스 상태 | sta? 리네이밍 |
| `sta?` | Cmd_sta | true | 디바이스 활성화/슬립 | 레거시 호환 |
| `str?` | Cmd_str | false | 디바이스 상태 읽기 | 미완성 (TODO) |
| `mcj?` | Cmd_mcj | true | PD-ADC 풀 측정 | parser.c 신규 |
| `scj?` | Cmd_mcj | true | mcj 호환 | 레거시 호환 |
| `sej?` | Cmd_sej | true | PD-ADC 측정 | 레거시 호환 |
| `ssj?` | Cmd_ssj | false | PD-ADC 측정 | 비활성 |
| `msn?` | Cmd_msn | true | 배터리 측정 | ssn? 리네이밍 |
| `ssn?` | Cmd_msn | true | 배터리 측정 | 레거시 호환 |
| `spn?` | Cmd_spn | false | 압력 센서 측정 | 비활성 |
| `sso?` | Cmd_sso | false | 온도 센서 측정 | 비활성 |
| `ssp?` | Cmd_ssp | true | 모션 센서 raw data | 레거시 호환 |
| `ssq?` | Cmd_ssq | false | 전원 OFF | 비활성 |
| `ssr?` | Cmd_ssr | false | 본딩 삭제+리셋 | 비활성 |
| `sss?` | Cmd_sss | false | 디바이스 리셋 | 비활성 |
| `sst?` | Cmd_sst | false | Ready 응답 | 비활성 |
| `mfv?` | Cmd_mfv | true | 펌웨어 버전 읽기 | ssv? 대체 |
#### cmd_parse.c 레거시 명령어 (parser.c에 없는 것)
| 태그 | 상태 | 기능 | 비고 |
|------|------|------|------|
| `spz?` | 활성 | 패스키 쓰기 | → parser.c `mpz?`로 대체 완료 |
| `sqz?` | 활성 | 패스키 읽기 | → parser.c `mqz?`로 대체 완료 |
| `sxz?` | 활성 | life_cycle 쓰기 | → parser.c `mxz?`로 대체 완료 |
| `syz?` | 활성 | life_cycle 읽기 | → parser.c `myz?`로 대체 완료 |
| `sez?` | 활성 (stub) | AGC gain — HW 제거됨 | param_error만 응답 |
| `sfz?` | 활성 (stub) | AGC gain — HW 제거됨 | param_error만 응답 |
| `ssv?` | `if(0&&)` 비활성 | 펌웨어 버전 | → parser.c `mfv?`로 대체됨 |
| `ssz?` | `if(0&&)` 비활성 | 시리얼번호 쓰기 | → parser.c `mws?`로 대체됨 |
| `srz?` | `if(0&&)` 비활성 | 시리얼번호 읽기 | → parser.c `mrs?`로 대체됨 |
| `siz?` | `if(0&&)` 비활성 | HW번호 읽기 | → parser.c `mrh?`로 대체됨 |
| `shz?` | `if(0&&)` 비활성 | HW번호 쓰기 | → parser.c `mwh?`로 대체됨 |
#### 통합 남은 작업
| # | 작업 | 현재 상태 |
|---|------|----------|
| 1 | parser.c에 누락 명령어 추가 (mpz/mqz/mxz/myz) | **완료** |
| 2 | received_command_process() 래퍼를 main.c로 이동 | **미완료** |
| 3 | 전역 변수 (SERIAL_NO, m_static_passkey 등) main.c로 이동 | **미완료** |
| 4 | cmd_parse.h include 정리 (7개 파일) | **미완료** |
| 5 | cmd_parse.c / cmd_parse.h 삭제 | **미완료** |
| 6 | Keil 프로젝트 파일 업데이트 | **미완료** |
### 2.4 주석 처리된 코드 ### 2.4 주석 처리된 코드
| 위치 | 내용 | 판정 | 현재 상태 | | 위치 | 내용 | 판정 | 현재 상태 |
@@ -448,10 +517,11 @@ BLE_GAP_EVT_CONNECTED/DISCONNECTED 핸들러에 IMU 타이머 start/stop 로직
| 5 | 미사용 HW 드라이버 삭제 (1.2절) | **완료** | | 5 | 미사용 HW 드라이버 삭제 (1.2절) | **완료** |
| 6 | cat_interface.c EEPROM 기능 삭제 + TWI를 i2c_manager.c로 통합 (1.5절) | **완료** | | 6 | cat_interface.c EEPROM 기능 삭제 + TWI를 i2c_manager.c로 통합 (1.5절) | **완료** |
| 7 | cmd_parse.c EEPROM 호출 -> FDS 전환 (1.5.4절) | **완료** | | 7 | cmd_parse.c EEPROM 호출 -> FDS 전환 (1.5.4절) | **완료** |
| 8 | 미사용 전역변수 + HW_NO 삭제 (2.2-2.3절) | **미완료** | | 8 | cmd_parse.c → parser.c 통합 (2.3.1절) | **진행 중** (명령어 이전 완료, 파일 삭제 미완료) |
| 9 | DEBUG_MINIMAL_BOOT 조건 제거 (3절) | **미완료** | | 9 | 미사용 전역변수 + HW_NO 삭제 (2.2-2.3절) | **미완료** |
| 10 | Keil uvprojx 소스 참조 제거 (4절) | 확인 필요 | | 10 | DEBUG_MINIMAL_BOOT 조건 제거 (3절) | **미완료** |
| 11 | 주석 처리된 코드 삭제 (2.4절) | **미완료** | | 11 | Keil uvprojx 소스 참조 제거 (4절) | 확인 필요 |
| 12 | 주석 처리된 코드 삭제 (2.4절) | **미완료** |
### 2차 정리 (저전력 최적화) ### 2차 정리 (저전력 최적화)

Binary file not shown.

Binary file not shown.

View File

@@ -49,6 +49,9 @@ extern void dr_piezo_burst_sw_20mhz(uint8_t cycles);
extern void dr_piezo_burst_sw_17mhz(uint8_t cycles); extern void dr_piezo_burst_sw_17mhz(uint8_t cycles);
extern void dr_piezo_burst_sw_22mhz(uint8_t cycles); extern void dr_piezo_burst_sw_22mhz(uint8_t cycles);
extern void dr_piezo_burst_sw_19mhz(uint8_t cycles); extern void dr_piezo_burst_sw_19mhz(uint8_t cycles);
extern void dr_piezo_power_off(void);
#include "parser.h"
/*============================================================================== /*==============================================================================
* DEBUG CONFIGURATION * DEBUG CONFIGURATION
@@ -791,7 +794,7 @@ dr_adc_err_t dr_adc_burst_capture_transmit(uint8_t freq_option, uint16_t delay_u
if (cycles < 3 || cycles > 9) cycles = 5; /* Valid range: 3~9, default 5 */ if (cycles < 3 || cycles > 9) cycles = 5; /* Valid range: 3~9, default 5 */
if (averaging == 0) averaging = 1; /* Minimum 1 */ if (averaging == 0) averaging = 1; /* Minimum 1 */
if (averaging > 1000) averaging = 1000; /* Maximum 1000 */ if (averaging > 1000) averaging = 1000; /* Maximum 1000 */
if (piezo_ch > 7) piezo_ch = 0; /* Validate piezo channel: 0~7 jhChun 26.01.29 */ if (piezo_ch >= MAA_NUM_CHANNELS) piezo_ch = 0; /* 채널 범위 검증 */
dr_adc_echo_t echo; dr_adc_echo_t echo;
echo.peak_raw = 0; echo.peak_raw = 0;
@@ -1325,6 +1328,8 @@ static dr_adc_err_t maa_async_capture_channel(uint8_t ch)
{ {
if (ch > (MAA_NUM_CHANNELS - 1)) return DR_ADC_ERR_INVALID_PARAM; if (ch > (MAA_NUM_CHANNELS - 1)) return DR_ADC_ERR_INVALID_PARAM;
if (g_plat.log) g_plat.log("[maa] capturing CH%u\r\n", ch);
return dr_adc_capture_channel_only( return dr_adc_capture_channel_only(
g_maa_ctx.freq_option, g_maa_ctx.freq_option,
g_maa_ctx.delay_us, g_maa_ctx.delay_us,
@@ -1434,6 +1439,13 @@ static void maa_async_send_completion(uint16_t status)
dr_binary_tx_safe(buf, 3); dr_binary_tx_safe(buf, 3);
/* 자동으로 켰으면 완료 후 전원 OFF */
if (g_maa_ctx.auto_powered) {
if (g_plat.log) g_plat.log("[Cmd_maa] TX/RX Active -> Sleep\r\n");
dr_piezo_power_off();
g_maa_ctx.auto_powered = false;
}
g_maa_ctx.state = MAA_ASYNC_IDLE; g_maa_ctx.state = MAA_ASYNC_IDLE;
ADC_LOG("maa_async: complete, status=0x%04X", status); ADC_LOG("maa_async: complete, status=0x%04X", status);
} }
@@ -1551,3 +1563,8 @@ void maa_async_abort(void)
} }
} }
void maa_async_set_auto_power(bool on)
{
g_maa_ctx.auto_powered = on;
}

View File

@@ -336,7 +336,7 @@ void dr_piezo_select_channel(uint8_t channel);
* @brief 8-channel echo buffer for maa? command * @brief 8-channel echo buffer for maa? command
* Memory: 140 samples × 2 bytes × 8 channels = 2,240 bytes * Memory: 140 samples × 2 bytes × 8 channels = 2,240 bytes
*/ */
#define MAA_NUM_CHANNELS 8 /* 4 -> 8 jhChun 26.02.12*/ #define MAA_NUM_CHANNELS 6 /* 4 -> 8 -> 6 jhChun 26.03.17*/
#define MAA_SAMPLES_MAX 200 #define MAA_SAMPLES_MAX 200
/** /**
@@ -454,6 +454,7 @@ typedef struct {
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 */
uint16_t total_packets; /**< Total packets for current channel */ uint16_t total_packets; /**< Total packets for current channel */
uint16_t data_packets; /**< Data packets for current channel */ uint16_t data_packets; /**< Data packets for current channel */
bool auto_powered; /**< true: 자동 전원 ON → 완료 후 OFF */
} maa_async_ctx_t; } maa_async_ctx_t;
/** /**
@@ -501,5 +502,10 @@ maa_async_state_t maa_async_get_state(void);
*/ */
void maa_async_abort(void); void maa_async_abort(void);
/**
* @brief 자동 전원 플래그 설정 (완료 후 자동 power off)
*/
void maa_async_set_auto_power(bool on);
#endif /* DR_ADC121S051_H */ #endif /* DR_ADC121S051_H */

View File

@@ -111,6 +111,7 @@ extern config_data_t m_config; /* 전체 설정 구조체 (FDS 저장 대
/* 피에조 초음파 트랜스듀서 제어 함수 */ /* 피에조 초음파 트랜스듀서 제어 함수 */
extern void dr_piezo_power_on( void ); /* 피에조 회로 전원 ON (TX/RX 보드) */ extern void dr_piezo_power_on( void ); /* 피에조 회로 전원 ON (TX/RX 보드) */
extern void dr_piezo_power_off( void ); /* 피에조 회로 전원 OFF */ extern void dr_piezo_power_off( void ); /* 피에조 회로 전원 OFF */
extern bool dr_piezo_is_power_on( void ); /* 피에조 전원 상태 확인 */
extern void dr_piezo_burst_sw(uint8_t cycles); /* 2.1MHz 버스트 펄스 발생 (기본 주파수) */ extern void dr_piezo_burst_sw(uint8_t cycles); /* 2.1MHz 버스트 펄스 발생 (기본 주파수) */
extern void dr_piezo_burst_sw_18mhz(uint8_t cycles); /* 1.8MHz 버스트 펄스 발생 */ extern void dr_piezo_burst_sw_18mhz(uint8_t cycles); /* 1.8MHz 버스트 펄스 발생 */
extern void dr_piezo_burst_sw_20mhz(uint8_t cycles); /* 2.0MHz 버스트 펄스 발생 */ extern void dr_piezo_burst_sw_20mhz(uint8_t cycles); /* 2.0MHz 버스트 펄스 발생 */
@@ -436,7 +437,7 @@ static CmdEntry g_cmd_table[] = {
/* D. 각종 센서 측정 */ /* D. 각종 센서 측정 */
{ "msn?", true, Cmd_msn }, { "msn?", true, Cmd_msn },
{ "mso?", false, Cmd_mso }, { "mso?", true, Cmd_mso },
{ "msp?", true, Cmd_msp }, { "msp?", true, Cmd_msp },
{ "msi?", true, Cmd_msi }, { "msi?", true, Cmd_msi },
@@ -511,7 +512,7 @@ static int dr_cmd_dispatch(const ParsedCmd *cmd)
int dr_cmd_parser(const uint8_t *buf, uint8_t len) int dr_cmd_parser(const uint8_t *buf, uint8_t len)
{ {
ParsedCmd cmd; ParsedCmd cmd;
g_plat.log("parser!!!!!!!!"); g_plat.log("parser!!!!!!!!\r\n");
if (g_plat.log) g_plat.log("[PARSER] in len=%u crc=%u\r\n", len, g_plat.crc_check); if (g_plat.log) g_plat.log("[PARSER] in len=%u crc=%u\r\n", len, g_plat.crc_check);
@@ -614,9 +615,7 @@ static int Cmd_msq(const ParsedCmd *cmd)
{ {
uint16_t val = 0; uint16_t val = 0;
dr_get_u16(cmd, 0, &val); dr_get_u16(cmd, 0, &val);
if (g_plat.log && g_log_enable) { if (g_plat.log) g_plat.log("[Cmd_msq] Power off val=%u\r\n", val);
g_plat.log("[Cmd_msq] Power off\r\n");
}
single_format_data(ble_bin_buffer, "rsq:", val); single_format_data(ble_bin_buffer, "rsq:", val);
dr_binary_tx_safe(ble_bin_buffer, 2); dr_binary_tx_safe(ble_bin_buffer, 2);
go_device_power_off = true; /* 메인 루프에서 전원 OFF 실행 예약 */ go_device_power_off = true; /* 메인 루프에서 전원 OFF 실행 예약 */
@@ -637,9 +636,7 @@ static int Cmd_msr(const ParsedCmd *cmd)
{ {
uint16_t val = 0; uint16_t val = 0;
dr_get_u16(cmd, 0, &val); dr_get_u16(cmd, 0, &val);
if (g_plat.log && g_log_enable) { if (g_plat.log) g_plat.log("[Cmd_msr] Bond delete + reset val=%u\r\n", val);
g_plat.log("[Cmd_msr] Bond delete + reset\r\n");
}
single_format_data(ble_bin_buffer, "rsr:", val); single_format_data(ble_bin_buffer, "rsr:", val);
dr_binary_tx_safe(ble_bin_buffer, 2); dr_binary_tx_safe(ble_bin_buffer, 2);
@@ -665,9 +662,7 @@ static int Cmd_mss(const ParsedCmd *cmd)
{ {
uint16_t val = 0; uint16_t val = 0;
dr_get_u16(cmd, 0, &val); dr_get_u16(cmd, 0, &val);
if (g_plat.log && g_log_enable) { if (g_plat.log) g_plat.log("[Cmd_mss] Device reset val=%u\r\n", val);
g_plat.log("[Cmd_mss] Device reset\r\n");
}
single_format_data(ble_bin_buffer, "rss:", val); single_format_data(ble_bin_buffer, "rss:", val);
dr_binary_tx_safe(ble_bin_buffer, 2); dr_binary_tx_safe(ble_bin_buffer, 2);
@@ -712,8 +707,7 @@ static int Cmd_mpa(const ParsedCmd *cmd)
g_plat.log("[Cmd_mpa] Piezo Activation\n"); g_plat.log("[Cmd_mpa] Piezo Activation\n");
} }
dr_piezo_power_on(); /* 피에조 보드 전원 ON (LDO 활성화) */ dr_piezo_system_init(); /* 내부에서 power_on + init 수행 */
dr_piezo_system_init(); /* 피에조 시스템 초기화 (DAC/MUX/ADC 설정) */
if (g_plat.tx_bin) { if (g_plat.tx_bin) {
single_format_data(ble_bin_buffer, "rpa:", 1); single_format_data(ble_bin_buffer, "rpa:", 1);
@@ -777,8 +771,8 @@ static int Cmd_mpc(const ParsedCmd *cmd)
(void)dr_get_u16(cmd, 1, &freq_option); /* word 1: 주파수 옵션 */ (void)dr_get_u16(cmd, 1, &freq_option); /* word 1: 주파수 옵션 */
(void)dr_get_u16(cmd, 2, &piezo_ch); /* word 2: 피에조 채널 */ (void)dr_get_u16(cmd, 2, &piezo_ch); /* word 2: 피에조 채널 */
/* 채널 범위 검증: 0~7 유효, 초과 시 0으로 리셋 */ /* 채널 범위 검증: 초과 시 0으로 리셋 */
if (piezo_ch > 7) piezo_ch = 0; if (piezo_ch >= MAA_NUM_CHANNELS) piezo_ch = 0;
if (g_plat.log && g_log_enable) { if (g_plat.log && g_log_enable) {
const char *freq_str = (freq_option == 0) ? "1.8MHz" : const char *freq_str = (freq_option == 0) ? "1.8MHz" :
@@ -851,6 +845,14 @@ static int Cmd_mec(const ParsedCmd *cmd)
uint16_t averaging = 1; /* 기본 1 (평균화 없음), 최대 1000 */ uint16_t averaging = 1; /* 기본 1 (평균화 없음), 최대 1000 */
uint16_t piezo_ch = 0; /* 기본 채널 0 (유효: 0~7) */ uint16_t piezo_ch = 0; /* 기본 채널 0 (유효: 0~7) */
/* 피에조 전원이 꺼져 있으면 자동으로 켜기 */
bool auto_powered = false;
if (!dr_piezo_is_power_on()) {
if (g_plat.log) g_plat.log("[Cmd_mec] TX/RX Sleep -> Active\r\n");
dr_piezo_system_init();
auto_powered = true;
}
/* 6개 파라미터 순서대로 추출 (데이터 부족 시 기본값 유지) */ /* 6개 파라미터 순서대로 추출 (데이터 부족 시 기본값 유지) */
(void)dr_get_u16(cmd, 0, &freq_option); /* word 0: 주파수 옵션 */ (void)dr_get_u16(cmd, 0, &freq_option); /* word 0: 주파수 옵션 */
(void)dr_get_u16(cmd, 1, &delay_us); /* word 1: 버스트→ADC 딜레이 */ (void)dr_get_u16(cmd, 1, &delay_us); /* word 1: 버스트→ADC 딜레이 */
@@ -863,8 +865,8 @@ static int Cmd_mec(const ParsedCmd *cmd)
if (averaging == 0) averaging = 1; if (averaging == 0) averaging = 1;
if (averaging > 1000) averaging = 1000; if (averaging > 1000) averaging = 1000;
/* 피에조 채널 범위 검증: 0~7 */ /* 피에조 채널 범위 검증 */
if (piezo_ch > 7) piezo_ch = 0; if (piezo_ch >= MAA_NUM_CHANNELS) piezo_ch = 0;
if (g_plat.log && g_log_enable) { if (g_plat.log && g_log_enable) {
const char *freq_str = (freq_option == 0) ? "1.8MHz" : const char *freq_str = (freq_option == 0) ? "1.8MHz" :
@@ -877,18 +879,10 @@ static int Cmd_mec(const ParsedCmd *cmd)
freq_option, freq_str, delay_us, num_samples, cycles, averaging, piezo_ch); freq_option, freq_str, delay_us, num_samples, cycles, averaging, piezo_ch);
} }
/* 통합 버스트+캡처+전송 함수 호출 /* 통합 버스트+캡처+전송 함수 호출 */
* 내부 동작 순서:
* 1. ADC 전원 ON
* 2. MUX로 피에조 채널 선택 (0~7)
* 3. 주파수별 버스트 펄스 발생 (freq_option에 따라)
* 4. delay_us 후 ADC 캡처 (averaging 횟수만큼 반복)
* 5. 캡처된 샘플 평균화 (노이즈 저감)
* 6. BLE 멀티패킷 전송 (패킷 간 타이밍 자동 관리)
*/
dr_adc_err_t err = dr_adc_burst_capture_transmit( dr_adc_err_t err = dr_adc_burst_capture_transmit(
(uint8_t)freq_option, delay_us, num_samples, (uint8_t)cycles, (uint8_t)freq_option, delay_us, num_samples, (uint8_t)cycles,
(uint16_t)averaging, (uint8_t)piezo_ch, ble_bin_buffer, 0); /* 0=raa 태그로 전송 */ (uint16_t)averaging, (uint8_t)piezo_ch, ble_bin_buffer, 0);
/* 에러 발생 시 에러 코드 + 요청 샘플 수 응답 */ /* 에러 발생 시 에러 코드 + 요청 샘플 수 응답 */
if (err != DR_ADC_OK) { if (err != DR_ADC_OK) {
@@ -899,6 +893,12 @@ static int Cmd_mec(const ParsedCmd *cmd)
g_plat.log("[Cmd_mec] result=%d\r\n", err); g_plat.log("[Cmd_mec] result=%d\r\n", err);
} }
/* 자동으로 켰으면 완료 후 전원 OFF */
if (auto_powered) {
if (g_plat.log) g_plat.log("[Cmd_mec] TX/RX Active -> Sleep\r\n");
dr_piezo_power_off();
}
return 1; return 1;
} }
@@ -981,7 +981,7 @@ static int Cmd_cmd(const ParsedCmd *cmd)
*/ */
#define MAA_FREQ_OPTION 1 /* 기본 2.1MHz */ #define MAA_FREQ_OPTION 1 /* 기본 2.1MHz */
#define MAA_DELAY_US 10 /* 버스트 후 10us 딜레이 */ #define MAA_DELAY_US 10 /* 버스트 후 10us 딜레이 */
#define MAA_NUM_SAMPLES 140 /* 140샘플 (약 25cm 거리) */ #define MAA_NUM_SAMPLES 100 /* 140샘플 (약 25cm 거리) */
#define MAA_CYCLES 7 /* 7사이클 버스트 */ #define MAA_CYCLES 7 /* 7사이클 버스트 */
#define MAA_AVERAGING 5 /* 5회 평균화 */ #define MAA_AVERAGING 5 /* 5회 평균화 */
@@ -1005,11 +1005,22 @@ static int Cmd_maa(const ParsedCmd *cmd)
return 1; return 1;
} }
/* 피에조 전원이 꺼져 있으면 자동으로 켜기 */
bool auto_powered = false;
bool pwr = dr_piezo_is_power_on();
if (g_plat.log) g_plat.log("[Cmd_maa] piezo_power=%u\r\n", pwr);
if (!pwr) {
if (g_plat.log) g_plat.log("[Cmd_maa] TX/RX Sleep -> Active\r\n");
dr_piezo_system_init();
auto_powered = true;
}
/*======================================================================= /*=======================================================================
* 비동기 4채널 캡처 시작 * 비동기 6채널 캡처 시작
* - maa_async_start(): CH0 캡처 실행 + 첫 헤더 패킷 전송 * - maa_async_start(): CH0 캡처 실행 + 첫 헤더 패킷 전송
* - 이후 패킷은 BLE_NUS_EVT_TX_RDY 콜백에서 자동 전송 * - 이후 패킷은 BLE_NUS_EVT_TX_RDY 콜백에서 자동 전송
* - 블로킹 딜레이 없음 → SoftDevice 이벤트 정상 처리 가능 * - 블로킹 딜레이 없음 → SoftDevice 이벤트 정상 처리 가능
* - auto_powered=true면 완료 후 자동 전원 OFF
*=======================================================================*/ *=======================================================================*/
err = maa_async_start( err = maa_async_start(
(uint8_t)MAA_FREQ_OPTION, (uint8_t)MAA_FREQ_OPTION,
@@ -1021,12 +1032,19 @@ static int Cmd_maa(const ParsedCmd *cmd)
); );
if (err != DR_ADC_OK) { if (err != DR_ADC_OK) {
/* 시작 실패 → maa_async_start 내부에서 에러 응답 이미 전송됨 */ /* 시작 실패 → 에러 응답 전송 (앱 타임아웃 방지) */
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);
if (auto_powered) dr_piezo_power_off();
return 1; return 1;
} }
/* 자동 전원 플래그 설정 → 비동기 완료 시 자동 OFF */
maa_async_set_auto_power(auto_powered);
/* 즉시 반환 → 비동기 전송 진행 중 */ /* 즉시 반환 → 비동기 전송 진행 중 */
/* 전체 완료 시 상태 머신이 "raa:" 응답 자동 전송 */ /* 전체 완료 시 상태 머신이 "raa:" 응답 + 자동 power off 처리 */
return 1; return 1;
} }
@@ -1119,10 +1137,7 @@ static int Cmd_mrs(const ParsedCmd *cmd)
/*============================================================================== /*==============================================================================
* 패스키 / 수명 카운터 (FDS)
*
* 패스키: BLE 페어링 시 사용하는 6자리 숫자 (예: "123456") * 패스키: BLE 페어링 시 사용하는 6자리 숫자 (예: "123456")
* 수명 카운터: 디바이스 사용 횟수 추적 (uint32, 상위16+하위16으로 분할 전송)
*============================================================================*/ *============================================================================*/
/* mpz? - BLE 패스키 FDS에 쓰기 /* mpz? - BLE 패스키 FDS에 쓰기
@@ -1154,6 +1169,8 @@ static int Cmd_mqz(const ParsedCmd *cmd)
(void)cmd; (void)cmd;
memcpy(m_static_passkey, m_config.static_passkey, 6); memcpy(m_static_passkey, m_config.static_passkey, 6);
if (g_plat.log) g_plat.log("[mqz] Passkey read: %.6s\r\n", m_static_passkey);
ascii_format_data(ble_bin_buffer, "rqz:", m_static_passkey, 6); ascii_format_data(ble_bin_buffer, "rqz:", m_static_passkey, 6);
dr_binary_tx_safe(ble_bin_buffer, 5); dr_binary_tx_safe(ble_bin_buffer, 5);
return 1; return 1;

View File

@@ -6,16 +6,36 @@
* @brief * @brief
******************************************************************************/ ******************************************************************************/
/*******************************************************************************
* [헤더 개요] 배터리 전압 및 압력센서 SAADC 측정 인터페이스
*
* nRF52840 SAADC를 이용한 배터리 전압 측정과 압력센서 2채널 측정의
* 외부 호출용 API를 선언한다.
*
* 주요 API:
* - battery_level_meas() : 배터리 전압 1회 측정 (AIN2)
* - pressure_all_level_meas() : 압력센서 2채널 1회 측정 (AIN7, AIN4)
* - battery_timer_init/start/stop() : 5초 주기 배터리 모니터링 타이머 제어
*
* LOW_BATTERY_VOLTAGE(3100mV) 이하가 10회 연속 감지되면 자동 전원 OFF
******************************************************************************/
#ifndef _BATTERY_SAADC_H_ #ifndef _BATTERY_SAADC_H_
#define _BATTERY_SAADC_H_ #define _BATTERY_SAADC_H_
/* 저전압 판정 임계값 (mV) — 이 값 이하가 10회 연속이면 자동 전원 OFF */
#define LOW_BATTERY_VOLTAGE 3100 /* Low Battery 임계값 */ #define LOW_BATTERY_VOLTAGE 3100 /* Low Battery 임계값 */
/** @brief 배터리 전압 1회 측정 시작 (비동기, 결과는 콜백에서 처리) */
void battery_level_meas(void); void battery_level_meas(void);
/** @brief 압력센서 2채널(AIN7, AIN4) 1회 측정 시작 (비동기) */
void pressure_all_level_meas(void); void pressure_all_level_meas(void);
/** @brief 배터리 모니터링 5초 반복 타이머 시작 */
void battery_timer_start(void); void battery_timer_start(void);
/** @brief 배터리 모니터링 타이머 정지 */
void battery_timer_stop(void); void battery_timer_stop(void);
/** @brief 배터리 모니터링 타이머 초기화 (앱 시작 시 1회 호출) */
void battery_timer_init(void); void battery_timer_init(void);
#endif //_BATTERY_SAADC_H_ #endif //_BATTERY_SAADC_H_

View File

@@ -1,13 +1,32 @@
// file: debug_print.h // file: debug_print.h
/*******************************************************************************
* [한국어 설명] 디버그 출력 매크로 (조건부 컴파일)
*
* SEGGER RTT(Real Time Transfer)를 이용한 디버그 출력 매크로.
* J-Link 디버거를 통해 실시간으로 로그를 PC에 전송한다.
* UART를 사용하지 않으므로 시스템 타이밍에 미치는 영향이 적다.
*
* === 조건부 컴파일 ===
* ENABLE_PRINTF = 1: DBG_PRINTF가 SEGGER_RTT_printf(채널0)로 치환됨
* -> 실제 로그 출력 (디버깅 시 사용)
* ENABLE_PRINTF = 0: DBG_PRINTF가 빈 매크로로 치환됨
* -> 코드에서 완전히 제거 (릴리스 빌드 시 사용)
*
* === 사용법 ===
* DBG_PRINTF("값: %d\r\n", value); // printf와 동일한 포맷 문자열
* 출력은 SEGGER RTT Viewer 또는 J-Link RTT Client에서 확인.
******************************************************************************/
#ifndef DEBUG_PRINT_H #ifndef DEBUG_PRINT_H
#define DEBUG_PRINT_H #define DEBUG_PRINT_H
#define ENABLE_PRINTF 1 // Set to 0 to disable globally #define ENABLE_PRINTF 1 /* 1=디버그 출력 활성화, 0=전역 비활성화 */
#if ENABLE_PRINTF #if ENABLE_PRINTF
#include "SEGGER_RTT.h" #include "SEGGER_RTT.h"
/* SEGGER RTT 채널 0으로 포맷 문자열 출력 */
#define DBG_PRINTF(...) SEGGER_RTT_printf(0, __VA_ARGS__) #define DBG_PRINTF(...) SEGGER_RTT_printf(0, __VA_ARGS__)
#else #else
/* 빈 매크로: 컴파일러가 호출 코드를 완전히 제거 */
#define DBG_PRINTF(...) // Do nothing #define DBG_PRINTF(...) // Do nothing
#endif #endif

View File

@@ -2,6 +2,36 @@
TEST medi50 Dec 23 TEST medi50 Dec 23
******************************************************************************/ ******************************************************************************/
/**
* @file fstorage.c
* @brief FDS(Flash Data Storage) 기반 설정 저장 모듈
*
* 외부 EEPROM을 대체하여 nRF52840 내장 플래시에 장치 설정을 저장/로드한다.
*
* [레코드 관리]
* - CONFIG_FILE = 0x8010, CONFIG_REC_KEY = 0x7010 으로 단일 레코드를 관리한다.
*
* [config_data_t 구조체 필드]
* - magic(4B) : 포맷 확인용 매직 넘버 (MAGIC = 0x20231226)
* - hw_no(12B) : 하드웨어 번호 (BLE 명령으로 설정)
* - serial_no(12B) : 시리얼 번호 (기본값: "VB026030000")
* - passkey(6B) : BLE 페어링용 정적 패스키
* - bond_delete(1B) : 본딩 데이터 삭제 플래그
* - reset_status(1B) : 리셋 상태 값
* - pd_adc_cnt : 포토다이오드 ADC 측정 횟수
* - pd_delay_us : 포토다이오드 측정 간 지연 시간(us)
* - life_cycle : 장치 사용 횟수
*
* [매직 넘버 검증]
* - 플래시에서 로드한 데이터의 magic 값이 0x20231226과 일치하는지 확인하여
* 유효한 설정인지 판별한다. 불일치 시 기본값으로 초기화한다.
*
* [FDS 이벤트 후처리]
* - FDS 쓰기/업데이트 완료 이벤트 수신 후, 대기 중인 후처리를 수행한다:
* 전원 OFF (go_device_power_off), 슬립 진입 (go_sleep_mode_enter),
* 시스템 리셋 (go_NVIC_SystemReset)
*/
#include "sdk_config.h" #include "sdk_config.h"
#include <string.h> #include <string.h>
@@ -27,23 +57,29 @@
#include "debug_print.h" #include "debug_print.h"
/* File ID and Key used for the configuration record. */ /* FDS 레코드 식별자: 파일 ID와 레코드 키로 단일 설정 레코드를 관리 */
#define CONFIG_FILE (0x8010) #define CONFIG_FILE (0x8010)
#define CONFIG_REC_KEY (0x7010) #define CONFIG_REC_KEY (0x7010)
/* 매직 넘버: 플래시에 저장된 데이터가 유효한 설정인지 판별하는 데 사용 */
#define CONFIG_MAGIC_NUMBER_VALUE (0x20231226) #define CONFIG_MAGIC_NUMBER_VALUE (0x20231226)
/* 전역 설정 데이터 구조체 인스턴스 */
config_data_t m_config; config_data_t m_config;
extern bool go_device_power_off; /* FDS 쓰기 완료 후 수행할 후처리 플래그 (main.c에서 선언) */
extern bool go_sleep_mode_enter; extern bool go_device_power_off; /* 전원 OFF 요청 */
extern bool go_NVIC_SystemReset; extern bool go_sleep_mode_enter; /* 슬립 모드 진입 요청 */
extern bool go_NVIC_SystemReset; /* 시스템 리셋 요청 */
/* Flag to check fds initialization. */ /* FDS 초기화 완료 여부 플래그 (fds_evt_handler에서 true로 설정) */
static bool volatile m_fds_initialized; static bool volatile m_fds_initialized;
/* FDS 쓰기 진행 중 플래그: true이면 쓰기 완료 대기 중 */
bool fds_flag_write = false; bool fds_flag_write = false;
/* A record containing dummy configuration data. */
/* FDS에 기록할 레코드 템플릿 (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,
@@ -54,9 +90,16 @@ static fds_record_t const m_dummy_record =
}; };
int8_t reset_status_dflt = 99; /* 기본 설정값 상수 */
uint8_t static_passkey_dflt[6] = "123456"; int8_t reset_status_dflt = 99; /* 리셋 상태 기본값 */
uint8_t static_passkey_dflt[6] = "123456"; /* BLE 패스키 기본값 */
/**
* @brief 기본 설정값 초기화
*
* m_config 구조체의 각 필드를 공장 초기값으로 설정한다.
* 플래시에 유효한 설정이 없거나 매직 넘버가 불일치할 때 호출된다.
*/
void fds_default_value_set(void) void fds_default_value_set(void)
{ {
/* HW Number - empty (set via BLE command) */ /* HW Number - empty (set via BLE command) */
@@ -84,8 +127,19 @@ void fds_default_value_set(void)
} }
/* 마지막 FDS 이벤트 ID 저장 (디버깅용) */
static volatile uint8_t fds_last_evt = 0xFF; static volatile uint8_t fds_last_evt = 0xFF;
/**
* @brief FDS 이벤트 콜백 핸들러
*
* FDS 내부에서 비동기 작업이 완료될 때 호출된다.
* - FDS_EVT_INIT : FDS 초기화 완료 → m_fds_initialized 플래그 설정
* - FDS_EVT_WRITE : 새 레코드 쓰기 완료 → fds_flag_write 해제
* - FDS_EVT_UPDATE : 레코드 업데이트 완료 → fds_flag_write 해제 후
* 대기 중인 전원 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;
@@ -140,7 +194,13 @@ static void fds_evt_handler( fds_evt_t const *p_evt )
} }
/**@brief Wait for fds to initialize. */ /**
* @brief FDS 초기화 완료 대기
*
* m_fds_initialized 플래그가 true가 될 때까지 대기한다.
* 최대 3초(3000ms) 타임아웃이 설정되어 있으며,
* 타임아웃 시 에러 로그를 출력하고 반환한다.
*/
static void wait_for_fds_ready( void ) static void wait_for_fds_ready( void )
{ {
uint32_t timeout = 0; uint32_t timeout = 0;
@@ -157,6 +217,20 @@ static void wait_for_fds_ready( void )
} }
/**
* @brief FDS에서 설정 로드
*
* 플래시에서 CONFIG_FILE/CONFIG_REC_KEY 레코드를 검색하여 m_config에 로드한다.
*
* 동작 흐름:
* 1. fds_record_find()로 레코드 검색 (실패 시 최대 10회 재시도, 100ms 간격)
* 2. 레코드 발견 시:
* - fds_record_open()으로 열기 (CRC 에러 시 삭제 후 기본값으로 재생성)
* - 데이터를 m_config로 복사
* - 매직 넘버 불일치 시 기존 레코드 삭제 → 기본값 설정 → 재기록
* 3. 레코드 미발견 시:
* - 기본값으로 새 레코드 생성 후 다시 로드
*/
void config_load( void ) void config_load( void )
{ {
ret_code_t rc; ret_code_t rc;
@@ -264,6 +338,21 @@ void config_load( void )
} }
/**
* @brief 현재 설정을 FDS에 저장
*
* m_config의 내용을 플래시에 기록한다.
*
* 동작 흐름:
* 1. 이전 FDS 쓰기 작업이 진행 중이면 최대 3초 대기
* 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;
@@ -334,11 +423,25 @@ void config_save( void )
} }
/**
* @brief config_load()의 래퍼 함수
*
* 외부 모듈에서 설정 로드를 요청할 때 사용한다.
*/
void fs_set_value(void) void fs_set_value(void)
{ {
config_load(); config_load();
} }
/**
* @brief FDS 초기화
*
* 부팅 시 호출되어 FDS 모듈을 초기화한다.
* 1. fds_register()로 이벤트 핸들러 등록
* 2. fds_init()로 FDS 초기화 시작
* 3. wait_for_fds_ready()로 초기화 완료 대기 (최대 3초)
* 4. fds_stat()로 플래시 상태 확인
*/
void fs_storage_init(void) void fs_storage_init(void)
{ {
ret_code_t rc; ret_code_t rc;

View File

@@ -3,7 +3,29 @@
* @author CandyPops Co. * @author CandyPops Co.
* @version V1.0.0 * @version V1.0.0
* @date 2022-09-05 * @date 2022-09-05
* @brief * @brief FDS(Flash Data Storage) 기반 설정 저장 모듈 인터페이스
*******************************************************************************
*
* [헤더 개요]
* nRF52840 내장 플래시에 디바이스 설정을 저장/로드하는 FDS 모듈의 공용 API.
* 외부 EEPROM을 대체하며, SoftDevice와 공존하여 플래시를 안전하게 관리한다.
*
* [config_data_t 구조체] (45바이트, 패킹됨)
* magic_number(4B): 포맷 확인용 (0x20231226)
* hw_no(12B): 하드웨어 번호
* serial_no(12B): 시리얼 번호 (BLE 디바이스 이름으로도 사용)
* static_passkey(6B): BLE 페어링 패스키 (숫자 6자리)
* bond_data_delete(1B): 본딩 삭제 플래그
* reset_status(1B): 리셋 상태 코드
* pd_adc_cnt(1B): ADC 샘플링 횟수
* pd_delay_us(2B): PD 안정화 딜레이 (마이크로초)
* life_cycle(4B): 디바이스 사용 횟수
*
* [주요 API]
* fs_storage_init(): FDS 초기화 (부트 시 1회)
* config_load(): FDS에서 설정 로드 (없으면 기본값 생성)
* config_save(): 현재 설정을 FDS에 저장
*
******************************************************************************/ ******************************************************************************/
#ifndef IHP_FSTORAGE_H_ #ifndef IHP_FSTORAGE_H_

View File

@@ -1,6 +1,28 @@
/******************************************************************************* /*******************************************************************************
* @file i2c_manager.c * @file i2c_manager.c
* @brief Reliable HW↔SW I2C Switching Logic (with Mode Set Logging) * @brief Reliable HW↔SW I2C Switching Logic (with Mode Set Logging)
*******************************************************************************
*
* [모듈 개요]
* I2C 버스의 HW(하드웨어 TWI) / SW(소프트웨어 비트뱅) 모드 전환을 관리하는 모듈.
*
* - HW I2C: nRF52840 내장 TWI 하드웨어 주변장치를 사용 (ICM42670P IMU 센서 통신용, 400kHz)
* - SW I2C: GPIO 비트뱅 방식의 소프트웨어 I2C (현재 사용하지 않는 레거시 코드)
*
* [핀 설정]
* SCL = P1.14 (ICM42670_I2C_SCL_PIN)
* SDA = P1.15 (ICM42670_I2C_SDA_PIN)
*
* [주요 함수]
* hw_i2c_init_once() : SW→HW 전환 또는 HW 초기화 (이미 HW 모드면 중복 초기화 방지)
* 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"
@@ -12,40 +34,52 @@
#include "boards.h" #include "boards.h"
#include "system_interface.h" #include "system_interface.h"
bool HW_I2C_FRQ = true; /* 현재 I2C 모드 상태 플래그 (true = 해당 모드 활성화) */
bool SW_I2C_FRQ = false; bool HW_I2C_FRQ = true; /* HW TWI 모드 활성 여부 (기본값: true, 초기 상태는 HW) */
bool SW_I2C_FRQ = false; /* SW 비트뱅 모드 활성 여부 */
/* TWI 인스턴스 번호 (nRF52840은 TWI0, TWI1 두 개 지원) */
#define TWI_INSTANCE 0 #define TWI_INSTANCE 0
/* TWI (I2C) 하드웨어 인스턴스 : IMU 드라이버에서 사용 - jhChun 26.03.16 */ /* 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 */ /* TWI (I2C) 해제 - jhChun 26.03.16 */
/* TWI 하드웨어를 비활성화하고 초기화 해제하여 GPIO 핀을 반환한다 */
static void twi_uninitialize(void){ static void twi_uninitialize(void){
nrfx_twi_disable(&m_twi); nrfx_twi_disable(&m_twi); /* TWI 주변장치 비활성화 */
nrfx_twi_uninit(&m_twi); nrfx_twi_uninit(&m_twi); /* TWI 초기화 해제 (핀 리소스 반환) */
} }
/* TWI (I2C) 하드웨어 초기화 (SCL/SDA핀, 400kHz) - jhChun 26.03.16 */ /* TWI (I2C) 하드웨어 초기화 (SCL/SDA핀, 400kHz) - jhChun 26.03.16 */
/* 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 = ICM42670_I2C_SCL_PIN, /* SCL 핀 (P1.14) */
.sda = ICM42670_I2C_SDA_PIN, .sda = ICM42670_I2C_SDA_PIN, /* SDA 핀 (P1.15) */
.frequency = NRF_TWI_FREQ_400K, .frequency = NRF_TWI_FREQ_400K, /* 400kHz Fast Mode */
.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); nrfx_twi_enable(&m_twi); /* TWI 주변장치 활성화 → I2C 통신 가능 */
} }
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* HW (TWI) 초기화 */ /* HW (TWI) 초기화 */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/*
* HW I2C 모드로 전환하거나 초기화하는 함수.
* - SW 모드가 활성화되어 있으면 SW 플래그를 해제하고 HW로 전환
* - 이미 HW 모드이면 중복 초기화를 방지하여 바로 리턴
* - 최초 HW 초기화 시 twi_initialize()를 호출하여 TWI 하드웨어 설정
*/
void hw_i2c_init_once(void) void hw_i2c_init_once(void)
{ {
// SW 모드일 경우 강제 해제 후 HW 전환 // SW 모드일 경우 강제 해제 후 HW 전환
@@ -55,9 +89,10 @@ void hw_i2c_init_once(void)
// SW 리소스 해제 (필요 시 추가) // SW 리소스 해제 (필요 시 추가)
SW_I2C_FRQ = false; SW_I2C_FRQ = false;
nrf_delay_ms(2); nrf_delay_ms(2); /* 모드 전환 안정화 대기 (2ms) */
} }
/* 이미 HW 모드가 활성화되어 있으면 중복 초기화 방지를 위해 즉시 리턴 */
// 이미 HW면 스킵 // 이미 HW면 스킵
if (HW_I2C_FRQ) if (HW_I2C_FRQ)
{ {
@@ -65,10 +100,12 @@ void hw_i2c_init_once(void)
return; return;
} }
/* HW TWI 하드웨어 초기화 수행 (SCL/SDA 핀 설정, 400kHz, 활성화) */
// 실제 HW 초기화 // 실제 HW 초기화
twi_initialize(); twi_initialize();
nrf_delay_ms(2); nrf_delay_ms(2); /* 초기화 후 안정화 대기 (2ms) */
/* 모드 플래그 갱신: HW 활성, SW 비활성 */
HW_I2C_FRQ = true; HW_I2C_FRQ = true;
SW_I2C_FRQ = false; SW_I2C_FRQ = false;
@@ -78,6 +115,12 @@ void hw_i2c_init_once(void)
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* SW (Port Bang-Bang) 초기화 */ /* SW (Port Bang-Bang) 초기화 */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/*
* SW I2C(비트뱅) 모드로 전환하는 함수. (현재 레거시, 사용하지 않음)
* - 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 전환 // HW 모드일 경우 강제 해제 후 SW 전환
@@ -85,9 +128,9 @@ void sw_i2c_init_once(void)
{ {
//DBG_PRINTF("[I2C]HW→SW\r\n"); //DBG_PRINTF("[I2C]HW→SW\r\n");
nrfx_twi_disable(&m_twi); nrfx_twi_disable(&m_twi); /* TWI 비활성화 */
nrfx_twi_uninit(&m_twi); nrfx_twi_uninit(&m_twi); /* TWI 초기화 해제 */
nrf_delay_ms(2); nrf_delay_ms(2); /* 모드 전환 안정화 대기 (2ms) */
HW_I2C_FRQ = false; HW_I2C_FRQ = false;
} }
@@ -99,10 +142,12 @@ void sw_i2c_init_once(void)
return; return;
} }
/* TWI 라인 완전 해제 후 SW 비트뱅 모드 진입 */
// 실제 SW 초기화 // 실제 SW 초기화
twi_uninitialize(); // TWI 라인 해제 twi_uninitialize(); // TWI 라인 해제
nrf_delay_ms(1); nrf_delay_ms(1); /* 해제 후 안정화 대기 (1ms) */
/* 모드 플래그 갱신: SW 활성, HW 비활성 */
SW_I2C_FRQ = true; SW_I2C_FRQ = true;
HW_I2C_FRQ = false; HW_I2C_FRQ = false;
@@ -112,9 +157,14 @@ void sw_i2c_init_once(void)
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* 전체 리셋 */ /* 전체 리셋 */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/*
* 모든 I2C 모드 플래그를 초기화하는 함수.
* HW/SW 모두 비활성 상태로 만들어, 다음 init 호출 시 강제로 재초기화되도록 한다.
* 주로 시스템 리셋이나 에러 복구 시 사용.
*/
void i2c_reset_state(void) void i2c_reset_state(void)
{ {
HW_I2C_FRQ = false; HW_I2C_FRQ = false; /* HW 모드 플래그 초기화 */
SW_I2C_FRQ = false; SW_I2C_FRQ = false; /* SW 모드 플래그 초기화 */
DBG_PRINTF("Flags reset\r\n"); DBG_PRINTF("Flags reset\r\n");
} }

View File

@@ -1,6 +1,16 @@
/******************************************************************************* /*******************************************************************************
* @file i2c_manager.h * @file i2c_manager.h
* @brief Common header for HW/SW I2C mutex control * @brief Common header for HW/SW I2C mutex control
*******************************************************************************
*
* [헤더 개요]
* I2C 버스 HW/SW 모드 전환 관리자의 공용 인터페이스 헤더.
*
* - HW_I2C_FRQ: HW TWI 모드 활성 여부 (true = HW I2C 사용 중)
* - SW_I2C_FRQ: SW 비트뱅 모드 활성 여부 (true = SW I2C 사용 중)
*
* 두 플래그는 상호 배제적으로 동작하며, 동시에 true가 되지 않도록 관리된다.
*
******************************************************************************/ ******************************************************************************/
#ifndef __I2C_MANAGER_H__ #ifndef __I2C_MANAGER_H__
#define __I2C_MANAGER_H__ #define __I2C_MANAGER_H__
@@ -8,11 +18,33 @@
#include <stdbool.h> #include <stdbool.h>
#include "app_error.h" #include "app_error.h"
extern bool HW_I2C_FRQ; /* I2C 모드 상태 플래그 (외부 참조용) */
extern bool SW_I2C_FRQ; extern bool HW_I2C_FRQ; /* HW TWI 모드 활성 여부 */
extern bool SW_I2C_FRQ; /* SW 비트뱅 모드 활성 여부 */
/**
* @brief HW I2C(TWI) 모드 초기화 (중복 초기화 방지)
*
* SW 모드가 활성화되어 있으면 해제 후 HW로 전환한다.
* 이미 HW 모드이면 아무 동작 없이 리턴한다.
* ICM42670P IMU 센서 통신 전에 호출하여 HW I2C를 준비한다.
*/
void hw_i2c_init_once(void); void hw_i2c_init_once(void);
/**
* @brief SW I2C(비트뱅) 모드 초기화 (레거시, 현재 미사용)
*
* HW 모드가 활성화되어 있으면 TWI를 해제한 후 SW로 전환한다.
* 이미 SW 모드이면 아무 동작 없이 리턴한다.
*/
void sw_i2c_init_once(void); void sw_i2c_init_once(void);
/**
* @brief I2C 모드 플래그 전체 초기화
*
* HW_I2C_FRQ, SW_I2C_FRQ를 모두 false로 리셋한다.
* 다음 init 호출 시 강제로 재초기화가 수행된다.
*/
void i2c_reset_state(void); void i2c_reset_state(void);
#endif #endif

View File

@@ -6,6 +6,19 @@
* @brief * @brief
******************************************************************************/ ******************************************************************************/
/*******************************************************************************
* [헤더 개요] ICM42670P IMU 드라이버 상위 레이어 선언
*
* IMU 센서의 초기화, 설정, 데이터 읽기를 위한 함수 프로토타입과
* 동작 모드 설정 매크로를 정의한다.
*
* 주요 설정 매크로:
* SERIF_TYPE - 통신 인터페이스 (UI_I2C)
* USE_LOW_NOISE_MODE - 1:저잡음(800Hz), 0:저전력(100Hz)
* USE_HIGH_RES_MODE - 1:20비트 고해상도, 0:16비트 표준
* USE_FIFO - 1:FIFO 사용, 0:레지스터 직접 읽기
******************************************************************************/
#ifndef _APP_RAW_H_ #ifndef _APP_RAW_H_
#define _APP_RAW_H_ #define _APP_RAW_H_
#include "sdk_config.h" #include "sdk_config.h"
@@ -16,64 +29,71 @@
#include "inv_imu_driver.h" #include "inv_imu_driver.h"
/*** Example configuration ***/ /*** 설정 매크로 ***/
/* /*
* Select communication link between SmartMotion and IMU * MCU와 IMU 간 통신 인터페이스 선택
* UI_I2C: I2C 통신 사용 (기본)
*/ */
#define SERIF_TYPE UI_I2C #define SERIF_TYPE UI_I2C
/* /*
* Set power mode flag * 전원 모드 설정
* Set this flag to run example in low-noise mode. * 1: 저잡음 모드 — 800Hz ODR, 높은 정밀도, 높은 전력 소모
* Reset this flag to run example in low-power mode. * 0: 저전력 모드 — 100Hz ODR, 낮은 전력 소모
* Note: low-noise mode is not available with sensor data frequencies less than 12.5Hz. * 주의: 12.5Hz 미만 ODR에서는 저잡음 모드 사용 불가
*/ */
#define USE_LOW_NOISE_MODE 1 #define USE_LOW_NOISE_MODE 1
/* /*
* Select Fifo resolution Mode (default is low resolution mode) * FIFO 해상도 모드 선택
* Low resolution mode: 16 bits data format * 0: 저해상도 — 16비트 데이터 (기본)
* High resolution mode: 20 bits data format * 1: 고해상도 — 20비트 데이터 (FSR이 16g/2000dps로 강제 고정됨)
* Warning: Enabling High Res mode will force FSR to 16g and 2000dps
*/ */
#define USE_HIGH_RES_MODE 0 #define USE_HIGH_RES_MODE 0
/* /*
* Select to use FIFO or to read data from registers * 데이터 읽기 방식 선택
* 0: 레지스터 직접 읽기 (현재 사용 중)
* 1: FIFO에서 읽기
*/ */
#define USE_FIFO 0 #define USE_FIFO 0
/** /**
* \brief This function is in charge of reseting and initializing IMU device. It should * \brief IMU 디바이스를 리셋하고 초기화한다. WHOAMI 확인 포함.
* be successfully executed before any access to IMU device. * 다른 IMU 접근 함수 호출 전에 반드시 성공적으로 실행되어야 한다.
* *
* \return 0 on success, negative value on error. * \return 0=성공, 음수=에러
*/ */
int setup_imu_device(struct inv_imu_serif *icm_serif); int setup_imu_device(struct inv_imu_serif *icm_serif);
/** /**
* \brief This function configures the device in order to output gyro and accelerometer. * \brief 자이로 및 가속도계 출력을 위한 디바이스 설정을 수행한다.
* \return 0 on success, negative value on error. * FSR, ODR, 전원 모드, FIFO 설정 등을 적용한다.
* \return 0=성공, 음수=에러
*/ */
int configure_imu_device(void); int configure_imu_device(void);
/** /**
* \brief This function extracts data from the IMU FIFO. * \brief FIFO 또는 레지스터에서 IMU 데이터를 추출한다.
* \return 0 on success, negative value on error. * 내부적으로 imu_callback()이 호출되어 데이터를 처리한다.
* \return 0=성공, 음수=에러
*/ */
int get_imu_data(void); int get_imu_data(void);
/** /**
* \brief This function is the custom handling packet function. * \brief 센서 데이터 수신 콜백. 마운팅 매트릭스 적용 후
* \param[in] event structure containing sensor data from one packet * info4/BLE/UART 모드에 따라 데이터를 출력한다.
* \param[in] event 하나의 센서 데이터 패킷을 담은 구조체
*/ */
void imu_callback(inv_imu_sensor_event_t *event); void imu_callback(inv_imu_sensor_event_t *event);
/** /**
* \brief Direct IMU register read — bypasses DRDY, sends rsp: via BLE. * \brief 드라이버 API를 우회한 직접 I2C 레지스터 읽기.
* \return 0 on success, negative value on error. * DRDY 인터럽트 없이 즉시 센서 데이터를 읽어 BLE로 전송한다.
* 읽기 후 IMU를 슬립 모드로 전환하여 전력을 절감한다.
* \return 0=성공, 음수=에러
*/ */
int imu_read_direct(void); int imu_read_direct(void);

View File

@@ -6,13 +6,26 @@
* @brief * @brief
******************************************************************************/ ******************************************************************************/
/*******************************************************************************
* [헤더 개요] ICM42670P 메인 초기화/폴링 루프 선언
*
* ICM42670P IMU 센서의 전체 초기화 및 메인 루프 함수를 선언한다.
* - icm42670_init() : 전체 초기화 (MCU설정 → IMU초기화 → 센서설정 → 인터럽트 활성화)
* - icm42670_main() : 메인 폴링 루프 (INT1 인터럽트 확인 → 데이터 읽기)
* - icm42670_uninit() : 해제 (프로토타입만 선언, 구현은 별도)
******************************************************************************/
#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 드라이버 초기화 → 센서 설정 → 인터럽트 활성화 */
int icm42670_init(void); int icm42670_init(void);
/* ICM42670P 메인 폴링 루프 — INT1 인터럽트 플래그 확인 후 센서 데이터 읽기 */
void icm42670_main(void); void icm42670_main(void);
/* ICM42670P 해제 (프로토타입 선언) */
int icm42670_uninit(void); int icm42670_uninit(void);
#endif /* !_APP_RAW_MAIN_H_ */ #endif /* !_APP_RAW_MAIN_H_ */

View File

@@ -6,6 +6,28 @@
* @brief * @brief
******************************************************************************/ ******************************************************************************/
/*******************************************************************************
* [모듈 개요] ICM42670P IMU 센서 I2C 통신 인터페이스
*
* nRF52840의 TWI(I2C) 하드웨어를 사용하여 ICM42670P IMU 센서와 통신하는
* 저수준 인터페이스 모듈이다.
*
* - I2C 슬레이브 주소: 0x68 (ICM42670P 기본 주소)
* - I2C 핀 설정: SCL=P1.14, SDA=P1.15 (system_interface.h에서 정의)
* - TWI 인스턴스: NRFX_TWI_INSTANCE(0) 사용
* - 통신 속도: 100kHz (NRF_TWI_FREQ_100K)
*
* 주요 함수 흐름:
* inv_io_hal_init() → I2C 또는 SPI 초기화 (현재 I2C만 구현)
* inv_io_hal_read_reg() → 레지스터 읽기 (TX로 주소 전송 → RX로 데이터 수신)
* inv_io_hal_write_reg() → 레지스터 쓰기 (주소+데이터를 한번에 TX)
*
* 에러 처리: 모든 I2C 읽기/쓰기에서 실패 시 1회 재시도 후 에러 출력
*
* 참고: SPI4 인터페이스 코드 경로도 존재하지만 현재 미구현 상태이며,
* 실제 운용에서는 I2C(UI_I2C)만 사용한다.
******************************************************************************/
/* board driver */ /* board driver */
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@@ -21,18 +43,31 @@
#include "system_interface.h" #include "system_interface.h"
#include "nrf_delay.h" #include "nrf_delay.h"
/* I2C number and slave address for INV device */ /* ICM42670P I2C 슬레이브 주소 및 직렬 쓰기 최대 바이트 수 */
#define ICM_I2C_ADDR 0x68 #define ICM_I2C_ADDR 0x68
#define INV_MAX_SERIAL_WRITE 16 #define INV_MAX_SERIAL_WRITE 16
/* TWI instance. */ /* TWI(I2C) 인스턴스 생성 — system_interface.h의 ICM42670_I2C_INSTANCE(0)을 사용 */
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()
* I2C 버스를 비활성화하고 TWI 인스턴스를 해제한다.
* 슬립 모드 진입 전 또는 재초기화 전에 호출된다.
*/
void inv_i2c_master_uninitialize(void){ void inv_i2c_master_uninitialize(void){
nrfx_twi_disable(&m_twi_icm42670); nrfx_twi_disable(&m_twi_icm42670);
nrfx_twi_uninit(&m_twi_icm42670); nrfx_twi_uninit(&m_twi_icm42670);
} }
/*
* inv_i2c_master_initialize()
* nRF52840 TWI 하드웨어를 초기화하고 활성화한다.
* - SCL: P1.14, SDA: P1.15
* - 속도: 100kHz
* - 인터럽트 우선순위: 최고 (APP_IRQ_PRIORITY_HIGH)
* - 이벤트 핸들러 없음 (블로킹 모드로 동작)
*/
void inv_i2c_master_initialize(void){ void inv_i2c_master_initialize(void){
ret_code_t err_code; ret_code_t err_code;
@@ -43,13 +78,20 @@ void inv_i2c_master_initialize(void){
.interrupt_priority = APP_IRQ_PRIORITY_HIGH, .interrupt_priority = APP_IRQ_PRIORITY_HIGH,
}; };
/* TWI 드라이버 초기화 (이벤트 핸들러=NULL → 블로킹 모드) */
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 가능 */
nrfx_twi_enable(&m_twi_icm42670); nrfx_twi_enable(&m_twi_icm42670);
} }
/*
* icm42670_twi_tx()
* I2C 전송 래퍼 함수. nrfx_twi_tx를 호출하여 데이터를 송신한다.
* no_stop=true이면 STOP 컨디션을 보내지 않음 (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,
@@ -61,6 +103,10 @@ uint32_t icm42670_twi_tx( uint8_t device_id,
} }
/*
* icm42670_twi_rx()
* I2C 수신 래퍼 함수. nrfx_twi_rx를 호출하여 데이터를 수신한다.
*/
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)
@@ -71,21 +117,35 @@ uint32_t icm42670_twi_rx( uint8_t device_id,
} }
/*
* inv_i2c_master_read_register()
* ICM42670P의 특정 레지스터에서 데이터를 읽는다.
*
* 동작 순서:
* 1) TX: 레지스터 주소 1바이트 전송 (no_stop=true → Repeated START 준비)
* 2) RX: 지정된 길이만큼 데이터 수신
*
* 에러 처리: TX, RX 각각 실패 시 1회 재시도한다.
*/
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 사용) */
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회 재시도 */
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단계: 해당 레지스터에서 데이터 수신 */
ret = icm42670_twi_rx(Address, RegisterValue, RegisterLen); ret = icm42670_twi_rx(Address, RegisterValue, RegisterLen);
if(ret != NRF_SUCCESS) { if(ret != NRF_SUCCESS) {
/* 실패 시 1회 재시도 */
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");
@@ -95,14 +155,28 @@ static unsigned long inv_i2c_master_read_register(unsigned char Address, unsigne
return ret; return ret;
} }
/*
* inv_i2c_master_write_register()
* ICM42670P의 특정 레지스터에 데이터를 쓴다.
*
* 동작 순서:
* 1) 버퍼[0]에 레지스터 주소, 버퍼[1~N]에 쓸 데이터를 배치
* 2) TX: 주소+데이터를 한번에 전송 (no_stop=false → STOP 컨디션 포함)
*
* 에러 처리: 실패 시 1회 재시도한다.
*/
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]; /* Addr + data */ uint8_t buffer[1 + INV_MAX_SERIAL_WRITE]; /* 레지스터 주소(1) + 데이터(최대 16바이트) */
/* 버퍼 구성: [레지스터 주소][데이터 바이트들] */
buffer[0] = (uint8_t)RegisterAddr; buffer[0] = (uint8_t)RegisterAddr;
memcpy(buffer+1, RegisterValue, RegisterLen); memcpy(buffer+1, RegisterValue, RegisterLen);
/* 주소+데이터를 한번에 전송 */
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회 재시도 */
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");
@@ -114,7 +188,12 @@ static unsigned long inv_i2c_master_write_register(unsigned char Address, unsign
/*
* inv_io_hal_init()
* IMU 드라이버가 사용하는 시리얼 인터페이스(I2C 또는 SPI)를 초기화한다.
* serif->serif_type에 따라 분기하며, 현재는 I2C만 구현되어 있다.
* 반환값: 0=성공, -1=지원하지 않는 인터페이스 타입
*/
int inv_io_hal_init(struct inv_imu_serif *serif) int inv_io_hal_init(struct inv_imu_serif *serif)
{ {
@@ -122,6 +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만 사용) */
break; break;
} }
@@ -137,6 +217,11 @@ int inv_io_hal_init(struct inv_imu_serif *serif)
} }
/*
* inv_io_hal_read_reg()
* IMU 드라이버 콜백: 지정된 레지스터에서 데이터를 읽는다.
* 시리얼 타입에 따라 I2C 또는 SPI 읽기를 수행한다.
*/
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)
{ {
switch (serif->serif_type) { switch (serif->serif_type) {
@@ -151,6 +236,11 @@ int inv_io_hal_read_reg(struct inv_imu_serif *serif, uint8_t reg, uint8_t * rbuf
} }
} }
/*
* inv_io_hal_write_reg()
* IMU 드라이버 콜백: 지정된 레지스터에 데이터를 쓴다.
* 시리얼 타입에 따라 I2C 또는 SPI 쓰기를 수행한다.
*/
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)
{ {
switch (serif->serif_type) { switch (serif->serif_type) {
@@ -164,6 +254,13 @@ int inv_io_hal_write_reg(struct inv_imu_serif *serif, uint8_t reg, const uint8_t
return -1; return -1;
} }
} }
/*
* cat_read()
* 범용 I2C 읽기 함수 (디버그/레거시용).
* 8바이트를 읽어 첫 번째 바이트를 반환하고, 읽은 데이터를 콘솔에 출력한다.
* 참고: 현재 실제 운용에서는 사용되지 않으며, 디버그 목적으로 남아 있다.
*/
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)
{ {
@@ -172,12 +269,15 @@ uint8_t cat_read(uint8_t device_id, uint8_t address, uint8_t *data)
ret_code_t err_code; ret_code_t err_code;
//address = 1|(address<<1); //address = 1|(address<<1);
address = (address & 0xFF); address = (address & 0xFF);
/* 레지스터 주소 전송 (STOP 없이, 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바이트 데이터 수신 */
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
@@ -196,6 +296,12 @@ uint8_t cat_read(uint8_t device_id, uint8_t address, uint8_t *data)
} }
/*
* cat_write()
* 범용 I2C 쓰기 함수 (디버그/레거시용).
* 주소 1바이트 + 데이터 1바이트를 전송한다.
* 참고: buffer에 6바이트를 복사하지만, 실제 전송은 2바이트만 수행한다.
*/
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){
uint8_t buffer[7]={0x00,0x00,0x00,0x00,0x00,0x00,0x00}; uint8_t buffer[7]={0x00,0x00,0x00,0x00,0x00,0x00,0x00};
@@ -207,6 +313,8 @@ void cat_write(uint8_t device_id, uint8_t address, uint8_t *data){
memcpy(buffer+1,data,6); memcpy(buffer+1,data,6);
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바이트 전송 */
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);
@@ -224,4 +332,3 @@ void cat_write(uint8_t device_id, uint8_t address, uint8_t *data){
} }

View File

@@ -6,6 +6,21 @@
* @brief * @brief
******************************************************************************/ ******************************************************************************/
/*******************************************************************************
* [헤더 개요] ICM42670P I2C 통신 인터페이스 선언
*
* nRF52840 TWI 하드웨어를 통해 ICM42670P IMU 센서와 통신하기 위한
* 핀 정의, 함수 프로토타입을 선언한다.
*
* 핀 배치:
* - I2C SCL : P1.14
* - I2C SDA : P1.15
* - INT1 : P1.13 (데이터 준비 인터럽트)
* - INT2 : P0.26 (보조 인터럽트, 현재 미사용)
*
* TWI 인스턴스: 0번 사용
******************************************************************************/
#ifndef _SYSTEM_INTERFACE_H_ #ifndef _SYSTEM_INTERFACE_H_
#define _SYSTEM_INTERFACE_H_ #define _SYSTEM_INTERFACE_H_
@@ -17,25 +32,42 @@
#endif #endif
#define ICM42670_I2C_INSTANCE 0 /**< I2C instance index. */ #define ICM42670_I2C_INSTANCE 0 /**< I2C(TWI) 인스턴스 인덱스 */
#define ICM42670_I2C_SDA_PIN NRF_GPIO_PIN_MAP(1,15) #define ICM42670_I2C_SDA_PIN NRF_GPIO_PIN_MAP(1,15) /**< SDA 핀: P1.15 */
#define ICM42670_I2C_SCL_PIN NRF_GPIO_PIN_MAP(1,14) #define ICM42670_I2C_SCL_PIN NRF_GPIO_PIN_MAP(1,14) /**< SCL 핀: P1.14 */
#define ICM42670_INT1_PIN NRF_GPIO_PIN_MAP(1,13) #define ICM42670_INT1_PIN NRF_GPIO_PIN_MAP(1,13) /**< INT1 핀: P1.13 (데이터 준비 인터럽트) */
#define ICM42670_INT2_PIN NRF_GPIO_PIN_MAP(0,26) #define ICM42670_INT2_PIN NRF_GPIO_PIN_MAP(0,26) /**< INT2 핀: P0.26 (보조, 현재 미사용) */
/* I2C 전송 래퍼 — no_stop=true이면 Repeated START를 위해 STOP 컨디션 생략 */
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 수신 래퍼 */
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바이트를 읽어 첫 바이트 반환 */
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바이트 전송 */
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 하드웨어 해제 (슬립 또는 재초기화 전 호출) */
void inv_i2c_master_uninitialize(void); void inv_i2c_master_uninitialize(void);
/* I2C 하드웨어 초기화 (100kHz, 블로킹 모드) */
void inv_i2c_master_initialize(void); void inv_i2c_master_initialize(void);
/* IMU 드라이버용 시리얼 인터페이스 초기화 (I2C/SPI 분기) */
int inv_io_hal_init(struct inv_imu_serif *serif); int inv_io_hal_init(struct inv_imu_serif *serif);
/* IMU 드라이버 콜백: 레지스터 읽기 */
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 드라이버 콜백: 레지스터 쓰기 */
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_ */

View File

@@ -608,11 +608,15 @@ static void nrf_qwr_error_handler(uint32_t nrf_error)
extern bool maa_async_on_tx_ready(void); extern bool maa_async_on_tx_ready(void);
extern bool maa_async_is_busy(void); extern bool maa_async_is_busy(void);
/* 2026-03-17: BLE 콜백에서 블로킹 방지 — 명령을 버퍼에 저장 후 메인 루프에서 처리 */
static volatile uint8_t pending_cmd_buf[BLE_NUS_MAX_DATA_LEN];
static volatile uint8_t pending_cmd_len = 0;
/** /**
* @brief NUS(Nordic UART Service) 데이터 수신 핸들러 * @brief NUS(Nordic UART Service) 데이터 수신 핸들러
* *
* BLE를 통해 스마트폰 앱에서 데이터를 수신했을 때 호출됨 * BLE를 통해 스마트폰 앱에서 데이터를 수신했을 때 호출됨
* - RX_DATA 이벤트: 수신된 명령을 received_command_process()로 전달 * - RX_DATA 이벤트: 수신 데이터를 버퍼에 복사 (메인 루프에서 dr_cmd_parser 호출)
* - TX_RDY 이벤트: BLE TX 버퍼에 공간이 생겼을 때 비동기 MAA 전송 계속 * - TX_RDY 이벤트: BLE TX 버퍼에 공간이 생겼을 때 비동기 MAA 전송 계속
*/ */
static void nus_data_handler(ble_nus_evt_t * p_evt) static void nus_data_handler(ble_nus_evt_t * p_evt)
@@ -622,7 +626,12 @@ static void nus_data_handler(ble_nus_evt_t * p_evt)
cmd_type_t = CMD_BLE; cmd_type_t = CMD_BLE;
ble_got_new_data = true; ble_got_new_data = true;
DBG_PRINTF("[NUS] RX len=%d\r\n", p_evt->params.rx_data.length); DBG_PRINTF("[NUS] RX len=%d\r\n", p_evt->params.rx_data.length);
dr_cmd_parser(p_evt->params.rx_data.p_data, p_evt->params.rx_data.length);
/* 콜백에서는 복사만 하고, 메인 루프에서 처리 */
if (p_evt->params.rx_data.length <= BLE_NUS_MAX_DATA_LEN) {
memcpy((void *)pending_cmd_buf, p_evt->params.rx_data.p_data, p_evt->params.rx_data.length);
pending_cmd_len = p_evt->params.rx_data.length;
}
} }
else if (p_evt->type == BLE_NUS_EVT_TX_RDY) else if (p_evt->type == BLE_NUS_EVT_TX_RDY)
{ {
@@ -1771,6 +1780,13 @@ int main(void)
// MAIN LOOP // MAIN LOOP
for (;;) for (;;)
{ {
/* BLE 콜백에서 수신된 명령이 있으면 여기서 처리 (블로킹 방지) */
if (pending_cmd_len > 0) {
uint8_t len = pending_cmd_len;
pending_cmd_len = 0;
dr_cmd_parser((const uint8_t *)pending_cmd_buf, len);
}
idle_state_handle(); idle_state_handle();
} }
} }

View File

@@ -4,14 +4,34 @@
* @version V1.0.0 * @version V1.0.0
* @date 2022-09-05 * @date 2022-09-05
* @brief * @brief
*******************************************************************************
*
* [헤더 개요]
* 메인 이벤트 루프 타이머의 공용 인터페이스 헤더.
*
* 10ms(일반) 또는 80ms(디테일) 간격의 싱글샷 타이머를 사용하여
* main_loop() 콜백에서 센서 데이터 수집 및 시스템 제어를 수행한다.
*
* [주요 함수]
* main_timer_start() : 타이머 시작 (싱글샷, 수동 재시작 필요)
* main_timer_stop() : 타이머 정지
* main_timer_init() : 타이머 초기화 (앱 시작 시 1회 호출)
*
******************************************************************************/ ******************************************************************************/
#ifndef TIMER_ROUTINE_H__ #ifndef TIMER_ROUTINE_H__
#define TIMER_ROUTINE_H__ #define TIMER_ROUTINE_H__
/** @brief 메인 루프 타이머 시작 (싱글샷, MAIN_LOOP_INTERVAL 후 main_loop 호출) */
void main_timer_start(void); void main_timer_start(void);
/** @brief 지정된 간격(ms)으로 메인 루프 타이머 시작 */
void main_timer_start_ms(uint32_t interval_ms);
/** @brief 메인 루프 타이머 정지 */
void main_timer_stop(void); void main_timer_stop(void);
/** @brief 메인 루프 타이머 초기화 (앱 시작 시 1회, 싱글샷 모드로 생성) */
void main_timer_init(void); void main_timer_init(void);
#endif //TIMER_ROUTINE_H__ #endif //TIMER_ROUTINE_H__

View File

@@ -418,16 +418,6 @@
<FileType>1</FileType> <FileType>1</FileType>
<FilePath>..\..\..\fstorage.c</FilePath> <FilePath>..\..\..\fstorage.c</FilePath>
</File> </File>
<File>
<FileName>cmd_parse.c</FileName>
<FileType>1</FileType>
<FilePath>..\..\..\cmd_parse.c</FilePath>
</File>
<File>
<FileName>cmd_parse.h</FileName>
<FileType>5</FileType>
<FilePath>..\..\..\cmd_parse.h</FilePath>
</File>
<File> <File>
<FileName>debug_print.h</FileName> <FileName>debug_print.h</FileName>
<FileType>5</FileType> <FileType>5</FileType>
@@ -4631,16 +4621,6 @@
<FileType>1</FileType> <FileType>1</FileType>
<FilePath>..\..\..\fstorage.c</FilePath> <FilePath>..\..\..\fstorage.c</FilePath>
</File> </File>
<File>
<FileName>cmd_parse.c</FileName>
<FileType>1</FileType>
<FilePath>..\..\..\cmd_parse.c</FilePath>
</File>
<File>
<FileName>cmd_parse.h</FileName>
<FileType>5</FileType>
<FilePath>..\..\..\cmd_parse.h</FilePath>
</File>
<File> <File>
<FileName>debug_print.h</FileName> <FileName>debug_print.h</FileName>
<FileType>5</FileType> <FileType>5</FileType>

View File

@@ -11473,7 +11473,7 @@
// <i> or this value is actually used. It depends on which one is bigger. // <i> or this value is actually used. It depends on which one is bigger.
#ifndef SEGGER_RTT_CONFIG_BUFFER_SIZE_UP #ifndef SEGGER_RTT_CONFIG_BUFFER_SIZE_UP
#define SEGGER_RTT_CONFIG_BUFFER_SIZE_UP 512 #define SEGGER_RTT_CONFIG_BUFFER_SIZE_UP 1024 // 512 -> 1024 jhChun 26.03.17
#endif #endif
// <o> SEGGER_RTT_CONFIG_MAX_NUM_UP_BUFFERS - Maximum number of upstream buffers. // <o> SEGGER_RTT_CONFIG_MAX_NUM_UP_BUFFERS - Maximum number of upstream buffers.

View File

@@ -1,6 +1,33 @@
/******************************************************************************* /*******************************************************************************
TEST medi50 Dec 23 TEST medi50 Dec 23
//=========power_control.c==================== //=========power_control.c====================
*******************************************************************************
*
* [모듈 개요]
* 디바이스 전원 시퀀스를 관리하는 모듈 (전원 켜기 / 끄기 / 슬립).
*
* [전원 켜기 흐름]
* device_activated()
* → p_order=0으로 초기화, power_loop 타이머 시작
* → power_loop() 상태머신이 20ms 간격으로 Step 0→1→2 순서대로 실행
* - Step 0: I2C 초기화 (sw_i2c_init_once)
* - Step 1: 예약 (현재 미사용)
* - Step 2: 전원 시퀀스 완료
*
* [슬립 모드]
* device_sleep_mode()
* → EEPROM OFF → processing 플래그 해제
*
* [재활성화]
* device_reactivated()
* → I2C 재초기화 후 전원 시퀀스를 처음부터 다시 시작
*
* [EEPROM 쓰기 보호 핀]
* EEP_WP = P0.24 (레거시, 현재는 FDS를 사용하므로 실질적으로 미사용)
*
* [타이머]
* 싱글샷 모드 app_timer, 20ms 간격으로 power_loop 호출
*
******************************************************************************/ ******************************************************************************/
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
@@ -15,50 +42,89 @@
#include "app_timer.h" #include "app_timer.h"
#include "debug_print.h" #include "debug_print.h"
#include "i2c_manager.h" #include "i2c_manager.h"
/* EEPROM 쓰기 보호 핀 (P0.24) - 레거시, 현재는 FDS 사용 */
#define EEP_WP NRF_GPIO_PIN_MAP(0, 24) #define EEP_WP NRF_GPIO_PIN_MAP(0, 24)
/* 전원 시퀀스용 싱글샷 타이머 인스턴스 */
APP_TIMER_DEF(m_power_timer_id); APP_TIMER_DEF(m_power_timer_id);
/* 전원 시퀀스 상태머신의 타이머 간격 (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: 완료) */
static uint8_t p_order; static uint8_t p_order;
/* 데이터 처리 중 플래그 (외부 모듈에서 선언) */
extern volatile bool processing; extern volatile bool processing;
/* 전원 시퀀스 잠금 플래그 (true = 전원 시퀀스 진행 중) */
bool lock_check = false; bool lock_check = false;
/**
* @brief 전원 관련 GPIO 핀 초기화
*
* EEP_WP 핀을 출력 모드로 설정한다.
*/
void power_gpio_init(void) void power_gpio_init(void)
{ {
nrf_gpio_cfg_output(EEP_WP); nrf_gpio_cfg_output(EEP_WP);
} }
/**
* @brief EEPROM 전원 제어 (ON/OFF)
*
* @param eeprom_st ON: EEP_WP 핀 HIGH (쓰기 허용), OFF: LOW (쓰기 보호)
* 레거시 기능으로, 현재는 FDS(Flash Data Storage)를 사용하여 실질적으로 미사용.
*/
void eeprom_control(on_off_cont_t eeprom_st) void eeprom_control(on_off_cont_t eeprom_st)
{ {
if(eeprom_st == OFF) { if(eeprom_st == OFF) {
nrf_gpio_pin_clear(EEP_WP); nrf_gpio_pin_clear(EEP_WP); /* LOW: EEPROM 쓰기 보호 활성화 */
}else if(eeprom_st == ON){ }else if(eeprom_st == ON){
nrf_gpio_pin_set(EEP_WP); nrf_gpio_pin_set(EEP_WP); /* HIGH: EEPROM 쓰기 허용 */
} }
} }
/**
* @brief 디바이스 슬립 모드 진입
*
* EEPROM을 OFF하고, 데이터 처리 플래그(processing)를 해제하여
* 메인 루프가 더 이상 센서 데이터를 처리하지 않도록 한다.
*
* @return 0 (항상 성공)
*/
int device_sleep_mode(void){ int device_sleep_mode(void){
int rc = 0; int rc = 0;
eeprom_control(OFF); eeprom_control(OFF); /* EEPROM 쓰기 보호 활성화 */
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; processing = false; /* 데이터 처리 플래그 해제 → 센서 처리 중단 */
return rc; return rc;
} }
/**
* @brief 디바이스 전원 켜기 (전원 시퀀스 시작)
*
* 전원 시퀀스 단계를 0으로 초기화하고, 잠금 플래그를 설정한 뒤
* 타이머를 시작하여 power_loop() 상태머신을 구동한다.
* EEPROM은 OFF 상태로 시작한다.
*
* @return 0 (항상 성공)
*/
int device_activated(void){ int device_activated(void){
int rc = 0; int rc = 0;
p_order = 0; p_order = 0; /* 상태머신 시작 단계 (Step 0: I2C 초기화) */
lock_check =true; lock_check =true; /* 전원 시퀀스 진행 중 잠금 */
power_timer_start(); power_timer_start(); /* 20ms 후 power_loop() 첫 호출 */
eeprom_control(OFF); eeprom_control(OFF); /* EEPROM OFF 상태로 시작 */
return rc; return rc;
} }
@@ -74,32 +140,49 @@ int device_activated(void){
* 1: (reserved) * 1: (reserved)
* 2: Complete * 2: Complete
*/ */
/*
* 전원 시퀀스 상태머신 (20ms 싱글샷 타이머 콜백).
*
* [실행 흐름]
* 1) 타이머 콜백 진입 → 타이머 정지 (싱글샷이므로)
* 2) 현재 p_order에 해당하는 초기화 단계 실행
* 3) p_order < 2이면 다음 단계로 진행하고 타이머 재시작
* 4) p_order == 2이면 전원 시퀀스 완료
*
* [DEBUG_MINIMAL_BOOT 모드]
* 센서 초기화를 건너뛰고 즉시 완료 단계(Step 2)로 점프
*/
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(); /* 현재 타이머 정지 (싱글샷 → 수동 재시작 필요) */
#if DEBUG_MINIMAL_BOOT #if DEBUG_MINIMAL_BOOT
/* Minimal Boot: Skip sensor initialization */ /* Minimal Boot: Skip sensor initialization */
/* 미니멀 부트 모드: 센서 초기화 생략하고 바로 완료 */
DBG_PRINTF("[PWR] Minimal mode - skipping sensor init\r\n"); DBG_PRINTF("[PWR] Minimal mode - skipping sensor init\r\n");
p_order = 2; // Jump to complete p_order = 2; // Jump to complete
#else #else
/* Full Boot: Execute power sequence */ /* Full Boot: Execute power sequence */
/* 풀 부트 모드: 전원 시퀀스 단계별 실행 */
switch (p_order) { switch (p_order) {
/* Step 0: I2C Initialize */ /* Step 0: I2C Initialize */
/* 단계 0: I2C 버스를 SW 모드로 초기화 (레거시 흐름) */
case 0: case 0:
sw_i2c_init_once(); sw_i2c_init_once(); /* SW I2C 초기화 (TWI 해제 → 비트뱅 모드) */
nrf_delay_ms(10); nrf_delay_ms(10); /* I2C 버스 안정화 대기 */
DBG_PRINTF("[PWR] Step %d: I2C Init\r\n", p_order); DBG_PRINTF("[PWR] Step %d: I2C Init\r\n", p_order);
break; break;
/* Step 1: Reserved */ /* Step 1: Reserved */
/* 단계 1: 예약 (추후 센서 초기화 등 추가 가능) */
case 1: case 1:
DBG_PRINTF("[PWR] Step %d: (reserved)\r\n", p_order); DBG_PRINTF("[PWR] Step %d: (reserved)\r\n", p_order);
break; break;
/* Step 2: Complete */ /* Step 2: Complete */
/* 단계 2: 전원 시퀀스 완료 → 디바이스 사용 준비 완료 */
case 2: case 2:
DBG_PRINTF("[PWR] Step %d: Sequence Complete\r\n", p_order); DBG_PRINTF("[PWR] Step %d: Sequence Complete\r\n", p_order);
break; break;
@@ -110,31 +193,49 @@ void power_loop(void *p_context)
#endif #endif
/* Advance to next step or finish */ /* Advance to next step or finish */
/* 다음 단계로 진행하거나, 시퀀스 완료 처리 */
if (p_order < 2) { if (p_order < 2) {
p_order++; p_order++; /* 다음 단계로 이동 */
power_timer_start(); power_timer_start(); /* 20ms 후 다음 단계 실행 */
} else { } else {
/* 전원 시퀀스 전체 완료 */
DBG_PRINTF("[PWR] Device Activated OK!\r\n"); DBG_PRINTF("[PWR] Device Activated OK!\r\n");
} }
} }
/**
* @brief 디바이스 재활성화 (슬립 복귀 시)
*
* I2C를 재초기화하고, 전원 시퀀스를 처음부터 다시 시작한다.
* 슬립 모드에서 깨어날 때 호출된다.
*
* @return 0 (항상 성공)
*/
int device_reactivated(void){ int device_reactivated(void){
int rc = 0; int rc = 0;
sw_i2c_init_once(); sw_i2c_init_once(); /* I2C 버스 재초기화 */
nrf_delay_ms(10); nrf_delay_ms(10); /* 안정화 대기 */
lock_check = true; lock_check = true; /* 전원 시퀀스 진행 중 잠금 */
p_order = 0; p_order = 0; /* 상태머신을 Step 0부터 재시작 */
power_timer_start(); power_timer_start(); /* 20ms 후 power_loop() 시작 */
return rc; return rc;
} }
/**
* @brief 전원 시퀀스 타이머 시작
*
* 싱글샷 모드로 POWER_LOOP_INTERVAL(20ms) 후 power_loop()를 호출한다.
*/
void power_timer_start(void) void power_timer_start(void)
{ {
APP_ERROR_CHECK(app_timer_start(m_power_timer_id, APP_TIMER_TICKS(POWER_LOOP_INTERVAL), NULL)); APP_ERROR_CHECK(app_timer_start(m_power_timer_id, APP_TIMER_TICKS(POWER_LOOP_INTERVAL), NULL));
} }
/**
* @brief 전원 시퀀스 타이머 정지
*/
void power_timer_stop(void) void power_timer_stop(void)
{ {
APP_ERROR_CHECK(app_timer_stop(m_power_timer_id)); APP_ERROR_CHECK(app_timer_stop(m_power_timer_id));
@@ -142,6 +243,12 @@ void power_timer_stop(void)
} }
/**
* @brief 전원 시퀀스 타이머 초기화 (앱 시작 시 1회 호출)
*
* 싱글샷 모드 타이머를 생성하고, 콜백으로 power_loop()를 등록한다.
* 싱글샷이므로 매 단계마다 power_timer_start()로 수동 재시작해야 한다.
*/
void power_timer_init(void) //active start void power_timer_init(void) //active start
{ {
APP_ERROR_CHECK(app_timer_create(&m_power_timer_id, APP_TIMER_MODE_SINGLE_SHOT, power_loop)); APP_ERROR_CHECK(app_timer_create(&m_power_timer_id, APP_TIMER_MODE_SINGLE_SHOT, power_loop));

View File

@@ -4,6 +4,21 @@
* @version V1.0.0 * @version V1.0.0
* @date 2022-09-05 * @date 2022-09-05
* @brief * @brief
*******************************************************************************
*
* [헤더 개요]
* 디바이스 전원 시퀀스 관리 모듈의 공용 인터페이스 헤더.
*
* [주요 함수 요약]
* power_gpio_init() : 전원 관련 GPIO 초기화 (EEP_WP 핀)
* eeprom_control() : EEPROM 쓰기 보호 ON/OFF (레거시)
* device_activated() : 전원 켜기 → power_loop 상태머신 시작
* device_sleep_mode() : 슬립 모드 진입 (EEPROM OFF, 처리 중단)
* device_reactivated() : 슬립 복귀 → I2C 재초기화 후 전원 시퀀스 재시작
* power_loop() : 20ms 간격 전원 시퀀스 상태머신 콜백
* power_timer_start/stop : 전원 시퀀스 타이머 제어
* power_timer_init() : 전원 시퀀스 타이머 초기화 (앱 시작 시 1회)
*
******************************************************************************/ ******************************************************************************/
#ifndef _POWER_CONTROL_H_ #ifndef _POWER_CONTROL_H_
@@ -11,14 +26,31 @@
#include "main.h" #include "main.h"
/** @brief 전원 관련 GPIO 핀 초기화 (EEP_WP 출력 설정) */
void power_gpio_init(void); void power_gpio_init(void);
/** @brief EEPROM 쓰기 보호 제어 (ON=허용, OFF=보호) - 레거시 */
void eeprom_control(on_off_cont_t eeprom_st); void eeprom_control(on_off_cont_t eeprom_st);
/** @brief 디바이스 슬립 모드 진입 (EEPROM OFF, processing 해제) */
int device_sleep_mode(void); int device_sleep_mode(void);
/** @brief 디바이스 전원 켜기 (전원 시퀀스 상태머신 시작) */
int device_activated(void); int device_activated(void);
/** @brief 디바이스 재활성화 (슬립 복귀 시 I2C 재초기화 후 시퀀스 재시작) */
int device_reactivated(void); int device_reactivated(void);
/** @brief 전원 시퀀스 상태머신 (20ms 타이머 콜백, Step 0→1→2) */
void power_loop(void * p_context); /* For x ms */ void power_loop(void * p_context); /* For x ms */
/** @brief 전원 시퀀스 타이머 시작 (20ms 싱글샷) */
void power_timer_start(void); void power_timer_start(void);
/** @brief 전원 시퀀스 타이머 정지 */
void power_timer_stop(void);; void power_timer_stop(void);;
/** @brief 전원 시퀀스 타이머 초기화 (앱 시작 시 1회 호출) */
void power_timer_init(void); void power_timer_init(void);
#endif //_POWER_CONTROL_H_ #endif //_POWER_CONTROL_H_

View File

@@ -6,6 +6,26 @@
* @date 2025-10-13 * @date 2025-10-13
******************************************************************************/ ******************************************************************************/
/*******************************************************************************
* [한국어 설명] PWM 펄스 생성기
*
* === 현재 상태 ===
* SKIP_PWM 매크로가 정의되어 있어 PWM 기능이 비활성화됨.
* init/start/stop 함수는 모두 Stub(빈 껍데기) 구현으로,
* 로그만 출력하고 실제 PWM 동작은 하지 않는다.
* 빌드 호환성을 위해 함수 인터페이스만 유지.
*
* === 실제 구현 (SKIP_PWM 미정의 시) ===
* nrfx_pwm 드라이버 + app_timer를 사용하여 3핀(P28/P29/P30) PWM 제어.
* PWM 인스턴스 0, 16MHz 클럭, TOP=8, 개별 채널 로드 모드.
* 6단계 시퀀스로 채널별 듀티 사이클 조절.
* app_timer(10ms)로 주기적 반복 재생.
*
* === 비활성화 사유 ===
* 현재 프로젝트에서는 피에조 구동을 dr_piezo 드라이버가 담당하므로
* 이 PWM 펄스 생성기는 사용하지 않음.
******************************************************************************/
#define SKIP_PWM // ? ?? ???? PWM ?? ??? #define SKIP_PWM // ? ?? ???? PWM ?? ???
#include "pulse_gen.h" #include "pulse_gen.h"
@@ -15,57 +35,72 @@
#ifdef SKIP_PWM #ifdef SKIP_PWM
/* ========================================================================== */ /* ========================================================================== */
/* PWM ?? ???? Stub ?? */ /* PWM 비활성화 모드: Stub 구현 (로그만 출력, 실제 동작 없음) */
/* ========================================================================== */ /* ========================================================================== */
/* Stub 초기화: 아무 동작 없이 성공 반환 */
ret_code_t pulse_gen_init(void) ret_code_t pulse_gen_init(void)
{ {
DBG_PRINTF("[PWM] skipped init\r\n"); DBG_PRINTF("[PWM] skipped init\r\n");
return NRF_SUCCESS; return NRF_SUCCESS;
} }
/* Stub 시작: 아무 동작 없음 */
void pulse_gen_start(void) void pulse_gen_start(void)
{ {
DBG_PRINTF("[PWM] skipped start\r\n"); DBG_PRINTF("[PWM] skipped start\r\n");
} }
/* Stub 정지: 아무 동작 없음 */
void pulse_gen_stop(void) void pulse_gen_stop(void)
{ {
DBG_PRINTF("[PWM] skipped stop\r\n"); DBG_PRINTF("[PWM] skipped stop\r\n");
} }
#else /* ==================================================================== */ #else /* ==================================================================== */
/* ?? PWM ?? ?? (SKIP_PWM ?? ?? ? ???) */ /* 실제 PWM 구현 코드 (SKIP_PWM 미정의 시에만 컴파일됨) */
/* ========================================================================== */ /* ========================================================================== */
#include "nrfx_pwm.h" #include "nrfx_pwm.h"
/* --- Configuration --- */ /* --- 설정값 --- */
#define PULSE_PIN_1 28 #define PULSE_PIN_1 28 /* PWM 출력 핀 1 (P0.28) */
#define PULSE_PIN_2 29 #define PULSE_PIN_2 29 /* PWM 출력 핀 2 (P0.29) */
#define PULSE_PIN_3 30 #define PULSE_PIN_3 30 /* PWM 출력 핀 3 (P0.30) */
#define PULSE_DELAY_MS 10 #define PULSE_DELAY_MS 10 /* 반복 재생 간격 (ms) */
#define PWM_INSTANCE_ID 0 #define PWM_INSTANCE_ID 0 /* PWM 하드웨어 인스턴스 번호 */
#define PWM_TOP_VALUE 8 #define PWM_TOP_VALUE 8 /* PWM 카운터 최대값 (듀티 사이클 분해능) */
#define SEQUENCE_LENGTH 6 #define SEQUENCE_LENGTH 6 /* PWM 시퀀스 단계 수 */
static nrfx_pwm_t m_pwm = NRFX_PWM_INSTANCE(PWM_INSTANCE_ID); static nrfx_pwm_t m_pwm = NRFX_PWM_INSTANCE(PWM_INSTANCE_ID); /* PWM 인스턴스 */
APP_TIMER_DEF(m_pulse_delay_timer_id); APP_TIMER_DEF(m_pulse_delay_timer_id); /* 반복 재생용 앱 타이머 */
static volatile bool m_pulses_running = false; static volatile bool m_pulses_running = false; /* 펄스 생성 동작 중 플래그 */
/* PWM 시퀀스 데이터: 6단계에 걸쳐 3채널의 듀티 사이클을 개별 설정 */
static nrf_pwm_values_individual_t m_pulse_seq_values[SEQUENCE_LENGTH]; static nrf_pwm_values_individual_t m_pulse_seq_values[SEQUENCE_LENGTH];
static nrf_pwm_sequence_t const m_pulse_sequence = static nrf_pwm_sequence_t const m_pulse_sequence =
{ {
.values.p_individual = m_pulse_seq_values, .values.p_individual = m_pulse_seq_values,
.length = NRF_PWM_VALUES_LENGTH(m_pulse_seq_values), .length = NRF_PWM_VALUES_LENGTH(m_pulse_seq_values),
.repeats = 0, .repeats = 0, /* 각 단계 반복 없음 */
.end_delay = 0 .end_delay = 0 /* 시퀀스 종료 후 추가 지연 없음 */
}; };
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* Local Handlers */ /* 내부 핸들러 함수 */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/*
* PWM 시퀀스 값 초기화
* 6단계에 걸쳐 CH0/CH1/CH2의 듀티 사이클을 교대 설정:
* 단계0: CH0=100%, CH1=0, CH2=0
* 단계1: CH0=0, CH1=100%, CH2=0
* 단계2: CH0=100%, CH1=0, CH2=0
* 단계3: CH0=0, CH1=100%, CH2=0
* 단계4: CH0=0, CH1=0, CH2=0 (모두 OFF)
* 단계5: CH0=0, CH1=0, CH2=100%
*/
static void prepare_pulse_sequence(void) static void prepare_pulse_sequence(void)
{ {
m_pulse_seq_values[0].channel_0 = PWM_TOP_VALUE; m_pulse_seq_values[0].channel_1 = 0; m_pulse_seq_values[0].channel_2 = 0; m_pulse_seq_values[0].channel_0 = PWM_TOP_VALUE; m_pulse_seq_values[0].channel_1 = 0; m_pulse_seq_values[0].channel_2 = 0;
@@ -76,6 +111,7 @@ static void prepare_pulse_sequence(void)
m_pulse_seq_values[5].channel_0 = 0; m_pulse_seq_values[5].channel_1 = 0; m_pulse_seq_values[5].channel_2 = PWM_TOP_VALUE; m_pulse_seq_values[5].channel_0 = 0; m_pulse_seq_values[5].channel_1 = 0; m_pulse_seq_values[5].channel_2 = PWM_TOP_VALUE;
} }
/* 타이머 타임아웃 핸들러: m_pulses_running이면 PWM 시퀀스 1회 재생 후 타이머 재시작 */
static void pulse_delay_timeout_handler(void * p_context) static void pulse_delay_timeout_handler(void * p_context)
{ {
if (m_pulses_running) if (m_pulses_running)
@@ -86,8 +122,14 @@ static void pulse_delay_timeout_handler(void * p_context)
} }
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* Public Functions */ /* 공개 함수 */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/*
* PWM 초기화: PWM 인스턴스 설정 + 앱 타이머 생성
* PWM: 16MHz 클럭, UP 카운트 모드, TOP=8, 개별 채널 로드
* 타이머: 단발(SINGLE_SHOT) 모드, 10ms 후 시퀀스 재생
*/
ret_code_t pulse_gen_init(void) ret_code_t pulse_gen_init(void)
{ {
ret_code_t err_code; ret_code_t err_code;
@@ -121,18 +163,20 @@ ret_code_t pulse_gen_init(void)
return err_code; return err_code;
} }
/* 펄스 생성 시작: 플래그 설정 후 타임아웃 핸들러를 직접 호출하여 즉시 첫 재생 */
void pulse_gen_start(void) void pulse_gen_start(void)
{ {
if (m_pulses_running) return; if (m_pulses_running) return; /* 이미 동작 중이면 무시 */
m_pulses_running = true; m_pulses_running = true;
pulse_delay_timeout_handler(NULL); pulse_delay_timeout_handler(NULL); /* 첫 번째 재생 즉시 시작 */
} }
/* 펄스 생성 정지: 플래그 해제 + 타이머 정지 + PWM 즉시 정지 */
void pulse_gen_stop(void) void pulse_gen_stop(void)
{ {
m_pulses_running = false; m_pulses_running = false;
app_timer_stop(m_pulse_delay_timer_id); app_timer_stop(m_pulse_delay_timer_id);
nrfx_pwm_stop(&m_pwm, true); nrfx_pwm_stop(&m_pwm, true); /* true = 즉시 정지 (현재 시퀀스 중단) */
} }
#endif /* SKIP_PWM */ #endif /* SKIP_PWM */

View File

@@ -1,3 +1,15 @@
/*******************************************************************************
* [한국어 설명] PWM 펄스 생성기 헤더
*
* PWM 기반 펄스 생성 모듈의 공개 인터페이스.
* 현재 SKIP_PWM 매크로로 비활성화되어 있으며, Stub 구현만 동작함.
* 실제 구현 시 nrfx_pwm + app_timer로 3핀(P28/P29/P30) PWM 제어.
*
* 비활성화 사유: 피에조 구동은 dr_piezo 드라이버가 담당하므로
* 이 PWM 모듈은 현재 프로젝트에서 사용하지 않음.
* 빌드 호환성을 위해 인터페이스만 유지.
******************************************************************************/
#ifndef PULSE_GEN_H__ #ifndef PULSE_GEN_H__
#define PULSE_GEN_H__ #define PULSE_GEN_H__
@@ -6,20 +18,23 @@
#include "sdk_errors.h" #include "sdk_errors.h"
/** /**
* @brief Initializes the PWM peripheral and app_timer for pulse generation. * @brief PWM 주변장치 및 app_timer를 초기화한다.
* (SKIP_PWM 정의 시 아무 동작 없이 NRF_SUCCESS 반환)
* *
* @return NRF_SUCCESS on successful initialization, otherwise an error code. * @return NRF_SUCCESS on successful initialization, otherwise an error code.
*/ */
ret_code_t pulse_gen_init(void); ret_code_t pulse_gen_init(void);
/** /**
* @brief Starts the repeating pulse generation. * @brief 반복 펄스 생성을 시작한다.
* Generates a burst of pulses, waits a few ms, and repeats. * 버스트 펄스를 생성하고, 수 ms 대기 후 반복.
* (SKIP_PWM 정의 시 아무 동작 없음)
*/ */
void pulse_gen_start(void); void pulse_gen_start(void);
/** /**
* @brief Stops the repeating pulse generation. * @brief 반복 펄스 생성을 정지한다.
* (SKIP_PWM 정의 시 아무 동작 없음)
*/ */
void pulse_gen_stop(void); void pulse_gen_stop(void);

View File

@@ -97,6 +97,7 @@ void tmp235_voltage_handler(nrf_drv_saadc_evt_t const * p_event) /* TMP325 Vout
/* ADC 변환 결과 읽기 */ /* 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 해제 — 배터리/압력센서 측정과 하드웨어 공유 */ /* SAADC 해제 — 배터리/압력센서 측정과 하드웨어 공유 */
nrf_drv_saadc_uninit(); nrf_drv_saadc_uninit();

View File

@@ -6,10 +6,25 @@
* @brief * @brief
******************************************************************************/ ******************************************************************************/
/*******************************************************************************
* [헤더 개요] TMP235-Q1 아날로그 온도센서 드라이버 인터페이스
*
* TMP235-Q1의 아날로그 전압 출력을 SAADC(AIN3)로 읽어
* 온도(°C)로 변환하는 기능의 외부 호출용 API를 선언한다.
*
* 주요 API:
* - tmp235_init() : SAADC 초기화 + 즉시 측정 시작 (내부 사용)
* - tmp235_voltage_level_meas() : 온도 1회 측정 (외부 호출용 래퍼)
*
* 온도 변환: Vout(mV) → Ta(°C) = (Vout - 500) / 10.0 (0~100°C 구간)
******************************************************************************/
#ifndef _TMP235_Q1_H_ #ifndef _TMP235_Q1_H_
#define _TMP235_Q1_H_ #define _TMP235_Q1_H_
/** @brief TMP235 SAADC 초기화 및 측정 시작 (AIN3 채널) */
void tmp235_init(void); void tmp235_init(void);
/** @brief 온도 1회 측정 외부 호출 함수 (내부적으로 tmp235_init 호출) */
void tmp235_voltage_level_meas(void); void tmp235_voltage_level_meas(void);
#endif /* !_TMP235_Q1_H_ */ #endif /* !_TMP235_Q1_H_ */

View File

@@ -23,15 +23,68 @@
* Full-period = 500ns = 8 ticks * Full-period = 500ns = 8 ticks
******************************************************************************/ ******************************************************************************/
#include "dr_piezo.h" /*******************************************************************************
#include "nrf_gpio.h" * [한국어 설명] 피에조 초음파 트랜스듀서 드라이버
#include "nrf_timer.h" *
#include "nrf_gpiote.h" * === 개요 ===
#include "nrf_ppi.h" * 방광 측정용 2MHz 초음파 송신 신호를 생성하는 드라이버.
#include "nrf_delay.h" * nRF52840의 하드웨어 주변장치(Timer2 + GPIOTE + PPI)를 활용하여
#include "power_control.h" * CPU 개입 없이 정밀한 2MHz 파형을 자동으로 생성한다.
#include "app_util_platform.h" *
* === 초음파 TX 시퀀스 ===
* 1단계: PE(Pulse Enable) = HIGH -> MOSFET 드라이버 활성화
* 2단계: P_OUT/N_OUT 교번 펄스 생성 (2MHz, 3~7 사이클)
* - P_OUT과 N_OUT은 역상(반대 위상)으로 동작
* - 피에조 소자 양단에 +/-20V 교번 전압 인가
* 3단계: DMP(Dump) = HIGH -> 펄스 완료 후 피에조에 남은 잔류 에너지 방전
* 4단계: DMP = LOW -> 방전 완료
* 5단계: PE = LOW -> MOSFET 드라이버 비활성화, 유휴 상태 복귀
*
* === 하드웨어 아키텍처 ===
* [Timer2] --- CC[0](반주기=4틱) ---> [PPI CH8,9] ---> [GPIOTE CH4,5] -> P_OUT/N_OUT 토글
* |-- CC[1](전체주기=8틱) --> [PPI CH10,11] --> [GPIOTE CH4,5] -> P_OUT/N_OUT 토글
* |-- CC[2](전체주기=8틱) --> 타이머 자동 클리어 + 인터럽트(잔여 사이클 카운트)
*
* - Timer2: 16MHz 클럭, 16비트 모드
* - 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 핀 토글 모드
* - GPIOTE CH5: N_OUT 핀 토글 모드
*
* - PPI CH8: CC[0] 이벤트 -> P_OUT 토글 태스크
* - PPI CH9: CC[0] 이벤트 -> N_OUT 토글 태스크
* - PPI CH10: CC[1] 이벤트 -> P_OUT 토글 태스크
* - PPI CH11: CC[1] 이벤트 -> N_OUT 토글 태스크
*
* === 전원 ===
* 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 "nrf_gpio.h" /* GPIO 제어 (핀 설정, 출력) */
#include "nrf_timer.h" /* 타이머 주변장치 제어 */
#include "nrf_gpiote.h" /* GPIOTE (GPIO Tasks and Events) 제어 */
#include "nrf_ppi.h" /* PPI (Programmable Peripheral Interconnect) 제어 */
#include "nrf_delay.h" /* 지연(딜레이) 함수 */
#include "power_control.h" /* 전원 관리 */
#include "app_util_platform.h" /* 인터럽트 우선순위 등 플랫폼 유틸리티 */
/* 조건부 디버그 출력: FEATURE_PRINTF 정의 시 SEGGER RTT로 출력 */
#ifdef FEATURE_PRINTF #ifdef FEATURE_PRINTF
#include "debug_print.h" #include "debug_print.h"
#else #else
@@ -39,62 +92,76 @@
#endif #endif
/*============================================================================== /*==============================================================================
* HARDWARE RESOURCE ALLOCATION * 하드웨어 리소스 할당
* - Timer2: 2MHz 파형 생성의 시간 기준 (16MHz 클럭)
* - GPIOTE CH4/5: P_OUT/N_OUT 핀의 하드웨어 토글
* - PPI CH8~11: 타이머 비교 이벤트 -> GPIOTE 토글 태스크 자동 연결
*============================================================================*/ *============================================================================*/
#define PIEZO_TIMER NRF_TIMER2 #define PIEZO_TIMER NRF_TIMER2 /* 사용할 타이머 인스턴스 */
#define PIEZO_TIMER_IRQn TIMER2_IRQn #define PIEZO_TIMER_IRQn TIMER2_IRQn /* 타이머2 인터럽트 번호 */
#define PIEZO_TIMER_IRQ_PRIORITY 6 #define PIEZO_TIMER_IRQ_PRIORITY 6 /* 인터럽트 우선순위 (6 = 중간) */
/* GPIOTE channels */ /* GPIOTE 채널 할당 - 핀 토글 제어용 */
#define GPIOTE_CH_P_OUT 4 #define GPIOTE_CH_P_OUT 4 /* P_OUT(양극 출력) 토글용 GPIOTE 채널 */
#define GPIOTE_CH_N_OUT 5 #define GPIOTE_CH_N_OUT 5 /* N_OUT(음극 출력) 토글용 GPIOTE 채널 */
/* PPI channels */ /* PPI 채널 할당 - 타이머 이벤트 -> GPIOTE 태스크 연결 */
#define PPI_CH_P_OUT_TOGGLE_0 8 #define PPI_CH_P_OUT_TOGGLE_0 8 /* CC[0](반주기) -> P_OUT 토글 */
#define PPI_CH_N_OUT_TOGGLE_0 9 #define PPI_CH_N_OUT_TOGGLE_0 9 /* CC[0](반주기) -> N_OUT 토글 */
#define PPI_CH_P_OUT_TOGGLE_1 10 #define PPI_CH_P_OUT_TOGGLE_1 10 /* CC[1](전체주기) -> P_OUT 토글 */
#define PPI_CH_N_OUT_TOGGLE_1 11 #define PPI_CH_N_OUT_TOGGLE_1 11 /* CC[1](전체주기) -> N_OUT 토글 */
/*============================================================================== /*==============================================================================
* TIMING CONSTANTS * 타이밍 상수
* Timer2 클럭: 16MHz -> 1틱 = 62.5ns
* 2MHz 신호: 주기 500ns(8틱), 반주기 250ns(4틱)
*============================================================================*/ *============================================================================*/
#define TIMER_FREQ_MHZ 16 #define TIMER_FREQ_MHZ 16 /* 타이머 클럭 주파수 (MHz) */
#define TICK_NS (1000 / TIMER_FREQ_MHZ) /* 62.5ns */ #define TICK_NS (1000 / TIMER_FREQ_MHZ) /* 1틱 = 62.5ns */
/* 2MHz: period = 500ns, half = 250ns */ /* 2MHz 기준 타이밍: 주기 = 500ns, 반주기 = 250ns */
#define PERIOD_TICKS_2MHZ 8 /* 500ns / 62.5ns = 8 */ #define PERIOD_TICKS_2MHZ 8 /* 전체 주기: 500ns / 62.5ns = 8 */
#define HALF_PERIOD_TICKS 4 /* 250ns / 62.5ns = 4 */ #define HALF_PERIOD_TICKS 4 /* 반주기: 250ns / 62.5ns = 4 */
/*============================================================================== /*==============================================================================
* PIEZO OPERATING FREQUENCY CONFIGURATION * 피에조 동작 주파수 설정
*============================================================================*/ *============================================================================*/
/* /*
* Target piezo frequency: 1.8 MHz * 목표 피에조 주파수: 2.1 MHz (하드웨어 버스트 모드용)
* *
* Timing (hardcoded in dr_piezo_burst_sw for stability): * 소프트웨어 버스트 모드의 타이밍은 각 주파수별 함수에 NOP 개수로 하드코딩됨.
* 1.8 MHz: 556ns period, 278ns half-period * 안정적인 파형 생성을 위해 컴파일 타임에 고정.
* At 64MHz CPU (1 NOP = 15.625ns):
* First half: 15 NOPs (~234ns)
* Second half: 12 NOPs (~188ns) + loop overhead
* *
* To change frequency, modify the NOP counts in dr_piezo_burst_sw(): * NOP 기반 타이밍 계산 (CPU 64MHz, 1 NOP = 15.625ns):
* 2.0 MHz: First=14, Second=11 * 1.7 MHz: 반주기 294ns -> 첫 반주기 18 NOP, 둘째 반주기 10 NOP + 루프 오버헤드
* 2.1 MHz: First=13, Second=10 * 1.8 MHz: 반주기 278ns -> 첫 반주기 17 NOP, 둘째 반주기 11 NOP + 루프 오버헤드
* 1.5 MHz: First=18, Second=15 * 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 #define PIEZO_FREQ_MHZ 2.1f /* 기본 동작 주파수 (MHz) */
/*============================================================================== /*==============================================================================
* STATIC VARIABLES * 정적 변수
*============================================================================*/ *============================================================================*/
static volatile bool m_tx_active = false; static volatile bool m_tx_active = false; /* TX 송신 중 플래그 (인터럽트에서 변경) */
static volatile uint8_t m_remaining_cycles = 0; static volatile uint8_t m_remaining_cycles = 0; /* 남은 펄스 사이클 수 (인터럽트에서 감소) */
static uint32_t m_period_ticks = PERIOD_TICKS_2MHZ; static uint32_t m_period_ticks = PERIOD_TICKS_2MHZ; /* 현재 주기 (타이머 틱 단위) */
static bool m_power_enabled = false; static bool m_power_enabled = false; /* DC/DC 컨버터 전원 상태 */
static bool m_initialized = false; static bool m_initialized = false; /* 드라이버 초기화 완료 여부 */
/*============================================================================== /*==============================================================================
* INTERRUPT HANDLER * 타이머2 인터럽트 핸들러
* 매 주기(CC[2])마다 호출되어 잔여 사이클을 감소시킨다.
* 잔여 사이클이 0이 되면:
* 1) 타이머 정지 및 클리어
* 2) GPIOTE 비활성화 (P_OUT/N_OUT 토글 중단)
* 3) GPIO를 출력 모드로 재설정 후 LOW로 초기화
* 4) DMP 펄스 발생 (피에조 잔류 에너지 방전)
* 5) PE = LOW (MOSFET 드라이버 비활성화)
*============================================================================*/ *============================================================================*/
void TIMER2_IRQHandler(void) void TIMER2_IRQHandler(void)
{ {
@@ -140,9 +207,12 @@ void TIMER2_IRQHandler(void)
} }
/*============================================================================== /*==============================================================================
* POWER CONTROL FUNCTIONS * 전원 제어 함수
* DC/DC 컨버터(±20V)를 ON/OFF하여 피에조 구동 전압을 제어한다.
* 전원 안정화에 약 10ms 필요.
*============================================================================*/ *============================================================================*/
/* 피에조 전원 ON: DC/DC 컨버터 활성화 → ±20V 생성 */
void dr_piezo_power_on(void) void dr_piezo_power_on(void)
{ {
nrf_delay_ms(20); nrf_delay_ms(20);
@@ -158,6 +228,7 @@ void dr_piezo_power_on(void)
DBG_PRINTF("[DR_PIEZO] Power ON: +/-20V ready\r\n"); DBG_PRINTF("[DR_PIEZO] Power ON: +/-20V ready\r\n");
} }
/* 피에조 전원 OFF: TX 비활성화 후 DC/DC 컨버터 차단 */
void dr_piezo_power_off(void) void dr_piezo_power_off(void)
{ {
dr_piezo_disable(); dr_piezo_disable();
@@ -169,10 +240,17 @@ void dr_piezo_power_off(void)
DBG_PRINTF("[DR_PIEZO] Power OFF\r\n"); DBG_PRINTF("[DR_PIEZO] Power OFF\r\n");
} }
/* 피에조 전원 상태 확인 */
bool dr_piezo_is_power_on(void)
{
return m_power_enabled;
}
/*============================================================================== /*==============================================================================
* PRIVATE FUNCTIONS * 내부(private) 초기화 함수
*============================================================================*/ *============================================================================*/
/* GPIO 초기화: 모든 신호 핀을 출력 모드로 설정하고 LOW(유휴)로 초기화 */
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);
@@ -192,6 +270,7 @@ static void dr_piezo_gpio_init(void)
DBG_PRINTF("[DR_PIEZO] GPIO init done\r\n"); DBG_PRINTF("[DR_PIEZO] GPIO init done\r\n");
} }
/* GPIOTE 초기화: P_OUT/N_OUT을 토글 모드로 설정 (PPI 연결 대상) */
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 */
@@ -215,6 +294,12 @@ static void dr_piezo_gpiote_init(void)
DBG_PRINTF("[DR_PIEZO] GPIOTE init done\r\n"); DBG_PRINTF("[DR_PIEZO] GPIOTE init done\r\n");
} }
/*
* 타이머 초기화: 16MHz 클럭, 16비트 모드
* CC[0]=반주기(4틱): 반주기 시점 토글 이벤트
* CC[1]=전체주기(8틱): 전체주기 시점 토글 이벤트
* CC[2]=전체주기(8틱): 인터럽트 발생 + 타이머 자동 클리어(SHORT)
*/
static void dr_piezo_timer_init(void) static void dr_piezo_timer_init(void)
{ {
nrf_timer_task_trigger(PIEZO_TIMER, NRF_TIMER_TASK_STOP); nrf_timer_task_trigger(PIEZO_TIMER, NRF_TIMER_TASK_STOP);
@@ -258,6 +343,12 @@ static void dr_piezo_timer_init(void)
DBG_PRINTF("[DR_PIEZO] Timer init done (period=%d ticks)\r\n", m_period_ticks); DBG_PRINTF("[DR_PIEZO] Timer init done (period=%d ticks)\r\n", m_period_ticks);
} }
/*
* PPI 초기화: 타이머 비교 이벤트 → GPIOTE 토글 태스크 연결 (4채널)
* CC[0] 이벤트(반주기) → P_OUT 토글 + N_OUT 토글
* CC[1] 이벤트(전체주기) → P_OUT 토글 + N_OUT 토글
* 이로써 P_OUT과 N_OUT은 항상 역상으로 동작함.
*/
static void dr_piezo_ppi_init(void) static void dr_piezo_ppi_init(void)
{ {
/* /*
@@ -302,9 +393,10 @@ static void dr_piezo_ppi_init(void)
} }
/*============================================================================== /*==============================================================================
* TX DRIVER FUNCTIONS * TX 드라이버 공개 함수
*============================================================================*/ *============================================================================*/
/* TX 드라이버 초기화: GPIO → GPIOTE → Timer → PPI 순서로 설정 */
void dr_piezo_init(void) void dr_piezo_init(void)
{ {
DBG_PRINTF("[DR_PIEZO] Initializing TX driver...\r\n"); DBG_PRINTF("[DR_PIEZO] Initializing TX driver...\r\n");
@@ -321,6 +413,7 @@ void dr_piezo_init(void)
DBG_PRINTF("[DR_PIEZO] TX driver ready (2MHz)\r\n"); DBG_PRINTF("[DR_PIEZO] TX driver ready (2MHz)\r\n");
} }
/* TX 드라이버 해제: 타이머 정지, PPI/GPIOTE 비활성화, 모든 핀 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);
@@ -346,6 +439,13 @@ void dr_piezo_uninit(void)
DBG_PRINTF("[DR_PIEZO] TX driver uninitialized\r\n"); DBG_PRINTF("[DR_PIEZO] TX driver uninitialized\r\n");
} }
/*
* 하드웨어 기반 버스트 송신 (Timer + PPI + GPIOTE 사용)
* 1) GPIOTE를 재설정: P_OUT=HIGH 시작, N_OUT=LOW 시작 (역상)
* 2) PE = HIGH → MOSFET 드라이버 활성화
* 3) 타이머 시작 → PPI가 자동으로 P_OUT/N_OUT 토글
* 4) 인터럽트 핸들러에서 잔여 사이클 관리 및 종료 처리
*/
void dr_piezo_burst(uint8_t cycles) void dr_piezo_burst(uint8_t cycles)
{ {
if (m_tx_active) if (m_tx_active)
@@ -409,11 +509,13 @@ void dr_piezo_burst(uint8_t cycles)
nrf_timer_task_trigger(PIEZO_TIMER, NRF_TIMER_TASK_START); nrf_timer_task_trigger(PIEZO_TIMER, NRF_TIMER_TASK_START);
} }
/* 기본 사이클 수(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) */
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);
@@ -423,6 +525,7 @@ void dr_piezo_enable(void)
DBG_PRINTF("[DR_PIEZO] TX enabled\r\n"); DBG_PRINTF("[DR_PIEZO] TX enabled\r\n");
} }
/* TX 출력 비활성화: 모든 신호 핀 LOW로 복귀 (유휴 상태) */
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);
@@ -432,11 +535,13 @@ void dr_piezo_disable(void)
DBG_PRINTF("[DR_PIEZO] TX disabled\r\n"); DBG_PRINTF("[DR_PIEZO] TX disabled\r\n");
} }
/* TX 송신 중 여부 확인 (인터럽트 핸들러에서 false로 전환) */
bool dr_piezo_is_busy(void) bool dr_piezo_is_busy(void)
{ {
return m_tx_active; return m_tx_active;
} }
/* 동작 주파수 변경 (100kHz~4MHz): 타이머 CC 레지스터 재설정 */
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)
@@ -456,9 +561,14 @@ void dr_piezo_set_frequency(uint32_t freq_hz)
} }
/*============================================================================== /*==============================================================================
* MUX CONTROL FUNCTION * MUX 제어 함수
* 8채널 아날로그 MUX로 피에조 에코 신호 경로를 선택한다.
* MUXA: CH0~CH3 담당, MUXB: CH4~CH7 담당
* SEL0, SEL1: MUX 내부 채널 주소 선택
* High Drive(H0H1) 모드: MUX IC의 빠른 스위칭을 위해 강한 출력 구동력 사용
*============================================================================*/ *============================================================================*/
/* MUX 제어 핀 초기화: 4개 핀 모두 High Drive 출력으로 설정, 기본값 LOW */
void dr_piezo_mux_init(void) void dr_piezo_mux_init(void)
{ {
/* Configure MUX control pins as outputs */ /* Configure MUX control pins as outputs */
@@ -522,9 +632,18 @@ void dr_piezo_mux_init(void)
} }
/*
* 피에조 채널 선택 (0~7)
* 채널 매핑 (EN_MUXA, EN_MUXB, SEL0, SEL1):
* CH0 = MUXA 입력0 (1,0,0,0) CH4 = MUXB 입력0 (0,1,1,1)
* CH1 = MUXA 입력2 (1,0,1,0) CH5 = MUXB 입력1 (0,1,0,1)
* CH2 = MUXA 입력1 (1,0,0,1) CH6 = MUXB 입력2 (0,1,1,0)
* CH3 = MUXA 입력3 (1,0,1,1) CH7 = MUXB 입력3 (0,1,0,0)
* 채널 전환 후 MUX 안정화 대기 시간(1.3ms) 필요.
*/
void dr_piezo_select_channel(uint8_t channel) void dr_piezo_select_channel(uint8_t channel)
{ {
channel = channel & 0x07; /* Mask to 0-7 */ channel = channel & 0x07; /* 0~7 범위로 마스킹 */
switch (channel) { switch (channel) {
// EN_A EN_B SEL0 SEL1 // EN_A EN_B SEL0 SEL1
@@ -566,6 +685,7 @@ void dr_piezo_select_channel(uint8_t channel)
nrf_delay_us(DR_PIEZO_MUX_SETTLING_US); nrf_delay_us(DR_PIEZO_MUX_SETTLING_US);
} }
/* 핀 테스트: 각 신호 핀을 순서대로 HIGH/LOW 토글 (오실로스코프 확인용) */
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");
@@ -606,9 +726,10 @@ void dr_piezo_test_pins(void)
} }
/*============================================================================== /*==============================================================================
* SYSTEM FUNCTIONS * 시스템 함수 (전원 + TX 드라이버 통합 제어)
*============================================================================*/ *============================================================================*/
/* 시스템 전체 초기화: 전원 ON → TX 드라이버 초기화 */
void dr_piezo_system_init(void) void dr_piezo_system_init(void)
{ {
DBG_PRINTF("[DR_PIEZO] System init...\r\n"); DBG_PRINTF("[DR_PIEZO] System init...\r\n");
@@ -617,6 +738,7 @@ void dr_piezo_system_init(void)
DBG_PRINTF("[DR_PIEZO] System ready\r\n"); DBG_PRINTF("[DR_PIEZO] System ready\r\n");
} }
/* 시스템 전체 종료: TX 드라이버 해제 → 전원 OFF */
void dr_piezo_system_uninit(void) void dr_piezo_system_uninit(void)
{ {
dr_piezo_uninit(); dr_piezo_uninit();
@@ -624,6 +746,7 @@ void dr_piezo_system_uninit(void)
DBG_PRINTF("[DR_PIEZO] System shutdown\r\n"); DBG_PRINTF("[DR_PIEZO] System shutdown\r\n");
} }
/* 전원 확인 후 버스트 송신 (블로킹: 송신 완료까지 대기) */
void dr_piezo_transmit(uint8_t cycles) void dr_piezo_transmit(uint8_t cycles)
{ {
if (!m_power_enabled) if (!m_power_enabled)
@@ -638,60 +761,74 @@ void dr_piezo_transmit(uint8_t cycles)
} }
/*============================================================================== /*==============================================================================
* SOFTWARE-BASED BURST * 소프트웨어 기반 버스트 모드
* 2025-12-11 Charles KWON * 2025-12-11 Charles KWON
*============================================================================== *==============================================================================
* *
* Timing Diagram: * Timer/PPI/GPIOTE 하드웨어 대신 CPU에서 직접 GPIO 레지스터를 조작하여
* 초음파 펄스를 생성하는 방식. 인터럽트를 비활성화하여 정확한 타이밍 보장.
* *
* |<-margin->|<----- pulses ----->|<-- DMP -->|<-margin->| * === 타이밍 다이어그램 ===
*
* |<-마진->|<----- 펄스들 ----->|<-- DMP -->|<-마진->|
* *
* PE ___/--------------------------------------------------\___ * PE ___/--------------------------------------------------\___
* P_OUT ___________/-\_/-\_/-\_/-\_/-\____________________\_______ * P_OUT ___________/-\_/-\_/-\_/-\_/-\____________________\_______
* N_OUT ___________\_/-\_/-\_/-\_/-\_/____________________\_______ * N_OUT ___________\_/-\_/-\_/-\_/-\_/____________________\_______
* DMP __________________________________/----------\____________ * DMP __________________________________/----------\____________
* *
* Signal Description: * === 신호 설명 ===
* - PE (Pulse Enable): Wraps entire sequence with margin before and after * - PE (Pulse Enable): 전체 시퀀스를 감싸는 활성화 신호 (전후 마진 포함)
* - P_OUT: Positive output, starts LOW, toggles at 2MHz, opposite phase to N_OUT * - P_OUT: 양극 출력, N_OUT과 역상으로 2MHz 토글
* - N_OUT: Negative output, starts LOW, toggles at 2MHz, opposite phase to P_OUT * - N_OUT: 음극 출력, P_OUT과 역상으로 2MHz 토글
* - DMP (Dump): Activates after pulses complete, simultaneous with N_OUT falling edge * - DMP (Dump): 펄스 완료 후 피에조 잔류 에너지 방전
* *
* Timing Specifications: * === 동작 원리 ===
* - Frequency: 2MHz (500ns period, 250ns half-period) * 1) __disable_irq()로 인터럽트 차단 → 타이밍 흔들림 방지
* - CPU Clock: 64MHz (1 NOP = 15.625ns) * 2) PE ON (P0.25 OUTSET 레지스터 사용 → 다른 P0 핀 영향 없음)
* - Duty Cycle: 50:50 * 3) NOP 마진 후 for 루프로 P_OUT/N_OUT 교번 출력
* - DMP Pulse Width: ~500ns * 4) DMP 펄스 (32 NOP ≒ 500ns)
* - PE Margin: ~3 NOPs before pulses and after DMP * 5) PE OFF 후 __enable_irq()로 인터럽트 복원
* *
* Pin Mapping (Port P1): * === 포트 레지스터 직접 접근 ===
* - P1.02: N_OUT (Negative output) * NRF_P1->OUT 레지스터에 미리 계산된 비트 마스크를 직접 기록.
* - P1.03: P_OUT (Positive output) * 이 방식으로 P_OUT, N_OUT, DMP를 동시에 제어하면서도
* - P1.05: PE (Pulse Enable) * 채널 선택 핀(MUX SEL)은 보존한다 (P1_CTRL_MASK로 제어 핀만 변경).
* - P1.09: DMP (Dump control) * PE는 P0 포트에 있으므로 OUTSET/OUTCLR 레지스터로 별도 제어.
*
* Note: Direct register access (NRF_P1->OUT) is used for simultaneous
* multi-pin control to ensure precise timing and phase alignment.
*============================================================================*/ *============================================================================*/
/* 핀 번호에서 포트 내 비트 위치 추출 (하위 5비트 = 0~31) */
#define PIN_NUM(pin) ((pin) & 0x1F) #define PIN_NUM(pin) ((pin) & 0x1F)
/* Bit masks for P1 port pins - derived from dr_piezo.h definitions /* P1 포트 핀의 비트 마스크 - dr_piezo.h의 핀 정의에서 자동 생성
* *
* WARNING: Never hardcode pin numbers! * 경고: 핀 번호를 절대 하드코딩하지 말 것!
* Hardcoding may save a developer's time momentarily, * 하드코딩은 개발자의 시간을 일시적으로 절약해줄 수 있지만,
* but it will also shorten their lifespan * 동시에 개발자의 수명을 단축시킬 것이다.
* - Charles KWON * - Charles KWON
*
* 각 마스크는 해당 핀의 포트 레지스터 내 비트 위치를 나타낸다.
* NRF_P1->OUT에 직접 쓸 때 사용되며, 여러 핀을 동시에 제어 가능.
*/ */
#define P_OUT_MASK (1UL << PIN_NUM(DR_PIEZO_PIN_P_OUT)) /* P1.03 -> P1.07 */ #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.02 -> P1.06 */ #define N_OUT_MASK (1UL << PIN_NUM(DR_PIEZO_PIN_N_OUT)) /* P1.06 음극 출력 */
#define PE_MASK (1UL << PIN_NUM(DR_PIEZO_PIN_PE)) /* P1.05 -> P0.25 */ #define PE_MASK (1UL << PIN_NUM(DR_PIEZO_PIN_PE)) /* P0.25 펄스 활성화 */
#define DMP_MASK (1UL << PIN_NUM(DR_PIEZO_PIN_DMP)) /* P1.09 -> P1.00 */ #define DMP_MASK (1UL << PIN_NUM(DR_PIEZO_PIN_DMP)) /* P1.00 방전 제어 */
/* Combined mask for all piezo control signals (excluding channel select) */ /* P1 포트에서 피에조 제어에 사용하는 핀들의 결합 마스크 (채널 선택 핀 제외) */
#define P1_CTRL_MASK (P_OUT_MASK | N_OUT_MASK | DMP_MASK) #define P1_CTRL_MASK (P_OUT_MASK | N_OUT_MASK | DMP_MASK)
/*
* 소프트웨어 버스트 - 기본 주파수 2.1MHz
*
* NOP 타이밍 계산:
* 2.1MHz → 주기 476ns, 반주기 238ns
* CPU 64MHz → 1 NOP = 15.625ns
* 첫 반주기: 14 NOP(≒219ns) + 레지스터 쓰기(≒30ns) = ≒249ns
* 둘째 반주기: 9 NOP(≒141ns) + 루프 오버헤드(≒47ns) + 레지스터 쓰기(≒30ns) = ≒218ns
* 합계: ≒436~476ns (≒2.1MHz)
*/
void dr_piezo_burst_sw(uint8_t cycles) void dr_piezo_burst_sw(uint8_t cycles)
{ {
/* Clamp cycles to valid range (1-20) */ /* Clamp cycles to valid range (1-20) */
@@ -838,6 +975,13 @@ void dr_piezo_burst_sw(uint8_t cycles)
* First half: 15 NOPs (~234ns) * First half: 15 NOPs (~234ns)
* Second half: 12 NOPs (~188ns) + loop overhead * Second half: 12 NOPs (~188ns) + loop overhead
*/ */
/*
* 소프트웨어 버스트 - 1.8MHz
* NOP 타이밍: 반주기 278ns
* 첫 반주기: 17 NOP(≒266ns) + 레지스터 쓰기(≒30ns) = ≒296ns
* 둘째 반주기: 11 NOP(≒172ns) + 루프 오버헤드(≒47ns) + 레지스터 쓰기(≒30ns) = ≒249ns
* 합계: ≒499~556ns (≒1.8MHz)
*/
void dr_piezo_burst_sw_18mhz(uint8_t cycles) void dr_piezo_burst_sw_18mhz(uint8_t cycles)
{ {
/* Clamp cycles to valid range (1-20) */ /* Clamp cycles to valid range (1-20) */
@@ -960,6 +1104,13 @@ void dr_piezo_burst_sw_18mhz(uint8_t cycles)
* Second half: 11 NOPs (~172ns) + loop overhead (~47ns) = ~219ns * Second half: 11 NOPs (~172ns) + loop overhead (~47ns) = ~219ns
* Total: ~468-500ns per cycle * Total: ~468-500ns per cycle
*/ */
/*
* 소프트웨어 버스트 - 2.0MHz
* NOP 타이밍: 반주기 250ns
* 첫 반주기: 15 NOP(≒234ns) + 레지스터 쓰기(≒30ns) = ≒264ns
* 둘째 반주기: 10 NOP(≒156ns) + 루프 오버헤드(≒47ns) + 레지스터 쓰기(≒30ns) = ≒233ns
* 합계: ≒468~500ns (≒2.0MHz)
*/
void dr_piezo_burst_sw_20mhz(uint8_t cycles) void dr_piezo_burst_sw_20mhz(uint8_t cycles)
{ {
/* Clamp cycles to valid range (1-20) */ /* Clamp cycles to valid range (1-20) */
@@ -1083,6 +1234,13 @@ void dr_piezo_burst_sw_20mhz(uint8_t cycles)
* Second half: 11 NOPs (~172ns) + loop overhead (~47ns) = ~219ns * Second half: 11 NOPs (~172ns) + loop overhead (~47ns) = ~219ns
* Total: ~468-500ns per cycle * Total: ~468-500ns per cycle
*/ */
/*
* 소프트웨어 버스트 - 1.9MHz
* NOP 타이밍: 반주기 263ns
* 첫 반주기: 15 NOP(≒234ns) + 레지스터 쓰기(≒30ns) = ≒264ns
* 둘째 반주기: 9 NOP(≒141ns) + 루프 오버헤드(≒47ns) + 레지스터 쓰기(≒30ns) = ≒218ns
* 합계: ≒468~500ns (≒1.9MHz)
*/
void dr_piezo_burst_sw_19mhz(uint8_t cycles) void dr_piezo_burst_sw_19mhz(uint8_t cycles)
{ {
/* Clamp cycles to valid range (1-20) */ /* Clamp cycles to valid range (1-20) */
@@ -1208,6 +1366,13 @@ void dr_piezo_burst_sw_19mhz(uint8_t cycles)
* Second half: 11 NOPs (~172ns) + loop overhead (~47ns) = ~219ns * Second half: 11 NOPs (~172ns) + loop overhead (~47ns) = ~219ns
* Total: ~452ns per cycle (~2.21 MHz) * Total: ~452ns per cycle (~2.21 MHz)
*/ */
/*
* 소프트웨어 버스트 - 2.2MHz
* NOP 타이밍: 반주기 227ns
* 첫 반주기: 13 NOP(≒203ns) + 레지스터 쓰기(≒30ns) = ≒233ns
* 둘째 반주기: 8 NOP(≒125ns) + 루프 오버헤드(≒47ns) + 레지스터 쓰기(≒30ns) = ≒202ns
* 합계: ≒435~454ns (≒2.2MHz)
*/
void dr_piezo_burst_sw_22mhz(uint8_t cycles) void dr_piezo_burst_sw_22mhz(uint8_t cycles)
{ {
/* Clamp cycles to valid range (1-20) */ /* Clamp cycles to valid range (1-20) */
@@ -1327,6 +1492,13 @@ void dr_piezo_burst_sw_22mhz(uint8_t cycles)
* Second half: 14 NOPs (~219ns) + loop overhead (~47ns) = ~266ns * Second half: 14 NOPs (~219ns) + loop overhead (~47ns) = ~266ns
* Total: ~532-588ns per cycle * Total: ~532-588ns per cycle
*/ */
/*
* 소프트웨어 버스트 - 1.7MHz
* NOP 타이밍: 반주기 294ns
* 첫 반주기: 18 NOP(≒281ns) + 레지스터 쓰기(≒30ns) = ≒311ns
* 둘째 반주기: 10 NOP(≒156ns) + 루프 오버헤드(≒47ns) + 레지스터 쓰기(≒30ns) = ≒233ns
* 합계: ≒532~588ns (≒1.7MHz)
*/
void dr_piezo_burst_sw_17mhz(uint8_t cycles) void dr_piezo_burst_sw_17mhz(uint8_t cycles)
{ {
/* Clamp cycles to valid range (1-20) */ /* Clamp cycles to valid range (1-20) */

View File

@@ -5,7 +5,7 @@
* @date 2025-12-09 * @date 2025-12-09
* *
* @note Hardware: nRF52840 + MD1822K6-G MOSFET Driver + TC7920K6-G MOSFET * @note Hardware: nRF52840 + MD1822K6-G MOSFET Driver + TC7920K6-G MOSFET
* Output: <EFBFBD>20V at 2MHz, 3~5 cycles * Output: +/-20V at 2MHz, 3~5 cycles
* *
* @details Timing Sequence: * @details Timing Sequence:
* 1. PE = HIGH (enable) * 1. PE = HIGH (enable)
@@ -17,6 +17,46 @@
* All signals (P_OUT, N_OUT, DMP) operate within PE HIGH period. * All signals (P_OUT, N_OUT, DMP) operate within PE HIGH period.
******************************************************************************/ ******************************************************************************/
/*******************************************************************************
* [한국어 설명] 피에조 초음파 트랜스듀서 드라이버 헤더
*
* === 개요 ===
* 방광 측정용 초음파 송신기의 핀 할당, 설정값, 함수 선언을 정의.
* nRF52840 + MD1822K6-G(MOSFET 드라이버) + TC7920K6-G(MOSFET) 하드웨어 구성.
* 출력: +/-20V, 2MHz, 3~7 사이클 버스트.
*
* === 핀 할당 ===
* 전원 제어:
* - DR_PIEZO_PWR_EN (P1.9): DC/DC 컨버터 활성화 -> +/-20V 고전압 생성
*
* 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
@@ -25,7 +65,8 @@
#include "nrf_gpio.h" #include "nrf_gpio.h"
/*============================================================================== /*==============================================================================
* POWER CONTROL PINS (DC/DC Converter +/-20V) * 전원 제어 핀 (DC/DC 컨버터 +/-20V)
* DR_PIEZO_PWR_EN: HIGH로 설정 시 DC/DC 컨버터가 +/-20V 고전압 생성
*============================================================================*/ *============================================================================*/
//#define DR_PIEZO_PWR_SHDN NRF_GPIO_PIN_MAP(0, 21) /**< SHDN_VPP/VNN (LT3463) */ // P0.21 : PZT_EN_MUXA jhChun 0128 //#define DR_PIEZO_PWR_SHDN NRF_GPIO_PIN_MAP(0, 21) /**< SHDN_VPP/VNN (LT3463) */ // P0.21 : PZT_EN_MUXA jhChun 0128
//#define DR_PIEZO_PWR_EN_10V NRF_GPIO_PIN_MAP(0, 22) /**< EN_+10V (MCP1804) */ // P0.22 : NIRS PIN //#define DR_PIEZO_PWR_EN_10V NRF_GPIO_PIN_MAP(0, 22) /**< EN_+10V (MCP1804) */ // P0.22 : NIRS PIN
@@ -33,7 +74,12 @@
#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) /** Power Enable jhChun 0128 */
/*============================================================================== /*==============================================================================
* TX SIGNAL PINS (MOSFET Driver Control) * TX 신호 핀 (MOSFET 드라이버 제어)
* PE: Pulse Enable - 전체 TX 시퀀스 활성화/비활성화
* DMP: Dump - 펄스 후 피에조 잔류 에너지 방전용
* P_OUT: Positive Output - 피에조 양극 구동 (N_OUT과 역상)
* N_OUT: Negative Output - 피에조 음극 구동 (P_OUT과 역상)
* 주의: 이전 핀 할당(주석 처리)에서 새 보드 레이아웃으로 변경됨 (jhChun 0128)
*============================================================================*/ *============================================================================*/
//#define DR_PIEZO_PIN_PE NRF_GPIO_PIN_MAP(1, 5) /**< Pulse Enable */ //#define DR_PIEZO_PIN_PE NRF_GPIO_PIN_MAP(1, 5) /**< Pulse Enable */
//#define DR_PIEZO_PIN_DMP NRF_GPIO_PIN_MAP(1, 9) /**< Dump control */ //#define DR_PIEZO_PIN_DMP NRF_GPIO_PIN_MAP(1, 9) /**< Dump control */
@@ -46,7 +92,11 @@
#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 */ // P1.2 -> P1.6 jhChun 0128
/*============================================================================== /*==============================================================================
* MUX CONTROL PINS (Echo Signal Path Selection) * MUX 제어 핀 (에코 신호 경로 선택)
* 8채널 아날로그 MUX로 피에조 센서 채널을 선택한다.
* MUXA(CH0~CH3)와 MUXB(CH4~CH7) 두 개의 4채널 MUX 사용.
* EN_MUXA/EN_MUXB: 각 MUX 활성화 (동시에 하나만 HIGH)
* SEL0/SEL1: MUX 내부 4채널 중 하나를 선택하는 주소 비트
*============================================================================*/ *============================================================================*/
//#define DR_PIEZO_MUX_SEL1 NRF_GPIO_PIN_MAP(1, 13) /**< MUX Select 1 (HIGH) */ //#define DR_PIEZO_MUX_SEL1 NRF_GPIO_PIN_MAP(1, 13) /**< MUX Select 1 (HIGH) */
//#define DR_PIEZO_MUX_SEL2 NRF_GPIO_PIN_MAP(1, 12) /**< MUX Select 2 (LOW) */ //#define DR_PIEZO_MUX_SEL2 NRF_GPIO_PIN_MAP(1, 12) /**< MUX Select 2 (LOW) */
@@ -59,7 +109,11 @@
#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. * @note Actual operating frequency is defined in dr_piezo.c as PIEZO_FREQ_MHZ.
@@ -73,7 +127,8 @@
#define DR_PIEZO_MUX_SETTLING_US 1300 /**< MUX settling delay (us) */ #define DR_PIEZO_MUX_SETTLING_US 1300 /**< MUX settling delay (us) */
/*============================================================================== /*==============================================================================
* POWER CONTROL FUNCTIONS * 전원 제어 함수
* DC/DC 컨버터(+/-20V)를 ON/OFF하여 피에조 구동 고전압을 제어한다.
*============================================================================*/ *============================================================================*/
/** /**
@@ -86,8 +141,15 @@ void dr_piezo_power_on(void);
*/ */
void dr_piezo_power_off(void); void dr_piezo_power_off(void);
/**
* @brief 피에조 전원 상태 확인
* @return true: 전원 ON, false: 전원 OFF
*/
bool dr_piezo_is_power_on(void);
/*============================================================================== /*==============================================================================
* TX DRIVER FUNCTIONS * TX 드라이버 함수
* 초음파 송신 관련: 초기화, 버스트 송신, 활성화/비활성화, 주파수 설정
*============================================================================*/ *============================================================================*/
/** /**
@@ -156,7 +218,12 @@ void dr_piezo_mux_init(void);
void dr_piezo_select_channel(uint8_t channel); void dr_piezo_select_channel(uint8_t channel);
/*============================================================================== /*==============================================================================
* SYSTEM FUNCTIONS (Power + TX combined) * 시스템 함수 (전원 + TX 통합 제어)
* 전원 ON/OFF와 TX 드라이버 초기화/해제를 한 번에 수행하는 편의 함수.
* 소프트웨어 버스트(burst_sw) 계열: CPU NOP 기반 정밀 타이밍.
* - Timer/PPI 없이 CPU에서 직접 GPIO를 제어
* - 인터럽트 비활성화 상태에서 동작하여 타이밍 정확도 보장
* - 주파수별 전용 함수 제공 (NOP 개수가 다름)
*============================================================================*/ *============================================================================*/
/** /**