메뉴 건너뛰기

제어 - RaspberryPi.NCLab

* 앞에서 이미 설명한 것들은 생략하고 넘어가기 때문에 생략된 내용은 앞에서 참고할 것


1. 목표

busy wait을 사용하여 LED를 점멸하게 만들기

* 여기서 사용될 디바이스 드라이버의 이름은 gpio-ok02



2. gpio-ok02의 기능

가. 등록 시 ok02_init()함수 호출

나. 해제 시 ok02_exit()함수 호출

다. open()함수로 열 때 ok02_open()함수가 호출되어 GPIO 16번의 Function을 output으로 지정하고 set과 clear를 busy wait 방식으로 10회 반복(LED가 점멸함)

라. close()함수로 닫을 때 ok02_release()함수가 호출되어GPIO 16번을 clear함(LED가 꺼짐)




3. 실습 순서

가. linux/driver/gpio/gpio-ok02.c  추가

나. linux/driver/gpio/Makefile 수정

다. gpio-ok02모듈 컴파일

라. RaspberryPi로 linux/driver/gpio/gpio-ok02.ko 전송

마. gpio-ok02모듈 삽입

바. gpio-ok02장치 파일 생성

사. gpio_ok02_app.c 추가

아. gpio_ok02_app.c 컴파일

자. GPIO 16번에 LED 연결

차. gpio_ok02_app실행

카. gpio-ok02모듈 제거



4. linux/driver/gpio/gpio-ok02.c  추가

가. WinSCP 프로그램을 이용하면 Windows에서 Ubuntu로 파일을 쉽게 옮길 수 있음


나. 또는 VMware의 공유 폴더 기능을 이용하여 복사 할 수 있음

* 개발환경 구축하기 - 04 Ubuntu 설치 참고


다. 또는 Ubuntu에서 vim 유틸리티를 통해 직접 작성

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

#include <linux/fs.h>

/* 주번호 변경됨 */
#define DEV_OK02_MAJOR_NUMBER 	222
/* 모듈 이름 변경됨 */
#define DEV_OK02_NAME			"gpio-ok02"

#define M_INPUT					0
#define M_OUTPUT				1
#define S_LOW					0
#define S_HIGH					1

#define BCM2835_PERI_BASE				0xF2000000
#define GPIO_BASE  (BCM2835_PERI_BASE + 0x00200000)

static volatile unsigned int * get_gpio_addr(void){
	return (volatile unsigned int *)GPIO_BASE;
}

static int set_bits(
	volatile unsigned int *addr, 
	const unsigned int shift, 
	const unsigned int val, 
	const unsigned int mask)
{
	unsigned int temp = *addr;
	
	temp &= ~(mask << shift);
	temp |= (val & mask) << shift;
	*addr = temp;

	return 0;
}

static int func_pin_16(const unsigned int mode){
	volatile unsigned int *gpio = get_gpio_addr();
	const unsigned int sel = 0x04;
	const unsigned int shift = 18;

	if(mode > 7) return -1;
	
	set_bits(gpio + sel/sizeof(unsigned int), shift, mode, 0x7);

	return 0;
}
 
static int set_pin_16(void){
	volatile unsigned int *gpio = get_gpio_addr();
	const unsigned int sel = 0x1C;
	const unsigned int shift = 16;

	set_bits(gpio + sel/sizeof(unsigned int), shift, S_HIGH, 0x1);
	set_bits(gpio + sel/sizeof(unsigned int), shift, S_LOW, 0x1);

	return 0;
}

static int clr_pin_16(void){
	volatile unsigned int *gpio = get_gpio_addr();
	const unsigned int sel = 0x28;
	const unsigned int shift = 16;

	set_bits(gpio + sel/sizeof(unsigned int), shift, S_HIGH, 0x1);
	set_bits(gpio + sel/sizeof(unsigned int), shift, S_LOW, 0x1);

	return 0;
}

/* GPIO 16번에 연결된 LED를 10회 점멸시키고 메시지를 출력하는 함수 */
static int ok02_open(struct inode *inode, struct file *filp){
	int i = 10;
	/* volatile 키워드를 사용하지 않으면 컴파일러가 이 변수를 없애버림 */
	volatile int busy_wait;
	if(func_pin_16(M_OUTPUT) != 0){
		printk("[gpio-ok02] func_pin_16() error!\n");
		return -1;
	}
	/* 10회 반복 */
	while(i--){
		/* LED를 끔 */
		clr_pin_16();
		/* 무의미한 연산을 반복(busy wait)하며 시간을 지연시킴 */
		busy_wait = 0x3F00000;
		while(busy_wait--);
		
		/* LED를 켬 */
		set_pin_16();
		/* 무의미한 연산을 반복(busy wait)하며 시간을 지연시킴 */
		busy_wait = 0x3F00000;
		while(busy_wait--);
	}

	printk("[gpio-ok02] ok02_open()\n");
	return 0;
}

/* 나머지 ok02_release(), ok02_init(), ok02_exit() 함수와
 ok02_fops 변수, module_init(), module_exit() 매크로는
 각각 함수 및 인자들의 이름만 ok01에서 ok02로 바뀌고 
 내부적인 기능은 ok01과 동일하므로 생략함 */
static int ok02_release(struct inode *inode, struct file *filp){
	clr_pin_16();
	printk("[gpio-ok02] ok02_close()\n");
	return 0;
}

static struct file_operations ok02_fops = {
	.owner		= THIS_MODULE,
	.open		= ok02_open,
	.release	= ok02_release,
	//.read		= ok02_read,
	//.write	= ok02_write,
	//.ioctl	= ok02_ioctl,
};

static int ok02_init(void){
	printk("[gpio-ok02] ok02_init()\n");
	register_chrdev(DEV_OK02_MAJOR_NUMBER, DEV_OK02_NAME, &ok02_fops);
	return 0;
}

static void ok02_exit(void){
	printk("[gpio-ok02] ok02_exit()\n");
	unregister_chrdev(DEV_OK02_MAJOR_NUMBER, DEV_OK02_NAME);

}

module_init(ok02_init);
module_exit(ok02_exit);

MODULE_LICENSE("Dual BSD/GPL");


5. linux/driver/gpio/Makefile 수정

* Ubuntu에서 진행

가. 해당 디렉토리에 있는 Device Driver를 Kernel에 어떤 방식으로 포함할 것인지에 대한 설정들이 포함되어 있음

1). obj-m : 모듈 형태로 제작 

2). obj-y : Kernel에 포함하여 제작

3). obj-  : 모듈을 생성하지 않음


나. 다음과 같이 마지막 부분을 수정

* 앞으로 새로운 모듈을 생성할 때마다 같은 방법으로 추가할 것

# generic gpio support: platform drivers, dedicated expander chips, etc

ccflags-$(CONFIG_DEBUG_GPIO)	+= -DDEBUG

obj-$(CONFIG_GPIO_DEVRES)	+= devres.o
obj-$(CONFIG_GPIOLIB)		+= gpiolib.o
obj-$(CONFIG_GPIOLIB)		+= gpiolib-legacy.o
obj-$(CONFIG_OF_GPIO)		+= gpiolib-of.o
obj-$(CONFIG_GPIO_SYSFS)	+= gpiolib-sysfs.o
obj-$(CONFIG_GPIO_ACPI)		+= gpiolib-acpi.o

# Device drivers. Generally keep list sorted alphabetically
obj-$(CONFIG_GPIO_GENERIC)	+= gpio-generic.o

obj-$(CONFIG_GPIO_74X164)	+= gpio-74x164.o
obj-$(CONFIG_GPIO_ADNP)		+= gpio-adnp.o
obj-$(CONFIG_GPIO_ADP5520)	+= gpio-adp5520.o
obj-$(CONFIG_GPIO_ADP5588)	+= gpio-adp5588.o
obj-$(CONFIG_GPIO_AMD8111)	+= gpio-amd8111.o
obj-$(CONFIG_GPIO_ARIZONA)	+= gpio-arizona.o
obj-$(CONFIG_GPIO_BCM_KONA)	+= gpio-bcm-kona.o
obj-$(CONFIG_GPIO_BT8XX)	+= gpio-bt8xx.o
obj-$(CONFIG_GPIO_CLPS711X)	+= gpio-clps711x.o
obj-$(CONFIG_GPIO_CS5535)	+= gpio-cs5535.o
obj-$(CONFIG_GPIO_CRYSTAL_COVE)	+= gpio-crystalcove.o
obj-$(CONFIG_GPIO_DA9052)	+= gpio-da9052.o
obj-$(CONFIG_GPIO_DA9055)	+= gpio-da9055.o
obj-$(CONFIG_GPIO_DAVINCI)	+= gpio-davinci.o
obj-$(CONFIG_GPIO_DWAPB)	+= gpio-dwapb.o
obj-$(CONFIG_GPIO_EM)		+= gpio-em.o
obj-$(CONFIG_GPIO_EP93XX)	+= gpio-ep93xx.o
obj-$(CONFIG_GPIO_F7188X)	+= gpio-f7188x.o
obj-$(CONFIG_GPIO_GE_FPGA)	+= gpio-ge.o
obj-$(CONFIG_GPIO_GRGPIO)	+= gpio-grgpio.o
obj-$(CONFIG_GPIO_ICH)		+= gpio-ich.o
obj-$(CONFIG_GPIO_IOP)		+= gpio-iop.o
obj-$(CONFIG_GPIO_IT8761E)	+= gpio-it8761e.o
obj-$(CONFIG_GPIO_JANZ_TTL)	+= gpio-janz-ttl.o
obj-$(CONFIG_GPIO_KEMPLD)	+= gpio-kempld.o
obj-$(CONFIG_ARCH_KS8695)	+= gpio-ks8695.o
obj-$(CONFIG_GPIO_INTEL_MID)	+= gpio-intel-mid.o
obj-$(CONFIG_GPIO_LP3943)	+= gpio-lp3943.o
obj-$(CONFIG_ARCH_LPC32XX)	+= gpio-lpc32xx.o
obj-$(CONFIG_GPIO_LYNXPOINT)	+= gpio-lynxpoint.o
obj-$(CONFIG_GPIO_MAX730X)	+= gpio-max730x.o
obj-$(CONFIG_GPIO_MAX7300)	+= gpio-max7300.o
obj-$(CONFIG_GPIO_MAX7301)	+= gpio-max7301.o
obj-$(CONFIG_GPIO_MAX732X)	+= gpio-max732x.o
obj-$(CONFIG_GPIO_MC33880)	+= gpio-mc33880.o
obj-$(CONFIG_GPIO_MC9S08DZ60)	+= gpio-mc9s08dz60.o
obj-$(CONFIG_GPIO_MCP23S08)	+= gpio-mcp23s08.o
obj-$(CONFIG_GPIO_ML_IOH)	+= gpio-ml-ioh.o
obj-$(CONFIG_GPIO_MM_LANTIQ)	+= gpio-mm-lantiq.o
obj-$(CONFIG_GPIO_MOXART)	+= gpio-moxart.o
obj-$(CONFIG_GPIO_MPC5200)	+= gpio-mpc5200.o
obj-$(CONFIG_GPIO_MPC8XXX)	+= gpio-mpc8xxx.o
obj-$(CONFIG_GPIO_MSIC)		+= gpio-msic.o
obj-$(CONFIG_GPIO_MSM_V1)	+= gpio-msm-v1.o
obj-$(CONFIG_GPIO_MSM_V2)	+= gpio-msm-v2.o
obj-$(CONFIG_GPIO_MVEBU)        += gpio-mvebu.o
obj-$(CONFIG_GPIO_MXC)		+= gpio-mxc.o
obj-$(CONFIG_GPIO_MXS)		+= gpio-mxs.o
obj-$(CONFIG_GPIO_OCTEON)	+= gpio-octeon.o
obj-$(CONFIG_GPIO_OMAP)		+= gpio-omap.o
obj-$(CONFIG_GPIO_PCA953X)	+= gpio-pca953x.o
obj-$(CONFIG_GPIO_PCF857X)	+= gpio-pcf857x.o
obj-$(CONFIG_GPIO_PCH)		+= gpio-pch.o
obj-$(CONFIG_GPIO_PL061)	+= gpio-pl061.o
obj-$(CONFIG_GPIO_PXA)		+= gpio-pxa.o
obj-$(CONFIG_GPIO_RC5T583)	+= gpio-rc5t583.o
obj-$(CONFIG_GPIO_RDC321X)	+= gpio-rdc321x.o
obj-$(CONFIG_GPIO_RCAR)		+= gpio-rcar.o
obj-$(CONFIG_GPIO_SAMSUNG)	+= gpio-samsung.o
obj-$(CONFIG_ARCH_SA1100)	+= gpio-sa1100.o
obj-$(CONFIG_GPIO_SCH)		+= gpio-sch.o
obj-$(CONFIG_GPIO_SCH311X)	+= gpio-sch311x.o
obj-$(CONFIG_GPIO_SODAVILLE)	+= gpio-sodaville.o
obj-$(CONFIG_GPIO_SPEAR_SPICS)	+= gpio-spear-spics.o
obj-$(CONFIG_GPIO_STA2X11)	+= gpio-sta2x11.o
obj-$(CONFIG_GPIO_STMPE)	+= gpio-stmpe.o
obj-$(CONFIG_GPIO_STP_XWAY)	+= gpio-stp-xway.o
obj-$(CONFIG_GPIO_SX150X)	+= gpio-sx150x.o
obj-$(CONFIG_GPIO_SYSCON)	+= gpio-syscon.o
obj-$(CONFIG_GPIO_TB10X)	+= gpio-tb10x.o
obj-$(CONFIG_GPIO_TC3589X)	+= gpio-tc3589x.o
obj-$(CONFIG_ARCH_TEGRA)	+= gpio-tegra.o
obj-$(CONFIG_GPIO_TIMBERDALE)	+= gpio-timberdale.o
obj-$(CONFIG_GPIO_PALMAS)	+= gpio-palmas.o
obj-$(CONFIG_GPIO_TPS6586X)	+= gpio-tps6586x.o
obj-$(CONFIG_GPIO_TPS65910)	+= gpio-tps65910.o
obj-$(CONFIG_GPIO_TPS65912)	+= gpio-tps65912.o
obj-$(CONFIG_GPIO_TS5500)	+= gpio-ts5500.o
obj-$(CONFIG_GPIO_TWL4030)	+= gpio-twl4030.o
obj-$(CONFIG_GPIO_TWL6040)	+= gpio-twl6040.o
obj-$(CONFIG_GPIO_TZ1090)	+= gpio-tz1090.o
obj-$(CONFIG_GPIO_TZ1090_PDC)	+= gpio-tz1090-pdc.o
obj-$(CONFIG_GPIO_UCB1400)	+= gpio-ucb1400.o
obj-$(CONFIG_GPIO_VIPERBOARD)	+= gpio-viperboard.o
obj-$(CONFIG_GPIO_VR41XX)	+= gpio-vr41xx.o
obj-$(CONFIG_GPIO_VX855)	+= gpio-vx855.o
obj-$(CONFIG_GPIO_WM831X)	+= gpio-wm831x.o
obj-$(CONFIG_GPIO_WM8350)	+= gpio-wm8350.o
obj-$(CONFIG_GPIO_WM8994)	+= gpio-wm8994.o
obj-$(CONFIG_GPIO_XGENE)	+= gpio-xgene.o
obj-$(CONFIG_GPIO_XILINX)	+= gpio-xilinx.o
obj-$(CONFIG_GPIO_XTENSA)	+= gpio-xtensa.o
obj-$(CONFIG_GPIO_ZEVIO)	+= gpio-zevio.o
obj-$(CONFIG_GPIO_ZYNQ)		+= gpio-zynq.o

# ok02 Driver Module
obj-m						+= gpio-ok02.o


6. gpio-ok02 모듈 컴파일

가. linux/ 디렉토리에서 다음과 같이 입력

$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
* RaspberryPi의 Kernel Soruce에 포함시켜 Kernel Module 형태의 Device Driver를 만드는 과정


나. 완료 후 명령어 뒤에 modules를 추가하여 다시 입력

$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- modules

* 앞으로 자주 사용될 명령어이므로 스크립트로 만들어 놓는 것을 추천



7. RaspberryPi로 linux/driver/gpio/gpio-ok02.ko 전송

가. gpio-ok02.ko가 생성 된 것을 확인

$ cd drivers/gpio
$ ls gpio-ok02*
* .ko는 kernel object를 의미하며 이 파일이 커널 모듈 형태의 디바이스 드라이버임


나. scp를 사용하여 RaspberryPi로 전송

$ scp gpio-ok02.ko root@192.168.1.231:/root
* 192.168.1.231은 RaspberryPi의 주소이며 상황에 따라 다를 수 있으므로 유의

8. gpio-ok02 모듈 삽입

*RaspberryPi에서 진행

가. 다음과 같은 명령어를 사용하여 모듈 삽입

# cd /root
# insmod gpio-ok02.ko

나. 다음과 같은 명령어를 사용하여 모듈 삽입 확인

# lsmod


다. 다음과 같은 명령어를 사용하여 커널 메시지 확인

# dmesg

* 디바이스 드라이버가 커널에 삽입되었을 때 커널 메시지가 출력되었음



9. gpio-ok02 장치 파일 생성

가. 삽입된 모듈로 장치 파일을 생성해야 사용자 프로그램에서 사용 가능

나. 다음과 같은 명령어를 사용하여 장치 파일 생성

* 사용자 프로그램에서도 mknod() 함수를 사용하여 생성 가능하지만 여기서는 쉘 명령어로 생성할 것임

* # mknod [장치 파일 경로] [장치 종류] [주번호] [부번호]

# mknod /dev/ok02 c 222 0

* 응용프로그램에서 이 장치 파일을 open() 함수를 통해 열 것이므로 주번호와 부번호, 그리고 이름을 잘 기억할 것


다. 다음과 같은 명령어를 사용하여 장치 파일 생성 확인

# ls /dev/ok02 -al


10. gpio_ok02_app.c 추가

가. Windows에서 WinSCP로 RaspberryPi에 접속하여 복사


나. 또는 Windows에서 Ubuntu로 공유 폴더를 이용하여 복사한 후 scp를 이용하여 RaspberryPi로 복사

* 번거로우므로 권장하지 않음


다. 또는 RaspberryPi에서 vim 유틸리티를 통해 직접 작성

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/kdev_t.h>

/* gpio_ok02_app.c 역시 주번호와 장치파일 이름만 바뀌고 
 내부적인 기능은 ok01과 동일하므로 생략함 */

/* 사용할 장치 파일의 이름이 변경됨 */
#define _MORSE_PATH_ "/dev/ok02"

int main(int argc, char *argv[]){
	int fd = 0;
	/* 장치파일의 주번호가 변경됨 */
	//mknod(_MORSE_PATH_, S_IRWXU | S_IRWXG | S_IFCHR, MKDEV(222, 0));
	if((fd = open( _MORSE_PATH_, O_RDWR | O_NONBLOCK)) < 0){
		perror("open()");
		exit(1);
	}
	printf("open sungkong!\n");	sleep(2);
	close(fd);
	return 0;
}


11. gpio_ok02_app.c 컴파일

* RaspberryPi에서 진행

가. RaspberryPi에 포함된 컴파일러를 사용하여 컴파일

나. 다음과 같은 명령어를 사용하여 컴파일

# gcc gpio_ok02_app.c -o gpio_ok02_app



12. GPIO 16번에 LED 연결

가. RaspberryPi 모델 별 GPIO 번호는 Raspberry Pi 별 GPIO Pin Numbering를 참고하여 판별

* 이 문서는 RaspberryPi model B+를 기준으로 함


나. 다음 그림과 같이 GPIO에 연결하기에 앞서 5V 핀에 연결하여 정상 작동 여부 확인

그림21.png

* 여기서 저항 없이 LED만 5V에 직접 연결할 경우 LED가 타버릴 수 있음

* 또한 LED의 극성을 고려하지 않고 연결 할 경우 LED가 타버릴 수 있음

* LED의 극성은 긴 쪽이 양의 전압(+) 짧은 쪽이 음의 전압(-)이 되도록 연결할 것


다. 정상적으로 작동한다면 5V 핀에 연결했던 것을 GPIO 16번에 연결

그림22.png

* 기본적으로 불이 켜지지 않음



13. gpio_ok02_app 실행

* RaspberryPi에서 진행

# ./gpio_ok02_app


가. LED가 10회 점멸하는 것을 확인

나. “open sungkong!” 메시지를 확인

다. 다음과 같은 명령어를 사용하여 커널 메시지 확인

# dmesg
* 응용프로그램에 의해 장치파일이 열렸을 때, 닫혔을 때 각각 커널 메시지가 출력되었음


14. gpio-ok02 모듈 제거

가. 다음과 같은 명령어를 사용하여 장치 파일 제거

# rm /dev/ok02

나. 다음과 같은 명령어를 사용하여 모듈 제거

# rmmod gpio-ok02

다. 다음과 같은 명령어를 사용하여 모듈 제거 확인

# lsmod | grep gpio_ok02 

라. 다음과 같은 명령어를 사용하여 커널 메시지 확인

# dmesg 
* 디바이스 드라이버가 커널에서 제거되었을 때 커널 메시지가 출력되었음



--

Device Driver를 이용한 GPIO Control - 01 개발 환경 소개

Device Driver를 이용한 GPIO Control - 02 Linux Device Driver 기본 형식 제작

Device Driver를 이용한 GPIO Control - 03 GPIO 16번에 LED를 연결하여 켜고 끄기

Device Driver를 이용한 GPIO Control - 05 다른 GPIO도 제어할 수 있도록 함수 일반화

Device Driver를 이용한 GPIO Control - 06 system timer를 사용하여 정밀하게 시간 제어

Device Driver를 이용한 GPIO Control - 07 사용자 입력을 받아와 Morse Code로 출력


위로