본문 바로가기

Programming/bash/Shell

[bash] 라즈베리 파이의 GPIO를 이용해 스위치/LED 제어하기

 우리가 파이페이스 CAD를 통해 일부를 써왔지만, 그 사실조차 모르고 있었던 라즈베리 파이의 GPIO 핀을 이번에 다뤄볼 것이다 라즈베리 파이는 일반적인 개발 보드 등에 쓰이는 단일 마이크로 프로세서와 같이 입출력(Input/Output) 신호를 제어할 수 있는 포트가 있는데, 그게 바로 범용적인 목적으로 입/출력을 담당하는 GPIO(General Purpose Input / Output) 라는 것이다.


 라즈베리 파이에서 GPIO를 통해서 입출력 신호/포트 제어가 가능하다는 점은 꽤나 재밌는 부분이 아닐 수 없다. 라즈베리 파이는 리눅스 기반이기 때문에 어느정도 제약이 있긴 하나, 오히려 그렇기 때문에 리얼타임 OS를 올리는 게 전부였던 마이크로 프로세서들에 비해 리눅스 기반의 OS를 올릴 수 있는 소형 'PC'이면서, 단일 마이크로 프로세서에서만 할 수 있는 것처럼 보였던 임베디드 컴퓨팅이 가능하다는 점은 분명 매력으로 다가온다.



 초기 모델에서는 대략 26핀 정도를 지원하는데 그쳤지만, 버전이 업그레이드되면서 현재 라즈베리 파이 3까지 총 40개의 GPIO 핀을 사용할 수 있게 됐다. 아직 UART니 ID_SD니 하는 건 생소할 수 있다. 이런 기능들은 추후에 다뤄보도록 하고, GPIO 핀들을 이용해 LED와 스위치를 제어하는 기초적인 방법을 알아보도록 하자.


 준비물은 다음과 같다. 지난 번 프로젝트들을 통해 기본적인 라즈베리 파이 세팅 및 사용 준비가 완료돼있다고 가정한다:


    • 라즈베리 파이 3 (홈페이지: https://www.raspberrypi.org/)
    • 라즈베리 파이용 T자형 코블러
    • 빵판(Breadboard)
    • 빵판용 점퍼 와이어(전선)
    • LED 5개
    • 스위치 2개
    • 고정 저항기(레지스터; resistor)
      • LED용 330Ω 5개
      • 스위치용 1kΩ 2개 / 10kΩ 2개




1. wiringPi 라이브러리


 ① GPIO에 쉽게 접근할 수 있도록 만들어진 라이브러리들이 몇 있다. 그 중에서 C언어로 짜여진 wiringPi를 이용해볼 것이다. 왜 C언어로 짜여진 라이브러리를 이용하는지 궁금하다면 다음 표를 보도록 하자. 파이썬, 루비 등 다양한 프로그래밍 언어로 GPIO 라이브러리가 작성됐지만, 그 중에서도 C언어로 작성된 라이브러리들의 속도(Square wave의 frequency)는 다른 언어들과는 단위부터 다르다는 걸 알 수 있을 것이다.


 그 중에서도 C언어 자체적으로만 작성하는 방법이 가장 빠르지만, 우리와 같은 초심자의 입장에서 프로그래밍하기엔 다소 어려운 감이 없지 않아 있기 때문에, 쉽고 한 눈에 들어오는 프로그래밍을 위해서 wiringPi 라이브러리를 사용하는 것이다.


 설치는 간단하다. 다음 명령어들을 차근차근 입력해주도록 하자:


$ sudo apt-get update

$ sudo apt-get upgrade

$ git clone git://git.drogon.net/wiringPi

$ cd wiringPi

$ git pull origin

$ ./build


 설치가 완료됐다면, 다음 명령어를 입력해 제대로 설치되었는지 확인해본다.


$ gpio readall


 다음과 같은 화면이 뜨면 정상적으로 설치가 된 것이다. 각 핀의 현재 입/출력 방향의 상태와 핀에 입력된 전압(0 or 1) 등을 표시해주는 기능을 한다. wiringPi는 라즈베리 파이의 BCM2835 프로세서에 접근하는 라이브러리이기 때문에, 우리가 핀의 번호를 얘기하면 앞으로 고개를 들어 주황색 상자로 쳐진 BCM 란을 바라보면 되겠다.






 ② 간단한 사용법을 알아보도록 하자. LED는 출력이고, 스위치는 입력임은 당연히 알고 있을 것이다. 그러면 각각 입력인지 출력인지 방향(Direction)을 정해줘야 한다. 그 땐 다음 명령어를 사용한다:


$ gpio -g mode 핀번호 input(혹은 output)


예를 들어 23번 핀에 LED를 연결했다면, 출력으로 설정해야 하기 때문에 다음과 같이 입력하게 될 것이다.

ex) gpio -g mode 23 output


26번 핀에 스위치를 연결했다면, 입력으로 설정해야 한다.

ex) gpio -g mode 26 input


 이제 LED를 출력할 수 있도록 설정했으니, 불빛이 들어오도록 설정을 해야한다. 1값이 들어올 때 출력이 되는 것이기 때문에, 다음 명령어를 통해 1 또는 0 값을 줄 수 있다.


$ gpio -g write 핀번호 1(혹은 0)


23번 핀에 연결한 LED의 불빛이 들어오도록 하고 싶다면 다음과 같이 써주면 되겠다.

ex) gpio -g write 23 1


끄고 싶다면:

ex) gpio -g write 23 0


 입력인 스위치는 반대로 0값이 들어올 때 입력을 받았다고 프로세서는 생각한다. 그러므로 스위치가 떼진 상태일 때는 1값을, 눌렀을 때는 0값을 갖도록 설정해야 하는데, 그게 바로 풀업(Pull-up) 저항이다. 입력이 풀업으로 설정되어있을 때 프로세서는 스위치가 Off(Pull) 상태인지 On(Push) 상태인지를 제대로 감지하기 때문에 입력 핀의 역할을 완벽하게 수행한다 할 수 있다.


$ gpio -g mode 핀번호 up (down과 tri가 있지만 앞으로 거의 사용하지 않을 것 같다)


26번 핀에 스위치를 입력으로 설정했다면, 다음과 같이 풀업으로 설정할 수 있다.

ex) gpio -g mode 26 up


 이제 응용을 해보도록 하자. 3번, 9번, 23번 핀이 출력 모드로 각각 0, 1, 1이 되도록 설정하고, 10번 핀은 입력 모드로 Pull Up이 되도록 설정해본다. 간단한 다음 명령어들만으로 끝난다. wiringPi는 이렇게 쉽게 GPIO를 제어하게 만들어주는 라이브러리이다.


$ gpio -g mode 3 output

$ gpio -g mode 9 output

$ gpio -g mode 23 output

$ gpio -g mode 10 input


$ gpio -g write 3 0

$ gpio -g write 9 1

$ gpio -g write 23 1

$ gpio -g mode 10 up







2. LED와 스위치를 활용한 bash 프로그래밍


 ① 우선 LED와 스위치를 본격적으로 활용하기에 앞서 빵판으로 간이 조립(?)이 필요한데, 이건 이 글에서 설명하기엔 너무 방대하기 때문에, 양해를 부탁드리며 따로 정리를 잘 해둔 글을 링크하도록 하겠다http://binworld.kr/13


 필자는 다음과 같이 빵판을 설계하였다. 18, 23, 24, 25번 핀을 LED용 출력으로 설정, 21, 26번 핀을 스위치용 입력/풀업으로 설정하였다. 1번 스위치는 원래 LED용으로 써야할 330Ω 저항을 부득이하게 쓰고 있는데, 여러분들은 스위치에 알맞는 resistor를 쓰도록 합시다….



 라즈베리 파이와 빵판을 쉽게 연결할 수 있도록 T자형 코블러를 쓰고 있는데, GPIO 포트와 어댑터의 연결 또한 정확한다. GPIO 포트를 다루다 실수를 저지르면 최대 라즈베리 파이 자체를 몽땅 터뜨려먹는, 무시무시한 재앙으로 이어질 수 있음을 반드시 명심하자. 그러므로 빵판에 코블러를 어떻게 장착시켰느냐에 따라 다음과 같이 아스트랄한 모양새도 나올 수 있다(...)






 ② LED/스위치를 제어하는 프로그램을 만드는데 앞서, 우리가 지난 번에 간단히 맛만 보았던 쉘 스크립트의 문법을 간단히 짚고 넘어가야 할 것 같은데 이마저도 분량이 너무나 방대하기 때문에, 쉘 스크립트 역시 양해 부탁드리며 잘 정리된 글을 링크해본다. 읽어보길 권장한다: http://egaoneko.github.io/os/2015/05/24/linux-starter-guide-8.html


 앞서 쉘 스크립트의 정의를 다시금 되짚어보자면, 쉘에서 사용할 수 있는 명령어들의 조합을 모아서 만든 배치(batch) 파일, 쉽게 얘기해 프로그램을 만든 것을 말한다고 지난 번에 설명한 바 있다. 쉘 스크립트에 사용되는 언어 중에는 배쉬(bash)라는 것이 있고, 우리는 이 bash를 사용해 쉘 스크립트를 작성해볼 것이다.




 ③ <1번 프로그램 : 스위치를 눌렀을 때 LED가 순차적으로 켜지고 꺼지는 도미노 LED>를 작성해보자.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#! /bin/bash
gpio -g mode 18 output
gpio -g mode 23 output
gpio -g mode 24 output
gpio -g mode 25 output
 
gpio -g mode 21 input
gpio -g mode 21 up
 
gpio -g write 18 0
gpio -g write 23 0
gpio -g write 24 0 
gpio -g write 25 0
 
on=1
off=0
push=0
 
led_active='gpio -g write'
led_array=("18" "23" "24" "25")



GPIO 핀을 초기화하는 부분이다. 스크립트 파일의 첫 줄은 bash로 작성되었으며, bash을 기준으로 실행된다는 것을 의미한다.


LED용으로 사용한 18, 23, 24, 25번 핀을 출력 모드로 설정하고 꺼진 상태(0)로 초기화한다. 스위치용으로 사용한 21번 핀을 출력 모드로 설정하고, 풀업으로 설정한다.


LED용 핀 번호를 led_array 배열에 저장한다. 쉘 스크립트에서는 콤마(,) 없이 띄어쓰기로 값을 넣는다.


문자열 변수에 값이나 문자열을 할당한다는 건, 어셈블리어에서 ASSUME이나 C언어에서 DEFINE을 생각하면 된다. 앞으로 'gpio -g write 18 1' 대신 $led_active $led_array[0] $on을 사용한다는 것이다. 이렇게 본인이 보기 좋게 환경 변수를 설정해도 무방하다. 바로 위에서 든 예제를 보면 알다시피, 쉘 스크립트에서 변수를 참조하려면 앞에 $를 꼭 붙여야 한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
while [ True ]
do
    sw=`gpio -g read 21`
 
    if [ $sw -eq $push ];then
        echo "Domino LED"
 
        for ((i=0; i<4; i++))
        do
            $led_active ${led_array[$i]} $on
            sleep 0.5
            $led_active ${led_array[$i]} $off
        done
    fi
done



while문을 통해 이벤트를 기다리는 프로그래밍은 임베디드 시스템의 기본이라고 할 수 있겠다. 스위치가 입력될 때까지 계속해서 무한 루프를 돌려준다.


쉘 스크립트에서 헷갈리지 말아야 할 것은 인용부호인데, bash에서 인용부호는 총 세 가지가 사용된다.

  • 작은 따옴표(' ') 안에 있는 것은 가급적 그대로 출력된다. 예를 들어 '$on'를 썼다면 $on이 출력되는 것이다.
  • 반면 큰 따옴표(" ") 안에 넣으면 변수가 실제 값으로 치환된 후 출력된다. "$on"은 1로 출력되는 것이다.
  • 역 따옴표(` `)는 안에 있는 명령문을 실행한 결과를 대입한다. 즉 sw=`gpio -g read 21`는, 스위치의 현재 상태(On-Push/Off-Pull)를 읽고 이를 sw에 대입하고 있는 것이다. 스위치가 눌리면, sw는 0값이 되고 바로 if 문으로 들어가게 된다.

if문 안에서 도미노 LED의 핵심적인 기능을 수행한다. LED가 4개이므로 for문을 네 번 돌리도록 한다. LED가 켜지고 0.5초후에 꺼지는 기능을 1번부터 4번 LED까지 반복하고 다시 while문 안으로 돌아가 스위치가 눌러질 때까지 기다린다.


※1 쉘 스크립트에서 for문은 위 소스처럼 C언어같이 써도 된다. 저걸 기본 문법대로 쓴다면 for i in {1...4}가 된다.


※2 기능을 종료할 때 LED는 반드시 모두 꺼진 상태가 만들어져야 한다. 하나라도 켜져있다면 스위치를 눌러 이벤트가 발생해도 LED 출력 기능이 제대로 동작하지 않는다. 





  <2번 프로그램 : 스위치를 눌렀을 때 LED가 1부터 100번째까지 순차적으로 켜지는 프로그램>을 작성해보자. 단, 숫자 3, 6, 9가 들어가는 순서의 LED는 켜지지 않는 것이 조건이다. 이른바 LED 369이다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
while [ True ]
do
    sw=`gpio -g read 21`

    if [ $sw -eq $push ];then
        echo "LED369"
 
        for ((k=1; k<101; k++))
        do
            num=(k-1)%4
            flag=0
 
            if [[ $k =~ .*3.* ]];then
                echo "x"
                flag=1
            elif [[ $k =~ .*6.* ]];then
                echo "x"
                flag=1
            elif [[ $k =~ .*9.* ]];then
                echo "x"
                flag=1
            else
                echo "$k"
            fi
 
            if [[ $flag -eq 1 ]];then
                $led_active ${led_array[$num]} $off
            else
                $led_active ${led_array[$num]} $on
            fi
 
            sleep 0.5
            $led_active ${led_array[$num]} $off
        done
    fi
done



마찬가지로 스위치가 입력될 때까지 계속해서 무한 루프를 돌려준다. 스위치가 눌렸을 때, k=1부터 100까지 루프를 돌며 LED369를 수행하는 연산에 들어간다.


먼저 if문에 대괄호가 두 개가 쓰인 이유가 궁금할 것이다. 우선 결과는 대괄호가 하나만 쓰인 경우와 거의 동일하다. 하지만 대괄호가 하나이냐 두 개이냐에 따라 실행되는 방식은 다르다. 대괄호 하나인 경우는 별도의 프로세스를 실행한다. 즉, /usr/bin/[ 이 파일을 실행하여 조건식의 결과를 얻는 것이다. 반면에 대괄호 두 개는 bash 자체적으로 내장된 기능을 사용한다. 따라서 별도의 프로세스를 실행하지 않는다.


간단한 스크립트에서는 전혀 상관이 없으나, 수많은 반복 작업을 해야 하는 상황이라면 별도의 프로세스를 사용하지 않고 내장된 기능을 쓰는 것이 조금 더 시간을 단축시킬 수 있지 않을까. 물론 그 이유도 있지만, 여기서 대괄호가 두 개 쓰인 이유는 bash에서 내장된 기능을 수행하기 위함이 우선이다.


[[ $k =~ .*3.* ]]는 눈치 빠른 사람이라면 알겠지만 숫자 k에 (자리에 상관없이) 3이 들어있는지를 체크하는 기능이다. 쉘 스크립트에서 정규식(regular expression, 줄여서 regex)를 사용한 방식인데, 비교 연산자는 =~를 사용하며, 변수에 들은 문자열에 포함되어있는지 비교해보고자 하는 문자(열)를 .* .* 안에 넣어준다.


※ 쉘 스크립트는 명령어 문법에 일관성이 없는 경우가 많고 기능이 중복되어, 대체로 변수는 문자열로 받아 드리게 된다. 즉 k에는 숫자가 들어가지만 이를 문자열로 읽기 때문에 위와 같은 비교 연산이 가능하다는 것. 다만 학자들마다 말이 조금씩 달라서, 이는 좀 더 정확하게 조사해볼 필요가 있을 것 같다.


그리하여 현재 순서를 나타내는 k의 값에 3, 6, 9가 들어있는지를 확인하고, 해당될 경우 불이 켜지는 걸 배제하기 위해 flag를 1로 만들어 if문을 통해 LED가 켜지지 않도록 한다k의 값을 LED 배열의 인덱스로 활용하기 위해 모듈러 연산을 이용해 num에 할당한다. 복잡해보이지만 아주 간단한 소스이다.




 ⑤ 이 두 프로그램을 한데 합쳐 스위치 2개를 각각 21, 26번 핀에 입력/풀업으로 설정하고, 각각 1번 프로그램인 도미노 LED와 2번 프로그램인 LED 369를 실행하는 최종 프로그램을 작성하였다.



 이제 최종 프로그램인 LED12를 작동시켜보고 수행 결과를 지켜보자.




3. 수행 결과


 

 도미노 LED와 LED 369 프로그램을 순서대로 수행한 결과를 촬영한 영상이다. 제대로 동작하고 있음을 알 수 있다.