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 제외.
250 lines
10 KiB
C
250 lines
10 KiB
C
/* =============================================================================
|
|
* bsp.c - 보드 지원 패키지 (클럭/HAL/공통 주변장치 초기화) [STM32F407VGT6]
|
|
*
|
|
* 책임:
|
|
* - HAL_Init() 및 SystemClock_Config(): HSE 8MHz -> PLL -> SYSCLK 168MHz,
|
|
* AHB 168MHz, APB1 42MHz, APB2 84MHz, Flash 5WS, PWR/VOS Scale1.
|
|
* - 공통 GPIO RCC 클럭 게이팅 + 상태 LED.
|
|
* - bsp_led_set/toggle, bsp_fatal(IRQ 차단 + 고속 점멸 + 워치독 미갱신 → 리셋).
|
|
* - HAL 타임베이스를 SysTick 이 아닌 TIM6 으로 둬서 FreeRTOS 가 SysTick 을
|
|
* 단독으로 소유하게 한다(HAL_InitTick / TIM6_DAC_IRQHandler → HAL_IncTick).
|
|
*
|
|
* 참고: SystemClock_Config 값은 STM32F407 + 8MHz HSE(예: STM32F4-Discovery,
|
|
* 또는 LAN8720 RMII 보드의 25MHz 크리스털을 8MHz 로 분주 공급하는 구성)
|
|
* 기준의 표준 168MHz 설정이다. HSE 주파수가 다르면 PLLM 을 조정한다.
|
|
* ===========================================================================*/
|
|
#include "bsp.h"
|
|
#include "app_config.h"
|
|
|
|
#include "stm32f4xx_hal.h"
|
|
|
|
/* ── 상태 LED 핀 ───────────────────────────────────────────────────────────
|
|
* TODO(hw): 보드에 맞게 조정. 기본값은 STM32F4-Discovery 의 녹색 LED(PD12).
|
|
* - Discovery 계열: PD12(녹), PD13(주황), PD14(빨), PD15(파)
|
|
* - Nucleo-F4 계열 : PA5 (LD2) 로 바꿀 것
|
|
* 여기서는 단일 "상태 LED" 만 운영 가시성 용도로 사용한다.
|
|
*/
|
|
#define BSP_LED_PORT GPIOD
|
|
#define BSP_LED_PIN GPIO_PIN_12
|
|
#define BSP_LED_GPIO_CLK_EN() __HAL_RCC_GPIOD_CLK_ENABLE()
|
|
|
|
/* bsp_fatal() 고속 점멸 주기(루프 카운트). 정확한 타이밍은 불필요(곧 리셋됨). */
|
|
#define BSP_FATAL_BLINK_BUSYLOOP 400000u
|
|
|
|
/* ── HAL 타임베이스 전용 타이머 핸들 (TIM6) ───────────────────────────────── */
|
|
static TIM_HandleTypeDef htim6;
|
|
|
|
/* =============================================================================
|
|
* SystemClock_Config — HSE 8MHz -> 168MHz
|
|
* PLL: VCO_in = HSE/PLLM = 8/8 = 1MHz
|
|
* VCO_out = 1MHz * PLLN(336) = 336MHz
|
|
* SYSCLK = VCO_out / PLLP(2) = 168MHz
|
|
* PLLQ(7) -> 48MHz (USB/SDIO/RNG 클럭)
|
|
* AHB=/1(168), APB1=/4(42), APB2=/2(84), Flash latency 5WS.
|
|
* ===========================================================================*/
|
|
static void SystemClock_Config(void)
|
|
{
|
|
RCC_OscInitTypeDef osc = {0};
|
|
RCC_ClkInitTypeDef clk = {0};
|
|
|
|
/* 전압 레귤레이터 클럭 게이팅 + Scale1 (168MHz 동작에 필수). */
|
|
__HAL_RCC_PWR_CLK_ENABLE();
|
|
__HAL_PWR_VOLTAGE_SCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
|
|
|
|
/* HSE -> PLL */
|
|
osc.OscillatorType = RCC_OSCILLATORTYPE_HSE;
|
|
osc.HSEState = RCC_HSE_ON;
|
|
osc.PLL.PLLState = RCC_PLL_ON;
|
|
osc.PLL.PLLSource = RCC_PLLSOURCE_HSE;
|
|
osc.PLL.PLLM = 8; /* 8MHz HSE -> 1MHz VCO 입력 */
|
|
osc.PLL.PLLN = 336; /* -> 336MHz VCO 출력 */
|
|
osc.PLL.PLLP = RCC_PLLP_DIV2; /* -> 168MHz SYSCLK */
|
|
osc.PLL.PLLQ = 7; /* -> 48MHz (RNG/USB) */
|
|
if (HAL_RCC_OscConfig(&osc) != HAL_OK) {
|
|
bsp_fatal("RCC_OscConfig");
|
|
}
|
|
|
|
/* 버스 클럭: SYSCLK=PLL, AHB=/1, APB1=/4, APB2=/2. Flash 5WS. */
|
|
clk.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK |
|
|
RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
|
|
clk.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
|
|
clk.AHBCLKDivider = RCC_SYSCLK_DIV1; /* HCLK = 168MHz */
|
|
clk.APB1CLKDivider = RCC_HCLK_DIV4; /* PCLK1 = 42MHz */
|
|
clk.APB2CLKDivider = RCC_HCLK_DIV2; /* PCLK2 = 84MHz */
|
|
if (HAL_RCC_ClockConfig(&clk, FLASH_LATENCY_5) != HAL_OK) {
|
|
bsp_fatal("RCC_ClockConfig");
|
|
}
|
|
}
|
|
|
|
/* ── 공통 GPIO 클럭 게이팅 ──────────────────────────────────────────────────
|
|
* RMII(LAN8720)/I2C/UART 등 다른 모듈이 자기 핀을 켜기 전에 흔히 쓰는 포트들을
|
|
* 미리 enable 해 둔다(중복 enable 은 무해). 각 드라이버가 자기 MspInit 에서
|
|
* 추가로 켜도 된다.
|
|
*/
|
|
static void bsp_gpio_clocks_init(void)
|
|
{
|
|
__HAL_RCC_GPIOA_CLK_ENABLE(); /* RMII: PA1/PA2/PA7 */
|
|
__HAL_RCC_GPIOB_CLK_ENABLE(); /* RMII: PB11/PB12/PB13 · SHT30 I2C1: PB6/PB7 */
|
|
__HAL_RCC_GPIOC_CLK_ENABLE(); /* RMII: PC1/PC4/PC5 */
|
|
__HAL_RCC_GPIOD_CLK_ENABLE(); /* 로그 UART USART3: PD8/PD9 · 상태 LED: PD12 */
|
|
}
|
|
|
|
/* ── 상태 LED 초기화 ────────────────────────────────────────────────────── */
|
|
static void bsp_led_init(void)
|
|
{
|
|
GPIO_InitTypeDef g = {0};
|
|
BSP_LED_GPIO_CLK_EN();
|
|
g.Pin = BSP_LED_PIN;
|
|
g.Mode = GPIO_MODE_OUTPUT_PP;
|
|
g.Pull = GPIO_NOPULL;
|
|
g.Speed = GPIO_SPEED_FREQ_LOW;
|
|
HAL_GPIO_Init(BSP_LED_PORT, &g);
|
|
HAL_GPIO_WritePin(BSP_LED_PORT, BSP_LED_PIN, GPIO_PIN_RESET);
|
|
}
|
|
|
|
/* =============================================================================
|
|
* 공개 API
|
|
* ===========================================================================*/
|
|
void bsp_init(void)
|
|
{
|
|
HAL_Init(); /* NVIC 그룹/Flash prefetch/기본 타임베이스 설정 */
|
|
SystemClock_Config(); /* 168MHz */
|
|
bsp_gpio_clocks_init();
|
|
bsp_led_init();
|
|
}
|
|
|
|
void bsp_led_set(int on)
|
|
{
|
|
HAL_GPIO_WritePin(BSP_LED_PORT, BSP_LED_PIN,
|
|
on ? GPIO_PIN_SET : GPIO_PIN_RESET);
|
|
}
|
|
|
|
void bsp_led_toggle(void)
|
|
{
|
|
HAL_GPIO_TogglePin(BSP_LED_PORT, BSP_LED_PIN);
|
|
}
|
|
|
|
void bsp_fatal(const char *reason)
|
|
{
|
|
(void)reason; /* 로그는 호출측 책임. 여기선 안전 정지만 한다. */
|
|
|
|
/* 인터럽트 전면 차단: 더 이상 어떤 태스크/ISR 도 실행되지 않게 한다.
|
|
* 이 함수는 워치독을 갱신하지 않으므로 IWDG 가 곧 MCU 를 리셋한다. */
|
|
__disable_irq();
|
|
|
|
for (;;) {
|
|
bsp_led_toggle();
|
|
for (volatile uint32_t i = 0; i < BSP_FATAL_BLINK_BUSYLOOP; ++i) {
|
|
__NOP();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* =============================================================================
|
|
* bsp_rand32 — 하드웨어 RNG 기반 32비트 난수 (강한 심볼)
|
|
*
|
|
* lwipopts.h 의 LWIP_RAND() 백엔드. net.c 의 약한(weak) 예측 가능 xorshift 대체
|
|
* 구현을 이 강한 심볼이 덮어쓴다. TCP 초기 시퀀스 번호 / DHCP·DNS 트랜잭션 ID 의
|
|
* 예측 가능성을 줄인다.
|
|
*
|
|
* 주의: RNG 주변장치는 TLS(tls_mbedtls.c)도 사용한다. 서로 다른 HAL 핸들이지만
|
|
* 둘 다 RNG->DR 을 읽기만 하므로 공존 가능하다(드물게 상관된 값이 나올 수 있으나
|
|
* lwip 용도에는 충분). RNG 클럭 소스는 PLLQ=48MHz (SystemClock_Config 참조).
|
|
* RNG 불가 시 마지막 수단으로 틱 혼합값을 반환한다(예측 가능 — 정상 동작에선 미사용).
|
|
* ===========================================================================*/
|
|
static RNG_HandleTypeDef s_bsp_rng;
|
|
static int s_bsp_rng_ready = 0;
|
|
|
|
uint32_t bsp_rand32(void)
|
|
{
|
|
if (!s_bsp_rng_ready) {
|
|
__HAL_RCC_RNG_CLK_ENABLE();
|
|
s_bsp_rng.Instance = RNG;
|
|
if (HAL_RNG_Init(&s_bsp_rng) != HAL_OK) {
|
|
return 0x9E3779B9u ^ (uint32_t)HAL_GetTick(); /* RNG 불가: 폴백 */
|
|
}
|
|
s_bsp_rng_ready = 1;
|
|
}
|
|
|
|
uint32_t v = 0;
|
|
if (HAL_RNG_GenerateRandomNumber(&s_bsp_rng, &v) != HAL_OK) {
|
|
return 0x9E3779B9u ^ (uint32_t)HAL_GetTick(); /* 일시 오류: 폴백 */
|
|
}
|
|
return v;
|
|
}
|
|
|
|
/* =============================================================================
|
|
* HAL 타임베이스 오버라이드 (TIM6)
|
|
*
|
|
* 기본 HAL 은 SysTick 을 타임베이스로 쓴다. 그러나 FreeRTOS 도 SysTick 을
|
|
* 스케줄러 틱으로 사용하므로 충돌한다. 아래 약(weak) 함수를 재정의하여 HAL
|
|
* 타임베이스를 TIM6(기본 우선순위) 으로 옮긴다. FreeRTOS 는 SysTick 을 단독
|
|
* 소유하고, HAL_GetTick()/HAL_Delay() 는 TIM6 인터럽트로 동작한다.
|
|
*
|
|
* HAL_Init() 내부에서 TICK_INT_PRIORITY 로 HAL_InitTick() 이 호출된다.
|
|
* ===========================================================================*/
|
|
HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
|
|
{
|
|
RCC_ClkInitTypeDef clkcfg;
|
|
uint32_t flashLatency;
|
|
uint32_t pclk1;
|
|
uint32_t tim_clk;
|
|
uint32_t prescaler;
|
|
|
|
/* TIM6 클럭 = APB1 타이머 클럭. APB1 분주(>1)면 타이머 클럭은 PCLK1 x2. */
|
|
HAL_RCC_GetClockConfig(&clkcfg, &flashLatency);
|
|
pclk1 = HAL_RCC_GetPCLK1Freq();
|
|
if (clkcfg.APB1CLKDivider == RCC_HCLK_DIV1) {
|
|
tim_clk = pclk1;
|
|
} else {
|
|
tim_clk = pclk1 * 2u; /* APB1 prescaler != 1 → 타이머 클럭 2배 (84MHz) */
|
|
}
|
|
|
|
/* 1MHz(=1us) 카운팅이 되도록 프리스케일러 계산 → 1ms 마다 업데이트 IRQ. */
|
|
prescaler = (tim_clk / 1000000u) - 1u;
|
|
|
|
__HAL_RCC_TIM6_CLK_ENABLE();
|
|
|
|
htim6.Instance = TIM6;
|
|
htim6.Init.Prescaler = prescaler; /* → 1MHz */
|
|
htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
|
|
htim6.Init.Period = 1000u - 1u; /* 1000 카운트 = 1ms */
|
|
htim6.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
|
|
htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
|
|
if (HAL_TIM_Base_Init(&htim6) != HAL_OK) {
|
|
return HAL_ERROR;
|
|
}
|
|
|
|
/* TIM6 업데이트 인터럽트 활성화. uwTickPrio 는 HAL 내부에서 참조됨. */
|
|
if (TickPriority < (1uL << __NVIC_PRIO_BITS)) {
|
|
HAL_NVIC_SetPriority(TIM6_DAC_IRQn, TickPriority, 0);
|
|
uwTickPrio = TickPriority;
|
|
} else {
|
|
return HAL_ERROR;
|
|
}
|
|
HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);
|
|
|
|
return HAL_TIM_Base_Start_IT(&htim6);
|
|
}
|
|
|
|
/* HAL_SuspendTick/ResumeTick 도 TIM6 기준으로 재정의(HAL_Delay 등 일관성). */
|
|
void HAL_SuspendTick(void)
|
|
{
|
|
__HAL_TIM_DISABLE_IT(&htim6, TIM_IT_UPDATE);
|
|
}
|
|
|
|
void HAL_ResumeTick(void)
|
|
{
|
|
__HAL_TIM_ENABLE_IT(&htim6, TIM_IT_UPDATE);
|
|
}
|
|
|
|
/* TIM6 업데이트 인터럽트 → HAL 틱 증가.
|
|
* (startup_stm32f407xx.s 의 벡터 이름과 일치해야 한다: TIM6_DAC_IRQHandler) */
|
|
void TIM6_DAC_IRQHandler(void)
|
|
{
|
|
if (__HAL_TIM_GET_FLAG(&htim6, TIM_FLAG_UPDATE) != RESET &&
|
|
__HAL_TIM_GET_IT_SOURCE(&htim6, TIM_IT_UPDATE) != RESET) {
|
|
__HAL_TIM_CLEAR_IT(&htim6, TIM_IT_UPDATE);
|
|
HAL_IncTick();
|
|
}
|
|
}
|