STM32 Basic Timer

STM32는 Basic, General Purpose, Advanced 3가지의 타이머를 제공하며, 아래 블로그에서 각 타이머에 대해 간단하게 설명 및 예제 코드를 제공한다.

 

[STM32 HAL] Timer# Basic 타이머

Reference: Mastering STM32 by Carmine Noviello** 자료 해석에 오류가 있을 수 있습니다 **​[배...

blog.naver.com

STM32F407 데모보드에서 16비트 기본 타이머는 TIM6, TIM7로 제공하며, TIM6 타이머 처리를 폴링, 인터럽트 및 DMA 방식으로 처리하는 것을 정리할 것이다.

 

STM32F407 데모보드의 TIM6 최대 클럭은 84MHz이기 때문에, 위 블로그를 참조하여 1초 타이머의 prescaler과 period 값은 아래와 같이 설정하였으며, 타이머 처리 방식(폴링/인터럽트/DMA)에 따라 추가적인 설정을 해야 한다. 그리고 TIM6가 연결된 APB1 버스의 클럭이 84MHz가 되도록 아래와 같이 설정하도록 한다.

1. 타이머 처리 - Polling 방식

위 이미지와 같이 Prescaler = 41999, Period = 1999 설정하여 1초마다 타이머 플래그가 설정되며, 폴링 방식인 경우 이것을 main 함수에서 아래와 같이 확인해야 한다.

int main(void)
{
	...
    HAL_TIM_Base_Start(&htim6);
    while (1)
    {
        if(__HAL_TIM_GET_FLAG(&htim6, TIM_FLAG_UPDATE))
        {
            __HAL_TIM_CLEAR_IT(&htim6, TIM_IT_UPDATE);
            HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_15);
            printf("GPIO_PIN_15 = %d, SysTick = %ld\r\n", HAL_GPIO_ReadPin(GPIOD, GPIO_PIN_15), HAL_GetTick());
        } 
    }
}

위 참고 블로그와 같이 오실로스코프로 GPIO_PIN_15를 측정해보면 좋겠지만, 그렇지 못한 환경인 경우 SysTick 값으로 아래 이미지와 같이 타이머가 정상동작 하는지 확인할 수 있다. 참고로 STM32F4xx에서 1tick은 1ms이다.

타이머를 main함수에서 폴링방식으로 타이머 플래그를 확인할 경우 정확히 1000ms 마다 TIM_IT_UPDATE 플래그가 설정되는 것을 확인할 수 있다.

 

2. 타이머 처리 - Interrupt 방식

타이머 처리를 인터럽트로 처리하기 위해 아래 이미지와 같이 NVIC를 추가하도록 한다. 그 이외의 설정은 폴링 방식에서 설정한 것과 완전 동일하다.

위와 같이 NVIC를 추가할 경우 CubeIDE에서 인터럽트 핸들러가 추가되어 인터럽트를 사용할 수 있다. 인터럽트 핸들러를 살펴보면 타이머 관련 수많은 인터럽트가 있는 것을 확인할 수 있다.

Period 값이 overflow가 되면 발생하는 인터럽트는 TIM_FLAG_UPDATE이며, 이것에 해당하는 콜백함수가 weak symbol로 선언되었기 때문에 오버라이드하여 아래와 같이 strong symbol로 설정하도록 한다.

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM6)
    {
        HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_15);
        printf("GPIO_PIN_15 = %d, SysTick = %ld\r\n", HAL_GPIO_ReadPin(GPIOD, GPIO_PIN_15), HAL_GetTick());
    }
}

int main(void)
{
	...
    HAL_TIM_Base_Start_IT(&htim6);
    while (1)
    {
    }
}

여기서 폴링 방식으로 했을 때와 다르게 인터럽트가 실행되는 시간이 아래 이미지와 같이 다소 오차가 발생한다. 인터럽트 핸들러 처리에 따른 오버헤드인 것으로 추정되나...

아마도 타이머 인터럽트는 정확히 발생했으나, 인터럽트 핸들러에서 해당 콜백함수를 실행하는데 소비되는 시간으로 인한 오차가 발생하는 것 같다. STM32 HAL layer가 모든 Chipset에 동일한 구조와 인터페이스로 개발 편의성은 제공하나 오버헤드(?)가 발생하는 단점이 있는 것 같다.

 

3. 타이머 처리 - Interrupt & DMA 전송

위 eziya's 블로그에서 설명했던 것처럼 TIM6는 DMA1 연결되어 있기 때문에, DMA로 LED(GPIOD Pin)에 데이터 전달이 불가하기 때문에 APB1 연결되어 있는 UART2로 데이터를 전달하도록 하였다. 기존 CubeIDE TIM6 설정에서 DMA 항목을 아래 이미지와 같이 추가하도록 한다.

1초 간격으로 타이머를 인터럽트를 발생시켜도 상관없지만, 1초로 타이머 인터럽트를 설정할 경우 UART로 데이터를 출력하는 것이 답답하게 느껴지기 때문에 100ms 마다 타이머 인터럽트가 발생하도록 prescaler = 4199 / period = 19999로 변경하였다.

int main(void)
{
	...
    HAL_TIM_Base_Start_IT(&htim6);
    HAL_DMA_Start(&hdma_tim6_up, (uint32_t)data, (uint32_t)&USART2->DR, sizeof(data)-1);
    __HAL_TIM_ENABLE_DMA(&htim6, TIM_DMA_UPDATE);

    while (1)
    {
    }
}

위 코드는 CubeIDE에서 설정한 TIM6 DMA를 사용하여 타이머 인터럽트가 발생할 때마다 UART2로 1바이트씩 데이터를 전송하는 코드이며, DMA Reqeust mode를 Circluar로 설정하여 무한히 data 변수에 저장된 데이터의 옵셋을 증가시키면서 UART2->DR로 1바이트씩 전달한다.

 

위 3가지의 예제코드는 아래 GitHub에 올려놓았으며, 필요하다면 나중에 참고하도록 하자.

 

highgon2/STM32

STM32 TestCode. Contribute to highgon2/STM32 development by creating an account on GitHub.

github.com