diff --git a/.gitignore b/.gitignore index 635a99b..f73a3a4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ # build /build*/ +log.log diff --git a/.vscode/settings.json b/.vscode/settings.json index b1f813a..fe2359f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,4 +4,4 @@ "${workspaceFolder}/build": "Launch blinky/build", "${workspaceFolder}/build/mcuboot": "Launch blinky/build/mcuboot" } -} \ No newline at end of file +} diff --git a/README.md b/README.md index dce1e47..42212d3 100644 --- a/README.md +++ b/README.md @@ -1,217 +1,184 @@ -# VesiScan BASIC - Zephyr (nRF Connect SDK) +# VesiScan BASIC - Zephyr -nRF52840 기반 VesiScan BASIC 펌웨어의 Zephyr 포팅 프로젝트 - -## 현재 구현 상태 - -| Stage | 내용 | 상태 | -|-------|------|------| -| 1 | GPIO + 전원 래치 + 버튼 상태머신 | 완료 | -| 1 | LED 제어 (green/orange 패턴) | 완료 | -| 2 | BLE 스택 + NUS (advertising, 연결, 재연결) | 완료 | -| 3 | 배터리 ADC (AIN2, 12bit) | 완료 | -| 3 | IMU 내부 온도 센서 | 완료 | -| 3 | IMU ICM42670P (I2C0) | 완료 | -| 3 | 피에조 TX + mux 제어 | 완료 | -| 3 | ADC121S051 echo capture (nrfx SPIM3) | 완료 | -| 3 | BLE 커맨드 파서 (TAG+DATA+CRC16) | 진행중 | -| 4 | MCUboot + MCUmgr BLE DFU 설정 | 진행중 | -| 4 | Flash 저장소 (NVS/settings, piezo config) | 완료 | -| - | BLE 보안 (본딩, 패스키) | 미구현 | -| - | HW/SN/Passkey 저장/복원 | 미구현 | +- nRF52840 기반 VesiScan BASIC 펌웨어의 Zephyr/nRF Connect SDK 포팅 프로젝트 +- 현재 빌드 대상은 `nrf52840dk_nrf52840` 기준이며, MCUboot + BLE MCUmgr DFU, BLE NUS 명령, 배터리/IMU/피에조/echo ADC 측정 기능 포함 ## 프로젝트 구조 -``` -blinky/ -├── CMakeLists.txt # 빌드 설정 (소스 파일 등록) -├── prj.conf # Kconfig 설정 (기능 ON/OFF) +```text +VesiScan-Basic_Zephyr/ +├── CMakeLists.txt +├── prj.conf ├── boards/ -│ └── nrf52840dk_nrf52840.overlay # Devicetree Overlay (핀 매핑) -└── src/ - ├── main.c # 메인 진입점 + 부팅 시퀀스 + 버튼 상태머신 - ├── main.h # 전역 상수/변수/타입 정의 - ├── debug_print.h # 디버그 출력 매크로 (RTT) - ├── parser.c / .h # BLE 커맨드 파서 (TAG+DATA+CRC16) - ├── power_control.c / .h # 전원 시퀀스 상태머신 - ├── ble/ - │ └── ble_service.c / .h # BLE NUS 서비스 (advertising, 연결, TX/RX) - └── drivers/ - ├── battery/ - │ └── battery_adc.c / .h # 배터리 전압 ADC (AIN2, 12bit) - ├── echo_adc/ - │ └── echo_adc.c / .h # ADC121S051 echo capture (SPIM3) - ├── imu/ - │ └── imu_i2c.c / .h # ICM42670P IMU (I2C0) - ├── led/ - │ └── led_control.c / .h # LED 패턴 제어 (k_timer 기반) - ├── piezo/ - │ └── piezo.c / .h # 피에조 전원, mux, SW burst +│ ├── nrf52840dk_nrf52840.overlay +│ └── nrf52840dk_nrf52840.conf +├── sysbuild/ +│ ├── mcuboot.conf +│ ├── hci_ipc.conf +│ └── mcuboot_power_hold_module/ +├── src/ +│ ├── main.c / main.h # 전원, 버튼, 부팅, work queue +│ ├── app_nvs.c / app_nvs.h # settings/NVS 저장소 +│ ├── power_control.c / power_control.h +│ ├── ble +│ │ ├── ble_service.c / ble_service.h # BLE NUS, 광고, 보안, DFU 광고 전환 +│ ├── command/ +│ │ ├── handlers +│ │ ├── cmd_common.c / cmd_common.h +│ │ ├── cmd_table.c / cmd_table.h +│ │ └── parser.c / parser.h # BLE 명령 파서와 응답 생성 +│ ├── drivers/ +│ │ ├── battery/ # 배터리 ADC +│ │ ├── echo_adc/ # ADC121S051 SPIM capture +│ │ ├── imu/ # ICM42670P I2C/FIFO +│ │ ├── led/ # LED 패턴 +│ │ └── piezo/ # Piezo TX/MUX 제어 +│ └── measurement/ +│ └── piezo_measurement.c / piezo_measurement.h # Piezo burst ~ capture +├── tools/ +├── manual/ +└── curcuit/ ``` -## 빌드 방법 +## 빌드 -### 일반 빌드 -소스 파일(.c/.h)만 변경한 경우 +권장 빌드 (nRF Connect SDK / west 사용): -### Pristine Build -아래 파일을 변경한 경우 반드시 Pristine Build: -- `prj.conf` (Kconfig 설정) -- `boards/*.overlay` (Devicetree 핀 매핑) -- `CMakeLists.txt` (소스/include 경로 추가) +```powershell +west build -b nrf52840dk_nrf52840 -p auto +west flash +``` -nRF Connect for VS Code → Build 패널 → **Pristine Build** 또는 `build` 폴더 삭제 후 빌드 +또는 로컬에 맞춘 기존 `tools/` 스크립트 또는 VS Code Task를 사용 (`tools/flash_*.ps1`). -## 핀 매핑 (Devicetree Overlay) +빌드 결과로 `zephyr.elf`, `merged.hex` 및 versioned DFU zip 생성됨 -| 이름 | 핀 | 방향 | 역할 | -|------|-----|------|------| -| `PWR_HOLD` | P0.08 | Output, Active HIGH | 전원 래치 | -| `BUTTON_CHECK` | P1.08 | Input, Pull-up, Active LOW | 전원 버튼 | -| `LED_BLE` | P0.12 | Output, Active LOW | 초록 LED | -| `FUNCTION_LED` | P0.29 | Output, Active LOW | 주황 LED | -| ADC AIN2 | P0.04 | Analog Input | 배터리 전압 분압 | -| I2C0 SCL | P1.14 | I2C | ICM42670P SCL | -| I2C0 SDA | P1.15 | I2C | ICM42670P SDA | -| `PIEZO_PWR` | P1.09 | Output, Active HIGH | 피에조 전원 enable | -| `PIEZO_PE` | P0.25 | Output, Active HIGH | 피에조 pulse enable | -| `PIEZO_P_OUT` | P1.07 | Output | 피에조 P 출력 | -| `PIEZO_N_OUT` | P1.06 | Output | 피에조 N 출력 | -| `PIEZO_DMP` | P1.00 | Output | 피에조 dump | -| `MUX_EN_A` | P0.21 | Output | 피에조 mux enable A | -| `MUX_EN_B` | P0.23 | Output | 피에조 mux enable B | -| `MUX_SEL0` | P1.10 | Output | 피에조 mux select 0 | -| `MUX_SEL1` | P0.28 | Output | 피에조 mux select 1 | -| `ECHO_SCLK` | P0.14 | SPIM/GPIO | ADC121S051 SCLK | -| `ECHO_MISO` | P0.15 | SPIM/GPIO | ADC121S051 MISO | -| `ECHO_CS` | P0.19 | Output, Active LOW | ADC121S051 CS | +아래 파일을 변경한 경우에는 pristine build 필요: -> UART0은 P0.08 충돌 방지를 위해 비활성화 (`status = "disabled"`) +- `prj.conf` +- `boards/*.overlay` 또는 보드 설정 +- `CMakeLists.txt` / sysbuild 설정 -## 전원 버튼 동작 +## 주요 핀 매핑 + +| 이름 | 핀 | 역할 | +|---|---|---| +| `PWR_HOLD` | P0.08 | 전원 래치, active high | +| `BUTTON_CHECK` | P1.08 | 전원 버튼 | +| `LED_BLE` | P0.12 | 녹색 LED, active low | +| `FUNCTION_LED` | P0.29 | 주황 LED, active low | +| Battery ADC | P0.04 / AIN2 | 배터리 전압 측정 | +| I2C0 SCL | P1.14 | ICM42670P SCL | +| I2C0 SDA | P1.15 | ICM42670P SDA | +| `PIEZO_PWR` | P1.09 | 피에조 전원 enable | +| `PIEZO_PE` | P0.25 | 피에조 pulse enable | +| `PIEZO_P_OUT` | P1.07 | 피에조 P 출력 | +| `PIEZO_N_OUT` | P1.06 | 피에조 N 출력 | +| `PIEZO_DMP` | P1.00 | 피에조 dump | +| `MUX_EN_A` | P0.21 | MUX enable A | +| `MUX_EN_B` | P0.23 | MUX enable B | +| `MUX_SEL0` | P1.10 | MUX select 0 | +| `MUX_SEL1` | P0.28 | MUX select 1 | +| `ECHO_SCLK` | P0.14 | ADC121S051 SCLK | +| `ECHO_MISO` | P0.15 | ADC121S051 MISO | +| `ECHO_CS` | P0.19 | ADC121S051 CS | + +`CONFIG_SERIAL=n`으로 UART0를 비활성화하여 P0.08 충돌 피함 + +## 전원 동작 | 동작 | 조건 | 결과 | -|------|------|------| -| 부팅 (OFF→ON) | 2초 이상 홀드 | 전원 래치 + LED 깜빡임 + BLE advertising | -| 무시 | 부팅 후 2초 미만 릴리스 | 전원 OFF (래치 안 됨) | -| 전원 OFF (ON→OFF) | 버튼 놓은 뒤 다시 2초 홀드 | advertising 중지 + LED OFF + 3초 후 전원 차단 | +|---|---|---| +| 부팅 | 전원 버튼 2초 이상 유지 | P0.08 래치 ON, LED 광고 패턴, BLE 광고 시작 | +| 짧은 부팅 시도 | 부팅 중 2초 전 버튼 해제 | 래치 해제, 전원 OFF | +| 수동 전원 OFF | ON 상태에서 버튼 재누름 2초 이상 | 광고 중지, LED 표시 후 전원 래치 해제 | +| BLE 10분 광고 만료 | 최초 부팅 광고 또는 정상 disconnect 후 광고 | 광고 중지, 전원 OFF 예약 | +| DFU/soft reset 복귀 | DFU 확인/리셋 플래그 감지 | 버튼 없이 전원 유지 및 광고 복귀 | -## BLE 커맨드 프로토콜 +## BLE 기능 -### 패킷 포맷 +- Device name: `SERIAL_NO` 기반 +- Advertising interval: 40ms +- Normal advertising timeout: 10분 +- DFU advertising: SMP UUID, timeout 미적용 +- NUS UUID 광고 포함 +- 연결 시 LED 광고 패턴 OFF +- 연결 파라미터 선호값: 15ms / latency 0 / supervision timeout 10s +- NUS TX는 mutex + completion semaphore로 순차 전송 +- BLE RX는 별도 work queue에서 명령 처리 +- 보안 설정: fixed passkey, MITM, Secure Connections only + +## BLE 명령 포맷 + +```text +[TAG 4 bytes][DATA N bytes][CRC16 2 bytes] ``` -[TAG 4바이트] [DATA N바이트] [CRC16 2바이트] -``` -- TAG: ASCII 4글자 (예: `msn?`, `mfv?`) -- DATA: uint16 little-endian 값들 -- CRC16: CRC-CCITT (초기값 0xFFFF) -- 응답 TAG: 첫 글자 `m` → `r` (예: `msn?` → `rsn:`) -### 구현된 커맨드 +- TAG: ASCII 4바이트, 예: `msn?`, `mbb?` +- DATA: 명령별 payload +- CRC16: CRC-CCITT, initial `0xFFFF` +- 응답 TAG: 보통 요청의 `m`을 `r`로 바꾼 형태, 예: `msn?` -> `rsn:` +- 알 수 없는 명령은 `rxn:` 응답 -| 커맨드 | 응답 | 기능 | -|--------|------|------| -| `msn?` | `rsn:` + uint16 mV | 배터리 전압 측정 | -| `mls?` | `rls:` + state | LED 상태 변경 | -| `msp?` | `rsp:` + accel/gyro | IMU 단발 측정 | -| `mst?` | `rso:` + int16 cdeg | IMU 내부 온도 측정 | -| `mpa?` | `rpa:` + status | 피에조 전원 ON | -| `mpb?` | `rpb:` + status | 피에조 전원 OFF | -| `mpc?` | `rpc:` + cycles/status | 피에조 burst 테스트 | -| `mec?` | `reb:` + `raa:` | 단일 채널 echo capture (`reb:` includes session/ch info) | -| `mad?` | `reb:` + `raa:` | burst 없이 ADC capture (`reb:` includes session/ch info) | -| `maa?` | `reb:` x6 + `raa:` | 6채널 echo sweep (`reb:` includes session/ch info) | -| `mbb?` | `rbb:` + `reb:` x6 + `raa:` | 배터리+IMU+온도+6채널 echo (`reb:` includes session/ch info) | -| `mcf?` | `rcf:` | 측정 파라미터 읽기(현재 설정값 반환) | -| `mcs?` | `rcs:` + current config | 측정 파라미터 쓰기 + NVS 저장 | -| `mfv?` | `rfv:` | FW version 읽기 | -| `mrh?` | `rrh:` | HW version 읽기(RAM/default) | -| `mwh?` | `rwh:` | HW version 쓰기(RAM only) | -| `mrs?` | `rrs:` | Serial number 읽기(RAM/default) | -| `mws?` | `rws:` | Serial number 쓰기(RAM only) | -| `mqz?` | `rqz:` | Passkey 읽기(RAM/default) | -| `mpz?` | `rpz:` | Passkey 쓰기(RAM only) | +## 구현된 명령 -### 전체 커맨드 목록 +| 명령 | 응답 | 기능 | 상태/비고 | +|---|---|---|---| +| `msq?` | `rsq:` | 전원 OFF | 구현 완료 | +| `mss?` | `rss:` | 재부팅 | 구현 완료 | +| `msr?` | `rsr:` | 본딩 삭제 후 재부팅 | 구현 완료, SMP enabled 기준 | +| `mdf?` | `rdf:` | DFU 광고 모드 진입 | 구현 완료, NUS -> SMP UUID 전환 | +| `msn?` | `rsn:` | 배터리 전압 측정 | 구현 완료 | +| `mls?` | `rls:` | LED 상태 변경 | 구현 완료 | +| `msp?` | `rsp:` | IMU accel/gyro 단발 측정 | 구현 완료 | +| `mst?` | `rso:` | IMU internal temperature | 구현 완료 | +| `mpa?` | `rpa:` | 피에조 전원 ON | 구현 완료 | +| `mpb?` | `rpb:` | 피에조 전원 OFF | 구현 완료 | +| `mpc?` | `rpc:` | 피에조 burst 테스트 | 구현 완료, 주파수 옵션은 제한적 | +| `mec?` | `reb:` -> `raa:` | 단일 채널 burst + echo capture | 구현 완료 | +| `mad?` | `reb:` -> `raa:` | ADC only capture | 구현 완료 | +| `maa?` | `reb:` x6 -> `raa:` | 6채널 echo sweep | 구현 완료 | +| `mbb?` | `rbb:` -> `reb:` x6 -> `raa:` | 배터리 + IMU + 온도 + 6채널 echo | 구현 완료 | +| `mtb?` | `reb:` x6 + `rim:` -> `raa:` | 피에조 sweep + IMU FIFO | 구현 완료, 실측 검증 필요 | +| `mcf?` | `rcf:` | 측정 파라미터 읽기 | 구현 완료 | +| `mcs?` | `rcs:` | 측정 파라미터 쓰기 | 구현 완료, NVS 저장 | +| `mid?` | `rid:` | HW/SN/FW identity 읽기 | 구현 완료 | +| `mfv?` | `rfv:` | FW version 읽기 | 구현 완료 | +| `mwh?` | `rwh:` | HW version 쓰기 | 구현 완료, NVS 저장 | +| `mrh?` | `rrh:` | HW version 읽기 | 구현 완료 | +| `mws?` | `rws:` | Serial number 쓰기 | 구현 완료, NVS 저장 | +| `mrs?` | `rrs:` | Serial number 읽기 | 구현 완료 | +| `mpz?` | `rpz:` | Passkey 쓰기 | 구현 완료, NVS 저장, 최초 1회만 가능 | +| `mqz?` | `rqz:` | Passkey 읽기 | 구현 완료 | -| # | 내용 | 명령 | 응답 | 비고 | -|---|------|------|------|------| -| 1 | 전원 OFF | `msq?` | `rsq:` | 미구현 | -| 2 | 재부팅 | `mss?` | `rss:` | 미구현 | -| 3 | 본딩 삭제 + 재부팅 | `msr?` | `rsr:` | 미구현 | -| 4 | LED 상태 | `mls?` | `rls:` | 구현 완료 | -| 5 | HW Version 읽기 | `mrh?` | `rrh:` | 구현 완료, RAM/default | -| 6 | HW Version 쓰기 | `mwh?` | `rwh:` | 구현 완료, RAM only | -| 7 | Serial Number 읽기 | `mrs?` | `rrs:` | 구현 완료, RAM/default | -| 8 | Serial Number 쓰기 | `mws?` | `rws:` | 구현 완료, RAM only | -| 9 | FW Version 읽기 | `mfv?` | `rfv:` | 구현 완료 | -| 10 | Passkey 읽기 | `mqz?` | `rqz:` | 구현 완료, RAM/default | -| 11 | Passkey 쓰기 | `mpz?` | `rpz:` | 구현 완료, RAM only | -| 12 | 배터리 측정 | `msn?` | `rsn:` | 구현 완료 | -| 13 | IMU 단발 측정 | `msp?` | `rsp:` | 구현 완료 | -| 14 | 온도 측정 | `mst?` | `rso:` | 구현 완료 | -| 15 | 피에조 전원 ON | `mpa?` | `rpa:` | 구현 완료 | -| 16 | 피에조 전원 OFF | `mpb?` | `rpb:` | 구현 완료 | -| 17 | 피에조 burst 테스트 | `mpc?` | `rpc:` | 구현 완료, `freq_option`은 아직 미사용 | -| 18 | 단일 채널 측정 | `mec?` | `reb:` → `raa:` | 구현 완료, 테스트용 | -| 19 | ADC only 측정 | `mad?` | `reb:` → `raa:` | 구현 완료, burst 없음 | -| 20 | 모든 채널(6) 측정 | `maa?` | `reb:`(6개) → `raa:` | 구현 완료 | -| 21 | 전체 측정 | `mbb?` | `rbb:` → `reb:`(6개) → `raa:` | 구현 완료 | -| 22 | 측정 파라미터 읽기 | `mcf?` | `rcf:` | 구현 완료, 현재 설정값 반환 | -| 23 | 측정 파라미터 쓰기 | `mcs?` | `rcs:` | 구현 완료, NVS 저장 | +## 저장되는 설정 -## 남은 포팅 작업 +| 항목 | 저장소 key | 쓰기 명령 | 비고 | +|---|---|---|---| +| 피에조 측정 파라미터 | `vesiscan/piezo` | `mcs?` | validate 통과 시 복원 | +| HW version | `vesiscan/hw` | `mwh?` | 고정 길이 ASCII | +| Serial number | `vesiscan/serial` | `mws?` | 광고 name에도 사용 | +| Passkey | `vesiscan/passkey` | `mpz?` | 고정 길이 6자리 | +| Passkey 변경 플래그 | `vesiscan/passkey_changed` | `mpz?` | 1이면 추가 변경 차단 | +| BLE bonding/settings | Zephyr `bt` subtree | BLE stack | `CONFIG_BT_SETTINGS=y` | -| 항목 | 현재 상태 | 다음 작업 | -|------|-----------|-----------| -| NVS/settings | piezo config 저장/복원 구현 완료 | HW/SN/Passkey 저장으로 확장 | -| 측정 파라미터 | `piezo_config` 구조체 사용 | 실제 하드웨어에서 값별 파형 검증 | -| `mcs?` | 구현 완료 | 앱/테스트툴에서 쓰기 후 재부팅 유지 확인 | -| HW/SN/Passkey 저장 | RAM only | `mwh?`, `mws?`, `mpz?` 값을 NVS에 저장 | -| BLE 보안 | `CONFIG_BT_SMP=n`, dev mode | bonding, fixed passkey, MITM/LESC 적용 | -| BLE DFU | MCUboot/mcumgr 설정 있음 | SMP 광고/업로드/리셋 후 confirm 검증 | - -## 디버그 로그 (RTT) - -- **VS Code**: nRF Connect 패널 → Connected Devices → 보드 선택 → 터미널 아이콘 -- **J-Link RTT Viewer**: Connection: USB, Target Device: NRF52840_XXAA - -### 로그 태그 -| 태그 | 내용 | -|------|------| -| `[BTN]` | 버튼 이벤트 (2s 홀드, 릴리스) | -| `[PWR]` | 전원 래치 ON/OFF | -| `[BOOT]` | 부팅 시퀀스 완료 | -| `[BLE]` | BLE 연결/해제/advertising/파라미터 | -| `[BLE RX]` | NUS 수신 데이터 (hex dump) | -| `[NUS TX]` | NUS 송신 | -| `[CMD]` | 커맨드 파서 처리 결과 | -| `[BATT]` | 배터리 ADC 측정값 | -| `[IMU]` | IMU 초기화/측정 | -| `[ECHO]` | ADC121S051 echo capture | -| `[PIEZO]` | 피에조 mux/burst/power | -| `[MBB]` | 전체 측정 시퀀스 | -| `[DFU]` | MCUboot 이미지 상태 | -| `[SYS]` | 슬립 진입 | - -## prj.conf 주요 설정 +## 주요 prj.conf 설정 | 설정 | 값 | 역할 | -|------|-----|------| -| `CONFIG_GPIO` | y | GPIO 드라이버 | -| `CONFIG_LOG` | y | 로깅 시스템 | -| `CONFIG_LOG_BACKEND_RTT` | y | RTT 로그 출력 | -| `CONFIG_SERIAL` | n | UART 비활성화 (P0.08 충돌 방지) | -| `CONFIG_BT` | y | BLE 스택 | -| `CONFIG_BT_NUS` | y | Nordic UART Service | -| `CONFIG_BT_L2CAP_TX_MTU` | 247 | MTU 크기 | -| `CONFIG_BT_CTLR_TX_PWR_PLUS_8` | y | TX power +8dBm | -| `CONFIG_ADC` | y | ADC 드라이버 (배터리) | -| `CONFIG_I2C` | y | IMU I2C 드라이버 | -| `CONFIG_NRFX_SPIM` | y | ADC121S051용 nrfx SPIM | -| `CONFIG_BOOTLOADER_MCUBOOT` | y | MCUboot 사용 | -| `CONFIG_MCUMGR_TRANSPORT_BT` | y | BLE mcumgr transport | -| `CONFIG_NVS` | y | NVS 플래시 저장소 | -| `CONFIG_SETTINGS` | y | Zephyr settings subsystem | -| `CONFIG_SETTINGS_NVS` | y | settings NVS backend | -| `CONFIG_BT_SMP` | n | BLE 보안 비활성화(개발 모드) | +|---|---|---| +| `CONFIG_SERIAL` | `n` | UART 비활성화, P0.08 충돌 방지 | +| `CONFIG_BT` | `y` | BLE stack | +| `CONFIG_BT_NUS` | `y` | Nordic UART Service | +| `CONFIG_BT_SMP` | `y` | BLE pairing/security | +| `CONFIG_BT_SMP_ENFORCE_MITM` | `y` | MITM 요구 | +| `CONFIG_BT_SMP_SC_ONLY` | `y` | LE Secure Connections only | +| `CONFIG_BT_SETTINGS` | `y` | BLE bonding/settings 저장 | +| `CONFIG_BT_L2CAP_TX_MTU` | `247` | 큰 NUS 패킷 전송 | +| `CONFIG_ADC` | `y` | 배터리 ADC | +| `CONFIG_I2C` | `y` | IMU I2C | +| `CONFIG_NRFX_SPIM` | `y` | ADC121S051 SPIM capture | +| `CONFIG_BOOTLOADER_MCUBOOT` | `y` | MCUboot 사용 | +| `CONFIG_MCUMGR_TRANSPORT_BT` | `y` | BLE DFU transport | +| `CONFIG_NVS` | `y` | NVS 저장소 | +| `CONFIG_SETTINGS` | `y` | Zephyr settings subsystem | +| `CONFIG_SETTINGS_NVS` | `y` | settings NVS backend | diff --git a/boards/nrf5340dk_nrf5340_cpuapp.overlay b/boards/nrf5340dk_nrf5340_cpuapp.overlay deleted file mode 100644 index 50599b2..0000000 --- a/boards/nrf5340dk_nrf5340_cpuapp.overlay +++ /dev/null @@ -1,185 +0,0 @@ -/* - * VesiScan BASIC - nRF5340 DK application core overlay - * - * Keeps the same external signal mapping used by the nRF52840 build: - * POWER_HOLD: P0.08 - * POWER_BTN: P1.08 - * LED_BLE: P0.12 - * LED_ORANGE: P0.29 - * BATT_ADC: P0.04 - * IMU_SCL: P1.14 - * IMU_SDA: P1.15 - */ - -#include -#include -#include - -&pinctrl -{ - i2c0_default: i2c0_default - { - group1 - { - psels = , - ; - }; - }; - - i2c0_sleep: i2c0_sleep - { - group1 - { - psels = , - ; - low-power-enable; - }; - }; -}; - -&i2c0 -{ - status = "okay"; - clock-frequency = ; - pinctrl-0 = <&i2c0_default>; - pinctrl-1 = <&i2c0_sleep>; - pinctrl-names = "default", "sleep"; -}; - -&adc -{ - status = "okay"; - #address-cells = <1>; - #size-cells = <0>; - - channel@0 - { - reg = <0>; - zephyr,gain = "ADC_GAIN_1_6"; - zephyr,reference = "ADC_REF_INTERNAL"; - zephyr,acquisition-time = ; - zephyr,input-positive = ; - zephyr,resolution = <12>; - zephyr,oversampling = <2>; - }; - -}; - -&arduino_adc -{ - status = "disabled"; -}; - -/ { - zephyr,user - { - io-channels = <&adc 0>; - io-channel-names = "battery"; - }; - - led - { - compatible = "gpio-leds"; - - LED_BLE: LED_BLE - { - gpios = <&gpio0 12 GPIO_ACTIVE_LOW>; - label = "Green LED"; - }; - - FUNCTION_LED: FUNCTION_LED - { - gpios = <&gpio0 29 GPIO_ACTIVE_LOW>; - label = "Orange LED"; - }; - }; - - pin - { - compatible = "gpio-keys"; - - PWR_HOLD: PWR_HOLD - { - gpios = <&gpio0 8 GPIO_ACTIVE_HIGH>; - label = "Power Hold Latch"; - }; - - BUTTON_CHECK: BUTTON_CHECK - { - gpios = <&gpio1 8 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; - label = "Power Button"; - }; - - PIEZO_PWR: PIEZO_PWR - { - gpios = <&gpio1 9 GPIO_ACTIVE_HIGH>; - label = "Piezo Power Enable"; - }; - - PIEZO_PE: PIEZO_PE - { - gpios = <&gpio0 25 GPIO_ACTIVE_HIGH>; - label = "Piezo Pulse Enable"; - }; - - PIEZO_P_OUT: PIEZO_P_OUT - { - gpios = <&gpio1 7 GPIO_ACTIVE_HIGH>; - label = "Piezo P Out"; - }; - - PIEZO_N_OUT: PIEZO_N_OUT - { - gpios = <&gpio1 6 GPIO_ACTIVE_HIGH>; - label = "Piezo N Out"; - }; - - PIEZO_DMP: PIEZO_DMP - { - gpios = <&gpio1 0 GPIO_ACTIVE_HIGH>; - label = "Piezo Dump"; - }; - - MUX_EN_A: MUX_EN_A - { - gpios = <&gpio0 21 GPIO_ACTIVE_HIGH>; - label = "Mux Enable A"; - }; - - MUX_EN_B: MUX_EN_B - { - gpios = <&gpio0 23 GPIO_ACTIVE_HIGH>; - label = "Mux Enable B"; - }; - - MUX_SEL0: MUX_SEL0 - { - gpios = <&gpio1 10 GPIO_ACTIVE_HIGH>; - label = "Mux Select 0"; - }; - - MUX_SEL1: MUX_SEL1 - { - gpios = <&gpio0 28 GPIO_ACTIVE_HIGH>; - label = "Mux Select 1"; - }; - - ECHO_SCLK: ECHO_SCLK - { - gpios = <&gpio0 14 0>; - label = "Echo ADC SCLK"; - }; - - ECHO_MISO: ECHO_MISO - { - gpios = <&gpio0 15 0>; - label = "Echo ADC MISO"; - }; - - ECHO_CS: ECHO_CS - { - gpios = <&gpio0 19 GPIO_ACTIVE_LOW>; - label = "Echo ADC CS"; - }; - }; -}; diff --git a/plan/TODO.md b/plan/TODO.md deleted file mode 100644 index f6e003c..0000000 --- a/plan/TODO.md +++ /dev/null @@ -1,118 +0,0 @@ -# TODO - -현재 프로젝트에서 다음에 이어서 작업할 항목들을 정리한다. - -## 현재 상태 - -- BLE 보안은 아직 개발 모드 기준이다. - - `CONFIG_BT_SMP=n`: [prj.conf](/C:/jhChun/blinky/prj.conf:42) - - `BLE_DEV_MODE = 1`: [src/ble/ble_service.h](/C:/jhChun/blinky/src/ble/ble_service.h:18) - - `Dev mode: no security / Production mode: bonding + passkey (TODO)`: [src/ble/ble_service.c](/C:/jhChun/blinky/src/ble/ble_service.c:10) -- 플래시 저장소는 아직 미구현이다. - - `Phase 3: FDS/NVS (TODO)`: [src/main.c](/C:/jhChun/blinky/src/main.c:328) -- DFU는 아직 미구현이다. - - `CONFIG_BOOTLOADER_MCUBOOT` 미설정: [build/blinky/zephyr/.config](/C:/jhChun/blinky/build/blinky/zephyr/.config:94) - - 파티션도 앱 단일 영역만 사용 중: [build/partitions.yml](/C:/jhChun/blinky/build/partitions.yml:1) -- `mcf?`는 현재 고정값 반환만 구현되어 있다. - - `freq=1`, `cycles=7`, `avg=10`, `delay=10`, `samples=100`: [src/parser.c](/C:/jhChun/blinky/src/parser.c:38) -- 실제 측정도 현재 위 고정값을 사용한다. - - `avg=10`: [src/parser.c](/C:/jhChun/blinky/src/parser.c:28) - - `cycles=7`, `delay=10us`: [src/drivers/piezo/piezo.h](/C:/jhChun/blinky/src/drivers/piezo/piezo.h:12) - - `samples=100`: [src/drivers/echo_adc/echo_adc.h](/C:/jhChun/blinky/src/drivers/echo_adc/echo_adc.h:11) - -## 1. BLE 보안 - -목표: -- LESC -- MITM -- Fixed Passkey -- Bonding - -해야 할 일: -- `CONFIG_BT_SMP=y` -- `CONFIG_BT_FIXED_PASSKEY=y` -- `CONFIG_BT_BONDABLE=y` -- `CONFIG_BT_SMP_ENFORCE_MITM=y` -- 필요 시 `CONFIG_BT_SMP_SC_ONLY=y` 또는 `CONFIG_BT_SMP_SC_PAIR_ONLY=y` -- `bt_conn_auth_cb` 등록 -- 고정 패스키를 `m_static_passkey`와 연결 - - 기본값 선언: [src/main.h](/C:/jhChun/blinky/src/main.h:21) - - RAM 보관: [src/main.c](/C:/jhChun/blinky/src/main.c:56) -- 본딩 키 복원을 위해 `BT_SETTINGS + SETTINGS + NVS` 연동 - -주의: -- Bonding은 저장소 없이 켜도 부팅 후 유지되지 않는다. -- 보안 설정을 올리면 앱 테스트 절차도 함께 바뀐다. - -## 2. 플래시 저장소 (NVS) - -목표: -- 예전 FDS 역할을 Zephyr/NCS 방식으로 대체 -- 측정 파라미터, passkey, 필요 시 serial/HW info 저장 - -권장 방향: -- FDS를 다시 붙이기보다 `NVS` 사용 -- Bluetooth bonding 키는 `BT_SETTINGS`가 저장하고 -- 앱 파라미터는 별도 `NVS` 또는 `settings/NVS backend`로 저장 - -우선 저장할 후보: -- piezo config - - `freq` - - `cycles` - - `avg` - - `delay_us` - - `samples` -- passkey -- 필요 시 `SERIAL_NO`, `HW_NO` - -해야 할 일: -- `piezo_config` 구조체 추가 -- 부팅 시 기본값 로드 후 NVS 값이 있으면 덮어쓰기 -- `mcf?`는 구조체 읽기 -- `mcs?`는 구조체 갱신 + 저장 -- 유효 범위 체크 추가 - -현재 메모: -- `mcf?`는 아직 고정 응답만 구현됨: [src/parser.c](/C:/jhChun/blinky/src/parser.c:442) -- `mcs?`는 아직 없음: [src/parser.c](/C:/jhChun/blinky/src/parser.c:503) - -## 3. DFU - -목표: -- BLE 기반 펌웨어 업데이트 지원 - -권장 방향: -- `MCUboot + MCUmgr SMP over BLE` - -해야 할 일: -- `CONFIG_BOOTLOADER_MCUBOOT=y` -- `CONFIG_MCUMGR=y` -- `CONFIG_MCUMGR_TRANSPORT_BT=y` -- `CONFIG_IMG_MANAGER=y` -- 필요 시 `CONFIG_MCUMGR_GRP_IMG=y` -- 파티션 구성 추가 - - `slot0` - - `slot1` - - 필요 시 `scratch` 또는 overwrite-only 전략 -- 광고/서비스에 SMP transport가 정상 노출되는지 확인 - -주의: -- 지금 프로젝트는 NUS만 사용 중이라 일반적인 OTA DFU는 바로 안 된다. -- DFU를 넣으면 파티션 구조와 빌드 방식이 함께 바뀐다. - -## 4. 권장 작업 순서 - -1. `piezo_config` 구조체 도입 -2. `mcf?`를 고정값이 아닌 실제 구조체 반환으로 변경 -3. `mcs?` 구현 -4. `NVS` 저장/복원 구현 -5. BLE 보안 적용 -6. Bonding 저장 확인 -7. 마지막에 DFU 적용 - -## 5. 체크 포인트 - -- `mcf?` 값과 실제 측정값이 항상 같아야 함 -- `mcs?`로 바꾼 값이 재부팅 후에도 유지되어야 함 -- 보안 적용 후 앱에서 pairing/passkey 흐름 확인 -- DFU 적용 후 기존 NUS 기능과 충돌 없는지 확인 diff --git a/plan/adc_zero_capture_notes.md b/plan/adc_zero_capture_notes.md deleted file mode 100644 index b5bf1b8..0000000 --- a/plan/adc_zero_capture_notes.md +++ /dev/null @@ -1,121 +0,0 @@ -# ADC 캡처 데이터가 전부 0으로 나온 상황 정리 - -작성일: 2026-06-11 - -## 배경 - -ADC echo 파형이 그래프 앞쪽으로 압축되어 보이는 문제가 있었다. - -원인 후보로 `src/drivers/echo_adc/echo_adc.c`에서 2바이트 샘플마다 `nrfx_spim_xfer()`를 한 번씩 호출하는 구조가 지목되었다. 이 구조는 샘플마다 드라이버 호출/대기/CS 처리 오버헤드가 붙기 때문에 실제 샘플 간격이 예상보다 커질 수 있다. - -그래서 샘플 간격을 줄이기 위해, 기존 레퍼런스 펌웨어처럼 SPIM 레지스터를 직접 만지는 hot loop 방식으로 바꾸는 실험을 했다. - -## 현재 정상 동작 기준 - -현재 정상 동작하는 기준 코드는 샘플 1개마다 아래 경로를 사용한다. - -```c -nrfx_spim_xfer_desc_t xfer = NRFX_SPIM_XFER_RX(buf, len); -nrfx_spim_xfer(&echo_spim, &xfer, 0); -``` - -그리고 `echo_adc_init()`에서는 CS 핀을 SPIM의 hardware SS 핀으로 넘긴다. - -```c -nrfx_spim_config_t config = NRFX_SPIM_DEFAULT_CONFIG( - gpio_abs_pin(&echo_sclk), - NRF_SPIM_PIN_NOT_CONNECTED, - gpio_abs_pin(&echo_miso), - gpio_abs_pin(&echo_cs)); -``` - -실패한 실험 뒤에는 이 정상 동작 기준으로 원복했다. - -## 실패 실험 1: SPIM SS를 끊고 CS를 GPIO로 수동 제어 - -`echo_adc_init()`에서 SPIM이 CS 핀을 소유하지 않도록 바꿨다. - -```c -NRF_SPIM_PIN_NOT_CONNECTED -``` - -즉 원래 있던 아래 설정을 제거했다. - -```c -gpio_abs_pin(&echo_cs) -``` - -그 뒤 CS를 GPIO 출력으로 직접 설정하고, `nrfx_spim_xfer()`와 direct capture loop 양쪽에서 수동으로 CS를 내리고 올렸다. - -```c -cs_port->OUTCLR = cs_mask; -nrfx_spim_xfer(&echo_spim, &xfer, 0); -cs_port->OUTSET = cs_mask; -``` - -캡처 루프도 아래처럼 SPIM 레지스터 직접 접근 방식으로 바꿨다. - -```c -spim->RXD.MAXCNT = 2U; - -for (...) { - cs_port->OUTCLR = cs_mask; - spim->RXD.PTR = (uint32_t)&raw_capture[i * 2U]; - spim->EVENTS_END = 0; - spim->TASKS_START = 1; - while (!spim->EVENTS_END) { - } - cs_port->OUTSET = cs_mask; -} -``` - -결과/위험: - -- ADC 데이터가 전부 0으로 나올 수 있다. -- 가능한 원인은 CS/SPIM 소유권 불일치, RX-only direct start가 `nrfx_spim_xfer()`와 다르게 동작하는 문제, 또는 nrfx가 내부적으로 준비하던 SPIM 상태가 빠진 문제다. -- 이 단순한 "SS 끊기 + direct loop" 패치는 그대로 쓰면 안 된다. - -## 실패 실험 2: Hardware SS를 유지한 채 같은 CS를 수동 토글 - -`echo_cs`는 계속 SPIM hardware SS 핀으로 설정해둔 상태에서, `echo_adc_capture()` 내부만 direct register loop로 바꿨다. - -예시: - -```c -NRF_SPIM_Type *spim = echo_spim.p_reg; -uint32_t cs_mask = BIT(echo_cs.pin); - -spim->RXD.MAXCNT = 2U; - -for (...) { - NRF_P0->OUTCLR = cs_mask; - spim->RXD.PTR = (uint32_t)&raw_capture[i * 2U]; - spim->EVENTS_END = 0; - spim->TASKS_START = 1; - while (!spim->EVENTS_END) { - } - NRF_P0->OUTSET = cs_mask; -} -``` - -결과/위험: - -- 이 경우도 ADC 데이터가 전부 0으로 나올 수 있다. -- 이유는 hardware SS가 켜진 상태에서 펌웨어가 같은 CS 핀을 직접 `NRF_P0->OUTCLR/OUTSET`으로 건드리기 때문이다. -- 즉 SPIM hardware SS 제어와 수동 GPIO CS 제어가 같은 핀에서 겹쳐 ADC 프레임 타이밍이 깨질 수 있다. -- hardware SS를 켠 상태에서 같은 CS 핀을 수동으로 토글하면 안 된다. - -## 핵심 정리 - -레퍼런스 펌웨어의 direct SPIM loop를 이 Zephyr/nrfx 포팅 코드에 내부 루프만 바꿔서 그대로 넣으면 안 된다. - -더 빠른 캡처 경로가 필요하면, 아래를 포함한 별도 low-level SPIM 경로로 제대로 구성해야 한다. - -- CS 소유권을 명확히 정하기: hardware SS 또는 manual GPIO 중 하나만 사용 -- SPIM PSEL 상태 확인 -- SCLK 생성을 보장하기 위한 RXD/TXD 설정 확인 -- `RXD.MAXCNT = 2` -- `EVENTS_END` 클리어 및 polling -- CS 타이밍 검증 - -그 전까지는 정상 동작이 확인된 `nrfx_spim_xfer()` 경로를 유지한다. diff --git a/plan/measurement_pipeline.md b/plan/measurement_pipeline.md deleted file mode 100644 index db22e13..0000000 --- a/plan/measurement_pipeline.md +++ /dev/null @@ -1,197 +0,0 @@ -# VesiScan BASIC — 초음파 측정 파이프라인 (Zephyr 포팅 계획) - -## 1. 하드웨어 구성 - -| 역할 | 칩 | 핀 | -|---|---|---| -| 피에조 전원 | DC/DC 컨버터 (+/-20V) | PWR_EN: P1.9 | -| TX Pulse Enable | MOSFET 드라이버 | PE: P0.25 | -| TX 양극 출력 | MOSFET | P_OUT: P1.7 | -| TX 음극 출력 | MOSFET | N_OUT: P1.6 | -| TX 방전 | Dump 회로 | DMP: P1.0 | -| 채널 선택 | 8ch 아날로그 MUX | EN_MUXA: P0.21, EN_MUXB: P0.23, SEL0: P1.10, SEL1: P0.28 | -| Echo ADC | ADC121S051 (TI, 12-bit) | SCLK: P0.14, MISO: P0.15, CS: P0.19 | - -### MUX 채널 매핑 (6채널 사용) - -| CH | EN_MUXA | EN_MUXB | SEL0 | SEL1 | -|---|---|---|---|---| -| 0 | 1 | 0 | 0 | 0 | -| 1 | 1 | 0 | 1 | 0 | -| 2 | 1 | 0 | 0 | 1 | -| 3 | 1 | 0 | 1 | 1 | -| 4 | 0 | 1 | 0 | 0 | -| 5 | 0 | 1 | 1 | 0 | - ---- - -## 2. 전체 측정 흐름 - -``` -[측정 커맨드 수신] - │ - ▼ -piezo_power_on() - └─ PWR_EN = HIGH - └─ 3ms 안정화 대기 - -echo_adc_init() - └─ SPIM3 초기화 (16MHz, SPI Mode 3, CPOL=1 CPHA=1) - └─ dummy read 1회 (ADC wake-up) - - │ - ▼ -┌─── for ch = 0 ~ 5 ───────────────────────────────────────────┐ -│ │ -│ piezo_select_channel(ch) │ -│ └─ MUX 핀 설정 │ -│ └─ 1300us settling 대기 │ -│ │ -│ [averaging 횟수만큼 반복] (기본 3회) │ -│ piezo_burst_sw(cycles) ← SW NOP 버스트 │ -│ ├─ __disable_irq() │ -│ ├─ PE ON │ -│ ├─ for i in cycles: │ -│ │ P_OUT=HIGH, N_OUT=LOW (NOP × 14) ┐ 2.1MHz │ -│ │ P_OUT=LOW, N_OUT=HIGH (NOP × 9) ┘ │ -│ ├─ DMP 펄스 (500ns) │ -│ ├─ PE OFF │ -│ └─ __enable_irq() │ -│ │ -│ delay_us 대기 (기본 10us) ← 버스트 → ADC 시작 딜레이 │ -│ │ -│ echo_adc_capture(buf, num_samples) ← SPIM3 연속 읽기 │ -│ └─ 100샘플 × 2byte = 200byte │ -│ └─ 소요시간: ~100us (16MHz SPI) │ -│ │ -│ 샘플 합산 (averaging용) │ -│ │ -│ averaging 나눗셈 → channel_buf[ch] 저장 │ -│ │ -└───────────────────────────────────────────────────────────────┘ - │ - ▼ -BLE 전송 (채널별) - reb: [샘플수(2B)] [raw data (200B)] + CRC16 ← 채널당 1패킷 - ... - raa: [상태(2B)] + CRC16 ← 완료 패킷 - - │ - ▼ -piezo_power_off() - └─ PE, P_OUT, N_OUT, DMP = LOW - └─ MUX EN 핀 = LOW - └─ PWR_EN = LOW -``` - ---- - -## 3. 타이밍 정리 - -| 구간 | 시간 | 비고 | -|---|---|---| -| 전원 안정화 | 3ms | DC/DC +/-20V 안정화 | -| MUX settling | 1.3ms | 채널 전환 후 아날로그 경로 안정화 | -| SW 버스트 (5 cycles @ 2.1MHz) | ~2.4µs | __disable_irq() 구간 | -| burst → ADC 딜레이 | 20µs (기본) | 에코 신호 도달 대기 | -| ADC 캡처 (100샘플 @ 16MHz SPI) | ~100µs | 1샘플 = 16 SCLK = 1µs | -| 채널당 총 측정 시간 (averaging 3회) | ~4.4ms + 3ms | MUX + (burst+딜레이+ADC) × 3 | -| 6채널 전체 측정 | ~30ms | 채널 × 6 | - ---- - -## 4. ADC121S051 SPI 프레임 - -``` -CS ────┐ ┌──── - └────────────────────┘ - -SCLK ────┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ──── (idle HIGH, 16 clocks) - └─┘ └─┘ └─ ... └─┘ └─┘ - -DATA ----...---- - │← 3 zeros →│← 12 data bits →│ - -변환 공식: value = (raw16 >> 1) & 0x0FFF -``` - -- SPI Mode 3: CPOL=1(idle HIGH), CPHA=1(rising edge sample) -- 16MHz clock → 1샘플 = 16 clocks = 1µs -- 12-bit 출력 (0 ~ 4095), VREF = 3.3V -- 1LSB = 3300mV / 4096 ≈ 0.8mV - ---- - -## 5. BLE 응답 패킷 포맷 - -### reb: (echo raw data) -``` -[r][e][b][:] [num_samples H][num_samples L] [s0_H][s0_L] [s1_H][s1_L] ... [CRC_L][CRC_H] - 4 bytes 2 bytes num_samples × 2 bytes 2 bytes -``` -- 100샘플 기준: 4 + 2 + 200 + 2 = **208 bytes** -- BLE MTU 247byte 이내이므로 단일 패킷으로 전송 가능 - -### raa: (완료) -``` -[r][a][a][:] [status H][status L] [CRC_L][CRC_H] - 4 bytes 2 bytes 2 bytes = 8 bytes -``` -- status 0x0000 = 성공 - ---- - -## 6. Zephyr 구현 계획 - -### 신규 파일 - -| 파일 | 내용 | -|---|---| -| `src/drivers/piezo/piezo.c` | SW 버스트, MUX 제어, 전원 제어 | -| `src/drivers/piezo/piezo.h` | 공개 API 선언 | -| `src/drivers/echo_adc/echo_adc.c` | ADC121S051 SPIM3 드라이버 | -| `src/drivers/echo_adc/echo_adc.h` | 공개 API 선언 | - -### 수정 파일 - -| 파일 | 변경 내용 | -|---|---| -| `boards/nrf52840dk_nrf52840.overlay` | 피에조/MUX GPIO, SPI 핀 비활성화 충돌 처리 | -| `prj.conf` | `CONFIG_NRFX_GPIOTE=y`, `CONFIG_NRFX_SPIM3=y` 추가 | -| `CMakeLists.txt` | 신규 소스/인클루드 추가 | -| `src/parser.c` | `maa?` 커맨드 핸들러 추가 | - -### 기존 SDK → Zephyr 변환표 - -| nRF5 SDK | Zephyr | -|---|---| -| `#include "nrf_gpio.h"` | `#include ` | -| `#include "nrf_gpiote.h"` | `#include ` | -| `#include "nrfx_spim.h"` | `#include ` | -| `nrf_delay_ms(x)` | `k_msleep(x)` | -| `nrf_delay_us(x)` | `k_busy_wait(x)` | -| `NRF_P0->OUTSET/OUTCLR` | 동일 (레지스터 직접 접근) | -| `__disable_irq()` / `__NOP()` | 동일 (ARM CMSIS) | -| `NRFX_SPIM_INSTANCE(3)` | 동일 | - -### SW 버스트 NOP 타이밍 (변경 없음) - -CPU 64MHz → 1 NOP = 15.625ns. nRF5 SDK, Zephyr 모두 동일하므로 NOP 개수 수정 불필요. - -| 주파수 | 반주기 | 1st half NOP | 2nd half NOP | -|---|---|---|---| -| 1.7 MHz | 294ns | 18 | 10 | -| 1.8 MHz | 278ns | 17 | 11 | -| 1.9 MHz | 263ns | 15 | 9 | -| 2.0 MHz | 250ns | 15 | 10 | -| 2.1 MHz | 238ns | 14 | 9 | -| 2.2 MHz | 227ns | 13 | 8 | - ---- - -## 7. 구현 순서 (권장) - -1. `piezo.c` — GPIO 초기화, MUX, 전원, SW 버스트 -2. `echo_adc.c` — SPIM3 초기화, 단일 샘플 읽기, 연속 캡처 -3. `parser.c` — `maa?` 커맨드 핸들러 (6채널 측정 → BLE 전송) -4. 빌드 검증 및 오실로스코프로 타이밍 확인 diff --git a/plan/system_pipeline.md b/plan/system_pipeline.md deleted file mode 100644 index dbe65bc..0000000 --- a/plan/system_pipeline.md +++ /dev/null @@ -1,425 +0,0 @@ -# VesiScan-Basic — 시스템 전체 파이프라인 - -## 목차 - -1. [전체 동작 개요](#1-전체-동작-개요) -2. [부팅 및 BLE 연결 흐름](#2-부팅-및-ble-연결-흐름) -3. [커맨드/응답 테이블](#3-커맨드응답-테이블) -4. [msp? 흐름 (1초 주기 IMU)](#4-msp-흐름-1초-주기-imu) -5. [mbb? 흐름 (10초 주기 전체 측정)](#5-mbb-흐름-10초-주기-전체-측정) -6. [구현 현황](#6-구현-현황) -7. [핀 배치 전체 정리](#7-핀-배치-전체-정리) -8. [IMU 테스트 가이드](#8-imu-테스트-가이드-msp) -9. [다음 구현 순서](#9-다음-구현-순서) - ---- - -## 1. 전체 동작 개요 - -``` -[앱] ──BLE NUS── [디바이스] - │ │ - │── msp? (1초마다) ──▶ │ IMU 측정 → rsp: (accel+gyro) - │ │ - │── mbb? (10초마다) ──▶ │ 6ch 피에조 측정 + 배터리/IMU/온도 - │ │ → rbb: (센서 번들) - │◀── rbb: ────────────│ - │◀── reb: × 6 ────────│ (채널별 echo 데이터) - │◀── raa: ────────────│ (완료) -``` - ---- - -## 2. 부팅 및 BLE 연결 흐름 - -``` -[부팅] - │ - ▼ -전원 버튼 2초 유지 - │ - ▼ -P0.08 래치 ON (전원 자가유지) - │ - ▼ -HW 초기화 - ├─ GPIO, LED - ├─ Battery ADC (SAADC AIN2) - ├─ IMU (I2C0, ICM42670P @ 0x68) - ├─ Temperature ADC (SAADC AIN3, TMP235-Q1) - ├─ [TODO] Piezo 드라이버 (GPIO + SW burst) - └─ [TODO] Echo ADC (SPIM3, ADC121S051) - │ - ▼ -BLE 스택 초기화 (NUS) - │ - ▼ -Advertising 시작 → LED: 파란불 깜빡임 - │ - ▼ -앱 연결됨 → LED: 연결 표시 - │ - ▼ -앱에서 주기적 커맨드 수신 - ├─ msp? (1초마다) - └─ mbb? (10초마다) -``` - ---- - -## 3. 커맨드/응답 테이블 - -| 커맨드 | 설명 | 응답 | 주기 | 구현 상태 | -|---|---|---|---|---| -| `msp?` | IMU 1회 측정 | `rsp:` (accel+gyro, 12B) | 1초 | ✅ 완료 | -| `mbb?` | 전체 측정 (센서+피에조 6ch) | `rbb:` + `reb:` ×6 + `raa:` | 10초 | ❌ 미구현 | -| `maa?` | 피에조 6ch 단독 측정 | `reb:` ×6 + `raa:` | 수동 | ❌ 미구현 | -| `msn?` | 배터리 전압 단독 측정 | `rsn:` (mV) | 수동 | ✅ 완료 | -| `mls?` | LED 상태 설정 | `rls:` (state echo) | 수동 | ✅ 완료 | - ---- - -## 4. msp? 흐름 (1초 주기 IMU) - -``` -앱 → msp? - │ - ▼ - imu_read(accel, gyro) - ├─ GYRO_CONFIG0 = 0x09 (±2000dps, 100Hz) - ├─ ACCEL_CONFIG0 = 0x29 (±4g, 100Hz) - ├─ PWR_MGMT0 = 0x0F (low-noise ON) - ├─ 80ms 대기 (자이로 스타트업) - ├─ 0x0B부터 12바이트 읽기 - └─ PWR_MGMT0 = 0x00 (슬립) - │ - ▼ - rsp: [accel X(2)] [accel Y(2)] [accel Z(2)] - [gyro X(2)] [gyro Y(2)] [gyro Z(2)] - [CRC16(2)] = 18 bytes -``` - -**구현 상태: ✅ 완료** -- `src/drivers/imu/imu_i2c.c` — ICM42670P I2C 드라이버 -- `src/parser.c` — `msp?` 핸들러, `rsp:` 응답 - ---- - -## 5. mbb? 흐름 (10초 주기 전체 측정) - -``` -앱 → mbb? - │ - ▼ - [Phase 1] 피에조 6채널 캡처 (타이밍 크리티컬) - ┌──────────────────────────────────────────────┐ - │ piezo_power_on() ← PWR_EN=HIGH, 3ms 대기 │ - │ echo_adc_init() ← SPIM3 초기화 │ - │ │ - │ for ch = 0 ~ 5: │ - │ piezo_select_channel(ch) ← MUX, 1.3ms │ - │ for avg = 0 ~ 2: ← 3회 평균 │ - │ piezo_burst_sw(5) ← 2.1MHz SW burst│ - │ delay 20us │ - │ echo_adc_capture(buf, 100) ← SPIM3 │ - │ channel_data[ch] = 평균값 │ - └──────────────────────────────────────────────┘ - │ - ▼ - [Phase 2] 센서 측정 (피에조 캡처 완료 후) - ┌──────────────────────────────────────────────┐ - │ battery_read_mv() → info_batt (mV) │ - │ imu_read(accel, gyro) → info_imu[6] │ - │ temperature_read() → info_temp (°Cx100) │ - └──────────────────────────────────────────────┘ - │ - ▼ - [Phase 3] BLE 전송 - ┌──────────────────────────────────────────────┐ - │ rbb: [batt(2)][IMU(12)][temp(2)][CRC(2)] │ - │ = 20 bytes │ - │ │ - │ for ch = 0 ~ 5: │ - │ reb: [num_samples(2)][raw(200)][CRC(2)] │ - │ = 204 bytes per channel │ - │ │ - │ raa: [status(2)][CRC(2)] = 8 bytes │ - └──────────────────────────────────────────────┘ - │ - ▼ - piezo_power_off() -``` - -**rbb: 패킷 포맷** -``` -[r][b][b][:] [batt_L][batt_H] - [imu0_L][imu0_H] ... [imu5_L][imu5_H] ← accel XYZ + gyro XYZ - [temp_L][temp_H] - [CRC_L][CRC_H] -= 4 + 2 + 12 + 2 + 2 = 22 bytes -``` - -**reb: 패킷 포맷 (채널당)** -``` -[r][e][b][:] [num_samples_H][num_samples_L] - [s0_H][s0_L] [s1_H][s1_L] ... [s99_H][s99_L] - [CRC_L][CRC_H] -= 4 + 2 + 200 + 2 = 208 bytes -``` - ---- - -## 6. 구현 현황 - -### ✅ 완료 - -| 모듈 | 파일 | 기능 | -|---|---|---| -| BLE NUS | `src/ble/ble_service.c` | advertising, 연결, RX/TX | -| 전원 제어 | `src/power_control.c`, `src/main.c` | 버튼 상태머신, 래치, 슬립 | -| LED | `src/drivers/led/led_control.c` | 상태별 LED 패턴 | -| 배터리 ADC | `src/drivers/battery/battery_adc.c` | SAADC AIN2, 주기 모니터링 | -| IMU | `src/drivers/imu/imu_i2c.c` | ICM42670P I2C (SCL=P1.14, SDA=P1.15) | -| 온도 센서 | `src/drivers/temperature/tmp235.c` | TMP235-Q1, SAADC AIN3 (P0.05) | -| 파서 | `src/parser.c` | `msn?`, `mls?`, `msp?` | - -#### SAADC 채널 공유 (배터리 ↔ 온도) - -배터리(AIN2)와 온도(AIN3)는 nRF52840 SAADC 하나를 공유한다. - -**nRF5 SDK에서는** 채널 전환 시 `nrfx_saadc_uninit()`을 명시적으로 호출하지 않으면 다음 `nrfx_saadc_channel_init()` 호출에서 `NRFX_ERROR_BUSY`가 발생하는 문제가 있었다. - -**Zephyr에서는** `adc_channel_setup_dt()` + `adc_read_dt()` 내부적으로 init/uninit 처리가 되므로 명시적 uninit 불필요. `battery_read_mv()`와 `temp_read_cdeg()` 모두 읽기 전에 `adc_channel_setup_dt()`를 호출하여 해당 채널로 SAADC를 재설정하기 때문에 임의 순서로 교대 호출해도 정상 동작한다. - -> 단, 두 함수를 **서로 다른 스레드에서 동시에** 호출하면 안 됨. mbb? 핸들러에서 직렬로 호출하는 한 문제없음. - -### 🔲 테스트 필요 - -| 모듈 | 테스트 방법 | 확인 항목 | -|---|---|---| -| 배터리 ADC (`msn?`) | RTT 로그 확인 + 앱에서 `msn?` 전송 | 아래 테스트 가이드 참고 | -| 온도 센서 | RTT 로그 확인 | 아래 테스트 가이드 참고 | -| IMU (`msp?`) | RTT 로그 확인 + 앱에서 `msp?` 전송 | 아래 테스트 가이드 참고 | - -#### 배터리 ADC 테스트 가이드 (msn?) - -**1. 부팅 시 확인 (RTT 로그)** - -`[1] HW Init` 단계에서 아래 줄이 나와야 함: -``` -[BATT] ADC init OK (DT-based, ch=2, res=12, os=2) -``` - -실패 패턴: - -| 로그 | 원인 | 조치 | -|---|---|---| -| `[BATT] ADC device not ready` | SAADC 드라이버 초기화 실패 | `CONFIG_ADC=y` 확인, overlay channel@2 확인 | -| `[BATT] Channel setup failed (err -x)` | 채널 설정 실패 | overlay AIN2 설정 확인 | - -**2. msn? 전송 시 확인 (RTT 로그)** - -앱에서 `msn?` 전송 시 아래 형식으로 출력됨: -``` -[CMD] msn -> 3800 mV -``` -- 정상 범위: 3500 ~ 4200 mV (완충 4200mV, 저전압 경고 3500mV) -- 3500mV 미만이 10회 연속이면 자동 전원 OFF - -**3. BLE 응답 패킷 포맷** -``` -[r][s][n][:] [mV_H][mV_L] [CRC_L][CRC_H] = 8 bytes -``` - ---- - -#### 온도 센서 테스트 가이드 (TMP235) - -**1. 부팅 시 확인 (RTT 로그)** - -`[1] HW Init` 단계에서 아래 줄이 나와야 함: -``` -[TEMP] OK — TMP235 ch=3, res=12, os=2 -``` - -실패 패턴: - -| 로그 | 원인 | 조치 | -|---|---|---| -| `[TEMP] FAIL — ADC device not ready` | SAADC 드라이버 초기화 실패 | overlay channel@3 확인 | -| `[TEMP] FAIL — channel setup (err=-x)` | 채널 설정 실패 | overlay AIN3 설정 확인, P0.05 핀 확인 | - -**2. 측정 시 확인 (RTT 로그)** - -`temp_read_cdeg()` 호출 시 아래 형식으로 출력됨: -``` -[TEMP] raw=1234 -> 25.50 C -``` -- 실온(25°C) 기준 raw 값 약 1404 (V = 750mV, raw = 750 × 4095 / 3600 ≈ 853) - > 실제 분압 회로가 있으면 값이 다를 수 있음, 첫 측정값 보고 판단 -- `INT16_MIN(-32768)` 반환 시 ADC 읽기 실패 - -**3. mbb? 패킷 내 위치** -``` -rbb: ... [temp_L][temp_H] ... ← °C × 100 단위 (25.50°C = 2550) -``` - ---- - -#### IMU 테스트 가이드 (msp?) - -**1. 부팅 시 확인 (RTT 로그)** - -`[1] HW Init` 단계에서 아래 줄이 나와야 함: -``` -[IMU] OK — ICM42670P detected (WHOAMI=0x67, addr=0x68) -``` - -실패 패턴: - -| 로그 | 원인 | 조치 | -|---|---|---| -| `[IMU] FAIL — I2C bus not ready` | Zephyr I2C 드라이버 초기화 실패 | `CONFIG_I2C=y` 확인, overlay 확인 | -| `[IMU] FAIL — WHOAMI read error (check SCL=P1.14, SDA=P1.15)` | I2C 통신 자체 실패 | 핀 납땜/연결 확인, 풀업 저항 확인 | -| `[IMU] FAIL — WHOAMI mismatch (got=0x00, expected=0x67)` | 버스는 살아있으나 응답 이상 | I2C 주소(0x68) 확인, AD0 핀 상태 확인 | - -**2. msp? 전송 시 확인 (RTT 로그)** - -앱에서 `msp?` 전송 시 아래 형식으로 출력됨: -``` -[IMU] msp: A=( 12345, -1234, 3210) G=( 100, -50, 200) -``` -- `A=` : 가속도 XYZ (int16, ±4g 풀스케일 → 1g ≈ 8192) -- `G=` : 자이로 XYZ (int16, ±2000dps 풀스케일 → 1dps ≈ 16.4) -- 디바이스 수평 정치 시 Z축 가속도 약 `+8192` 근처, 자이로 `0` 근처 (±수십 이내) - -실패 패턴: - -| 로그 | 원인 | -|---|---| -| `[IMU] FAIL — gyro config write (ret=-5)` | I2C TX 에러 | -| `[IMU] FAIL — data read (ret=-5)` | I2C RX 에러 | -| BLE로 `rsp: 0xFFFF` 수신 | 위 에러 발생 시 앱으로 전송되는 에러 응답 | - -**3. BLE 응답 패킷 포맷** -``` -[r][s][p][:] [AX_H][AX_L] [AY_H][AY_L] [AZ_H][AZ_L] - [GX_H][GX_L] [GY_H][GY_L] [GZ_H][GZ_L] - [CRC_L][CRC_H] = 18 bytes -``` - -### ❌ 미구현 - -| 모듈 | 목표 파일 | 필요 기능 | -|---|---|---| -| 피에조 드라이버 | `src/drivers/piezo/piezo.c` | SW burst (2.1MHz), MUX, 전원 제어 | -| Echo ADC | `src/drivers/echo_adc/echo_adc.c` | ADC121S051, SPIM3 @ 16MHz | -| `maa?` 커맨드 | `src/parser.c` 추가 | 피에조 6ch 단독 측정 → `reb:` ×6 + `raa:` | -| `mbb?` 커맨드 | `src/parser.c` 추가 | 전체 측정 오케스트레이션 → `rbb:` + `reb:` ×6 + `raa:` | - ---- - -## 7. 핀 배치 전체 정리 - -| 신호 | 핀 | 방향 | 모듈 | 상태 | -|---|---|---|---|---| -| POWER_HOLD | P0.08 | OUT | 전원 래치 | ✅ | -| POWER_BTN | P1.08 | IN | 전원 버튼 | ✅ | -| LED_BLE | P0.12 | OUT | 파란 LED | ✅ | -| LED_FUNC | P0.29 | OUT | 주황 LED | ✅ | -| BATT_ADC | P0.04 (AIN2) | AIN | 배터리 ADC | ✅ | -| IMU_SCL | P1.14 | I2C | ICM42670P | ✅ | -| IMU_SDA | P1.15 | I2C | ICM42670P | ✅ | -| TEMP_ADC | P0.05 (AIN3) | AIN | TMP235-Q1 | 🔲 | -| PIEZO_PWR_EN | P1.09 | OUT | DC/DC +/-20V | ❌ | -| PIEZO_PE | P0.25 | OUT | Pulse Enable | ❌ | -| PIEZO_P_OUT | P1.07 | OUT | 양극 출력 | ❌ | -| PIEZO_N_OUT | P1.06 | OUT | 음극 출력 | ❌ | -| PIEZO_DMP | P1.00 | OUT | Dump | ❌ | -| MUX_EN_A | P0.21 | OUT | MUXA 활성화 | ❌ | -| MUX_EN_B | P0.23 | OUT | MUXB 활성화 | ❌ | -| MUX_SEL0 | P1.10 | OUT | MUX 채널 선택 | ❌ | -| MUX_SEL1 | P0.28 | OUT | MUX 채널 선택 | ❌ | -| ECHO_SCLK | P0.14 | SPI | ADC121S051 CLK | ❌ | -| ECHO_MISO | P0.15 | SPI | ADC121S051 DATA | ❌ | -| ECHO_CS | P0.19 | OUT | ADC121S051 CS | ❌ | - ---- - -## 8. IMU 테스트 가이드 (msp?) - -### 부팅 시 확인 (RTT 로그) - -부팅 직후 `[1] HW Init` 단계에서 아래 줄이 나와야 함: - -``` -[IMU] OK — ICM42670P detected (WHOAMI=0x67, addr=0x68) -``` - -#### 실패 패턴별 원인 - -| 로그 | 원인 | 조치 | -|---|---|---| -| `[IMU] FAIL — I2C bus not ready` | Zephyr I2C 드라이버 초기화 실패 | `CONFIG_I2C=y` 확인, overlay 확인 | -| `[IMU] FAIL — WHOAMI read error (check SCL=P1.14, SDA=P1.15)` | I2C 통신 자체 실패 | 핀 납땜/연결 확인, 풀업 저항 확인 | -| `[IMU] FAIL — WHOAMI mismatch (got=0x00, expected=0x67)` | 버스는 살아있으나 응답 이상 | I2C 주소(0x68) 확인, AD0 핀 상태 확인 | - ---- - -### msp? 커맨드 전송 시 확인 (RTT 로그) - -앱에서 `msp?` 전송 시 아래 형식으로 출력됨: - -``` -[IMU] msp: A=( 12345, -1234, 3210) G=( 100, -50, 200) -``` - -- `A=` : 가속도 XYZ (int16, ±4g 풀스케일 → 1g ≈ 8192) -- `G=` : 자이로 XYZ (int16, ±2000dps 풀스케일 → 1dps ≈ 16.4) -- 디바이스가 수평으로 놓여 있으면 Z축 가속도가 약 `+8192` 근처여야 함 -- 자이로는 정지 상태에서 `0` 근처 (±수십 이내) - -#### 실패 패턴 - -| 로그 | 원인 | -|---|---| -| `[IMU] FAIL — gyro config write (ret=-5)` | I2C TX 에러 (부팅 후 재연결 문제) | -| `[IMU] FAIL — data read (ret=-5)` | I2C RX 에러 | -| BLE로 `rsp: 0xFFFF` 수신 | 위 에러 발생 시 앱으로 전송되는 에러 응답 | - ---- - -### BLE 응답 패킷 확인 - -정상 수신 시 앱에서 받는 `rsp:` 패킷 포맷: - -``` -[r][s][p][:] [AX_H][AX_L] [AY_H][AY_L] [AZ_H][AZ_L] - [GX_H][GX_L] [GY_H][GY_L] [GZ_H][GZ_L] - [CRC_L][CRC_H] -= 18 bytes -``` - ---- - -## 9. 다음 구현 순서 - -``` -Step 1 ✅ 온도 센서 (tmp235.c) — 완료 - -Step 2 피에조 드라이버 (piezo.c) - └─ GPIO 초기화, MUX, SW burst (NOP 기반, nrf HAL 직접 사용) - └─ CONFIG_NRFX_GPIOTE=y 추가 - -Step 3 Echo ADC (echo_adc.c) - └─ SPIM3 초기화, 연속 샘플 캡처 - └─ CONFIG_NRFX_SPIM3=y 추가 - -Step 4 maa? 커맨드 (parser.c 추가) - └─ 피에조 6ch 측정 + BLE 전송 (reb: ×6 + raa:) - └─ 센서 번들 없음 — 피에조/ADC 단독 검증용 - -Step 5 mbb? 커맨드 (parser.c 추가) - └─ 배터리 + IMU + 온도 + 피에조 6ch 전체 오케스트레이션 - └─ rbb: + reb: ×6 + raa: -``` diff --git a/prj.conf b/prj.conf index c4793f8..bd66e05 100644 --- a/prj.conf +++ b/prj.conf @@ -121,7 +121,7 @@ CONFIG_BOOTLOADER_MCUBOOT=y # Enable zcbor library used by mcumgr/SMP/DFU CBOR payloads. CONFIG_ZCBOR=y # Version string used by MCUboot imgtool signing and versioned DFU zip naming. -CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION="0.0.2+9" +CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION="0.0.2+26" # Internal flash # Enable internal flash driver. @@ -149,7 +149,7 @@ CONFIG_MCUMGR=y # Enable mcumgr/SMP messages over BLE SMP service. CONFIG_MCUMGR_TRANSPORT_BT=y # Require authenticated BLE connection for mcumgr read/write operations. -CONFIG_MCUMGR_TRANSPORT_BT_PERM_RW_AUTHEN=y +CONFIG_MCUMGR_TRANSPORT_BT_PERM_RW_AUTHEN=n # Reassemble SMP packets fragmented over BLE. CONFIG_MCUMGR_TRANSPORT_BT_REASSEMBLY=y # mcumgr transport receive buffer size for DFU image chunks. diff --git a/src/app_nvs.c b/src/app_nvs.c index d85c301..7643a50 100644 --- a/src/app_nvs.c +++ b/src/app_nvs.c @@ -22,14 +22,14 @@ #define APP_NVS_KEY_HW_NO "hw" #define APP_NVS_KEY_SERIAL_NO "serial" #define APP_NVS_KEY_PASSKEY "passkey" -#define APP_NVS_KEY_PASSKEY_CHANGED "passkey_changed" /* 패스키 1회 변경 완료 여부 */ +#define APP_NVS_KEY_PASSKEY_CHANGED "passkey_changed" // 패스키 1회 변경 완료 여부 /* settings_save_one에 넘기는 전체 키 */ #define APP_NVS_FULL_KEY_PIEZO "vesiscan/piezo" #define APP_NVS_FULL_KEY_HW_NO "vesiscan/hw" #define APP_NVS_FULL_KEY_SERIAL_NO "vesiscan/serial" #define APP_NVS_FULL_KEY_PASSKEY "vesiscan/passkey" -#define APP_NVS_FULL_KEY_PASSKEY_CHANGED "vesiscan/passkey_changed" /* 0은 변경 가능, 1은 변경 차단 */ +#define APP_NVS_FULL_KEY_PASSKEY_CHANGED "vesiscan/passkey_changed" // 0은 변경 가능, 1은 변경 차단 /* parser.c가 가진 피에조 설정을 NVS 로드 시 갱신하기 위한 포인터 */ static piezo_config_t *m_piezo_cfg; @@ -60,7 +60,7 @@ static int app_nvs_settings_set(const char *name, size_t len, settings_read_cb r { const char *next; - /* 피에조 설정은 검증을 통과한 값만 기본값 대신 적용 */ + // 피에조 설정은 검증을 통과한 값만 기본값 대신 적용 if (settings_name_steq(name, APP_NVS_KEY_PIEZO, &next) && (next == NULL)) { piezo_config_t loaded; @@ -93,7 +93,7 @@ static int app_nvs_settings_set(const char *name, size_t len, settings_read_cb r return 0; } - /* HW Version은 NUL 종료 없이 고정 길이 ASCII로 저장 */ + // HW Version은 NUL 종료 없이 고정 길이 ASCII로 저장 if (settings_name_steq(name, APP_NVS_KEY_HW_NO, &next) && (next == NULL)) { if (len != HW_NO_LENGTH) @@ -106,7 +106,7 @@ static int app_nvs_settings_set(const char *name, size_t len, settings_read_cb r return read_exact(read_cb, cb_arg, HW_NO, HW_NO_LENGTH, APP_NVS_KEY_HW_NO); } - /* Serial Number는 NUL 종료 없이 고정 길이 ASCII로 저장 */ + // Serial Number는 NUL 종료 없이 고정 길이 ASCII로 저장 if (settings_name_steq(name, APP_NVS_KEY_SERIAL_NO, &next) && (next == NULL)) { if (len != SERIAL_NO_LENGTH) @@ -119,7 +119,7 @@ static int app_nvs_settings_set(const char *name, size_t len, settings_read_cb r return read_exact(read_cb, cb_arg, SERIAL_NO, SERIAL_NO_LENGTH, APP_NVS_KEY_SERIAL_NO); } - /* 패스키는 mpz? 명령으로 최초 1회만 저장 */ + // 패스키는 mpz? 명령으로 최초 1회만 저장 if (settings_name_steq(name, APP_NVS_KEY_PASSKEY, &next) && (next == NULL)) { if (len != PASSKEY_LENGTH) @@ -153,7 +153,7 @@ SETTINGS_STATIC_HANDLER_DEFINE(app_nvs_settings, APP_NVS_SETTINGS_NAME, NULL, ap int app_nvs_init(piezo_config_t *piezo_cfg, app_nvs_piezo_validate_fn piezo_validate, app_nvs_piezo_log_fn piezo_log) { - /* settings_load_subtree가 호출되기 전에 복원용 포인터와 콜백 저장 */ + // settings_load_subtree가 호출되기 전에 복원용 포인터와 콜백 저장 m_piezo_cfg = piezo_cfg; m_piezo_validate = piezo_validate; m_piezo_log = piezo_log; @@ -181,7 +181,7 @@ int app_nvs_init(piezo_config_t *piezo_cfg, app_nvs_piezo_validate_fn piezo_vali int app_nvs_save_piezo(const piezo_config_t *cfg) { #if IS_ENABLED(CONFIG_SETTINGS) - /* 현재 측정 파라미터 저장 */ + // 현재 측정 파라미터 저장 int err = settings_save_one(APP_NVS_FULL_KEY_PIEZO, cfg, sizeof(*cfg)); if (err) { @@ -197,7 +197,7 @@ int app_nvs_save_piezo(const piezo_config_t *cfg) int app_nvs_save_hw_no(const char *hw_no) { #if IS_ENABLED(CONFIG_SETTINGS) - /* 고정 길이 HW Version 저장 */ + // 고정 길이 HW Version 저장 int err = settings_save_one(APP_NVS_FULL_KEY_HW_NO, hw_no, HW_NO_LENGTH); if (err) { @@ -213,7 +213,7 @@ int app_nvs_save_hw_no(const char *hw_no) int app_nvs_save_serial_no(const char *serial_no) { #if IS_ENABLED(CONFIG_SETTINGS) - /* 고정 길이 Serial Number 저장 */ + // 고정 길이 Serial Number 저장 int err = settings_save_one(APP_NVS_FULL_KEY_SERIAL_NO, serial_no, SERIAL_NO_LENGTH); if (err) { @@ -229,7 +229,7 @@ int app_nvs_save_serial_no(const char *serial_no) int app_nvs_save_passkey(const char *passkey) { #if IS_ENABLED(CONFIG_SETTINGS) - /* mpz? 명령이 허용된 뒤 고정 길이 패스키 저장 */ + // mpz? 명령이 허용된 뒤 고정 길이 패스키 저장 int err = settings_save_one(APP_NVS_FULL_KEY_PASSKEY, passkey, PASSKEY_LENGTH); if (err) { @@ -245,7 +245,7 @@ int app_nvs_save_passkey(const char *passkey) int app_nvs_save_passkey_changed(uint8_t changed) { #if IS_ENABLED(CONFIG_SETTINGS) - /* 재부팅 후에도 mpz?(패스키 변경) 재실행을 막기 위한 플래그 저장(NVS) */ + // 재부팅 후에도 mpz?(패스키 변경) 재실행을 막기 위한 플래그 저장(NVS) int err = settings_save_one(APP_NVS_FULL_KEY_PASSKEY_CHANGED, &changed, sizeof(changed)); if (err) { diff --git a/src/ble/ble_service.c b/src/ble/ble_service.c index de29a81..f74ec54 100644 --- a/src/ble/ble_service.c +++ b/src/ble/ble_service.c @@ -31,9 +31,7 @@ LOG_MODULE_REGISTER(ble_svc, LOG_LEVEL_INF); -/*============================================================================== - * Module variables - *============================================================================*/ +/* Module variables */ static struct bt_conn *current_conn; static ble_data_rx_cb_t rx_callback; static bool dfu_advertising_mode; @@ -43,6 +41,7 @@ static int64_t last_rx_conn_param_update_ms; #define BLE_TX_POWER_NORMAL_DBM 4 #define BLE_TX_POWER_STRESS_DBM 8 +#define BLE_RSSI_MONITOR_ENABLED 0 #define BLE_RSSI_STRESS_DBM (-80) #define BLE_RSSI_STRESS_COUNT 3 #define BLE_RSSI_MONITOR_INTERVAL_MS 500 @@ -68,7 +67,7 @@ static uint32_t pairing_passkey = BT_PASSKEY_RAND; /* * - mutex: 여러 곳에서 동시에 NUS 전송을 시작하지 못하게 막음 - * - sem : "직전 전송이 정말 끝났는지" 기다리는 신호 + * - sem : 직전 전송이 정말 끝났는지 기다리는 신호 */ static struct k_mutex nus_tx_lock; static struct k_sem nus_tx_done_sem; @@ -195,6 +194,11 @@ static void tx_power_monitor_handler(struct k_work *work) return; } + if (!BLE_RSSI_MONITOR_ENABLED) + { + return; + } + err = ble_read_conn_rssi(&rssi); if (err) { @@ -221,23 +225,6 @@ static void tx_power_monitor_handler(struct k_work *work) K_MSEC(BLE_RSSI_MONITOR_INTERVAL_MS)); } -static const char *ble_addr_type_str(uint8_t type) -{ - switch (type) - { - case BT_ADDR_LE_PUBLIC: - return "public"; - case BT_ADDR_LE_RANDOM: - return "random"; - case BT_ADDR_LE_PUBLIC_ID: - return "public-id"; - case BT_ADDR_LE_RANDOM_ID: - return "random-id"; - default: - return "unknown"; - } -} - static void ble_log_local_identity(void) { bt_addr_le_t addr = {0}; @@ -463,8 +450,7 @@ static int ble_request_preferred_conn_params(bool count_attempt) err = bt_conn_le_param_update(current_conn, &conn_param); if (err) { - DBG_ERR("[BLE] Param update failed err=%d -> disconnect\r\n", err); - (void)bt_conn_disconnect(current_conn, BT_HCI_ERR_UNACCEPT_CONN_PARAM); + DBG_ERR("[BLE] Param update failed err=%d\r\n", err); return err; } @@ -493,25 +479,21 @@ static void conn_param_update_handler(struct k_work *work) } } -/*============================================================================== - * Advertising data - *============================================================================*/ +/* Advertising data */ static const struct bt_data sd[] = { - /* 평상시 Web Bluetooth 앱이 찾을 수 있도록 NUS UUID 광고 */ - BT_DATA_BYTES(BT_DATA_UUID128_SOME, BT_UUID_NUS_VAL), + // 평상시 Web Bluetooth 앱이 찾을 수 있도록 NUS UUID 광고 + BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_NUS_VAL), }; static const struct bt_data sd_dfu[] = { - /* DFU 진입 후 mcumgr 앱이 찾을 수 있도록 SMP UUID 광고 */ + // DFU 진입 후 mcumgr 앱이 찾을 수 있도록 SMP UUID 광고 BT_DATA_BYTES(BT_DATA_UUID128_SOME, SMP_BT_SVC_UUID_VAL), }; -/*============================================================================== - * Connection callbacks - *============================================================================*/ +/* Connection callbacks */ static void connected(struct bt_conn *conn, uint8_t err) { if (err) @@ -528,7 +510,10 @@ static void connected(struct bt_conn *conn, uint8_t err) low_rssi_count = 0U; slow_tx_count = 0U; (void)ble_set_conn_tx_power(BLE_TX_POWER_NORMAL_DBM, "connected"); - k_work_schedule(&tx_power_monitor_work, K_MSEC(BLE_RSSI_MONITOR_INTERVAL_MS)); + if (BLE_RSSI_MONITOR_ENABLED) + { + k_work_schedule(&tx_power_monitor_work, K_MSEC(BLE_RSSI_MONITOR_INTERVAL_MS)); + } /* 연결 정보 출력 */ char addr_str[BT_ADDR_LE_STR_LEN]; @@ -537,8 +522,6 @@ static void connected(struct bt_conn *conn, uint8_t err) conn_param_update_attempts = 0U; last_rx_conn_param_update_ms = 0; - k_work_schedule(&conn_param_update_work, - K_MSEC(BLE_CONN_PARAM_FIRST_DELAY_MS)); #if IS_ENABLED(CONFIG_BT_SMP) err = bt_conn_set_security(conn, BLE_REQUIRED_SECURITY_LEVEL); @@ -578,29 +561,14 @@ static void disconnected(struct bt_conn *conn, uint8_t reason) reason, advertising_unlimited ? "unlimited" : "10min"); - /* 워크큐에서 advertising 재시작 */ + // 워크큐에서 advertising 재시작 k_work_submit(&adv_restart_work); } static bool le_param_req(struct bt_conn *conn, struct bt_le_conn_param *param) { ARG_UNUSED(conn); - - if (!ble_conn_param_is_preferred(param->interval_min, - param->latency, - param->timeout) || - (param->interval_max != BLE_MAX_CONN_INTERVAL)) - { - DBG_PRINTF("[BLE] Override requested params: min=%u max=%u latency=%u timeout=%u\r\n", - param->interval_min, - param->interval_max, - param->latency, - param->timeout); - param->interval_min = BLE_MIN_CONN_INTERVAL; - param->interval_max = BLE_MAX_CONN_INTERVAL; - param->latency = BLE_SLAVE_LATENCY; - param->timeout = BLE_CONN_SUP_TIMEOUT; - } + ARG_UNUSED(param); return true; } @@ -646,9 +614,7 @@ BT_CONN_CB_DEFINE(conn_callbacks) = #endif }; -/*============================================================================== - * NUS callbacks - *============================================================================*/ +/* NUS callbacks */ static void nus_received(struct bt_conn *conn, const uint8_t *data, uint16_t len) { #if IS_ENABLED(CONFIG_BT_SMP) @@ -661,18 +627,13 @@ static void nus_received(struct bt_conn *conn, const uint8_t *data, uint16_t len ARG_UNUSED(conn); #endif - DBG_CORE("[NUS RX] len=%u\r\n", len); + //DBG_CORE("[NUS RX] len=%u\r\n", len); if (current_conn == conn) { int64_t now_ms = k_uptime_get(); - if ((last_rx_conn_param_update_ms == 0) || - ((now_ms - last_rx_conn_param_update_ms) >= BLE_CONN_PARAM_RETRY_INTERVAL_MS)) - { - last_rx_conn_param_update_ms = now_ms; - (void)ble_request_preferred_conn_params(false); - } + ARG_UNUSED(now_ms); } if (rx_callback) @@ -684,7 +645,7 @@ static void nus_received(struct bt_conn *conn, const uint8_t *data, uint16_t len static void nus_sent(struct bt_conn *conn) { ARG_UNUSED(conn); - /* 직전 NUS 패킷 전송 완료 */ + // 직전 NUS 패킷 전송 완료 k_sem_give(&nus_tx_done_sem); //DBG_CORE("[NUS TX] complete\r\n"); } @@ -695,9 +656,7 @@ static struct bt_nus_cb nus_cb = .sent = nus_sent, }; -/*============================================================================== - * Public functions - *============================================================================*/ +/* Public functions */ int ble_service_init(ble_data_rx_cb_t rx_cb) { int err; @@ -711,7 +670,7 @@ int ble_service_init(ble_data_rx_cb_t rx_cb) k_work_init_delayable(&conn_param_update_work, conn_param_update_handler); k_work_init_delayable(&tx_power_monitor_work, tx_power_monitor_handler); - /* Enable BLE stack */ + // Enable BLE stack err = bt_enable(NULL); if (err) { @@ -740,7 +699,7 @@ int ble_service_init(ble_data_rx_cb_t rx_cb) } #endif - /* Initialize NUS */ + // Initialize NUS err = bt_nus_init(&nus_cb); if (err) { @@ -766,9 +725,9 @@ int ble_advertising_start(void) struct bt_le_adv_param adv_param = BT_LE_ADV_PARAM_INIT( BT_LE_ADV_OPT_CONN, - APP_ADV_INTERVAL, /* min interval */ - APP_ADV_INTERVAL, /* max interval */ - NULL /* undirected */ + APP_ADV_INTERVAL, // min interval + APP_ADV_INTERVAL, // max interval + NULL // undirected ); err = bt_set_name(SERIAL_NO); @@ -817,7 +776,7 @@ int ble_dfu_advertising_enable(void) return -EALREADY; } - /* mdf? 응답 전송 후 현재 연결을 끊고 SMP UUID 광고로 재시작 */ + // mdf? 응답 전송 후 현재 연결을 끊고 SMP UUID 광고로 재시작 dfu_advertising_mode = true; DBG_PRINTF("[BLE] DFU advertising mode enabled\r\n"); @@ -860,10 +819,10 @@ int ble_data_send(const uint8_t *data, uint16_t len) } #endif - /* 먼저 들어온 전송이 끝나기 전 다음 전송이 겹치지 않게 잠금 (NUS는 한 번에 한 패킷씩 보내는 것이 안전) */ + // 먼저 들어온 전송이 끝나기 전 다음 전송이 겹치지 않게 잠금 (NUS는 한 번에 한 패킷씩 보내는 것이 안전) k_mutex_lock(&nus_tx_lock, K_FOREVER); - /* 혹시 남아 있는 완료 신호가 있으면 비우고 이번 전송 전용 상태로 만듦 */ + // 혹시 남아 있는 완료 신호가 있으면 비우고 이번 전송 전용 상태로 만듦 while (k_sem_take(&nus_tx_done_sem, K_NO_WAIT) == 0) { } @@ -895,7 +854,7 @@ int ble_data_send(const uint8_t *data, uint16_t len) return err; } - /* bt_nus_send() 성공 후 sent 콜백이 올 때까지 기다림 (다음 패킷을 보내도 되는 시점) */ + // bt_nus_send() 성공 후 sent 콜백이 올 때까지 기다림 (다음 패킷을 보내도 되는 시점) err = k_sem_take(&nus_tx_done_sem, K_MSEC(BLE_TX_STUCK_MS)); if (err) { diff --git a/src/ble/ble_service.h b/src/ble/ble_service.h index 92d3457..bfbf847 100644 --- a/src/ble/ble_service.h +++ b/src/ble/ble_service.h @@ -3,8 +3,6 @@ * @brief BLE NUS service module * * Nordic UART Service (NUS) based BLE communication - * Original: SoftDevice S140 + ble_nus SDK module - * Ported: Zephyr BLE + NCS bt_nus ******************************************************************************/ #ifndef BLE_SERVICE_H__ #define BLE_SERVICE_H__ @@ -12,29 +10,23 @@ #include #include -/*============================================================================== - * BLE configuration - *============================================================================*/ -#define APP_ADV_INTERVAL 64 /* 64 x 0.625ms = 40ms */ -#define APP_ADV_DURATION 60000 /* 60000 x 10ms = 600s (10 min) */ +/* BLE configuration */ +#define APP_ADV_INTERVAL 64 // 64 x 0.625ms = 40ms +#define APP_ADV_DURATION 60000 // 60000 x 10ms = 600s (10 min) -#define BLE_MIN_CONN_INTERVAL 12 /* 15ms / 1.25ms = 12 */ -#define BLE_MAX_CONN_INTERVAL 12 /* 15ms / 1.25ms = 12 */ +#define BLE_MIN_CONN_INTERVAL 12 // 15ms / 1.25ms = 12 +#define BLE_MAX_CONN_INTERVAL 12 // 15ms / 1.25ms = 12 #define BLE_SLAVE_LATENCY 0 -#define BLE_CONN_SUP_TIMEOUT 1000 /* 10000ms / 10ms = 1000 */ +#define BLE_CONN_SUP_TIMEOUT 1000 // 10000ms / 10ms = 1000 #define BLE_CONN_PARAM_FIRST_DELAY_MS 5000 #define BLE_CONN_PARAM_RETRY_INTERVAL_MS 30000 #define BLE_CONN_PARAM_MAX_ATTEMPTS 3 -/*============================================================================== - * Callback types - *============================================================================*/ +/* Callback types */ typedef void (*ble_data_rx_cb_t)(const uint8_t *data, uint16_t len); -/*============================================================================== - * Public functions - *============================================================================*/ +/* Public functions */ int ble_service_init(ble_data_rx_cb_t rx_cb); int ble_advertising_start(void); int ble_advertising_stop(void); diff --git a/src/command/cmd_common.c b/src/command/cmd_common.c index 317545c..117cf37 100644 --- a/src/command/cmd_common.c +++ b/src/command/cmd_common.c @@ -11,9 +11,7 @@ #include "debug_print.h" #include "ble_service.h" -/*============================================================================== - * Command response state - *============================================================================*/ +/* Command response state */ static uint8_t tx_u16_buf[8]; static uint8_t tx_imu_buf[18]; static uint8_t tx_echo_buf[4 + 2 + 2 + (PIEZO_MEASURE_MAX_SAMPLES * 2) + 2]; @@ -35,9 +33,7 @@ void cmd_reboot_after_response(void) sys_reboot(SYS_REBOOT_COLD); } -/*============================================================================== - * CRC16 (CRC-CCITT, Nordic SDK 호환) - *============================================================================*/ +/* CRC16 (CRC-CCITT, Nordic SDK 호환) */ uint16_t cmd_crc16_compute(const uint8_t *p_data, uint32_t size) { uint16_t crc = 0xFFFF; @@ -53,8 +49,7 @@ uint16_t cmd_crc16_compute(const uint8_t *p_data, uint32_t size) return crc; } -bool cmd_get_data_u16_be(const uint8_t *data, uint8_t data_len, - uint8_t word_index, uint16_t *out) +bool cmd_get_data_u16_be(const uint8_t *data, uint8_t data_len, uint8_t word_index, uint16_t *out) { uint8_t offset = (uint8_t)(word_index * 2U); @@ -88,11 +83,7 @@ char cmd_ascii_to_lower(char ch) return ch; } -/*============================================================================== - * 응답 패킷 전송 - *============================================================================*/ - -/* TAG(4B) + uint16 값(2B) + CRC16(2B) = 8바이트 전송 */ +/* 응답 패킷 전송: TAG(4B) + uint16 값(2B) + CRC16(2B) = 8바이트 전송 */ int cmd_send_response_u16(const char *tag, uint16_t value) { uint8_t *buf = tx_u16_buf; @@ -170,12 +161,7 @@ int cmd_send_response_tag_echo(const char *tag, const char *echo_tag) return ble_data_send(buf, 10); } -/*============================================================================== - * 응답 패킷 전송 (IMU) - *============================================================================*/ - -/* TAG(4B) + int16×6 빅엔디안(12B) + CRC16(2B) = 18바이트 전송 - * 기존 format_data() + dr_binary_tx_safe(buf, 8) 방식과 동일한 레이아웃 */ +/*응답 패킷 전송 (IMU): TAG(4B) + int16×6 빅엔디안(12B) + CRC16(2B) = 18바이트 전송 */ int cmd_send_response_imu(const int16_t accel[3], const int16_t gyro[3]) { uint8_t *buf = tx_imu_buf; @@ -201,7 +187,7 @@ int cmd_send_response_imu(const int16_t accel[3], const int16_t gyro[3]) void cmd_send_response_echo(uint8_t session, uint8_t channel, const uint16_t *samples, uint16_t num_samples) { - /* 정적 버퍼 사용 (한 번에 한 명령만 처리) */ + // 정적 버퍼 사용 (한 번에 한 명령만 처리) uint8_t *buf = tx_echo_buf; buf[0] = 'r'; buf[1] = 'e'; buf[2] = 'b'; buf[3] = ':'; @@ -302,10 +288,4 @@ void cmd_send_response_piezo_config(const char *tag, uint16_t freq, uint16_t cyc buf[15] = (uint8_t)(crc >> 8); ble_data_send(buf, sizeof(tx_cfg_buf)); -} - -/*============================================================================== - * 커맨드 핸들러 - *============================================================================*/ - -/* msn? → 배터리 전압 측정 → rsn: + mV */ +} \ No newline at end of file diff --git a/src/command/cmd_table.c b/src/command/cmd_table.c index 8974433..4548bee 100644 --- a/src/command/cmd_table.c +++ b/src/command/cmd_table.c @@ -36,6 +36,7 @@ const cmd_entry_t cmd_table[] = { "mrs?", cmd_mrs }, { "mpz?", cmd_mpz }, { "mqz?", cmd_mqz }, + { "mim?", cmd_mim }, }; const int cmd_table_size = (int)(sizeof(cmd_table) / sizeof(cmd_table[0])); diff --git a/src/command/handlers/cmd_device.c b/src/command/handlers/cmd_device.c index a4b7b0b..279d613 100644 --- a/src/command/handlers/cmd_device.c +++ b/src/command/handlers/cmd_device.c @@ -70,10 +70,6 @@ int cmd_msr(const uint8_t *data, uint8_t data_len) return 1; } - /* - * 현재 연결의 bond/security 정보를 먼저 지우면 같은 연결에서 rsr: 응답을 보낼 수 없음 - * 응답 전송 완료 후 bond 삭제와 reboot 진행 필요 - */ k_msleep(100); #if IS_ENABLED(CONFIG_BT_SMP) @@ -112,7 +108,7 @@ int cmd_mdf(const uint8_t *data, uint8_t data_len) uint16_t status = ble_dfu_advertising_is_enabled() ? 0xFFFF : 0x0000; - /* DFU advertising 전환 중 연결이 끊겨도 전원 래치가 유지되도록 먼저 ON 상태를 확정 */ + // DFU advertising 전환 중 연결이 끊겨도 전원 래치가 유지되도록 먼저 ON 상태를 확정 device_power_keep_on(); int tx_err = cmd_send_response_u16("rdf:", status); @@ -124,7 +120,7 @@ int cmd_mdf(const uint8_t *data, uint8_t data_len) if (status == 0x0000) { - /* rdf: 응답이 나간 뒤 연결 해제와 SMP 광고 전환 실행 */ + // rdf: 응답이 나간 뒤 연결 해제와 SMP 광고 전환 실행 k_msleep(100); int err = ble_dfu_advertising_enable(); if (err) diff --git a/src/command/handlers/cmd_info.c b/src/command/handlers/cmd_info.c index 02ea4ff..aadd23a 100644 --- a/src/command/handlers/cmd_info.c +++ b/src/command/handlers/cmd_info.c @@ -11,6 +11,9 @@ #include "app_nvs.h" #include "cmd_info.h" +/* + * mfv: firmware version read + */ int cmd_mfv(const uint8_t *data, uint8_t data_len) { char fw_version[SERIAL_NO_LENGTH]; @@ -28,6 +31,9 @@ int cmd_mfv(const uint8_t *data, uint8_t data_len) return 1; } +/* + * mid: device information(hw version + fw version + serial number) read + */ int cmd_mid(const uint8_t *data, uint8_t data_len) { int err; @@ -43,6 +49,9 @@ int cmd_mid(const uint8_t *data, uint8_t data_len) return 1; } +/* + * mwh: hardware version write + */ int cmd_mwh(const uint8_t *data, uint8_t data_len) { if (data_len < HW_NO_LENGTH) @@ -64,6 +73,9 @@ int cmd_mwh(const uint8_t *data, uint8_t data_len) return 1; } +/* + * mrh: hardware version read + */ int cmd_mrh(const uint8_t *data, uint8_t data_len) { int err; @@ -79,6 +91,9 @@ int cmd_mrh(const uint8_t *data, uint8_t data_len) return 1; } +/* + * mws: serial number write + */ int cmd_mws(const uint8_t *data, uint8_t data_len) { if (data_len < SERIAL_NO_LENGTH) @@ -100,6 +115,9 @@ int cmd_mws(const uint8_t *data, uint8_t data_len) return 1; } +/* + * mrs: serial number read + */ int cmd_mrs(const uint8_t *data, uint8_t data_len) { int err; @@ -115,6 +133,9 @@ int cmd_mrs(const uint8_t *data, uint8_t data_len) return 1; } +/* + * mpz: passkey write + */ int cmd_mpz(const uint8_t *data, uint8_t data_len) { if (data_len < PASSKEY_LENGTH) @@ -125,13 +146,13 @@ int cmd_mpz(const uint8_t *data, uint8_t data_len) if (m_passkey_changed != 0U) { - /* 패스키가 변경된 경우 다시 바꿀 수 없음(FFFF 반환) */ + // 패스키가 변경된 경우 다시 바꿀 수 없음(FFFF 반환) cmd_send_response_u16("rpz:", 0xFFFF); DBG_PRINTF("[CMD] mpz: passkey already changed\r\n"); return 1; } - /* NVS 저장이 모두 성공한 뒤 RAM 값 확정 */ + // NVS 저장이 모두 성공한 뒤 RAM 값 확정 char new_passkey[PASSKEY_BUF_SIZE]; memset(new_passkey, 0, sizeof(new_passkey)); memcpy(new_passkey, data, PASSKEY_LENGTH); @@ -143,7 +164,7 @@ int cmd_mpz(const uint8_t *data, uint8_t data_len) return 1; } - /* 변경 완료 플래그 세트 : 다음 mpz? 명령 차단 */ + // 변경 완료 플래그 세트 : 다음 mpz? 명령 차단 err = app_nvs_save_passkey_changed(1U); if (err) { @@ -159,6 +180,9 @@ int cmd_mpz(const uint8_t *data, uint8_t data_len) return 1; } +/* + * mqz: passkey read (삭제 예정) + */ int cmd_mqz(const uint8_t *data, uint8_t data_len) { ARG_UNUSED(data); @@ -166,12 +190,4 @@ int cmd_mqz(const uint8_t *data, uint8_t data_len) cmd_send_response_ascii("rqz:", m_static_passkey, PASSKEY_LENGTH); return 1; -} - -/* - * LED 상태 변경 명령 - * mls?: 앱에서 전달한 LED 상태값을 적용하고 rls로 echo back - * - 파라미터 부족: 0xFFFF - * - 범위 초과: 0xFFFE - * - LED_STATE_OFF 진입 시 IMU FIFO 정지 - */ +} \ No newline at end of file diff --git a/src/command/handlers/cmd_piezo.c b/src/command/handlers/cmd_piezo.c index 96d5367..74fe490 100644 --- a/src/command/handlers/cmd_piezo.c +++ b/src/command/handlers/cmd_piezo.c @@ -17,8 +17,280 @@ static uint8_t tx_rim_samples[IMU_FIFO_RIM_TARGET_SAMPLES * IMU_FIFO_SAMPLE_BYTES]; /* - * 테스트용 - * mpa?: piezo 전원 ON + * 주기 측정 명령 + * mbb?: piezo echo sweep + 배터리 + IMU + IMU 온도 측정 + * - 성공 시 rbb 1개, reb 6개, raa 상태값 전송 + * - processing=true 구간에서는 내부 모니터의 I2C 접근을 막는 기준으로 사용 + * + * - 순서: + * 1. battery / imu / temp 측정 + * 2. rbb: 1개 전송 + * 3. piezo / echo ADC 준비 + * 4. piezo 6채널 burst + * 5. echo 샘플 채널별로 모으기 + * 6. reb: 6개 전송 + * 7. raa: 상태값 전송 + */ +int cmd_mbb(const uint8_t *data, uint8_t data_len) +{ + ARG_UNUSED(data); + ARG_UNUSED(data_len); + + uint8_t session = cmd_next_echo_session(); + + int16_t accel[3]; + int16_t gyro[3]; + + processing = true; + power_button_suspend(true); + DBG_PRINTF("[MBB] cmd start\r\n"); + + int batt_mv = -1; + int16_t temp_cdeg = INT16_MIN; + + /* Read battery first (idle measurement) so mbb? result matches msn? */ + DBG_PRINTF("[MBB] battery read (idle)\r\n"); + batt_mv = battery_read_mv(); + + int status = piezo_measure_start_session(); + + if (status == ECHO_STATUS_OK) // Piezo GPIO, power, ADC initialization/wake succeeded + { + DBG_PRINTF("[MBB] imu/temp read\r\n"); + imu_read_with_temperature(accel, gyro, &temp_cdeg); + + battery_protection_record_continuous(batt_mv, temp_cdeg); // update protection counters for continuous monitoring + + DBG_PRINTF("[MBB] response rbb: tx start\r\n"); + cmd_send_response_bundle((uint16_t)batt_mv, accel, gyro, temp_cdeg); + + DBG_PRINTF("[MBB] piezo sweep start\r\n"); + status = piezo_measure_sweep(); + if (status == ECHO_STATUS_OK) + { + for (uint8_t ch = 0; ch < PIEZO_NUM_CHANNELS; ch++) + { + DBG_PRINTF("[MBB] response reb: tx start ch=%d\r\n", ch); + cmd_send_response_echo(session, ch, piezo_measure_channel_buffer(ch), piezo_config_get()->samples); + } + } + } + + piezo_power_off(); + power_button_suspend(false); + DBG_PRINTF("[MBB] done\r\n"); + cmd_send_response_u16("raa:", (uint16_t)status); // 최종 상태 raa: 전송 + DBG_PRINTF("[CMD] mbb status=0x%04X\r\n", status); + + processing = false; + return 1; +} + +/* + * 정렬모드 + * mtb?: IMU FIFO + piezo echo sweep + * - 순서: + * 1. IMU FIFO start + * 2. piezo 6채널 burst + * 3. echo 샘플 채널별로 모으기 + * 4. reb: 패킷 6개 전송 + * 5. raa: 상태값 전송 + * 6. rim: IMU FIFO 15 샘플 전송 + */ +int cmd_mtb(const uint8_t *data, uint8_t data_len) +{ + ARG_UNUSED(data); + ARG_UNUSED(data_len); + + uint8_t session = cmd_next_echo_session(); + uint16_t rim_count = 0U; + bool fifo_started = false; + + processing = true; + power_button_suspend(true); + DBG_PRINTF("[MTB] cmd start\r\n"); + + int imu_ret = imu_fifo_start(); + if (imu_ret != 0) + { + DBG_PRINTF("[MTB] fifo start fail ret=%d\r\n", imu_ret); + } + else + { + fifo_started = true; + } + + int status = piezo_measure_start_session(); + + if (status == ECHO_STATUS_OK) // Piezo GPIO, power, ADC initialization/wake succeeded + { + DBG_PRINTF("[MTB] piezo sweep start\r\n"); + status = piezo_measure_sweep(); + if (status == ECHO_STATUS_OK) + { + for (uint8_t ch = 0; ch < PIEZO_NUM_CHANNELS; ch++) + { + DBG_PRINTF("[MTB] response reb: tx start ch=%d\r\n", ch); + cmd_send_response_echo(session, ch, piezo_measure_channel_buffer(ch), piezo_config_get()->samples); + } + } + } + + cmd_send_response_u16("raa:", (uint16_t)status); // 최종 상태 raa: 전송 + + if (status == ECHO_STATUS_OK) + { + if (fifo_started) + { + int imu_ret = imu_fifo_read_latest(tx_rim_samples, IMU_FIFO_RIM_TARGET_SAMPLES, &rim_count); + if (imu_ret != 0) + { + DBG_PRINTF("[MTB] fifo read fail ret=%d\r\n", imu_ret); + rim_count = 0U; + } + } + + cmd_send_response_rim(tx_rim_samples, rim_count); // IMU FIFO 샘플 rim: 전송 + } + DBG_PRINTF("[CMD] mtb status=0x%04X\r\n", status); + + piezo_power_off(); + power_button_suspend(false); + DBG_PRINTF("[CMD] mtb status=0x%04X rim=%u\r\n", status, rim_count); + + processing = false; + return 1; +} + +/* + * mcf?: piezo measurement parameter read + */ +int cmd_mcf(const uint8_t *data, uint8_t data_len) +{ + ARG_UNUSED(data); + ARG_UNUSED(data_len); + + const piezo_config_t *cfg = piezo_config_get(); + cmd_send_response_piezo_config("rcf:", cfg->freq, cfg->cycles, cfg->avg, cfg->delay_us, cfg->samples); + return 1; +} + +/* + * mcs?: piezo measurement parameter write + */ +int cmd_mcs(const uint8_t *data, uint8_t data_len) +{ + piezo_config_t cfg; + + if (data_len < 10U) + { + cmd_send_response_piezo_config("rcs:", 0xFFFF, 0U, 0U, 0U, 0U); + return 1; + } + + cmd_get_data_u16_be(data, data_len, 0, &cfg.freq); + cmd_get_data_u16_be(data, data_len, 1, &cfg.cycles); + cmd_get_data_u16_be(data, data_len, 2, &cfg.avg); + cmd_get_data_u16_be(data, data_len, 3, &cfg.delay_us); + cmd_get_data_u16_be(data, data_len, 4, &cfg.samples); + + if (!piezo_config_validate(&cfg)) + { + cmd_send_response_piezo_config("rcs:", 0xFFFF, cfg.cycles, cfg.avg, cfg.delay_us, cfg.samples); + return 1; + } + + int err = piezo_config_set(&cfg); + if (err) + { + cmd_send_response_piezo_config("rcs:", 0xFFFD, cfg.cycles, cfg.avg, cfg.delay_us, cfg.samples); + return 1; + } + + const piezo_config_t *saved_cfg = piezo_config_get(); + cmd_send_response_piezo_config("rcs:", saved_cfg->freq, saved_cfg->cycles, saved_cfg->avg, saved_cfg->delay_us, saved_cfg->samples); + return 1; +} + +// =============================== Test Command =============================== +/* TEST +* mim? : IMU FIFO 15 samples +*/ +int cmd_mim(const uint8_t *data, uint8_t data_len) +{ + ARG_UNUSED(data); + ARG_UNUSED(data_len); + + uint16_t rim_count = 0U; + int imu_ret = imu_fifo_start(); + if (imu_ret != 0) + { + DBG_PRINTF("[MTB] fifo start fail ret=%d\r\n", imu_ret); + cmd_send_response_rim(tx_rim_samples, 0U); + return 1; + } + + k_msleep(320); // IMU FIFO is 50Hz, 15 samples need about 300ms + + imu_ret = imu_fifo_read_latest(tx_rim_samples, IMU_FIFO_RIM_TARGET_SAMPLES, &rim_count); + if (imu_ret != 0) + { + DBG_PRINTF("[MIM] fifo read fail ret=%d\r\n", imu_ret); + rim_count = 0U; + } + + cmd_send_response_rim(tx_rim_samples, rim_count); // IMU FIFO 15 samples rim: 전송 + imu_fifo_stop(); + return 1; +} + +/* + * TEST (이전 정렬모드) + * maa?: piezo echo sweep + * - 순서: + * 1. piezo 6채널 burst + * 2. echo 샘플 채널별로 모으기 + * 3. reb: 패킷 6개 전송 + * 4. raa: 상태값 전송 + */ +int cmd_maa(const uint8_t *data, uint8_t data_len) +{ + ARG_UNUSED(data); + ARG_UNUSED(data_len); + + uint8_t session = cmd_next_echo_session(); + + processing = true; + power_button_suspend(true); + + int status = piezo_measure_start_session(); + + if (status == ECHO_STATUS_OK) // Piezo GPIO, power, ADC initialization/wake succeeded + { + DBG_PRINTF("[MBB] piezo sweep start\r\n"); + status = piezo_measure_sweep(); + if (status == ECHO_STATUS_OK) + { + for (uint8_t ch = 0; ch < PIEZO_NUM_CHANNELS; ch++) + { + DBG_PRINTF("[MBB] response reb: tx start ch=%d\r\n", ch); + cmd_send_response_echo(session, ch, piezo_measure_channel_buffer(ch), piezo_config_get()->samples); + } + } + } + + piezo_power_off(); + power_button_suspend(false); + cmd_send_response_u16("raa:", (uint16_t)status); // 최종 상태 raa: 전송 + DBG_PRINTF("[CMD] maa status=0x%04X\r\n", status); + + processing = false; + return 1; +} + +/* + * TEST + * mpa?: piezo power ON */ int cmd_mpa(const uint8_t *data, uint8_t data_len) { @@ -42,8 +314,8 @@ int cmd_mpa(const uint8_t *data, uint8_t data_len) } /* - * 테스트용 - * mpb?: piezo 전원 OFF + * TEST + * mpb?: piezo power OFF */ int cmd_mpb(const uint8_t *data, uint8_t data_len) { @@ -67,9 +339,8 @@ int cmd_mpb(const uint8_t *data, uint8_t data_len) } /* - * 테스트용 - * mpc?: burst만 한 번 발생시키는 테스트 명령 - * echo를 읽지 않고 초음파가 나가는지만 볼 때 사용 + * TEST + * mpc?: burst only */ int cmd_mpc(const uint8_t *data, uint8_t data_len) { @@ -109,10 +380,7 @@ int cmd_mpc(const uint8_t *data, uint8_t data_len) return 1; } - /* - * 현재 Zephyr 포팅본은 2.1MHz SW burst 하나만 구현되어 있음 - * 레거시의 freq_option 값은 받아두되, 아직은 같은 burst 함수로 처리 - */ + // 현재 2.1MHz 고정 piezo_burst_sw((uint8_t)cycles); piezo_power_off(); power_button_suspend(false); @@ -122,8 +390,8 @@ int cmd_mpc(const uint8_t *data, uint8_t data_len) } /* - * 테스트용 - * mec?: 단일 채널 burst + echo capture + * TEST + * mec?: 1 channel burst + echo capture */ int cmd_mec(const uint8_t *data, uint8_t data_len) { @@ -180,11 +448,8 @@ int cmd_mec(const uint8_t *data, uint8_t data_len) } /* - * 테스트용 - * mad?: 단일 채널 echo capture만 수행 - * - piezo burst는 하지 않고 echo ADC만 수행 - * - echo 샘플을 모은 뒤 reb: 패킷 1개 전송 - * - raa: 상태값은 전체 작업 성공/실패 요약 + * TEST + * mad?: 1 channel echo capture only */ int cmd_mad(const uint8_t *data, uint8_t data_len) { @@ -232,238 +497,4 @@ int cmd_mad(const uint8_t *data, uint8_t data_len) processing = false; return 1; -} - -/* - * 테스트용(이전 정렬모드) - * maa?: piezo echo sweep - * - 순서: - * 1. piezo 6채널 burst - * 2. echo 샘플 채널별로 모으기 - * 3. reb: 패킷 6개 전송 - * 4. raa: 상태값은 전체 작업 성공/실패 요약 - */ -int cmd_maa(const uint8_t *data, uint8_t data_len) -{ - ARG_UNUSED(data); - ARG_UNUSED(data_len); - - uint8_t session = cmd_next_echo_session(); - - processing = true; - power_button_suspend(true); - - int status = piezo_measure_start_session(); - - if (status == ECHO_STATUS_OK) // Piezo GPIO, 전원, ADC 초기화/wake 성공 - { - DBG_PRINTF("[MBB] piezo sweep start\r\n"); - status = piezo_measure_sweep(); - - for (uint8_t ch = 0; ch < PIEZO_NUM_CHANNELS; ch++) - { - DBG_PRINTF("[MBB] response reb: tx start ch=%d\r\n", ch); - cmd_send_response_echo(session, ch, piezo_measure_channel_buffer(ch), piezo_config_get()->samples); - } - } - - piezo_power_off(); - power_button_suspend(false); - cmd_send_response_u16("raa:", (uint16_t)status); // 최종 상태값 raa: 전송 - DBG_PRINTF("[CMD] maa status=0x%04X\r\n", status); - - processing = false; - return 1; -} - -/* - * 전체 측정 명령 - * mbb?: piezo echo sweep + 배터리 + IMU + IMU 온도 측정 - * - 성공 시 rbb 1개, reb 6개, raa 상태값 전송 - * - processing=true 구간에서는 내부 모니터의 I2C 접근을 막는 기준으로 사용 - * - * - 순서: - * 1. battery / imu / temp 측정 - * 2. rbb: 1개 전송 - * 3. piezo / echo ADC 준비 - * 4. piezo 6채널 burst - * 5. echo 샘플 채널별로 모으기 - * 6. reb: 6개 전송 - * 7. raa: 최종 상태 전송 - */ -int cmd_mbb(const uint8_t *data, uint8_t data_len) -{ - ARG_UNUSED(data); - ARG_UNUSED(data_len); - - uint8_t session = cmd_next_echo_session(); - - int16_t accel[3]; - int16_t gyro[3]; - - processing = true; - power_button_suspend(true); - DBG_PRINTF("[MBB] cmd start\r\n"); - - int status = piezo_measure_start_session(); - - int batt_mv = -1; - int16_t temp_cdeg = INT16_MIN; - - if (status == ECHO_STATUS_OK) // Piezo GPIO, 전원, ADC 초기화/wake 성공 - { - DBG_PRINTF("[MBB] battery read\r\n"); - batt_mv = battery_read_mv(); - - DBG_PRINTF("[MBB] imu/temp read\r\n"); - imu_read_with_temperature(accel, gyro, &temp_cdeg); - - DBG_PRINTF("[MBB] response rbb: tx start\r\n"); - cmd_send_response_bundle((uint16_t)batt_mv, accel, gyro, temp_cdeg); - - DBG_PRINTF("[MBB] piezo sweep start\r\n"); - status = piezo_measure_sweep(); - - for (uint8_t ch = 0; ch < PIEZO_NUM_CHANNELS; ch++) - { - DBG_PRINTF("[MBB] response reb: tx start ch=%d\r\n", ch); - cmd_send_response_echo(session, ch, piezo_measure_channel_buffer(ch), piezo_config_get()->samples); - } - } - - piezo_power_off(); - power_button_suspend(false); - DBG_PRINTF("[MBB] done\r\n"); - cmd_send_response_u16("raa:", (uint16_t)status); // 최종 상태값 raa: 전송 - DBG_PRINTF("[CMD] mbb status=0x%04X\r\n", status); - - processing = false; - return 1; -} - -/* - * 정렬모드 - * mtb?: IMU FIFO + piezo echo sweep - * - 순서: - * 1. IMU FIFO start - * 2. piezo 6채널 burst - * 3. echo 샘플 채널별로 모으기 - * 4. reb: 패킷 6개 전송 - * 5. raa: 상태값은 전체 작업 성공/실패 요약 - * - IMU FIFO 샘플은 rim: 패킷으로 전송 - */ -int cmd_mtb(const uint8_t *data, uint8_t data_len) -{ - ARG_UNUSED(data); - ARG_UNUSED(data_len); - - uint8_t session = cmd_next_echo_session(); - uint16_t rim_count = 0U; - bool fifo_started = false; - - processing = true; - power_button_suspend(true); - DBG_PRINTF("[MTB] cmd start\r\n"); - - int imu_ret = imu_fifo_start(); - if (imu_ret != 0) - { - DBG_PRINTF("[MTB] fifo start fail ret=%d\r\n", imu_ret); - } - else - { - fifo_started = true; - } - - int status = piezo_measure_start_session(); - - if (status == ECHO_STATUS_OK) // Piezo GPIO, 전원, ADC 초기화/wake 성공 - { - DBG_PRINTF("[MTB] piezo sweep start\r\n"); - status = piezo_measure_sweep(); - - for (uint8_t ch = 0; ch < PIEZO_NUM_CHANNELS; ch++) - { - DBG_PRINTF("[MTB] response reb: tx start ch=%d\r\n", ch); - cmd_send_response_echo(session, ch, piezo_measure_channel_buffer(ch), piezo_config_get()->samples); - } - } - - cmd_send_response_u16("raa:", (uint16_t)status); // 최종 상태값 raa: 전송 - - if (fifo_started) - { - int imu_ret = imu_fifo_read_latest(tx_rim_samples, IMU_FIFO_RIM_TARGET_SAMPLES, &rim_count); - if (imu_ret != 0) - { - DBG_PRINTF("[MTB] fifo read fail ret=%d\r\n", imu_ret); - rim_count = 0U; - } - } - - cmd_send_response_rim(tx_rim_samples, rim_count); // IMU FIFO 샘플 rim: 전송 - DBG_PRINTF("[CMD] mtb status=0x%04X\r\n", status); - - piezo_power_off(); - power_button_suspend(false); - DBG_PRINTF("[CMD] mtb status=0x%04X rim=%u\r\n", status, rim_count); - - processing = false; - return 1; -} - -/* - * mcf?: piezo 측정 파라미터 읽기 - * 응답 rcf: + 설정값 echo back - */ -int cmd_mcf(const uint8_t *data, uint8_t data_len) -{ - ARG_UNUSED(data); - ARG_UNUSED(data_len); - - const piezo_config_t *cfg = piezo_config_get(); - cmd_send_response_piezo_config("rcf:", cfg->freq, cfg->cycles, cfg->avg, cfg->delay_us, cfg->samples); - return 1; -} - -/* - * mcs?: piezo 측정 파라미터 쓰기 - */ -int cmd_mcs(const uint8_t *data, uint8_t data_len) -{ - piezo_config_t cfg; - - if (data_len < 10U) - { - cmd_send_response_piezo_config("rcs:", 0xFFFF, 0U, 0U, 0U, 0U); - return 1; - } - - cmd_get_data_u16_be(data, data_len, 0, &cfg.freq); - cmd_get_data_u16_be(data, data_len, 1, &cfg.cycles); - cmd_get_data_u16_be(data, data_len, 2, &cfg.avg); - cmd_get_data_u16_be(data, data_len, 3, &cfg.delay_us); - cmd_get_data_u16_be(data, data_len, 4, &cfg.samples); - - if (!piezo_config_validate(&cfg)) - { - cmd_send_response_piezo_config("rcs:", 0xFFFF, cfg.cycles, cfg.avg, cfg.delay_us, cfg.samples); - return 1; - } - - int err = piezo_config_set(&cfg); - if (err) - { - cmd_send_response_piezo_config("rcs:", 0xFFFD, cfg.cycles, cfg.avg, cfg.delay_us, cfg.samples); - return 1; - } - - const piezo_config_t *saved_cfg = piezo_config_get(); - cmd_send_response_piezo_config("rcs:", saved_cfg->freq, saved_cfg->cycles, saved_cfg->avg, saved_cfg->delay_us, saved_cfg->samples); - return 1; -} - -/* mls? → LED 상태 변경 → rls: + state echo back - * 파라미터: [state(2B LE)] — led_state_t enum 값 - * 0=OFF, 4=DETACH_WARNING, 5=ALIGN_SEARCHING, 6=ALIGN_COMPLETE, 7=ERROR - * 에러 응답: 0xFFFF=파라미터 없음, 0xFFFE=범위 초과 */ +} \ No newline at end of file diff --git a/src/command/handlers/cmd_piezo.h b/src/command/handlers/cmd_piezo.h index a8a4ca4..86bf480 100644 --- a/src/command/handlers/cmd_piezo.h +++ b/src/command/handlers/cmd_piezo.h @@ -17,5 +17,6 @@ int cmd_mbb(const uint8_t *data, uint8_t data_len); int cmd_mtb(const uint8_t *data, uint8_t data_len); int cmd_mcf(const uint8_t *data, uint8_t data_len); int cmd_mcs(const uint8_t *data, uint8_t data_len); +int cmd_mim(const uint8_t *data, uint8_t data_len); #endif /* CMD_PIEZO_H__ */ diff --git a/src/command/handlers/cmd_sensor.c b/src/command/handlers/cmd_sensor.c index b797ff2..bb40a89 100644 --- a/src/command/handlers/cmd_sensor.c +++ b/src/command/handlers/cmd_sensor.c @@ -13,6 +13,9 @@ #include "led_control.h" #include "cmd_sensor.h" +/* + * msn: battery voltage read + */ int cmd_msn(const uint8_t *data, uint8_t data_len) { ARG_UNUSED(data); @@ -28,6 +31,9 @@ int cmd_msn(const uint8_t *data, uint8_t data_len) return 1; } +/* + * msp: IMU data direct read + */ int cmd_msp(const uint8_t *data, uint8_t data_len) { ARG_UNUSED(data); @@ -47,8 +53,9 @@ int cmd_msp(const uint8_t *data, uint8_t data_len) return 1; } -/* mst? -> IMU internal temperature -> rso: + temperature (degC x 100, BE) - * Error response: 0xFFFF = IMU temperature read failed. */ +/* + * mst: IMU temperature direct read + */ int cmd_mst(const uint8_t *data, uint8_t data_len) { ARG_UNUSED(data); @@ -68,19 +75,18 @@ int cmd_mst(const uint8_t *data, uint8_t data_len) return 1; } - /* 음수 온도도 2's complement로 그대로 전송 (앱이 int16로 해석) */ + // 음수 온도도 2's complement로 그대로 전송 (앱이 int16로 해석) cmd_send_response_u16("rso:", (uint16_t)t_cdeg); DBG_PRINTF("[CMD] mst -> %d.%02d C\r\n", t_cdeg / 100, (t_cdeg < 0 ? -t_cdeg : t_cdeg) % 100); return 1; } /* - * 테스트용 - * mpa?: piezo TX/RX 전원 레일만 켬 + * mls: LED status set */ int cmd_mls(const uint8_t *data, uint8_t data_len) { - /* 파라미터 부족: 에러 코드 0xFFFF 에코 */ + // 파라미터 부족: 에러 코드 0xFFFF 에코 if (data_len < 2) { cmd_send_response_u16("rls:", 0xFFFF); @@ -88,10 +94,10 @@ int cmd_mls(const uint8_t *data, uint8_t data_len) return 1; } - /* state 필드: 2바이트 big-endian */ + // state 필드: 2바이트 big-endian uint16_t state = ((uint16_t)data[0] << 8) | (uint16_t)data[1]; - /* 범위 초과: 에러 코드 0xFFFE 에코 */ + // 범위 초과: 에러 코드 0xFFFE 에코 if (state > LED_STATE_ERROR) { cmd_send_response_u16("rls:", 0xFFFE); @@ -101,14 +107,12 @@ int cmd_mls(const uint8_t *data, uint8_t data_len) led_set_state((led_state_t)state); - /* 정렬 LED 상태와 보호 판정 정책 연결 - * - ALIGN_SEARCHING/ALIGN_COMPLETE 동안 보호 판정 차단 - * - 다른 LED 상태에서는 기존 조건에 따라 보호 판정 재개 - */ - battery_protection_set_alignment_mode((state == LED_STATE_ALIGN_SEARCHING) || - (state == LED_STATE_ALIGN_COMPLETE)); + // 정렬 LED 상태와 보호 판정 정책 연결 + // - ALIGN_SEARCHING/ALIGN_COMPLETE 동안 보호 판정 차단 + // - 다른 LED 상태에서는 기존 조건에 따라 보호 판정 재개 + battery_protection_set_alignment_mode((state == LED_STATE_ALIGN_SEARCHING) || (state == LED_STATE_ALIGN_COMPLETE)); - /* OFF 상태에서는 정렬/측정용 FIFO를 함께 정리 */ + // OFF 상태에서는 정렬/측정용 FIFO를 함께 정리 if (state == LED_STATE_OFF) { (void)imu_fifo_stop(); @@ -117,7 +121,3 @@ int cmd_mls(const uint8_t *data, uint8_t data_len) cmd_send_response_u16("rls:", state); return 1; } - -/*============================================================================== - * 커맨드 테이블 - *============================================================================*/ diff --git a/src/command/parser.c b/src/command/parser.c index e855091..70a44ab 100644 --- a/src/command/parser.c +++ b/src/command/parser.c @@ -12,9 +12,7 @@ #include "cmd_table.h" #include "debug_print.h" -/*============================================================================== - * 파서 엔트리 - *============================================================================*/ +/* 파서 엔트리 */ int ble_cmd_dispatch(const uint8_t *buf, uint16_t len) { //DBG_CORE("[PARSER] enter len=%u\r\n", len); diff --git a/src/command/parser.h b/src/command/parser.h index 90c1725..d124bbc 100644 --- a/src/command/parser.h +++ b/src/command/parser.h @@ -2,7 +2,7 @@ * @file parser.h * @brief BLE command parser * - * 4바이트 TAG + DATA + CRC16 패킷 파싱 및 디스패치 + * Packet format : [TAG 4B] [DATA NB] [CRC16 2B] ******************************************************************************/ #ifndef CMD_PARSER_H__ #define CMD_PARSER_H__ diff --git a/src/drivers/battery/battery_adc.c b/src/drivers/battery/battery_adc.c index e0c66e0..4feaee2 100644 --- a/src/drivers/battery/battery_adc.c +++ b/src/drivers/battery/battery_adc.c @@ -26,9 +26,7 @@ #include "imu_i2c.h" #include "main.h" -/*============================================================================== - * ADC 디바이스트리 설정 - *============================================================================*/ +/* ADC 디바이스트리 설정 */ #define ZEPHYR_USER_NODE DT_PATH(zephyr_user) /* 디바이스트리에서 ADC 채널 스펙 가져오기 (gain, ref, acq time, input, resolution, oversampling) */ @@ -36,78 +34,79 @@ static const struct adc_dt_spec battery_adc = ADC_DT_SPEC_GET_BY_NAME(ZEPHYR_USE static int16_t adc_buffer; -/*============================================================================== - * Module variables - *============================================================================*/ +/* Module variables */ volatile bool battery_saadc_done = false; volatile uint16_t info_batt = 0; static struct k_timer battery_timer; static struct k_work battery_work; -/* 60초 내부 모니터 전용 카운터 - * - 앱 측정 명령(mbb?)이 없는 일반 상태에서만 사용 - * - mbb? 성공 시 연속 측정 판정으로 넘어가므로 0으로 초기화 +/* + * 60초 내부 모니터 전용 카운터 + * - 앱 측정 명령(mbb?/mtb?)이 없는 일반 상태에서만 사용 + * - mbb? 성공 시 mbb? 측정 판정으로 넘어가므로 0으로 초기화 */ static uint8_t periodic_low_battery_cnt; static uint8_t periodic_high_temperature_cnt; -/* 연속 측정(mbb?) 응답값 전용 카운터 +/* + * 주기 측정(mbb?) 응답값 전용 카운터 * - mbb? 응답에 포함된 배터리/IMU 온도 값으로 판정 * - 측정 실패 시 호출되지 않으므로 기존 카운트 유지 */ static uint8_t continuous_low_battery_cnt; static uint8_t continuous_high_temperature_cnt; -/* 정렬 상태 보호 판정 차단 플래그 +/* + * 정렬 상태 보호 판정 차단 플래그 * - mls?로 ALIGN_SEARCHING/ALIGN_COMPLETE 진입 시 true * - 정렬 중에는 배터리/온도 보호 판정 자체를 진행하지 않음 */ static bool alignment_mode_active; -/* 최근 mbb? 성공 이후 내부 60초 모니터를 잠시 막는 기준 시각 - * - 연속 측정 중에는 mbb? 응답값으로 보호 판정 +/* + * 최근 mbb? 성공 이후 내부 60초 모니터를 잠시 막는 기준 시각 + * - 주기 측정 중에는 mbb? 응답값으로 보호 판정 * - 이 값보다 현재 시간이 작으면 60초 내부 모니터는 skip */ static int64_t continuous_monitor_active_until_ms; -#define BATTERY_MONITOR_LIMIT_COUNT 5U -#define BATTERY_CONTINUOUS_MONITOR_LIMIT_COUNT 30U -#define HIGH_TEMPERATURE_CDEG 4000 /* 40.00 C */ -#define CONTINUOUS_MONITOR_ACTIVE_MS 15000 /* mbb? 10초 주기 대비 여유 */ +#define BATTERY_MONITOR_LIMIT_COUNT 5U // 정렬모드(mtb?)나 주기 측정(mbb?)이 아닌 경우 60초 주기 5회 연속 저전압 감지 시 전원 OFF(5분) +#define BATTERY_CONTINUOUS_MONITOR_LIMIT_COUNT 10U // 주기 측정 10회 연속 저전압 감지 시 전원 OFF(100초) +#define HIGH_TEMPERATURE_CDEG 4000 // 40.00 C +#define CONTINUOUS_MONITOR_ACTIVE_MS 15000 // mbb? 10초 주기 대비 여유 -#define BATTERY_MONITOR_INTERVAL_MS 60000 /* 60초 주기 */ +#define BATTERY_MONITOR_INTERVAL_MS 60000 // 정렬모드(mtb?)나 주기 측정(mbb?)이 아닌 경우 저전압/고온 모니터 주기 60초 -/*============================================================================== +/* * 전압 변환 (ADC raw → mV 변환) * 공식: (raw × 600 / 4095) × 6 × 1.42 * 정수 연산: raw × 600 × 6 × 142 / (4095 × 100) - *============================================================================*/ + */ static int adc_raw_to_mv(int16_t raw) { if (raw < 0) { raw = 0; } - /* 오버플로 방지: 600 × 6 × 142 = 511,200 → raw 최대 4095 → 4095 × 511200 = ~2B → int32 OK */ + // 오버플로 방지: 600 × 6 × 142 = 511,200 → raw 최대 4095 → 4095 × 511200 = ~2B → int32 OK int32_t mv = ((int32_t)raw * 600 * 6 * 142) / (4095 * 100); return (int)mv; } static bool continuous_monitor_is_active(void) { - /* 마지막 mbb? 성공 이후 CONTINUOUS_MONITOR_ACTIVE_MS 안쪽이면 연속 측정 중으로 간주 */ - return (continuous_monitor_active_until_ms > 0) && - (k_uptime_get() < continuous_monitor_active_until_ms); + // 마지막 mbb? 성공 이후 CONTINUOUS_MONITOR_ACTIVE_MS 안쪽이면 주기 측정(mbb?) 중으로 간주 + return (continuous_monitor_active_until_ms > 0) && (k_uptime_get() < continuous_monitor_active_until_ms); } -static void record_battery_value(int batt_mv, uint8_t *cnt, - uint8_t limit, const char *mode) +/* +* mode별 카운터만 전달받아 공통 판정 로직 사용 +* - periodic: 60초 내부 모니터, limit 5 +* - continuous: mbb? 응답값 기반, limit 10 +*/ +static void record_battery_value(int batt_mv, uint8_t *cnt, uint8_t limit, const char *mode) { - /* mode별 카운터만 전달받아 공통 판정 로직 사용 - * - periodic: 60초 내부 모니터, limit 5 - * - continuous: mbb? 응답값 기반, limit 30 - */ if (batt_mv <= LOW_BATTERY_VOLTAGE) { if (*cnt < UINT8_MAX) @@ -115,8 +114,7 @@ static void record_battery_value(int batt_mv, uint8_t *cnt, (*cnt)++; } - DBG_PRINTF("[BATT] LOW! mode=%s cnt=%u/%u mv=%d\r\n", - mode, *cnt, limit, batt_mv); + DBG_PRINTF("[BATT] LOW! mode=%s cnt=%u/%u mv=%d\r\n", mode, *cnt, limit, batt_mv); if (*cnt >= limit) { @@ -131,14 +129,14 @@ static void record_battery_value(int batt_mv, uint8_t *cnt, } } -static void record_temperature_value(int16_t temp_cdeg, uint8_t *cnt, - uint8_t limit, const char *mode) +/* + * IMU 온도는 centi-degree 단위 + * - 4000 = 40.00 C + * - 40.00 C 이상이면 고온 카운트 증가 + * - 기준 미만이면 해당 mode 카운터 초기화 +*/ +static void record_temperature_value(int16_t temp_cdeg, uint8_t *cnt, uint8_t limit, const char *mode) { - /* IMU 온도는 centi-degree 단위 - * - 4000 = 40.00 C - * - 40.00 C 이상이면 고온 카운트 증가 - * - 기준 미만이면 해당 mode 카운터 초기화 - */ if (temp_cdeg >= HIGH_TEMPERATURE_CDEG) { if (*cnt < UINT8_MAX) @@ -166,20 +164,17 @@ static void record_temperature_value(int16_t temp_cdeg, uint8_t *cnt, } } -/*============================================================================== - * Public functions - *============================================================================*/ - +/* Public functions */ void battery_adc_init(void) { - /* ADC 디바이스 준비 상태 확인 */ + // ADC 디바이스 준비 상태 확인 if (!adc_is_ready_dt(&battery_adc)) { DBG_PRINTF("[BATT] ADC device not ready\r\n"); return; } - /* 디바이스트리 설정으로 채널 구성 */ + // 디바이스트리 설정으로 채널 구성 int err = adc_channel_setup_dt(&battery_adc); if (err) { @@ -195,7 +190,7 @@ void battery_adc_init(void) int battery_read_mv(void) { - /* 매번 채널 재설정 (다른 센서와 SAADC 공유 시 필요) */ + // 매번 채널 재설정 (다른 센서와 SAADC 공유 시 필요) int err = adc_channel_setup_dt(&battery_adc); if (err) { @@ -203,7 +198,7 @@ int battery_read_mv(void) return -1; } - /* 디바이스트리에서 시퀀스 초기화 (channels, resolution, oversampling) */ + // 디바이스트리에서 시퀀스 초기화 (channels, resolution, oversampling) struct adc_sequence seq = {0}; err = adc_sequence_init_dt(&battery_adc, &seq); @@ -216,7 +211,7 @@ int battery_read_mv(void) seq.buffer = &adc_buffer; seq.buffer_size = sizeof(adc_buffer); - /* ADC 읽기 */ + // ADC 읽기 err = adc_read_dt(&battery_adc, &seq); if (err) { @@ -233,13 +228,13 @@ int battery_read_mv(void) return mv; } - +/* + * 정렬 상태 진입/해제는 mls? 명령에서 전달 + * - enabled true: 보호 판정 중지 + 기존 누적값 초기화 + * - enabled false: 다음 조건부터 다시 판정 가능 + */ void battery_protection_set_alignment_mode(bool enabled) { - /* 정렬 상태 진입/해제는 mls? 명령에서 전달 - * - enabled true: 보호 판정 중지 + 기존 누적값 초기화 - * - enabled false: 다음 조건부터 다시 판정 가능 - */ alignment_mode_active = enabled; if (enabled) { @@ -250,55 +245,43 @@ void battery_protection_set_alignment_mode(bool enabled) } } +/* + * mbb? 측정 성공 시에만 호출 + * - 실패한 측정은 호출되지 않아서 continuous 카운트 유지 + * - 정렬 중에는 응답값이 있어도 보호 판정 제외 + * - mbb?가 들어온 직후에는 내부 60초 모니터 대신 응답값 기반 판정 사용 +*/ void battery_protection_record_continuous(int batt_mv, int16_t temp_cdeg) { - /* mbb? 측정 성공 시에만 호출 - * - 실패한 측정은 호출되지 않아서 continuous 카운트 유지 - * - 정렬 중에는 응답값이 있어도 보호 판정 제외 - */ + // 정렬모드 중에는 continuous 보호 판정 제외 if (alignment_mode_active) { return; } - /* mbb?가 들어온 직후에는 내부 60초 모니터 대신 응답값 기반 판정 사용 - * - periodic 카운터는 섞이지 않게 초기화 - * - continuous 카운터는 아래 record_* 함수에서 값에 따라 증가/초기화 - */ + // 15초 후 일반 60초 주기 모니터 재개 continuous_monitor_active_until_ms = k_uptime_get() + CONTINUOUS_MONITOR_ACTIVE_MS; + + // periodic 모니터 카운트 초기화 periodic_low_battery_cnt = 0; periodic_high_temperature_cnt = 0; - record_battery_value(batt_mv, - &continuous_low_battery_cnt, - BATTERY_CONTINUOUS_MONITOR_LIMIT_COUNT, - "continuous"); - record_temperature_value(temp_cdeg, - &continuous_high_temperature_cnt, - BATTERY_CONTINUOUS_MONITOR_LIMIT_COUNT, - "continuous"); + record_battery_value(batt_mv, &continuous_low_battery_cnt, BATTERY_CONTINUOUS_MONITOR_LIMIT_COUNT, "continuous"); + record_temperature_value(temp_cdeg, &continuous_high_temperature_cnt, BATTERY_CONTINUOUS_MONITOR_LIMIT_COUNT, "continuous"); } -/*============================================================================== - * 주기 모니터링 타이머 - *============================================================================*/ - /* k_work 핸들러: 스레드 컨텍스트에서 ADC 읽기 (adc_read는 블로킹이라 ISR 불가) */ - static void battery_work_handler(struct k_work *work) { ARG_UNUSED(work); - /* 보호 판정 제외 구간 - * - 정렬 중: 정책상 보호 판정 미진행 - * - 최근 mbb? 성공 직후: mbb? 응답값 기반 continuous 판정이 담당 - */ + // 일반 60초 주기 보호 판정 제외 구간: 정렬모드 또는 주기 측정 if (alignment_mode_active || continuous_monitor_is_active()) { return; } - /* 측정 처리 중이거나 IMU FIFO 사용 중(정렬모드)이면 I2C 접근 충돌 방지 */ + // I2C 접근 충돌 방지: 긴 측정 명령 처리 중이거나 IMU FIFO 동작 중 if (processing || imu_fifo_is_active()) { return; @@ -311,14 +294,8 @@ static void battery_work_handler(struct k_work *work) return; } - /* 일반 상태의 60초 내부 모니터 판정 - * - 저전압이면 periodic_low_battery_cnt 증가 - * - 정상 전압이면 periodic_low_battery_cnt 초기화 - */ - record_battery_value(batt, - &periodic_low_battery_cnt, - BATTERY_MONITOR_LIMIT_COUNT, - "periodic"); + // 정렬모드나 주기 측정 중이 아닌 경우 60초 내부 모니터 판정(배터리) + record_battery_value(batt, &periodic_low_battery_cnt, BATTERY_MONITOR_LIMIT_COUNT, "periodic"); int16_t temp_cdeg = 0; int temp_ret = imu_read_temperature_cdeg(&temp_cdeg); @@ -329,14 +306,8 @@ static void battery_work_handler(struct k_work *work) return; } - /* 일반 상태의 IMU 온도 판정 - * - 고온이면 periodic_high_temperature_cnt 증가 - * - 정상 온도이면 periodic_high_temperature_cnt 초기화 - */ - record_temperature_value(temp_cdeg, - &periodic_high_temperature_cnt, - BATTERY_MONITOR_LIMIT_COUNT, - "periodic"); + // 정렬모드나 주기 측정 중이 아닌 경우 60초 내부 모니터 판정(온도) + record_temperature_value(temp_cdeg, &periodic_high_temperature_cnt, BATTERY_MONITOR_LIMIT_COUNT, "periodic"); } /* 타이머 ISR → k_work 예약 */ diff --git a/src/drivers/echo_adc/echo_adc.c b/src/drivers/echo_adc/echo_adc.c index 410973e..39e227e 100644 --- a/src/drivers/echo_adc/echo_adc.c +++ b/src/drivers/echo_adc/echo_adc.c @@ -18,6 +18,9 @@ #define ECHO_MISO_NODE DT_NODELABEL(echo_miso) #define ECHO_CS_NODE DT_NODELABEL(echo_cs) +/* SPIM END event 대기 최대 반복 횟수 */ +#define ECHO_ADC_SPIM_END_TIMEOUT_LOOPS 100000U + /* ADC SPI 핀 */ static const struct gpio_dt_spec echo_sclk = GPIO_DT_SPEC_GET(ECHO_SCLK_NODE, gpios); static const struct gpio_dt_spec echo_miso = GPIO_DT_SPEC_GET(ECHO_MISO_NODE, gpios); @@ -42,7 +45,7 @@ static uint32_t gpio_abs_pin(const struct gpio_dt_spec *spec) return NRF_GPIO_PIN_MAP(port, spec->pin); } -/* SPIM RX 전송 */ +/* SPIM RX 전송: ADC wake/dummy read용 nrfx 경로 */ static int echo_adc_transfer_rx(uint8_t *buf, size_t len) { nrfx_spim_xfer_desc_t xfer = NRFX_SPIM_XFER_RX(buf, len); @@ -68,37 +71,7 @@ static void echo_adc_unpack_samples(uint16_t *samples, uint16_t num_samples) } } -/* 무응답 샘플 확인 */ -static bool echo_adc_samples_all_zero(const uint16_t *samples, uint16_t num_samples) -{ - for (uint16_t i = 0; i < num_samples; i++) - { - if (samples[i] != 0U) - { - return false; - } - } - - return true; -} - -/* 안정 우선 캡처 */ -static int echo_adc_capture_slow(uint16_t *samples, uint16_t num_samples) -{ - for (uint16_t i = 0; i < num_samples; i++) - { - int err = echo_adc_transfer_rx(&raw_capture[i * 2U], 2U); - if (err) - { - return err; - } - } - - echo_adc_unpack_samples(samples, num_samples); - return 0; -} - -/* 속도 우선 캡처 */ +/* 샘플마다 CS를 직접 제어하고 SPIM RX 2바이트를 수행 */ static int echo_adc_capture_fast(uint16_t *samples, uint16_t num_samples) { NRF_SPIM_Type *spim = echo_spim.p_reg; @@ -116,9 +89,19 @@ static int echo_adc_capture_fast(uint16_t *samples, uint16_t num_samples) cs_port->OUTCLR = cs_mask; spim->TASKS_START = 1; - /* 전송 완료 대기 */ + /* 전송 완료 대기: SPIM이 꼬인 경우 IRQ lock 상태로 영구 정지하지 않도록 timeout 처리 */ + uint32_t timeout = ECHO_ADC_SPIM_END_TIMEOUT_LOOPS; while (!spim->EVENTS_END) { + if (--timeout == 0U) + { + spim->EVENTS_STOPPED = 0; + spim->TASKS_STOP = 1; + cs_port->OUTSET = cs_mask; + spim->EVENTS_END = 0; + DBG_PRINTF("[ECHO] SPIM END timeout sample=%u\r\n", i); + return -ETIMEDOUT; + } } cs_port->OUTSET = cs_mask; @@ -130,14 +113,14 @@ static int echo_adc_capture_fast(uint16_t *samples, uint16_t num_samples) int echo_adc_init(void) { - /* 중복 초기화 방지 */ + // 중복 초기화 방지 if (echo_adc_initialized || nrfx_spim_init_check(&echo_spim)) { echo_adc_initialized = true; return 0; } - /* GPIO 준비 확인 */ + // GPIO 준비 확인 const struct gpio_dt_spec *pins[] = { &echo_sclk, &echo_miso, &echo_cs }; for (size_t i = 0; i < ARRAY_SIZE(pins); i++) @@ -190,7 +173,7 @@ int echo_adc_wake(void) int echo_adc_capture(uint16_t *samples, uint16_t num_samples) { - /* 입력값 검사 */ + // 입력값 검사 if (samples == NULL) { return -EINVAL; @@ -207,14 +190,5 @@ int echo_adc_capture(uint16_t *samples, uint16_t num_samples) return err; } - /* 빠른 캡처 우선 */ - err = echo_adc_capture_fast(samples, num_samples); - if ((err == 0) && !echo_adc_samples_all_zero(samples, num_samples)) - { - return 0; - } - - /* 실패 시 안정 캡처 */ - DBG_PRINTF("[ECHO] fast capture fallback err=%d\r\n", err); - return echo_adc_capture_slow(samples, num_samples); + return echo_adc_capture_fast(samples, num_samples); } diff --git a/src/drivers/imu/imu_i2c.c b/src/drivers/imu/imu_i2c.c index 2d44013..ed9df91 100644 --- a/src/drivers/imu/imu_i2c.c +++ b/src/drivers/imu/imu_i2c.c @@ -1,16 +1,7 @@ /******************************************************************************* * @file imu_i2c.c * @brief ICM42670P IMU Driver (Zephyr I2C API) - * - * 동작 방식 (매 측정 시): - * 1) GYRO_CONFIG0 = 0x09 → ±2000dps, 100Hz ODR - * 2) ACCEL_CONFIG0 = 0x29 → ±4g, 100Hz ODR - * 3) PWR_MGMT0 = 0x0F → accel(low-noise) + gyro(low-noise) ON - * 4) 80ms 대기 (자이로 스타트업 최소 45ms + 여유) - * 5) 0x0B부터 12바이트 연속 읽기: accel XYZ(6B) + gyro XYZ(6B) - * 6) 빅엔디안 → int16_t 변환 - * 7) PWR_MGMT0 = 0x00 → 슬립 (전력 절감) - * + * I2C 핀: SCL=P1.14, SDA=P1.15 (overlay에서 설정) ******************************************************************************/ @@ -29,34 +20,34 @@ * 디바이스트리 / I2C 설정 *============================================================================*/ #define IMU_I2C_NODE DT_NODELABEL(i2c0) -#define IMU_I2C_ADDR 0x68 /* ICM42670P 기본 I2C 주소 (AD0=LOW) */ +#define IMU_I2C_ADDR 0x68 // ICM42670P 기본 I2C 주소 (AD0=LOW) /*============================================================================== * ICM42670P 레지스터 주소 *============================================================================*/ -#define REG_PWR_MGMT0 0x1F /* 전원 관리: accel/gyro 동작 모드 */ -#define REG_GYRO_CONFIG0 0x20 /* 자이로 FSR + ODR 설정 */ -#define REG_ACCEL_CONFIG0 0x21 /* 가속도 FSR + ODR 설정 */ -#define REG_GYRO_CONFIG1 0x23 /* 자이로 LN 필터 BW */ -#define REG_ACCEL_CONFIG1 0x24 /* 가속도 LN 필터 BW */ -#define REG_FIFO_CONFIG1 0x28 /* FIFO bypass/mode */ -#define REG_FIFO_CONFIG2 0x29 /* FIFO watermark LSB */ -#define REG_FIFO_CONFIG3 0x2A /* FIFO watermark MSB */ -#define REG_INTF_CONFIG0 0x35 /* FIFO count/endian 설정 */ -#define REG_FIFO_COUNTH 0x3D /* FIFO count high */ -#define REG_FIFO_DATA 0x3F /* FIFO data pop */ -#define REG_BLK_SEL_W 0x79 /* MREG write block */ -#define REG_MADDR_W 0x7A /* MREG write addr */ -#define REG_M_W 0x7B /* MREG write data */ -#define REG_BLK_SEL_R 0x7C /* MREG read block */ -#define REG_MADDR_R 0x7D /* MREG read addr */ -#define REG_M_R 0x7E /* MREG read data */ -#define REG_TEMP_DATA1 0x09 /* IMU 내부 온도 상위 바이트 */ -#define REG_ACCEL_DATA_X1 0x0B /* 가속도 X축 상위 바이트 (시작 레지스터) */ -#define REG_WHO_AM_I 0x75 /* WHOAMI 레지스터 */ -#define REG_MCLK_RDY 0x00 /* MCLK ready */ -#define REG_SIGNAL_PATH_RESET 0x02 /* FIFO flush */ -#define ICM_WHOAMI 0x67 /* ICM42670P 식별값 */ +#define REG_PWR_MGMT0 0x1F // 전원 관리: accel/gyro 동작 모드 +#define REG_GYRO_CONFIG0 0x20 // 자이로 FSR + ODR 설정 +#define REG_ACCEL_CONFIG0 0x21 // 가속도 FSR + ODR 설정 +#define REG_GYRO_CONFIG1 0x23 // 자이로 LN 필터 BW +#define REG_ACCEL_CONFIG1 0x24 // 가속도 LN 필터 BW +#define REG_FIFO_CONFIG1 0x28 // FIFO bypass/mode +#define REG_FIFO_CONFIG2 0x29 // FIFO watermark LSB +#define REG_FIFO_CONFIG3 0x2A // FIFO watermark MSB +#define REG_INTF_CONFIG0 0x35 // FIFO count/endian 설정 +#define REG_FIFO_COUNTH 0x3D // FIFO count high +#define REG_FIFO_DATA 0x3F // FIFO data pop +#define REG_BLK_SEL_W 0x79 // MREG write block +#define REG_MADDR_W 0x7A // MREG write addr +#define REG_M_W 0x7B // MREG write data +#define REG_BLK_SEL_R 0x7C // MREG read block +#define REG_MADDR_R 0x7D // MREG read addr +#define REG_M_R 0x7E // MREG read data +#define REG_TEMP_DATA1 0x09 // IMU 내부 온도 상위 바이트 +#define REG_ACCEL_DATA_X1 0x0B // 가속도 X축 상위 바이트 (시작 레지스터) +#define REG_WHO_AM_I 0x75 // WHOAMI 레지스터 +#define REG_MCLK_RDY 0x00 // MCLK ready +#define REG_SIGNAL_PATH_RESET 0x02 // FIFO flush +#define ICM_WHOAMI 0x67 // ICM42670P 식별값 /* 자이로 스타트업 대기 시간 (ms) — 스펙 최소 45ms, 80ms로 여유 확보 */ #define IMU_GYRO_STARTUP_MS 80 @@ -95,9 +86,7 @@ static const struct device *i2c_bus; static bool fifo_active; static uint8_t fifo_raw[IMU_FIFO_PACKET_BYTES * IMU_FIFO_MAX_RECORDS]; -/*============================================================================== - * 내부 I2C 래퍼 - *============================================================================*/ +/* 내부 I2C 래퍼 */ /* 레지스터 1바이트 쓰기: [reg, val] 2바이트 TX */ static int imu_write_reg(uint8_t reg, uint8_t val) @@ -375,9 +364,7 @@ static uint16_t imu_fifo_compact_records(uint16_t record_count) return write_idx; } -/*============================================================================== - * 공개 API - *============================================================================*/ +/* 공개 API */ int imu_init(void) { @@ -407,31 +394,31 @@ int imu_read(int16_t accel[3], int16_t gyro[3]) uint8_t raw[12]; int ret; - /* 자이로 설정: ±2000dps FSR, 100Hz ODR */ + // 자이로 설정: ±2000dps FSR, 100Hz ODR ret = imu_write_reg(REG_GYRO_CONFIG0, 0x09); if (ret) { DBG_PRINTF("[IMU] FAIL — gyro config write (ret=%d)\r\n", ret); return -1; } - /* 가속도 설정: ±4g FSR, 100Hz ODR */ + // 가속도 설정: ±4g FSR, 100Hz ODR ret = imu_write_reg(REG_ACCEL_CONFIG0, 0x29); if (ret) { DBG_PRINTF("[IMU] FAIL — accel config write (ret=%d)\r\n", ret); return -1; } ret = imu_set_data_big_endian(); if (ret) { DBG_PRINTF("[IMU] FAIL — endian config write (ret=%d)\r\n", ret); return -1; } - /* 전원 ON: accel(저잡음) + gyro(저잡음) 모두 활성화 */ + // 전원 ON: accel(저잡음) + gyro(저잡음) 모두 활성화 ret = imu_write_reg(REG_PWR_MGMT0, 0x0F); if (ret) { DBG_PRINTF("[IMU] FAIL — power on write (ret=%d)\r\n", ret); return -1; } - /* 자이로 스타트업 대기 */ + // 자이로 스타트업 대기 k_msleep(IMU_GYRO_STARTUP_MS); - /* ACCEL_DATA_X1(0x0B)부터 12바이트 연속 읽기 - * [0..5] = accel X,Y,Z (MSB first) - * [6..11] = gyro X,Y,Z (MSB first) */ + // ACCEL_DATA_X1(0x0B)부터 12바이트 연속 읽기 + // [0..5] = accel X,Y,Z (MSB first) + // [6..11] = gyro X,Y,Z (MSB first) ret = imu_read_regs(REG_ACCEL_DATA_X1, raw, 12); if (ret) { DBG_PRINTF("[IMU] FAIL — data read (ret=%d)\r\n", ret); return -2; } - /* 빅엔디안 → int16_t 변환 */ + // 빅엔디안 → int16_t 변환 accel[0] = (int16_t)((raw[0] << 8) | raw[1]); accel[1] = (int16_t)((raw[2] << 8) | raw[3]); accel[2] = (int16_t)((raw[4] << 8) | raw[5]); @@ -441,7 +428,7 @@ int imu_read(int16_t accel[3], int16_t gyro[3]) //DBG_PRINTF("[IMU] msp: A=(%6d,%6d,%6d) G=(%6d,%6d,%6d)\r\n", accel[0], accel[1], accel[2], gyro[0], gyro[1], gyro[2]); - /* 슬립 모드: 전력 절감 */ + // 슬립 모드: 전력 절감 imu_write_reg(REG_PWR_MGMT0, 0x00); return 0; diff --git a/src/drivers/led/led_control.c b/src/drivers/led/led_control.c index c5d34fa..5134797 100644 --- a/src/drivers/led/led_control.c +++ b/src/drivers/led/led_control.c @@ -11,33 +11,25 @@ #include "led_control.h" #include "debug_print.h" -/*============================================================================== - * Devicetree LED specs - *============================================================================*/ +/* Devicetree LED specs */ #define LED_BLE_NODE DT_NODELABEL(led_ble) #define FUNCTION_LED_NODE DT_NODELABEL(function_led) static const struct gpio_dt_spec led_ble = GPIO_DT_SPEC_GET(LED_BLE_NODE, gpios); static const struct gpio_dt_spec function_led = GPIO_DT_SPEC_GET(FUNCTION_LED_NODE, gpios); -/*============================================================================== - * Color constants - *============================================================================*/ +/* Color constants */ #define COLOR_NONE 0 #define COLOR_GREEN 1 #define COLOR_ORANGE 2 -/*============================================================================== - * Error pattern constants (State 7) - *============================================================================*/ +/* Error pattern constants (State 7) */ #define ERROR_BLINK_ON_MS 166 #define ERROR_BLINK_OFF_MS 166 #define ERROR_BLINK_COUNT 3 #define ERROR_PAUSE_MS 1000 -/*============================================================================== - * Pattern table - *============================================================================*/ +/* Pattern table */ typedef struct { uint32_t on_ms; uint32_t off_ms; @@ -56,9 +48,7 @@ static const led_pattern_t m_patterns[LED_STATE_COUNT] = { [LED_STATE_ERROR] = { 0, 0, COLOR_ORANGE, true }, }; -/*============================================================================== - * Module variables - *============================================================================*/ +/* Module variables */ static struct k_timer m_led_timer; static led_state_t m_current_state = LED_STATE_OFF; @@ -68,9 +58,7 @@ static bool m_phase_on; static uint8_t m_error_blink_cnt; static uint8_t m_error_phase; -/*============================================================================== - * GPIO helpers - *============================================================================*/ +/* GPIO helpers */ static inline void led_ble_on(void) { gpio_pin_set_dt(&led_ble, 1); } static inline void led_ble_off(void) { gpio_pin_set_dt(&led_ble, 0); } static inline void function_led_on(void) { gpio_pin_set_dt(&function_led, 1); } @@ -89,18 +77,14 @@ static void led_color_on(uint8_t color) else if (color == COLOR_ORANGE) function_led_on(); } -/*============================================================================== - * Timer helper - *============================================================================*/ +/* Timer helper */ static void timer_start_ms(uint32_t ms) { if (ms == 0) return; k_timer_start(&m_led_timer, K_MSEC(ms), K_NO_WAIT); } -/*============================================================================== - * Error pattern state machine - *============================================================================*/ +/* Error pattern state machine */ static void error_pattern_start(void) { m_error_blink_cnt = 0; @@ -113,12 +97,12 @@ static void error_pattern_tick(void) { switch (m_error_phase) { - case 0: /* ON period done -> OFF */ + case 0: // ON period done -> OFF led_all_off(); m_error_phase = 1; timer_start_ms(ERROR_BLINK_OFF_MS); break; - case 1: /* OFF period done */ + case 1: // OFF period done m_error_blink_cnt++; if (m_error_blink_cnt < ERROR_BLINK_COUNT) { m_error_phase = 0; @@ -129,7 +113,7 @@ static void error_pattern_tick(void) timer_start_ms(ERROR_PAUSE_MS); } break; - case 2: /* Pause done -> restart */ + case 2: // Pause done -> restart m_error_blink_cnt = 0; m_error_phase = 0; led_color_on(COLOR_ORANGE); @@ -140,9 +124,7 @@ static void error_pattern_tick(void) } } -/*============================================================================== - * Timer callback - *============================================================================*/ +/* Timer callback */ static void led_timer_handler(struct k_timer *timer) { ARG_UNUSED(timer); @@ -157,7 +139,7 @@ static void led_timer_handler(struct k_timer *timer) if (m_phase_on) { - /* ON -> OFF transition */ + // ON -> OFF transition led_all_off(); m_phase_on = false; @@ -171,12 +153,12 @@ static void led_timer_handler(struct k_timer *timer) led_all_off(); m_current_state = LED_STATE_OFF; } - /* POWER_ON: stays lit, no timer */ + // POWER_ON: stays lit, no timer } } else { - /* OFF -> ON transition */ + // OFF -> ON transition if (p->repeat) { led_color_on(p->color); @@ -186,10 +168,7 @@ static void led_timer_handler(struct k_timer *timer) } } -/*============================================================================== - * Public functions - *============================================================================*/ - +/* Public functions */ void led_init(void) { gpio_pin_configure_dt(&led_ble, GPIO_OUTPUT_INACTIVE); @@ -242,5 +221,5 @@ void led_ble_solid(void) k_timer_stop(&m_led_timer); led_all_off(); led_ble_on(); - m_current_state = LED_STATE_OFF; /* no pattern running */ + m_current_state = LED_STATE_OFF; // no pattern running } diff --git a/src/drivers/led/led_control.h b/src/drivers/led/led_control.h index 5cf5561..c8d4b7a 100644 --- a/src/drivers/led/led_control.h +++ b/src/drivers/led/led_control.h @@ -16,13 +16,13 @@ typedef enum { LED_STATE_OFF = 0, - LED_STATE_POWER_ON, /* 1: Green ON 2s -> stay on */ - LED_STATE_POWER_OFF, /* 2: Green ON 2s -> OFF */ - LED_STATE_ADVERTISING, /* 3: Green blink 500ms/500ms */ - LED_STATE_DETACH_WARNING, /* 4: Green 1s on / 3s off */ - LED_STATE_ALIGN_SEARCHING, /* 5: Orange blink 1s/1s */ - LED_STATE_ALIGN_COMPLETE, /* 6: Green 3s on / 1s off */ - LED_STATE_ERROR, /* 7: Orange 3Hz x3 / 1s off */ + LED_STATE_POWER_ON, // 1: Green ON 2s -> stay on + LED_STATE_POWER_OFF, // 2: Green ON 2s -> OFF + LED_STATE_ADVERTISING, // 3: Green blink 500ms/500ms + LED_STATE_DETACH_WARNING, // 4: Green 1s on / 3s off + LED_STATE_ALIGN_SEARCHING, // 5: Orange blink 1s/1s + LED_STATE_ALIGN_COMPLETE, // 6: Green 3s on / 1s off + LED_STATE_ERROR, // 7: Orange 3Hz x3 / 1s off LED_STATE_COUNT } led_state_t; diff --git a/src/drivers/piezo/piezo.c b/src/drivers/piezo/piezo.c index 6746f0f..c5d3900 100644 --- a/src/drivers/piezo/piezo.c +++ b/src/drivers/piezo/piezo.c @@ -123,13 +123,13 @@ static void piezo_drive_idle(void) int piezo_init(void) { - /* 중복 초기화 방지 */ + // 중복 초기화 방지 if (piezo_initialized) { return 0; } - /* 모든 제어 핀 목록 */ + // 모든 제어 핀 목록 const struct gpio_dt_spec *pins[] = { &piezo_pwr, &piezo_pe, &piezo_p_out, &piezo_n_out, @@ -138,14 +138,14 @@ int piezo_init(void) for (size_t i = 0; i < ARRAY_SIZE(pins); i++) { - /* GPIO 포트 준비 확인 */ + // GPIO 포트 준비 확인 if (!device_is_ready(pins[i]->port)) { DBG_PRINTF("[PIEZO] GPIO device not ready (%d)\r\n", (int)i); return -ENODEV; } - /* 초기 출력 Low */ + // 초기 출력 Low int err = gpio_pin_configure_dt(pins[i], GPIO_OUTPUT_INACTIVE); if (err) { @@ -154,7 +154,7 @@ int piezo_init(void) } } - /* 직접 제어용 핀/마스크 캐시 */ + // 직접 제어용 핀/마스크 캐시 piezo_pe_abs = gpio_abs_pin(&piezo_pe); piezo_p_out_abs = gpio_abs_pin(&piezo_p_out); piezo_n_out_abs = gpio_abs_pin(&piezo_n_out); @@ -172,32 +172,33 @@ int piezo_init(void) void piezo_power_on(void) { - /* 필요 시 자동 초기화 */ + // 필요 시 자동 초기화 if (!piezo_initialized && piezo_init() != 0) { return; } - /* 피에조 전원 인가 */ + // 피에조 전원 인가 gpio_pin_set_dt(&piezo_pwr, 1); } void piezo_power_off(void) { - /* 필요 시 자동 초기화 */ + // 필요 시 자동 초기화 if (!piezo_initialized && piezo_init() != 0) { return; } - /* 출력 정지 후 전원 차단 */ + // 출력 정지 후 전원 차단 piezo_drive_idle(); gpio_pin_set_dt(&piezo_pwr, 0); } +/* Piezo 채널 선택 */ int piezo_select_channel(uint8_t channel) { - /* 필요 시 자동 초기화 */ + // 필요 시 자동 초기화 if (!piezo_initialized) { int err = piezo_init(); @@ -207,35 +208,36 @@ int piezo_select_channel(uint8_t channel) } } - /* 채널 범위 검사 */ + // 채널 범위 검사 if (channel >= PIEZO_NUM_CHANNELS) { return -EINVAL; } - /* MUX 채널 선택 */ + // MUX 채널 선택 gpio_pin_set_dt(&mux_en_a, mux_map[channel].en_a); gpio_pin_set_dt(&mux_en_b, mux_map[channel].en_b); gpio_pin_set_dt(&mux_sel0, mux_map[channel].sel0); gpio_pin_set_dt(&mux_sel1, mux_map[channel].sel1); - /* MUX 안정화 대기 */ + // MUX 안정화 대기 k_busy_wait(PIEZO_MUX_SETTLE_US); return 0; } +/* Piezo Burst Switching : 현재는 2.1MHz 고정, 1.8~2.2MHz 추가 예정 */ void piezo_burst_sw(uint8_t cycles) { - /* 초기화/횟수 검사 */ + // 초기화/횟수 검사 if (!piezo_initialized || cycles == 0U) { return; } - /* 타이밍 흔들림 방지 */ + // 타이밍 흔들림 방지 unsigned int key = irq_lock(); - /* P1 비제어 핀 상태 보존 */ + // P1 비제어 핀 상태 보존 uint32_t saved_p1_out = NRF_P1->OUT; uint32_t p1_ctrl_mask = piezo_p_out_mask | piezo_n_out_mask | piezo_dmp_mask; uint32_t p1_all_low = saved_p1_out & ~p1_ctrl_mask; @@ -243,28 +245,28 @@ void piezo_burst_sw(uint8_t cycles) uint32_t p1_p_low_n_high = p1_all_low | piezo_n_out_mask; uint32_t p1_dmp_high = p1_all_low | piezo_dmp_mask; - /* 구동 Enable 전 초기 Low 정렬 */ + // 구동 Enable 전 초기 Low 정렬 NRF_P0->OUTCLR = piezo_pe_mask; NRF_P1->OUT = p1_all_low; - NRF_P0->OUTSET = piezo_pe_mask; /* PE High */ + NRF_P0->OUTSET = piezo_pe_mask; // PE High for (uint8_t i = 0; i < cycles; i++) { - /* P High / N Low */ + // P High / N Low NRF_P1->OUT = p1_p_high_n_low; nop_delay_14(); - /* P Low / N High */ + // P Low / N High NRF_P1->OUT = p1_p_low_n_high; nop_delay_9(); } - /* 잔류 전하 방전 */ + // 잔류 전하 방전 NRF_P1->OUT = p1_dmp_high; - nop_delay_32(); /* DMP High 폭 */ + nop_delay_32(); // DMP High 폭 - /* 출력 Low 복귀 */ + // 출력 Low 복귀 NRF_P1->OUT = p1_all_low; - NRF_P0->OUTCLR = piezo_pe_mask; /* PE Low */ + NRF_P0->OUTCLR = piezo_pe_mask; // PE Low irq_unlock(key); } diff --git a/src/main.c b/src/main.c index e353bb0..e95cd51 100644 --- a/src/main.c +++ b/src/main.c @@ -1,27 +1,8 @@ /******************************************************************************* * @file main.c * @brief VesiScan-Basic - * - * 부팅 흐름: - * 1. 리셋/DFU 복구 상태 확인, 전원 유지 핀 초기 설정 - * 2. GPIO, 타이머, workqueue, LED, 배터리 ADC, IMU, piezo 설정 초기화 - * 3. BLE 스택 + NUS 초기화 - * 4. 전원 버튼 상태머신 시작 - * - * 일반 버튼 부팅: - * - 버튼을 BOOT_THRESHOLD 동안 유지하면 P0.08 전원 래치 ON - * - 래치 후 LED advertising 패턴, 배터리 모니터, BLE advertising 시작 - * - 기준 시간 = POWER_ON_DELAY * BOOT_THRESHOLD - * - * 리셋/DFU 복구: - * - 버튼 없이 전원 래치와 advertising을 자동 복구할 수 있음 - * - * 전원 버튼 상태머신: - * [OFF→ON] 2초 이상 버튼 유지 → 전원 래치 ON (초기화 시간 고려 체감상 2초, 실제 코드상 1초) - * 임계 시간 전에 놓으면 전원 래치 해제 - * [ON→OFF] 버튼 유지 → advertising 중지 + POWER_OFF_DELAY 후 전원 차단 - * ******************************************************************************/ + #include #include #include @@ -37,6 +18,7 @@ #include "battery_adc.h" #include "parser.h" #include "imu_i2c.h" +#include "piezo_measure.h" LOG_MODULE_REGISTER(vesiscan, LOG_LEVEL_INF); @@ -55,51 +37,45 @@ LOG_MODULE_REGISTER(vesiscan, LOG_LEVEL_INF); */ #define BLE_CMD_WORKQ_PRIORITY 10 -/*============================================================================== - * Devicetree GPIO Settings - *============================================================================*/ -#define POWER_HOLD_NODE DT_NODELABEL(pwr_hold) /* 전원 래치 */ -#define POWER_BTN_NODE DT_NODELABEL(button_check) /* 전원 버튼 */ +/* Devicetree GPIO Settings */ +#define POWER_HOLD_NODE DT_NODELABEL(pwr_hold) // 전원 래치 +#define POWER_BTN_NODE DT_NODELABEL(button_check) // 전원 버튼 /* 전원 제어 GPIO */ static const struct gpio_dt_spec power_hold = GPIO_DT_SPEC_GET(POWER_HOLD_NODE, gpios); static const struct gpio_dt_spec power_btn = GPIO_DT_SPEC_GET(POWER_BTN_NODE, gpios); -/*============================================================================== - * Timer - *============================================================================*/ -static struct k_timer m_power_on_delay_timer; /* 전원 버튼 폴링용 (5ms 싱글샷) */ -static struct k_timer m_power_off_delay_timer; /* 전원 OFF 지연용 (3초 후 전원 차단) */ +/* Timer */ +static struct k_timer m_power_on_delay_timer; // 전원 버튼 폴링용 (5ms 싱글샷) +static struct k_timer m_power_off_delay_timer; // 전원 OFF 지연용 (3초 후 전원 차단) -/*============================================================================== - * 전역 변수 - *============================================================================*/ -volatile bool ble_connection_st = false; /* BLE 연결 상태 */ -volatile bool processing = false; /* 센서 데이터 처리 중 플래그 */ -bool bond_data_delete = true; /* 본딩 데이터 삭제 요청 */ -uint32_t m_life_cycle = 0; /* 디바이스 수명 카운터 */ -uint8_t m_reset_status = 1; /* 리셋 상태 코드 */ -char SERIAL_NO[SERIAL_NO_BUF_SIZE]; /* 시리얼 번호 */ -char HW_NO[HW_NO_BUF_SIZE]; /* 하드웨어 번호 */ -char m_static_passkey[PASSKEY_BUF_SIZE]; /* BLE 정적 패스키 */ -uint8_t m_passkey_changed; /* 패스키 변경은 출고 후 1회만 허용 */ +/* 전역 변수 */ +volatile bool ble_connection_st = false; // BLE 연결 상태 +volatile bool processing = false; // 센서 데이터 처리 중 플래그 +bool bond_data_delete = true; // 본딩 데이터 삭제 요청 +uint32_t m_life_cycle = 0; // 디바이스 수명 카운터 +uint8_t m_reset_status = 1; // 리셋 상태 코드 +char SERIAL_NO[SERIAL_NO_BUF_SIZE]; // 시리얼 번호 +char HW_NO[HW_NO_BUF_SIZE]; // 하드웨어 번호 +char m_static_passkey[PASSKEY_BUF_SIZE]; // BLE 정적 패스키 +uint8_t m_passkey_changed; // 패스키 변경은 출고 후 1회만 허용 -static uint16_t cnt_s; /* 전원 버튼 폴링 카운터 (5ms 단위) */ -static bool device_on = false; /* 디바이스 전원 상태 (래치 완료 여부) */ -static bool boot_btn_released = false; /* 부팅 후 버튼 놓았는지 여부 */ -static bool power_btn_suspended; /* 측정 중 전원 버튼 상태머신 일시 정지 */ +static uint16_t cnt_s; // 전원 버튼 폴링 카운터 (5ms 단위) +static bool device_on = false; // 디바이스 전원 상태 (래치 완료 여부) +static bool boot_btn_released = false; // 부팅 후 버튼 놓았는지 여부 +static bool power_btn_suspended; // 측정 중 전원 버튼 상태머신 일시 정지 /* * BLE advertising 제어용 워크 아이템 * - main_s()에서 k_work_submit()으로 예약하여 사용 */ -static bool resume_without_power_button; /* 리셋 후 버튼 없이 복귀 */ -static bool dfu_confirm_pending_boot; /* DFU test 이미지 확인 전 자동 복귀 */ -static bool dfu_reset_resume_request; /* DFU 완료 리셋 후 자동 복귀 */ -static uint32_t boot_reset_reason; /* RESETREAS 원본 값 */ -static struct k_work adv_start_work; /* advertising 시작 work */ -static struct k_work adv_stop_work; /* advertising 중지 work */ -static bool dfu_led_active; /* DFU 업로드 중 초록 LED ON */ +static bool resume_without_power_button; // 리셋 후 버튼 없이 복귀 +static bool dfu_confirm_pending_boot; // DFU test 이미지 확인 전 자동 복귀 +static bool dfu_reset_resume_request; // DFU 완료 리셋 후 자동 복귀 +static uint32_t boot_reset_reason; // RESETREAS 원본 값 +static struct k_work adv_start_work; // advertising 시작 work +static struct k_work adv_stop_work; // advertising 중지 work +static bool dfu_led_active; // DFU 업로드 중 초록 LED ON static const char *power_off_pending_reason = "none"; static enum mgmt_cb_return dfu_status_cb(uint32_t event, @@ -137,21 +113,21 @@ static struct mgmt_callback smp_cmd_status_callback = { */ static struct k_work_q ble_cmd_work_q; K_THREAD_STACK_DEFINE(ble_cmd_workq_stack, BLE_CMD_WORKQ_STACK_SZ); -static struct k_work ble_cmd_work; /* BLE 명령 처리 work */ -static struct k_spinlock ble_cmd_lock; /* RX 공유 버퍼 보호 */ -static uint8_t ble_cmd_buf[BLE_CMD_MAX_LEN];/* RX 명령 복사 버퍼 */ -static uint16_t ble_cmd_len; /* RX 명령 길이 */ -static bool ble_cmd_pending; /* 처리 대기 명령 있음 */ +static struct k_work ble_cmd_work; // BLE 명령 처리 work +static struct k_spinlock ble_cmd_lock; // RX 공유 버퍼 보호 +static uint8_t ble_cmd_buf[BLE_CMD_MAX_LEN];// RX 명령 복사 버퍼 +static uint16_t ble_cmd_len; // RX 명령 길이 +static bool ble_cmd_pending; // 처리 대기 명령 있음 static void ble_cmd_work_handler(struct k_work *work) { ARG_UNUSED(work); - /* work queue 로컬 복사본 */ + // work queue 로컬 복사본 uint8_t local_buf[BLE_CMD_MAX_LEN]; uint16_t local_len = 0U; - /* 공유 RX 명령 슬롯 보호: 현재 구조는 한 번에 한 명령만 보관 */ + // 공유 RX 명령 슬롯 보호: 현재 구조는 한 번에 한 명령만 보관 k_spinlock_key_t key = k_spin_lock(&ble_cmd_lock); if (ble_cmd_pending) { @@ -166,11 +142,11 @@ static void ble_cmd_work_handler(struct k_work *work) return; } - /* 일반 스레드 문맥 명령 처리 */ + // 일반 스레드 문맥 명령 처리 //DBG_CORE("[BLE RX] worker dispatch len=%u\r\n", local_len); ble_cmd_dispatch(local_buf, local_len); - /* 명령 처리 완료 표시 */ + // 명령 처리 완료 표시 key = k_spin_lock(&ble_cmd_lock); ble_cmd_pending = false; ble_cmd_len = 0U; @@ -191,9 +167,7 @@ static void adv_stop_work_handler(struct k_work *work) ble_advertising_stop(); } -/*============================================================================== - * BLE RX 콜백 - *============================================================================*/ +/* BLE RX 콜백 */ static void ble_rx_handler(const uint8_t *data, uint16_t len) { if (len > BLE_CMD_MAX_LEN) @@ -202,7 +176,7 @@ static void ble_rx_handler(const uint8_t *data, uint16_t len) return; } - /* 공유 RX 명령 슬롯 보호: 현재 구조는 한 번에 한 명령만 보관 */ + // 공유 RX 명령 슬롯 보호: 현재 구조는 한 번에 한 명령만 보관 k_spinlock_key_t key = k_spin_lock(&ble_cmd_lock); if (ble_cmd_pending) { @@ -211,13 +185,13 @@ static void ble_rx_handler(const uint8_t *data, uint16_t len) return; } - /* 명령 버퍼 복사 */ + // 명령 버퍼 복사 memcpy(ble_cmd_buf, data, len); ble_cmd_len = len; ble_cmd_pending = true; k_spin_unlock(&ble_cmd_lock, key); - /* 명령 처리 work 예약 */ + // 명령 처리 work 예약 int err = k_work_submit_to_queue(&ble_cmd_work_q, &ble_cmd_work); if (err < 0) { @@ -230,15 +204,13 @@ static void ble_rx_handler(const uint8_t *data, uint16_t len) } } -/*============================================================================== - * 전원 유지 (POWER_HOLD) 제어 - *============================================================================*/ +/* 전원 유지 (POWER_HOLD) 제어 */ static void boot_context_detect(void) { - /* 부팅 원인 확인 */ + // 부팅 원인 확인 boot_reset_reason = nrf_power_resetreas_get(NRF_POWER); - /* DFU 완료 직후 리셋인지 확인 */ + // DFU 완료 직후 리셋인지 확인 #if NRF_POWER_HAS_GPREGRET dfu_reset_resume_request = (nrf_power_gpregret_get(NRF_POWER, DFU_RESUME_GPREGRET_REG) == DFU_RESUME_MAGIC); if (dfu_reset_resume_request) @@ -249,7 +221,7 @@ static void boot_context_detect(void) dfu_reset_resume_request = false; #endif - /* 소프트 리셋 계열 자동 복귀 */ + // 소프트 리셋 계열 자동 복귀 resume_without_power_button = (boot_reset_reason & (NRF_POWER_RESETREAS_SREQ_MASK | NRF_POWER_RESETREAS_DOG_MASK | @@ -258,7 +230,7 @@ static void boot_context_detect(void) if (boot_reset_reason != 0U) { - /* RESETREAS 플래그 정리 */ + // RESETREAS 플래그 정리 nrf_power_resetreas_clear(NRF_POWER, boot_reset_reason); } } @@ -266,13 +238,13 @@ static void boot_context_detect(void) static void power_hold_init(void) { #if defined(CONFIG_BOOTLOADER_MCUBOOT) - /* DFU 이미지 확인 전 전원 유지 */ + // DFU 이미지 확인 전 전원 유지 dfu_confirm_pending_boot = !boot_is_img_confirmed(); #else dfu_confirm_pending_boot = false; #endif - /* 리셋 복귀/DFU 확인 대기 시 즉시 래치 */ + // 리셋 복귀/DFU 확인 대기 시 즉시 래치 gpio_pin_configure_dt(&power_hold, (resume_without_power_button || dfu_confirm_pending_boot || @@ -295,30 +267,26 @@ static void power_control_handler(on_off_cont_t device_power_st, const char *rea ble_dfu_advertising_is_enabled() ? 1U : 0U, dfu_confirm_pending_boot ? 1U : 0U, dfu_reset_resume_request ? 1U : 0U); - gpio_pin_set_dt(&power_hold, 0); /* P0.08 LOW → 전원 래치 해제 → 전원 차단 */ + gpio_pin_set_dt(&power_hold, 0); // P0.08 LOW → 전원 래치 해제 → 전원 차단 } else { - gpio_pin_set_dt(&power_hold, 1); /* P0.08 HIGH → 전원 유지 */ + gpio_pin_set_dt(&power_hold, 1); // P0.08 HIGH → 전원 유지 } } -/*============================================================================== - * GPIO 초기화 - *============================================================================*/ +/* GPIO 초기화 */ static void gpio_init(void) { - gpio_pin_configure_dt(&power_btn, GPIO_INPUT); /* 전원 버튼(P1.08) 입력 설정 */ + gpio_pin_configure_dt(&power_btn, GPIO_INPUT); // 전원 버튼(P1.08) 입력 설정 DBG_PRINTF("[INIT] HW - GPIO OK (POWER BTN=%d)\r\n", gpio_pin_get_dt(&power_btn)); } -/*============================================================================== - * 기본 설정값 로드 - *============================================================================*/ +/* 기본 설정값 로드 */ static void load_default_config(void) { - /* 기본 문자열 길이 제한 */ + // 기본 문자열 길이 제한 size_t serial_len = strlen(SERIAL_NUMBER); size_t hw_len = strlen(HARDWARE_VERSION); size_t passkey_len = strlen(DEFAULT_PASSKEY); @@ -339,15 +307,15 @@ static void load_default_config(void) memset(SERIAL_NO, 0, sizeof(SERIAL_NO)); memcpy(SERIAL_NO, SERIAL_NUMBER, serial_len); - /* 기본 HW Version 복사 */ + // 기본 HW Version 복사 memset(HW_NO, 0, sizeof(HW_NO)); memcpy(HW_NO, HARDWARE_VERSION, hw_len); - /* 기본 BLE 패스키 복사 */ + // 기본 BLE 패스키 복사 memset(m_static_passkey, 0, sizeof(m_static_passkey)); memcpy(m_static_passkey, DEFAULT_PASSKEY, passkey_len); - /* NVS 로드 전 기본값은 변경 가능 상태 */ + // NVS 로드 전 기본값은 변경 가능 상태 m_passkey_changed = 0U; /* 기본 상태값 */ @@ -357,10 +325,7 @@ static void load_default_config(void) DBG_CORE("[INIT] Default (S/N=%s)\r\n", SERIAL_NO); } -/*============================================================================== - * 전원 OFF 타임아웃 콜백 - * LED 표시 후 3초(POWER_OFF_DELAY) 경과 시 호출되어 물리적 전원 차단 - *============================================================================*/ +/* 전원 OFF 타임아웃 콜백 */ static void t_power_off_timeout_handler(struct k_timer *timer) { ARG_UNUSED(timer); @@ -369,10 +334,7 @@ static void t_power_off_timeout_handler(struct k_timer *timer) power_control_handler(OFF, "off-timeout"); } -/*============================================================================== - * 슬립 모드 / 전원 OFF - * LED로 사용자에게 알린 후 3초 뒤 전원 차단 - *============================================================================*/ +/* 슬립 모드 / 전원 OFF: LED 표시 후 3초 뒤 전원 차단 */ static void power_off_schedule(const char *reason, led_state_t led_state) { power_off_pending_reason = reason ? reason : "unknown"; @@ -412,25 +374,13 @@ void device_power_keep_on(void) void power_button_suspend(bool suspend) { - /* 측정 시간이 긴 커맨드 처리 중에만 버튼 폴링 판단 잠시 멈춤 */ + // 측정 시간이 긴 커맨드 처리 중에만 버튼 폴링 판단 잠시 멈춤 power_btn_suspended = suspend; cnt_s = 0; } -/*============================================================================== - * 전원 버튼 상태머신 (5ms 폴링) - * - * [부팅 시퀀스] (device_on == false) - * 1. 버튼 누름 → 전원 공급 → MCU 부팅 → 5ms마다 폴링 시작 - * 2. cnt_s < 200 (2초 미만)에서 놓으면 → 전원 OFF (래치 안 됨) - * 3. cnt_s == 200 (2초) 도달 → P0.08 래치 ON + 녹색 LED 깜빡임 - * 4. 버튼 놓으면 → 깜빡임 유지, 폴링 계속 - * - * [전원 OFF 시퀀스] (device_on == true) - * 1. 버튼 2초 이상 누름 → sleep_mode_enter() → 전원 차단 - * 2. 버튼 2초 미만 놓으면 → 카운터 리셋, 무시 - *============================================================================*/ -#define BOOT_THRESHOLD 200 /* 5ms x 400 = 2초 -> 초기화 시간 고려 체감상 2초에 맞춤 */ +/* 전원 버튼 상태머신 (5ms 폴링) */ +#define BOOT_THRESHOLD 200 // 5ms x 400 = 2초 -> 초기화 시간 고려 체감상 2초에 맞춤 static void main_s(struct k_timer *timer) { @@ -444,23 +394,23 @@ static void main_s(struct k_timer *timer) return; } - /* 부팅 시퀀스 (OFF → ON) */ + // 부팅 시퀀스 (OFF → ON) if (!device_on) { - if (!button_pressed) /* 버튼 놓음 → 2초 미만이면 전원 OFF */ + if (!button_pressed) // 버튼 놓음 → 2초 미만이면 전원 OFF { //DBG_PRINTF("[BTN] Short press (%d) -> OFF\r\n", cnt_s); power_control_handler(OFF, "boot-button-released-before-latch"); cnt_s = 0; } - else /* 버튼 계속 누르고 있음 */ + else // 버튼 계속 누르고 있음 { cnt_s++; - if (cnt_s == BOOT_THRESHOLD) /* 2초 도달: 래치 + 부팅 완료 */ + if (cnt_s == BOOT_THRESHOLD) // 2초 도달: 래치 + 부팅 완료 { device_on = true; - cnt_s = 0; /* 카운터 리셋: 안 하면 다음 틱에서 ON→OFF 분기가 cnt_s >= 200 조건을 즉시 만족하여 전원 OFF됨 */ + cnt_s = 0; // 카운터 리셋: 안 하면 다음 틱에서 ON→OFF 분기가 cnt_s >= 200 조건을 즉시 만족하여 전원 OFF됨 power_control_handler(ON, "button-2s-latch"); led_set_state(LED_STATE_ADVERTISING); k_work_submit(&adv_start_work); @@ -472,18 +422,18 @@ static void main_s(struct k_timer *timer) /* 전원 OFF 시퀀스 (ON → OFF) */ else { - if (!boot_btn_released) /* 부팅 시 눌렀던 버튼을 아직 안 놓음 → 대기 */ + if (!boot_btn_released) // 부팅 시 눌렀던 버튼을 아직 안 놓음 → 대기 { if (!button_pressed) { boot_btn_released = true; } } - else if (button_pressed) /* 버튼 새로 누르고 있음 */ + else if (button_pressed) // 버튼 새로 누르고 있음 { cnt_s++; - if (cnt_s >= BOOT_THRESHOLD) /* 2초 이상 → 전원 OFF */ + if (cnt_s >= BOOT_THRESHOLD) // 2초 이상 → 전원 OFF { battery_timer_stop(); k_work_submit(&adv_stop_work); @@ -494,7 +444,7 @@ static void main_s(struct k_timer *timer) return; } } - else /* 버튼 놓음 → 카운터 리셋 */ + else // 버튼 놓음 → 카운터 리셋 { if (cnt_s > 0) { @@ -507,30 +457,28 @@ static void main_s(struct k_timer *timer) k_timer_start(&m_power_on_delay_timer, K_MSEC(POWER_ON_DELAY), K_NO_WAIT); } -/*============================================================================== - * 타이머 초기화 / 시작 - *============================================================================*/ +/* 타이머 초기화 / 시작 */ static void timers_init(void) { - /* 전원 상태머신 타이머 */ + // 전원 상태머신 타이머 k_timer_init(&m_power_on_delay_timer, main_s, NULL); k_timer_init(&m_power_off_delay_timer, t_power_off_timeout_handler, NULL); - /* BLE advertising work */ + // BLE advertising work k_work_init(&adv_start_work, adv_start_work_handler); k_work_init(&adv_stop_work, adv_stop_work_handler); - /* BLE 명령 처리 work queue */ + // BLE 명령 처리 work queue k_work_init(&ble_cmd_work, ble_cmd_work_handler); k_work_queue_start(&ble_cmd_work_q, ble_cmd_workq_stack, K_THREAD_STACK_SIZEOF(ble_cmd_workq_stack), BLE_CMD_WORKQ_PRIORITY, NULL); - /* 전원 제어 타이머 */ + // 전원 제어 타이머 power_timer_init(); - /* DFU 업로드 상태를 LED에 반영 */ + // DFU 업로드 상태를 LED에 반영 mgmt_callback_register(&dfu_status_callback); - /* DFU 명령 실패 시 LED 상태 복구 */ + // DFU 명령 실패 시 LED 상태 복구 mgmt_callback_register(&smp_cmd_status_callback); } @@ -557,26 +505,26 @@ static enum mgmt_cb_return dfu_status_cb(uint32_t event, switch (event) { - case MGMT_EVT_OP_IMG_MGMT_DFU_STARTED: - case MGMT_EVT_OP_IMG_MGMT_DFU_CHUNK: - /* DFU 업로드 중에는 초록 LED ON */ - dfu_led_active = true; - led_ble_solid(); - break; + case MGMT_EVT_OP_IMG_MGMT_DFU_STARTED: + case MGMT_EVT_OP_IMG_MGMT_DFU_CHUNK: + // DFU 업로드 중에는 초록 LED ON + dfu_led_active = true; + led_ble_solid(); + break; - case MGMT_EVT_OP_IMG_MGMT_DFU_PENDING: - /* 업로드 완료 후 리셋 전까지 초록 LED ON */ - dfu_led_active = true; - led_ble_solid(); + case MGMT_EVT_OP_IMG_MGMT_DFU_PENDING: + // 업로드 완료 후 리셋 전까지 초록 LED ON + dfu_led_active = true; + led_ble_solid(); - /* DFU 리셋 후 버튼 없이 전원 ON */ + // DFU 리셋 후 버튼 없이 전원 ON #if NRF_POWER_HAS_GPREGRET nrf_power_gpregret_set(NRF_POWER, DFU_RESUME_GPREGRET_REG, DFU_RESUME_MAGIC); #endif break; case MGMT_EVT_OP_IMG_MGMT_DFU_STOPPED: - /* 실패나 중단 시 일반 광고 LED로 복귀 */ + // 실패나 중단 시 일반 광고 LED로 복귀 if (dfu_led_active) { dfu_led_active = false; @@ -612,7 +560,7 @@ static enum mgmt_cb_return smp_cmd_status_cb(uint32_t event, const struct mgmt_evt_op_cmd_arg *cmd = data; - /* 이미지 업로드/test 명령 실패 시 DFU LED 고정 해제 */ + // 이미지 업로드/test 명령 실패 시 DFU LED 고정 해제 if (cmd->group == MGMT_GROUP_ID_IMAGE && (cmd->id == IMG_MGMT_ID_UPLOAD || cmd->id == IMG_MGMT_ID_STATE) && cmd->err != MGMT_ERR_EOK) @@ -622,7 +570,7 @@ static enum mgmt_cb_return smp_cmd_status_cb(uint32_t event, dfu_led_active = false; led_set_state(LED_STATE_ADVERTISING); - /* 실패 후 리셋 복구 표식이 남지 않게 정리 */ + // 실패 후 리셋 복구 표식이 남지 않게 정리 #if NRF_POWER_HAS_GPREGRET nrf_power_gpregret_set(NRF_POWER, DFU_RESUME_GPREGRET_REG, 0U); #endif @@ -633,21 +581,19 @@ static enum mgmt_cb_return smp_cmd_status_cb(uint32_t event, static void resume_device_after_soft_reset(void) { - /* 일반 버튼 부팅이면 skip */ - if (!resume_without_power_button && - !dfu_confirm_pending_boot && - !dfu_reset_resume_request) + // 일반 버튼 부팅이면 skip + if (!resume_without_power_button && !dfu_confirm_pending_boot && !dfu_reset_resume_request) { return; } - /* 리셋 복귀나 DFU test 부팅은 버튼 없이도 ON 상태로 복구 */ + // 리셋 복귀나 DFU test 부팅은 버튼 없이도 ON 상태로 복구 device_on = true; cnt_s = 0; boot_btn_released = (gpio_pin_get_dt(&power_btn) != 1); m_reset_status = 1; - /* 정상 ON 상태 서비스 재시작 */ + // 정상 ON 상태 서비스 재시작 power_control_handler(ON, "resume-after-reset"); led_set_state(LED_STATE_ADVERTISING); battery_timer_start(); @@ -659,7 +605,7 @@ static void resume_device_after_soft_reset(void) static void confirm_running_image(void) { #if defined(CONFIG_BOOTLOADER_MCUBOOT) - /* 기본 초기화와 BLE 준비가 끝난 뒤 MCUboot revert 방지 */ + // 기본 초기화와 BLE 준비가 끝난 뒤 MCUboot revert 방지 if (!boot_is_img_confirmed()) { int err = boot_write_img_confirmed(); @@ -681,7 +627,7 @@ static void confirm_running_image(void) static const char *swap_type_to_str(int swap_type) { - /* MCUboot swap 상태 문자열 */ + // MCUboot swap 상태 문자열 switch (swap_type) { case BOOT_SWAP_TYPE_NONE: @@ -702,7 +648,7 @@ static const char *swap_type_to_str(int swap_type) static void log_mcuboot_state(void) { #if defined(CONFIG_BOOTLOADER_MCUBOOT) - /* 현재 부팅 슬롯/교체 상태 */ + // 현재 부팅 슬롯/교체 상태 struct mcuboot_img_header header; uint8_t active_slot = boot_fetch_active_slot(); int swap_type = mcuboot_swap_type(); @@ -733,30 +679,28 @@ static void log_mcuboot_state(void) #endif } -/*============================================================================== - * 메인 함수 - *============================================================================*/ +/* 메인 함수 */ int main(void) { - /* 리셋/복귀 컨텍스트 */ + // 리셋/복귀 컨텍스트 boot_context_detect(); - /* 하드웨어 기본 초기화 */ + // 하드웨어 기본 초기화 power_hold_init(); cnt_s = 0; - /* 버튼 부팅 기본 상태 */ + // 버튼 부팅 기본 상태 device_on = false; boot_btn_released = false; confirm_running_image(); DBG_CORE("\r\n========================================\r\n"); DBG_CORE(" TEST BUILD %s (Zephyr)\r\n", FIRMWARE_VERSION); - DBG_CORE(" BUILD TAG: TEST-ADV-UNIT-002\r\n"); + DBG_CORE(" BUILD TAG: TEST-003 - tx power dynamic control\r\n"); DBG_CORE("========================================\r\n"); DBG_CORE("[INIT] HW Init\r\n"); - /* 기본 하드웨어/센서 초기화 */ + // 기본 하드웨어/센서 초기화 gpio_init(); timers_init(); log_mcuboot_state(); @@ -768,14 +712,14 @@ int main(void) piezo_config_init(); DBG_CORE("[INIT] HW - gpio/timer/config/led/batt/imu/temp/piezo-cfg OK\r\n"); - /* BLE 스택 + NUS */ + // BLE 스택 + NUS DBG_CORE("[INIT] BLE Init\r\n"); if (ble_service_init(ble_rx_handler) == 0) { DBG_CORE("[INIT] BLE - ble/nus OK\r\n"); - /* 소프트 리셋 후 서비스 복구 */ + // 소프트 리셋 후 서비스 복구 resume_device_after_soft_reset(); - /* BLE 준비까지 확인한 뒤 DFU test 이미지를 확정 */ + // BLE 준비까지 확인한 뒤 DFU test 이미지를 확정 confirm_running_image(); } else @@ -787,10 +731,10 @@ int main(void) DBG_CORE(" READY [%s]\r\n", SERIAL_NO); DBG_CORE("========================================\r\n"); - /* 전원 버튼 상태머신 시작 (부팅 시 버튼이 눌려있는 상태) */ + // 전원 버튼 상태머신 시작 (부팅 시 버튼이 눌려있는 상태) timers_start(); - /* 메인 루프 - idle */ + // 메인 루프 - idle for (;;) { k_msleep(100); diff --git a/src/measurement/piezo_measure.c b/src/measurement/piezo_measure.c index fd2f10e..1108f0b 100644 --- a/src/measurement/piezo_measure.c +++ b/src/measurement/piezo_measure.c @@ -23,13 +23,10 @@ #define PIEZO_CFG_SAMPLES_MAX 117 #define PIEZO_CFG_DELAY_MAX_US 50 -/* - * piezo_select_channel() 안에서 MUX settle 1.3 ms를 이미 기다린 뒤, - * measurement layer에서 analog path/ADC 입력 안정화를 위해 0.5 ms를 한 번 더 기다린다. - */ +/* piezo_select_channel() 안에서 MUX settle 1.3 ms를 이미 기다린 뒤 analog path/ADC 입력 안정화를 위해 0.5 ms를 한 번 더 기다림 */ #define PIEZO_POST_SELECT_SETTLE_US 500 -/* 평균/dummy burst 사이의 회복 시간 */ +/* burst 사이의 회복 시간 */ #define PIEZO_AVG_INTER_BURST_GAP_US 650 /* real capture 전에 같은 채널에서 버스트+ADC capture를 버리는 횟수(dummy) */ @@ -83,8 +80,7 @@ bool piezo_config_validate(const piezo_config_t *cfg) return false; } - if ((cfg->samples < PIEZO_CFG_SAMPLES_MIN) || - (cfg->samples > PIEZO_CFG_SAMPLES_MAX)) + if ((cfg->samples < PIEZO_CFG_SAMPLES_MIN) || (cfg->samples > PIEZO_CFG_SAMPLES_MAX)) { return false; } @@ -219,24 +215,20 @@ int piezo_measure_sweep(void) return ECHO_STATUS_CAPTURE; } - // Dummy burst + dummy capture - 결과는 버리고, piezo/mux/ADC 입력의 이전 상태가 real 평균에 섞이지 않도록 함 - for (uint8_t dummy = 0; dummy < PIEZO_DUMMY_CAPTURE_COUNT; dummy++) + if (ch == 0U) // 채널 0 시작 전에만 dummy burst 수행 TESTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT { - k_busy_wait(PIEZO_AVG_INTER_BURST_GAP_US); - piezo_burst_sw((uint8_t)cfg->cycles); - k_busy_wait(capture_delay_us); - - err = echo_adc_capture(echo_capture, cfg->samples); - if (err) + // Dummy burst - ADC caputre는 수행하지 않음 + for (uint8_t dummy = 0; dummy < PIEZO_DUMMY_CAPTURE_COUNT; dummy++) { - DBG_PRINTF("[ECHO] dummy capture fail ch=%d dummy=%d err=%d\r\n", ch, dummy, err); - return ECHO_STATUS_CAPTURE; + k_busy_wait(PIEZO_AVG_INTER_BURST_GAP_US); + piezo_burst_sw((uint8_t)cfg->cycles); + k_busy_wait(capture_delay_us); } + + // 마지막 dummy capture와 첫 real capture 사이에도 같은 회복 시간 + k_busy_wait(PIEZO_AVG_INTER_BURST_GAP_US + 3000); } - - // 마지막 dummy capture와 첫 real capture 사이에도 같은 회복 시간 - k_busy_wait(PIEZO_AVG_INTER_BURST_GAP_US); - + // 여기부터 실제 평균에 들어갈 capture memset(echo_accum, 0, sizeof(echo_accum)); @@ -285,11 +277,7 @@ int piezo_measure_sweep(void) * 단일 채널 burst + echo capture * mec? 디버그/테스트 명령에서 사용하며, 결과는 echo_capture에 평균 저장 */ -int piezo_measure_single_capture(uint8_t cycles, - uint16_t delay_us, - uint16_t num_samples, - uint16_t averaging, - uint8_t channel) +int piezo_measure_single_capture(uint8_t cycles, uint16_t delay_us, uint16_t num_samples, uint16_t averaging, uint8_t channel) { if (channel >= PIEZO_NUM_CHANNELS) { @@ -321,7 +309,7 @@ int piezo_measure_single_capture(uint8_t cycles, return ECHO_STATUS_CAPTURE; } - /* real capture 전에 같은 채널에서 dummy burst/capture를 수행하고 버림 */ + // real capture 전에 같은 채널에서 dummy burst/capture를 수행하고 버림 for (uint8_t dummy = 0; dummy < PIEZO_DUMMY_CAPTURE_COUNT; dummy++) { k_busy_wait(PIEZO_AVG_INTER_BURST_GAP_US); @@ -335,7 +323,7 @@ int piezo_measure_single_capture(uint8_t cycles, } } - /* 마지막 dummy capture와 첫 real capture 사이에도 같은 회복 시간 */ + // 마지막 dummy capture와 첫 real capture 사이에도 같은 회복 시간 k_busy_wait(PIEZO_AVG_INTER_BURST_GAP_US); memset(echo_accum, 0, sizeof(echo_accum)); @@ -377,9 +365,7 @@ int piezo_measure_single_capture(uint8_t cycles, * 단일 채널 ADC-only capture * piezo burst 없이 현재 echo ADC 입력만 평균해서 확인할 때 사용 */ -int piezo_measure_adc_only_capture(uint16_t num_samples, - uint16_t averaging, - uint8_t channel) +int piezo_measure_adc_only_capture(uint16_t num_samples, uint16_t averaging, uint8_t channel) { if (channel >= PIEZO_NUM_CHANNELS) { @@ -419,7 +405,7 @@ int piezo_measure_adc_only_capture(uint16_t num_samples, k_busy_wait(PIEZO_AVG_INTER_BURST_GAP_US); } - /* burst가 없으므로 ADC capture 자체의 간섭만 줄이기 위해 짧게 IRQ를 잠금 */ + // burst가 없으므로 ADC capture 자체의 간섭만 줄이기 위해 짧게 IRQ를 잠금 unsigned int key = irq_lock(); err = echo_adc_capture(echo_capture, num_samples); irq_unlock(key); diff --git a/src/power_control.c b/src/power_control.c index 834f94a..1303ce3 100644 --- a/src/power_control.c +++ b/src/power_control.c @@ -43,7 +43,7 @@ void power_loop(struct k_timer *timer) { power_timer_stop(); - /* Sensor init not needed - imu_read_direct() handles it per measurement */ + // Sensor init not needed - imu_read_direct() handles it per measurement p_order = 2; if (p_order < 2) @@ -59,7 +59,7 @@ void power_loop(struct k_timer *timer) int device_reactivated(void) { - /* sw_i2c_init_once() will be added in Stage 3 */ + // sw_i2c_init_once() will be added in Stage 3 k_msleep(10); lock_check = true; p_order = 0; diff --git a/tests/boot_route_sim.c b/tests/boot_route_sim.c new file mode 100644 index 0000000..db566c9 --- /dev/null +++ b/tests/boot_route_sim.c @@ -0,0 +1,161 @@ +#include +#include +#include + +#include "../src/main.h" + +#define BOOT_THRESHOLD 200 + +typedef struct { + bool device_on; + bool boot_btn_released; + bool power_btn_suspended; + uint16_t cnt_s; +} boot_sm_t; + +static void route_step(const char *name) +{ + printf(" -> %s\n", name); +} + +static void print_main_boot_route(bool resume_without_button, + bool dfu_confirm_pending, + bool dfu_reset_resume) +{ + printf("main() boot route\n"); + route_step("boot_context_detect()"); + printf(" resume_without_power_button=%u, dfu_confirm_pending=%u, dfu_reset_resume=%u\n", + resume_without_button ? 1U : 0U, + dfu_confirm_pending ? 1U : 0U, + dfu_reset_resume ? 1U : 0U); + + route_step("power_hold_init()"); + printf(" POWER_HOLD initial=%s\n", + (resume_without_button || dfu_confirm_pending || dfu_reset_resume) ? "ON" : "OFF"); + + route_step("confirm_running_image()"); + route_step("gpio_init()"); + route_step("timers_init()"); + route_step("log_mcuboot_state()"); + route_step("load_default_config()"); + route_step("led_init()"); + route_step("battery_adc_init()"); + route_step("battery_timer_init()"); + route_step("imu_init()"); + route_step("piezo_config_init()"); + route_step("ble_service_init(ble_rx_handler)"); + + if (resume_without_button || dfu_confirm_pending || dfu_reset_resume) { + route_step("resume_device_after_soft_reset()"); + route_step("POWER_HOLD ON, LED_ADVERTISING, battery_timer_start(), adv_start_work"); + } + + route_step("timers_start()"); + route_step("idle loop"); +} + +static void power_control(bool on, const char *reason) +{ + printf(" POWER_HOLD=%s reason=%s\n", on ? "ON" : "OFF", reason); +} + +static void boot_button_tick(boot_sm_t *sm, bool button_pressed) +{ + if (sm->power_btn_suspended) { + return; + } + + if (!sm->device_on) { + if (!button_pressed) { + power_control(false, "boot-button-released-before-latch"); + sm->cnt_s = 0; + } else { + sm->cnt_s++; + if (sm->cnt_s == BOOT_THRESHOLD) { + sm->device_on = true; + sm->cnt_s = 0; + power_control(true, "button-2s-latch"); + printf(" led_set_state(LED_STATE_ADVERTISING)\n"); + printf(" adv_start_work submitted\n"); + printf(" battery_timer_start()\n"); + } + } + return; + } + + if (!sm->boot_btn_released) { + if (!button_pressed) { + sm->boot_btn_released = true; + printf(" boot button released after latch\n"); + } + } else if (button_pressed) { + sm->cnt_s++; + if (sm->cnt_s >= BOOT_THRESHOLD) { + printf(" battery_timer_stop()\n"); + printf(" adv_stop_work submitted\n"); + sm->device_on = false; + sm->boot_btn_released = false; + sm->cnt_s = 0; + printf(" sleep_mode_enter_reason(button-long-press)\n"); + } + } else { + sm->cnt_s = 0; + } +} + +static void simulate_short_press(void) +{ + boot_sm_t sm = {0}; + + printf("\nscenario: short press during boot\n"); + for (int i = 0; i < 80; i++) { + boot_button_tick(&sm, true); + } + boot_button_tick(&sm, false); + printf(" result: device_on=%u cnt_s=%u\n", sm.device_on ? 1U : 0U, sm.cnt_s); +} + +static void simulate_long_press_boot(void) +{ + boot_sm_t sm = {0}; + + printf("\nscenario: hold button until boot latch\n"); + for (int i = 0; i < BOOT_THRESHOLD; i++) { + boot_button_tick(&sm, true); + } + boot_button_tick(&sm, false); + printf(" result: device_on=%u boot_btn_released=%u cnt_s=%u\n", + sm.device_on ? 1U : 0U, + sm.boot_btn_released ? 1U : 0U, + sm.cnt_s); +} + +static void simulate_power_off_after_boot(void) +{ + boot_sm_t sm = { + .device_on = true, + .boot_btn_released = true, + }; + + printf("\nscenario: long press while already ON\n"); + for (int i = 0; i < BOOT_THRESHOLD; i++) { + boot_button_tick(&sm, true); + } + printf(" result: device_on=%u cnt_s=%u\n", sm.device_on ? 1U : 0U, sm.cnt_s); +} + +int main(void) +{ + printf("PC GCC boot-route simulator for %s\n\n", FIRMWARE_VERSION); + + print_main_boot_route(false, false, false); + + printf("\nsoft-reset/DFU resume route\n"); + print_main_boot_route(true, false, false); + + simulate_short_press(); + simulate_long_press_boot(); + simulate_power_off_after_boot(); + + return 0; +}