[임베디드] 라즈베리파이로 회로를 연결하여 Rotary와 LED제어

목차

  1. 간단설명
  2. 영상 및 설명
  3. 코드설명

1. 간단설명

라즈베리파이를 통해 구성한 회로의 Rotary로 LED들을 제어하는 기말과제 프로젝트입니다.

회로의 Rotary를 시계방향으로 회전시키면 8개의 LED가 켜지는데 이 LED들이 켜지면 1을 의미하고 꺼진LED는 0을 의미하는 비트표현의 회로입니다.


2. 영상 및 설명

구현영상

회로는 반납한 상태라 따로 올리지 못해 미리 찍어둔 영상을 올렸습니다.

(제가 봐도 손은 못생겼네요..ㅠㅠ 운동으로 극복 불가능..)

 

라즈베리파이를 리눅스를 사용하는 가상머신에 설치하여 회로의 제어 커널코드와 테스트용 C코드를 작성하여 라즈베리파이에 전송하고, 라즈베리파이에 접속하여 전송시킨 코드를 실행시켜 회로와 코드를 작동시키는 과제였습니다.

 

회로사진은 학교의 참고자료라서 따로 올리지 못하는점 양해바랍니다.

 


3. 코드설명

코딩을 할때에는 Visual Studio Code로 가상머신에 접속해서 코딩했습니다. 그래야 편하더라구요.

 

커널 코드는 교수님께서 직접 짜신 코드를 기반으로 구현한거라 제가 직접 구현한 부분만 보여드리며 설명드리겠습니다.

 

Init함수 부분이며 이곳에 Rotary의 기본설정을 합니다. 

gpsel1 = (uint32_t*)ioremap(GPIO_BASE_ADDR,4);; //여기서 1~9 GPIO 선택하도록 기본주소를 넣어준다.

    *gpsel1 = (*gpsel1 & ~(0x7 << 15)) | (1 << 15); //5번 설정을 위해 3X5=15이므로 15를 넣어준다.
    *gpsel1 = (*gpsel1 & ~(0x7 << 18)) | (1 << 18); //6번 설정을 위해 3X6=16이므로 18을 넣어준다.

    gplev0 = (uint32_t*)ioremap(GPIO_BASE_ADDR |0x34, 4);
    gpeds0 = (uint32_t*)ioremap(GPIO_BASE_ADDR |0x40, 4);
    gpfen0 = (uint32_t*)ioremap(GPIO_BASE_ADDR |0x58, 4);

    /* FSEL5 and FSEL6 set to INPUT */
    *gpsel1 = (*gpsel1) & !(0x7 << (5 * 3));  // GPIO 5번을 입력 모드로 설정
    *gpsel1 = (*gpsel1) & !(0x7 << (6 * 3));  // GPIO 6번을 입력 모드로 설정

    /* FEN5 and FEN6 set */
    *gpfen0 |= (0x1 << 5);  // GPIO 5번에 대한 하강 엣지 감지 활성화
    *gpfen0 |= (0x1 << 6);  // GPIO 6번에 대한 하강 엣지 감지 활성화

    /* EDS5 and EDS6 clear */
    *gpeds0 |= (0x1 << 5);  // GPIO 5번의 이벤트 감지 상태 초기화
    *gpeds0 |= (0x1 << 6);  // GPIO 6번의 이벤트 감지 상태 초기화

    rotary_count=0; //insmod시 0으로 초기화

    /*GPIO IRQ register handler*/
    gpio_virq = register_handler_legacy(GPIO_IRQ_SPINUM, rotary_irq_handler,"rotary_handler",dev);
    if(gpio_virq < 0){
        pr_err("module cannot be initialized");
        return -EINVAL;
    }

GPIO의 5번과 6번을 쓰게위해 sel에 기본주소에 15와 18로 하여 설정합니다.

gplev는 GPIO에 연결된 상태를 위한 변수

gpfen은 라이징엣지를 위한 변수

gpeds는 이벤트감지 초기화를 위한 변수

로 구성하였습니다.

 

Rotary 이벤트 핸들러 함수

static irqreturn_t rotary_irq_handler(int irq, void *dev_id){
    // GPIO 5번과 6번의 현재 상태를 읽습니다.
    uint32_t dt_state = (*gplev0) & (0x1 << 5);
    uint32_t clk_state = (*gplev0) & (0x1 << 6);
/*
clear gpio event status to prevent this handler
from begin called again. bit is cleared by writing a '1'
to the relevant bit
*/


// 클럭(Clock)이 라이징 엣지이고, DT의 상태를 확인
    if (clk_state && !dt_state) {
        // 클럭이 라이징 엣지이고 DT가 0일 때 (시계 방향 회전)
        if(rotary_count <= rotary_max_value) {
            rotary_count++;
        }
        /*else{
            rotary_count=0;
        }*/
        pr_info("Rotary turned clockwise, count: %d\n", rotary_count);
    } else if (clk_state && dt_state) {
        // 클럭이 라이징 엣지이고 DT가 1일 때 (반시계 방향 회전)
        if(rotary_count > rotary_min_value) {
            rotary_count--;
        }
        pr_info("Rotary turned counterclockwise, count: %d\n", rotary_count);
    }

// eds5 와 EDS6 초기화
*gpeds0 |= (0x1 << 5);  // GPIO 5번의 이벤트 감지 상태 초기화
*gpeds0 |= (0x1 << 6);  // GPIO 6번의 이벤트 감지 상태 초기화


/*wake up blocked process on read*/
//wake_up_all(&button_wq);
up(&my_semaphore);

return IRQ_HANDLED;

}

Rotary를 회전시키면 이벤트가 발생한다라고 하며 이벤트 핸들러 함수를 실행합니다.

GPIO 5번과 6번의 상태를 읽습니다. Rotary를 회전시킬때 DT핀에서 펄스가 발생하지 않는다로 하여 시계방향임을 판단하고 DT에서 펄스가 발생하면 반시계방향으로 하여 구현했습니다.

 

원래는 시계방향 회전시 CLK핀에서 High펄스가 발생하고 반시계방향이면 펄스가 발생하지않는다 라고 설명을 들었는데 어찌저찌 하다보니 저는 위와 같이 구현하게 됐습니다..

 

Rotary를 회전시키면 CLK핀과 DT핀에서 둘 중 하나를 기준으로 다른 하나가 0이냐 1이냐를 통해 시계방향인지 반시계방향인지 판단하는 것으로 알고있습니다.

 

아무튼 이렇게 rotary를 시계방향으로 회전시키면 전역변수에 Count를 증가시키고 반시계방향이면 감소시키도록 구현하였습니다.

 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h> 
#include <sys/ioctl.h> 


#define LEDS_MAGIC xxxxx
#define LEDS_SET_ALL        _IO(LEDS_MAGIC, 0)
#define LEDS_SET_CLEAR      _IO(LEDS_MAGIC, 1)

int main() {
    int num; 
    int fd_rotary, fd_8led; 
    uint8_t bval; 
    size_t ret;
    //Rotary를 실행시킨다. 읽기모드
   fd_rotary = open("/dev/Rotary", O_RDWR);
   if (fd_rotary < 0) {
      printf("dev open error: /dev/Rotary\n");
      return -1;
   }
    //8leds의 모듈을 킨다. 쓰기모드
   fd_8led = open("/dev/8leds", O_WRONLY);
   if (fd_8led < 0) {
      printf("dev open error: /dev/8leds\n");
      return -1;
   }

   printf("Rotary+8leds test\n");

   num = 0; //처음 실행시키면 num을 0으로 하여 led초기화
   while (1) {
        printf("Going Rotary\n");

        //Rotary의 rotary_count를 num으로가져온다
        if (read(fd_rotary, &num, sizeof(num)) < 0) {
            perror("Read rotary failed");
            break;
        }

        printf("Rotary count: %d\n", num);

        if (num <= 0) {
            printf("Rotary rolling, requesting clear\n"); //0밑으로 가면 초기화시키기
            //bval = 0;
            //write(fd_8led, &bval, sizeof(bval));
            //bval=(uint8_t)255;
            //write(fd_rotary, &bval, sizeof(bval)); //전부 켰으니 255로 입력값 보내기
            ioctl(fd_8led,LEDS_SET_ALL,NULL);
        }
        
        else if(num > 255){
            printf("num이 255를 넘음\n");
            bval=(uint8_t)1;
            write(fd_rotary, &bval, sizeof(bval)); //전부 꺼져서 0으로 입력값 보내기 1은 아무값
            //if(ret < 0){
            //printf("dev write error\n");
            //return -1;
            //}
            ioctl(fd_8led,LEDS_SET_CLEAR, NULL); //실질적으로 꺼지는곳은 8led이기에 8led의 ioctl작동
        }
        
        else if (num > 0 && num <= 255){ //1부터 255까진 기존의 8led와 똑같이 작동
            printf("Rotary rolling, writing num %d\n", num);
            bval = (uint8_t) num;
            ret = write(fd_8led, &bval, sizeof(bval));
            //if(ret < 0){
            //printf("dev write error\n");
            //return -1;
            //}
        

        }
        sleep(0.1); // 0.1초 대기
   }

   close(fd_rotary);
   close(fd_8led);
    return 0;
}

저의 Rotarytest.c 코드입니다.

 

아까 커널코드에 Count변수가 있었는데 그 Count를 C코드로 Write시켜 이 C코드에 전송합니다.

커널의 Count를 여기서는 num변수로 사용합니다.

 

num값이 0보다 작으면 최대값인 255로 초기화 시키도록 하였는데 지금은 주석처리 되어있네요

 

num값이 0보다 크고 255보다 작으면 8led의 led를 제어하는 코드로 전송시켜 led를 제어합니다.

8led의 커널코드는 num변수의 값을 비트값으로 led를 킵니다. 

 

num값이 255보다 크면 로터리의 count를 다시 0으로 초기화시켜 커널코드로 전송합니다.

 


정리

1. Rotary를 회전시키면 커널에서 제어한다.

2. 제어하는 과정에서 시계방향인지 반시계방향인지 판단하여 Count값을 증감한다. 최소값0 최대값255

3. Count값을 Test코드인 C코드로 전송하여 값에 따라 LED를 제어하는 8leds 커널 코드로 재전송한다.

4. 8leds 코드는 어느 한 값(전송받은값)의 수를 비트로 표현하여 LED를 키는 코드다.

5. Num값이 0보다 작으면 255로 초기화되고 255보다 크면 0으로 초기화되는 구조이다.

이렇게 무한반복입니다.

 

많이 부족하고 어설프지만 꽤 어려웠습니다.. 읽어주셔서 감사합니다.