[커널] GPIO 연결

정재훈·2022년 5월 10일
0

Kernel

목록 보기
5/8

라즈베리파이 데이터 시트 분석

bcm2711 칩 라즈베리파이 데이터시트

일반적인 Memory Address는 32bit를 사용한다.
0x0000 0000 ~ 0xFFFF FFFF 까지 각 1Byte 공간을 저장하여 총 4GByte 저장 공간 Address를 가짐

하지만, 라즈베리파이의 ARM에 LPAE 기능을 사용하여 35bit까지 표현하고 최대 메모리는 32GB까지 저장 가능

C언어에서 35bit까지 표기 불가능, Low Peripheral 메모리 주소를 사용하면 된다.

0x7C00 0000을 건드리고 싶다면, 0xFC00 0000을 건드리면 된다.

GPIO 연결


데이터시트를 살펴보면, Base 주소 : 0x7e20 0000이다. 따라서, 실제 라즈베리파이에서 건드려야 하는 주소는 0xFe20 0000이다.

참고) 임베디드에서 e는 에코라 읽는다. 2와 커뮤니케이션 혼동을 피하기 위해

offset : 얼마나 떨어져 있는지 표기

LED를 18번핀에 연결하여 출력한다고 하면

GPFSEL1 Register의 26:24에 001을 넣어야하고, 0xFE20 0004 주소 공간을 건드려야한다.

SET / CLEAR

18번핀 SET : 0xFE20 001C의 18번 bit에다가 1 세팅 => LED ON
18번핀 Clear : 0xFE20 0028의 18번 bit에다가 1 세팅 => LED OFF

커널 모듈 붙이고 뗄때 LED 키고 끄는 코드

static : 해당 파일 내에서만 사용가능한 전역변수
nobrand.c 에 추가

#include <asm/io.h>

static volatile uint32_t *BASE;
static volatile uint32_t *GPFSEL1;
static volatile uint32_t *GPSET0; 
static volatile uint32_t *GPCLR0; 

static int nobrand_init(void) {
    BASE = (uint32_t *)ioremap(0xFE200000, 256); // 해당 주소의 물리적 공간을 사용하기 위해 

	// offset을 해주는데, int의 자료형 크기가 4Byte이기 때문에 4를 나누어줘야 주소 공간 offset이 정상 작동된다.
    GPFSEL1 = BASE + (0x04 / 4); // `0xFE20 0004`
    GPSET0 = BASE + (0x1C / 4); // `0xFE20 001C`
    GPCLR0 = BASE + (0x28 / 4); // `0xFE20 0028`주소

	// GPFSEL1 Register의 26:24에 001을 넣는다.
    *GPFSEL1 &= ~(0x7 << 24);
    *GPFSEL1 |= (1 << 24);

    *GPSET0 = (1 << 18); // LED ON : `0xFE20 001C`의 18번 bit에다가 1 세팅
   
    if (register_chrdev(NOD_MAJOR, NOD_NAME, &fops) < 0) {
        printk( KERN_INFO "ERROR!!! register error\n");
    }
 
    printk(KERN_INFO "hi\n");
    return 0;
}

static void nobrand_exit(void) {
    *GPCLR0 = (1 << 18); // LED OFF : `0xFE20 0028`의 18번 bit에다가 1 세팅
    iounmap(BASE);

    unregister_chrdev(NOD_MAJOR, NOD_NAME);
    printk( KERN_INFO "BYE BYE\n\n");
}

버튼으로 LED 제어하기

버튼을 제어하기 위해서는 데이터 시트에서 다음 부분을 참고해야합니다.

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

int main()
{
    printf("OPEN FILE!!\n");
    int fd = open("/dev/nobrand", O_RDWR);
    if (fd == -1) {
        printf("FILE OPEN ERROR!!!\n");
        return 0;
    }

    //read
    char buf[100];
    while(1) {
        read(fd, buf, 2);  // 커널에서 buf값 읽어오기
        if (buf[0] == 'P')  // 만약 buf[0]이 'P'이면 LED ON
        {
            ioctl(fd, 3, 0);
        }
        else  // 만약 buf[0]이 'P'가 아니면 LED OFF
        {
            ioctl(fd, 4, 0);
        }

        usleep(100*1000);
    }

    printf("CLOSE FILE!!\n");
    close(fd);

    return 0;
}

nobrand.c

#include <linux/module.h>
#include <linux/fs.h>
#include <asm/io.h>

#define NOD_NAME "nobrand"
#define NOD_MAJOR 100

MODULE_LICENSE("GPL");

static volatile uint32_t *BASE;
static volatile uint32_t *GPFSEL1;
static volatile uint32_t *GPSET0;
static volatile uint32_t *GPCLR0;

static volatile uint32_t *GPLEV0;
static volatile uint32_t *GPIO_PUP_PDN_CNTRL_REG0;

static void led(int isTurnOn);
static int isButtonPush(void);

static int nobrand_open(struct inode *inode, struct file *filp) {
    printk( KERN_INFO "OPEN!!!\n");
    return 0;
}

static int nobrand_release(struct inode *inode, struct file *filp) {
    printk( KERN_INFO "CLOSE!!!\n");
    return 0;
}

static ssize_t nobrand_read(struct file *filp, char *buf, size_t count, loff_t *ppos) {

    if (isButtonPush()) buf[0] = 'R';
    else buf[0] = 'P';
    buf[1] = '\0';

    return count;
}

static long nobrand_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{

    if (cmd == 3) {
        led(1);
    }

    if (cmd == 4) {
        led(0);
    }

    printk( KERN_ALERT "%d\n", cmd);
    return 0;
}

static struct file_operations fops = {
    .open = nobrand_open,
    .release = nobrand_release,
    .read = nobrand_read,
    .unlocked_ioctl = nobrand_ioctl
};

static int nobrand_init(void) {

    printk( KERN_INFO "OK HELLO NOBRAND!!\n");
    if (register_chrdev(NOD_MAJOR, NOD_NAME, &fops) < 0) {
        printk( KERN_INFO "ERROR!!! register error\n");
    }

    BASE = (uint32_t *)ioremap(0xFE200000, 256);

    GPFSEL1 = BASE + (0x04 / 4);
    GPSET0 = BASE + (0x1C / 4);
    GPCLR0 = BASE + (0x28 / 4);
    GPLEV0 = BASE + (0x34 / 4);
    GPIO_PUP_PDN_CNTRL_REG0 = BASE + (0xE4 / 4);

    //24~26bit 001 output
    *GPFSEL1 &= ~(0x7 << 21); //000
    *GPFSEL1 |= (1 << 21); //001

    //using embedded pull up : 데이터 시트에서 초기값이 01이라 다음 코드 안해줘도 되긴 한다.
    *GPIO_PUP_PDN_CNTRL_REG0 &= ~(0x3 << 4);
    *GPIO_PUP_PDN_CNTRL_REG0 |= (0x1 << 4); 

    return 0;
}

static void nobrand_exit(void) {
    unregister_chrdev(NOD_MAJOR, NOD_NAME);

    led(OFF);
    iounmap(BASE);

    printk( KERN_INFO "BYE BYE\n\n");
}

static int isButtonPush(void) {
    int ret = 0;

    ret = (*GPLEV0 >> 2) & 0x1;  // 버튼 2번핀에 연결되어 있다.
    printk(KERN_INFO "BTN : %d\n", ret);
    return ret;
}

static void led(int isTurnOn) {

    if (isTurnOn)  // LED ON
    {
        *GPSET0 = (1 << 18);
    }
    else  // LED OFF
    {
        *GPCLR0 = (1 << 18);
    }
}


module_init(nobrand_init);
module_exit(nobrand_exit);
profile
여러 방향으로 접근하는 개발자

0개의 댓글