메뉴 건너뛰기

제어 - RaspberryPi.NCLab

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


1. 목표

system timer를 사용하여 정밀하게 시간 제어

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



2. gpio-ok04의 기능

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

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

다. open()함수로 열 때 ok04_open()함수가 호출되어 CUR_GPIO로 정의된 GPIO 16번을 output으로 지정하고 set과 clear를 system timer를 사용하여10회 반복(1초 간격)

라. close()함수로 닫을 때 ok04_release()함수가 호출되어 CUR_GPIO로 정의된 GPIO 16번을 clear함

마. compile 시 다른 GPIO를 선택할 수 있도록 함수 일반화 





3. 실습 순서

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

나. linux/driver/gpio/Makefile 수정

다. gpio-ok04모듈 컴파일

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

마. gpio-ok04모듈 삽입

바. gpio-ok04장치 파일 생성

사. gpio_ok04_app.c 추가

아. gpio_ok04_app.c 컴파일

자. GPIO 16번에 LED 연결

차. gpio_ok04_app실행

카. gpio-ok04모듈 제거




4. linux/driver/gpio/gpio-ok04.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_OK04_MAJOR_NUMBER 	224
/* 모듈 이름 변경됨 */
#define DEV_OK04_NAME			"gpio-ok04"

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

#define TIMER_DELAY				500000

#define CUR_GPIO				16

#define BCM2835_PERI_BASE				0xF2000000
#define GPIO_BASE  (BCM2835_PERI_BASE + 0x00200000)
/* 커널 모드의 가상 메모리에서 System Timer Base 주소 */
#define TIMER_BASE (BCM2835_PERI_BASE + 0x00003000)

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(
	const unsigned int pin_num,
	const unsigned int mode)
{
	volatile unsigned int *gpio = get_gpio_addr();
	unsigned int pin_bank = pin_num / 10;

	if(pin_num > 53) return -1;
	if(mode > 7) return -1;

	gpio += pin_bank;

	set_bits(gpio, (pin_num % 10) * 3, mode, 0x7);

	return 0;
}

static int set_pin(
	const unsigned int pin_num, 
	const unsigned int status)
{
	volatile unsigned int *gpio = get_gpio_addr();
	unsigned int pin_bank = pin_num >> 5;

	if(pin_num > 53) return -1;
	if(status != S_OFF && status != S_ON) return -1;

	gpio += pin_bank;

	if(status == S_OFF){
		set_bits(
			gpio + 0x28/sizeof(unsigned int), 
			pin_num, S_HIGH, 0x1);
		set_bits(
			gpio + 0x28/sizeof(unsigned int), 
			pin_num, S_LOW, 0x1);
	}
	else if(status == S_ON){
		set_bits(
			gpio + 0x1C/sizeof(unsigned int), 
			pin_num, S_HIGH, 0x1);
		set_bits(
			gpio + 0x1C/sizeof(unsigned int), 
			pin_num, S_LOW, 0x1);
	}

	return 0;
}

/* 함수 호출 시 System Timer Base 주소를 반환 */
static volatile unsigned int * get_timer_addr(void){
	return (volatile unsigned int *)TIMER_BASE;
}

/* 함수 호출 시 현재까지의 time stamp를 반환 */
static unsigned long get_time_stamp(void){
	volatile unsigned long * timer 
		= (volatile unsigned long *) get_timer_addr();
	/* System Timer의 time stamp가 저장되는 레지스터는 base로부터 0x04만큼 떨어진 곳에 존재함 */
	/* 0x04부터 0x08까지 32bit짜리 두 개를 사용하여 64bit 정수를 기록하고 있음 */
	return *(timer + 0x04/sizeof(unsigned int));
}

/* 인자 delay만큼의 시간을 us단위로 기다리는 함수 */
static int timer_wait(const unsigned long delay){
	unsigned long start = get_time_stamp();
	unsigned long elapsed = 0;
	//printk("start = %ld\n", start);
	/* 함수가 불리고 나서부터 경과된 시간이 delay보다 같거나 커질때까지 반복 */
	while(elapsed < delay){
		elapsed = get_time_stamp() - start;
		//printk("elapsed = %ld\n", elapsed);
	}
	return 0;
}

/* CUR_GPIO에 해당하는 GPIO의 상태를 1초 간격으로 10회 점멸시키고 커널 메시지를 출력 */
static int ok04_open(struct inode *inode, struct file *filp){
	int i = 10;
	if(func_pin(CUR_GPIO, M_OUTPUT) != 0){
		printk("[gpio-ok04] func_pin() error!\n");
		return -1;
	}
	while(i--){
		if(set_pin(CUR_GPIO, S_OFF) != 0){
			printk("[gpio-ok04] set_pin() error!\n");
			return -1;
		}
		/* 앞에서 사용하던 busy wait을 사용하지 않고 System Timer를 사용 */
		timer_wait(TIMER_DELAY);

		if(set_pin(CUR_GPIO, S_ON) != 0){
			printk("[gpio-ok04] set_pin() error!\n");
			return -1;
		}
		timer_wait(TIMER_DELAY);
	}

	printk("[gpio-ok04] ok04_open()\n");
	return 0;
}

/* CUR_GPIO에 해당하는 GPIO를 clear하고 커널 메시지를 출력 */
static int ok04_release(struct inode *inode, struct file *filp){
	set_pin(CUR_GPIO, S_OFF);
	printk("[gpio-ok04] ok04_close()\n");
	return 0;
}

/* 나머지 ok04_release(), ok04_init(), ok04_exit() 함수와
 ok04_fops 변수, module_init(), module_exit() 매크로는
 각각 함수 및 인자들의 이름만 ok03에서 ok04로 바뀌고 
 내부적인 기능은 ok03과 동일하므로 생략함 */
static struct file_operations ok04_fops = {
	.owner		= THIS_MODULE,
	.open		= ok04_open,
	.release	= ok04_release,
	//.read		= ok04_read,
	//.write	= ok04_write,
	//.ioctl	= ok04_ioctl,
};

static int ok04_init(void){
	printk("[gpio-ok04] ok04_init()\n");
	register_chrdev(DEV_OK04_MAJOR_NUMBER, DEV_OK04_NAME, &ok04_fops);
	return 0;
}

static void ok04_exit(void){
	printk("[gpio-ok04] ok04_exit()\n");
	unregister_chrdev(DEV_OK04_MAJOR_NUMBER, DEV_OK04_NAME);

}

module_init(ok04_init);
module_exit(ok04_exit);

MODULE_LICENSE("Dual BSD/GPL");

* GPIO에 관련된 레지스터의 설정 방법은 bcm2835의 datasheet 참고

BCM_2835datasheet.pdf


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

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


6. gpio-ok04 모듈 컴파일

가. 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-ok04.ko 전송

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

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


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

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

8. gpio-ok04 모듈 삽입

* RaspberryPi에서 진행

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

# cd /root
# insmod gpio-ok04.ko

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

# lsmod


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

# dmesg

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



9. gpio-ok04 장치 파일 생성

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

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

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

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

# mknod /dev/ok04 c 224 0

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


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

# ls /dev/ok04 -al


10. gpio_ok04_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_ok04_app.c 역시 주번호와 장치파일 이름만 바뀌고 
 내부적인 기능은 ok01과 동일하므로 생략함 */

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

int main(int argc, char *argv[]){
	int fd = 0;
    /* 장치파일의 주번호가 변경됨 */
	//mknod(_MORSE_PATH_, S_IRWXU | S_IRWXG | S_IFCHR, MKDEV(224, 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_ok04_app.c 컴파일

* RaspberryPi에서 진행

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

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

# gcc gpio_ok04_app.c -o gpio_ok04_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_ok04_app 실행

* RaspberryPi에서 진행

# ./gpio_ok04_app


가. LED가 1초 간격으로 10회 점멸하는 것을 확인

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

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

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


14. gpio-ok04 모듈 제거

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

# rm /dev/ok04

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

# rmmod gpio-ok04

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

# lsmod | grep gpio_ok04 

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

# 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 - 04 busy wait을 사용하여 LED를 점멸하게 만들기

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

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


위로