지노랩 /JinoLab

STM32Cube HAL의 타임베이스 소스를 Systick → TIM6으로 변경하기 본문

임베디드 시스템/RTOS

STM32Cube HAL의 타임베이스 소스를 Systick → TIM6으로 변경하기

지노랩/JinoLab 2025. 6. 10. 11:54

이전 강의까지 FreeRTOS 커널을 프로젝트에 성공적으로 통합했지만, STM32Cube HAL과 FreeRTOS가 동시에 SysTick 타이머를 타임베이스(timer tick)로 사용할 경우 충돌이 발생할 수 있습니다.
따라서, HAL은 SysTick 대신 TIM6(Timer 6)을 타임베이스 소스로, FreeRTOS는 원래대로 SysTick을 사용하도록 설정해야 합니다.

아래 단계대로 진행하면 됩니다.


1. 왜 타임베이스 소스를 변경해야 하는가?

  • FreeRTOS
    • 기본적으로 ARM Cortex-M 계열 프로세서의 내장 SysTick 타이머를 “RTOS 틱”으로 이용하여
      • vTaskDelay() 처리,
      • 주기적 스케줄러 실행(시스템 틱 발생 시)
        등 다양한 시간 기반 기능을 수행합니다.
  • STM32Cube HAL(HAL_Delay(), HAL_GetTick(), Timeout 처리 등)
    • HAL 라이브러리 또한 기본적으로 “SysTick 타이머”를 사용해 시스템 틱을 생성하고,
      HAL_Delay(), HAL_GetTick() 함수나
      통신 모듈(예: UART 통신에서 Rx 타임아웃, SPI 타임아웃 등)
      타임아웃 처리에 SysTick을 사용합니다.

🚨 문제:
두 개의 서로 다른 모듈(FreeRTOS와 HAL)이 동시에 SysTick을 타임베이스로 쓰면,

  • FreeRTOS가 틱 인터럽트를 잡아서 스케줄러를 돌려야 할 때,
  • HAL도 “1ms마다 tick 증가”를 원하지만, 이미 FreeRTOS가 SysTick을 독점하고 있으므로 HAL의 지연 함수(HAL_Delay())나 타임아웃 기능이 오작동합니다.

따라서, HAL 쪽 타이머 소스를 TIM6(또는 다른 일반 타이머)으로 변경하여,
FreeRTOS가 SysTick을 그대로 사용하도록 두고, HAL만 별도의 타이머로 시간을 관리하게 해야 서로 충돌이 나지 않습니다.


2. STM32CubeMX 설정에서 타임베이스 소스 변경하기

  1. .ioc 파일 열기
    • 프로젝트 루트에서 MyProject.ioc (STM32CubeMX 설정 파일)을 더블클릭해 엽니다.
  2. 좌측 메뉴에서 “System Core → SYS” 선택
    • 좌측 패널 트리뷰에서 **“System Core”**를 펼치고, “SYS”(System Configuration) 메뉴를 클릭합니다.
    • 우측 창이 SYS 설정 화면으로 전환됩니다.
  3. “Time Base Source”를 SysTick → TIM6으로 변경
    • SYS 설정 화면에서 중간쯤에 “Timebase Source” 옵션이 있습니다.
    • 기본값은 **“Time Base Source = SysTick”**으로 설정되어 있는데,
    • 이 드롭다운을 클릭하여 **“TIM6”**으로 변경합니다.
    (※ 구성 화면은 CubeMX 버전마다 UI가 약간 다를 수 있지만, “Timebase Source” 라벨과 드롭다운 메뉴를 찾아 “TIM6”으로 바꿔주면 됩니다.)
  4. NVIC(우선순위 그룹) 설정 (선택 사항이지만 권장)
    FreeRTOS와 HAL이 공통 인터럽트 우선순위 체계를 사용하므로,
    NVIC 우선순위 그룹 설정을 미리 조정하면 예기치 않은 선점(preemption) 충돌을 방지할 수 있습니다.
    • 좌측 메뉴 **“System Core → NVIC”**를 선택
    • 우측 상단의 “Priority Group” 드롭다운에서 “4 bits for pre-emption priority, 0 bits for subpriority” (Group 4))를 선택
    • 즉, “Preempt Priority 4 bits / Sub Priority 0 bits” 로 변경
    설명: Cortex-M 시리즈는 하드웨어적으로 4비트 우선순위(015)만 지원하므로,
    “Preempt Priority 4비트, Sub Priority 0비트”로 설정하면 **순수하게 0
    15 우선순위**만 사용합니다.
    실질적으로 “HAL 예외” vs. “FreeRTOS 예외” 간 우선순위 충돌을 최소화할 수 있습니다.
  5. 코드 재생성(Generate Code)
    • 우측 상단 또는 하단의 “GENERATE CODE” 버튼(돋보기 모양 옆) 또는
      화면 상단 메뉴바의 **“Project → Generate Code”**를 누릅니다.
    • “Yes”를 눌러 변경사항을 수락하면, CubeMX가 *.ioc 파일에 맞게 코드(특히 main.c, stm32f4xx_hal_conf.h, it.c, 새 타임베이스 소스 파일 등)를 자동으로 재생성합니다.
  6. 타임베이스 소스 코드 확인
    • 재생성 후 Project Explorer에서 Src/ 폴더에파일이 새로 생겼는지 확인합니다.
    • stm32f4xx_hal_timebase_tim.c
    • 이 파일은 HAL이 더 이상 SysTick이 아니라 TIM6를 이용하여 “1ms 틱”을 발생시키도록 설정해 주는 드라이버 코드입니다.
    /* stm32f4xx_hal_timebase_tim.c */
    /* 
     * HAL library에서 
     * “1ms마다 HAL_GetTick가 증가하도록 하는 타이머로 TIM6을 사용”하도록 구성된 코드
     */
    #include "stm32f4xx_hal.h"
    #if (USE_HAL_DRIVER)
    #include "stm32f4xx_hal_tim.h"
    #endif
    
    TIM_HandleTypeDef        htim6;
    
    /* 
     * HAL_InitTick() 함수 구현 예시 
     *   → HAL이 내부적으로 호출해 TIM6를 초기화하고 1ms마다 인터럽트를 발생시키도록 설정 
     */
    HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
    {
        /* TIM6 clock enable */
        __HAL_RCC_TIM6_CLK_ENABLE();
    
        /* TIM6 설정: 1 MHz 카운터 → 프리스케일러(APB1(42MHz) 기준, 
         prescaler = 42-1 → 1 MHz), 
         → 1 MHz / 1000 → 1 kHz(1ms) */
        htim6.Instance = TIM6;
        htim6.Init.Prescaler = (uint32_t)(SystemCoreClock / 1000000) - 1;
        htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
        htim6.Init.Period = (uint32_t)(1000 - 1);
        htim6.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
        if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
        {
            return HAL_ERROR;
        }
        HAL_NVIC_SetPriority(TIM6_DAC_IRQn, TickPriority ,0);
        HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);
        HAL_TIM_Base_Start_IT(&htim6);
        return HAL_OK;
    }
    
    /* 
     * TIM6의 인터럽트 핸들러 예시
     *   → 인터럽트가 발생할 때마다 HAL_IncTick()을 호출하여 HAL tick 값을 갱신 
     */
    void TIM6_DAC_IRQHandler(void)
    {
        HAL_TIM_IRQHandler(&htim6);
    }
    
    /* 
     * HAL_TIM_PeriodElapsedCallback() 예시 
     *   → Timer tick 인터럽트가 발생할 때마다 HAL_GetTick()에 사용되는 ‘uwTick’ 증가
     */
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    {
        if (htim->Instance == TIM6)
        {
            HAL_IncTick();
        }
    }
    
    • CubeMX가 자동 생성하므로, 직접 수정할 필요는 없고 “Timebase = TIM6” 옵션만 바꾸면 자동으로 코드가 추가됩니다.

3. 프로젝트 빌드 및 확인

  1. 변경사항 저장(Ctrl+S)
  2. 프로젝트를 Build
  3. Project → Build Project
  4. 빌드 성공 메시지 확인
    • 이전에는 SysTick 충돌 등으로 에러가 났지만,
    • 지금은 HAL이 TIM6를 타임베이스로 사용하므로 SysTick 충돌 없이 FreeRTOS가 SysTick을 독점하여 사용할 수 있습니다.

4. 정리 및 다음 단계

  • 결론:
    • FreeRTOS → SysTick 사용
    • STM32Cube HAL → TIM6 사용
      로 타임베이스를 분리하였으므로, 두 모듈 간의 타이머 충돌이 사라지고
      HAL_Delay(), HAL_GetTick() 등 HAL 타이밍 기능도 정상 동작합니다.

이로써 FreeRTOS 커널 + HAL 환경에서 타임베이스(Tick) 자원을 올바르게 분리할 수 있었습니다.
다음 강의부터는 FreeRTOS에서 **Task(태스크)**를 생성하고 사용법을 익히면서,
“Hello World” 예제를 실제 보드에서 실행해보겠습니다.