임베디드시스템소프트웨어 과목의 강의영상과 강의자료를 바탕으로 작성한 학습용 게시글입니다.
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 | |
예시 |
|
|
|
특징 |
|
|
|
'LECTURE > [2023-1] 임베디드시스템소프트웨어' 카테고리의 다른 글
[임베디드시스템소프트웨어] 04. Blocking I/O (0) | 2023.04.12 |
---|---|
[임베디드시스템소프트웨어] 03. Basic Kernel Functions (0) | 2023.04.11 |
[임베디드시스템소프트웨어] 02. Character Device Drivers (캐릭터 디바이스 드라이버) (0) | 2023.04.10 |
[임베디드시스템소프트웨어] 01. Loadable Kernel Modules (적재 가능 커널 모듈) (0) | 2023.04.09 |