| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
| 29 | 30 | 31 |
- java
- cpp
- 우선순위큐
- 통신 인터페이스
- 프로그래머스
- C
- BFS
- 임베디드
- kotlin
- lv2
- 구현
- 백준
- 컴퓨터 비전
- 자바
- 다이나믹프로그래밍
- level2
- c++
- 이분탐색
- 다이나믹 프로그래밍
- 누적합
- level3
- 컴퓨터비전
- 코틀린
- 2018 KAKAO BLIND RECRUITMENT
- 동적계획법
- 그리디
- dfs
- Stack
- JavaScript
- dp
- Today
- Total
코드를 느껴바라
[임베디드] 리눅스 디바이스 드라이버로 GPIO LED 제어하기 (Linux Kernel) 본문
리눅스 디바이스 드라이버로 GPIO LED 제어하기
오늘은 리눅스 커널 레벨에서 하드웨어를 직접 제어하는 Character Device Driver를 작성하고, 유저 응용 프로그램에서 이를 호출해 LED를 켜고 끄는 실습을 진행했습니다.
전체 코드는 포스팅 제일 하단에 위치하고 있습니다.!!

1. 실습 개요 (What I did)
리눅스 시스템(User Space)에서는 하드웨어에 직접 접근할 수 없습니다. 따라서 Kernel Space에서 동작하는 모듈을 만들어 물리 메모리에 접근하고, 유저 공간에서 open, write, read 시스템 콜을 통해 LED를 제어하는 구조를 구현했습니다.
- 타겟 보드: 라즈베리파이 4 (추정: Base Address가
0xFE200000임) - 제어 핀: GPIO 17번
- 주요 기능:
1입력: LED ON0입력: LED OFF숫자(N)입력: N회 깜빡임 (Blink)
2. 드라이버 코드 작성 (Kernel Space)
먼저 커널 모듈 소스 (gpio_module.c)입니다. 이 코드는 물리 메모 주소를 가상 주소로 매핑하여 GPIO 레지스터를 직접 조작합니다.
주요 포인트
ioremap:0xFE200000(물리 주소)를 커널 가상 주소로 매핑하여 하드웨어 접근 권한 획득.register_chrdev_region& `cdev_add:/dev/gpioled`라는 장치 드라이버를 커널에 등록.file_operations: 유저의open,read,write요청을 처리할 함수 연결.
// gpio_module.c
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
MODULE_LICENSE("GPL");
#define GPIO_BASE (0xFE200000)
#define GPIO_SIZE (256)
#define GPIO_IN(g) (*(gpio + ((g)/10)) &= ~(7<<(((g)%10)*3)))
#define GPIO_OUT(g) (*(gpio + ((g)/10)) |= (1<<(((g)%10)*3)))
#define GPIO_SET(g) (*(gpio + 7) = 1 << g)
#define GPIO_CLR(g) (*(gpio + 10) = 1 << g)
#define GPIO_GET(g) (*(gpio + 13) & (1 << g))
static volatile unsigned int *gpio;
/* 디바이스 파일의 주 번호와 부 번호 */
#define GPIO_MAJOR 200
#define GPIO_MINOR 0
#define GPIO_DEVICE "gpioled"
#define GPIO_LED 17
#define BLOCK_SIZE 100
static char msg[BLOCK_SIZE] = {0};
static int gpio_open(struct inode* inod, struct file* fil){
printk("GPIO Device opened\n");
return 0;
}
static ssize_t gpio_write(struct file* inode, const char *buff, size_t len, loff_t* off){
// 유저 영역에서 데이터 가져오기 (copy_from_user)
copy_from_user(msg, buff, len);
if (!strcmp(msg, "0")) {
GPIO_CLR(GPIO_LED); // LED OFF
} else if (!strcmp(msg, "1")) {
GPIO_SET(GPIO_LED); // LED ON
} else {
// 숫자만큼 깜빡이기
int val;
kstrtoint(msg, 10, &val);
for (int i = 0; i < val; i++) {
GPIO_SET(GPIO_LED);
ssleep(1);
GPIO_CLR(GPIO_LED);
ssleep(1);
}
}
return count;
}
// ... (나머지 함수 생략)
3. 유저 어플리케이션 작성 (User Space)
드라이버가 잘 작동하는지 테스트할 응용 프로그램(gpio.c)입니다. 디바이스 파일을 열어서 명령어를 쓰고, 결과를 읽어옵니다.
// gpio.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define BUFFSIZ 100
int main(int argc, char **argv){
char buf[BUFFSIZ];
int fd = -1;
memset(buf, 0, BUFFSIZ);
printf("GPIO SET : %s\n", argv[1]);
// 1. 디바이스 파일 열기
fd = open("/dev/gpioled", O_RDWR);
// 2. 커널로 명령 전송 (write)
write(fd, argv[1], strlen(argv[1]));
// 3. 커널로부터 응답 수신 (read)
read(fd, buf, BUFFSIZ);
printf("Read Data : %s\n", buf);
close(fd);
return 0;
}
4. 빌드 및 실행 과정 (How to run)
터미널에서 실제로 수행한 명령어 순서입니다.
1) 커널 모듈 빌드 (Makefile 이용)
먼저 드라이버를 컴파일하여 .ko 파일을 생성합니다.
$ make
Makefile코드는 이렇습니다.
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
obj-m := gpio_module.o
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
결과:
gpio_module.ko파일이 생성됩니다.
2) 커널 모듈 적재 (insmod)
생성된 모듈을 커널에 적재합니다. 관리자 권한(sudo)이 필요합니다.
적재를 하지 않는다면 아무런 동작의 변화가 없음.
$ sudo insmod gpio_module.ko
3) 디바이스 파일 생성 (mknod)
드라이버는 로드되었지만, 유저가 접근할 "파일"이 필요합니다. 소스 코드에 정의된 Major 번호(200)를 사용하여 디바이스 노드를 만듭니다.
# 디바이스 노드 생성 (c: 문자 디바이스, 200: 주번호, 0: 부번호)
$ sudo mknod /dev/gpioled c 200 0
# 누구나 접근 가능하도록 권한 변경
$ sudo chmod 666 /dev/gpioled
4) 어플리케이션 컴파일 (코드는 포스팅 제일 하단에 위치)
테스트용 C 프로그램을 컴파일합니다.
$ gcc -o gpio gpio.c
5. 테스트 결과
이제 실제로 LED를 제어해 봅니다.
1. LED 켜기
$ ./gpio 1
GPIO SET : 1
Read Data : LED ON from Kerne
결과: GPIO 17번 핀에 연결된 LED가 켜집니다.
2. LED 끄기
$ ./gpio 0
GPIO SET : 0
Read Data : LED OFF from Kerne
결과: LED가 꺼집니다.
3. LED 깜빡이기 (예: 3회)
$ ./gpio 3
GPIO SET : 3
# (3초간 깜빡거림)
Read Data : LED BLINK from Kerne
4. 커널 로그 확인printk로 출력한 로그는 dmesg 명령어로 확인할 수 있습니다.
$ dmesg | tail
[ 1234.5678] Hello LED module!
[ 1234.5678] 'mknod /dev/gpioled c 200 0'
[ 1234.5678] GPIO Device opened(200/0)
[ 1234.5678] GPIO Device (200) write : 1(1)
6. 마무리 및 정리 (Clean up)
실습이 끝나면 리소스를 해제해야 합니다.
# 커널 모듈 제거
$ sudo rmmod gpio_module.ko
이번 실습을 통해 유저 영역의 요청이 System Call을 통해 커널 영역으로 전달되고, 커널 모듈이 Mapping된 주소를 통해 하드웨어를 제어하는 전체 흐름을 이해할 수 있었습니다.
gpio_module.c 및 gpio.c 전체코드 공유
gpio_module.c
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
MODULE_LICENSE("GPL");
#define GPIO_BASE (0xFE200000)
#define GPIO_SIZE (256)
#define GPIO_IN(g) (*(gpio + ((g)/10)) &= ~(7<<(((g)%10)*3)))
#define GPIO_OUT(g) (*(gpio + ((g)/10)) |= (1<<(((g)%10)*3)))
#define GPIO_SET(g) (*(gpio + 7) = 1 << g)
#define GPIO_CLR(g) (*(gpio + 10) = 1 << g)
#define GPIO_GET(g) (*(gpio + 13) & (1 << g))
static volatile unsigned int *gpio;
/* 디바이스 파일의 주 번호와 부 번호 */
#define GPIO_MAJOR 200
#define GPIO_MINOR 0
#define GPIO_DEVICE "gpioled"
#define GPIO_LED 17
#define BLOCK_SIZE 100
static char msg[BLOCK_SIZE] = {0};
/* 입출력 함수를 위한 선언 */
static int gpio_open(struct inode*, struct file*);
static ssize_t gpio_read(struct file*, char *, size_t, loff_t*);
static ssize_t gpio_write(struct file*, const char *, size_t, loff_t*);
static int gpio_close(struct inode*, struct file*);
static struct file_operations gpio_fops = {
.owner = THIS_MODULE,
.read = gpio_read,
.write = gpio_write,
.open = gpio_open,
.release = gpio_close,
};
static struct cdev gpio_cdev;
int init_module(void){
dev_t devno;
unsigned int count;
static void *map;
int err;
printk(KERN_INFO "Hello LED module!\n");
devno = MKDEV(GPIO_MAJOR, GPIO_MINOR);
register_chrdev_region(devno, 1, GPIO_DEVICE);
cdev_init(&gpio_cdev, &gpio_fops);
gpio_cdev.owner = THIS_MODULE;
count = 1;
err = cdev_add(&gpio_cdev, devno, count);
if(err < 0){
printk("Error : Device Add\n");
return -1;
}
printk("'mknod /dev/%s c %d 0'\n", GPIO_DEVICE, GPIO_MAJOR);
printk("'chmod 666 /dev/%s'\n", GPIO_DEVICE);
map = ioremap(GPIO_BASE, GPIO_SIZE);
if(!map){
printk("Error : mapping GPIO memory\n");
iounmap(map);
return -EBUSY;
}
gpio = (volatile unsigned int*)map;
GPIO_IN(GPIO_LED);
GPIO_OUT(GPIO_LED);
return 0;
}
void cleanup_module(void){
dev_t devno = MKDEV(GPIO_MAJOR, GPIO_MINOR);
unregister_chrdev_region(devno, 1);
cdev_del(&gpio_cdev);
if(gpio){
iounmap(gpio);
}
module_put(THIS_MODULE);
}
static int gpio_open(struct inode* inod, struct file* fil){
printk("GPIO Device opened(%d/%d)\n", imajor(inod), iminor(inod));
return 0;
}
static int gpio_close(struct inode* inod, struct file* fil){
printk("GPIO Device closed(%d)\n", MAJOR(fil->f_path.dentry->d_inode->i_rdev));
return 0;
}
static ssize_t gpio_read(struct file* inode, char *buff, size_t len, loff_t* off){
int count;
strcat(msg, " from Kerne");
count = copy_to_user(buff, msg, strlen(msg) + 1);
printk("GPIO Device(%d) read : %s(%d)\n", MAJOR(inode->f_path.dentry->d_inode->i_rdev), msg, count);
return count;
}
static ssize_t gpio_write(struct file* inode, const char *buff, size_t len, loff_t* off){
short count;
memset(msg, 0, BLOCK_SIZE);
count = copy_from_user(msg, buff, len);
if (!strcmp(msg, "0")) {
GPIO_CLR(GPIO_LED);
snprintf(msg, BLOCK_SIZE, "LED OFF");
}
else if (!strcmp(msg, "1")) {
GPIO_SET(GPIO_LED);
snprintf(msg, BLOCK_SIZE, "LED ON");
}
else {
int val;
if(kstrtoint(msg, 10, &val) < 0) {
snprintf(msg, BLOCK_SIZE, "Invalid Command");
printk("GPIO Device (%d) write : %s(%d)\n", MAJOR(inode->f_path.dentry->d_inode->i_rdev), msg, len);
return count;
}
for (int i = 0; i < val; i++) {
GPIO_SET(GPIO_LED);
ssleep(1);
GPIO_CLR(GPIO_LED);
ssleep(1);
}
snprintf(msg, BLOCK_SIZE, "LED BLINK");
}
printk("GPIO Device (%d) write : %s(%d)\n", MAJOR(inode->f_path.dentry->d_inode->i_rdev), msg, len);
return count;
}
gpio.c
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#define BUFFSIZ 100
int main(int argc, char **argv){
char buf[BUFFSIZ];
char i = 0;
int fd = -1;
memset(buf, 0, BUFFSIZ);
printf("GPIO SET : %s\n", argv[1]);
fd = open("/dev/gpioled", O_RDWR);
write(fd, argv[1], strlen(argv[1]));
read(fd, buf, BUFFSIZ);
printf("Read Data : %s\n", buf);
close(fd);
return 0;
}'개발 > 임베디드(Embedded)' 카테고리의 다른 글
| [임베디드] TC275로 스톱워치 구현 : 74HC595 제어와 Flag 기반 인터럽트 설계 (2) | 2026.02.23 |
|---|---|
| [임베디드] TC275 STM 신호등 : 외부 인터럽트(ERU)와 Flag 기반 상태 머신 구현 (2) | 2026.02.23 |
| [임베디드] 라즈베리파이로 7-Segment 제어해서 스톱워치 만들기 (2) | 2026.02.11 |
| [네트워크] I2C 통신 인터페이스 개요 (2) | 2026.02.06 |
| [네트워크] U(S)ART 통신 인터페이스 (0) | 2026.02.06 |
