자 이제 본격적으로 스마트홈 구축을 위한 프로그래밍을 진행해보자. 앞에서 언급했던 것 처럼 파이썬은 지그비 통신을 활용하여 온습도 데이터를 수신받아 데이터베이스화 할 것이며 PHP로 구축된 웹페이지에서 이 DB를 활용하여 차트로 도식화 한다. 이러한 프로그램 각각의 역할과 코드의 기능에 대해 정확히 알고 있어야 본 예제를 입맛에 맞게 수정하거나 응용할 수 있으므로 모쪼록 잘 이해하고 넘어가주길 바란다.



1. MySQL


MySQL을 다루려면 기본 SQL(Structured Query Language) 명령어를 어느정도 숙지하고 있어야 한다. 여기서 SQL은 데이터베이스의 자료를 검색하고 추가및 삭제 등의 조작을 할 수 있도록 고안된 컴퓨터 언어이다. 사람이 사용하는 언어와 문법 자체가 비슷하기 때문에 이해하기 쉽고 사후 관리도 편리하다. 또한 Query 라는 이름에서도 알 수 있듯이 검색 부문에 특화되어있는 데이터베이스이다.


구조 자체가 어렵진 않으니 여기서는 간단히 사용되는 명령어만 서술할 것이며 자세한 내용은 인터넷 상에 자료가 많으므로 검색해 볼 것을 추천한다.


  # MySQL 접속

mysql -u user -p password


위에서 user 부분은 사용자명을 뜻하며 따로 설정을 하지 않았다면 기본값인 root를 적어주며 password 부분은 MySQL을 설치할 때 생성한 비밀번호를 적어준다.


# 데이터베이스 생성

  CREATE DATABASE db_name;


# 데이터베이스 목록 표시

SHOW DATABASES;


# 데이터베이스 사용

USE db_name;


# 테이블 생성

  CREATE TABLE table_name(ddate datetime, zone text, temp float, humi float);


# 테이블 목록 표시

SHOW TABLES;


# 테이블 구조 표시

DESCRIBE table_name;


위 명령어 들을 사용해보면 데이터베이스를 만들거나 관리하는 것이 그렇게 어려운 부분이 아니라는 것을 알게 될 것이다. 자세한 설명은 생략하며 아래처럼 테이블까지 만들어서 파이썬을 통해 데이터베이스에 현재 시간과 장소, 온습도 데이터를 넣을 준비를 하자. 필자는 db의 이름을 testdb, 테이블의 이름을 tempdata로 지정했으며 다른 이름을 사용해도 무방하다.




2. Python


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
import serial
import MySQLdb
import datetime
 
# Xbee 모듈과 시리얼 통신
port = serial.Serial("/dev/ttyAMA0""9600")
 
# MySQL 접속
db = MySQLdb.connect("localhost""User_name""User_password""testdb")
curs = db.cursor()
 
ZONE = "HOUSE"
print 'Start Monitor'
 
while True:
        try:
            # 지그비 통신을 통한 데이터 수신
            data = port.readline()
 
            print("House temp/humi is ")
            print(data)  
 
            # 수신한 데이터와 현재 시간 및 장소를 SQL 쿼리문으로 전송
            curs.execute("""INSERT INTO tempdata (ddate, zone, temp, humi) VALUES (%s,%s,%s,%s)""",
                (datetime.datetime.now().strftime('%Y-%m-%d %H:%M'), ZONE, data.split(',')[0], data.split(',')[1].strip()))
            db.commit()
 
        except KeyboardInterrupt:
                break
 
port.close()
db.close()

cs


파이썬 코드의 경우 먼저 지그비 통신을 하기 위해 Xbee 모듈과 시리얼로 연결된다. 그리고 MySQL에 접속하여 수신 받은 데이터를 SQL 쿼리문 형태로 전송하여 앞서 생성한 DB에 데이터를 입력하게된다. 제대로 동작한다면 다음과 같이 1분마다 DB에 데이터가 입력되는 것을 볼 수 있다.




3. Highcharts


다음은 저장된 DB를 가지고 그래프로 출력하는 법을 알아보겠다. 사실 DB와 연동하여 PHP 만으로도 꽤나 괜찮은 그래프를 만들어볼 수 있긴 하지만 여기서는 그보다 간단하면서 깔끔하게 나타낼 수 있는 오픈소스를 사용해보자. 차트를 만들어 볼 수 있는 오픈소스로는 Google chart나 Nwagon, chart.js 등의 다양한 종류가 있으며 여기서는 개인 홈페이지에서는 무료로 사용할 수 있으며 모바일 부분도 잘 지원해주는 highchart 소스를 활용할 것이다. 


주소는 다음과 같으며 해당 소스를 다운로드한 후 라즈베리파이의 웹서버 경로인 /var/www/html 에 압축을 풀어주면 된다.



http://www.highcharts.com/



4. PHP + HTML


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
37
38
39
40
41
42
43
44
45
46
<?php
 
// PHP 에러 코드 확인
ini_set('display_errors''On');
error_reporting(E_ALL|E_STRICT);
 
// MySQL 접속
$mysql_host = 'localhost';
$mysql_user = 'User_name';
$mysql_password = 'User_password';
$mysql_db = 'testdb';
$conn = @mysql_connect($mysql_host$mysql_user$mysql_password);
$dbconn = mysql_select_db($mysql_db$conn);
 
// DB에서 원하는 데이터 검색
$sql="
select * from (
SELECT DATE_FORMAT( ddate,  '%m-%d %HH' )  mdh , COUNT( * ) cnt, SUM( temp ) , 
round(SUM( temp ) / COUNT( * ),1)  atemper, round(SUM( humi ) / COUNT( * ),1)  ahumier
FROM  `tempdata`
GROUP BY DATE_FORMAT( ddate,  '%Y%m%d%H' )
order by ddate desc
limit 12
) t_a
order by t_a.mdh
";
 
$result = mysql_query($sql) ;
 
$str_mdh="";
$str_atemper="";
$str_ahumier="";
 
// 온습도 문자열 연결
while ($row = mysql_fetch_array($result, MYSQL_ASSOC)) {
 $str_mdh .="'".$row['mdh']."',";
 $str_atemper .="".$row['atemper'].",";
 $str_ahumier .="".$row['ahumier'].",";
}
 
// 오른쪽 공백 제거
$str_mdhsubstr($str_mdh,0,-1);
$str_atempersubstr($str_atemper,0,-1);
$str_ahumiersubstr($str_ahumier,0,-1);
 
?>
cs


highchart 웹페이지에서 예제 소스를 보면 아래 HTML 코드의 xAxis-categories 부분에 시간이나 날짜가, series-data 부분에 y축을 나타내는 데이터가 각각 쉼표와 함께 들어간다는 사실을 알 수 있을 것이다. 따라서 차트를 따라 그리려면 데이터가 알맞게 들어갈 수 있도록  PHP 코드상에서 한 문자열로 만들어주어야 한다. 


위 코드는 이러한 방식을 구현한 코드로서 select 문을 통해 db에서 데이터를 순서대로 가져온 후 while 문을 통해 각각의 날짜와 시간을 변수 str_mdh에 저장한다. 또한 온도와 습도 데이터는 변수 str_atemper와 str_ahumier 에 쉼표와 함께 한 문자열로 연결하고 최종적으로 뒤의 공백을 제거함으로서 완성한다.


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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<!DOCTYPE HTML>
<html>
<head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <title>House Monitor</title>
        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
        <style type="text/css">${demo.css}</style>
        <script type="text/javascript">
 
$(function () {
    $('#temp').highcharts({
        chart: {
            type: 'line'
        },
        title: {
            text: 'Temperature'
        },
        subtitle: {
            text: 'http://creamp.tistory.com'
        },
        xAxis: {
            categories: [<?php echo $str_mdh?>]
        },
        yAxis: {
            title: {
                text: 'Temperature (°C)'
            }
        },
        plotOptions: {
            line: {
                dataLabels: {
                    enabled: true
                },
                enableMouseTracking: false
            }
        },
        series: [{
            name'HOUSE',
            data: [<?php echo $str_atemper?>]
        }]
      });
    $('#humi').highcharts({
        chart: {
            type: 'line'
   },
        title: {
            text: 'Humidity'
        },
        xAxis: {
            categories: [<?php echo $str_mdh?>]
        },
        yAxis: {
            title: {
                text: 'humidity (%)'
            }
        },
        plotOptions: {
            line: {
                dataLabels: {
                    enabled: true
                },
                enableMouseTracking: false
            }
        },
        series: [{
            name'HOUSE',
            data: [<?php echo $str_ahumier?>]
        }]
    });
});
        </script>
</head>
<body>
        <script src="https://code.highcharts.com/highcharts.js"></script>
        <script src="https://code.highcharts.com/modules/exporting.js"></script>
        <div id="temp" style="width: 900px; height: 440px; margin: 30px auto"></div>
        <div id="humi" style="width: 900px; height: 440px; margin: 30px auto"></div>
</body>
</html>
cs




5. 결과 화면


이제 모든 과정이 완료되었다. 라즈베리파이의 웹서버에 접속하여 위와 같은 화면이 제대로 나오는지 확인해보자.

 


본 포스팅에서는 라즈베리파이와 아두이노를 지그비 통신을 활용하여 무선으로 연결하고 이를 통해 온습도 센서 데이터를 전송받아 파이의 웹서버에 차트로 뿌려주는 프로젝트에 대한 내용을 서술하려고 한다. 또한 여기에 추가적으로 웹서버 접속을 통해 집밖에서도 전등을 제어할 수 있도록 할 것이다.(여기서는 실제 전등 대신 LED로 대체) 말은 거창하지만 실상 그다지 어려운 부분은 없으며 최대한 상세하게 설명하고자 하니 파이를 활용한 스마트홈 구축이나 기타 IOT와 관련된 프로젝트를 진행하고자 하는 사람들에게 도움이 되길 바란다.

 

우선 지그비 통신에 대한 기본 개념과 설정 방법의 경우에는 아두이노 카테고리에 자세히 포스팅 해놓았기 때문에 꼭 참고하기 바라며 지그비 모듈에 대한 모든 설정이 마쳐진 상태에서 시작하도록 하자. 필요 준비물은 라즈베리파이와 아두이노, 설정이 완료된 지그비 모듈 2개, 마지막으로 온습도센서(여기서는 DHT11 사용)가 필요하다.

 

 

1. 동작흐름도


시작하기에 앞서 어떤 방식으로 어떻게 구현할 것인지 소개하기 위해 간단하게 흐름도를 만들어보았다. 먼저 센서부의 경우에는 아두이노와 온습도 센서로 구성되며 센서 데이터를 측정하고 라즈베리파이에 지그비 모듈을 통해 전송하게 될 것이다. 그리고 라즈베리파이에서는 파이썬 프로그램을 통해 전송받은 센서 데이터를 MySql DB로 저장하게 되며 마지막으로 php 기반의 자체 웹서버와 저장된 DB를 연결하여 아두이노로 부터 받은 센서 데이터를 차트로 도식화하여 보여주게 된다.

 

 

2. 아두이노 설정 



아두이노 회로 구성에 경우 위와 같이 온습도 센서(DHT11)와 지그비 모듈을 결선하도록 하며 코드는 다음과 같다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <DHT11.h>
#include <SoftwareSerial.h>
SoftwareSerial xbee(23); 
int pin=4;
DHT11 dht11(pin); 
 
void setup()
{
  xbee.begin(9600); 
}
 
void loop()
{
  int err;
  float temp, humi;
  if((err=dht11.read(humi, temp))==0)
  {
    xbee.print(temp);
    xbee.print(",");
    xbee.print(humi);
    xbee.println();
    delay(60000);
  }
}
cs


 

3. 라즈베리파이 설정


필자도 이 부분에서 많이 애를 먹었다. 아두이노에서는 간단하게 코드 몇 줄이면 지그비 모듈을 사용할 수 있는 반면에 라즈베리파이의 경우 UART 통신을 하려면 따로 설정이 필요하기 때문이다. 또한 파이1에서는 2로 넘어오면서 설정 방법이 바뀌는 바람에 시간을 많이 날려버렸다. 여기서는 파이2에 대한 방법을 서술하도록 하겠다.

 

먼저 초기 설정값을 default로 만들기 위해 ttyAMA0에 대한 모든 설정을 삭제하도록 한다. 여기서 ttyAMA0는 우리가 지그비 통신을 위해 사용하게될 시리얼 포트 이름이다.


  $ sudo nano /boot/cmdline.txt


모두 삭제가 완료되면 다음과 같은 코드만 남아있어야 한다. 물론 기존의 것이 아래와 같다면 수정할 필요는 없다.


  dwc_otg.lpm_enable=0 rpitestmode=1 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait


또한 라즈베리파이 아크 리눅스 환경에서는 ttyAMA0 포트가 이미 설정된 상태로 시스템 콘솔에서 동작하고 있다. 따라서 포트를 사용하기 위해서는 기존의 동작하고 있는 상태를 disable 시켜주어야 하므로 터미널에 다음과 같은 명령어를 쳐서 포트를 정지시키도록 한다.


  $ sudo systemctl mask serial-getty@ttyAMA0.service


마지막으로 라즈베리파이의 회로 구성은 다음과 같이 진행하여 설정을 모두 마치도록 하자.




4.  웹서버 환경 구축


라즈베리파이를 외부에서도 접속하고 관리할 수 있도록 웹서버를 구축해보도록 하자. 여기서는 웹서버의 표준 구성이라고 할 수있는 APM(Apache + PHP + MySQL)을 설치하려고한다. APM은 라즈베리파이를 강력한 웹서버로 활용할 수 있게 해줄 뿐만아니라 웹사이트를 보다 쉽게 운영하고 관리할 수 있도록 도와줄 것이다. 따라서 파트별로 간단하게 어떤 역할을 하는지와 설치 방법을 알아보도록 하며 언어 문법이나 보다 자세한 내용은 따로 검색해 볼 것을 추천한다.


1) Apache

아파치는 우리가 구축할 웹서버을 동작시키는데 있어서 가장 중요한 역할을 하는 웹 서버 어플리케이션이다. 리눅스 기반으로 만들어져 있으며 확장성이 좋아 여러 모듈을 통해 수많은 기능을 덧붙일 수 있다.(PHP, MySQL 등) 또한 매우 단순하면서 강력한 무료 웹서버를 무료로 제공하기 때문에 서버 애플리케이션 중에서 가장 인지도가 높다.


$ sudo apt-get update

$ sudo apt-get install apache2


위 명령어를 통해 설치가 완료되면 자동으로 설정되는 기본위치인 /var/www경로를 사용할 수 있도록 권한 설정을 하자.


$ sudo chown pi -R /var/www

$ sudo chmod 0777 /var/www


마지막으로 라즈베리파이가 부팅시에 프로그램이 가동될 수 있도록 설정해준다.


  $ sudo update-rc.d apache2 defaults


2) MySQL

데이터베이스(DB)란 데이터를 쉽게 저장하고 검색할 수 있는 시스템을 말하며 이 데이터를 삽입하고 검색할 수 있는 언어가 바로 SQL이다. 따라서 MySQL은 데이터를 보다 쉽게 관리하게 해주며 데이터 저장에 특화되어 있는 오픈소스 애플리케이션이다.


  $ sudo apt-get install mysql-server mysql-client libmysqlclient-dev


설치가 완료되면 MySQL 접속에 필요한 비밀번호를 2번 입력하도록 한다. 또한 외부에서 데이터베이스에 접속할 수 있게 바인드 어드레스를 비활성화 한다. 여기서 바인드 어드레스란 해당 주소에서만 접속을 허용하며 그 외는 접속을 차단해주는 주소를 의미한다.


  $ sudo nano /etc/mysql/my.cnf



위처럼 bind-adreess 앞에 #을 붙여주면 주석처리되어 비활성된다.


3) PHP

PHP는 웹페이지에 동적 기능을 불어넣어 주는 서버단에서 동작하는 프로그래밍 언어이다. 사실 HTML 만으로는 사용자의 입력에 유연하게 반응하지 못하기 때문에 PHP는 이러한 정적 HTML에 동적으로 동작할 수 있도록 개발되었다. 때문에 HTML의 처리에 강점을 가지며 다양한 데이터베이스를 지원하기 때문에 데이터베이스와 사용자간의 중간 다리 역할도 훌륭히 수행한다.


  $ sudo apt-get install php5


설치 후 PHP를 효과적으로 관리할 수 있도록 도와주는 PHP MyAdmin도 설치하도록 하자. 이때 웹서버 인증은 apache2를 선택하고 웹에서 사용할 비밀번호를 3번 정도 입력하면 설치가 완료된다.


  $ sudo apt-get install phpmyadmin



4) Python 

마지막으로 파이썬의 경우 지그비 통신을 통해 온습도 데이터를 수신받는 역할을 할 것이며 이 데이터를 MySQL의 데이터베이스로 만들어 줄 것이다. 따라서 파이썬에서 MySQL로 접속할 수 있도록 연동해주어야하며 아래와 같은 라이브러리 설치가 필요하다.


  $ sudo apt-get install libmysqlclient-dev

$ sudo apt-get install python-dev

$ sudo pip install MySQL-python


설치가 모두 마무리되면 제대로 동작하는지 확인을 위해 파이썬 쉘을 실행시키고 import MySQLdb 를 입력해보자. 에러없이 그대로 임포트 된다면 성공적으로 설치가 완료 되었으므로 파이썬에서 DB를 생성할 수 있다. 반면에 에러 발생시에는 실수한 것은 없는지 순서대로 확인해보도록 한다.


이제 필요한 모든 설정을 마쳤다. 다음 포스팅에서는 위 프로그래밍 언어들을 사용하여 온습도 데이터를 기반으로 하는 스마트홈 구축과 관련된 내용을 서술하도록 하겠다.


앞서 wiringPi 라이브러리를 활용하여 라즈베리파이에서 실행할 수 있는 프로그램을 간단히 코딩해 보았는데 그렇다면 이렇게 개발한 프로그램을 부팅할 때 마다 자동으로 실행시켜 주려면 어떻게 해야할까? 이 문제는 의외로 코드 몇줄 추가하는 것 만으로 간단히 해결할 수 있다. 리눅스 환경에서는 프로그램을 자동으로 실행시켜 줄 수 있는 다양한 방법을 지원하므로 각기 알맞는 방법을 알아보고 사용해보자.



1. rc.local


자동실행 방법 중에 간단하면서도 효과적인 방법 중 하나로서 여기에 명령을 적어주게 되면 라즈베리파이가 부팅시에 자동으로 root 권한을 가지고 프로그램을 실행시켜준다. 주의해야할 점으로는 시스템과 관련된 명령을 입력할 경우에 rc.local의 실행이 더 빠르기 때문에 실행되지 않을 수 있으며 프로그램에 에러가 있는 상태에서 명령어 뒤에 & 문자를 입력하지 않을 경우에는 부팅이 완료되지 않을 수 있으므로 유의해야 한다. 


  $ sudo nano /etc/rc.local


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will “exit 0” on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.
# Print the IP address
_IP=$(hostname -I) || true
if [ “$_IP” ]; then
printf “My IP address is %sn” “$_IP”
fi
/home/pi/Desktop/Work/usbSerial & 
exit 0
cs


파일을 열면 다음과 같은 내용이 보일 것이다. 여기서 추가하고 싶은 명령어를 'fi' 와 'exit 0' 사이에 입력하면 된다. 앞서 언급했던것 처럼 rc.local은 root권한을 가지기때문에 sudo 나 su를 입력할 필요가 없다.(여기서 sudo가 쓰이는 경우는 다른 사용자로 프로그램을 실행시킬 경우에만 사용됨) 따라서 실행할 프로그램의 경로만 적어준 뒤에 &기호를 추가하면 자동실행 등록이 완료된다. &는 등록한 프로그램과 시스템 부팅이 별개의 프로세스로 진행되도록 분리해주는 역할을 한다.



2. LXDE


LXDE란 리눅스 환경에서 사용할 수 있는 오픈소스 데스크탑 환경을 의미하며 쉽게 말해 라즈비안에서 startx 명령어를 통해 나타나는 GUI환경을 의미한다. 이 방법은 라즈베리파이가 부팅되면서 xwindow 실행시 자동으로 실행하도록 설정해주는 방식이며 앞서 언급한 rc.local 자동실행보다 한 박자 느리게 실행된다. 설정 방법은 매우 간단하며 다음과 같은 명령어를 통해 파일을 읽어온 뒤 @문자를 붙인상태에서 실행할 프로그램의 경로를 지정해주면 된다. 경로를 설정할 때는 밑의 @xscreensaver 라인 전에 적어야 한다.


  $ sudo nano /etc/xdg/lxsession/LXDE-pi/autostart


1
2
3
@lxpanel --profile LXDE-pi
@pcmanfm --desktop --profile LXDE-pi
@xscreensaver -no-splash
cs




3. 스크립트 파일


보통 특정 프로그램을 가동할때 많이 사용하는 방식이며 부팅시에 스크립트가 자동으로 실행되는 폴더인 profile.d 경로에 원하는 파일을 만들어서 사용할 수 있다. 활용 방법은 2가지가 있으며 직접 /etc/profile.d 경로에 들어가 스크립트 파일을 넣어 자동으로 실행하게 하거나 profile 파일을 편집하여 스크립트 파일을 직접 실행시키게 할 수 있다. 


  $ sudo nano /etc/profile


해당 파일의 맨 마지막 라인에 .을 찍고 한칸 띄어쓰기 한 후 실행할 스크립트 파일의 경로를 적어주면 된다.

ex) . /home/pi/Desktop/Work/usbSerial.sh


이전에 아두이노에서 설정한 문자열을 라즈베리파이를 통해 Serial로 연결하여 수신받는 예제를 확인해 보았다. 이번에는 반대로 라즈베리파이에서 텍스트파일을 읽어와 아두이노에 뿌려줄 수 있는 통신을 구축해보도록 하자. 필자의 경우에는 라즈베리파이에 있는 파일을 아두이노 SD카드에 전송하고자하는 특수한 목적 때문에 본 예제를 활용했을뿐 실상 잘 쓰이는 방법이 아니기 때문에 그냥 이렇게도 가능하구나 정도만 이해하고 넘어가도 될 듯 하다.


예제의 경우 이전과 같은 예제를 활용하였으며 파일을 읽어 아두이노에 전송하기 위해 약간의 수정을 가했다. 따라서 수정된 부분에 대한 설명을 위해 C언어 파일 입출력에 대한 간단한 개념을 기술하였고 이를 통해 아두이노와 직접 통신하는 부분까지 진행해보자.



1. 파일 문자열 송신 예제


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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#ifdef RaspberryPi 
#include <stdio.h> //for printf
#include <stdint.h> //uint8_t definitions
#include <stdlib.h> //for exit(int);
#include <string.h> //for errno
#include <errno.h> //error output
#include <wiringPi.h>
#include <wiringSerial.h>
 
char device[]= "/dev/ttyACM0";
int fd;
unsigned long baud = 9600;
unsigned long time=0;
 
char strHello[] = "HELLO";
char cTemp[512= {0};
int ch;
char Endc;
 
//prototypes
int main(void);
void loop(void);
void setup(void);
 
void setup(){
 
  printf("%s \n""Raspberry Startup!");
  fflush(stdout);
 
  //get filedescriptor
  if ((fd = serialOpen (device, baud)) < 0){
    fprintf (stderr, "Unable to open serial device: %s\n", strerror (errno)) ;
    exit(1); //error
  }
 
  //setup GPIO in wiringPi mode
  if (wiringPiSetup () == -1){
    fprintf (stdout, "Unable to start wiringPi: %s\n", strerror (errno)) ;
    exit(1); //error
  }
}
 
// main function for normal c++ programs on Raspberry
int main(){
  setup();
  FILE * fp=fopen("/home/pi/Desktop/Work/transfer.txt","rt");
  if(fp==NULL){
    printf("file open fail\n");
    return -1;
}else{  
   printf("file open success\n"); 
   while(fgets(cTemp, sizeof(cTemp),fp) != NULL){
      serialPuts (fd, cTemp);
      printf(cTemp);
}
  
if(feof(fp) != 0){
    printf("file upload success\n");
}
 else
    printf("file upload fail\n");
 fclose(fp);
}
 
  return 0;
}
 
#endif //#ifdef RaspberryPi
 
 
cs

예제를 보면 아래 int main() 부분만 다르다는 것을 알 수 있다. 동작 순서를 간단히 언급하자면 설정된 경로의 텍스트 파일을 읽어와 각 줄단위로 읽고 이를 아두이노로 전송하며 만약 파일의 끝부분에 도달했을 경우 통신을 마치게 되는 구조이다. 그럼 각 함수에 대해 간단하게 설명하도록 하겠다.


2. 코드 설명


  FILE * fp=fopen("transfer.txt","rt")


C언어 프로그램상에서 파일에 저장되어 있는 데이터를 읽기 위해서는 데이터가 이동할 수 있는 다리 역할을 할 수 있는 스트림을 형성해야 한다. 쉽게 말해 파일로부터 데이터를 읽을 수 있는 최소한의 준비를 해주는 과정이라고 생각하면 된다.  따라서 위 fopen 함수는 스트림 형성을 요청하는 호출문 역할을 하며 괄호 안에 첫번째 인자의 경우 읽어들일 파일의 경로를 뜻하고 두번째 인자는 형성할 스트림의 종류를 뜻하며 입력스트림과 출력스트림 이렇게 2가지 형태가 존재한다. 위 코드는 "rt" 로 설정하였으므로 입력스트림을 뜻하며 파일의 데이터를 읽을 수 있는 반면에 쓰지는 못하기 때문에 만약 데이터를 쓰기 원한다면 별도로 출력스트림을 다시 형성해야 한다. C언어에서는 이러한 개념을 바탕으로 총 6가지 스트림으로 세분화 할 수 있다.


 

내용

   오직 읽기만 가능

w

   오직 쓰기만 가능

   만약 파일이 존재하지 않으면 새로운 파일을 생성해서 데이터 쓰기

   만약 파일이 존재하면 기존의 데이터를 지우고 데이터 쓰기 

a

   w 모드와 달리 파일이 존재하면 파일 끝에 덧붙여 데이터 추가

r+ 

   읽기와 쓰기 가능

   만약 파일이 존재하면 기존의 데이터를 지우지 않고 데이터 덮어 쓰기

w+

   읽기와 쓰기 가능

   만약 파일이 존재하면 기존의 데이터를 지우고 데이터 쓰기

a+

   읽기와 쓰기 가능 (쓰기의 경우 a 모드와 특징이 같다.)


 "rt" 에서 뒤의 t는 텍스트모드를 뜻하며 t의 텍스트모드와 b의 바이너리모드가 있다고만 이해하고 넘어가자. 


  char *fgets(char *string, sizeof(*string), File *stream)


위 함수는 텍스트 파일에 저장된 문자를 줄 단위로 읽어들어와 반환하는 함수로서 첫번째 인자인 *string은 입력 받은 문자열을 저장할 포인터(읽은 데이터를 잠시 저장하는 공간이라고만 생각하자)를 뜻하고 두번째 인자인 sizeof()는 입력 받을 문자의 수를 설정하는 것이며 세번째 인자인 파일 포인터는 형성한 스트림의 이름을 뜻한다.  이 함수는 읽어드린 문자열에 대한 포인터를 반환하며 파일의 끝에 도달하거나 오류가 발생할 경우 NULL을 반환한다.


  void serialPuts (int fd, char *s) 


위 함수는 wiringPi의 입출력 함수중 하나로 쉽게 설명하자면 fgets로 저장한 문자열을 fd(여기서 fd는 시리얼 통신할 디바이스라고 생각해두자)로 전송하게 된다. 사실 위 함수만 제대로 활용해도 아두이노로 문자열을 송신할 수 있지만 여기서는 파일입출력까지 같이 응용하여 예제를 만들어 보았다. wiringPi가 지원하는 함수에 대해 보다 자세히 알고싶다면 공식 홈페이지를 참고하도록 하자.


http://wiringpi.com/reference/serial-library 


마지막으로 fopen을 통해 파일을 개방하였다면 fclose 함수를 통해 파일을 꼭 닫아주어야 한다. 그 이유는 스트림을 형성하기 위해서는 시스템에서 메모리를 할당해야 하는데 파일을 닫아주지 않으면 메모리가 할당된 채로 유지되면서 손실이 일어나기 때문이다. 쉽게 말해 메모리가 유출되는 것을 막기 위한 것으로 이해하면 된다.



3. 아두이노 코드


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
char cTemp;
String sReal = "";
 
void setup()
{
  Serial.begin(9600);
}
void loop()
{
    while(Serial.available()){
      cTemp = Serial.read();
      sReal.concat(cTemp);
      if(cTemp=='\n')
      {
        Serial.print(sReal);
        sReal="";
      }
    }
}
 
cs


아두이노 소스코드는 간단하게 구현해보았는데 여기서 유의해야할 것이 Serial.read() 함수이다. 라즈베리파이의 경우 문자를 한 줄로 읽거나 쓰는 것이 가능한데 아두이노는 한 글자씩 밖에 읽기가 불가능하다. 때문에 이러한 문제점을 해결하기위해 필자의 경우에는 while문을 활용하여 라즈베리파이를 통해 송신된 문자열을 각 문자 단위로 읽고 이를 concat 함수 (문자와 문자를 이어주는 역할을 한다.)를 사용하여 한 문장으로 만든 뒤에 해당 문자열의 끝을 나타내는 개행문자가 입력으로 들어올 시 문자열을 출력하고 문자열 변수를 초기화 하는 형태로 코드를 구현해보았다. 나름대로 구현이 잘 되긴 했지만 완성도면에서 미흡하다고 생각하기 때문에 후에 더 나은 방향으로 수정이 필요해 보인다.



4. 결과 확인


컴파일 방법은 기존과 동일하며 읽어올 파일을 형성하고 그 경로를 지정하여 예제 코드에 반영해주자.



아두이노와 라즈베리파이 기초 강좌 교재이다.


시소드림이라는 회사에서 코딩키트라는 제품을 출시하면서 무료로 배포한 강의 교재인데 기초개념과 함께 전자회로적 지식까지 같이 서술하고 있어서 라즈베리파이를 처음 다루는 초보자에게 적합하다. 예제소스의 경우에는 코딩키트 제품 없이도 아두이노나 라즈베리파이와 기타 센서만으로 실습해볼 수 있으며 한번 정도 읽어보는 것을 추천한다. 


필자의 경우 이 회사에서 주최하는 라즈베리파이 강의를 실제로 들어본 경험이 있으며 도움이 되었던 걸로 기억한다. 물론 무료 강의가 아니어서 가격이 조금 비싼감이 있었지만...


기초 개념잡는데 도움이되길 바란다.



Codingbook_Part1_ARD_1_V10_20150828.pdf


Codingbook_Part2_RASPI_1_V10_20150828.pdf


라즈베리파이와 아두이노 사이에는 SPI, I2C, UART 등 다양한 통신 방법들을 적용할 수 있다. 하지만 아두이노의 입출력 핀 전압이 5V인 반면에 라즈베리파이의 GPIO 입출력 핀 전압은 3.3V이기 때문에 직접적으로 결선할 경우 문제가 발생하게 된다. 따라서 여기서는 가장 간편하게 사용할 수 있는 Serial(UART) 통신을 활용해보자. 


시리얼통신은 라즈베리파이와 아두이노를 USB케이블로 연결하는 것으로 간단하게 구현할 수 있으며 전압에 따른 문제 또한 발생하지 않는다.



1. 아두이노 Idle 설치하기


  $ sudo apt-get install arduino


패키지 설치를 위해 라즈베리파이에 update 명령어를 통해 업데이트 시켜주고 위 명령어를 입력하여 아두이노 패키지를 설치한다. 이때 설치된 패키지는 권한을 아직 가지고 있지 않은 상태이므로 바로 실행시키지 말고 아래 명령어를 입력하여 아두이노 패키지가 시리얼 포트에 엑세스 할 수 있는 권한을 얻도록 한다.


  $ sudo usermod -a -G tty pi

  $ sudo usermod -a -G dialout pi


위 과정을 완료하였다면 아두이노 패키지가 성공적으로 라즈베리파이에 설치되었다. 이제 아두이노를 실행시키고 라즈베리파이에 아두이노를 연결한 후에 포트를 설정하고 ("/dev/ttyACM0" 일 것이다.) blink 예제를 업로드하여 제대로 동작하는지 확인해본다.




2. 시리얼 통신하기


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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/*
 Pi_Serial_test.cpp - SerialProtocol library - demo
 Copyright (c) 2014 NicoHood.  All right reserved.
 Program to test serial communication
 
 Compile with:
 sudo gcc -o Pi_Serial_Test.o Pi_Serial_Test.cpp -lwiringPi -DRaspberryPi -pedantic -Wall
 sudo ./Pi_Serial_Test.o
 */
 
// just that the Arduino IDE doesnt compile these files.
#ifdef RaspberryPi 
 
//include system librarys
#include <stdio.h> //for printf
#include <stdint.h> //uint8_t definitions
#include <stdlib.h> //for exit(int);
#include <string.h> //for errno
#include <errno.h> //error output
 
//wiring Pi
#include <wiringPi.h>
#include <wiringSerial.h>
 
// Find Serial device on Raspberry with ~ls /dev/tty*
// ARDUINO_UNO "/dev/ttyACM0"
// FTDI_PROGRAMMER "/dev/ttyUSB0"
// HARDWARE_UART "/dev/ttyAMA0"
char device[]= "/dev/ttyACM0";
// filedescriptor
int fd;
unsigned long baud = 9600;
unsigned long time=0;
 
//prototypes
int main(void);
void loop(void);
void setup(void);
 
void setup(){
 
  printf("%s \n""Raspberry Startup!");
  fflush(stdout);
 
  //get filedescriptor
  if ((fd = serialOpen (device, baud)) < 0){
    fprintf (stderr, "Unable to open serial device: %s\n", strerror (errno)) ;
    exit(1); //error
  }
 
  //setup GPIO in wiringPi mode
  if (wiringPiSetup () == -1){
    fprintf (stdout, "Unable to start wiringPi: %s\n", strerror (errno)) ;
    exit(1); //error
  }
 
}
 
void loop(){
  // Pong every 3 seconds
  if(millis()-time>=3000){
    serialPuts (fd, "Pong!\n");
    // you can also write data from 0-255
    // 65 is in ASCII 'A'
    serialPutchar (fd, 65);
    time=millis();
  }
 
  // read signal
  if(serialDataAvail (fd)){
    char newChar = serialGetchar (fd);
    printf("%c", newChar);
    fflush(stdout);
  }
 
}
 
// main function for normal c++ programs on Raspberry
int main(){
  setup();
  while(1) loop();
  return 0;
}
 
#endif //#ifdef RaspberryPi
cs

위 코드는 wiringPi 라이브러리를 활용하여 아두이노와 통신할 수 있는 예제 코드이다. 여기서 "/dev/ttyACM0" 부분은 아두이노가 연결된 장치의 포트명을 말하는 것으로 가끔씩 에러가 발생할 경우 ACM1, ACM2 이렇게 순차적으로 증가할 수 있으니 유의하도록 한다. 라즈베리파이의 USB포트 구성 목록을 확인하려면 다음 과 같은 명령어를 입력하면 된다.

  $ dmesg|tail



그럼 이제 위 소스코드를 컴파일한다. 필자는 소스코드의 파일명을 test.c로 하였으며 hello라는 이름으로 컴파일했다. 컴파일할 때는 파일이 저장된 디렉토리로 들어간 후에 하도록 한다.


  $ sudo gcc test.c -o hello -l wiringPi -DRaspberryPi

  $ sudo ./hello


아래는 간단하게 구현한 아두이노 소스코드이며 1초마다 라즈베리파이로 Hello World 문자를 송신할 것이다.


1
2
3
4
5
6
7
8
void setup(){  
  Serial.begin(9600);
 
void loop(){
  Serial.println("Hello World");
  delay(1000);
}
cs


3. 결과 확인


위와 같은 결과가 나오면 성공이다. 여기서 실행된 프로그램을 종료하려면 Ctrl + C를 눌러주면 된다. 만약 종료를 하지 않을경우 터미널을 종료해도 계속해서 프로그램이 동작하며 아두이노를 다시 연결시 ttyACM0 포트는 사용하지 못하기 때문에 테스트 종료시에 유의하도록 한다.


라즈베리파이의 대표적인 운영체제인 라즈비안은 리눅스를 기반으로 하며 사용자가 손쉽게 프로그래밍 할 수 있도록 파이썬 언어를 지원한다. 하지만 여기에서는 기존 임베디드 환경에서 익숙한 언어인 C를 통해 개발할 수 있도록 wiringPi라는 라이브러리를 활용하여 라즈베리파이를 제어해보도록 하자. wiringPi 라이브러리는 I2C, SPI, UART 등의 통신을 제어할 수 있는 함수를 제공하며 GPIO포트에 대한 설정이나 프로그래밍을 가능하게 해준다.



1. 패키지 설치 준비


  $ sudo apt-get update

  $ sudo apt-get upgrade


새로운 패키지를 설치하기 전에 꼭 해야하는 부분이 바로 업데이트이다. 위 명령은 패키지 관리 서버로부터 이용 가능한 프로그램 패키지의 목록을 최신버전으로 업데이트 해주며 갱신된 패키지 목록을 통해 새로운 버전으로 설치해준다.



2. wiringPi 설치하기


  $ sudo apt-get install git-core

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


wiringPi 설치를 위해 소스 관리 툴인 git을 다운로드하고 git을 이용하여 wiringPi 라이브러리를 다운로드 한다. 위 주소는 실제 git주소를 통해 다운로드하기 때문에 대소문자에 주의해서 입력하도록 한다.

또한 위 명령어는 해당 폴터를 통째로 복사해오기 때문에 홈디렉토리에 wiringPi 폴터가 생성된다. 따라서 해당 디렉토리 이동 후에 사용자가 직접 설치하도록 하자.


  $ cd wiringPi

  $ ./build



3. wiringPi 설치 확인


  $ gpio -v

  $ gpio readall


제대로 설치가 되었는지 확인하기 위해 위 명령어를 입력하여 확인해본다.