이전에 아두이노에서 설정한 문자열을 라즈베리파이를 통해 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. 결과 확인


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