STM32 (Cortex-M4)

STM32 media 보드 - delay

gigax 2025. 3. 27. 23:02

◎ 타이머 제어 

 

이번 글은 delay와 관련하여 정리해 보겠습니다. delay 함수는 보통 ms , us 단위로 함수를 만들어 동작 시키고는 합니다. 보통 delay 는 두가지 방식으로 나뉘어 집니다. blocking delay 와 Non blocking delay 입니다.
※ 임베디드 시스템을 접근하고 펌웨어를 제작하는데 있어서 이 delay 시스템은 굉장히 필수적이고 중요합니다.

1.  blocking delay 

blocking delay 는 보통 일반적인 delay 입니다.

blocking 이라는 것은 한마디로 delay 함수가 실행 중인 상황에서는 다른 시스템 접근이나 실행이 blocking (막힌다) 라는 것입니다.

● 코드를 보면 단순하고 직관적인 구현 방식을 가진것을 알수 있습니다.

void delay_ms(uint32_t ms) {
    // 단순 루프를 이용한 블로킹 딜레이 예시
    for(uint32_t i = 0; i < ms * 1000; i++) {
        // 시간 지연을 위한 루프
        __NOP(); // No Operation 명령어
    }
}

 

● 보통 이런 딜레이를 사용하는 경우는 무조건 정해진 시간에 하나의 동작을 수행을 Top down 으로 수행할 경우 적용합니다. 예를들어 LED 토클 동작 같은 경우에 적용 됩니다.

2.  Non blocking delay

● 위에서 언급한것처럼 여러 드라이브가 시스템에 접근하거나 동작을 수행할 경우 적용하는 delay 입니다.
● 보통 ISR (Interrupt service routine) 함수를 이용하여 타이머 인터럽트를 이용해 구현하는 방식 입니다.

volatile uint32_t delay_counter = 0;

void SysTick_Handler(void) {
    if(delay_counter > 0) {
        delay_counter--;
    }
}

void non_blocking_delay_ms(uint32_t ms) {
    delay_counter = ms;
    while(delay_counter != 0) {
        // 다른 작업 수행 가능
        // 예: 센서 읽기, 통신 등
    }
}


● 멀티태스킹과 실시간 시스템에 적합 합니다.

● 시스템 자원을 더 효율적으로 사용할 경우 적용합니다.

● 일반적인 딜레이는 함수 1개만 이용하여 제작이 가능하지만 Non blocking 은 인터럽트를 연동해야 하기 때문에 다소 조금 복잡 합니다.

이글에서 일반적인 blocking delay 보다는 Non blocking delay 관련하여 내용을 다루어 보고자 합니다.
위에서 말한것 처럼 Non blocking delay 를 구현 하기 위해서는 ISR ( Interrupt service routine ) 을 이용해야 합니다.
※ stm32f4xx_it.h , stm32f4xx_it.c 에 관련된 함수가 있습니다.

프로젝트 생성 과정에서 별도의 세팅은 필요 없고 기본적으로 이 라이브러리 파일은 포함되어 있습니다.

코드를 보면서 동작 방식을 한번 설명해 보겠습니다. 

void bsp_InitTimer(void)
{
    uint8_t i;

    for(i=0; i<TMR_COUNT; i++)
    {
        s_tTmr[i].Count = 0;      // 4개의 타이머 카운터 값 모두 0으로 초기화
        s_tTmr[i].PreLoad = 0;    
        s_tTmr[i].Flag = 0;       // 플래그값 모두 0으로 초기화
        s_tTmr[i].Mode = TMR_ONCE_MODE; 
    }
    /*SystemCoreClock 를 168MHz 로 할경우 1000Hz 1ms(1/1000초) 주기를 가짐 
      1ms 주기당 168,000 클록 사이클 동작
      초당 1000번에 인터럽트가 발생
      각 인터럽트 사이 간격 1ms 
    */
    SysTick_Config(SystemCoreClock/1000);   
}

 

먼저 프로그램을 초기화 할때 타이머 변수들을 초기화 하고, 시스템 클록의 주기를 설정 합니다.
여기서 중요한 함수는 SysTick_Config 함수입니다. 이 함수는 cmsis 에 기본적으로 포함되어 호출되는 함수 입니다. 이 함수는 주기 마다 인터럽트를 발생에 동작을 실행하는 함수 입니다. 
위에서 우리는 시스템 클록을 최대 168MHz 로 설정을 하였고 이렇게 하면 1000으로 분주 할경우 1ms 당 168,000 클록 사이클이라는 굉장히 빠른 속도로 클록이 동작으로 하고 1초당 tick 이라고 하는 개념으로 1000번에 인터럽트가 계속 발생하게 됩니다. 결론적으로 1ms 인터럽트가 발생되는 겁니다.

우리가 보통 동영상으로 보면 60FPS 라고 하는데 이거는 초당 60장의 사진을 연속으로 보는거와 같습니다. 이걸 생각해 보면 1초당 1000번에 인터럽트는 사실상 실시간 시스템에서 다른 이벤트가 발생할때 거의 간섭을 주지 않는 이벤트 라고 생각하면 됩니다.

void bsp_DelayMS(uint32_t n)
{
    if(n == 0){
        return; 
    }
    else if(n == 1)
    {
        n = 2;   
    }
    DISABLE_INT();          // 인터럽트 비활성화

    s_uiDelayCount = n;     // 1000 ms
    s_ucTimeOutFlag = 0;

    ENABLE_INT();          // 인터럽트 활성화

    while(s_uiDelayCount > 0)
    {
        if (s_ucTimeOutFlag == 1){
            s_ucTimeOutFlag = 0;
        }
    }
}

이 코드에서 핵심이 되는 변수는 s_uiDelayCount 입니다 여기에 설정하고 싶은 지연 값을 매개변수로 받고 그값을 s_uiDelayCount 에 저장하게 됩니다. 중간에 DISABLE_INT 와 ENABLE_INT 를 준것은 다른 시스템에 영향을 보호하기 위해서 설정을 한겁니다. (이 부분에 대한 내용은 나중에 다시 다뤄보겠습니다.)

void SysTick_ISR(void)
{
    //static uint8_t s_sount = 0;
    //uint8_t i;

    if(s_uiDelayCount > 0)
    {
        if(--s_uiDelayCount == 0)     
        {
            s_ucTimeOutFlag = 1;
        }    
    }
}


사실상 딜레이에 동작을 진행 하는 함수 입니다. s_uiDelayCount 에 저장된 값 만큼 1ms 씩 마이너스로 빠지면서 최종 0이 되면  s_ucTimeOutFlag 에 도달하게 됩니다. 이동작을 계속 반복 하는 겁니다.
그렇다면 이 동작에 계속 반복은 무엇을 의미 할까요 바로 이함수가 ISR에 등록이 되야 합니다. 그러기 위해서는 아래와 같이 처리하면 됩니다.
아까 위에서 말한 stm32f4xx_it.c 파일 안에 SysTick_Handler 라는 함수에 작성된  SysTick_ISR 함수를 등록 하면 최종적인 딜레이 동작이 구성이 됩니다.

/**
  * @brief This function handles System tick timer.
  */
void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */

  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  SysTick_ISR();
  /* USER CODE BEGIN SysTick_IRQn 1 */

  /* USER CODE END SysTick_IRQn 1 */
}

 

 

LED 가 500ms 단위로 동작되는 영상입니다.