GPIO는 일반적인 용도의 입출력 포트로 마이크로컨트롤러나 SoC에서 외부 장치와 통신하거나 제어하는 데 사용됩니다. GPIO의 개념, 구조, 회로도 이해, 제어 방법 그리고 GPIO 디바이스 드라이버 개발까지 GPIO에 대해 설명하겠습니다.





export
echo 17 > /sys/class/gpio/export
direction
echo out > /sys/class/gpio/gpio17/direction
value
# GPIO17 핀을 High로 설정
echo 1 > /sys/class/gpio/gpio17/value
# GPIO17 핀의 입력 값 확인
cat /sys/class/gpio/gpio17/value
LED 확인
unexport
echo 17 > /sys/class/gpio/unexport
# GPIO17 핀의 기능 확인 (GPFSEL1 레지스터의 23:21 비트)
devmem 0xFE200004
# 출력 예시: 0x00024000
# 23:21 비트가 000 이므로 GPIO17은 입력 모드로 설정# GPIO17 핀을 출력 모드로 설정 (GPFSEL1 레지스터의 23:21 비트를 001로 설정)
devmem 0xFE200004 32 0x00024000 # 0000 0000 0000 0000 0010 0100 0000 0000# GPIO17 핀을 High로 설정 (GPSET0 레지스터의 17번 비트를 1로 설정)
devmem 0xFE20001C 32 0x00020000 # 0000 0000 0000 0010 0000 0000 0000 0000# GPIO17 핀을 Low로 설정 (GPCLR0 레지스터의 17번 비트를 1로 설정)
devmem 0xFE200028 32 0x00020000 # 0000 0000 0000 0010 0000 0000 0000 000// 모듈 파라미터로 GPIO 핀 번호와 출력 값을 설정
#define KDT_GPIO_OUTPUT 17
#define KDT_GPIO_INPUT 16
static ssize_t kdt_driver_read(struct file *filp, char __user *buf, size_t len, loff_t *offset)
{
// copy_to_user() 함수의 반환값을 0과 비교하여 복사 성공 여부 확인
char data[2];
data[0] = (char) gpio_get_value(KDT_GPIO_INPUT) + '0';
data[1] = '\0';
if (copy_to_user(buf, data, 1)) { // GPIO 핀 값을 읽어와서 사용자 공간으로 복사
pr_err("Failed to copy data to user space\n");
return -EFAULT; // 에러 발생 시 -EFAULT 반환
}
pr_info("Value of button: %d\n", gpio_get_value(KDT_GPIO_INPUT)); // GPIO 핀 값 읽기
return 1; // 복사한 바이트 수 반환
}
static ssize_t kdt_driver_write(struct file *filp, const char __user *buf, size_t len, loff_t *offset)
{
// copy_from_user() 함수의 반환값을 0과 비교하여 복사 성공 여부 확인
char kernel_write_buffer[BUF_SIZE];
if (copy_from_user(kernel_write_buffer, buf, len)) {
pr_err("Failed to copy data from user space\n");
return -EFAULT; // 에러 발생 시 -EFAULT 반환
}
kernel_write_buffer[len] = '\0';
switch (kernel_write_buffer[0])
{
case '0':
pr_info("low\n");
gpio_set_value(KDT_GPIO_OUTPUT, 0); // GPIO 핀 값을 0으로 설정
break;
case '1':
pr_info("high\n");
gpio_set_value(KDT_GPIO_OUTPUT, 1); // GPIO 핀 값을 1로 설정
break;
default:
pr_info("Invalid Input!\n");
break;
}
pr_info("write: done\n");
return len; // 복사한 바이트 수 반환
}
static int __init kdt_module_init(void)
{
/* 여기서 노드를 할당 받는다. */
if (alloc_chrdev_region(&kdt_dev, 0, 1, DRIVER_NAME) < 0) {
pr_err("Device Nr. could not be allocated!\n");
return -1;
}
pr_info("Allocated Major = %d & Minor = %d \n", MAJOR(kdt_dev), MINOR(kdt_dev));
/* device class 생성 */
if ((kdt_class = class_create(THIS_MODULE, DRIVER_CLASS)) == NULL) {
pr_err("Device class can not be created!\n");
goto ClassError;
}
/* device file 생성 */
if (device_create(kdt_class, NULL, kdt_dev, NULL, DRIVER_NAME) == NULL) {
pr_err("Can not create device file!\n");
goto FileError;
}
/* character device file 초기화 */
cdev_init(&kdt_cdev, &fops);
/* 커널에 등록 */
if (cdev_add(&kdt_cdev, kdt_dev, 1) == -1) {
pr_err("Registering of device to kernel failed!\n");
goto AddError;
}
// GPIO 핀 요청
if (gpio_request(KDT_GPIO_OUTPUT, "kdt-gpio-17")) {
pr_err("Can not allocate GPIO 17\n");
goto GpioOutError;
}
// GPIO 핀을 출력으로 설정
if (gpio_direction_output(KDT_GPIO_OUTPUT, 0)) {
pr_err("Can not set GPIO 17 to output!\n");
goto GpioOutDirError;
}
// GPIO 핀 요청
if (gpio_request(KDT_GPIO_INPUT, "kdt-gpio-16")) {
pr_err("Can not allocate GPIO 16\n");
goto GpioInError;
}
// GPIO 핀을 입력으로 설정
if (gpio_direction_input(KDT_GPIO_INPUT)) {
pr_err("Can not set GPIO 16 to input!\n");
goto GpioInDirError;
}
pr_info("kdt_gpio_driver loaded\n");
return 0;
GpioInDirError:
gpio_free(KDT_GPIO_INPUT);
pr_err("Failed to set GPIO 16 to input!\n");
GpioInError:
gpio_free(KDT_GPIO_OUTPUT);
pr_err("Can not allocate GPIO 16\n");
GpioOutDirError:
gpio_free(KDT_GPIO_OUTPUT);
pr_err("Can not set GPIO 17 to output!\n");
GpioOutError:
cdev_del(&kdt_cdev);
pr_err("Can not allocate GPIO 17\n");
AddError:
device_destroy(kdt_class, kdt_dev);
pr_err("Registering of device to kernel failed!\n");
FileError:
class_destroy(kdt_class);
pr_err("Can not create device file!\n");
ClassError:
unregister_chrdev_region(kdt_dev, 1);
pr_err("Device class can not be created!\n");
return -1;
}
static void __exit kdt_module_exit(void)
{
gpio_set_value(KDT_GPIO_OUTPUT, 0); // 모듈 언로드 시 GPIO 핀을 0으로 설정
gpio_free(KDT_GPIO_OUTPUT); // GPIO 핀 사용 해제
gpio_free(KDT_GPIO_INPUT);
cdev_del(&kdt_cdev);
device_destroy(kdt_class, kdt_dev);
class_destroy(kdt_class);
unregister_chrdev_region(kdt_dev, 1);
pr_info("kdt_gpio_driver unloaded\n");
}#include <stdio.h>#include <stdlib.h>#include <fcntl.h>#include <unistd.h>#include <string.h>#define MODULE_FILENAME "/dev/kdt_gpio_driver"int main(int argc, char *argv[])
{
int dev;
char buf = (argc > 1) ? atoi(argv[1]) : 1;
dev = open(MODULE_FILENAME, O_RDWR | O_NDELAY);
if (dev < 0) {
printf("module open error\n");
exit(1);
}
if (write(dev, &buf, 1) < 0) {
printf("write error\n");
goto err;
}
if (read(dev, &buf, 1) < 0) {
printf("read error\n");
goto err;
}
printf("read data: %c\n", buf);
err:
close(dev);
return 0;
}Debugfs 마운트
sudo mount -t debugfs none /sys/kernel/debug
pinctrl 정보 확인
cat /sys/kernel/debug/pinctrl/pinctrl-handles
cat /sys/kernel/debug/pinctrl/pinctrl-maps
cat /sys/kernel/debug/pinctrl/fe200000.gpio/pinmux-pins # BCM2711의 GPIO 핀 정보
Pinmux settings per pin
Format: pin (name): (config)
pin 0 (GPIO0): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 1 (GPIO1): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 2 (GPIO2): gpio_in (GPIO UNCLAIMED)
pin 3 (GPIO3): gpio_in (GPIO UNCLAIMED)
pin 4 (GPIO4): gpio_in (GPIO UNCLAIMED)
pin 5 (GPIO5): gpio_in (GPIO UNCLAIMED)
pin 6 (GPIO6): gpio_in (GPIO UNCLAIMED)
...
pin 17 (GPIO17): gpio_out (GPIO UNCLAIMED)
...GPIO의 개념, 구조, 회로도 이해, 제어 방법 그리고 핀 설정 확인 방법까지 GPIO 전반에 대해 다루었습니다. GPIO는 SoC에서 외부 장치와 통신하고 제어하는데 필수적인 요소이며 리눅스 커널은 GPIO를 효율적으로 관리하기 위한 다양한 인터페이스와 프레임워크를 제공합니다.