메뉴 건너뛰기

제어 - RaspberryPi.NCLab

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


1. 목표

다른 GPIO도 제어할 수 있도록 함수 일반화

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



2. gpio-ok03의 기능

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

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

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

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

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





3. 실습 순서

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

나. linux/driver/gpio/Makefile 수정

다. gpio-ok03모듈 컴파일

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

마. gpio-ok03모듈 삽입

바. gpio-ok03장치 파일 생성

사. gpio_ok03_app.c 추가

아. gpio_ok03_app.c 컴파일

자. GPIO 16번에 LED 연결

차. gpio_ok03_app실행

카. gpio-ok03모듈 제거

타. CUR_GPIO를 GPIO 25번으로 변경 

파. gpio-ok03 모듈 컴파일부터 장치 파일 생성까지 수행

하. GPIO 25번에 LED 연결

* 새롭게 추가된 부분

거. gpio_ok03_app 실행

너. gpio-ok03 모듈 제거




4. linux/driver/gpio/gpio-ok03.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_OK03_MAJOR_NUMBER 	223
/* 모듈 이름 변경됨 */
#define DEV_OK03_NAME			"gpio-ok03"

#define M_INPUT					0
#define M_OUTPUT				1
#define S_LOW					0
#define S_HIGH					1
/* LED의 상태에 대한 매크로 */
#define S_OFF					0
#define S_ON					1

/* 제어할 GPIO에 대한 매크로 */
#define CUR_GPIO				16

#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;
}

/* CUR_GPIO에 해당하는 GPIO의 Funtion을 mode로 할당 */
static int func_pin(
	const unsigned int pin_num,
	const unsigned int mode)
{
	volatile unsigned int *gpio = get_gpio_addr();
	/* 하나의 레지스터에 10개의 GPIO에 대한 Funtion을 설정할 수 있기 때문*/
	unsigned int pin_bank = pin_num / 10;

	/* 총 53개의 GPIO를 다룰 수 있음 */
	if(pin_num > 53) return -1;
	/* 하나의 GPIO에 대한 기능은 3개의 bit로 표현됨 */
	if(mode > 7) return -1;

	gpio += pin_bank;

	/* 하나의 GPIO에 대한 기능은 3개의 bit로 표현됨 */
	set_bits(gpio, (pin_num % 10) * 3, mode, 0x7);

	return 0;
}

/* CUR_GPIO에 해당하는 GPIO의 상태를 status로 할당 */
static int set_pin(
	const unsigned int pin_num, 
	const unsigned int status)
{
	volatile unsigned int *gpio = get_gpio_addr();
	/* 하나의 레지스터에 32개의 GPIO에 대한 output을 설정할 수 있기 때문 */
	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;

	/* status 값에 따라 set과 clear 중 하나를 선택 할 수 있음 */
	if(status == S_OFF){
		set_bits(
			/*  GPIO의 output을 clear하는 레지스터는 base로부터 0x28만큼 떨어진 곳에 존재 */
			gpio + 0x28/sizeof(unsigned int), 
			/* 하나의 GPIO에 대한 output은 1개의 bit로 표현됨 */
			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의 output을 set하는 레지스터는 base로부터 0x1C만큼 떨어진 곳에 존재 */
			gpio + 0x1C/sizeof(unsigned int), 
			pin_num, S_HIGH, 0x1);
		set_bits(
			gpio + 0x1C/sizeof(unsigned int), 
			pin_num, S_LOW, 0x1);
	}

	return 0;
}

/* CUR_GPIO에 해당하는 GPIO의 상태를 점멸시키고 커널 메시지를 출력 */
static int ok03_open(struct inode *inode, struct file *filp){
	int i = 10;
	volatile int busy_wait;
	/* Funtion을 선택할 GPIO를 지정할 수 있음 */
	if(func_pin(CUR_GPIO, M_OUTPUT) != 0){
		printk("[gpio-ok03] func_pin() error!\n");
		return -1;
	}
	while(i--){
		/* 상태를 변경할 GPIO를 지정할 수 있음 */
		if(set_pin(CUR_GPIO, S_OFF) != 0){
			printk("[gpio-ok03] set_pin() error!\n");
			return -1;
		}
		busy_wait = 0x3F00000;
		while(busy_wait--);

		if(set_pin(CUR_GPIO, S_ON) != 0){
			printk("[gpio-ok03] set_pin() error!\n");
			return -1;
		}
		busy_wait = 0x3F00000;
		while(busy_wait--);
	}

	printk("[gpio-ok03] ok03_open()\n");
	return 0;
}

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

/* 나머지 ok03_release(), ok03_init(), ok03_exit() 함수와
 ok03_fops 변수, module_init(), module_exit() 매크로는
 각각 함수 및 인자들의 이름만 ok02에서 ok03로 바뀌고 
 내부적인 기능은 ok02과 동일하므로 생략함 */
static struct file_operations ok03_fops = {
	.owner		= THIS_MODULE,
	.open		= ok03_open,
	.release	= ok03_release,
	//.read		= ok03_read,
	//.write	= ok03_write,
	//.ioctl	= ok03_ioctl,
};

static int ok03_init(void){
	printk("[gpio-ok03] ok03_init()\n");
	register_chrdev(DEV_OK03_MAJOR_NUMBER, DEV_OK03_NAME, &ok03_fops);
	return 0;
}

static void ok03_exit(void){
	printk("[gpio-ok03] ok03_exit()\n");
	unregister_chrdev(DEV_OK03_MAJOR_NUMBER, DEV_OK03_NAME);

}

module_init(ok03_init);
module_exit(ok03_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

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


6. gpio-ok03 모듈 컴파일

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

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

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


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

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

8. gpio-ok03 모듈 삽입

* RaspberryPi에서 진행

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

# cd /root
# insmod gpio-ok03.ko

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

# lsmod


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

# dmesg

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



9. gpio-ok03 장치 파일 생성

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

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

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

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

# mknod /dev/ok03 c 223 0

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


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

# ls /dev/ok03 -al


10. gpio_ok03_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_ok03_app.c 역시 주번호와 장치파일 이름만 바뀌고 
 내부적인 기능은 ok01과 동일하므로 생략함 */
 
/* 사용할 장치 파일의 이름이 변경됨 */
#define _MORSE_PATH_ "/dev/ok03"
 
int main(int argc, char *argv[]){
    int fd = 0;
    /* 장치파일의 주번호가 변경됨 */
    //mknod(_MORSE_PATH_, S_IRWXU | S_IRWXG | S_IFCHR, MKDEV(223, 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_ok03_app.c 컴파일

* RaspberryPi에서 진행

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

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

# gcc gpio_ok03_app.c -o gpio_ok03_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_ok03_app 실행

* RaspberryPi에서 진행

# ./gpio_ok03_app


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

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

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

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


14. gpio-ok03 모듈 제거

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

# rm /dev/ok03

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

# rmmod gpio-ok03

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

# lsmod | grep gpio_ok03 

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

# dmesg 

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


14. CUR_GPIO를 GPIO 25번으로 변경 

가. gpio-ok03.c에서 해당 부분을 변경

/* 제어할 GPIO에 대한 매크로 */
#define CUR_GPIO                25


15. gpio-ok03 모듈 컴파일 ~ 장치 파일 생성 수행

가. 위를 참고하여 동일하게 수행


16. GPIO 25번에 LED 연결

가. 아래의 그림과 Raspberry Pi 별 GPIO Pin Numbering를 참고하여 GPIO 16번에 연결 했던 것을 GPIO 25번으로 변경

그림23.png



17. gpio_ok03_app 실행

가. 위와 같은 방식으로 수행하고 똑같이 동작하는지 확인



18. gpio-ok03 모듈 제거

가. 위와 같은 방식으로 수행



--

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 - 06 system timer를 사용하여 정밀하게 시간 제어

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


위로