Study/Device Driver

[Device Driver] Led Device driver 제작

Alex An 2021. 8. 15. 16:21

라즈베리파이 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