해당 글은 건국대학교 진현욱 교수님의 임베디드 시스템 소프트웨어 수업 내용을 정리한 글입니다.
General Purpose I/O(GPIO)
각각의 GPIO는 한 특정 핀으로 연결되는 한 비트를 나타낸다.보통 2개의 전자제품을 연결하며 0이나 1로 나타내는 2개의 전압 level을 가진다.

검정색 칩의 Phys11, Phys13 같은 번호들이 이게 실제 보드에 튀어나와 있는 물리적인 구멍이다. 이 구멍 하나에 전선(Jumper Wire) 하나를 꽂는다고 생각하면 된다. 구멍 1개 = 선 1개 = 1비트
우리는 저 물리적인 구멍을 직접 조종하는 것보다 위에서 말했던 GPIO library로 접근할 것이다.
BSP(Board Support Package)
그렇다면 물리적인 구멍과 소프트웨어의 GPIO 번호를 매칭하고 제어하는 것은 바로 누가할까? 바로 BSP 라는 것이다.
BSP는 리눅스 커널에게 "이 보드의 0x3F200000 주소에 GPIO 컨트롤러가 있고, 핀은 총 40개야" 라는 디바이스 트리를 제공한다.
BSP가 없다면, 리눅스 커널 개발자는 모든 보드마다 코드를 새로 짜야 할 것이다. 하지만 BSP 덕분에 커널은 표준화된 방식으로 명령을 내리고, 실제 하드웨어를 제어하는 복잡한 일은 해당 보드의 BSP가 담당하는 것이다.
GPIO Uses
이러한 GPIO는 다양한 센서들을 읽는데 사용이 되는데 IR, video, temperature, 3-axis orientation, acceleration,
DC motors via PWM, audio, LCD, displays, LEDS 등이 있지만 우리 수업시간에는 굵기 표시된 센서들을 사용 예정이다.
GPIO 핀들은 하나씩 따로 노는 게 아니라, 보통 Bank(뱅크)라는 단위로 묶여서 관리된다. GPIO 핀 자체는 단순히 0(Low) 또는 1(High)만 출력하는 단순한 핀이지만, 이 핀들을 여러 개 조합하고 특정 규칙(타이밍)에 맞춰 작동시키면 복잡한 대화가 가능해지는데
그 예시가 Inter-Integrated Circuit 이다.
Input/Output Modes
GPIO Pins은 input 이나 output 모드만 가질 수 있다.
Output value는 당연히 쓰기도 읽기도 가능하다. (high = 1(3.3v), low = 0(0v)) / Input value는 읽기만 가능하다.

지금이야 BSP가 라즈베리 파이에 구현되어 있지만 나중에 실제로는 구현을 직접 해야한다.
GPIO in the Linux Kernel
이제 함수들에 대해 알아보자
자원 예약
int gpio_request(unsigned int gpio, const char *label)
- return 0 for success
- 컴퓨터의 메모리에 쓸 때 malloc을 하듯이 GPIO 핀을 쓰기 전에 이 핀을 쓸거라고 요청을 하는 함수
- 매개변수
- gpio : 특정 GPIO 번호를 사용하겠다!
- lable : 디버깅용 이름
void gpio_free(unsigned int gpio)
- return a GPIO to the system
- 사용이 끝난 GPIO 핀을 시스템에 반납하는 함수
- 이 함수를 안쓰면 그 핀을 다른 모듈이 못씀
int gpio_direction_input(unsigned int gpio)
- 해당 핀을 입력 모드로 설정한다.
- 센서 값이나 버튼 입력을 읽을 때 사용
int gpio_direction_output( unsigned int gpio, int value)
- 해당 핀을 출력 모드로 설정한다.
- value : 설정과 동시에 처음으로 내보낼 전압 상태(0 또는 1)를 지정할 수 있다.
int gpio_request_one( unsigned gpio, unsigend long flags, const char *label)
- input, output 둘 다 쓸 수 있는 함수
- flags
- GPIOF_IN -> input 모드로 설정
- GPIOF_OUT_INIT_LOW -> output모드에서 처음 출력을 0으로 초기화
- GPIOF_OUT_INIT_HIGH -> output모드에서 처음 출력을 1으로 초기화
int gpio_get_value(unsigned int gpio)
- input GPIO에 대해 현재 value를 읽어 올 수 있는 함수
void gpio_set_value(unsigned int gpio, int value)
- output GPIO의 value를 설정할 수 있는 함수
WiringPi
GPIO를 조금 더 쉽게 유저 레벨에서 할 수 있게하는 라이브러리이다. 즉 커널 모드(Kernel Space)에서 동작하느냐, 유저 모드(User Space)에서 동작하느냐의 차이라고 볼 수 있겠다.
int wiringPiSetup(void)
- WiringPi를 사용하기 위해 가장 먼저 호출해야 하는 함수이다.
- 라이브러리를 초기화하고 WiringPi 전용 핀 번호 체계를 사용하도록 설정하는 것이다.
- 반드시 root 권한으로 불러야 하는 것을 명심하자
void pinMode (int pin, int mode)
- 특정 핀의 모드를 INPUT(입력) 또는 OUTPUT(출력)으로 설정할 수 있다.
void digitalWrite (int pin, int value)
- 출력 모드로 설정된 핀에 HIGH(1) 또는 LOW(0) 값으로 설정할 수 있다.
- 위에서도 말했지만 high -> 3.3V / low -> 0V 이다.
int digitalRead (int pin)
- 특정 핀의 현재 전압 상태를 반환해준다.
예시 코드
#include <wiringPi.h>
int main (void){
wiringPiSetup() ;
pinMode(0, OUTPUT) ;
for(;;){
digitalWrite(0, HIGH) ; delay(500) ;
digitalWrite(0, LOW) ; delay(500) ;
}
return 0 ;
}
WiringPi.c
유저 레벨 라이브러리인 WiringPi가 실제 하드웨어 레지스터를 건드리기 위해서는 다음과 같은 논리적인 연결이 필요함!

- piGpioBase: CPU(BCM 칩셋)마다 물리적인 GPIO 레지스터가 시작되는 주소이다.
- 실제 칩셋의 물리적 메모리 지도(Physical Memory Map)를 찾아가는 코드라고 보면 될 것같다.
- mmap (Memory Mapping) 함수의 역할
- mmap(): 리눅스 시스템 콜 중 하나로, 물리적인 장치의 메모리(또는 파일)를 현재 실행 중인 프로세스의 가상 메모리 주소 공간에 매핑시킨다.
중요한 이유
- 커널 오버헤드 제거: 매번 커널에게 "핀 좀 켜줘"라고 요청(System Call)하는 대신, 내 메모리에 값을 쓰는 것만으로 제어가 가능하므로 속도가 매우 빠르다.
- 포인터 연산: wiringPi.c 내부를 보면 결국 uint32_t *gpio 같은 포인터를 사용해 레지스터 값을 직접 수정하는 것이다.
- root 권한의 이유: /dev/mem은 시스템의 심장부와 같아서 일반 사용자가 건드리면 위험하기 때문에 mmap을 사용하는 WiringPi 프로그램은 반드시 sudo 권한이 필요한 것!
Kernel Timers
Dynamic Timers
- 동적으로 만들고 없어지는 커널용 타이머이다.
- 수에 한계가 없다.
- timer_list 구조체 안에 저장되어 있다.
- expires : jiffies 값(누적되는 값)으로 타이머가 만료될 때를 결정하는 매개변수
- function : 타이머가 만료될 때 실행되는 함수의 주소를 담고 있다.

요즘엔 HR 타이머라고 많이 촘촘하고 좋은 성능이지만 HR_clock이라는 하드웨어가 필요하기 때문에 우리는 dynamic timer를 공부하는 것
Initialztion
init_timer()
timer_list 구조체를 초기화 시킨다.
Insertion
add_timer()
timer 요소를 구조체에 삽입하고 타이머를 돌린다.
Deletion
del_timer() -> timer_delete() //최신
timer를 지운다.
del_timer_sync() -> timer_delete_sync()// 최신
위와 똑같지만 timer를 없애려고 했는데 이미 만료되서 함수가 돌아가기 시작했다면 그 함수까지 기다렸다가 timer를 울린다.
그래서 sleep에 빠질 수도 있으므로 interrupt handler에서는 쓰지 말자!
Timer handler
TIMER_SOFTIRQ : 타이머 인터럽트가 발생했을 때, 실제로 만료된 타이머들을 확인하고 등록된 함수를 실행해 주는 커널 내부 루틴이다.
-> 소프트웨어 인터럽트 방식(TIMER_SOFTIRQ)으로 동작하기 때문에, 다른 중요한 작업이 밀려 있으면 타이머가 만료된 즉시 실행되지 못하고 약간의 지연이 더 발생할 수 있다.
또 HZ 단위보다 더 세밀한(Precise) 시간 제어는 어렵다는 점을 상기하자!
Sleep-wait Vs. Busy-wat
A. Sleep-wait (잠자는 놈들)
- 종류: msleep(), schedule_timeout(), wait_event_timeout()
- 특징: 기다리는 동안 CPU를 다른 프로세스에게 양보하고 자신은 잠든다.
- 제약: 프로세스 컨텍스트(Process Context)에서만 사용 가능하다. 그리고 인터럽트가 발생해서 처리 중인 상황(Interrupt Context)에서는 절대로 사용하면 안된다.
B. Busy-wait (바쁘게 도는 놈들)
- 종류: udelay(), ndelay()
- 특징: 지정된 시간 동안 CPU를 붙잡고 무의미한 루프를 돌며 기다린다.
- 장점: 잠들지 않기 때문에 인터럽트 컨텍스트에서도 사용할 수 있다.
'수업 정리 > 임베디드시스템소프트웨어' 카테고리의 다른 글
| [embedded system software/10주차] 이론 (0) | 2026.05.09 |
|---|---|
| [embedded system software/9주차] 이론 (0) | 2026.04.29 |
| [embedded system software/5주차] 수업정리 (0) | 2026.04.01 |
| [embedded system software/4주차] 수업 정리 (0) | 2026.03.25 |
| [embedded system software/3주차] Character Device Drivers (0) | 2026.03.21 |