ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [C/C++] 라즈베리파이 피코 기본입출력 -GPIO
    라즈베리파이 피코 2023. 2. 25. 14:27

    어느 마이크로컨트롤러를 사용하든, 핀 입출력, 디버그를 위한 USB Serial 통신, Uart/I2C/SPI 통신, 인터럽트(핀, 타이머/카운터)  정도 쓸 수 있으면 웬만한건 다 만들 수 있다. 이번에는 이중에서 핀 입출력과 기본 핀 인터럽트를 다루고자 한다.

    파이썬 자료에 비해 C/C++ 자료는 매우 적은데 이 글이 C/C++로 피코를 하려는 사람들에게 도움이 되면 좋겠다.

     

     출력

    하나의 핀을 설정하는 경우

    //내장 LED 켜기
    //PICO_DEFAULT_LED_PIN = 25
    gpio_init(25); //gpio_set_funct(25, GPIO_FUNC_SIO) 이것과 같다.
    gpio_set_dir(25, GPIO_OUT);
    gpio_put(25, 1);

    Initialize 초기화

    set_direction 방향(입출력) 설정

    put 출력(1이라는 상태를 해당 핀에 둔다.)

     

    기본 입출력이 아닌 다른 기능을 위해 핀을 사용하는 경우

    gpio_set_function (uint gpio, enum gpio_function fn)
    
    //function 열거체 정의
    gpio_function {
      GPIO_FUNC_XIP = 0 , GPIO_FUNC_SPI = 1 , GPIO_FUNC_UART = 2 , GPIO_FUNC_I2C = 3 ,
      GPIO_FUNC_PWM = 4 , GPIO_FUNC_SIO = 5 , GPIO_FUNC_PIO0 = 6 , GPIO_FUNC_PIO1 = 7 ,
      GPIO_FUNC_GPCK = 8 , GPIO_FUNC_USB = 9 , GPIO_FUNC_NULL = 0x1f
    }

     

    이 함수를 사용해야 한다.

     

    여러 핀을 설정하는 경우: 비트 마스크

    처음에야 핀을 한두 개 정도 사용하는 것에 그치더라도 나중에는 많은 수의 핀을 다루게 된다. 

    총 30개(0~29)의 GPIO 중 내부 기능으로 쓰이는 4개를 제외하고 26개의 GPIO를 다룰 수 있다. 설정해야될 핀이 4개 넘어가면서 부터 위에 방법대로 한다면 많은 노가다를 해야한다. 비트 마스크를 이용해 한번에 제어할 수 있다면 훨씬 효율적인 코드 작성이 가능할 것이다.

     

    비트 마스크에 대해 이해를 할려면 진법과 몇가지 불 대수에 관한 지식이 필요하다.

     

    진법

    먼저 진법이란 수를 세는 법이다. 우리는 십진법을 사용하는데, 0~9까지 10개의 숫자가 지나고 나면 자릿수가 하나 올라간다. 이진법이면 0, 1, 0b10(2), 0b11(3), 0b200(4), ... 이렇게 자릿수가 올라가고, 16진법인 경우 10부터는 알파벳 a로 대응시켜서 1, 2, 3, ..., 9, A(10), B(11), C(12), D(13), E(14), F(15), 0x10(16) 이렇게 자릿수가 올라가게 된다.

     

    진법 한눈에 보기

    예시 십진수 1234 = 1*10^3 + 2*10^2 + 3*10^1 + 4*10^0

    자리별 숫자 1 2 3 4
      10^3 10^2 10^1 10^0

     

    예시 이진수 1011 = 2^3 + 2^1 + 2^0

    자리별 숫자 1 0 1 1
      2^3 2^2 2^1 2^0

     

    예시 16진수 EF

    자리별 숫자 E(14) F(15)
      14*16^1 15*16^0

     

    십진수로 N진법의 각 자리수가 의미하는 수는 가장 낮은 자리수부터 N^0, N^1, ... 이렇게 대응이 된다.

    꺼꾸로 십진수를 N진법으로 바꿔 표현하고 싶다면, 크지 않은 범위에서 N^x 을 찾아 빼나가면 된다.

     

    ex) 50을 이진법으로 표현하면?

    2^6 = 64, 2^5 = 32이고,                                         32 = 0b100000,

    32를 빼고 남은 18은 2^4에 가장 가까우니    32 + 16 = 0b110000,

    다시 16을 빼면 2가 남으니 최종적으로   32 + 16 + 2 = 0b110010

     

    그런데 이진수는 0을 몇개, 1을 몇개 써야되는지 실수가 발생하기 쉽다. 그래서 0과 1을 좀 덜쓰기 위해 이진법과 변환이 쉬운 16진법을 사용하는 것이다.

     

    이진수  0b110010을 16진수로 바꾸기 위해서는 낮은 자리부터 4글자씩 끊어서 바꿔주면 된다.

    11 0010인데 이는 0011 0010과 같고

    이진수 0011 0010
    16진수 3 2

    16진수로 변환하면 0x32가 된다.

     


    기존값을 모르는 상태에서 레지스터같은데 새로운 값을 대입해버리면, 큰 문제가 생길 수 있다. 나는 사용하지 않았지만 라이브러리 안에서 사용되고 있는 중일 수도 있기 때문이다. 때문에 특정 비트만 설정하는 법을 알아야 한다. 

    특정 비트 세트

    특정 비트를 세트 하기위해서는 or 연산을 하면 된다. or 연산은 둘 중에 하나라도 1이면 결과도 1이된다.

    비트 마스크 0 1 1 0
    기존 상태 1 0 1 0
    연산 결과 1 1 1 0

    *이러한 비트 마스크는 (1 << 3) | (1 << 2)로도 얻을 수 있다. << 오른 쪽의 숫자가 몇번째 비트인지를 나타내므로, 직관적으로 몇번 비트에 대한 비트마스크 인지 알아볼 수 있다.

     

    비트 마스크의 0과 or 하는 경우 기존 상태가 그대로 반영되게 된다.

    비트 마스크의 1과 or하게 되면 비트마스크에 1이 있으니 무조건 1로 반영된다.

     

    특정 비트 클리어

    특정 비트를 클리어 하기위해서는 and 연산을 이용한다. and 연산은 둘 다 1이어야 결과도 1로 출력이 되는데 비트 마스크가 1이라면 기존 값이 그대로 유지되고, 0이라면 확실하게 0으로 클리어가 된다. 따라서 비트 마스크를 만든 뒤 반전시켜야 한다.

    비트 마스크 1 0 0 1
    기존 상태 1 0 1 0
    연산 결과 1 0 0 0

    *이러한 비트 마스크는 ~( (1 << 3) | (1 << 2) )로도 얻을 수 있다.

    특정 비트 반전

    특정 비트를 반전하기 위해 xor(베타적 논리합)을 사용한다. xor은 다른 경우 1, 같은 경우 0을 출력한다.

    비트 마스크 0 1 1 0
    기존 상태 1 0 1 0
    연산 결과 1 1 0 0

    비트 마스크가 0이라면 기존 상태가 1인경우 1이 나와 그대로 유지된다. 그리고 기존 상태가 0인 경우 두 비트가 같으니 0이 출력되어 값이 유지된다.

    비트 마스크가 1이라면 기존상태가 0인경우 1이 나와 반전되며, 기존상태가 1인 경우 두 비트가 다르니 0이 출력되어 값이 반전된다.


    #함수가 내부적으로 레지스터에 or, and, xor 연산을 해주기 때문에 설정할 핀에 대한 비트 마스크만 입력하면 된다.
    #gpio 초기화(I/O 로 설정)
    gpio_init_mask(비트 마스크);
    
    #입출력 설정
    gpio_set_dir (uint gpio, bool out)
    gpio_set_dir_out_masked(비트 마스크) #gpio_set_dir_masked(bitmask, bitmask)와 같음
    gpio_set_dir_in_masked(비트 마스크) #gpio_set_dir_masked(비트 마스크, 0) 과 같음
    gpio_set_dir_masked(대상 핀 비트 마스크, OUT으로 설정할 핀 비트 마스크)
    gpio_set_dir_all_bits (uint32_t values) #주의 모든 핀을 대상으로 값을 씀
    gpio_set_input_enabled (uint gpio, bool enabled) #gpio_set_dir(gpio, 0)과 같은 역할을 함
    
    #출력
    #아두이노의 digitalWrite와 같은 역할
    gpio_set_mask(비트 마스크); #gpio_put_masked(bitmask, bitmask) 와 같음
    gpio_clr_mask(비트 마스크); #gpio_put_masked(비트 마스크, 0) 과 같음
    gpio_xor_mask(비트 마스크);
    
    gpio_put_masked(대상 핀 비트 마스크, HIGH/LOW 상태) #위의 set, clr, xor 보다 속도가 떨어짐
    gpio_put_all (uint32_t value) #주의 모든 핀 대상으로 값을 쓰게 됨
    
    #현재 GPIO 핀 설정 상태 가져오기
    gpio_get_dir (uint gpio)
    gpio_get_function (uint gpio)

    예제

    라즈베리파이 피코는 3.3V를 사용하고, 붉은 색 LED는 통상 20mA, 2.1V를 사용하므로 1.2V를 추가로 저항에서 강하시켜줘야 한다.(LED의 파장이 짧을 수록 더 많이 전압을 강하시킨다.)

    R=V/I 에서 1.2V / 0.02A = 60Ω 이다. 가지고 있는 저항 중에 가장 작은 게 100Ω이니 병렬 저항을 통해 비슷한 값을 맞춰야 한다.

    100Ω과 220Ω을 병렬로 사용하면

    1/R = 1/100 + 1/220 에서  R = 68.75Ω(저항 오차 범위 ±5%이니  65.3125Ω ~  71.1875Ω)으로 밝기는 약간 줄겠지만 충분히 LED와 저항의 오차범위 안에서 작동할 수 있다.(만약 이렇게 근사값을 사용한다 하더라도 68.75Ω 보다 낮은 저항을 사용하면 회로가 데미지를 입을 수 있다.)

     

    그다음에 저항에 불을 내지 않으려면 소비 전력을 맞춰야 한다. 1/4W 저항을 사용하는데, 저항에서 강하되는 전압이 1.30625V~1.44375V이며, 최대 LED 8개가 동시에 작동하니 0.16A가 흐를 수 있다. 따라서 저항에서 소비되는 전력은 P = IV에서 0.209W ~ 0.231W 이며, 1/4W= 0.250W를 넘지 않으니 충분히 사용할 수 있는 범위이다. 저항에서 소비된 전력은 모두 열로 바뀌게 되며, 저항의 소비 전력보다 큰 전력이 소모되면 저항이 탈 수 있으니 조심하여야 한다.

     

    마지막으로 라즈베리파이 피코의 데이터 시트를 보면 3.3V OUT 핀은 300mA보다 적게 부하를 주라고 되어있고, LED 8개에 160mA이니 괜찮다.

     

    어려운 내용이 아니라 중학교에서도 배우고, 고등학교에서 한 번 더 배우는 내용이다. 학교에서 배웠으면 이 정도는 활용할 수 있어야 한다고 생각한다.

     

    #include "pico/stdlib.h"
    
    int main() {
        //하나의 핀을 설정하는 경우
        //PICO_DEFAULT_LED_PIN = 25
        gpio_init(25); //gpio_set_funct(25, GPIO_FUNC_SIO); 이것과 같다.
        gpio_set_dir(25, GPIO_OUT);
        gpio_put(25, 1);
    
        /*
        여러 핀을 설정하는 경우 -> 비트마스크 활용
        GP0는 0번째 비트에 대응되고
        GP3은 3번째 비트에 대응됨
        이진수를 네자리씩 끊어 읽어서 16진수로 변환한다.
         0000 0000 0000 0000 0000 0011 1111 1100
          0    0     0    0    0   3     f    c
         0x000003fc
        */
        uint32_t bitmask = 0x000003fc;
        /*
        추천은 안하지만 이진법이 어렵다면 이런 방법도 있다.
        
        핀 번호들이 연속적이면 이렇게도 쓸 수 있고
        for(int i=2; i<9; i++) {
            bitmask |= (1 << i);
        }
        연속적이지 않다면 배열을 이용해 비트마스크를 만들 수 있다.
        배열을 이용해 비트 마스크를 만든 뒤 설정해도, 하나하나 함수를 불러 설정하는 것 보다 훨씬 빠르다.
    
        갯수가 얼마 되지 않는다면 가독성을 위해
        bitmask = (1 << 2) | (1 << 3) | (1 << 4);
        이렇게 쓸 수도 있다.
        */
        gpio_init_mask(bitmask);
        gpio_set_dir_out_masked(bitmask);
        /*
        gpio_set_dir_masked(bitmask, bitmask);
        이렇게도 쓸 수 있다.
    
        만약 전부다 in으로 설정하려면
        gpio_set_dir_in_masked(bitmask); 또는
        gpio_set_dir_masked(bitmask, 0);
    
        이중 특정 핀들만 out으로 설정하고 나머지는 in으로 설정하고 싶다면
        out으로 설정할 핀에 해당하는 비트만 1로 나두고 나머지는 0으로 해서 비트 마스크를 만들어야 한다.
    
        가령 gpio_set_dir_masked(0x3,0x2) 하게되면 1번핀은 input으로 설정되고, 2번핀은 output으로 설정된다.
    
        전체에서 한두개 정도 in으로 바꾸려면
        gpio_set_dir_masked(bitmask, bitmask & ~( (1 << 3) | (1 << 5) ) )
        이렇게도 가능하다
        */
         
        gpio_put_masked(bitmask, bitmask);
        sleep_ms(500);
        gpio_put_masked(bitmask, 0);
        sleep_ms(500);
    
        //gpio 핀 반전 -> xor 연산, 반전이니 2번 실행해야 한번 깜빡임
        for(int i=0; i<2; i++) {
            gpio_xor_mask(bitmask);
            sleep_ms(500);
        }
        sleep_ms(2000);
    
        // 0000 0000 0000 0000 0000 0001 0101 0100
        //  0    0     0    0    0   1     5    4
        // 0x00000154
       
        gpio_set_mask(0x00000154); //gpio_put_masked(0x00000154, 1)과 효과는 같지만 조금더 빠름
        for(int i=0; i<4; i++) {
            gpio_xor_mask(bitmask);
            sleep_ms(500);
        }
        gpio_clr_mask(bitmask); //gpio_put_masked(bitmask, 0)과 효과는 같지만 조금더 빠름
        sleep_ms(2000);
    
        //이 패턴은 직접 레지스터에 쉬프트 연산을 하는게 효율적인 코드를 작성할 수 있지만
        //다른 사용 중인 핀에 영향을 주니 고려해야될 게 많고, 위험하다.
        for(int n=0; n<3; n++) {
            for(int i=0; i<4; i++) {
                uint32_t bitmask2 = (0x00000004 << i) | (0x00000200 >> i);
                gpio_set_mask(bitmask2);
                sleep_ms(500);
                gpio_clr_mask(bitmask2);
            }
    
            for(int i=2; 0<i; i--) {
                uint32_t bitmask2 = (0x00000004 << i) | (0x00000200 >> i);
                gpio_set_mask(bitmask2);
                sleep_ms(500);
                gpio_clr_mask(bitmask2);
            }
        }
        gpio_set_mask(0x00000204);
        sleep_ms(500);
        gpio_clr_mask(0x00000204);
        
        //참고로 gpio_put_masked 보다는 gpio_set_mask, gpio_clr_mask, gpio_xor_mask가 빠르다.
    
    }

    #gpio_exam/CMakeLists.txt
    cmake_minimum_required(VERSION 3.12)
    
    # Pull in SDK (must be before project)
    set(BToolDIR $ENV{PICO_SDK_PATH}/../pico-examples)
    include(${BToolDIR}/pico_sdk_import.cmake)
    
    include(${BToolDIR}/pico_extras_import_optional.cmake)
    
    project(pico_examples C CXX ASM)
    set(CMAKE_C_STANDARD 11)
    set(CMAKE_CXX_STANDARD 17)
    
    if (PICO_SDK_VERSION_STRING VERSION_LESS "1.3.0")
        message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.3.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}")
    endif()
    
    set(PICO_EXAMPLES_PATH ${PROJECT_SOURCE_DIR})
    
    # Initialize the SDK
    pico_sdk_init()
    
    include(${BToolDIR}/example_auto_set_url.cmake)
    
    #빌드할 코드가 담긴 폴더 이름 입력
    add_subdirectory(gpio_mask)
    
    add_compile_options(-Wall
            -Wno-format          # int != int32_t as far as the compiler is concerned because gcc has int32_t as long int
            -Wno-unused-function # we have some for the docs that aren't called
            -Wno-maybe-uninitialized
            )
    #gpio_exam/gpio_mask/CMakeLists.txt
    set(projname gpio_mask)
    
    file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS
            ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp
    )
    
    add_executable(${projname} ${SRC_FILES})
    
    # pull in common dependencies
    target_link_libraries(${projname} pico_stdlib)
    
    # create map/bin/hex file etc.
    pico_add_extra_outputs(${projname})
    
    # add url via pico_set_program_url
    example_auto_set_url(${projname})

     


    입력

    #현재 핀 상태 가져오기 high(1) or low(0)
    gpio_get(uint gpio)
    
    #현재 모든 gpio 상태 읽기, 비트 마스크를 리턴함
    gpio_get_all (void)
    
    #PULL up/down
    gpio_set_pulls (uint gpio, bool up, bool down)
    gpio_pull_up (uint gpio)
    gpio_is_pulled_up (uint gpio)
    gpio_pull_down (uint gpio)
    gpio_is_pulled_down (uint gpio)
    gpio_disable_pulls (uint gpio)

    아까랑 똑같이 저항은 100Ω과 220Ω을 병렬로 사용하였다. 예제에서는 따로 디바운스 회로를 적용하지 않아도 제대로 작동하였지만, 실제 프로젝트에서는 정확한 입력을 위해 필요할 수도 있다.

     

    #include "pico/stdlib.h"
    
    #define LED 2
    #define BUTTON 3
    
    int main() {
        gpio_init_mask((1 << LED) | (1 << BUTTON) | (1 << PICO_DEFAULT_LED_PIN));
        gpio_set_dir_masked((1 << LED) | (1 << BUTTON) | (1 << PICO_DEFAULT_LED_PIN),
                            (1 << LED) | (1 << PICO_DEFAULT_LED_PIN) );
        gpio_put(PICO_DEFAULT_LED_PIN, 1);
        gpio_pull_up(BUTTON);
    
        while(1) {
            if(!gpio_get(BUTTON)) {
                gpio_put(LED, 1);
            }
            else gpio_put(LED, 0);
        }
        
    }

    gpio_mask 에서 썻던 CMakeLists.txt에서 프로젝트 이름만 바꿔주면 된다.

     

    이렇게 소프트웨어에서 주기적으로 감시하여 핀 상태를 확인하는 방식을 폴링 방식이라고 하며, 버튼을 누르고 있을 때만 LED에 불이 들어오는 것을 확인할 수 있다. 버튼을 누르고 있기 힘들다면 xor 연산을 사용해서 반전시키면 된다. 버튼을 누를 때마다 LED 상태가 반전된다.

     

    입력 - 핀 인터럽트

    버튼같은 경우 소프트웨어 상에서 계속 감시하기 곤란한 경우가 많다. 소스 코드가 긴 경우나 중간에 사용된 sleep 함수때문에 버튼을 눌럿음에도 제대 인지하지 못할 수 있다. (LCD 같이 제대로 된 화면 표시를 위해서 어쩔 수 없이 sleep 함수를 넣어야 될 수 도 있다.) 또한 로터리 인코더처럼 정확한 제어가 필요한 경우도 인터럽트를 사용해야 한다. 물론 피코는 멀티코어 프로그래밍을 지원하긴 하지만, 이런데 사용하기에는 멀티코어가 많이 아깝고, 확인해야될 입력이 많아진다면 한계가 있을 수 밖에 없다.

     

    피코에서 핀 인터럽트를 사용하는데 callback 함수를 지정하는 방법과 인터럽트 핸들을 추가하는 방법이 있다. 대부분의 경우 사용이 간편한 콜벡 함수를 지정하면 된다. 그런데 한 핀에서 rise 때는 a를 동작시키고, fall 일때는 b를 동작시키고 싶다면 후자의 방법을 사용해야 한다.(콜벡 함수에서 if 문으로 상태를 확인해서 후자와 같은 효과를 낼 수 있지만 권장하지 않는다. 인터럽트 핸들의 실행시간은 되도록 짧게 해야하기 때문이다.)

     

    회로는 위의 버튼 예제와 같은걸 사용한다.

     

    콜벡

    콜벡 함수는 두가지 정의가 있는 데 첫째는 다른 함수의 인자로 전달되는 함수를 뜻하고, 두번째는 특정 상황에서 실행되는 함수를 말한다.(AVR의 ISR, 아두이노의 attachInterrupt가 여기에 해당한다.) 지금 사용할 콜벡은 두가지 정의를 모두 만족한다고 할 수 있다.

    gpio_set_irq_enabled_with_callback (uint gpio, uint32_t event_mask, bool enabled, gpio_irq_callback_t callback)

    여러 함수가 있지만, 한번에 모든 설정을 할 수 있는 이 함수를 사용하면 된다. 

     

    이벤트 마스크가 낮설 수 있는데

    #event mask
    enum gpio_irq_level {
        GPIO_IRQ_LEVEL_LOW = 0x1u,
        GPIO_IRQ_LEVEL_HIGH = 0x2u,
        GPIO_IRQ_EDGE_FALL = 0x4u,
        GPIO_IRQ_EDGE_RISE = 0x8u,
    };

    이렇게 정의되며, 뒤에 붙은 u는 unsigned 라는 뜻이다. 만약에 RISE일때와 FALL일 때 모두 발동시키고 싶다면(아두이노의 CHANGE, AVR의 PCINT 인터럽트와 같은 기능을 하게됨)

    GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE 이렇게 해서 event mask 부분에 넣으면 된다.

     

    다만 인터럽트인 경우 HIGH나 LOW로는 하지 않는 것을 추천한다. 핀 상태가 HIGH 나 LOW로 있는 동안 무수히 많은 인터럽트가 요청되서 처리될텐데, 이러면 메인루프가 제대로 진행되지 않을 것이다.

     

    C 인경우

    #include "pico/stdlib.h"
    
    #define BUTTON 3
    
    void gp2_callback() {
        //함수 이름은 마음대로 해도 상관 없음
        //인터럽트 발생시 실행할 내용
    };
    
    int main() {
        //해당 gpio 초기화, 입력 설정, Pull Up/down
        gpio_set_irq_enabled_with_callback(BUTTON, GPIO_IRQ_EDGE_FALL, 1, &gp2_callback);
        while(1);
        return 0;
    }

    gpio 초기화, 입력 설정은 (예제를 보니) 반드시 해야하는 건 아니지만, 명시적으로 해두자. 미연의 사고를 방지할 수 있을 것이다. 

     

    C++ 인경우

    #include "pico/stdlib.h"
    
    #define BUTTON 3
    
    void gp2_callback() {
        //함수 이름은 마음대로 해도 상관 없음
        //인터럽트 발생시 실행할 내용
    };
    
    int main() {
        //해당 gpio 초기화, 입력 설정, Pull Up/down
        gpio_set_irq_enabled_with_callback(BUTTON, GPIO_IRQ_EDGE_FALL, 1, (gpio_irq_callback_t)gp2_callback);
        while(1);
        return 0;
    }

    cpp 인경우 강제로 타입 캐스팅을 해주어야 한다. (C++ 에 있는 static_cast는 사용이 안된다.) 

    typedef void (*gpio_irq_callback_t)(uint gpio, uint32_t event_mask);

    gpio_irq_callback_t는 보이드형 함수 포인터인데, C 컴파일러는 그냥 컴파일이 가능하지만, C++ 에서는 인수 갯수가 달라 호환이 되지 않는다면서 에러를 일으키기 때문이다.

    C++17 을 사용할 수 있다고 해놓고, 제대로 호환성 테스트를 안해본것 같다. pico-sdk는 대부분 C로 작성되었으며, 일부는 C++로 작성되었는데도 말이다.(cpp 확장자로 된 소스도 있고, 오버로딩된 함수도 있는데, 예제가 죄다 C 이다 보니, 아직 개발자도 모르는 것 같다.) 

     

    버튼 입력 예제

    #include "pico/stdlib.h"
    
    #define LED 2
    #define BUTTON 3
    
    
    void gp2_callback() {
        gpio_xor_mask(0x4);
    }; 
    
    int main() {
        gpio_init_mask((1 << LED) | (1 << BUTTON) | (1 << PICO_DEFAULT_LED_PIN));
        gpio_set_dir_masked((1 << LED) | (1 << BUTTON) | (1 << PICO_DEFAULT_LED_PIN),
                            (1 << LED) | (1 << PICO_DEFAULT_LED_PIN) );
        gpio_put(PICO_DEFAULT_LED_PIN, 1);
        gpio_pull_up(BUTTON);
    
        gpio_set_irq_enabled_with_callback(BUTTON, GPIO_IRQ_EDGE_FALL, 1, (gpio_irq_callback_t)gp2_callback);
    
        while(1);
        return 0;
    }

    CMakeLists.txt는 포르젝트 이름만 바뀌고 똑같다.

    인터럽트 요청 핸들 추가

    콜벡과 인터럽트 핸들은 같이 쓸 수 없다. 인터럽트 핸들을 추가하면 콜백이 해제된다.

    irq는 interrupt request의 줄임말이다. (우리가 응응을 초성 자음만 남겨서 ㅇㅇ으로 쓰듯이 영어도 맨 앞글자 + 자음을 남겨서 줄임말을 만든다. 맨 앞글자는 알아 듣는데 중요하니 모음이라도 남기고, 뒤쪽의 모음은 남기면 올 수 있는 경우의 수가 너무 많아서 알아들을 수 없기 때문에 잘 안남긴다고 생각한다.)

    버튼 입력 예제

    #include "pico/stdlib.h"
    
    #define LED 2
    #define BUTTON 3
    
    void gp2_irq() {
        if(gpio_get_irq_event_mask(BUTTON) & GPIO_IRQ_EDGE_FALL) {
            gpio_acknowledge_irq(BUTTON, GPIO_IRQ_EDGE_FALL);
            gpio_xor_mask(0x4);
        }
    };
    
    
    int main() {
        gpio_init_mask((1 << LED) | (1 << BUTTON) | (1 << PICO_DEFAULT_LED_PIN));
        gpio_set_dir_masked((1 << LED) | (1 << BUTTON) | (1 << PICO_DEFAULT_LED_PIN),
                            (1 << LED) | (1 << PICO_DEFAULT_LED_PIN) );
        gpio_put(PICO_DEFAULT_LED_PIN, 1);
        gpio_pull_up(BUTTON);
    
        gpio_set_irq_enabled(BUTTON, GPIO_IRQ_EDGE_FALL, true);
        gpio_add_raw_irq_handler(BUTTON, gp2_irq);
        irq_set_enabled(IO_IRQ_BANK0, true);
    
        while(1);
        return 0;
    }
    //다양한 동작을 원한다면 핸들 함수를 이렇게 적으면 된다.
    void gp2_irq() {
        if(gpio_get_irq_event_mask(BUTTON) & GPIO_IRQ_EDGE_FALL) {
            gpio_acknowledge_irq(BUTTON, GPIO_IRQ_EDGE_FALL);
            //실행 내용
        }
        else if(gpio_get_irq_event_mask(BUTTON) & GPIO_IRQ_EDGE_RISE) {
            gpio_acknowledge_irq(BUTTON, GPIO_IRQ_EDGE_RISE);
            //실행 내용
        }
    };

    CMakeLists.txt는 포르젝트 이름만 바뀌고 똑같다.

    공식 문서에 따르면 if로 어느 핀에서 발생하였는지 확인하고, 안에서 gpio_acknowledge_irq 를 if 안쪽에 반드시 해야한다고 한다.

     

    두 예제 모두 버튼을 누르면 LED 상태가 반전되는 것을 확인할 수 있다. 인터럽트가 알아서 버튼 입력을 처리해주니 소프트웨어가 감시하는 폴링 방식과 하드웨어가 감시하는 인터럽트 방식 모두 적절히 활용할 수 있어야 한다.

     

    gpio_exam.zip
    0.57MB

     

    댓글

Designed by Tistory.