diff --git a/README.md b/README.md index b17e7d9..fed5678 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # VesiScan BASIC - Zephyr (nRF Connect SDK) -nRF52840 기반 VesiScan BASIC 펌웨어의 Zephyr 포팅 프로젝트입니다. +nRF52840 기반 VesiScan BASIC 펌웨어의 Zephyr 포팅 프로젝트 ## 현재 구현 상태 @@ -9,13 +9,16 @@ nRF52840 기반 VesiScan BASIC 펌웨어의 Zephyr 포팅 프로젝트입니다. | 1 | GPIO + 전원 래치 + 버튼 상태머신 | 완료 | | 1 | LED 제어 (green/orange 패턴) | 완료 | | 2 | BLE 스택 + NUS (advertising, 연결, 재연결) | 완료 | -| 3 | 배터리 ADC (AIN2, 12bit, 저전압 자동 OFF) | 진행중 | -| 3 | 커맨드 파서 (msn? 구현) | 진행중 | -| - | Flash 저장소 (NVS) | 미구현 | -| - | 온도 센서 (TMP235, AIN3) | 미구현 | -| - | IMU (ICM42670P, I2C) | 미구현 | -| - | 피에조 + ADC121S051 | 미구현 | +| 3 | 배터리 ADC (AIN2, 12bit) | 완료 | +| 3 | 온도 센서 TMP235 (AIN3) | 완료 | +| 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 저장/복원 | 미구현 | ## 프로젝트 구조 @@ -36,14 +39,22 @@ blinky/ └── drivers/ ├── battery/ │ └── battery_adc.c / .h # 배터리 전압 ADC (AIN2, 12bit) - └── led/ - └── led_control.c / .h # LED 패턴 제어 (k_timer 기반) + ├── 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 + └── temperature/ + └── tmp235.c / .h # TMP235 온도 센서 (AIN3) ``` ## 빌드 방법 ### 일반 빌드 -소스 파일(.c/.h)만 변경한 경우. +소스 파일(.c/.h)만 변경한 경우 ### Pristine Build 아래 파일을 변경한 경우 반드시 Pristine Build: @@ -51,7 +62,7 @@ blinky/ - `boards/*.overlay` (Devicetree 핀 매핑) - `CMakeLists.txt` (소스/include 경로 추가) -nRF Connect for VS Code → Build 패널 → **Pristine Build** 또는 `build` 폴더 삭제 후 빌드. +nRF Connect for VS Code → Build 패널 → **Pristine Build** 또는 `build` 폴더 삭제 후 빌드 ## 핀 매핑 (Devicetree Overlay) @@ -62,6 +73,21 @@ nRF Connect for VS Code → Build 패널 → **Pristine Build** 또는 `build` | `LED_BLE` | P0.12 | Output, Active LOW | 초록 LED | | `FUNCTION_LED` | P0.29 | Output, Active LOW | 주황 LED | | ADC AIN2 | P0.04 | Analog Input | 배터리 전압 분압 | +| ADC AIN3 | P0.05 | Analog Input | TMP235 온도 센서 | +| I2C0 SCL | P1.14 | I2C | ICM42670P SCL | +| I2C0 SDA | P1.15 | I2C | ICM42670P SDA | +| `PIEZO_PWR` | P1.09 | Output, Active HIGH | 피에조/TMP235 전원 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 | > UART0은 P0.08 충돌 방지를 위해 비활성화 (`status = "disabled"`) @@ -89,30 +115,62 @@ nRF Connect for VS Code → Build 패널 → **Pristine Build** 또는 `build` | 커맨드 | 응답 | 기능 | |--------|------|------| | `msn?` | `rsn:` + uint16 mV | 배터리 전압 측정 | +| `mls?` | `rls:` + state | LED 상태 변경 | +| `msp?` | `rsp:` + accel/gyro | IMU 단발 측정 | +| `mst?` | `rso:` + int16 cdeg | TMP235 온도 측정 | +| `mpa?` | `rpa:` + status | 피에조 전원 ON | +| `mpb?` | `rpb:` + status | 피에조 전원 OFF | +| `mpc?` | `rpc:` + cycles/status | 피에조 burst 테스트 | +| `mec?` | `reb:` + `raa:` | 단일 채널 echo capture | +| `maa?` | `reb:` x6 + `raa:` | 6채널 echo sweep | +| `mbb?` | `rbb:` + `reb:` x6 + `raa:` | 배터리+IMU+온도+6채널 echo | +| `mcf?` | `rcf:` | 측정 파라미터 읽기(현재 설정값 반환) | +| `mcs?` | `rcs:` + status | 측정 파라미터 쓰기 + 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) | -### 전체 커맨드 목록 (구현 예정) +### 전체 커맨드 목록 | # | 내용 | 명령 | 응답 | 비고 | |---|------|------|------|------| -| 1 | 전원 OFF | `msq?` | `rsq:` | | -| 2 | 재부팅 | `mss?` | `rss:` | | -| 3 | 본딩 삭제 + 재부팅 | `msr?` | `rsr:` | | -| 4 | LED 상태 | `mls?` | - | | -| 5 | HW Version 읽기 | `mrh?` | `rrh:` | FDS | -| 6 | HW Version 쓰기 | `mwh?` | `rwh:` | FDS | -| 7 | Serial Number 읽기 | `mrs?` | `rrs:` | FDS | -| 8 | Serial Number 쓰기 | `mws?` | `rws:` | FDS | -| 9 | FW Version 읽기 | `mfv?` | `rfv:` | | -| 10 | Passkey 읽기 | `mqz?` | `rqz:` | FDS | -| 11 | Passkey 쓰기 | `mpz?` | `rpz:` | FDS | +| 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 | 온도 측정 | `mso?` | `rso:` | | -| 15 | 단일 채널 측정 | `mec?` | `reb:` → `raa:` | 테스트용 | -| 16 | 모든 채널(6) 측정 | `maa?` | `reb:`(6개) → `raa:` | | -| 17 | 전체 측정 | `mbb?` | `rbb:` → `reb:`(6개) → `raa:` | 배터리+IMU+온도+피에조 | -| 18 | 측정 파라미터 읽기 | `mcf?` | `rcf:` | FDS | -| 19 | 측정 파라미터 쓰기 | `mcs?` | `rcs:` | FDS | +| 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 | 모든 채널(6) 측정 | `maa?` | `reb:`(6개) → `raa:` | 구현 완료 | +| 20 | 전체 측정 | `mbb?` | `rbb:` → `reb:`(6개) → `raa:` | 구현 완료 | +| 21 | 측정 파라미터 읽기 | `mcf?` | `rcf:` | 구현 완료, 현재 설정값 반환 | +| 22 | 측정 파라미터 쓰기 | `mcs?` | `rcs:` | 구현 완료, NVS 저장 | + +## 남은 포팅 작업 + +| 항목 | 현재 상태 | 다음 작업 | +|------|-----------|-----------| +| 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) @@ -130,6 +188,11 @@ nRF Connect for VS Code → Build 패널 → **Pristine Build** 또는 `build` | `[NUS TX]` | NUS 송신 | | `[CMD]` | 커맨드 파서 처리 결과 | | `[BATT]` | 배터리 ADC 측정값 | +| `[IMU]` | IMU 초기화/측정 | +| `[ECHO]` | ADC121S051 echo capture | +| `[PIEZO]` | 피에조 mux/burst/power | +| `[MBB]` | 전체 측정 시퀀스 | +| `[DFU]` | MCUboot 이미지 상태 | | `[SYS]` | 슬립 진입 | ## prj.conf 주요 설정 @@ -145,3 +208,11 @@ nRF Connect for VS Code → Build 패널 → **Pristine Build** 또는 `build` | `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 보안 비활성화(개발 모드) | diff --git a/boards/nrf5340dk_nrf5340_cpuapp.overlay b/boards/nrf5340dk_nrf5340_cpuapp.overlay index 1fac2a1..9b5f02d 100644 --- a/boards/nrf5340dk_nrf5340_cpuapp.overlay +++ b/boards/nrf5340dk_nrf5340_cpuapp.overlay @@ -126,5 +126,71 @@ 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/measurement_pipeline.md b/plan/measurement_pipeline.md index e1a1d2a..db22e13 100644 --- a/plan/measurement_pipeline.md +++ b/plan/measurement_pipeline.md @@ -58,7 +58,7 @@ echo_adc_init() │ ├─ PE OFF │ │ └─ __enable_irq() │ │ │ -│ delay_us 대기 (기본 20us) ← 버스트 → ADC 시작 딜레이 │ +│ delay_us 대기 (기본 10us) ← 버스트 → ADC 시작 딜레이 │ │ │ │ echo_adc_capture(buf, num_samples) ← SPIM3 연속 읽기 │ │ └─ 100샘플 × 2byte = 200byte │ diff --git a/pm_static.yml b/pm_static.yml new file mode 100644 index 0000000..199d204 --- /dev/null +++ b/pm_static.yml @@ -0,0 +1,35 @@ +app: + address: 0xc200 + region: flash_primary + size: 0x78e00 +mcuboot: + address: 0x0 + region: flash_primary + size: 0xc000 +mcuboot_pad: + address: 0xc000 + region: flash_primary + size: 0x200 +mcuboot_primary: + address: 0xc000 + orig_span: &id001 + - mcuboot_pad + - app + region: flash_primary + size: 0x79000 + span: *id001 +mcuboot_primary_app: + address: 0xc200 + orig_span: &id002 + - app + region: flash_primary + size: 0x78e00 + span: *id002 +mcuboot_secondary: + address: 0x85000 + region: flash_primary + size: 0x79000 +settings_storage: + address: 0xfe000 + region: flash_primary + size: 0x2000 diff --git a/prj.conf b/prj.conf index c0c056e..f776646 100644 --- a/prj.conf +++ b/prj.conf @@ -56,8 +56,46 @@ CONFIG_I2C=y CONFIG_NRFX_SPIM=y # System -CONFIG_HEAP_MEM_POOL_SIZE=2048 +# 런타임 중 동적으로 확보해서 사용하는 메모리 크기 +CONFIG_HEAP_MEM_POOL_SIZE=4096 +# 백그라운드 작업 처리 스레드 작업 공간(workqueue) 크기 CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 # BLE RX stack sizes (prevent stack overflow on BT RX WQ) CONFIG_BT_RX_STACK_SIZE=4096 + +# DFU +# 애플리케이션이 MCUboot 환경(부트로더)에서 동작(MCUboot: FW 교체 판단, 서명 검증) +CONFIG_BOOTLOADER_MCUBOOT=y +CONFIG_ZCBOR=y +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y + +# Persistent application settings (FDS replacement) +CONFIG_NVS=y +CONFIG_SETTINGS=y +CONFIG_SETTINGS_NVS=y +CONFIG_SETTINGS_NVS_SECTOR_COUNT=2 +CONFIG_PM_PARTITION_SIZE_SETTINGS_STORAGE=0x2000 + +# mcumgr 프레임워크 활성화 +CONFIG_MCUMGR=y +# BLE를 통해 mcumgr 메시지 송수신 +CONFIG_MCUMGR_TRANSPORT_BT=y +# BLE로 수신된 mcumgr 메시지에 대해 읽기/쓰기 권한 허용 +CONFIG_MCUMGR=y +CONFIG_MCUMGR_TRANSPORT_BT=y +CONFIG_MCUMGR_TRANSPORT_BT_PERM_RW=y +# mcumgr 이미지 관리 그룹 활성화 (이미지 업로드, 테스트, 확인, 슬롯 상태 조회) +CONFIG_MCUMGR_GRP_IMG=y +# mcumgr OS 관리 그룹 활성화 (시스템 정보 조회, 재부팅 등 OS 관련 명령 지원) +CONFIG_MCUMGR_GRP_OS=y + +# MCUboot 이미지 관리 활성화 (이미지 검증, 교체, 롤백 등) +CONFIG_IMG_MANAGER=y +# 플래시 메모리를 스트림으로 접근하여 이미지 업로드/교체 처리 +CONFIG_STREAM_FLASH=y + +# Netcore HCI IPC (BLE 통신을 위한 인터페이스) : 2 cores 간 IPC 활성화 +#CONFIG_SB_CONFIG_NETCORE_HCI_IPC=y +#SB_CONFIG_NETCORE_HCI_IPC=y diff --git a/src/ble/ble_service.c b/src/ble/ble_service.c index bc14b46..f4e41b3 100644 --- a/src/ble/ble_service.c +++ b/src/ble/ble_service.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "ble_service.h" @@ -135,7 +136,13 @@ static const struct bt_data ad[] = static const struct bt_data sd[] = { - BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_NUS_VAL), + /* + * Legacy NUS + DFU(SMP) UUIDs together exceed the 31-byte scan response + * limit for legacy advertising. Keep the device name in the advertising + * packet and expose only SMP here so DFU remains discoverable. + + BT_DATA_BYTES(BT_DATA_UUID128_SOME, SMP_BT_SVC_UUID_VAL), // BLE DFU 동작을 위한 SMP UUDI 광고 */ + BT_DATA_BYTES(BT_DATA_UUID128_SOME, BT_UUID_NUS_VAL), // UUID에 NUS 안 넣으면 Web bluetooth 앱에서 안 보임 }; /*============================================================================== diff --git a/src/drivers/piezo/piezo.c b/src/drivers/piezo/piezo.c index d76baf6..eb9504b 100644 --- a/src/drivers/piezo/piezo.c +++ b/src/drivers/piezo/piezo.c @@ -87,7 +87,8 @@ static inline void nop_delay_14(void) static inline void nop_delay_32(void) { - for (int i = 0; i < 32; i++) { + for (int i = 0; i < 32; i++) + { burst_nop(); } } @@ -107,23 +108,28 @@ static void piezo_drive_idle(void) int piezo_init(void) { - if (piezo_initialized) { + if (piezo_initialized) + { return 0; } - const struct gpio_dt_spec *pins[] = { + const struct gpio_dt_spec *pins[] = + { &piezo_pwr, &piezo_pe, &piezo_p_out, &piezo_n_out, &piezo_dmp, &mux_en_a, &mux_en_b, &mux_sel0, &mux_sel1 }; - for (size_t i = 0; i < ARRAY_SIZE(pins); i++) { - if (!device_is_ready(pins[i]->port)) { + for (size_t i = 0; i < ARRAY_SIZE(pins); i++) + { + if (!device_is_ready(pins[i]->port)) + { DBG_PRINTF("[PIEZO] GPIO device not ready (%d)\r\n", (int)i); return -ENODEV; } int err = gpio_pin_configure_dt(pins[i], GPIO_OUTPUT_INACTIVE); - if (err) { + if (err) + { DBG_PRINTF("[PIEZO] GPIO init fail idx=%d err=%d\r\n", (int)i, err); return err; } @@ -146,7 +152,8 @@ int piezo_init(void) void piezo_power_on(void) { - if (!piezo_initialized && piezo_init() != 0) { + if (!piezo_initialized && piezo_init() != 0) + { return; } @@ -155,7 +162,8 @@ void piezo_power_on(void) void piezo_power_off(void) { - if (!piezo_initialized && piezo_init() != 0) { + if (!piezo_initialized && piezo_init() != 0) + { return; } @@ -165,14 +173,17 @@ void piezo_power_off(void) int piezo_select_channel(uint8_t channel) { - if (!piezo_initialized) { + if (!piezo_initialized) + { int err = piezo_init(); - if (err) { + if (err) + { return err; } } - if (channel >= PIEZO_NUM_CHANNELS) { + if (channel >= PIEZO_NUM_CHANNELS) + { return -EINVAL; } @@ -187,7 +198,8 @@ int piezo_select_channel(uint8_t channel) void piezo_burst_sw(uint8_t cycles) { - if (!piezo_initialized || cycles == 0U) { + if (!piezo_initialized || cycles == 0U) + { return; } @@ -203,7 +215,8 @@ void piezo_burst_sw(uint8_t cycles) NRF_P1->OUT = p1_all_low; NRF_P0->OUTSET = piezo_pe_mask; - for (uint8_t i = 0; i < cycles; i++) { + for (uint8_t i = 0; i < cycles; i++) + { NRF_P1->OUT = p1_p_high_n_low; nop_delay_14(); diff --git a/src/main.c b/src/main.c index a5eafa1..a051ea3 100644 --- a/src/main.c +++ b/src/main.c @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include "main.h" #include "debug_print.h" @@ -79,6 +81,8 @@ static bool power_btn_suspended; /* 측정 중 전원 버튼 상태 * k_work를 사용하면 시스템 워크큐 스레드에서 실행되므로 안전하게 BLE API 호출 가능. * main_s() 타이머 콜백에서 k_work_submit()으로 예약하여 사용한다. */ +static bool resume_without_power_button; +static uint32_t boot_reset_reason; static struct k_work adv_start_work; static struct k_work adv_stop_work; @@ -111,13 +115,15 @@ static void ble_cmd_work_handler(struct k_work *work) uint16_t local_len = 0U; k_spinlock_key_t key = k_spin_lock(&ble_cmd_lock); - if (ble_cmd_pending) { + if (ble_cmd_pending) + { local_len = ble_cmd_len; memcpy(local_buf, ble_cmd_buf, local_len); } k_spin_unlock(&ble_cmd_lock, key); - if (local_len == 0U) { + if (local_len == 0U) + { DBG_ERR("[BLE RX] worker: empty\r\n"); return; } @@ -204,17 +210,43 @@ static void ble_rx_handler(const uint8_t *data, uint16_t len) /* 전원 유지 핀(P0.08) 초기화 - 아직 래치하지 않음 * 버튼을 물리적으로 누르고 있는 동안만 전원 유지됨 * 2초 후 main_s()에서 래치(HIGH) 설정 */ +static void boot_context_detect(void) +{ + boot_reset_reason = nrf_power_resetreas_get(NRF_POWER); + resume_without_power_button = + (boot_reset_reason & (NRF_POWER_RESETREAS_SREQ_MASK | + NRF_POWER_RESETREAS_DOG_MASK | + NRF_POWER_RESETREAS_LOCKUP_MASK | + NRF_POWER_RESETREAS_RESETPIN_MASK)) != 0U; + + if (boot_reset_reason != 0U) + { + nrf_power_resetreas_clear(NRF_POWER, boot_reset_reason); + } +} + static void power_hold_init(void) { - gpio_pin_configure_dt(&power_hold, GPIO_OUTPUT_INACTIVE); +#if defined(CONFIG_BOOTLOADER_MCUBOOT) + bool dfu_confirm_pending = !boot_is_img_confirmed(); +#else + bool dfu_confirm_pending = false; +#endif + + gpio_pin_configure_dt(&power_hold, + (resume_without_power_button || dfu_confirm_pending) ? GPIO_OUTPUT_ACTIVE + : GPIO_OUTPUT_INACTIVE); } /* 전원 ON/OFF 제어 - P0.08 핀으로 물리적 전원 래치/해제 */ static void power_control_handler(on_off_cont_t device_power_st) { - if (device_power_st == OFF) { + if (device_power_st == OFF) + { gpio_pin_set_dt(&power_hold, 0); /* P0.08 LOW → 전원 래치 해제 → 전원 차단 */ - } else { + } + else + { gpio_pin_set_dt(&power_hold, 1); /* P0.08 HIGH → 전원 유지 */ DBG_PRINTF("[PWR] ON\r\n"); } @@ -239,13 +271,16 @@ static void load_default_config(void) size_t hw_len = strlen(HARDWARE_VERSION); size_t passkey_len = strlen(DEFAULT_PASSKEY); - if (serial_len > SERIAL_NO_LENGTH) { + if (serial_len > SERIAL_NO_LENGTH) + { serial_len = SERIAL_NO_LENGTH; } - if (hw_len > HW_NO_LENGTH) { + if (hw_len > HW_NO_LENGTH) + { hw_len = HW_NO_LENGTH; } - if (passkey_len > PASSKEY_LENGTH) { + if (passkey_len > PASSKEY_LENGTH) + { passkey_len = PASSKEY_LENGTH; } @@ -403,11 +438,7 @@ static void timers_init(void) k_work_init(&adv_start_work, adv_start_work_handler); k_work_init(&adv_stop_work, adv_stop_work_handler); 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); + 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(); } @@ -417,59 +448,145 @@ static void timers_start(void) k_timer_start(&m_power_on_delay_timer, K_MSEC(POWER_ON_DELAY), K_NO_WAIT); } +static void resume_device_after_soft_reset(void) +{ + if (!resume_without_power_button) + { + return; + } + + device_on = true; + cnt_s = 0; + boot_btn_released = (gpio_pin_get_dt(&power_btn) != 1); + m_reset_status = 1; + + power_control_handler(ON); + led_set_state(LED_STATE_ADVERTISING); + battery_timer_start(); + k_work_submit(&adv_start_work); + + DBG_CORE("[BOOT] Resume after resetreas=0x%08x\r\n", boot_reset_reason); +} + +static void confirm_running_image(void) +{ +#if defined(CONFIG_BOOTLOADER_MCUBOOT) + if (!boot_is_img_confirmed()) + { + int err = boot_write_img_confirmed(); + if (err == 0) + { + DBG_CORE("[DFU] Running image confirmed\r\n"); + } + else + { + DBG_ERR("[DFU] Image confirm failed (err %d)\r\n", err); + } + } + else + { + DBG_CORE("[DFU] Running image already confirmed\r\n"); + } +#endif +} + +static const char *swap_type_to_str(int swap_type) +{ + switch (swap_type) + { + case BOOT_SWAP_TYPE_NONE: + return "none"; + case BOOT_SWAP_TYPE_TEST: + return "test"; + case BOOT_SWAP_TYPE_PERM: + return "perm"; + case BOOT_SWAP_TYPE_REVERT: + return "revert"; + case BOOT_SWAP_TYPE_FAIL: + return "fail"; + default: + return "unknown"; + } +} + +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(); + int err = boot_read_bank_header(active_slot, &header, sizeof(header)); + + DBG_CORE("[DFU] active_slot=%u swap_type=%s(%d) confirmed=%d\r\n", + active_slot, + swap_type_to_str(swap_type), + swap_type, + boot_is_img_confirmed()); + + if (err == 0) + { + DBG_CORE("[DFU] image version %u.%u.%u+%u size=0x%x\r\n", + header.h.v1.sem_ver.major, + header.h.v1.sem_ver.minor, + header.h.v1.sem_ver.revision, + header.h.v1.sem_ver.build_num, + header.h.v1.image_size); + } + else + { + DBG_ERR("[DFU] image header read failed (slot %u, err %d)\r\n", + active_slot, + err); + } +#endif +} + /*============================================================================== * 메인 함수 *============================================================================*/ int main(void) { + boot_context_detect(); + /*── Phase 1: 하드웨어 기본 초기화 ──*/ power_hold_init(); cnt_s = 0; - /* - * 현재는 전원 버튼/mbb 문제를 같이 디버깅 중이라 - * "버튼을 2초 이상 눌러야 래치 ON" 규칙이 오히려 부팅 자체를 막고 있다. - * - * 그래서 디버깅 동안만, MCU가 main()까지 올라오면 바로 P0.08 래치를 ON으로 잡는다. - * - * 의미: - * - 사용자는 전원 버튼을 "부팅이 시작될 만큼만" 누르면 된다. - * - 예전처럼 2초를 정확히 맞춰 길게 누를 필요가 없다. - * - 일단 켜진 뒤의 BLE/측정/로그 문제를 보기 쉬워진다. - * - * 나중에 전원 시퀀스 디버깅이 끝나면 이 블록은 다시 제거하면 된다. - */ 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-042\r\n"); + DBG_CORE(" BUILD TAG: TEST-ADV-UNIT-000\r\n"); DBG_CORE("========================================\r\n"); DBG_CORE("[1] HW Init\r\n"); gpio_init(); timers_init(); + log_mcuboot_state(); load_default_config(); led_init(); battery_adc_init(); battery_timer_init(); imu_init(); temp_init(); - DBG_CORE(" gpio/timer/config/led/batt/imu/temp OK\r\n"); + piezo_config_init(); + DBG_CORE(" gpio/timer/config/led/batt/imu/temp/piezo-cfg OK\r\n"); /*── Phase 2: BLE 스택 + NUS ──*/ DBG_CORE("[2] BLE Init\r\n"); if (ble_service_init(ble_rx_handler) == 0) { DBG_CORE(" ble/nus OK\r\n"); + resume_device_after_soft_reset(); } else { DBG_ERR(" ble FAIL\r\n"); } - /*── Phase 3: FDS/NVS (TODO) ──*/ + /*── Phase 3: FDS/NVS ──*/ /*── Phase 4: 애플리케이션 (TODO) ──*/ DBG_CORE("\r\n========================================\r\n"); @@ -480,7 +597,8 @@ int main(void) timers_start(); /* 메인 루프 - idle */ - for (;;) { + for (;;) + { k_msleep(100); } diff --git a/src/parser.c b/src/parser.c index 4b4c791..44a8134 100644 --- a/src/parser.c +++ b/src/parser.c @@ -8,6 +8,11 @@ * 현재 구현된 커맨드: msn? (배터리 측정), mls? (LED 상태 설정) ******************************************************************************/ #include +#include +#if IS_ENABLED(CONFIG_SETTINGS) +#include +#endif +#include #include #include @@ -40,9 +45,15 @@ #define PIEZO_CFG_DELAY_DEFAULT 10 #define PIEZO_CFG_SAMPLES_DEFAULT 100 #define PIEZO_CFG_AVG_DEFAULT 10 +#define PIEZO_CFG_AVG_MAX 100 +#define PIEZO_CFG_DELAY_MAX_US 1000 #define PIEZO_POST_SELECT_SETTLE_US 500 #define PIEZO_AVG_INTER_BURST_GAP_US 500 +#define PIEZO_SETTINGS_NAME "vesiscan" +#define PIEZO_SETTINGS_CFG_KEY "piezo" +#define PIEZO_SETTINGS_FULL_KEY "vesiscan/piezo" + static uint16_t piezo_channels[PIEZO_NUM_CHANNELS][ECHO_ADC_MAX_SAMPLES]; static uint16_t echo_capture[ECHO_ADC_MAX_SAMPLES]; static uint32_t echo_accum[ECHO_ADC_MAX_SAMPLES]; @@ -52,6 +63,140 @@ static uint8_t tx_echo_buf[4 + 2 + (ECHO_ADC_MAX_SAMPLES * 2) + 2]; static uint8_t tx_bundle_buf[22]; static uint8_t tx_cfg_buf[16]; static uint8_t tx_ascii_buf[4 + HW_NO_LENGTH + 2]; +static piezo_config_t g_piezo_config = { + .freq = PIEZO_CFG_FREQ_DEFAULT, + .cycles = PIEZO_CFG_CYCLES_DEFAULT, + .avg = PIEZO_CFG_AVG_DEFAULT, + .delay_us = PIEZO_CFG_DELAY_DEFAULT, + .samples = PIEZO_CFG_SAMPLES_DEFAULT, +}; + +static bool piezo_config_validate(const piezo_config_t *cfg) +{ + if (cfg == NULL) { + return false; + } + + if (cfg->freq != PIEZO_CFG_FREQ_DEFAULT) { + return false; + } + + if ((cfg->cycles < 3U) || (cfg->cycles > 7U)) { + return false; + } + + if ((cfg->avg == 0U) || (cfg->avg > PIEZO_CFG_AVG_MAX)) { + return false; + } + + if (cfg->delay_us > PIEZO_CFG_DELAY_MAX_US) { + return false; + } + + if ((cfg->samples == 0U) || (cfg->samples > ECHO_ADC_MAX_SAMPLES)) { + return false; + } + + return true; +} + +static void piezo_config_log(const char *prefix, const piezo_config_t *cfg) +{ + DBG_PRINTF("%s freq=%u cycles=%u avg=%u delay=%u samples=%u\r\n", + prefix, + cfg->freq, + cfg->cycles, + cfg->avg, + cfg->delay_us, + cfg->samples); +} + +const piezo_config_t *piezo_config_get(void) +{ + return &g_piezo_config; +} + +#if IS_ENABLED(CONFIG_SETTINGS) +static int piezo_settings_set(const char *name, size_t len, + settings_read_cb read_cb, void *cb_arg) +{ + const char *next; + int rc; + + if (settings_name_steq(name, PIEZO_SETTINGS_CFG_KEY, &next) && (next == NULL)) + { + piezo_config_t loaded; + + if (len != sizeof(loaded)) { + DBG_ERR("[CFG] piezo settings size mismatch (%u)\r\n", (uint32_t)len); + return -EINVAL; + } + + rc = read_cb(cb_arg, &loaded, sizeof(loaded)); + if (rc < 0) { + DBG_ERR("[CFG] piezo settings read failed err=%d\r\n", rc); + return rc; + } + if (rc != sizeof(loaded)) { + DBG_ERR("[CFG] piezo settings short read (%d)\r\n", rc); + return -EINVAL; + } + + if (!piezo_config_validate(&loaded)) { + DBG_ERR("[CFG] piezo settings invalid, keep defaults\r\n"); + return 0; + } + + g_piezo_config = loaded; + piezo_config_log("[CFG] piezo restored", &g_piezo_config); + return 0; + } + + return -ENOENT; +} + +SETTINGS_STATIC_HANDLER_DEFINE(piezo_settings, + PIEZO_SETTINGS_NAME, + NULL, + piezo_settings_set, + NULL, + NULL); +#endif + +int piezo_config_init(void) +{ +#if IS_ENABLED(CONFIG_SETTINGS) + int err = settings_subsys_init(); + if (err && (err != -EALREADY)) { + DBG_ERR("[CFG] settings init failed err=%d\r\n", err); + return err; + } + + err = settings_load_subtree(PIEZO_SETTINGS_NAME); + if (err) { + DBG_ERR("[CFG] piezo settings load failed err=%d\r\n", err); + return err; + } +#endif + + piezo_config_log("[CFG] piezo active", &g_piezo_config); + return 0; +} + +static int piezo_config_save(void) +{ +#if IS_ENABLED(CONFIG_SETTINGS) + int err = settings_save_one(PIEZO_SETTINGS_FULL_KEY, + &g_piezo_config, + sizeof(g_piezo_config)); + if (err) { + DBG_ERR("[CFG] piezo settings save failed err=%d\r\n", err); + } + return err; +#else + return 0; +#endif +} /*============================================================================== * CRC16 (CRC-CCITT, Nordic SDK 호환) @@ -312,6 +457,13 @@ static int start_piezo_session(void) static int perform_piezo_sweep(void) { + const piezo_config_t *cfg = piezo_config_get(); + uint16_t capture_delay_us = cfg->delay_us; + + if (capture_delay_us < PIEZO_BURST_TO_ADC_DELAY_US) { + capture_delay_us = PIEZO_BURST_TO_ADC_DELAY_US; + } + for (uint8_t ch = 0; ch < PIEZO_NUM_CHANNELS; ch++) { DBG_PRINTF("[MBB] sweep ch=%d start\r\n", ch); @@ -334,32 +486,32 @@ static int perform_piezo_sweep(void) memset(echo_accum, 0, sizeof(echo_accum)); - for (uint8_t avg = 0; avg < PIEZO_AVERAGE_COUNT; avg++) + for (uint16_t avg = 0; avg < cfg->avg; avg++) { if (avg > 0U) { k_busy_wait(PIEZO_AVG_INTER_BURST_GAP_US); } - piezo_burst_sw(PIEZO_SW_BURST_CYCLES); - k_busy_wait(PIEZO_BURST_TO_ADC_DELAY_US); + piezo_burst_sw((uint8_t)cfg->cycles); + k_busy_wait(capture_delay_us); - err = echo_adc_capture(echo_capture, ECHO_ADC_MAX_SAMPLES); + err = echo_adc_capture(echo_capture, cfg->samples); if (err) { DBG_PRINTF("[ECHO] capture fail ch=%d avg=%d err=%d\r\n", ch, avg, err); return ECHO_STATUS_CAPTURE; } - for (uint16_t i = 0; i < ECHO_ADC_MAX_SAMPLES; i++) + for (uint16_t i = 0; i < cfg->samples; i++) { echo_accum[i] += echo_capture[i]; } } - for (uint16_t i = 0; i < ECHO_ADC_MAX_SAMPLES; i++) + for (uint16_t i = 0; i < cfg->samples; i++) { - piezo_channels[ch][i] = (uint16_t)(echo_accum[i] / PIEZO_AVERAGE_COUNT); + piezo_channels[ch][i] = (uint16_t)(echo_accum[i] / cfg->avg); } DBG_PRINTF("[MBB] sweep ch=%d done\r\n", ch); @@ -521,12 +673,15 @@ static int cmd_mst(const uint8_t *data, uint8_t data_len) return 1; } +/** + * 테스트용 + * mpa?: piezo TX/RX 전원 레일만 켬 + */ static int cmd_mpa(const uint8_t *data, uint8_t data_len) { ARG_UNUSED(data); ARG_UNUSED(data_len); - /* mpa?: piezo TX/RX 전원 레일만 켠다. 측정은 하지 않는다. */ DBG_CORE("[MPA] enter\r\n"); if (piezo_init() != 0) { DBG_ERR("[MPA] piezo_init failed\r\n"); @@ -563,10 +718,16 @@ static int cmd_mpb(const uint8_t *data, uint8_t data_len) return 1; } +/** + * 테스트용 + * mpc?: burst만 한 번 발생시키는 테스트 명령 + * echo를 읽지 않고 초음파가 나가는지만 볼 때 사용 + */ static int cmd_mpc(const uint8_t *data, uint8_t data_len) { - uint16_t cycles = 5; - uint16_t freq_option = 1; + const piezo_config_t *cfg = piezo_config_get(); + uint16_t cycles = cfg->cycles; + uint16_t freq_option = cfg->freq; uint16_t piezo_ch = 0; get_data_u16_be(data, data_len, 0, &cycles); @@ -575,10 +736,6 @@ static int cmd_mpc(const uint8_t *data, uint8_t data_len) ARG_UNUSED(freq_option); - /* - * mpc?: burst만 한 번 발생시키는 테스트 명령. - * echo를 읽지 않으므로 "초음파가 나가는지"만 빠르게 볼 때 쓴다. - */ if ((cycles < 3U) || (cycles > 7U)) { send_response_u16("rpc:", 2); @@ -603,8 +760,8 @@ static int cmd_mpc(const uint8_t *data, uint8_t data_len) } /* - * 현재 Zephyr 포팅본은 2.1MHz SW burst 하나만 구현되어 있다. - * 레거시의 freq_option 값은 받아두되, 아직은 같은 burst 함수로 처리한다. + * 현재 Zephyr 포팅본은 2.1MHz SW burst 하나만 구현되어 있음 + * 레거시의 freq_option 값은 받아두되, 아직은 같은 burst 함수로 처리 */ piezo_burst_sw((uint8_t)cycles); piezo_power_off(); @@ -616,11 +773,12 @@ static int cmd_mpc(const uint8_t *data, uint8_t data_len) static int cmd_mec(const uint8_t *data, uint8_t data_len) { - uint16_t freq_option = 1; - uint16_t delay_us = PIEZO_BURST_TO_ADC_DELAY_US; - uint16_t num_samples = ECHO_ADC_MAX_SAMPLES; - uint16_t cycles = PIEZO_SW_BURST_CYCLES; - uint16_t averaging = 1; + const piezo_config_t *cfg = piezo_config_get(); + uint16_t freq_option = cfg->freq; + uint16_t delay_us = cfg->delay_us; + uint16_t num_samples = cfg->samples; + uint16_t cycles = cfg->cycles; + uint16_t averaging = cfg->avg; uint16_t piezo_ch = 0; get_data_u16_be(data, data_len, 0, &freq_option); @@ -633,8 +791,8 @@ static int cmd_mec(const uint8_t *data, uint8_t data_len) ARG_UNUSED(freq_option); /* - * mec?: 단일 채널 burst + echo capture. - * maa?/mbb?보다 가벼워서 "한 채널만 먼저 살아 있는지" 확인하기 좋다. + * 테스트용 + * mec?: 단일 채널 burst + echo capture */ if (num_samples > ECHO_ADC_MAX_SAMPLES) { num_samples = ECHO_ADC_MAX_SAMPLES; @@ -671,19 +829,19 @@ static int cmd_mec(const uint8_t *data, uint8_t data_len) return 1; } +/** + * 테스트용(이전 정렬모드) + * maa?: + * - piezo 6채널 burst 순서대로 쏘고 + * - echo 샘플을 채널별로 모은 뒤 + * - reb: 패킷 6개 전송 + * - raa: 상태값은 전체 작업 성공/실패 요약 + */ static int cmd_maa(const uint8_t *data, uint8_t data_len) { ARG_UNUSED(data); ARG_UNUSED(data_len); - /* - * maa?: - * - piezo 6채널을 순서대로 쏘고 - * - echo 샘플을 채널별로 모은 뒤 - * - reb: 패킷 6개를 보낸다. - * - * 마지막 raa: 상태값은 "전체 작업 성공/실패 요약"이다. - */ processing = true; power_button_suspend(true); @@ -697,7 +855,7 @@ static int cmd_maa(const uint8_t *data, uint8_t data_len) { for (uint8_t ch = 0; ch < PIEZO_NUM_CHANNELS; ch++) { - send_response_echo(piezo_channels[ch], ECHO_ADC_MAX_SAMPLES); + send_response_echo(piezo_channels[ch], piezo_config_get()->samples); } } @@ -710,6 +868,9 @@ static int cmd_maa(const uint8_t *data, uint8_t data_len) return 1; } +/** + * 전체 측정 시 사용 + */ static int cmd_mbb(const uint8_t *data, uint8_t data_len) { ARG_UNUSED(data); @@ -747,7 +908,7 @@ static int cmd_mbb(const uint8_t *data, uint8_t data_len) if (status == ECHO_STATUS_OK) { - /* info 성격 데이터는 echo sweep이 정상 끝났을 때만 읽는다. */ + /* info 성격 데이터는 echo sweep이 정상 끝났을 때만 읽음 */ DBG_PRINTF("[MBB] battery read\r\n"); batt_mv = battery_read_mv(); if (batt_mv < 0) @@ -777,7 +938,7 @@ static int cmd_mbb(const uint8_t *data, uint8_t data_len) if (status == ECHO_STATUS_OK) { - /* rbb: 한 번에 보내는 "요약 정보 묶음" 패킷 */ + /* rbb: 센서(배터리+IMU+온도) 정보 패킷 */ DBG_PRINTF("[MBB] response tx start\r\n"); send_response_bundle((uint16_t)batt_mv, accel, gyro, temp_cdeg); @@ -785,7 +946,7 @@ static int cmd_mbb(const uint8_t *data, uint8_t data_len) { /* reb: 채널별 raw echo 파형 */ DBG_PRINTF("[MBB] tx reb ch=%d\r\n", ch); - send_response_echo(piezo_channels[ch], ECHO_ADC_MAX_SAMPLES); + send_response_echo(piezo_channels[ch], piezo_config_get()->samples); } } @@ -799,22 +960,52 @@ static int cmd_mbb(const uint8_t *data, uint8_t data_len) return 1; } +/** + * mcf?: piezo 측정 파라미터 읽기 + * 응답 rcf: + 설정값 echo back + */ static int cmd_mcf(const uint8_t *data, uint8_t data_len) { ARG_UNUSED(data); ARG_UNUSED(data_len); - send_response_piezo_config(PIEZO_CFG_FREQ_DEFAULT, - PIEZO_CFG_CYCLES_DEFAULT, - PIEZO_CFG_AVG_DEFAULT, - PIEZO_CFG_DELAY_DEFAULT, - PIEZO_CFG_SAMPLES_DEFAULT); - DBG_PRINTF("[CMD] mcf -> freq=%d cycles=%d avg=%d delay=%d samples=%d\r\n", - PIEZO_CFG_FREQ_DEFAULT, - PIEZO_CFG_CYCLES_DEFAULT, - PIEZO_CFG_AVG_DEFAULT, - PIEZO_CFG_DELAY_DEFAULT, - PIEZO_CFG_SAMPLES_DEFAULT); + const piezo_config_t *cfg = piezo_config_get(); + send_response_piezo_config(cfg->freq, cfg->cycles, cfg->avg, cfg->delay_us, cfg->samples); + piezo_config_log("[CMD] mcf ->", cfg); + return 1; +} + +/** + * mcs?: piezo 측정 파라미터 쓰기 + */ +static int cmd_mcs(const uint8_t *data, uint8_t data_len) +{ + piezo_config_t cfg; + + if (data_len < 10U) + { + send_response_u16("rcs:", 0xFFFF); + DBG_PRINTF("[CMD] mcs: insufficient data len=%u\r\n", data_len); + return 1; + } + + get_data_u16_be(data, data_len, 0, &cfg.freq); + get_data_u16_be(data, data_len, 1, &cfg.cycles); + get_data_u16_be(data, data_len, 2, &cfg.avg); + get_data_u16_be(data, data_len, 3, &cfg.delay_us); + get_data_u16_be(data, data_len, 4, &cfg.samples); + + if (!piezo_config_validate(&cfg)) + { + send_response_u16("rcs:", 0xFFFE); + piezo_config_log("[CMD] mcs invalid", &cfg); + return 1; + } + + g_piezo_config = cfg; + int err = piezo_config_save(); + send_response_u16("rcs:", (err == 0) ? 0U : 0xFFFD); + piezo_config_log("[CMD] mcs saved", &g_piezo_config); return 1; } @@ -977,6 +1168,7 @@ static const cmd_entry_t cmd_table[] = { "maa?", cmd_maa }, { "mbb?", cmd_mbb }, { "mcf?", cmd_mcf }, + { "mcs?", cmd_mcs }, { "mfv?", cmd_mfv }, { "mwh?", cmd_mwh }, { "mrh?", cmd_mrh }, @@ -1026,7 +1218,8 @@ int dr_parser(const uint8_t *buf, uint16_t len) } /* TAG 추출 (4바이트) */ - char tag[5] = { + char tag[5] = + { ascii_to_lower((char)buf[0]), ascii_to_lower((char)buf[1]), ascii_to_lower((char)buf[2]), @@ -1050,7 +1243,8 @@ int dr_parser(const uint8_t *buf, uint16_t len) } DBG_ERR("[CMD] Unknown: raw=%s normalized=%s\r\n", raw_tag, tag); - if (send_response_tag_echo("rxx:", raw_tag) != 0) { + if (send_response_tag_echo("rxx:", raw_tag) != 0) + { DBG_ERR("[CMD] rxx tx failed\r\n"); } return 0; diff --git a/src/parser.h b/src/parser.h index 1c6f6d2..41ed78d 100644 --- a/src/parser.h +++ b/src/parser.h @@ -9,6 +9,17 @@ #include +typedef struct +{ + uint16_t freq; + uint16_t cycles; + uint16_t avg; + uint16_t delay_us; + uint16_t samples; +} piezo_config_t; + +int piezo_config_init(void); +const piezo_config_t *piezo_config_get(void); int dr_parser(const uint8_t *buf, uint16_t len); #endif /* CMD_PARSER_H__ */ diff --git a/sysbuild.cmake b/sysbuild.cmake index d3872b9..965d725 100644 --- a/sysbuild.cmake +++ b/sysbuild.cmake @@ -6,3 +6,27 @@ if(SB_CONFIG_NETCORE_HCI_IPC) "Extra configuration for the nRF5340 net core HCI image" ) endif() + +if(SB_CONFIG_BOOTLOADER_MCUBOOT) + list(APPEND mcuboot_EXTRA_CONF_FILE + "${APP_DIR}/sysbuild/mcuboot.conf" + ) + list(REMOVE_DUPLICATES mcuboot_EXTRA_CONF_FILE) + set( + mcuboot_EXTRA_CONF_FILE + "${mcuboot_EXTRA_CONF_FILE}" + CACHE INTERNAL + "Extra configuration for the MCUboot image" + ) + + list(APPEND mcuboot_EXTRA_DTC_OVERLAY_FILE + "${APP_DIR}/sysbuild/mcuboot.overlay" + ) + list(REMOVE_DUPLICATES mcuboot_EXTRA_DTC_OVERLAY_FILE) + set( + mcuboot_EXTRA_DTC_OVERLAY_FILE + "${mcuboot_EXTRA_DTC_OVERLAY_FILE}" + CACHE INTERNAL + "Extra devicetree overlay for the MCUboot image" + ) +endif() diff --git a/sysbuild.conf b/sysbuild.conf index 0a9207b..682f504 100644 --- a/sysbuild.conf +++ b/sysbuild.conf @@ -1,2 +1,4 @@ # nRF52840 build: no network core image. # Re-enable NETCORE_HCI_IPC only for nRF5340 sysbuild configurations. + +SB_CONFIG_BOOTLOADER_MCUBOOT=y \ No newline at end of file diff --git a/sysbuild/mcuboot.conf b/sysbuild/mcuboot.conf new file mode 100644 index 0000000..099508f --- /dev/null +++ b/sysbuild/mcuboot.conf @@ -0,0 +1 @@ +CONFIG_GPIO_HOGS=y diff --git a/sysbuild/mcuboot.overlay b/sysbuild/mcuboot.overlay new file mode 100644 index 0000000..b88e085 --- /dev/null +++ b/sysbuild/mcuboot.overlay @@ -0,0 +1,10 @@ +#include + +&gpio0 { + power_hold_hog: power_hold_hog { + gpio-hog; + gpios = <8 GPIO_ACTIVE_HIGH>; + output-high; + line-name = "POWER_HOLD"; + }; +};