CategoriesnRF52 Lecture

Lecture 6. RTC 인터럽트로 초시계 만들기​

Lecture 6. RTC 인터럽트로 초시계 만들기

강의목표

  • Real Time Clock(RTC) 기능을 이용하여 1초 단위로 시간을 계산한다.
  • RTC와 인터럽트를 이용하여 LED를 제어한다.
  • 내장된 버튼을 이용하여 초시계 기능을 시작하거나 중단할 수 있다.

준비물

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

RTC의 정의 및 기본 동작

(위 사진 : 하드웨어 RTC 회로)

RTC는 Real Time Clock의 약자로 말 그대로 실시간(Real Time) 시계(Clock)을 뜻합니다. 따라서 시간을 측정하거나 정해진 시간 간격으로 숫자를 세는(Counting) 등의 동작을 수행할 수 있습니다. 이는 기존의 Timer와 유사한 동작 방식을 가지고 있습니다. 하지만 RTC는 Timer와 하드웨어 구조부터 다르며 개발자는 작성중인 애플리케이션을 좀 더 최적화 하기 위해 RTC와 타이머를 적절히 선택하여 사용할 필요가 있습니다. 타이머와 RTC는 다음과 같은 차이가 있습니다.

  • RTC는 Low Frequency Clock을 사용합니다(32.769kHz)
  • RTC는 동일한 기능을 수행할 때 좀 더 낮은 전력 소모율을 보입니다.(약 1μA)
  • RTC는 낮은 해상도(Resolution)을 가집니다.(Counter Resolutiuon : 최소 30.517μs)
  •  최대 카운팅 가능한 수 : 24 bits (167,777,216)

즉, RTC는 Timer 기능을 대체하여 사용될 수 있지만 동작 방식에 다소 차이가 있으며 좀 더 낮은 해상도를 제공하는 대신 낮은 전력 소모율을 보여주는 것을 알 수 있습니다. RTC는 Timer와 마찬가지로 인터럽트 기능을 제공하고 있습니다. 다만, 전력 소모율을 낮추기 위해 이벤트를 활성화 혹은 비활성화 할 수 있는 레지스터가 추가되어 있습니다.

 RTC는 다양한 용도로 사용됩니다. 특히 mBed나 freeRTOS 등과 같이 임베디드 RTOS는 시스템 작업 스케쥴링을 위해 TICK 이벤트를 사용합니다. 또한 정확하지는 않지만 개략적인 시간이 얼마나 흘렀는지 알려줄 수 있어 보조 타이머로도 많이 사용됩니다.

RTC로 1초 카운트 하기

RTC는 여러 목적으로 사용할 수 있지만, 그 중에서 저전력 시계로도 활용할 수 있습니다. RTC는 24비트 카운터를 지원하므로 최대 0xFFFFFFFF까지 카운트 할 수 있습니다. 따라서 Over Flow가 일어나는 것을 염두하고 펌웨어를 설계하여야 합니다.

 RTC의 카운터는 Prescaler 값에 따라 1초의 단위가 달라집니다. 만약 RTC의 Prescaler를 32로 설정하였다면 다음과 같은 공식으로 1초를 세는데 필요한 카운터 값을 알 수 있습니다.

32760 / (PRESCALER + 1) = 32760/ (32+1) = 992.96

따라서 RTC의 COUNTER 레지스터 값이 993인 경우 1초를 카운트 한다고 할 수 있습니다. 전체 소스코드는 아래와 같습니다.

				
					#include <zephyr/kernel.h>

// RTC0 Interrupt Handler
void RTC2_IRQHandler();


// Global Variables
uint32_t counter = 0;
int main(void)
{
        // Initialize RTC
        NRF_RTC2->TASKS_CLEAR = 0x01;
        NRF_RTC2->INTENSET = RTC_INTENSET_COMPARE0_Enabled << RTC_INTENSET_COMPARE0_Pos;
        NRF_RTC2->EVTENSET = RTC_EVTENSET_COMPARE0_Enabled << RTC_EVTENSET_COMPARE0_Pos;
        NRF_RTC2->PRESCALER = 32;       // 32768/(32+1) = 992.96counts = 1sec
        NRF_RTC2->TASKS_START = 1;

        // Set RTC Compare Register
        counter = NRF_RTC2->COUNTER + 993;
        NRF_RTC2->CC[0] = counter;   // Call RTC1 Compare0 Handler after 1sec

        // Activate IRQ
        IRQ_DIRECT_CONNECT(RTC2_IRQn, 0, RTC2_IRQHandler, 0);
        irq_enable(RTC2_IRQn);
        
        while(true){
                // Main Loop
                //__WFI();
                k_usleep(1);
        }
        return 0;
}

void RTC2_IRQHandler(){
        // Function for RTC0 Interrupt/Events
        if(NRF_RTC2->EVENTS_COMPARE[0] == 1){
                NRF_RTC2->EVENTS_COMPARE[0] = 0;
                
                // Print message
                printk("Elapsed Time : %ds(%08d)\n", counter / 993, counter);        // 992.9 == 1sec

                // Set next compare register for repeat
                counter = NRF_RTC2->COUNTER + 993;
                NRF_RTC2->CC[0] = counter;
        }
}
				
			

RTC를 이용하여 초시계 만들기

				
					/*
        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초를 주기로 켜짐과 꺼짐을 반복함을 확인할 수 있습니다.

CategoriesnRF52 Lecture

Lecture 5. Timer Interrupt 사용하기

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초를 주기로 켜짐과 꺼짐을 반복함을 확인할 수 있습니다.

CategoriesnRF52 Lecture

Lecture 4. UART로 PC와 통신하기

Lecture 4. UART로 PC와 통신하기

강의목표

  • nRF52 개발 보드에 내장된 UARTE-USB Birdge를 사용하여  PC와 Serial 통신을 할 수 있다.
  •  UARTE 통신으로 PC에서 보드의 RGB LED를 켜고 끌 수 있다.
  • UARTE에 관한 다양한 설정과 핀 설정 등 사용법을 익힌다.

준비물

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

UARTE 통신 및 기본 설정

UARTE 통신은 Universal asynchronous receiver/transmitter with EasyDMA의 약자로 일반적인 UART 통신에 MCU의 동적 메모리에 손쉽게 접근할 수 있는 Nordic 사의 고유 기능인 EasyDMA를 추가하여 손쉽게 UARTE 통신 및 인터럽트 기반 통신을 수행할 수 있도록 구성한 것입니다. 실제 물리적인 동작은 기존의 UART와 동일합니다. 각각 RX, TX 선과 CTS, RTS 선을 이용하여 통신을 수행하며 Parity Bit, Flow Control 등의 기능 또한 활용할 수 있습니다. 

 nRF52 시리즈의 UARTE 기능은 기본적으로 HAL(Hardware Access Layer)를 이용하여 설정하고 사용할 수 있습니다. 위 이미지는 nRF52 시리즈에서 UARTE를 사용하는 간단한 방법에 대한 예시입니다. UARTE를 사용하기 위해서는 기본적으로 RX, TX 핀을 설정하고 각각의 데이터를 저장할 버퍼를 설정하며 통신 속도(Baudrate)를 설정하여 사용할 수 있습니다. 뿐만 아니라 TX와 RX에 관한 다양한 이벤트가 있으며 각 이벤트는 인터럽트와 연동되어 사용할 수 있습니다. UARTE 는 기본적으로 다른 IC 혹은 MCU 등과 연결되어서 사용할 수 있으며, 특히 UART to USB Bridge IC를 이용하면 컴퓨터의 COM 포트를 통하여 직접 컴퓨터와 통신할 수 있습니다.

Roverdyn의 nRF52 개발 보드는 버전에 따라 두 가지 버전의 UART to USB Bridge IC를 지원합니다. Revision 1의 경우 SiliconLabs 사의 CP2104 브릿지 IC를 내장하였으며 Revision 2의 경우 CH340BN 브릿지 IC를 내장하였습니다. 두 IC 모두 nRF52시리즈 MCU가 지원하는 최대 속도인 1Mbps 이상의 통신 속도를 지원하며 두 버전의 개발 보드 모두 동일한 RX, TX 핀을 사용하고 있습니다. 사용하는 핀은 다음과 같습니다.

  • RX핀 : P0.07
  • TX핀 : P0.08

개발 보드의 버전에 따라 브릿지 IC의 종류와 연결되는 핀은 달라질 수 있으니 반드시 데이터시트를 사전에 확인하시기 바랍니다.

UARTE 설정 및 사용하기

UARTE를 사용하기 위해서는 기본적인 설정을 해주어야 합니다. 다음은 필수로 설정하여야 하는 설정입니다.

  • NRF_UARTE0->BAUTEDATE : 통신 속도 설정
  • NRF_UARTE0->RXD.PTR : 수신 데이터 버퍼
  • NRF_UARTE0->TXD.PTR : 송신 데이터 버퍼
  • NRF_UARTE0->PSEL.RXD : 수신 핀 번호
  • NRF_UARTE0->PSEL.TXD : 송신 핀 번호
  • NRF_UARTE0->ENABLE : UARTE 기능 활성화

위 항목 중 Baudrate는 통신 속도를 설정할 수 있습니다. Baudrate의 범위는 9600bps ~ 1MBaud까지 지원합니다. RXD.PTR, TXD.PTR은 송수신 데이터 버퍼를 지정하며, 데이터를 저장할 버퍼의 포인터 주소를 지정하는 방법으로 사용할 수 있습니다. PSEL.RXD와 PSEL.TXD는 각각 Bridge IC와 연결된 RX핀과 TX핀의 번호를 입력하면 됩니다. 마지막으로 ENABLE은 해당 모듈을 활성화 할 것인지 결정하는 것으로서, 활성화 하기 위해서는 0x08 값을 넣어주면 됩니다.

만약 인터럽트 기능을 이용하고자 한다면 다음 항목을 설정하여야 합니다.

  • NRF_UARTE0->INTENSET
  • NRF_UARTE0->SHORTS

INTENSET은 사용하고자 하는 인터럽트 종류를 설정하는데 사용됩니다. 다양한 인터럽트 이벤트가 있으며 이 튜토리얼에서는 ENDTX, ENDRX, RXDRDY 이벤트의 인터럽트를 사용합니다. SHORTS는 말 그대로 지름길을 설정하는 레지스터로서,  ENDRX_STARTRX 혹은 ENDRX_STOPRX를 설정할 수 있습니다. 이 레지스터를 설정하게 되면 ENDRX 이벤트가 생성되면 자동으로 STARTRX 태스크를 수행하거나 STOPRX 태스크를 수행합니다.

또한 인터럽트는 Zephyr RTOS의 인스턴스를 활성화하여야 사용할 수 있으므로 다음 함수를 이용하여 인터럽트를 활성화 할 수 있습니다.

  • IRQ_DIRECT_CONNECT
  • irq_enable

IRQ_DIRECT_CONNECT 함수는 매개변수로 각각 인터럽트 인스턴스, 인터럽트 Priority, 리스너 함수, IRQ 설정 플래그를 입력하면 됩니다. 대개, 인터럽트 우선순위나 IRQ 설정 플래그는 0으로 설정하여 사용할 수 있습니다. irq_enable은 인터럽트 인스턴스를 활성화 시키는 함수이며, 이 튜토리얼에서는 UARTE0_UART0_IRQn 인스턴스를 사용하였습니다.

 

소스 코드

				
					/*
        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>

/* Pin definition */
#define GPIO_LED_RED            22
#define GPIO_LED_GREEN          23
#define GPIO_LED_BLUE           24
#define UARTE_PIN_RX            8
#define UARTE_PIN_TX            7

/* UARTE Buffer */
uint8_t rxBuffer[255] = {0}, txBuffer[255] = {0};

/* UARTE Functions */
void write(uint8_t *data, uint8_t length);
void UARTE_Listener();
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);

        // UARTE Configuration
        NRF_UARTE0->BAUDRATE = UARTE_BAUDRATE_BAUDRATE_Baud115200;
        NRF_UARTE0->PSEL.RXD = UARTE_PIN_RX;
        NRF_UARTE0->PSEL.TXD = UARTE_PIN_TX;
        NRF_UARTE0->TXD.PTR = (uint32_t)txBuffer;
        NRF_UARTE0->RXD.PTR = (uint32_t)rxBuffer;
        NRF_UARTE0->SHORTS = UARTE_SHORTS_ENDRX_STARTRX_Enabled << UARTE_SHORTS_ENDRX_STARTRX_Pos;
        NRF_UARTE0->INTENSET = UARTE_INTEN_RXDRDY_Enabled << UARTE_INTEN_RXDRDY_Pos;
        NRF_UARTE0->INTENSET = UARTE_INTEN_ENDRX_Enabled << UARTE_INTEN_ENDRX_Pos;
        NRF_UARTE0->INTENSET = UARTE_INTEN_ENDTX_Enabled << NFCT_INTEN_ENDTX_Pos;
        NRF_UARTE0->ENABLE = 0x08;
        NRF_UARTE0->RXD.MAXCNT = 255;
        NRF_UARTE0->TASKS_STARTRX = 1;
        
        // Enable IRQ Handler
        IRQ_DIRECT_CONNECT(UARTE0_UART0_IRQn, 0, UARTE_Listener, 0);
        irq_enable(UARTE0_UART0_IRQn);

        while(true){
                k_usleep(10);
        }
        return 0;
}

void write(uint8_t *data, uint8_t length){
        // Function for UARTE Write
        memcpy(txBuffer, data, length);
        NRF_UARTE0->TXD.MAXCNT = length;
        NRF_UARTE0->TASKS_STARTTX = 1;
}

void UARTE_Listener(){
        // Function for UARTE RXDRDY interrupt
        static uint8_t order = 0;
        if(NRF_UARTE0->EVENTS_ENDTX == 1){
                // Clear Tx Buffer
                NRF_UARTE0->EVENTS_ENDTX = 0;
                memset(txBuffer, 0x00, sizeof(uint8_t));
        }

        if(NRF_UARTE0->EVENTS_RXDRDY == 1){
                // Clear and Stop RxBuffer
                printk("RX data received : %c\r\n", rxBuffer[order]);
                write(&rxBuffer[order], 1);
                if(rxBuffer[order] == 0x0D){
                        NRF_UARTE0->TASKS_STOPRX = 1;
                }else{
                        order++;
                }
                NRF_UARTE0->EVENTS_RXDRDY = 0;
        }

        if(NRF_UARTE0->EVENTS_ENDRX == 1){
                // Clear and Stop RxBuffer
                printk("%d byte received.\r\n", strlen(rxBuffer));
                write(rxBuffer, strlen(rxBuffer));

                // Control LED
                if(strncmp(rxBuffer, "off", 3) == 0){
                        NRF_GPIO->OUTSET = (1 << GPIO_LED_RED) | (1 << GPIO_LED_GREEN) | (1 << GPIO_LED_BLUE);
                }else if(strncmp(rxBuffer, "red", 3) == 0){
                        NRF_GPIO->OUTCLR = 1 << GPIO_LED_RED;
                }else if(strncmp(rxBuffer, "green", 3) == 0){
                        NRF_GPIO->OUTCLR = 1 << GPIO_LED_GREEN;
                }else if(strncmp(rxBuffer, "blue", 3) == 0){
                        NRF_GPIO->OUTCLR = 1 << GPIO_LED_BLUE;
                }

                // Clear parameter
                order = 0;
                memset(rxBuffer, 0x00, sizeof(uint8_t));
                NRF_UARTE0->EVENTS_ENDRX = 0;
        }
}
				
			

빌드 및 컴파일, 다운로드

작성한 소스 코드는 빌드 및 컴파일을 한 후 최종적으로는 디버거 혹은 프로그래머 장비를 이용하여 개발 보드에 다운로드할 수 있습니다. 개발 보드에 관한 설정은 “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 혹은 기타 디버거, 프로그래머와 연결이 되어 있어야 합니다.

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

동작 시켜보기

이 예제를 실행하기 위해서는 먼저 PuTTY 프로그램을 이용하여 동작 시킬 수 있습니다. PuTTY는 공식 홈페이지에서 다운로드 할 수 있습니다. PuTTY를 실행시킨 후 “Connection type” 항목에서 “Serial”을 선택한 후 상단의 Serial Line에서 COM포트 번호와 Speed를 입력합니다. 연결되어 있는 COM포트 번호는 운영체제의 “장치관리자” 혹은 각 운영체제에 맞는 방법으로 확인할 수 있으며 사용하는 환경에 따라 포트 번호는 달라질 수 있습니다. 위 이미지에서는 COM4 포트에 개발 보드가 연결되어 있으며 통신 속도는 115200으로 설정하였습니다. 설정한 후 “Open” 버튼을 눌러 개발 보드와 연결합니다.

정상적으로 연결되었다면 위와 같이 소문자로 “green” 이라고 입력한 후 엔터 키를 누르면 개발 보드의 초록색 LED가 켜지는 것을 확인 할 수 있습니다. 마찬가지로 “off” 글자를 입력하면 모든 LED가 꺼지는 것을 확인할 수 있습니다. 이 튜토리얼은 백스페이스키를 지원하지 않으므로 백스페이스 키를 이용하여 입력 명령어를 수정할 수 없습니다.

 Red, Green, Blue LED를 각각 켜고 꺼서 3색 이외에도 조합된 색상을 얻을 수 있습니다.

CategoriesnRF52 Lecture

Lecture 3. 버튼과 LED 제어하기(GPIOTE)

Lecture 3. 버튼과 LED 제어하기(GPIOTE)

강의목표

  • nRF52 개발 보드에 내장된 버튼의 입력을 받아 RGB LED를 제어할 수 있다.
  • GPIOTE(GPIO Task & Event) 레지스터를 제어할 수 있다.
  • GPIOTE의 인터럽트 기능을 활용하여 이벤트를 처리할 수 있다.

준비물

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

GPIOTE 레지스터 설정 및 사용하기

GPIOTE는 General Purpose Input/Output Task & Event의 약자로서 말 그대로 GPIO의 작업과 이벤트를 담당하는 레지스터입니다. GPIO의 작업으로는 SET, CLEAR 등과 같이 핀의 출력을 설정하였을 때 어떠한 동작을 할 것인지 설정하는 것이 있으며 Event는 GPIO의 값의 변화에 따라 동작하는 모드입니다. GPIOTE는 단순히 GPIO를 이용하는 것 보다 더 간단하고 효율적으로 GPIO의 동작을 제어하거나 이벤트를 캡쳐하여 효율적인 펌웨어를 개발하는데 도움을 줍니다.

GPIOTE Event

GPIOTE 이벤트는 각 GPIO의 입력에 따라 동작하는 모드입니다. 각 이벤트는 다음과 같은 이벤트를 입력 받을 수 있습니다.

  • Rising Edge : GPIO핀 값이 Low -> High로 변할 때 동작
  • Falling Edge : GPIO핀 값이 High->Low로 변할 때 동작
  • Any changes : GPIO핀 값의 상태가 High 혹은 Low에서 다른 상태로 변할 때 동작

GPIOTE Task

GPIOTE Task는 GPIO의 입력 혹은 동작에 따라 특정 동작을 지정하여 실행시킬 수 있습니다. 매크로에 가까운 개념으로 인터럽트와 결합하여 다양한 동작을 수행시킬 수 있습니다.

본 튜토리얼은 GPIOTE의 Event 기능을 이용하여 내장 버튼의 입력을 받아 개발 보드의 LED를 제어합니다. 이를 위해서 GPIOTE의 핀 번호 할당, 인터럽트 설정을 하여 각 LED를 제어할 수 있습니다. 또한, LED 켜지는 순서를 제어하기 위해서 간단한 알고리즘을 구현하여야 합니다.

로버다인의 nRF52 개발 보드는 모두 세 개의 버튼이 있으며 이 중 하나는 Reset 핀에 연결되어 하드웨어 리셋을 수행할 수 있습니다. 각 버튼은 다음과 같은 핀에 연결되어 있습니다.

  • 버튼1 : P0.19
  • 버튼2 : p0.20
  • 리셋버튼 : p0.21/RESET

 리셋 버튼은 일반 GPIO포트인 P0.21로 변경하여 사용할 수 있으나 별도의 설정이 필요합니다.

GPIOTE 설정 및 사용하기

GPIO의 레지스터는 몇 가지 설정을 하여 사용할 수 있습니다. 다음은 기본적인 GPIO의 설정입니다.

  • NRF_GPIO->DIRSET : 핀의 출력 방향을 설정합니다.
  • NRF_GPIO->OUTSET : 핀을 출력으로 설정하였을 때 핀을 High로 설정합니다.
  • NRF_GPIO->OUTCLR : 핀을 출력으로 설정하였을 때 핀을 Low로 설정합니다.

GPIO 레지스터의 값을 입력하기 위해서는 설정하고자 하는 핀의 비트를 0 혹은 1로 설정하여 제어할 수 있습니다. 이를 확인하기 위해 먼저 nRF52 시리즈의 데이터 시트를 확인합니다.

 위 데이터시트는 nRF52832의 데이터시트입니다. GPIO 레지스터 챕터에서 확인할 수 있는 내용으로, NRFGPIO->DIRSET 레지스터에 관한 내용입니다. 해당 데이터시트를 자세히 보면 Bit number가 0인  부분의 아이디는 ‘A’로 표시되어 있으며, 해당 비트를 0으로 설정하면 해당 핀(PIN0)을 Input으로 설정하고 1로 설정하면 Output으로 설정됩니다. 만약 22번핀(P0.22)를 출력으로 설정하고자 한다면 NRF_GPIO->DIRSET = (1 << 22) 으로 설정하면 위 데이터시트상 ID가 ‘W’인 22번 핀의 입출력 방향이 출력으로 설정됩니다. 만약 중복으로 설정하고자 한다면 Or(|) 연산자를 붙여 설정하면 됩니다.

 Zephyr RTOS는 간단한 Sleep 함수가 있습니다. 메인 쓰레드에서 사용할 수 있는 k_msleep 이라는 함수는 매개 변수로  밀리초(ms) 단위로 입력받아 CPU의 동작을 중지 시킬 수 있습니다. 예를 들어 k_msleep(1000)으로 설정하면 1초간 지연 시킨다는 뜻입니다. 또 다른 함수로는 k_usleep이 있으며 k_msleep 함수와 비슷하지만 매개 변수로는 마이크로초 단위(us)를 입력 받습니다.

 보다 자세한 내용은 아래 소스코드를 참조해주세요.

소스 코드

				
					/*
        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>

/* Button pin no. definition */
#define USER_BTN1       19
#define USER_BTN2       20

/* LED pin no. definition */
#define LED_RED         22
#define LED_GREEN       23
#define LED_BLUE        24

/* Define Event Lisenter */
void GPIOTE_IRQHandler();

/* Global Variables */
static uint8_t led_order = 0;

int main(void)
{
        // Debug message for start
        printk("Application start.\n");

        // Set gpio direction for LEDs & turn off
        NRF_GPIO->DIRSET = (1 << LED_RED) | (1 << LED_GREEN) | (1 << LED_BLUE);
        NRF_GPIO->OUTSET = (1 << LED_RED) | (1 << LED_GREEN) | (1 << LED_BLUE);

        // Set Button GPIO as Input
        NRF_GPIO->PIN_CNF[USER_BTN1] = 0x0C;
        NRF_GPIO->PIN_CNF[USER_BTN2] = 0x0C;

        // Set Interrupt Event for buttons
        NRF_GPIOTE->INTENSET = 0x01;                    // Enable EVENT_IN[0]
        NRF_GPIOTE->INTENSET = (0x01 << 4);                   // Enable EVENT_IN[4]
        
        // Set GPIOTE for user button 1
        NRF_GPIOTE->CONFIG[0] = 0x01;                   // Set Event mode for EVENT_IN[0]
        NRF_GPIOTE->CONFIG[0] |= (USER_BTN1 << 8);      // Set pin no. for EVENT_IN[0]
        NRF_GPIOTE->CONFIG[0] |= (0x02 << 16);          // Set HiToLo polarity for EVENT_IN[0]
        
        // Set GPIOTE for user button 2
        NRF_GPIOTE->CONFIG[4] = 0x01;                   // Set Event mode for EVENT_IN[4]
        NRF_GPIOTE->CONFIG[4] |= (USER_BTN2 << 8);      // Set pin no. for EVENT_IN[4]
        NRF_GPIOTE->CONFIG[4] |= (0x02 << 16);          // Set HiToLo polarity for EVENT_IN[4]
        
        // Enable IRQHandler for interrupt
        IRQ_DIRECT_CONNECT(GPIOTE_IRQn, 0, GPIOTE_IRQHandler, 0);
        irq_enable(GPIOTE_IRQn);

        // Main loop
        while(true){
                k_usleep(1);
        }
        return 0;
}

void GPIOTE_IRQHandler(){
        // Interrupt event for button 1
        if(NRF_GPIOTE->EVENTS_IN[0] == 1){
                // Button 1 : Shift color to left
                NRF_GPIOTE->EVENTS_IN[0] = 0;
                if(led_order == 0){
                        led_order = 2;
                }else{
                        led_order--;
                }

                // Turn off all LEDs
                NRF_GPIO->OUTSET = (1 << LED_RED) | (1 << LED_GREEN) | (1 << LED_BLUE);
                switch(led_order){
                        case 0:
                                printk("Button 1 pressed : LED Red\n");
                                NRF_GPIO->OUTCLR = (1 << LED_RED);
                        break;
                        case 1:
                                printk("Button 1 pressed : LED Green\n");
                                NRF_GPIO->OUTCLR = (1 << LED_GREEN);
                        break;
                        case 2:
                                printk("Button 1 pressed : LED Blue\n");
                                NRF_GPIO->OUTCLR = (1 << LED_BLUE);
                        break;
                }                
        }

        // Interrupt event for button 2
        if(NRF_GPIOTE->EVENTS_IN[4] == 1){
                // Button 2 : Shift color to right
                NRF_GPIOTE->EVENTS_IN[4] = 0;
                if(led_order == 2){
                        led_order = 0;
                }else{
                        led_order++;
                }

                // Turn off all LEDs
                NRF_GPIO->OUTSET = (1 << LED_RED) | (1 << LED_GREEN) | (1 << LED_BLUE);
                switch(led_order){
                        case 0:
                                printk("Button 2 pressed : LED Red\n");
                                NRF_GPIO->OUTCLR = (1 << LED_RED);
                        break;
                        case 1:
                                printk("Button 2 pressed : LED Green\n");
                                NRF_GPIO->OUTCLR = (1 << LED_GREEN);
                        break;
                        case 2:
                                printk("Button 2 pressed : LED Blue\n");
                                NRF_GPIO->OUTCLR = (1 << LED_BLUE);
                        break;
                }  
        }
}
				
			

빌드 및 컴파일, 다운로드

작성한 소스 코드는 빌드 및 컴파일을 한 후 최종적으로는 디버거 혹은 프로그래머 장비를 이용하여 개발 보드에 다운로드할 수 있습니다. 개발 보드에 관한 설정은 “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 혹은 기타 디버거, 프로그래머와 연결이 되어 있어야 합니다.

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

동작 시켜보기

이 예제를 실행시키기 위해서는 프로그램을 컴파일 및 다운로드한 후 리셋 버튼을 누르거나 전원을 분리 후 재인가 하여 동작 시킬 수 있습니다. 

 동작 시 보드에 내장된 버튼을 눌러 각 LED의 색상을 변경할 수 있습니다. 1번 버튼을 누를 경우 파랑->초록->빨강 순서대로 색상이 변경되며 2번 버튼을 누를 경우 빨강->초록->파랑 순서대로 색상이 변경됩니다.

위 코드를 수정하여 버튼의 동작을 바꿀 수 있습니다. 예를 들어 1회 클릭이 아닌 두 번 클릭으로 색상이 변경되도록 하던지, 아니면 두 버튼을 동시에 누르면 LED가 꺼지도록 할 수 있습니다.

 다양한 방법으로 이 예제를 활용해 보시기 바랍니다.