# SHT30 온습도 모니터링 STM32F407 펌웨어 > Raspberry Pi SHT30 온습도 노드를 **STM32F407VGT6** 베어메탈 펌웨어로 대체한 프로젝트. > 기존 서버측 PHP API / 서명 규약 / 요청 포맷을 **바이트 단위로 그대로 재현**하여 > 서버 무중단으로 RPi → STM32 전환을 목표로 한다. - 대상 MCU: STM32F407VGT6 (Cortex-M4F @168MHz, Flash 1MB, SRAM 192KB + CCM 64KB) - 네트워크: **유선 Ethernet** (LAN8720 RMII PHY) → LwIP + mbedTLS (HTTPS) - 런타임: **FreeRTOS** (네이티브 API — `task.h`/`queue.h`/`timers.h`/`semphr.h`) - 빌드: `arm-none-eabi-gcc` 15.2 + CMake + Ninja - 배포: **폐쇄망(air-gapped)** — 모든 서드파티 의존성은 `third_party/`에 벤더링, 빌드·런타임에 외부 다운로드 0건 상세 이식 근거는 [`../docs/stm32f407_migration_plan.md`](../docs/stm32f407_migration_plan.md) 참고. --- ## 1. 시스템 아키텍처 물리 센서에서 SMS 발송까지의 전체 경로. **굵게** 표시한 구간이 본 펌웨어가 대체하는 부분이다. ``` ┌──────────────┐ I2C1 ┌────────────────────────────┐ Ethernet(RMII) ┌──────────────┐ │ SHT30 센서 │ ──────────▶ │ STM32F407VGT6 │ ───────────────▶ │ LAN8720 PHY │ │ (온습도) │ PB6/PB7 │ (FreeRTOS 펌웨어) │ │ (외부 PHY) │ │ 0x44 │ (SCL/SDA) │ │ └──────┬───────┘ │ │ │ Task_Net : 링크/DHCP/SNTP │ │ UTP │ │ │ Task_Sensor: 5분 주기 측정 │ ▼ │ │ │ Task_Report: TLS POST │ ┌──────────────┐ └──────────────┘ │ Task_Watchdog: IWDG refresh │ │ 스위치/공유기 │ └────────────────────────────┘ └──────┬───────┘ │ lowercase_hex(sha256(API_KEY || raw_body)) X-Signature 헤더 │ HTTPS/TLS1.2 ▼ ┌────────────────────────────────────────────────────────┐ │ Cafe24 호스팅 (변경 없음) │ │ sensor_data.php ──▶ MySQL (로그 저장) │ │ │ │ │ └──▶ 서버 임계 판정(config.php METRIC_*) ──▶ SMS │ └────────────────────────────────────────────────────────┘ ``` - 펌웨어는 매 보고마다 `connect → TLS 핸드셰이크 → POST → close` 를 수행한다(메모리 절약, 보고 주기가 길어 성능 무관). - 서명은 본문 바이트 그대로 `sha256(API_KEY || body)` 를 계산해 **`X-Signature`** 헤더로 보낸다. 본문의 키 정렬/float 포맷 재현이 불필요하다(서버 `verify_signature_raw`). - **임계(고온/저온/고습/저습) 판정과 SMS 발송은 서버(PHP)에서 수행**한다. 펌웨어는 온습도 원값과 plausibility 상태(`metric_status`)만 보고하고, 경보 임계는 서버 `config.php` 의 `METRIC_*` 상수가 결정한다(폐쇄망 재플래시 비용을 피하기 위함). --- ## 2. 보드 구성 (SHT30 단일 보드) 이 프로젝트는 **SHT30 온습도 보드 단일 구성**이다. CMake `sht30_fw` 타깃이 `-DBOARD_SHT30` 을 자동으로 주입한다. | 빌드 매크로 | CMake 타깃 | 산출물 | sensor_id | device_id | 센서 | 동작 | |----------------|-----------|-----------------|-----------|------------------|---------------|--------------------------------------------| | `-DBOARD_SHT30`| `sht30_fw`| `sht30_fw.bin` | 2 | `stm32-sht30-01` | SHT30 온습도 | 주기(300s) I2C 측정 + 원값 보고 | 식별값 정의는 [`common/board_config.h`](common/board_config.h)(`BOARD_SHT30` 단일). 공통 튜너블은 [`common/app_config.h`](common/app_config.h). --- ## 3. 기능 패리티 (RPi 대비) | 기능 | RPi (Python) | STM32 펌웨어 | 상태 | |----------------------|----------------------------------|-------------------------------------------|-----------| | SHT30 측정/CRC | `smbus2` + `crc8_sht3x` | HAL I2C + `sht30_crc8`/`sht30_parse` | 유지 | | 주기 측정/보고 | 300s (최소 30s) | `APP_SHT30_REPORT_INTERVAL_SEC` (300s) | 유지 | | metric_status 판정 | `classify_reading()` plausibility | reporter 호출부 `metric_status` | 유지 | | 임계 경보/SMS | (서버측 처리) | **서버 `config.php` METRIC_* 에서 판정** | 유지(서버) | | HTTP 재시도 | requests + 루프(3회/5s) | `report_*` 내부 재시도(3회/5s) | 유지 | | TLS/HTTPS | OpenSSL(`requests`) | mbedTLS + LwIP | 변경(이식) | | 네트워크 | WiFi | 유선 Ethernet(LAN8720 RMII) | 변경(이식) | | 동시성 | Python threads | FreeRTOS tasks | 변경(이식) | | 시간원 | OS NTP | SNTP(LwIP) → RTC | 변경(이식) | | 서명 | 본문 필드 정규화 서명 | raw-body `X-Signature` | 변경(단순화)| | 프로세스 관리 | systemd 재시작 | IWDG 워치독 + 재연결 | 변경(이식) | | 로깅 | 로테이팅 파일 로그 | UART 콘솔 | 변경(축소) | > **임계 판정의 위치:** 펌웨어는 `temperature_c`/`humidity_percent` 원값과 `metric_status`(센서 물리/타당성 범위 기준 `normal`/`out_of_range`)만 보고한다. 고온/저온/고습/저습 경보와 SMS 트리거는 **서버 `config.php` 의 `METRIC_*` 임계**가 담당한다. 폐쇄망에서 임계값을 조정할 때 펌웨어 재플래시가 불필요하도록 의도된 분리다. 자세한 함수 단위 매핑은 [`docs/PORTING_NOTES.md`](docs/PORTING_NOTES.md) 참고. --- ## 4. 빠른 시작 (빌드/플래시) > 폐쇄망 전체 절차는 [`docs/BUILD_OFFLINE.md`](docs/BUILD_OFFLINE.md). 핀맵/배선은 [`docs/HARDWARE.md`](docs/HARDWARE.md). ```bash # 0) 비밀값 준비 (한 번) cp firmware/common/secrets.h.example firmware/common/secrets.h # secrets.h 의 APP_API_KEY 를 서버 php/config.php 의 API_KEY 와 동일하게 채운다. # 1) 서버 루트 CA 임베드 (한 번) # 실제 Cafe24 인증서 체인의 루트 CA PEM 으로 certs/server_ca.c 를 생성한다. # (certs/server_ca.h 가 SERVER_CA_PEM / SERVER_CA_PEM_LEN 을 extern 으로 선언함) # 2) 빌드 (sht30_fw 단일 타깃) cmake -S firmware -B firmware/build -G Ninja \ -DCMAKE_TOOLCHAIN_FILE=firmware/cmake/arm-none-eabi.cmake cmake --build firmware/build --target sht30_fw # → sht30_fw.bin/.hex/.elf # 3) 플래시 (ST-Link) — 보드 파라미터 없이 sht30_fw 고정 pwsh firmware/scripts/flash.ps1 # 대안: st-flash write firmware/build/sht30_fw.bin 0x08000000 # 대안: openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \ # -c "program sht30_fw.elf verify reset exit" # 4) 콘솔 확인 (USART3 PD8/PD9, 115200 8N1) # 부팅 로그 → 링크업/DHCP/SNTP → 첫 startup POST 200 확인 ``` > 참고: `cmake/`, `third_party/`, `board_sht30/`, `certs/server_ca.c` 는 별도 작업으로 생성/벤더링된다(아래 디렉터리 맵 참고). 위 명령은 그 산출물이 갖춰진 뒤의 표준 절차다. --- ## 5. `firmware/` 디렉터리 맵 ``` firmware/ ├─ README.md ← (이 문서) ├─ CMakeLists.txt 빌드 루트: sht30_fw 타깃, third_party 통합 ├─ cmake/ │ └─ arm-none-eabi.cmake 크로스 툴체인 파일 (gcc 15.2) ├─ scripts/ │ ├─ vendor.* 빌드 머신에서 고정 버전 의존성 벤더링(폐쇄망 준비) │ ├─ build.ps1 cmake configure + sht30_fw 빌드 │ └─ flash.ps1 sht30_fw 플래싱(보드 파라미터 없이 고정) ├─ common/ 공유 코드/계약 헤더 │ ├─ app_config.h 튜너블: API 호스트/포트/경로, 주기, 재시도, SNTP, 망주소, 워치독, 핀, SHT30 범위 │ ├─ board_config.h BOARD_DEVICE_ID/LOCATION/SENSOR_ID/SENSOR_NAME (BOARD_SHT30 단일) │ ├─ secrets.h.example APP_API_KEY 템플릿 (실 secrets.h 는 운영자가 생성, 미커밋) │ ├─ bsp.{h,c} 클럭/HAL/LED 초기화 (HSE 8MHz → 168MHz) │ ├─ net.{h,c} ETH MAC + LAN8720 RMII + LwIP, DHCP/static │ ├─ timesync.{h,c} SNTP → RTC │ ├─ tls.{h,c} mbedTLS over LwIP socket (transport_t 제공) │ ├─ transport.h 바이트 스트림 전송 추상화(connect/send/recv/close) │ ├─ httpapi.{h,c} HTTP/1.1 POST 작성 + X-Signature + 상태 파싱 (검증된 코어) │ ├─ sig.{h,c} raw-body 서명 sig_raw_body() (검증된 코어) │ ├─ jsonbody.{h,c} 본문 JSON 생성 jb_sht30_event (검증된 코어) │ ├─ sha256_backend.h SHA-256 추상 인터페이스 │ ├─ sha256_sw.c 자급식 SHA-256 (공개 도메인) (검증된 코어) │ ├─ hexutil.{h,c} 바이트 ↔ 소문자 hex (검증된 코어) │ ├─ sht30.h SHT30 I2C 드라이버 계약 │ ├─ sht30_convert.{h,c} SHT30 CRC8/변환 (이식성 순수 로직) (검증된 코어) │ ├─ reporter.h 이벤트 보고 헬퍼(본문→서명→POST→재시도) 계약 │ ├─ watchdog.h IWDG 계약 │ └─ applog.h UART 로그 계약 ├─ board_sht30/ SHT30 보드(sensor_id=2) 진입점 + 드라이버 │ ├─ main.c FreeRTOS 태스크 구성 + 부팅 │ ├─ app_sht30.c 주기 측정 → metric_status → 보고 │ └─ sht30.c HAL I2C 측정 ├─ certs/ │ ├─ server_ca.h SERVER_CA_PEM / SERVER_CA_PEM_LEN extern 선언 │ └─ server_ca.c 실제 Cafe24 루트 CA PEM (운영자가 교체) ← TODO(hw) ├─ third_party/ 벤더링(폐쇄망): CMSIS, STM32F4_HAL, FreeRTOS-Kernel, lwip, mbedtls ├─ test/host/ 호스트 빌드 패리티 테스트 (PHP 기대값 대조) │ ├─ parity_test.py │ ├─ php_verify.php │ └─ reference.py ├─ build/ CMake/Ninja 산출물 (armgate: 호스트 컴파일 게이트 .o) └─ docs/ ├─ HARDWARE.md SHT30 보드 핀맵/배선/핀 충돌 해소 ├─ BUILD_OFFLINE.md 폐쇄망 빌드·전달·플래시·롤백 절차 └─ PORTING_NOTES.md Python → C 매핑 / 동작 패리티 / 변경점 / TODO(hw) ``` > 위 맵 중 `CMakeLists.txt`, `cmake/`, `scripts/`, `board_sht30/`, `third_party/`, `certs/server_ca.c` 는 별도 작업으로 채워진다. 본 문서·`common/`·`certs/server_ca.h`·`test/`·`build/` 산출물은 이미 존재한다. --- ## 6. 핵심 제약 (반드시 인지) - **하드웨어 해시 가속기 없음**: STM32F407 에는 CRYP/HASH 블록이 없다(F415/F417 전용). SHA-256/AES 는 **소프트웨어**로 수행한다. RSA-2048 핸드셰이크는 ~1–2s 소요(보고 주기가 길어 허용). - **하드웨어 RNG 있음**: TLS 엔트로피 소스로 사용한다. - **SRAM 예산이 최대 리스크**: mbedTLS record buffer 를 `MBEDTLS_SSL_IN/OUT_CONTENT_LEN ≈ 4096` 으로 축소하고, FreeRTOS 태스크 스택을 CCM(64KB)에 배치한다(linker script). - **폐쇄망**: 빌드·설치·런타임 모두 인터넷 접근 불가를 가정한다. 모든 의존성·인증서·SNTP 대상 IP 까지 사전에 준비한다(자세히 [`docs/BUILD_OFFLINE.md`](docs/BUILD_OFFLINE.md)).