본문 바로가기
  • 경제적 자유를 얻는 그날까지
엔지니어링/임베디드

[STM32] sysTick 을 이용한 usDelay 함수 만들기

by 베니스상인 2020. 3. 9.

STM32CubeIDE에서는 기본적으로 1ms단위로 입력이 가능한 msDelay를 제공한다. 

그러나 가끔 시간을 측정하거나 1ms보다 작은 delay를 사용하고자 한다면 msDelay를 사용할 수 없다. 

 

 

us 단위의 delay를 만드는 방법은 타이머 소스를 사용하거나 system clock을 사용하거나 여러가지 방법이 있지만 오늘은 sysTick을 사용하여 usDelay함수를 구현해보고자 한다.

 

 

1. SysTick

sysTick는 Cortex-M 코어에만 지원하는 24bit 타이머이다. Tick 타이머에 적합하도록 설계되어 있는 간단한 타이머이다. sysTick은 시스템 클럭를 그대로 사용하거나 8분주한 클럭을 소스로 사용할 수 있다.

 

타이머의 동작은 간단하다. Reload Register를 이용하여 타이머가 0이 되었을 때 다시 카운트할 값을 불러오고 Tick마다 값을 감소하다가 0이 되면 SysTick Value Register의 값을 하나씩 증가시킨다. 

 

예를 들어 8MHz 클럭을 사용하여 1ms 타이머를 만든다고 하면 8MHz는 초당 8,000,000번 카운트되므로 1ms에는 8,000번 카운트가 된다. Reload Register를 8,000으로 설정하면 SysTick이 감소하면서 0이 될 때마다 1ms의 시간이 된다. 이때 SysTick 타이머 인터럽트 이용하면 1ms이 될 때마다 인터럽트를 발생시킬 수 있다.

 

그러면 1us Delay를 만들기 위해서는 reload를 8로 하면 되고 10us Delay는 1us 인터럽트가 발생할 때마다 개수를 세어 10이 될 때 함수가 종료하도록 만들면 된다.

 

 

2. SysTick 레지스터

 

SysTick은 다음과 같이 4개의 레지스터로 구성되어 있다. 

- SysTick Current Value Register

- SysTick Reload Register

- SysTick Control and Status Register

- SysTick Calibation Register

 

 

 

1) SysTick Current Value Register(STK_VAL)

SysTick Current Value는 카운터의 현재값을 나타내는 레지스터이다.

카운터의 시작은 Reload Register의 값에서 시작하여 Tick마다 하나씩 감소시킨다.

zero가 되면 COUNTFLAG가 1로 된다. 

 

 

2) SysTick Reload Value Register(STK_LOAD)

 

SysTick Value Register가 0이 되었을 때 새로 시작되는 값을 설정하는 레지스터이다. 

 

 

 

3) SysTick Control and Status Register(STK_CTRL)

 

 

SysTick Control and Status Register는 SysTick 관련 설정과 상태를 확인하는 레지스터이다.

 

bit 0은 클럭 카운트를 시작할 때 1로 설정해주면 된다.

 

bit 1은 SysTick의 counter를 감소시키다가 0이 되면 exception request를 발생시킨다. 즉 zero가 되었을 때 인터럽트를 발생시키기 위해서는 이 레지스터를 1로 설정해야 한다. 인터럽트가 발생했을 때 bit 16을 읽으면 count 가 zero임을 알수 있다.

 

bit 2는 SysTick에서 사용하는 클럭소스를 그대로 쓰거나 8분주한 것을 사용하거나 선택할 수 있다. 소스를 그대로 사용할 것이므로 1로 세팅한다.

 

그러면 내 시스템에서 SysTick으로 사용하는 클럭 소스가 어떤 것인지 확인해야 한다.

 

 

아래 그림은 STM32F405의 clock configuration이다. 외부 8MHz 클럭을 입력받아 최대 168MHz까지 채배하여 여러곳에 클럭소스로 사용하고 있다.  그 중 sysTick에 사용되는 클럭소스는 To Cortex System Timer(MHz)라고 표시된 부분이다.  168MHz라고 되어 있으니 sysTick으로 168MHz를 사용할 수 있다.

 

 

 

 

 

3. usDelay 함수 구현

 

 

먼저 SysTick Timer의 인터럽트를 활성화하기 위해 아래 내용을 확인한다. 

 

Time base: System tick Timer가 활성화되어 있어야 한다. 보통 기본적을 활성화되어 있지만 활성화되어 있지 않으면 인터럽트가 발생하더라도 SysTick_Handler 함수를 호출하지 않는다. 

 

 

 

 

프로젝트를 저장하고 STM32CubeIDE가 자동으로 생성한 초기화 함수들을 보면 systemClock_Config 함수에 SysTick을 이용하여 LL_Init1msTick 함수에 1ms Tick을 만드는 설정이 되어 있다. 클럭으로 168000000이 되어 있으니 168MHz를 클럭 소스로 사용하고 있음을 알 수 있다.

 

※ 참고로 본인은 LL타입으로 코드를 생성하였음, HAL드라이버는 코드 생성이 좀 다르게 나타남

 

 

LL_Init1msTick 함수를 살펴보니 LL_InitTick에는 클럭 소스를 1000으로 나눈 값을 reload register에 사용하고, LL_mDelay 함수를 만들었다는 것을 알 수 있다. 

 

이 부분을 약간 수정하여 LL_InitTick을 1000000으로 수정하면 168000000/1000000 = 168이므로 168 Tick 마다 Reload가 되게하여 1us의 Tick을 사용할 수 있다.

 

 

 

main.c에서 SystemClock_Config(); 다음 user code 시작 부분에 아래와 같이 Tick을 설정해준다.

 LL_InitTick(168000000, 1000000U);

 

 

 

 

usDelay함수를 아래와 같이 작성한다.

함수가 호출되면 STK_CTRL 레지스터와 STK_VAL 레지스터를 초기화하고 STK_CTRL 레지스터를 설정한다.

- bit2: 1, 클럭 소스 그대로 사용(168MHz)

- bit1: 1,  SysTick Timer 인터럽트 활성화

- bit0: 1, 카운터 시작

 

TimingDelay 는 사용자가 설정한 delay의 integer 값이므로 0이 되면 함수가 종료된다.

이 값은 SysTick_Handler가 1us마다 발생하면서 1씩 감소된다.

 

unsigned int TimingDelay;


void usDelay(unsigned int nTime)
{
  __IO unsigned int  tmp = SysTick->CTRL;
  ((void)tmp);

  SysTick->VAL = 0;

  SysTick->CTRL = SysTick->CTRL | SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; // clock source

  TimingDelay = nTime-1;

  while(TimingDelay);

  SysTick->CTRL = 0;

}

 

 

stm32f4xx_it.c에서 SysTick_Handler에 아래와 같은 코드를 작성한다. SysTick Timer 인터럽트가 발생할 때마다 아래 Handler가 호출된다.

 

void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */
  if (TimingDelay != 0x00) {

     TimingDelay--;
}

 

 

4. 동작 확인

 

함수의 동작을 확인하기 위해 간단하게 GPIO Toggle 함수를 구성하고, 출력을 LED에 연결하여 200us마다 Blink 되도록 하였다.

 

 

200us 마다 LED를 blink하면 너무 짧은 시간이기 때문에 LED가 blink되는지 확인하기 어렵다. 따라서 LED가 연결된 GPIO의 출력포트 쪽으로 오실로 스코프를 이용해 Toggle되는 시간을 측정하였다. 

 

사진을 보면 200us마다 정확하게 GPIO 상태가 Toggling 되는 것을 확인할 수 있다.

 

 

 

STM32에서 printf를 UART로 출력하면 상당히 많은 시간을 잡아 먹는다.  UART로 printf를 출력하는 방법은 아래 포스트를 참고하면 된다.

 

https://swiftcam.tistory.com/143?category=783715

 

[STM32] printf를 시리얼로 출력하기

printf 함수는 펌웨어 개발단계에서 디버깅을 위해 가장 많이 사용하는 방법중 하나이다. 보통 visual studio나 PC 기반의 IDE는 printf를 통하여 툴에서 화면으로 출력되도록 지정되어 있다. 그러나 STM32와 같은..

swiftcam.tistory.com

 

 

여기에서 _write 라는 함수를 잘 살펴보면 1byte를 전송할 때마다 1ms의 delay를 잡아먹게 된다. 그러니 10 byte char를 전송하게 되면 벌써 10ms이 소요된다. 

 

따라서 이 시간을 줄이기 위해 usDelay를 사용하여 시간을 줄여보았다. 여러번 시도하여 100us이면 적정한 시간으로 동작할 수 있다. integer 타입만 사용하면 시간을 더 줄일 수 있으나 float형 데이터까지 처리하기 위해서는 100us정도는 필요했다. 

 

100us로 Delay를 설정하여 float형과 integer형을 출력할 때 정상으로 출력되는 것을 확인하였다.

 

 

 

5. 결론

 

SysTick Timer를 이용하여 usDelay 함수를 구현한 결과 1us 단위로 인터럽트를 발생하여 시간을 측정할 수 있는 충분한 성능을 보여주었다.

 

다만 1us마다 인터럽트가 발생하기 때문에 runtime중에 오랜 시간을 측정하는데 사용하는 것은 좋지 않고 짧은 시간동안 측정을 하거나 Delay를 주고자 할 때 사용하는 것이 바람직하다.

 

그리고 main.c의 초기화 부분에서 Reload Register의 값을 1us단위로 조정하였기 때문에 LL_mDelay함수는 사용할 수 없다

 

 

 

 

 

728x90

댓글