라즈베리파이 3 모델 B 스펙
- SoC : Broadcom BCM2837 SoC
- CPU : 1.2GHz ARM Cortex-A53 MP4
- GPU : Broadcom VideoCore IV MP2 400 MHz
- 메모리 : 1GB LPDDR2
- SD카드 : Micro SD, push-pull type
라즈베리파이 3 모델 B 핀 맵
GPIO 시작 주소
- 주변 장치의 물리 주소 범위 : 0x3F00 0000 ~ 0x3FFF FFFF
- 주변 장치의 가상 주소의 시작 주소 : 0x7E00 0000
- GPIO 가상 주소의 시작 주소가 0x7E20 0000 로, 시작 주소와 0x0020 0000 만큼 떨어져있음
- 따라서 GPIO 물리 주소의 시작 주소는 0x3F20 0000
GPIO 핀 Input / Output 설정 (GPFSELn)
- GPFSELn : GPIO 핀의 Input / Output 사용 여부 설정
- GPIO 18번 핀 사용
- 따라서 GPFSEL1 레지스터의 24~26번째 비트 값을 001로 바꾸어 18번 핀을 Output 모드로 설정
GPIO 핀 출력 설정 (GPFSETn / GPCLRn)
- GPFSETn : GPIO 핀을 High Level로 만들어줌
- GPFSET0 레지스터의 18번째 비트 값을 1로 바꾸어 Led를 켬
- GPCLRn : GPIO 핀을 Low Level로 만들어줌(Clear)
- GPCLR0 레지스터의 18번째 비트 값을 1로 바꾸어 Led를 끔
테스트 코드
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#define GPIO_BASE 0x3F200000
#define GPFSEL1 0x04
#define GPSET0 0x1C
#define GPCLR0 0x28
int main()
{
int i;
int fd = open("/dev/mem", O_RDWR | O_SYNC);
if(fd < 0)
{
printf("can't open /dev/mem\n");
exit(-1);
}
char *gpio_memory_map = (char *)mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO_BASE);
if(gpio_memory_map == MAP_FAILED)
{
printf("Error : mmap\n");
exit(-1);
}
volatile unsigned int* gpio = (volatile unsigned int*)gpio_memory_map;
//gpio[GPFSEL1/4] = 0b0000_0001_0000_0000_0000_0000_0000_0000;
gpio[GPFSEL1/4] |= (1 << 24);
for(i = 0; i < 5; i++)
{
//gpio[GPSET0/4] = 0b0000_0000_0000_0100_0000_0000_0000_0000;
gpio[GPSET0/4] |= (1 << 18);
sleep(1);
//gpio[GPCLR0/4] = 0b0000_0000_0000_0100_0000_0000_0000_0000;
gpio[GPCLR0/4] |= (1 << 18);
sleep(1);
munmap(gpio_memory_map, 4096);
return 0;
}
- mmap() : system call을 통해 driver를 거치지 않고 직접 device에 접근
- device memory에 user memory를 바로 연결할 수 있기 때문에 memory copy를 줄일 수 있음
- GPFSEL1, GPSET0, GPCLR0 을 4로 나누는 이유 : gpio의 변수 크기가 4byte이기 때문에 bit 연산을 하기 위함
Led 디바이스 드라이버 코드
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/io.h>
#define GPIO_BASE 0x3F200000
#define BLOCK_SIZE 4096
#define GPFSEL1 0x04
#define GPSET0 0x1C
#define GPCLR0 0x28
volatile unsigned int* gpio;
static int led_init(void)
{
printk("led module init\n");
gpio = ioremap(GPIO_BASE, BLOCK_SIZE);
gpio[GPFSEL1/4] |= (1 << 24);
for(i = 0; i < 5; i++)
{
//gpio[GPSET0/4] = 0b0000_0000_0000_0100_0000_0000_0000_0000;
gpio[GPSET0/4] |= (1 << 18);
mdelay(1000);
//gpio[GPCLR0/4] = 0b0000_0000_0000_0100_0000_0000_0000_0000;
gpio[GPCLR0/4] |= (1 << 18);
mdelay(1000);
}
}
static led_exit(void)
{
iounmap(gpio);
printk("led module exit\n");
}
module_init(led_init);
module_exit(led_exit);
- ioremap() : 물리 주소를 가상 주로소 매핑
- 디바이스 드라이버 모듈을 적재하여 사용자 모드에서 사용할 수 있는 형태로 만들기 위해 코드 개선 필요
개선된 Led 디바이스 드라이버 코드
[ chrdev.c ]
#define GPIO_BASE 0x3F200000
#define GPFSEL1 0x04
#define GPSET0 0x1C
#define GPCLR0 0x28
#define BLOCK_SIZE 4096
#define DEVICE_NAME "led_drv"
MODULE_LICENSE("GPL");
volatile unsigned int* gpio;
static int led_drv_open(struct inode* inode, struct file* file)
{
printk("led device drive open\n");
// Set GPIO PIN 18 to Output
gpio[GPFSEL1/4] |= (1 << 24);
return 0;
}
static ssize_t led_drv_write(struct file* file, const char __user* buf, size_t count, loff_t* ppos)
{
int val;
int i;
printk("write to led device driver\n");
get_user(val, (int *)buf);
printk("get number : %d\n", val);
for(i = 0; i < val; i++)
{
// Led On
gpio[GPSET0/4] |= (1 << 18);
mdelay(500);
// Led Off
gpio[GPCLR0/4] |= (1 << 18);
mdelay(500);
}
return 0;
}
static struct file_operations led_drv_fops = {
.owner = THIS_MODULE,
.open = led_drv_open,
.write = led_drv_write,
}
static int led_drv_init(void)
{
printk("led module init\n");
register_chrdev(345, DEVICE_NAME, &led_drv_fops);
gpio = ioremap(GPIO_BASE, BLOCK_SIZE);
return 0;
}
static void led_drv_exit(void)
{
unregister_chrdev(345, DEVICE_NAME);
iounmap(gpio);
printk("led module exit\n");
}
module_init(led_drv_init);
module_exit(led_drv_exit);
[ Makefile ]
obj-m := chrdev.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
[ app.c ]
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
int fd;
int val;
fd = open("/dev/LED", O_RDWR);
if(fd < 0)
{
printf("Unable to open file\n");
return 0;
}
if(argc != 2)
{
printf("Only 1 parameter can inputted\n");
printf("You input %d parameters\n", argc-1);
return 0;
}
val = atoi(argv[1]);
printf("input number : %d\n", val);
write(fd, &val, sizeof(int));
close(fd);
return 0;
}
디바이스 드라이버 소스 컴파일
make
디바이스 드라이버 모듈을 리눅스 커널에 삽입
insmod chrdev.ko
디바이스 파일 노드 생성
mknod /dev/LED c 345 0
테스트 코드 컴파일
gcc -o app app.c
테스트 실행
./test 5
발생했던 오류
/home/pi/src/led_module.c:5:10: fatal error:stdio.h: No such file or directory
#include <stdio.h>
^~~~~~
해결
sudo apt install --reinstall build-essential
'Study > Device Driver' 카테고리의 다른 글
[Device Driver] Character Device Driver Led Driver 예제 참고용 (0) | 2021.08.16 |
---|---|
[Device Driver] 리눅스 커널 (0) | 2021.08.13 |