리눅스 디바이스 드라이버 프로그래밍(3) - 7segment led 예제 fpga에 적용시켜보기
내가 학교 실습시간에 사용한 키트는 DE1-SoC로, microSD카드에 리눅스를 넣어 사용하였다.
이전 포스팅을 참고하면 이해하는데 조금 도움이 될 것이다.
2019/06/12 - [임베디드 시스템] - 리눅스 디바이스 드라이버 프로그래밍(2) - 모듈 프로그래밍
7 segment LED 출력 장치 예제 코드
6개의 7 segment LED 출력장치를 /dev/hex 문자 장치 파일을 통하여 사용할 수 있도록 디바이스 드라이버를 작성해보자. (major number = 240)
write 함수에서는 32비트(실제로는 하위 24비트가 출력됨)을 6개의 7세그먼트 LED에 16진수로 출력한다. (하위 16비트를 4개의 7 segment LED를 위한 32비트 데이터로 변환하고, 상위 8비트를 2개의 7 segment LED를 위한 데이터로 변환한다.)
read 함수에서는 현재 출력된 32비트의 값을 출력한다. 출력 값을 변수에도 저장하고 이 값을 반환한다.
이 예제에서는 open, release 함수에서는 아무 동작도 하지 않기 때문에 바로 return해주는 동작을 한다.
7 segment LED 디바이스 드라이버 내용
아래는 디바이스 드라이버를 생성해 준 코드이다.
//8-2.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <asm/io.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/ioport.h>
MODULE_LICENSE("GPS");
MODULE_AUTHOR("Jiyeon Lee");
MODULE_DESCRIPTION("Seven Segment LED");
#define base_lwFPGA 0xFF200000
#define len_lwFPGA 0x200000
#define addr_LED 0
#define addr_HEX0 0x20
#define addr_HEX1 0x30
#define addr_SW 0x40
#define addr_KEY 0x50
#define HEX_DEVMAJOR 240
#define HEX_DEVNAME "HEX"
static void *mem_base;
static void *hex0_addr; //hex3-hex0
static void *hex1_addr; //hex5-hex4
static unsigned int data = -1;
static unsigned int mode = 0;
#define NOFILL 4
#define BLINK 8
unsigned int hex0, hex1;
static int turnoff = 0;
int hex_conversion[16] = {
0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07,
0x7F, 0x67, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71
};
static int hex_open(struct inode *minode, struct file *mfile){return 0;}
static int hex_release(struct inode *minode, struct file *mfile){return 0;}
//write 연산
static ssize_t hex_write(struct file *mfile, const char __user *buf, size_t count, loff_t *f_pos){
unsigned int hex_data = 0;
unsigned int nofill = 0;
get_user(hex_data, (unsigned int *)buf);
hex_data = hex_data & 0xFFFFFF;
data = hex_data;
if(mode & NOFILL) nofill = 1;
hex1 = 0;
hex0 = hex_conversion[hex_data&0xF];
do{
hex_data >>= 4;
if(nofill && hex_data==0) break;
hex0 |= hex_conversion[hex_data&0xF]<<8;
hex_data >>=4;
if(nofill && hex_data==0) break;
hex0 |= hex_conversion[hex_data&0xF]<<16;
hex_data >>=4;
if(nofill && hex_data==0) break;
hex0 |= hex_conversion[hex_data&0xF]<<24;
hex_data >>=4;
if(nofill && hex_data==0) break;
hex1 |= hex_conversion[hex_data&0xF];
hex_data >>=4;
if(nofill && hex_data==0) break;
hex1 |= hex_conversion[hex_data&0xF]<<8;
}while(0);
iowrite32(hex0, hex0_addr);
iowrite32(hex1, hex1_addr);
return 4;
}
//read 연산
static ssize_t hex_read(struct file *mfile, const char __user *buf, size_t count, loff_t *f_pos){
put_user(data, (unsigned int*)buf);
return 4;
}
static long hex_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
unsigned int newcmd;
mode = cmd;
return 0;
}
static struct file_operations hex_fops = {
.read = hex_read,
.write = hex_write,
.open = hex_open,
.release = hex_release
.unlocked_ioctl = hex_ioctl,
};
static int __init hex_init(void){
int res;
res = register_chrdev(HEX_DEVMAJOR, HEX_DEVNAME, &hex_fops);
if(res<0){
printk(KERN_ERR " HEX : failed to register device. \n");
return res;
}
mem_base = ioremap_nocache(base_lwFPGA, len_lwFPGA);
if(!mem_base){
printk("Error mapping memory.\n");
release_mem_region(base_lwFPGA, len_lwFPGA);
return -EBUSY;
}
printk("Device : %s / Major : %d \n\n\n", HEX_DEVNAME, HEX_DEVMAJOR);
hex0_addr = mem_base + addr_HEX0;
hex1_addr = mem_base + addr_HEX1;
return 0;
}
static void __exit hex_exit(void){
unregister_chrdev(HEX_DEVMAJOR, HEX_DEVNAME);
printk(" %s unregistered. \n\n", HEX_DEVNAME);
iowrite32(0, hex0_addr);
iowrite32(0, hex1_addr);
iounmap(mem_base);
}
module_init(hex_init);
module_exit(hex_exit);
보면 저번 포스팅에서 한 디바이스 드라이버의 골격을 제대로 갖춘 것을 확인할 수 있다.
그럼 시리얼 통신으로 fpga를 테라텀으로 연결한 곳으로 가서 우리가 짠 디바이스 드라이버를 등록해주자. Makefile을 만든 후 바로 make 명령어를 실행한다. (이전 포스팅 참고)
(가독성 넘 떨어지지만..)
make
이 명령어를 통해 커널 오브젝트 파일(.ko)를 생성해준다.
insmod [파일명.ko]
insmod 명령어는 우리가 만든 디바이스 드라이버의 커널 오브젝트 파일을 loading 하는 명령어이다. 정상적 커널에 적재되었다면 lsmod 명령어로 확인할 수 있다.
lsmod
그 다음에는 mknod 명령어를 통해 디바이스 파일을 생성한다.
mknod /dev/HEX c 240 0
위 명령어의 뜻은 /dev/HEX라는 디바이스 파일을 생성하는데, 문자 디바이스(c) 형태이고 Major number는 240, Minor number는 0이라는 뜻이다.
chmod ug+w /dev/HEX
모드를 변경해주면 끝!
그럼 이제 만든 디바이스 드라이버를 테스트하는 프로그램을 작성해보자.
만든 디바이스 드라이버 테스트하는 프로그램 소스코드
//8-2-test.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#define NOFILL 4
#define BLINK 8
int main(void){
int dev, data, rdata;
dev = open("/dev/HEX", O_RDWR);
if(dev<0){
fprintf(stderr, "Cannot Open LED device.");
return 1;
}
//ioctl - default
ioctl(dev, 0, NULL);
//write
printf("Input HEX7 data (hex) : ");
scanf("%x", &data);
write(dev, &data, 4);
//read
read(dev, &rdata, 4);
printf("read data : %x \n", rdata);
//write - NOFILL
printf("Input HEX 7 data (hex) for NOFILL : ");
scanf("%x", &data);
ioctl(dev, NOFILL, NULL);
write(dev, &data, 4);
return 0;
}
dev를 보면 파일 형태로 디바이스 드라이버에 접근한다는 것을 알 수 있다. (문자 디바이스 드라이버의 특징) data 변수는 우리가 입력한 숫자 데이터를 의미하고, rdata는 디바이스 드라이버에서 읽는 데이터를 의미한다.
이렇게 프로그램을 작성해준 후 아래와 같은 명령어를 통해 실행파일을 만들어준다.
#cc -o 8-2-test 8-2-test.c
#./8-2-test
DE1-SoC 보드에서의 실행 결과는 아래와 같다.
입력 | DE1-SoC |
|
|
'Computer Engineering > 임베디드 시스템' 카테고리의 다른 글
리눅스 디바이스 드라이버 프로그래밍(4) - ioctl 함수, blocked 입력 (0) | 2019.06.13 |
---|---|
리눅스 디바이스 드라이버 프로그래밍(2) - 모듈 프로그래밍 (0) | 2019.06.12 |
리눅스 디바이스 드라이버 프로그래밍(1) - 디바이스 드라이버 개요, 디바이스 드라이버 종류 (0) | 2019.06.12 |
임베디드 리눅스 커널 프로그래밍(3) - 시스템 호출 함수 구현 (0) | 2019.06.12 |
임베디드 리눅스 커널 프로그래밍(2) - 커널 데이터타입, 커널 인터페이스 함수 (0) | 2019.06.11 |
댓글
이 글 공유하기
다른 글
-
리눅스 디바이스 드라이버 프로그래밍(4) - ioctl 함수, blocked 입력
리눅스 디바이스 드라이버 프로그래밍(4) - ioctl 함수, blocked 입력
2019.06.13 -
리눅스 디바이스 드라이버 프로그래밍(2) - 모듈 프로그래밍
리눅스 디바이스 드라이버 프로그래밍(2) - 모듈 프로그래밍
2019.06.12 -
리눅스 디바이스 드라이버 프로그래밍(1) - 디바이스 드라이버 개요, 디바이스 드라이버 종류
리눅스 디바이스 드라이버 프로그래밍(1) - 디바이스 드라이버 개요, 디바이스 드라이버 종류
2019.06.12 -
임베디드 리눅스 커널 프로그래밍(3) - 시스템 호출 함수 구현
임베디드 리눅스 커널 프로그래밍(3) - 시스템 호출 함수 구현
2019.06.12