ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 리눅스 어플리케이션에서 GPIO 사용하기
    프로그래밍/리눅스 2013. 3. 13. 14:21
    728x90

    ※ 이 글에 사용된 마이크로프로세서는 TI사의 Sitara AM1808을 사용하였습니다.

     

    리눅스 개발을 처음 접했을 때 주변 기기를 제어할 줄 몰라 난감했던 기억이 있습니다.

     

    그나마 SPI나 I2C 등을 이용하는 것은 /dev 디렉터리에 있는 노드들을 사용해서 제어하는 방법은 여기 저기 잘 정리되어 별문제 없었지만, GPIO는 따로 노드가 존재하지 않아서 간단한 조작조차 리눅스 디바이스 드라이버로 만들어서 제어했던 기억이 있습니다...

     

    그래서 간단히(?) 리눅스 어플리케이션에서 GPIO를 사용하는 방법을 정리해 볼까 합니다.

     

    먼저, 리눅스 커널 빌드 옵션을 설정합니다.

     Device Drivers --->
     -*- GPIO Support
         [*] /sys/class/gpio/... (sysfs interface)

     

    위와 같이 옵션을 설정하고 컴파일한 리눅스 커널로 부팅해 보면 다음과 같이 '/sys/class/gpio' 디렉터리가 생성된 것을 보실 수 있습니다.

     root@am180x-evm:~# ls /sys/class/gpio
     export       gpiochip128  gpiochip64   unexport
     gpiochip0    gpiochip32   gpiochip96

     

    그럼, 예제로 GPIO 10번핀(GP0[10])을 제어해보도록 하겠습니다.

     root@am180x-evm:~# echo "10" > /sys/class/gpio/export

     

    export를 하고나면 다음과 같이 'gpio10' 디렉터리가 생성된 것을 확인하실 수 있습니다.

     root@am180x-evm:~# ls /sys/class/gpio
     export       gpiochip0    gpiochip32   gpiochip96
     gpio10       gpiochip128  gpiochip64   unexport

     

    그리고, 'gpio10' 디렉터리에 다음과 같은 파일이 생성된 것도 확인하실 수 있습니다.

     root@am180x-evm:~# ls /sys/class/gpio10
     active_low  edge        subsystem   value
     direction   power       uevent
    1. 아래에서 다루는 direction과 value도 export를 할 때와 같은 방법으로 변경할 수 있습니다.

     

    GPIO 핀을 출력으로 설정할 경우 (핀 상태가 Low로 설정됩니다.)

     root@am180x-evm:~# echo "out" > /sys/class/gpio/gpio10/direction
    1. "out" 대신 "low"를 사용하셔도 됩니다.

     

    GPIO 핀을 출력으로 설정하고, 핀 상태를 High로 설정할 경우

     root@am180x-evm:~# echo "high" > /sys/class/gpio/gpio10/direction

     

    GPIO 핀을 입력으로 설정할 경우

     root@am180x-evm:~# echo "in" > /sys/class/gpio/gpio10/direction

     

    GPIO 핀이 출력일 경우 핀 상태를 Low로 설정할 경우

     root@am180x-evm:~# echo "0" > /sys/class/gpio/gpio10/value

     

    GPIO 핀이 출력일 경우 핀 상태를 High로 설정할 경우

     root@am180x-evm:~# echo "1" > /sys/class/gpio/gpio10/value

     

    그럼 이제 위에 컨트롤한 것을 프로그래밍 언어로 코딩하면 어플리케이션에서 제어할 수 있겠죠~

     

    아래 예제 코드는 GPIO 인터럽트를 폴링으로 읽어오는 방법도 소개하고 있습니다.

     

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <errno.h>
    
    #define GPIO_OUTPUT 0
    #define GPIO_INPUT 1
    #define GPIO_HIGH  1
    #define GPIO_LOW  0
    
    #define GPIO_NONE  "none"
    #define GPIO_FALLING "falling"
    #define GPIO_RISING "rising"
    #define GPIO_BOTH  "both"
    
    #define SYSFS_GPIO_DIR "/sys/class/gpio"
    
    #define MAX_BUF 64
    
    int gpio_export(unsigned int gpio)
    {
    	int fd, len;
    	char buf[MAX_BUF];
    
    	fd = open(SYSFS_GPIO_DIR "/export", O_WRONLY);
    
    	if (fd < 0) {
    		fprintf(stderr, "Can't export GPIO %d pin: %s\n", gpio, strerror(errno));
    		return fd;
    	}
    
    	len = snprintf(buf, sizeof(buf), "%d", gpio);
    	write(fd, buf, len);
    	close(fd);
    
    	return 0;
    }
    
    int gpio_unexport(unsigned int gpio)
    {
    	int fd, len;
    	char buf[MAX_BUF];
    
    	fd = open(SYSFS_GPIO_DIR "/unexport", O_WRONLY);
    
    	if (fd < 0) {
    		fprintf(stderr, "Can't unexport GPIO %d pin: %s\n", gpio, strerror(errno));
    		return fd;
    	}
    
    	len = snprintf(buf, sizeof(buf), "%d", gpio);
    	write(fd, buf, len);
    	close(fd);
    
    	return 0;
    }
    
    int gpio_get_dir(unsigned int gpio, unsigned int *dir)
    {
    	int fd, len;
    	char buf[MAX_BUF];
    
    	len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/direction", gpio);
    
    	fd = open(buf, O_RDONLY);
    
    	if (fd < 0) {
    		fprintf(stderr, "Can't get GPIO %d pin direction: %s\n", gpio, strerror(errno));
    		return fd;
    	}
    
    	read(fd, &buf, MAX_BUF);
    	close(fd);
    
    	if (stricmp(buf, "in") == 0)
    		*dir = GPIO_INPUT;
    	else
    		*dir = GPIO_OUTPUT;
    
    	return 0;
    }
    
    int gpio_set_dir(unsigned int gpio, unsigned int dir, unsigned int val)
    {
    	int fd, len;
    	char buf[MAX_BUF];
    
    	len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR  "/gpio%d/direction", gpio);
    
    	fd = open(buf, O_WRONLY);
    
    	if (fd < 0) {
    		fprintf(stderr, "Can't set GPIO %d pin direction: %s\n", gpio, strerror(errno));
    		return fd;
    	}
    
    	if (dir == GPIO_OUTPUT) {
    		if (val == GPIO_HIGH)
    			write(fd, "high", 5);
    		else
    			write(fd, "out", 4);
    	} else {
    		write(fd, "in", 3);
    	}
    
    	close(fd);
    
    	return 0;
    }
    
    int gpio_get_val(unsigned int gpio, unsigned int *val)
    {
    	int fd, len;
    	char buf[MAX_BUF];
    
    	len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/value", gpio);
    
    	fd = open(buf, O_RDONLY);
    
    	if (fd < 0) {
    		fprintf(stderr, "Can't get GPIO %d pin value: %s\n", gpio, strerror(errno));
    		return fd;
    	}
    
    	read(fd, buf, 1);
    	close(fd);
    
    	if (*buf != '0')
    		*val = GPIO_HIGH;
    	else
    		*val = GPIO_LOW;
    
    	return 0;
    }
    
    int gpio_set_val(unsigned int gpio, unsigned int val)
    {
    	int fd, len;
    	char buf[MAX_BUF];
    
    	len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/value", gpio);
    
    	fd = open(buf, O_WRONLY);
    
    	if (fd < 0) {
    		fprintf(stderr, "Can't set GPIO %d pin value: %s\n", gpio, strerror(errno));
    		return fd;
    	}
    
    	if (val == GPIO_HIGH)
    		write(fd, "1", 2);
    	else
    		write(fd, "0", 2);
    
    	close(fd);
    
    	return 0;
    }
    
    int gpio_set_edge(unsigned int gpio, char *edge)
    {
    	int fd, len;
    	char buf[MAX_BUF];
    
    	len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/edge", gpio);
    
    	fd = open(buf, O_WRONLY);
    
    	if (fd < 0) {
    		fprintf(stderr, "Can't set GPIO %d pin edge: %s\n", gpio, strerror(errno));
    		return fd;
    	}
    
    	write(fd, edge, strlen(edge)+1);
    	close(fd);
    
    	return 0;
    }
    
    int gpio_open(unsigned int gpio)
    {
    	int fd, len;
    	char buf[MAX_BUF];
    
    	len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/value", gpio);
    
    	fd = open(buf, O_RDONLY|O_NONBLOCK);
    
    	if (fd < 0)
    		fprintf(stderr, "Can't open GPIO %d pin: %s\n", gpio, strerror(errno));
    
    	return fd;
    }
    
    int gpio_close(int fd)
    {
    	return close(fd);
    }
    
    int gpio_read(int fd, unsigned int *val)
    {
    	int ret;
    	char ch;
    
    	lseek(fd, 0, SEEK_SET);
    
    	ret = read(fd, &ch, 1);
    
    	if (ret != 1) {
    		fprintf(stderr, "Can't read GPIO %d pin: %s\n", gpio, strerror(errno));
    		return ret;
    	}
    
    	if (ch != '0')
    		*val = GPIO_HIGH;
    	else
    		*val = GPIO_LOW;
    
    	return 0;
    }
    
    int main(int argc, char *argv[])
    {
    	unsigned int gpio;
    	unsigned int val;
    	char *end_ptr;
    
    	if (argc < 3)
    		exit(1);
    
    	if (strcmp(argv[1], "r") == 0) {
    		gpio = strtoul(argv[2], &end_ptr, 0);
    		gpio_export(gpio);
    		gpio_set_input(gpio);
    		gpio_get_val(gpio, &val);
    		gpiof_unexport(gpio);
    		fprintf(stderr, "gpio%d = %d\n", gpio, val);
    	} else if (strcmp(argv[1], "w") == 0) {
    		if (argc < 4)
    			exit(1);
    
    		gpio = strtoul(argv[2], &end_ptr, 0);
    		val = strtoul(argv[3], &end_ptr, 0);
    		gpio_export(gpio);
    		gpio_set_output(gpio, val);
    		gpio_unexport(gpio);
    	} else if (strcmp(argv[1], "i") == 0) {
    		int fd;
    		struct pollfd fdset[2];
    		int ret;
    
    		if (argc < 4)
    			exit(1);
    
    		gpio = strtoul(argv[2], &end_ptr, 0);
    		gpio_export(gpio);
    		gpio_set_input(gpio);
    		gpiof_set_edge(gpio, argv[3]); // "none", "falling", "rising", "both"
    		fd = gpio_open(gpio);
    		gpio_read(fd, &val);
    
    		while (1) {
    			memset(fdset, 0, sizeof(fdset));
    			fdset[0].fd = STDIN_FILENO;
    			fdset[0].events = POLLIN;
    			fdset[1].fd = fd;
    			fdset[1].events = POLLPRI;
    			ret = poll(fdset, 2, 3*1000);
    
    			if (ret < 0) {
    				perror("poll");
    				break;
    			}
    
    			fprintf(stderr, ".");
    
    			if (fdset[1].revents & POLLPRI) {
    				fprintf(stderr, "\nGPIO %d interrupt occurred!\n", gpio);
    				gpio_read(fdset[1].fd, &val);
    			}
    
    			if (fdset[0].revents & POLLIN)
    				break;
    
    			fflush(stdout);
    		}
    
    		gpio_close(fd);
    		gpio_unexport(gpio);
    	} else {
    		exit(1);
    	}
    
    	return 0;
    }
    1. AM1808의 GPIO를 사용하시기에 앞서 리눅스 커널에서 PinMux 설정을 하셔야 합니다.

     

    참고 사이트

    728x90

    댓글

Designed by Tistory.