아두이노프로세싱 프로그래밍

아두이노 RF(Radio requency)모듈에 의한 센서 측정값 전송 및 프로세싱 그래픽

coding art 2016. 12. 30. 13:22
728x90

아두이노 보드 기반으로 하는 드론의 비행제어 보드를 시제작하여 사용할 경우 드론에 부착된 센서에서 측정한 데이터를 노트북에 의해 모니터링할 필요가 있다. 이러한 필요성 대비하여 주파수 433MHz rf 통신 모듈을 이용하여 센서에 의하여 측정한 아날로그 데이터 값을 전송하는 프로그래밍을 알아보기로 한다.
드론을 사용하여 비행 중에 BMP 180센서에 의한 대기 압력이라든지 또는 GPS 의 좌표 값 측정 데이터라든지 아니면 초음파 센서를 사용한 장애물과의 거리 데이터를 숫자화하여 전송해야 할 필요가 있다. 아두이노의 아날로그 데이터 핀들 즉 A0,A1,A2,A3,A4,A5의 측정 범위가 0에서 1023 까지의 정수로 규정된다는 점에 착안하여 프로그래밍하기로 한다.
물론 여기에서 소개되는 프로그램을 아두이노 비행제어 보드에 어떻게 접목할 것인가 하는 문제는 비행제어 보드 코드를 작성하는 자의 몫으로 남을 것이다. 이는 아두이노 보드의 메모리 용량이라든지 데이터 전송을 위한 별도의 데이터 핀 설정 확보와 같은 점들이 문제가 될 것이며 아울러 드론 시험 비행 횟수가 많이 요구될 수 있다. 특히 이러한 점이 어렵게 느껴진다면 별도의 무선 측정 시스템으로 부착하는 것도 무방하리라 본다.

참고로 본 프로그램은 인터넷 사이트 “Home of the generic herd”에서 Markus Ulfberg씨의 프로그램을 참조하였음을 밝히며 실제 센서 데이터가 아닌 아날로그 정수 데이터를 생성하기 위해 sin() 함수를 사용하여 일부분 수정 하였다. 생성된 sin() 함수의 값은 차 후 프로세싱 프로그램에서 실시간 그래픽을 위해 0에서 256 범위로 제한하였다.
(
http://genericnerd.blogspot.kr/2012/07/arduino-sending-integers-over-rf-with.html)


프로그램 테스트를 위한 두 개의 아두이노 보드 준비는 앞서와 동일하므로 추가 설명을 생략하기로 한다.

Virtual_rf_transmit_03
#include <VirtualWire.h>

int Sensor1Data;
char Sensor1CharMsg[4];
float x = -0.1; // x 값은 0.1씩 증가시켜 처음 시작 값이 0.0dl 되도록 한다.

void setup() {

  // for debugging
 Serial.begin(9600);
 
 // VirtualWire setup
 vw_setup(2000);     // Bits per sec
}

void loop() {
 
  // Read and store Sensor 1 data
  x=x+0.1;
  Sensor1Data = 128*sin(x)+128; //analogRead()의 역할을 대신
 
  // 정수 데이터 Sensor1Data를 문자 어레이 즉 Char array로 변환
  itoa(Sensor1Data,Sensor1CharMsg,10);
 
  // DEBUG
  Serial.print("Sensor1 Integer: ");
  Serial.print(Sensor1Data);
  Serial.print(" Sensor1 CharMsg: ");
  Serial.print(Sensor1CharMsg);
  Serial.println(" ");
//  delay(10);

// END DEBUG

 vw_send((uint8_t *)Sensor1CharMsg, strlen(Sensor1CharMsg));
 vw_wait_tx(); // Wait until the whole message is gone
 
// delay(20);
 
} // END void loop...


프로그램이 수행되며 시리얼 모니터에서 x=0일때 128이 출력됨을 확인할 수 있다.

일차적으로 수신측에서는 송신모듈로부터 전송된 데이터를 DEBUG차원에서 한번 시리얼 모니터에서 출력해 볼 필요가 있으나 여기서는 프로세싱 프로그램과 핸드셰이킹을 통해 실시간 그래프 작성을 목표로 establishContact()라는 routine을 사용함과 동시에 Serial.print() 명령대신 바이트 데이터를 프로세싱에 넘길 수 있도록 Serial.write()를 사용함에 유의해야 한다.
실제로 시리얼 모니터를 보면 정수 데이터가 출력되는 대신 프로세싱에서 데이터를 요구하는 징표인 문자 A가 연속으로 출력됨을 알 수 있다.
아울러 프로세싱에서 적어도 두 종류의 실시간 데이터들의 그래픽 출력을 위해 송신 모듈에서 넘겨 받은 sin() 함수 데이터 외에 인위적으로 cos() 함수를 사용하여 두 번 째 데이터를 생성한다.
프로세싱에서 구현되는 실시간 그래픽을 관찰해보면 sin() 함수와 cos() 함수 사이에 일정한 간격을 유지해야 하나 그것이 지켜지지 않는데 그 이유는 송신 모듈의 연산 속도와 수신 모듈에서 cos() 데이터 생성 속도가 일정치 않아 생기는 문제이다. 이 문제를 해결하려면 아예 송신 모듈에서 두 가지 데이터를 께 생성하여 전송해야 할 것이다.
핸드셰이킹에 필수적인 프로그램 명령어로서 void setup() 내부에 establishContact()을 호출하고 별도로 void loop() 이 후에 void establishContact()을호출이 가능하도록 위치시킨다. 아울러 establishContact()는 void loop() 내의 Serial.write() 명령과 한 조를 이루게 된다. void setup() 내부에 establishContact() 구문이 있음에도 불구하고 void loop() 내에 Serial.print() 명령을 사용하게 되면 이해하기 어려운 문제나 Error가 발생할 수도 있음에 유의한다. 예를 들면 시리얼 모니터에 데이터 값이 출력되지 않고 여전히 “AAA....”가 출력되는 이상 현상이 관찰되기도 한다.

Virtual_rf_receive_send_03
#include <VirtualWire.h>

// Sensors
int Sensor1Data;
int Sensor2Data;
float x1=0.0;

// RF Transmission container
char Sensor1CharMsg[4];

void setup() {
  Serial.begin(9600);
  
     // VirtualWire
    // Initialise the IO and ISR
    // Required for DR3100
    vw_set_ptt_inverted(true);
    // Bits per sec
    vw_setup(2000);    
   
    // Start the receiver PLL running
    vw_rx_start();      

establishContact();

} // END void setup

void loop(){
 
    uint8_t buf[VW_MAX_MESSAGE_LEN];
    uint8_t buflen = VW_MAX_MESSAGE_LEN;
   
    // Non-blocking
    if (vw_get_message(buf, &buflen))
    {
    int i;
    
        // Message with a good checksum received, dump it.
        for (i = 0; i < buflen; i++)
    {           
          // Fill Sensor1CharMsg Char array with corresponding
          // chars from buffer.  
          Sensor1CharMsg[i] = char(buf[i]);
    }
        
        // Null terminate the char array
        // This needs to be done otherwise problems will occur
        // when the incoming messages has less digits than the
        // one before.
        Sensor1CharMsg[buflen] = '\0';
       
        // Convert Sensor1CharMsg Char array to integer
        Sensor1Data = atoi(Sensor1CharMsg);
         
// DEBUG
//        Serial.print("Sensor 1: ");
//        Serial.println(Sensor1Data);
          x1=x1+0.1;

          Sensor2Data=128*cos(x1)+128;
        Serial.write(Sensor1Data);
        Serial.write(Sensor2Data);
        // END DEBUG
           
    }
}

void establishContact() {
 while (Serial.available() <= 0) {
 Serial.write('A// send a capital A');
 delay(10);
 }
}

프로세싱에서 아두이노 데이터를 받아들이기 위해 import processing.serial* 라이브러리 명령을 반드시 실행한다.
스크린 화면 크기는 ScreenWidth와 ScreenHeight 변수를 사용하여 값을 주지만 size() 명령에서 직접 숫자 값을 입력해야 할 필요가 있다.
그래프 시작 화면에서 송신모듈에서 sin()함수에 128을 더하여 생성된 그래프는 초기 값이 128로서 꼭 화면 상단에서부터 시작함을 관찰하기 바란다.
그래픽 출력 속도는 오실로스코프로 사용하기에는 느린 것이 사실이나 우리가 드론을 시각적으로 보면서 조정할 경우 드론의 기울어짐이나 회전 속도를 센싱하기에는 충분한 속도이다.




Processing_Virtual_rf_receive_03
// Graph Decoration //
String xLabel = "Time";
String yLabel = "Analog Volts";
String Heading = "Real Time Sensor Data";

int NumOfBars=2; // you can choose the number of bars, but it can cause issues

int ScreenWidth = 600, ScreenHeight=400;

import processing.serial.*;
Serial myPort;

boolean firstContact = false;
int[] serialInArray = new int[6];
int serialCount = 0;
PFont font;

int[] bars = new int[NumOfBars];
int xPos = 2;
int prev_xPos = 1;
float prev_bars0 = 600;// It means the bottom side of screen.
float prev_bars1 = 600;// It means the bottom side of screen.

void setup(){

    println(Serial.list());
    myPort = new Serial(this, Serial.list()[2], 9600);

     size(600,400);
     font = createFont("Arial",12);
     textAlign(CENTER);
     textFont(font);
    
     background(0);
}
// 아두이노에서는 void loop()가 사용되나 프로세싱에서는 void draw()가 사용된다.
// Axis(), Labels(), Graphlines()는 별도로 void draw() 이후에 설치된다.
void draw(){

     Axis();
     Labels();
     Graphlines ();

}
// Graphlines는 각 그래프 작도 시 두 개의 좌표 데이터가 필요하다.
// 현재 2개의 실시간 그래프 작도를 위해 line 명령이 두 번 사용된다.

 void Graphlines () {
   // draw the line:
      stroke(230);
   strokeWeight(1);
   line(prev_xPos,prev_bars0 , xPos, height - bars[0]);
   prev_bars0 = height-bars[0];

   stroke(220);
   strokeWeight(2);
   line(prev_xPos, prev_bars1, xPos, height - bars[1]);
   prev_bars1 = height-bars[1];

   // at the edge of the screen, go back to the beginning:
   if (xPos >= width) {
     xPos = 1;
     prev_xPos = 0;
     background(0); //after one cycle, elliminate page.
   } else {
     // increment the horizontal position:
     xPos++;
     prev_xPos++;
   }
 }

// 아두이노와 데이터 핸드셰이킹 작업을 위해 반드시 필요한 routine 이다.
void serialEvent(Serial myPort) {
// read a byte from the serial port:
 int inByte = myPort.read();
  if (firstContact == false) {
    if (inByte == 'A') {
       myPort.clear(); // clear the serial port buffer
       firstContact = true; // you've had first contact from the microcontroller
       myPort.write("A// ask for more");
    }
   }
   else {

     // Add the latest byte from the serial port to array:
     serialInArray[serialCount] = inByte;
     serialCount++;

     // If we have 6 bytes:
     if (serialCount > 1 ) {
       // 2종의 데이터를 출력하자면 경우 0 과 1이되면 x<2 표현이 필요하다.
      // 3종의 데이터를 출력하자면 경우 0,1,2 이므로 x<3 표현이 필요하게 된다.
        for (int x=0;x<2;x++){
        bars[x] = int ( (ScreenHeight)*(serialInArray[x]/256.0));
       }

     // Send a capital A to request new sensor readings:
     myPort.write("A // Reset serialCount:");
     serialCount = 0;
    
    }
   }
}

// size(600,400) 화면 중앙선을 긋는다.
void Axis(){
// center line plot
  line(0,200,600,200);
  }

// 화면 내 부에 세로 축 및 가로 축 그리고 그래프 명칭 라벨을 출력한다.
void Labels(){

 textFont(font,18);
 fill(180);
 rotate(radians(-90));
 text(yLabel,-200,20);
 textFont(font,16);
 
 textFont(font,18);
 rotate(radians(90));
 text(xLabel,300,380);
 textFont(font,24);
 fill(250);
 text(Heading,300,50);

}