POSA_LEAKSMS/firmware/docs/HARDWARE.md
유창욱 90f121e14c chore: import codebase with security hardening
SHT30 온습도 모니터링 시스템 전체 소스(서버 PHP, STM32 펌웨어, SQL, 테스트).
전체 코드리뷰에서 도출된 보안 하드닝 10건 반영:
- 요청 서명 HMAC-SHA256 전환(펌웨어 sig.c/서버 config.php/호스트 패리티 동시)
- 재전송 방어 + 기본 API_KEY fail-closed + 디바이스 문자열 정제(api/sensor_data.php)
- 오프라인 SMS 중복 발송 경합 제거(cron_heartbeat.php, 원자적 선점)
- CSV 수식 주입 방지(monthly_report.php), 감사로그 회전 락(retention_cleanup.php)
- 브루트포스 카운터 원자화(login.php), 예시 TOTP 비밀키 무효화, 마이그레이션 멱등화

_backup/(하드코딩 실 비밀값 포함)·config.local.php·런타임 상태는 .gitignore 제외.
2026-06-20 09:37:40 +09:00

222 lines
12 KiB
Markdown

# HARDWARE.md — STM32F407VGT6 핀맵 & 배선 (SHT30 단일 보드)
대상 MCU: **STM32F407VGT6** (LQFP-100), Cortex-M4F @168MHz.
권장 기준 보드: STM32F4-DISCOVERY 계열 또는 동등한 커스텀 PCB. 핀은 LQFP-100 패키지 기준.
이 문서는 **SHT30 온습도 보드**의 핀 배치, 외부 배선, 전원, 클럭원, 그리고 반드시 해소해야 하는
**핀 충돌(USART2 ↔ LAN8720 MDIO, 둘 다 PA2)** 을 다룬다.
> 사이트/하드웨어 결정이 필요한 항목은 `TODO(hw):` 로 표시했다. 코드는 합리적 기본값으로 컴파일된다.
---
## 0. ⚠️ PA2 핀 충돌 (USART2 TX vs. LAN8720 MDIO) — ✅ 코드에서 해소 완료
**문제(배경).** 초기에는 디버그 로그 UART 가 **USART2 (PA2=TX / PA3=RX)** 로 설계되었으나(ST-Link VCP
호환), LAN8720 RMII 연결에서 **MDIO 신호 역시 PA2 (`ETH_MDIO`, AF11)** 를 사용한다. PA2 를 두 기능이
동시에 점유할 수 없고, RMII 동작에 MDIO(PHY 레지스터 설정/링크 폴링)가 필수이므로 PA2 는 반드시
`ETH_MDIO` 여야 한다.
**✅ 해소(코드 반영됨).** 로그 UART 를 **USART3 (PD8=TX / PD9=RX, AF7)** 로 이동했다. 반영 위치:
- [`common/applog.c`](../common/applog.c): `HAL_UART_MspInit`/`applog_init` 가 USART3 + GPIOD(PD8/PD9, AF7)로 초기화.
- [`common/app_config.h`](../common/app_config.h): `APP_LOG_UART_BAUD` 주석을 USART3 PD8/PD9 로 정정.
PA2 는 `ETH_MDIO` 전용이며, 코드에는 USART2 사용처가 더 이상 없다.
### 권장 해소안 (택일)
| 방안 | 로그 UART 핀 | AF | ST-Link VCP | 권장도 | 비고 |
|------|-------------|----|-------------|--------|------|
| **A. USART3 → PD8(TX)/PD9(RX)** | PD8/PD9 | AF7 | 별도 USB-UART 어댑터 필요 | **★ 권장** | RMII/I2C 와 충돌 없음. PD 포트는 본 설계에서 비어 있음 |
| B. USART1 → PA9(TX)/PA10(RX) | PA9/PA10 | AF7 | 별도 어댑터 | 차선 | PA9/PA10 미사용. 단, PA9 는 USB-OTG VBUS 와 보드에 따라 충돌 가능 — 디스커버리 보드 확인 |
| C. USART2 유지 + RMII 포기 | PA2/PA3 | — | VCP 사용 | ✗ 불가 | 이더넷이 필수이므로 채택 불가 |
**채택: 방안 A — 로그 UART = USART3 (PD8 TX / PD9 RX, AF7, 115200 8N1).**
USART3 의 다른 핀쌍(PB10/PB11, PC10/PC11)은 RMII(PB11=TXEN)·SHT30 I2C 와 겹치므로 **PD8/PD9 를 사용한다.**
> **상태:**
> 1. ✅ 코드 반영됨: `applog.c` 가 USART3 + GPIOD(PD8/PD9, AF7)로 초기화, `app_config.h` 주석 정정.
> 2. ⬜ `TODO(hw)`: ST-Link VCP(PA2/PA3)를 못 쓰므로 운영 로그는 **외부 3.3V USB-UART 어댑터를 PD8/PD9 에 연결**.
> 본 문서의 모든 표는 **방안 A(USART3 PD8/PD9)** 기준이다.
---
## 1. LAN8720 Ethernet PHY (RMII)
STM32 내장 ETH MAC + 외부 LAN8720 PHY, **RMII 모드**. 모든 RMII 핀은 대체기능 **AF11 (`ETH`)**.
| STM32 핀 | 신호 (RMII) | AF | LAN8720 핀 | 방향(MCU 기준) | 비고 |
|----------|--------------------|------|-----------|----------------|------|
| **PA1** | `ETH_REF_CLK` | AF11 | nINT/REFCLKO | 입력 | 50MHz 기준 클럭 입력 (§1.1) |
| **PA2** | `ETH_MDIO` | AF11 | MDIO | 양방향 | ⚠️ §0 충돌 핀 — USART2 금지 |
| **PC1** | `ETH_MDC` | AF11 | MDC | 출력 | 관리 클럭 |
| **PA7** | `ETH_CRS_DV` | AF11 | CRS_DV | 입력 | 캐리어 감지/데이터 유효 |
| **PC4** | `ETH_RXD0` | AF11 | RXD0 | 입력 | 수신 데이터 0 |
| **PC5** | `ETH_RXD1` | AF11 | RXD1 | 입력 | 수신 데이터 1 |
| **PB11** | `ETH_TX_EN` | AF11 | TXEN | 출력 | 송신 인에이블 |
| **PB12** | `ETH_TXD0` | AF11 | TXD0 | 출력 | 송신 데이터 0 |
| **PB13** | `ETH_TXD1` | AF11 | TXD1 | 출력 | 송신 데이터 1 |
추가 배선(RMII 표준):
| LAN8720 핀 | 연결 | 비고 |
|-----------|------|------|
| MDIO 풀업 | 1.5kΩ → 3.3V | 권장 |
| nRST | MCU GPIO 또는 RC 리셋 | `TODO(hw):` 소프트 리셋 핀 배정 시 GPIO 1개 추가(예 PE0). 미배정 시 전원-온 RC 리셋 |
| 25MHz XTAL / OSC | §1.1 클럭 구성에 따라 | |
| RXER | (선택) 미사용 가능 | RMII 에서 생략 가능 |
> RMII 는 RXD2/RXD3/TXD2/TXD3/COL/CRS/RX_CLK/TX_CLK 가 **없다**(MII 대비 핀 절감). 위 9개 + MDC/MDIO 가 전부다.
### 1.1 RMII 50MHz REF_CLK 소싱 (중요)
RMII 는 MAC 과 PHY 가 **공통 50MHz REF_CLK** 를 공유해야 한다. PA1(`ETH_REF_CLK`)은 MCU **입력**이며,
이 50MHz 를 어디서 만들지가 보드 설계의 핵심이다. 두 가지 표준 구성:
1. **PHY 가 50MHz 생성 → MCU 로 공급 (권장, 디스커버리 보드 방식)**
- LAN8720 에 25MHz 크리스털을 달고, PHY 내부에서 50MHz 를 만들어 `REFCLKO` → MCU `PA1` 으로 공급.
- 보드에 따라 MCU `MCO1(PA8)` 로 25MHz 를 PHY 에 주고 PHY 가 50MHz 를 되돌리는 변형도 있음.
2. **외부 50MHz 오실레이터 → MCU PA1 + PHY 동시 공급**
- 50MHz 캔 오실레이터 1개로 두 칩에 동시 분배. 가장 단순하고 안정적.
> `TODO(hw):` 보드 설계에 맞춰 1) 또는 2) 중 하나를 확정하고 BOM/스템핑을 결정한다.
> 펌웨어 측 RCC `RMII` 클럭 선택(`SYSCFG->PMC.MII_RMII_SEL=RMII`)은 [`common/net.c`](../common/net.c)/`ethernetif.c`
> 초기화에서 처리한다(코드 기본값 = RMII).
---
## 2. SHT30 온습도 센서 (`-DBOARD_SHT30`, sensor_id=2)
Sensirion SHT30 (한진데이터 P4422-3 모듈), I2C. RPi `sht30_monitor.py` 와 동일 규약.
### 2.1 I2C 배선
| STM32 핀 | 신호 | AF | I2C | 비고 |
|----------|------|----|-----|------|
| **PB6** | `I2C1_SCL` | AF4 | I2C1 | 오픈드레인, 외부 풀업 필요 |
| **PB7** | `I2C1_SDA` | AF4 | I2C1 | 오픈드레인, 외부 풀업 필요 |
- 7-bit 주소: **0x44** (`APP_SHT30_I2C_ADDR`). HAL 호출 시 `0x44<<1`.
- 명령: `0x2C06` (high-repeatability, clock-stretch off) → ~20ms 대기 → 6바이트 read.
- 풀업: SCL/SDA 각각 **4.7kΩ → 3.3V** (모듈에 내장 풀업이 있으면 생략). I2C 속도 100kHz(Standard) 권장.
- plausibility 범위(벗어나면 `metric_status="out_of_range"`): T `[-40, 125]°C`, RH `[0, 100]%` ([`common/app_config.h`](../common/app_config.h)).
운영 경보 임계(고온/저온/고습/저습 → SMS)는 **서버 `php/config.php``METRIC_*` 상수**가 판정한다(펌웨어는 원값만 보고).
```
SHT30 모듈 STM32
┌────────┐
│ VDD ──┼────────────── 3.3V ──┬──[4.7kΩ]──┐ ┌──[4.7kΩ]── 3.3V
│ SCL ──┼────────────── PB6 ───┘ │ │
│ SDA ──┼────────────── PB7 ────────────────┼───┘
│ GND ──┼────────────── GND │
│ ADDR ─┼── GND (주소 0x44; VDD 면 0x45) │
└────────┘
```
> `TODO(hw):` ADDR 핀 결선으로 0x44(ADDR→GND) / 0x45(ADDR→VDD) 가 결정된다. 본 설계는 **0x44** 고정.
### 2.2 SHT30 보드 핀 요약
| 핀 | 용도 |
|----|------|
| PB6 / PB7 | I2C1 SCL / SDA (0x44) |
| (RMII 9핀) | §1 표 그대로 — **단 PB11/PB12/PB13 가 RMII 에 쓰임에 주의** |
| PD8/PD9 | 로그 UART (USART3, §0 방안 A) |
| 상태 LED | §3 |
> 주의: I2C1 은 PB6/PB7 를 쓰고 RMII 는 PB11/PB12/PB13 을 쓴다 — **PB 포트 내부에서 충돌 없음**.
---
## 3. 공통 주변장치 — 상태 LED, 클럭원, 워치독/RTC, 전원
### 3.1 상태 LED ([`common/bsp.h`](../common/bsp.h) `bsp_led_*`)
| 핀 | 용도 | 비고 |
|----|------|------|
| **PD12** | 상태 LED (녹색) | `TODO(hw):` 디스커버리 보드 온보드 LED(PD12~PD15) 사용 가정. 커스텀 PCB 면 임의 GPIO 1개로 변경 |
권장 점멸 패턴(운영 가시성): 부팅=점등 / 정상 보고=느린 토글 / 망 단절·TLS 실패=빠른 점멸 / `bsp_fatal()`=고속 점멸.
### 3.2 시스템 클럭 / 오실레이터
| 항목 | 값 | 비고 |
|------|----|------|
| HSE | **8MHz 크리스털** | `SystemClock_Config()` 가 PLL 로 168MHz 생성([`common/bsp.h`](../common/bsp.h) 주석) |
| SYSCLK | 168MHz | Cortex-M4F |
| RMII REF_CLK | 50MHz | §1.1 (HSE 와 별개 경로) |
> `TODO(hw):` 보드에 HSE 8MHz 크리스털 + 부하 커패시터 실장 확인. 없으면 `SystemClock_Config` 의 HSE 값 정정 필요.
### 3.3 RTC 클럭원 (SNTP → RTC 시간 보존)
| 항목 | 권장 | 비고 |
|------|------|------|
| RTC 클럭원 | **LSE 32.768kHz 크리스털** | TLS 인증서 유효기간 검증 + `timestamp` 필드용. 정전 시 VBAT 로 시간 보존 |
| 대안 | LSI(~32kHz, 내장) | 정밀도 낮음. SNTP 로 주기 보정하면 허용. **VBAT 백업 불가** |
| VBAT | 코인셀(CR2032) 또는 VBAT→3.3V | LSE + 백업 도메인 유지용. 미실장 시 매 부팅 SNTP 재동기 필요 |
> `TODO(hw):` LSE 크리스털 + VBAT 백업 실장 여부 확정. 미실장이면 [`common/timesync.c`](../common/timesync.c) 는 매 부팅 SNTP 동기에 의존(이식 계획 R4: SNTP 실패 시 경보).
### 3.4 워치독 (IWDG)
| 항목 | 값 | 비고 |
|------|----|------|
| IWDG 클럭원 | **LSI (~32kHz, 내장)** | IWDG 는 항상 LSI 구동(별도 핀 없음) |
| 타임아웃 | `APP_WATCHDOG_TIMEOUT_MS = 20000` (20s) | 이 시간 내 `watchdog_refresh()` 없으면 MCU 리셋([`common/watchdog.h`](../common/watchdog.h)) |
| BOR | 활성 권장 | 브라운아웃 리셋(이식 계획 Phase 7) — `TODO(hw):` 옵션 바이트로 BOR 레벨 설정 |
### 3.5 전원
| 레일 | 용도 | 비고 |
|------|------|------|
| 3.3V | MCU VDD/VDDA, LAN8720, SHT30, 풀업 | LAN8720 RMII I/O 는 3.3V. PHY 전류 여유(~수십 mA) 확보 |
| VDDA | ADC/PLL 기준 | 0.1µF + 1µF 디커플링, 페라이트 비드 권장 |
| VBAT | RTC 백업(§3.3) | 코인셀/점퍼 |
| 디커플링 | 각 VDD 핀당 0.1µF + 벌크 4.7µF | 표준 STM32 권장 |
> `TODO(hw):` PoE/외부 12V→3.3V 등 실제 급전 방식은 설치 환경에 따라 확정. RPi 가 쓰던 5V USB 어댑터 재사용 가능(5V→3.3V 레귤레이터 추가).
---
## 4. 최종 권장 핀맵 (전체 통합)
> §0 방안 A(로그 UART = USART3 PD8/PD9) 채택을 가정한 **최종 권장** 핀맵. 충돌 해소 완료 상태.
| STM32 핀 | SHT30 보드 | 기능 | AF | 충돌 여부 |
|----------|------------|------|----|-----------|
| PA1 | ✔ | ETH_REF_CLK (RMII 50MHz in) | AF11 | OK |
| PA2 | ✔ | **ETH_MDIO** (USART2 금지) | AF11 | **§0 해소** |
| PA7 | ✔ | ETH_CRS_DV | AF11 | OK |
| PB6 | ✔ | I2C1_SCL | AF4 | OK |
| PB7 | ✔ | I2C1_SDA | AF4 | OK |
| PB11 | ✔ | ETH_TX_EN | AF11 | OK |
| PB12 | ✔ | ETH_TXD0 | AF11 | OK |
| PB13 | ✔ | ETH_TXD1 | AF11 | OK |
| PC1 | ✔ | ETH_MDC | AF11 | OK |
| PC4 | ✔ | ETH_RXD0 | AF11 | OK |
| PC5 | ✔ | ETH_RXD1 | AF11 | OK |
| PD8 | ✔ | USART3_TX (로그) | AF7 | OK (방안 A) |
| PD9 | ✔ | USART3_RX (로그) | AF7 | OK (방안 A) |
| PD12 | ✔ | 상태 LED | — | OK |
| OSC_IN/OUT | ✔ | HSE 8MHz | — | OK |
| PC14/PC15 | ✔ | LSE 32.768kHz (RTC, 선택) | — | §3.3 |
| VBAT | ✔ | RTC 백업 전원(선택) | — | §3.3 |
**미사용/주의:** PA2 는 **절대 USART2_TX 로 설정하지 말 것**(이더넷 MDIO 전용). PA3 도 USART2_RX 로 쓰지 않는다.
---
## 5. 핀 충돌 점검표 (요약)
| 잠재 충돌 | 상태 | 해소 |
|-----------|------|------|
| **PA2: USART2_TX ↔ ETH_MDIO** | ✅ 해소(코드) | 로그 UART 를 USART3(PD8/PD9)로 이동(`applog.c`). PA2=ETH_MDIO 고정 |
| USART3 PB10/PB11 ↔ ETH_TX_EN(PB11) | 회피됨 | USART3 를 PD8/PD9 핀쌍으로 사용 |
| USART3 PC10/PC11 ↔ ETH RXD(PC4/PC5) | 회피됨 | 동일(PD8/PD9 사용) |
| 로그 UART(PD8/PD9) ↔ 상태 LED(PD12) | 없음 | 같은 D 포트, 다른 핀 |
| I2C1(PB6/PB7) ↔ RMII(PB11~13) | 없음 | PB 포트 내 핀 분리 |
| RMII 클럭 게이팅(`bsp.c`) | 무해 | `bsp.c` 가 공통 클럭으로 GPIOA/B/C/D 를 켬. ETH MspInit 도 GPIOA/B/C 를 자체 enable(중복 무해) |
> 결론: 코드 기준으로 **남은 핀 충돌은 없다.** PA2 충돌은 로그 UART 를 USART3 PD8/PD9 로 옮겨 해소했고
> (코드 반영 완료), 나머지는 모두 포트 내 핀 분리로 충돌이 없다. 남은 항목은 §0의 외부 USB-UART 어댑터 배선
> 과 §3 의 클럭원/전원 `TODO(hw)` 뿐이다.