/* ============================================================================= * 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(); } }