Lecture 5. Timer Interrupt 사용하기

강의목표

  • nRF52 개발 보드에 내장된 RGB LED를 타이머 인터럽트를 이용하여 제어할 수 있다.
  • nRF52832 개발 보드를 사용하여 타이머 인터럽트의 사용 설정을 할 수 있다.
  • 타이머 레지스터에 관해 다양한 설정 방법을 파악한다.

준비물

  • Roverdyn nRF52 개발 보드 × 1 (구매하기)
  • 전원 공급용 Type-C USB 케이블 × 1
  • Windows 데스크톱 혹은 랩톱 × 1
  • VS Code 개발 환경
  • J-Link 디버거

타이머-카운터 인터럽트의 정의 및 동작

타이머-카운터 인터럽트(Timer-Counter Interrupt)는 MCU의 내외부 클럭을 활용하여 정확한 시간 단위로 어떠한 동작을 수행하거나 횟수를 셀 수 있는(=Count) 기능을 말합니다. 또한 인터럽트(Interrupt)는 사전적 의미 그대로 어떠한 작업을 잠시 중단 시키고(=Interrupt) 정해진 작업을 수행하는 기능을 말합니다. 타이머-카운터 인터럽트는 타이머 혹은 카운터 기능과 인터럽트가 결합한 MCU의 기능을 말합니다. nRF 시리즈에서 제공하는 타이머와 카운트는 다음과 같은 동작을 수행할 수 있습니다.

  • 타이머 : 정해진 시간 간격으로 특정 기능을 동작시킴
  • 카운터 : 정해진 시간 간격으로 숫자를 하나씩 더하거나 뺌

위의 두 특성을 이용하여 개발자는 정확한 시간 간격으로 다양한 동작을 정의하여 실행시킬 수 있으며 여기에 더해 기존 작업을 잠시 중지하고 타이머/카운터 인터럽트가 정의한 작업을 수행하는 인터럽트 기능을 추가하여 개발자의 의도대로 작업을 스케쥴링할 수 있습니다. nRF52시리즈는 총 3개 ~ 5개의 타이머/카운터 인스턴스를 지원합니다. 이 중 nRF52832 시리즈는 총 5개의 타이머 레지스터를 지원합니다. 각각의 타이머는 Compare Channel(CC) 이라고 하는 비교 기준 값을 설정하는 레지스터를 가지고 있으며, CC 레지스터는 타이머의 인스턴스에 따라 지원되는 갯수가 다르며 nRF52832 MCU의 경우에는 다음 사항을 참조하기 바랍니다.

  • NRF_TIMER0 : 총 4개의 CC
  • NRF_TIMER1 : 총 4개의 CC
  • NRF_TIMER2 :  총 6개의 CC
  • NRF_TIMER3 :  총 6개의 CC
  • NRF_TIMER4 :  총 6개의 CC

각 타이머가 지원하는 CC의 갯수는 MCU의 종류와 타입에 따라 달라질 수 있으므로 데이터 시트를 반드시 참조하여야 합니다.

타이머 인터럽트 설정하여 사용하기

타이머/카운터 인터럽트중 타이머 인터럽트는 일정한 주기로 작업을 실행시키는 용도로 자주 사용되는 기능입니다. 예를들면 특정 주기로 데이터를 보내거나, LED를 동작시키거나 혹은 외부 신호를 확인하는 등의 동작을 수행 할 수 있으며 스탑 워치등의 기능으로 활용할 수 있습니다. nRF52시리즈 MCU는 타이머/카운터 기능을 내부 레지스터로 설정하여 사용할 수 있으며 총 세 개의 모드(Counter, LowPowerCounter, Timer)를 지원합니다. 우리는 이 중 Timer 모드를 활용하여 LED를 정해진 주기로 제어하려고 합니다. 이를 위해서 먼저 TIMER 레지스터를 사용하여 몇 가지 설정을 해주어야 합니다.

  • BITMODE : 타이머/카운터가 셀 수 있는 총 갯수를 설정합니다. 8/16/24/32Bit를 설정할 수 있습니다.
  • PRESCALER : 시스템 클럭(=16MHz)을 나눌 수 있는 설정값입니다. Prescaler는 0~9까지 설정할 수 있으며 각각 2^0 ~ 2^9(512)를 의미합니다.
  • PRESCALER는 1초가 몇 개의 카운트인지 결정합니다. 예를 들어 PRESCALER를 9로 설정할 경우 다음 공식으로 1초에 해당하는 카운터 값을 계산할 수 있습니다 : 16000000/2^9 = 16MHz/512 = 31250 = 1초
  • MODE : 타이머/카운터 동작 모드를 설정할 수 있습니다. Timer, Counter, LowPowerCounter를 지원합니다.
  • INTENSET : 인터럽트를 설정하는 레지스터입니다.
  • SHORTS : 특정 동작 후 별다른 명령 없이 정해진 동작을 수행하도록 설정할 수 있습니다.
  • CC : Compare Channel의 약자로서 기준이 되는 카운터/타이머 값을 설정할 수 있으며 EVENTS_COMPARE 레지스터는 이 값을 기준으로 호출됩니다.

위의 설명대로 작성한 위 이미지의 설정 값은 다음과 같습니다.

  • Bitmode : 32Bit
  • Prescaler : 512
  • 1초 카운터 : 16000000/512 = 31250
  • 인터럽트 채널 : EVENTS_COMPARE[0]
  • 인터럽트 호출 주기 : 31250 / 15625 = 0.5초
  • SHOTS : EVENTS_COMPARE[0] 호출 후 TIMER/COUNTER 값이 0으로 자동으로 초기화
  • 인터럽트 호출 시 TIMER2_IRQHandler 함수를 호출함

소스 코드

				
					/*
        Roverdyn Inc. Timer Interrupt Example.
        2024. 7. 15.

        MIT License

        Copyright (c) 2024. Seonguk Jeong, Roverdyn Inc.

        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:

        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.

        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
*/

#include <zephyr/kernel.h>

#define GPIO_LED_RED    22
#define GPIO_LED_GREEN  23
#define GPIO_LED_BLUE   24

// Interrupt Handler
void TIMER2_IRQHandler();
int main(void)
{
        // GPIO Configuration
        NRF_GPIO->DIRSET = (1 << GPIO_LED_RED) | (1 << GPIO_LED_GREEN) | (1 << GPIO_LED_BLUE);
        NRF_GPIO->OUTSET = (1 << GPIO_LED_RED) | (1 << GPIO_LED_GREEN) | (1 << GPIO_LED_BLUE);

        /*
                Timer Interrupt Setting
                Timer Bitmode : 32bit => Max. 2^32 counts
                Mode : Timer Mode
                Interrupt Event Channel : EVENT_COMPARE[0]
                Prescaler = 2^9 = 512
                1sec counts : 16000000/512 = 31250 = 1sec
                Comapre Counter(CC) : 15625 -> 31250 / 15625 = 0.5 period 
        */
        NRF_TIMER2->BITMODE = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos;
        NRF_TIMER2->PRESCALER = 9 << TIMER_PRESCALER_PRESCALER_Pos;
        NRF_TIMER2->MODE = TIMER_MODE_MODE_Timer << TIMER_MODE_MODE_Pos;
        NRF_TIMER2->INTENSET = TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos;
        NRF_TIMER2->SHORTS = TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE0_CLEAR_Pos;
        NRF_TIMER2->CC[0] = 15625;      // Interrupt Period : 0.5sec(31250/2 = 0.5)
        NRF_TIMER2->TASKS_START = 1;
        
        // Enable IRQn
        IRQ_DIRECT_CONNECT(TIMER2_IRQn, 0, TIMER2_IRQHandler, 0);
        irq_enable(TIMER2_IRQn);
        
        while(true){
                k_usleep(1);        // Wait For Interrupt
        }
        return 0;
}

void TIMER2_IRQHandler(){
        // TIMER2 Interrupt Handler
        static bool isLedOn = false;
        if(NRF_TIMER2->EVENTS_COMPARE[0] == 1){
                NRF_TIMER2->EVENTS_COMPARE[0] = 0;
                if(!isLedOn){
                        isLedOn = true;
                        printk("LED On : Green\n");
                        NRF_GPIO->OUTCLR = (1 << GPIO_LED_GREEN);
                }else{
                        isLedOn = false;
                        printk("LED Off : Green\n");
                        NRF_GPIO->OUTSET = (1 << GPIO_LED_GREEN);
                }
        }
}
				
			

빌드 및 컴파일, 다운로드

작성한 소스 코드는 빌드 및 컴파일을 한 후 최종적으로는 디버거 혹은 프로그래머 장비를 이용하여 개발 보드에 다운로드할 수 있습니다. 개발 보드에 관한 설정은 “nRF52  개발환경 구축하기” 튜토리얼을 확인하여 설정할 수 있습니다.

 작성한 코드를 빌드하기 위해서는 우선 다운로드 하고자 하는 보드를 설정해주어야 합니다. 대상 보드를 정의하기 위하여 좌측 패널의 메뉴 중 “Run and Debug” 메뉴를 클릭한 후 “NRF CONNECT:WELCOME” 패널 내부의 “Create a new board” 메뉴를 클릭합니다.

화면 상단에 패널이 나타나면 새로운 보드의 이름을 작성한 후 엔터키를 눌러줍니다. 보드의 이름은 영문으로 작성하여야 합니다.

다음으로 보드의 이름을 한번 더 작성합니다. 다만 이 이름은 기계가 인식할 수 있는 방법으로 작성합니다. 대게 자동으로 작성되어 있으므로 엔터키를 눌러 다음으로 넘어갑니다.

보드가 사용하는 MCU의 종류를 입력합니다. nRF52 개발 보드는 nRF52810-QFAA 혹은 nRF52832-QFAA 두 종류를 사용합니다.

새로 작성한 보드의 정보가 저장될 위치를 설정합니다. 기본적으로 프로젝트 내부의 “board” 폴더가 지정되어 있습니다.

마지막으로 보드의 제조사를 설정합니다. 해당 내용은 작성하지 않아도 동작에 지장이 없습니다.

위와 같이 새 보드의 정의 및 파일 생성이 완료되었다면 빌드 환경을 구성할 수 있습니다. 빌드 환경은 위의 보드 추가 방법과 같이 동일한 “Run & Debug” 패널의 “NRF CONNECT: APPLICATION” 항목에서 설정할 수 있습니다.

 APPLICATION 패널 중 우측에 위치한 “Add Build Configuration” 아이콘을 눌러 설정창을 열어줍니다.

빌드 환경 설정 창이 나타나면 위 그림과 같이 “Custom board” 항목을 눌러준 다음 이전에 생성한 보드를 선택합니다. 위 예제에서는 “nrf52_devboard_52832” 항목을 선택하였습니다. 이는 사용자가 만든 보드 이름에 따라 달라질 수 있습니다. 또한 Configuration 항목을 prj.conf 로 선택합니다. 이후 우측 하단의 “Build configuration” 버튼을 눌러 빌드 환경을 구성합니다.

이상 없이 빌드가 완료되었다면 다시 APPLICATION  패널로 이동하여 “Flash All Linked Devices” 항목을 눌러 컴파일된 펌웨어의 다운로드를 시작합니다. 이 때, 타겟 보드는 컴퓨터와 Jlink 혹은 기타 디버거, 프로그래머와 연결이 되어 있어야 합니다.

위와 같이 빌드를 시작하며 컴파일이 완료된 후 자동으로 컴파일된 펌웨어를 개발 보드로 전송합니다. 전송이 완료되면 개발 보드를 재부팅 하거나 전원을 분리하였다가 다시 꽂으면 새로운 펌웨어로 동작합니다.

동작 시켜보기

이 예제는 별다른 소프트웨어 없이 동작할 수 있습니다. 컴파일 및 다운로드 후 전원을 제거했다가 다시 연결하거나 개발 보드의 리셋 버튼을 이용하여 동작시킬 수 있습니다. 동작 시 SEGGER RTT Viewer를 이용하여 디버깅 메시지를 출력할 수 있으며, 0.5초 주기로 LED On 및 Off 문구가 출력됩니다. 또한, 보드 상에서는 초록색 LED가 0.5초를 주기로 켜짐과 꺼짐을 반복함을 확인할 수 있습니다.

Shopping cart close