DEVELOP

임베디드시스템소프트웨어 과목의 강의영상과 강의자료를 바탕으로 작성한 학습용 게시글입니다.


GPIO (General Purpose Input/Output )

  • 특정 PIN에 연결된 비트를 나타낸다.
  • 0또는1의 1bit를 표현할 수 있다. (0은 0V, 1은 3.3V를 뜻한다.)
  • GPIO Pin을 통해서 한 쪽에서는 1bit짜리의 데이터를 보내고, 다른 한 쪽에서는 1bit짜리의 데이터를 읽어온다.

GPIO의 사용

  • 센서나 액추에이터를 연결하고 센서나 액추에이터에 특정 정보를 보내거나 읽어들일 때 사용한다. 
  • 각 PIN은 1bit를 나타낸다. 
  • Pin 하나로만 다양한 센서와 액추에이터를 연결하기는 어렵다. 
    -> 보통 Pin을 다발로 묶어서 여러 개의 핀을 집합적으로 사용한다. (I^2C)
  • GPIO Pin은 input 또는 output이 가능하다. 
  • Output 값은 읽기 / 쓰기 모두 가능하다. (high = 1, low = 0)
    • HIGH는 3.3V, LOW는 0V이다. 
  • Input 값은 읽기전용이다.  (high = 1, low = 0)
    • Input 값은 주로 IRQs 로 사용된다. (인터럽트 발생, wakeup 이벤트 중 하나)
    • 대표적인 값을 읽는 용도로는 센서가 있다. 

GPIO in the Linux Kernel

# GPIO를 할당하는 함수

int gpio_request(
    unsigned int gpio,
    const char *labe
l)
  • gpio : 할당 받을 GPIO pin 번호
  • label : null 가능
  • 성공적으로 할당되면 0을 리턴

# GPIO 할당 해제하는 함수

void gpio_free(unsigned int gpio)
  • gpio : 할당 해제할(리턴할) GPIO pin 번호

# GPIO를 Input mode로 사용할 때 

int gpio_direction_input(unsigned int gpio)
  • gpio : input mode로 사용할 GPIO pin 번호 

# GPIO를 Input mode로 사용할 때 

int gpio_direction_output(
    unsigned int gpio,
    int value
)
  • gpio : output mode로 사용할 GPIO pin 번호 
  • value : 초기값 ( 1 또는 0 )

# GPIO핀 할당 + input / output mode 설정 한번에 하는 함수

int gpio_request_one(
    unsigned gpio,
    unsigned long flags,
    const char *label
)
  • gpio : 할당할 GPIO pin 번호
  • flag 
    • GPIOF_IN : Input mode
    • GPIOF_OUT_INIT_LOW : Output mode, 초기값 0
    • GPIOF_OUT_INIT_HIGH : Output mode, 초기값 1

# input GPIO의 현재 값을 읽어오는 함수 

int gpio_get_value(unsigned int gpio)
# output GPIO의 값을 설정하는(쓰는) 함수 
void gpio_set_value(
    unsigned int gpio,
    int value
)
  • value: GPIO에 설정할(쓸) 값
  • gpio_direction_output() 함수도 값을 set하는 함수이다. 

 

  • 해당 Pin 번호에 하드웨어적으로 실제로 0또는 1이 써질 수 있는 이유는 BSP가 있기 때문이다.
  • BSP (Board Support Package)
    : 커널 패치로 존재,
    • 라즈베리파이 모델이나 휴대폰 모델에 따라 다를 수 있다.
    • 0또는 1이 들어갈 수 있도록 조절한다. 
    • 임의의 버전을 설치하지 않고 알맞은 버전을 설치해야 pin번호와 hw가 잘 매핑될 수 있다. 

WiringPi

  • 유저 level의 응용프로그램으로서, GPIO를 쉽게 사용할 수 있도록 제공해주고 있는 라이브러리이다.
  • 단점
    : 여러 응용프로그램이 동시에 GPIO를 공유할 때 발생하는 문제점을 예측할 수 없기 때문에, 본 강의에서는 권장되지는 않고, 알고만 있자

# WiringPi Setup 

int wiringPiSetup (void)
  • 위 함수를 호출 후 wiringPi를 사용할 수 있다. 
  • 루트 권한이 있어야 이 함수를 호출할 수 있다. 

# WiringPi PinMode 선언하는 함수

void pinMode (int pin, int mode)
  • wiringPi가 성공적으로 리턴이 되면, 사용할 wiringpi Pin들과 Mode를 선언해야 한다. 
  • pin : 사용할 Wpi 번호
  • mode : input or output
  • model마다 다를 수 있기 때문에 Pin 번호가 어떤 Pin을 가리키는지, 구성한 Pin 번호를 잘 알고 있어야 한다.

# WiringPi Write 함수

void digitalWrite (int pin, int value)
  • pin : 값을 쓸 WiringPi의 Pin 번호
  • value : pin에 쓸 초기값 (High or Low , 1 or 0 )

# WiringPi Read 함수

int digitalRead (int pin)
  • pin : 값을 읽어올 WiringPi의 Pin 번호 
    (읽어온 값은 High or Low , 1 or 0 )

# WiringPi Example

#include <wiringPi.h>
int main (void)
{
    wiringPiSetup () ;
    pinMode (0, OUTPUT) ;
    for (;;)
    {
        digitalWrite (0, HIGH) ; delay (500) ;
        digitalWrite (0, LOW) ; delay (500) ;
    }
    return 0 ;
}
  • 루트 권한으로 실행되어야 한다. 
    (wiringPiSetup() 함수 자체가 루트 권한으로 실행되어야한다.) 

# wiringPi.c

...
GPIO_PADS = piGpioBase + 0x00100000 ;
GPIO_CLOCK_BASE = piGpioBase + 0x00101000 ;
GPIO_BASE = piGpioBase + 0x00200000 ;
GPIO_TIMER = piGpioBase + 0x0000B000 ;
GPIO_PWM = piGpioBase + 0x0020C000 ;
...
gpio = (uint32_t *)mmap(0, BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED,
fd, GPIO_BASE) ;
if (gpio == MAP_FAILED)
return wiringPiFailure (WPI_ALMOST,
"wiringPiSetup: mmap (GPIO) failed: %s\n",
strerror (errno)) ;
...
  • 특정 Pin의 I/O 주소를 선언한다. (1~5 line)
  • GPIO_BASE는 시작주소를 뜻한다.  
  • mmap : 유저 영역의 메모리에 매핑한다.
    -> 가상메모리에 있는 영역에 매핑되어있는 특정 주소에 값을 쓰면 내부적으로 GPIO의 특정 Pin에 값을 쓰게 된다.

Kernel Timers

Dynamic Timer

  • 커널 안에서 쓸 수 있는 타이머로서, 커널 안에서 dynamic하게 타이머를 등록할 수 있는 인터페이스이다.
  • 여러 개 등록도 가능하다. 
  • timer_list 구조체에 저장된다.
struct timer_list {
unsigned long expires;
void (*function)(unsigned long);
};
  • expires : 얼만큼의 시간 후에 타이머가 끝날 것인가를 명세 (jiffies값이 얼마나 된다는 것을 표현)
    • 주기적으로 발생시키는 커널 타이머 인터럽트이다.
    • Dynamic Timer 자체와는 별도의 것이다.
    • 타이머 인터럽트가 발생할 때마다 jiffiy값을 1씩 증가시킨다. 
  • function : expires 시간이 지난 후에 실행되는 함수의 주소를 포함한다. 
 

# Initialization

init_timer()
  • timer_list 구조체를 초기화한다. 

# Insertion

add_timer()
  • 커널에 타이머를 등록해서 내가 원하는 시간만큼 기다렸다가 expire되도록 해준다.
  • 리스트에 타이머를 추가한다. 

# Deletion

  • 등록된 Dynamic Timer를 지운다. 
  • 시간이 expire되고 function이 실행되면 등록된 Dynamic Timer는 반복호출 되지 않고 (One Shot)
    자동으로 삭제된다.
  • 자동 삭제 이외에 인위적으로 삭제해야 하는 경우에 사용하는 삭제 방법들이다. 
  • 3가지 방법 중에서 상황에 맞게 적절한 것을 선택하여 사용한다. 
del_timer( )
  • 리스트에서 타이머를 삭제한다.
del_timer_sync( )
  • 타이머를 삭제하는데, 이 함수가 호출되었을 때 CPU에서 Timer에 해당하는 function이 실행되고 있다면, 그 function이 종료될 때까지 기다렸다가 리턴이 된다. 
  • 이 함수가 리턴되었다는 것은 타이머가 시스템의 어떤 프로세서에서도 실행되고 있지 않다는 것을 보장한다는 뜻이다. 
  • 모든 CPU를 돌면서 Timer가 실행중인지를 확인하기 때문에 오버헤드가 발생할 가능성이 높다. 
del_singleshot_timer_sync( )
  • 위의 del_timer_sync()함수와 동일한 기능을 하지만, 내부 구조가 다르다. 
  • 타이머에 해당하는 functoin이 실행되는 CPU를 기억해두었다가,
    모든 CPU를 스캔할 필요없이 특정 코어를 보면 핸들러가 종료되었는지, 아직 실행중인지를 알 수 있다. 
  • 코어가 많은 시스템에서 매우 효율적이다. 
  • del_timer_sync()함수보다 CPU자원을 아낄 수 있고, 더 간단하며 빠르다.

# Timer Handler

TIMER_SOFTIRQ 

  • timer의 function은 thread 형태로 전달되는 것이 아니고 timer가 expire되면, 일종의 인터럽트 핸들러같은 것이 그 함수를 호출하도록 되어 있다.
  • 일종의 인터럽트 핸들러 = TIMER_SOFTIRQ
  • run_timer_softirq()에 의해서 타이머 구조체 안에 있는 함수들이 호출되도록 되어 있다. 
  • 핸들러는 바로 처리해야하는 Top half와 조금 늦게 처리해도되는 Bottom half로 이루어져 있는데, 
    TIMER)SOFTIRQ는 Bottom half에 해당한다. 

* Timer의 Function과 jiffy

  • 프로세스 또는 스레드의 context에 의해 실행되는 것이 아니고, 인터럽트 핸들러(TIMER_SOFTIRQ)의 context에서 실행된다.
  • jiffy값은 HZ의 frequency로서, 계속해서 증가되는 값이다. 
  • 정확한 jiffy값을 가지고 HZ를 기반으로 계산한 값이 흐른 후에 expire되는 것이 아니다. 
  • OS가 보장하는 것은 expire에 지정해 준 값만큼은 지나갔고, expire 후 최대한 빨리 해당하는 function을 호출한다는 것이다. ( 정확히 jiffy값이 끝난 후 바로 호출하는 것은 보장x)

Delay Funtions 비교

  커널함수 (Sleep-wait) Busy-waiting  Dynamic Timers
예시
  • msleep()
  • schedule_timeout()
    : 인자값으로 전달된 시간이 지날때까지 해당 프로세스의 CPU 자원을 양보하도록 함 
  • wait_event_timeout()
    : wait queue를 위해 사용한다. 
  • udelay()
  • ndelay()
 
특징
  • 명시되어 있는 시간동안 sleep하면서 해당 프로세스를 blocked state로 전이시킨다. 
  • process context에서 호출되어야 하는 함수들이다. 
  • interrupt context에서는 호출할 수 없다. 
  • interrupt context는 독자적인 stste가 없다. 
  • 호출한 프로세스를 sleep->block state로 전이시키는 것이 아니고, busy-wating한다. 
  • CPU 자원을 양보하지 않고, CPU 자원을 쥔 채로 실행시간이 지날 때까지 바쁘게 CPU 자원을 burning하는 것이다. 
  • process context뿐만 아니라, interrupt context에서도 호출이 가능하다. 
  • 단순히 Timer 자체를 특정 function과 함께 등록한다.
  • function은 interrupt handler(SOFTIRQ)에 의해 실행된다. 
  • Dynamic Timer을 등록하는 것 자체는 process context, interrupt context 모두에서 실행가능하다. 
  • 다만, function 자체는 interrupt handler에서 실행된다. 
    (특정 프로세스와 binding되어 있는 상태라고 가정하면 안된다.)

 

profile

DEVELOP

@JUNGY00N