티스토리 뷰
GPIO를 통한 LED 제어 글에서 LED가 일정 시간동안 켜져 있다가 다시 일정 시간이 지난 후 꺼지게 하기 위해서 for loop에 의한 시간 지연 함수를 사용했었다.
그러나 그 때에는 어느정도의 시간이 지연되었는지 정확히 알 수 없었고 따라서 정확한 시간을 제어할 수가 없었다.
이번 글에서는 타이머 기능을 이용하여 정확한 시간동안 delay를 주는 방법에 대해서 설명하도록 하겠다.
AVR 입문 과정에서 이미 설명하였듯이 타이머 기능은 본질적으로 카운터를 이용하는 것이다. 카운터로 들어오는 입력 신호가 아주 정확한 주기로 들어온다면 타이머로도 사용할 수 있다는 설명이었다.
System Timer에서 이미 Cortex-M에서 사용할 수 있는 시스템 타이머에 대해서 설명하였지만 이번에는 좀더 많은 기능을 수행할 수 있는 주변장치로서의 타이머에 대한 설명이다. 단순한 시간 지연만을 목적으로 한다면 쉽게 사용할 수 있는 시스템 타이머를 이용하여도 되겠지만, 좀 더 복잡한 기능을 사용해야 할 경우에는 각 프로세서 제조사에 제공하는 타이머를 사용하여야 한다.
STM32F103에는 4개의 타이머가 있다.
빨간색으로 표시된 TIM1은 APB2버스에 연결되어 있고, 파란색으로 표시된 TIM2, TIM3, TIM4는 APB1에 연결되어 있는것을 볼 수 있다.
Reference manual 을 보면 알겠지만 TIM1은 advanced timer라는 챕터에 설명되어 있고, 나머지 타이머들은 general purpose timer라는 챕터에 따로 구분해서 설명되어 있다.
이렇게 비록 구분되어 설명되어 있더라도 몇가지 특수한 기능을 제외하고 나머지 부분은 크게 다르지 않아보이므로 크게 고민할 필요는 없을것이다.
위의 그림이 TIM1에 대한 내부 구조를 보여주고 있는데 첫눈에 보기에 상당히 복잡하다. 하지만 위의 그림을 입력, 제어, 출력으로 나누어 보면 조금은 간단해 질 수 있다.
입력부분을 분석해 보면 기본적으로 internal clock을 사용할 수도 있고, TIMx_ETR이라는 external clock을 사용할 수도 있다. AVR에는 없었지만, 다른 타이머의 출력을 입력으로 받아서 동작할 수도 있다. 이런 방식을 사용하게 되면 두개의 타이머가 서로 동기를 맞추어 구동되므로 동기가 필요한 경우 아주 유용하게 사용할 수 있게 된다.
제어 부분은 우선 입력 신호의 해상도를 낮추기 위해서 prescaler 설정 부분이 있고, prescaler를 통과한 신호가 몇번 발생했는지 기록하기 위한 CNT 레지스터가 있다. CNT레지스터는 autoreload register를 기준으로 최대값을 가지게 되며 CNT 레지스터 값이 어떤 특정한 값에 도달할 때 신호를 발생할 수 있도록 설정값을 알려주기 위하여 Capture/Compare 레지스터를 사용한다.
CNT값과 Compare값이 같을때 외부로 신호를 내보낼 수 있도록 하는 출력 부분이 있다.
이번 글에서는 타이머의 가장 기본 기능에 해당되는 일정 시간동안 타이머가 동작되도록 하는것에 대한 설명을 하도록 하겠다.
먼저 타이머로 들어오는 입력 신호가 어떤 특징을 가지고 있는지 분석할 필요가 있다.
가장 쉽게 사용할 수 있는것이 internal clock이므로 internal clock이 어떤 경로로 들어오는지 보면 될 것이다.
TIM1이 APB2버스에 연결되어 있으므로 당연히 APB2 clock을 입력 신호로 연결되어 있음을 볼 수 있다. 한가지 주의할 점은 APB2의 prescaler값이 1이면 APB2 clock을 그대로 사용하지만, prescaler 값이 1이 아니면 APB2 clock에 2를 곱한 값이 TIM1의 입력 신호로 들어오게 된다.
따라서 TIM1을 사용하기 이전에 반드시 APB2의 prescaler값이 얼마로 설정되어 있는지 알고 있어야 한다.
그러므로 위에 보이는 그림의 모든 clock 주파수값을 계산하는 함수를 만들어 두면 편리할 것 같다.
코드에 나오는것과 같이 rcc_get_sysclk(), rcc_get_ahbclk(), rcc_get_apb1clk(), rcc_get_apb2clk(), rcc_get_tmrxclk(), rcc_get_tmr1clk()를 만든다.
실제로 각각의 clock들이 어떤 값으로 설정되어 있는지 다음과 같은 코드를 만들어서 확인해 보도록 한다.
각 clock 주파수를 MHz로 표시하기 위하여 1000000으로 나누었다.
위의 결과는 다음과 같이 나타난다.
각 버스의 clock과 타이머로 공급되는 clock 주파수를 확인할 수 있다. 시스템 clock 설정을 여러가지값으로 바꾸어가며 원하는 값으로 설정되는지 확인해 보기 바란다.
그럼 타이머를 초기화 하는 코드를 만들어 보겠다.
timer_init() 함수의 첫번째 인자는 4개의 타이머중 어떤타이머를 초기화 할것인지 알려주는 것이다. 두번째 인자는 prescaler값이고 세번째 인자는 autoreload 값이다.
internal clock신호를 prescaler 값으로 분주한 다음 autoreload 값만큼 CNT레지스터의 값을 증가 시키는 코드이다.
실제 타이머를 초기화 하기 위해서는 더 많은 정보가 필요하지만 이번에 설명하는 타이머와 관련된 내용으로로는 이정도만 있어도 충분하다. 다른 설정이 필요한 경우에는 필요한 만큼 수정하여야 한다. 타이머 설정을 위해 필요한 내용에 대해서는 CMSIS 코드를 참고하면 많은 도움을 받을 수 있다.
이번 글에서는 위와 같은 값으로 초기화 하였다.
먼저 TIM1을 사용하기 위하여 RCC 레지스터에서 TIM1을 enable 시켜준 다음,
prescaler값으로 tim1clk을 사용하였다. tim1clk은 TIM1으로 들어가는 clock을 MHz단위로 표시하기 위해서 1000000 나눈 값이므로 48이라는 값이 들어가게 된다.
즉, 타이머로 들어오는 input clock이 48번 들어오면 CNT값이 1 증가하게 된다. 결국 1us에 한번씩 CNT값이 증가하게 되는것이다.
Autoreload 값을 1000을 주었으므로 CNT값이 0에서 1000이 되려면 정확히 1ms의 시간이 필요하게 된다.
이 상태에서 delay_ms()라는 함수를 다음과 같이 만든다.
CNT레지스터 값을 0으로 초기화 한 다음 control register에서 enable bit값을 1로 만들면 CNT값이 1씩 증가하게 된다. 이 값이 autoreload값과 같게 될때 status register의 UIF bit값이 1로 변하게 된다. 이 시간이 1ms이 되는 것이다.
이것을 delay_ms() 함수의 입력인자로 들어오는 값만큼 반복하면 원하는 만큼의 시간 delay를 줄 수 있게 되는 것이다.
전체 코드는 위와 같다. 프로그램이 실행되면 콘솔 화면에 현재 설정된 각 clock값들을 표시해주고, PC13에 연결되어 있는 LED를 500ms마다 켜고 끄고를 반복하게 된다.
실제로 1초 주기로 LED가 점멸되는지 확인해 본다.
'ARM Cortex-M' 카테고리의 다른 글
ST-Link/V2 SWD 연결 문제 해결 (4) | 2017.04.15 |
---|---|
PWM (0) | 2017.04.08 |
Cortex-M3 RTOS (0) | 2017.03.25 |
heap 메모리 이해 (1) | 2017.03.17 |
printf()를 만들어 보자 (3) | 2017.03.12 |