Embedded Lecture/Arduino2017. 11. 30. 18:04


아두이노에서 기본적으로 제공하는 Wire 라이브러리를 이용해도 되지만 I2C 라이브러리를 이용하여 MPU6050의 X축, Y축 그리고 Z축의 기울기를 구하였습니다.


i2c.ino

const uint8_t IMUAddress = 0x68; // AD0 is logic low on the PCB

const uint16_t I2C_TIMEOUT = 1000; // Used to check for errors in I2C communication


uint8_t i2cWrite(uint8_t registerAddress, uint8_t data, bool sendStop) {

  return i2cWrite(registerAddress,&data,1,sendStop); // Returns 0 on success

}


uint8_t i2cWrite(uint8_t registerAddress, uint8_t* data, uint8_t length, bool sendStop) {

  Wire.beginTransmission(IMUAddress);

  Wire.write(registerAddress);

  Wire.write(data, length);

  return Wire.endTransmission(sendStop); // Returns 0 on success

}


uint8_t i2cRead(uint8_t registerAddress, uint8_t* data, uint8_t nbytes) {

  uint32_t timeOutTimer;

  Wire.beginTransmission(IMUAddress);

  Wire.write(registerAddress);

  if(Wire.endTransmission(false)) // Don't release the bus

    return 1; // Error in communication

  Wire.requestFrom(IMUAddress, nbytes,(uint8_t)true); // Send a repeated start and then release the bus after reading

  for(uint8_t i = 0; i < nbytes; i++) {

    if(Wire.available())

      data[i] = Wire.read();

    else {

      timeOutTimer = micros();

      while(((micros() - timeOutTimer) < I2C_TIMEOUT) && !Wire.available());

      if(Wire.available())

        data[i] = Wire.read();

      else

        return 2; // Error in communication

    }

  }

  return 0; // Success

}


Kalman.h

/* Copyright (C) 2012 Kristian Lauszus, TKJ Electronics-> All rights reserved->

 This software may be distributed and modified under the terms of the GNU

 General Public License version 2 (GPL2) as published by the Free Software

 Foundation and appearing in the file GPL2->TXT included in the packaging of

 this file-> Please note that GPL2 Section 2[b] requires that all works based

 on this software must also be made publicly available under the terms of

 the GPL2 ("Copyleft")->


 Contact information

 -------------------

 Kristian Lauszus, TKJ Electronics

 Web      :  http://www->tkjelectronics->com

 e-mail   :  kristianl@tkjelectronics->com

 */


#ifndef _Kalman_h

#define _Kalman_h


struct Kalman {

    /* Kalman filter variables */

    double Q_angle; // Process noise variance for the accelerometer

    double Q_bias; // Process noise variance for the gyro bias

    double R_measure; // Measurement noise variance - this is actually the variance of the measurement noise


    double angle; // The angle calculated by the Kalman filter - part of the 2x1 state vector

    double bias; // The gyro bias calculated by the Kalman filter - part of the 2x1 state vector

    double rate; // Unbiased rate calculated from the rate and the calculated bias - you have to call getAngle to update the rate


    double P[2][2]; // Error covariance matrix - This is a 2x2 matrix

    double K[2]; // Kalman gain - This is a 2x1 vector

    double y; // Angle difference

    double S; // Estimate error

};


void Init(struct Kalman* klm){

    /* We will set the variables like so, these can also be tuned by the user */

    klm->Q_angle = 0.001;

    klm->Q_bias = 0.003;

    klm->R_measure = 0.03;


    klm->angle = 0; // Reset the angle

    klm->bias = 0; // Reset bias

   klm->P[0][0] = 0; // Since we assume that the bias is 0 and we know the starting angle (use setAngle), the error covariance matrix is set like so - see: http://en->wikipedia->org/wiki/Kalman_filter#Example_application->2C_technical

    klm->P[0][1] = 0;

    klm->P[1][0] = 0;

    klm->P[1][1] = 0;

}


// The angle should be in degrees and the rate should be in degrees per second and the delta time in seconds

double getAngle(struct Kalman * klm, double newAngle, double newRate, double dt) {

    // KasBot V2  -  Kalman filter module - http://www->x-firm->com/?page_id=145

    // Modified by Kristian Lauszus

   // See my blog post for more information: http://blog->tkjelectronics->dk/2012/09/a-practical-approach-to-kalman-filter-and-how-to-implement-it


    float P00_temp;

    float P01_temp;


    // Discrete Kalman filter time update equations - Time Update ("Predict")

    // Update xhat - Project the state ahead


   /* Step 1 */

    klm->rate = newRate - klm->bias;

    klm->angle += dt * klm->rate;


    // Update estimation error covariance - Project the error covariance ahead


    /* Step 2 */

    klm->P[0][0] += dt * (dt*klm->P[1][1] - klm->P[0][1] - klm->P[1][0] + klm->Q_angle);

    klm->P[0][1] -= dt * klm->P[1][1];

    klm->P[1][0] -= dt * klm->P[1][1];

    klm->P[1][1] += klm->Q_bias * dt;


    // Discrete Kalman filter measurement update equations - Measurement Update ("Correct")

    // Calculate Kalman gain - Compute the Kalman gain


    /* Step 4 */

    klm->S = klm->P[0][0] + klm->R_measure;


    /* Step 5 */

    klm->K[0] = klm->P[0][0] / klm->S;

    klm->K[1] = klm->P[1][0] / klm->S;


    // Calculate angle and bias - Update estimate with measurement zk (newAngle)


    /* Step 3 */

    klm->y = newAngle - klm->angle;

 

   /* Step 6 */

    klm->angle += klm->K[0] * klm->y;

    klm->bias += klm->K[1] * klm->y;


    // Calculate estimation error covariance - Update the error covariance


    /* Step 7 */

    P00_temp = klm->P[0][0];

    P01_temp = klm->P[0][1];


    klm->P[0][0] -= klm->K[0] * P00_temp;

    klm->P[0][1] -= klm->K[0] * P01_temp;

    klm->P[1][0] -= klm->K[1] * P00_temp;

    klm->P[1][1] -= klm->K[1] * P01_temp;

 

   return klm->angle;

}

// Used to set angle, this should be set as the starting angle

void setAngle(struct Kalman* klm, double newAngle) { klm->angle = newAngle; }


// Return the unbiased rate

double getRate(struct Kalman* klm) { return klm->rate; }

 

/* These are used to tune the Kalman filter */

void setQangle(struct Kalman* klm, double newQ_angle) { klm->Q_angle = newQ_angle; }


/* Default value is (0.003f), raise this to follow input more closely, lower this to smooth result of kalman filter */

void setQbias(struct Kalman* klm, double newQ_bias) { klm->Q_bias = newQ_bias; }

void setRmeasure(struct Kalman* klm, double newR_measure) { klm->R_measure = newR_measure; }

double getQangle(struct Kalman* klm) { return klm->Q_angle; }

double getQbias(struct Kalman* klm) { return klm->Q_bias; }

double getRmeasure(struct Kalman* klm) { return klm->R_measure; }

#endif


mpu6050.ino

/* Copyright (C) 2012 Kristian Lauszus, TKJ Electronics. All rights reserved.

This software may be distributed and modified under the terms of the GNU

General Public License version 2 (GPL2) as published by the Free Software

Foundation and appearing in the file GPL2.TXT included in the packaging of

this file. Please note that GPL2 Section 2[b] requires that all works based

on this software must also be made publicly available under the terms of

the GPL2 ("Copyleft").

Contact information

-------------------

Kristian Lauszus, TKJ Electronics

Web : http://www.tkjelectronics.com

e-mail : kristianl@tkjelectronics.com

Updated by Joe YOON in 2017.12.02

*/


#include <Wire.h>

#include "Kalman.h"


struct kalman Kal_struct_X, Kal_struct_Y; // Create the Kalman instances


/* IMU Data */

int16_t accX, accY, accZ; // 3-axis accelerometer

int16_t tempRaw;

int16_t gyroX, gyroY, gyroZ; // 3-axis gyroscope


double accXangle, accYangle; // Angle calculate using the accelerometer

double temp; // Temperature

double gyroXangle, gyroYangle; // Rate calculate using the gyro

double compAngleX, compAngleY; // Calculate the angle using a complementary filter

double kalAngleX, kalAngleY; // Calculate the angle using a Kalman filter


uint32_t timer;

uint8_t i2cData[14]; // Buffer for I2C data


void setup() {

  Init(&Kal_struct_X); // Initialize Kalman filter for X-axis

  Init(&Kal_struct_Y); // Initialize Kalman filter for Y-axis

  Serial.begin(9600);

  Wire.begin();

  i2cData[0] = 7; // Set the sample rate to 1000Hz - 8kHz/(7+1) = 1000Hz

  i2cData[1] = 0x00; // Disable FSYNC and set 260 Hz Acc filtering, 256 Hz Gyro filtering, 8 KHz sampling

  i2cData[2] = 0x00; // Set Gyro Full Scale Range to +/-250[deg/s]

  i2cData[3] = 0x00; // Set Accelerometer Full Scale Range to +/-2[g]

  while(i2cWrite(0x19,i2cData,4,false)); // Write to all four registers at once

  while(i2cWrite(0x6B,0x01,true)); // PLL with X axis gyroscope reference and disable sleep mode

  while(i2cRead(0x75,i2cData,1));

  if(i2cData[0] != 0x68) { // Read "WHO_AM_I" register

    Serial.print(F("Error reading sensor"));

    while(1);

  }

  

  delay(100); // Wait for sensor to stabilize

  

  /* Set kalman and gyro starting angle */

  while(i2cRead(0x3B,i2cData,6));

  accX = ((i2cData[0] << 8) | i2cData[1]);

  accY = ((i2cData[2] << 8) | i2cData[3]);

  accZ = ((i2cData[4] << 8) | i2cData[5]);


  // atan2 outputs the value of -? to ? (radians) - see http://en.wikipedia.org/wiki/Atan2

  // We then convert it to 0 to 2? and then from radians to degrees

  accYangle = atan2(-1*accX/sqrt(pow(accY,2) + pow(accZ,2)))*RAD_TO_DEG;

  accXangle = atan2(accY/sqrt(pow(accX,2) + pow(accZ,2)))*RAD_TO_DEG;

   

  setAngle(&Kal_struct_X, accXangle); // Set starting angle

  setAngle(&Kal_struct_Y, accYangle);


  gyroXangle = accXangle;

  gyroYangle = accYangle;

  compAngleX = accXangle;

  compAngleY = accYangle;

  

  timer = micros();

}


void loop() {

  /* Update all the values */

  while(i2cRead(0x3B,i2cData,14));

  accX = ((i2cData[0] << 8) | i2cData[1]);

  accY = ((i2cData[2] << 8) | i2cData[3]);

  accZ = ((i2cData[4] << 8) | i2cData[5]);

  tempRaw = ((i2cData[6] << 8) | i2cData[7]);

  gyroX = ((i2cData[8] << 8) | i2cData[9]);

  gyroY = ((i2cData[10] << 8) | i2cData[11]);

  gyroZ = ((i2cData[12] << 8) | i2cData[13]);

  

  // atan2 outputs the value of -? to ? (radians) - see http://en.wikipedia.org/wiki/Atan2

  // We then convert it to 0 to 2? and then from radians to degrees

  accYangle = atan2(-1*accX/sqrt(pow(accY,2) + pow(accZ,2)))*RAD_TO_DEG;

  accXangle = atan2(accY/sqrt(pow(accX,2) + pow(accZ,2)))*RAD_TO_DEG;

   

  double gyroXrate = (double)gyroX/131.0;

  double gyroYrate = -((double)gyroY/131.0);

  gyroXangle += gyroXrate*((double)(micros()-timer)/1000000); // Calculate gyro angle without any filter

  gyroYangle += gyroYrate*((double)(micros()-timer)/1000000);

  //gyroXangle += kalmanX.getRate()*((double)(micros()-timer)/1000000); // Calculate gyro angle using the unbiased rate

  //gyroYangle += kalmanY.getRate()*((double)(micros()-timer)/1000000);

  

  compAngleX = (0.93*(compAngleX+(gyroXrate*(double)(micros()-timer)/1000000)))+(0.07*accXangle); // Calculate the angle using a Complimentary filter

  compAngleY = (0.93*(compAngleY+(gyroYrate*(double)(micros()-timer)/1000000)))+(0.07*accYangle);

  

  kalAngleX = getAngle(&Kal_struct_X, accXanglegyroXrate(double)(micros()-timer)/1000000); // Calculate the angle using a Kalman filter

  kalAngleY = getAngle(&Kal_struct_Y, accYanglegyroYrate(double)(micros()-timer)/1000000);

  timer = micros();

  

  temp = ((double)tempRaw + 12412.0) / 340.0;

  

  /* Print Data */

  display_formatted_float(accX, 5, 0, 3, false);

  display_formatted_float(accY, 5, 0, 3, false);

  display_formatted_float(accZ, 5, 0, 3, false);

  display_formatted_float(gyroX, 5, 0, 3, false);

  display_formatted_float(gyroY, 5, 0, 3, false);

  display_formatted_float(gyroZ, 5, 0, 3, false);


  Serial.print("\t");


  display_formatted_float(accXangle, 5, 2, 3, false);

  display_formatted_float(gyroXangle, 5, 2, 3, false);

  display_formatted_float(compAngleX, 5, 2, 3, false);

  display_formatted_float(kalAngleX, 5, 2, 3, false);


  Serial.print("\t");


  display_formatted_float(accYangle, 5, 2, 3, false);

  display_formatted_float(gyroYangle, 5, 2, 3, false);

  display_formatted_float(compAngleY, 5, 2, 3, false);

  display_formatted_float(kalAngleY, 5, 2, 3, false);


  //Serial.print(temp);Serial.print("\t");


  Serial.print("\r\n");

  delay(1);

}


void display_formatted_float(double val, int characteristic, int mantissa, int blank, boolean linefeed) {

  char outString[16];

  int len;


  dtostrf(val, characteristic, mantissa, outString);

  len = strlen(outString);

  for(int i = 0; i < ((characteristic+mantissa+blank)-len); i++) Serial.print(F(" "));

  Serial.print(outString);

  if(linefeed)

    Serial.print(F("\n"));

}


Posted by Nature & Life
Embedded Lecture/Arduino2017. 5. 8. 20:34


인터럽트(interrupt)란 지정된 핀의 신호가 원하는 조건과 일치하면 미리 등록한 인터럽트 callback 함수(ISR; Interrupt Service Routines)를 자동으로 호출해주는 기능입니다. 이 ISR 함수는 사용자가 만든 함수이며, 이때 실행 중이던 loop() 함수 내부의 루틴은 인터럽트 callback 함수가 끝날 때까지 멈추게 됩니다.


즉, 특정 핀의 입력 상태가 바뀔 때 Arduino는 이를 자동으로 감지해서 모든 동작을 잠시 멈춘 다음, ISR(Interrupt Service Routines) 이라 불리는 미리 지정된 함수를 실행하고, 다시 원래 작업으로 복귀한다는 것입니다. 이를 '하드웨어 인터럽트'라 부릅니다. 이 외에도 비슷한 타이머(timer) 인터럽트가 있는데 이는 사용법이 전혀 다르므로 이 글에서 다루진 않습니다.

♧ 타이머 인터럽트 - AVR 칩을 비롯한 임베디드 시스템에서는 시간을 재기 위해서 타이머(Timer)/카운터(Counter)를 내장합니다. 만일 타이머/카운터가 없다면, 소프트웨어적으로 시간지연 함수를 사용해야 함으로써 MCU는 동작을 멈추게 되고, 이로 인해 MCU의 효율을 떨어뜨리게 된다는 것입니다.

뿐만 아니라 이와 같은 내장 타이머/카운터는 정확한 시간을 잴 수 있으며, MCU가 다른 작업과 병행할 수 있고, 미리 설정한 조건에서 타이머 인터럽트도 발생하게 할 수 있다는 것입니다. 이러한 각각 인터럽트들은 동시에 발생할 수도 있으므로 우선 순위를 갖습니다.


Arduino의 인터럽트 핀

Arduino Uno 기준으로 2개의 인터럽트 핀이 할당되어 있습니다. 즉, Number 0 (D2), Number 1 (D3). 몇개의 보드별 지원되는 인터럽트 핀은 다음과 같습니다. 인터럽트 callback(ISR)을 등록할 때 유의할 점은 핀 번호가 아니라 반드시 인터럽트 넘버를 사용한다는 것입니다. 예를 들어, 다음 표에서 Leonardo 보드는 Uno 보다 많은 5개의 인터럽트 핀을 제공합니다. 그리고 다섯번째 인터럽트 핀(int4)은 Number 7이라는 것입니다. 참고로 Arduino Due 보드는 모든 핀에 인터럽트가 지원됩니다.


 보드

 int0

 int1

 int2

 int3

 int4

 int5

 Uno/Ethernet

 2

 3

 x

 x

 x

 x

 Mega2560

 2

 3

 21

 20

 19

 18

 Leonardo

 3

 2

 0

 1

 7

 x


인터럽트 callback 함수(ISR)는 입력 인자가 없고 반환값이 없습니다. 즉 파라미터를 전달하거나 리턴할 수가 없다는 것입니다. 게다가 ISR 함수 내에서는 delay() 함수를 사용할 수 없습니다. 또한 milli second의 시각을 가져오는 함수인 millis()를 사용하더라도 값이 증가하지는 않습니다. delayMicroseconds()의 경우에는 인터럽트에 독립적이므로 정상 동작합니다.


뿐만 아니라 ISR 함수 내에서는 Serial data를 읽을 경우 값이 소실되며, 이전 글에서 언급했던 것처럼 ISR 함수 내에서 업데이트 되는 전역 변수는 volatile로 반드시 선언되어야 합니다. ISR 함수를 만드는 요령은 최대한 짧고 빠르게 수행되도록 간결하게 작성해야 합니다. 왜냐면 이 코드가 길어지는 만큼 CPU는 멈추어 메인 작업을 수행하지 않아 효율적이지 않다는 것입니다.


또한 여러 개의 ISR 함수가 등록되어 있더라도 동시에 수행되지는 않습니다. 블루투스 모듈과의 통신을 위해 주로 사용되는 Software Serial 라이브러리의 경우, 내부적으로 인터럽트를 사용하는 것으로 알려져 있습니다. 따라서 Uno 보드의 경우 D2, D3 에 연결해야만 정상 동작 가능합니다. 따라서 이를 회피하기 위해서 D0, D1 핀에 연결해서 Hardware Serial로 동작시킬 수 있습니다.



Posted by Nature & Life
Embedded Lecture/Arduino2017. 4. 16. 21:57


시중에서 쉽게 구할 수 있는 저가형 초음파 센서 모듈(HC-SR04)는 그림과 같이 4핀 인터페이스를 갖습는다. VCC/GND는 5V 전원핀이고 Trig는 초음파를 발생시키는 펄스 신호를 내보내는 핀이며, Echo 핀은 반사파가 감지되었음을 알려주는 신호선으로 측정된 거리에 해당하는 High level 펄스를 출력합니다. 그러므로 이 모듈을 사용하기 위해서는 Arduino Uno의 2개의 디지털 핀이 필요하게 됩니다.



HC-SR04, 초음파 센서는 정밀도가 3mm정도로 2~400cm 까지의 거리를 초음파를 이용하여 측정할 수 있으며, 이 모듈에는 초음파 송수신, 제어회로가 모두 내장되어 있어서 마이크로컨트롤러를 이용하면 비교적 손쉽게 응용이 가능하다는 것입니다. 기본원리는 다음과 같습니다. 우선 Uno 보드로 모듈의 Trig 핀으로 약 10us 정도의 High level 펄스를 보냅니다.



그러면 모듈의 내부에서는 40KHz의 초음파를 정면으로 발사하고 나서 장애물에 반사되어 오는 초음파를 감지합니다.



신호가 반사되어 되돌아오면 모듈에서 Echo 핀을 통해 마이크로컨트롤러의 IO로 측정거리만큼의 High level 펄스 신호를 보내줍니다.



마이크로컨트롤러에서는 모듈에서 받은 High 펄스의 시간(High 펄스 폭)을 계산해서 다음 식에 의해 거리로 환산합니다. 여기서 초음파는 장애물은 만나기 전과 만난 후 반사되어 되돌아가기 때문에 왕복으로 2로 나누어야 합니다. 이와 같은 일련의 과정을 계속 반복하여 거리 측정값을 업데이트 할 수 있습니다.


측정거리 = (High level 펄스 시간 x 음속(340m/s)) / 2


위 초음파 센서 모듈을 이용하기 위해서는 쉬운 방법으로 아두이노의 NewPing 라이브러리가 필요한데, 압축된 zip 파일을 다운로드 받은 후에 다음과 같이 Arduino IDE에서 등록할 수 있습니다.


스케치 > Include Library > Add .ZIP Library


설치하였다면 다음과 같이 메뉴에 항목이 새로 만들어진다. 이것을 선택하면 스케치 파일에 #include “NewPing.h” 행이 추가되어 라이브러리 함수를 사용할 수 있게 됩니다.



다음은 NewPing 라이브러리를 이용한 거리 측정 예제입니다. 여기 Trig는 2번 핀에 Echo는 3번 핀에 연결되었다고 가정합니다.


#include <NewPing.h>

#define TRIGGER_PIN 2

#define ECHO_PIN 3

NewPing sonar(TRIGGER_PIN, ECHO_PIN);

void setup() {

  Serial.begin(9600);

}


void loop() {

  float fDist = (float)sonar.ping()/US_ROUNDTRIP_CM;  // 반사파가 감지될 때 까지의 시간을 us 단위의 정수로 반환

                                                               // US_ROUNDTRIP_CM는 us를 cm단위로 바꾸어주는 상수

  Serial.print("Ping: ");

  Serial.print(fDist);

  Serial.println(" cm");

  Serial.println();

  delay(300);

}


NewPing sonar(trigger_pin, echo_pin [, max_cm_distance])는 해당되는 핀 번호 두 개와 최대 측정 거리(디폴트값 500 cm)를 주게 되어 있으며 최대 측정 거리는 생략할 수도 있습니다. 이 라이브러리는 다양한 함수를 제공하고 있으며 여기서 사용할 함수는 sonar.ping()으로, 이 함수는 초음파가 발사되고 그 반사파가 감지될 때까지 걸린 시간을 microsecond 단위의 정수로 반환합니다. 따라서 이 값을 라이브러리에 이미 정의된 상수 US_ROUNDTRIP_CM으로 나누면 장애물까지의 거리를 cm단위로 얻을 수 있습니다. 만일 감지 가능 거리 내에 장애물 없다면 0을 반환합니다.


위에서 fDist 변수값을 구하는데 sonar.ping() 함수의 반환값을 정확한 값을 계산하기 위해서 float로 변환(forcing)하였습니다. 결국 NewPing sonar() 함수는 지정된 Trig 핀에 초음파 센서를 구동하기 위한 10us 펄스를 내보내고, 지정된 Echo 핀에서는 측정 거리에 비례하는 HIGH 펄스 신호의 폭을 계산하는 루틴이 포함되어 있습니다.


다음은 NewPing 라이브러리를 사용하지 않고 거리를 측정하는 예제입니다.


#define TRIGGER_PIN 2

#define ECHO_PIN 3


void setup() {

  Serial.begin(9600);

  pinMode(TRIGGER_PIN, OUPUT); // 핀 모드를 출력으로 설정합니다.

  pinMode(ECHO_PIN, INPUT); // 핀 모드를 입력으로 설정합니다.

}


void loop() {

  float fDist;

  digitalWrite(TRIGGER_PIN,HIGH); 

  delayMicroseconds(10); // 10us 지연

  digitalWrite(TRIGGER_PIN, LOW); // 10us의 트리거 신호를 만들어 보냅니다.

  fDist = (float)pulseIn(ECHO_PIN, HIGH)/US_ROUNDTRIP_CM;

  Serial.print("Ping: ");

  Serial.print(fDist);

  Serial.print(" cm");

  Serial.println();

  delay(300); // 0.3s 대기 후 다시 측정합니다.

}

위 예제에서 사용된 pulseIn(pin, value, timeout)는 지정된 핀에 value는 'LOW' 혹은 'HIGH' 값으로, 'LOW'이면 low 펄스 구간의 폭을 'HIGH' 이면 high 펄스 구간의 폭을 측정하여 microsecond 단위로 반환합니다. 이 반화값의 자료형은 unsigned long 형으로 계산의 정확도를 위해서 float 형으로 변환(forcing)하였습니다. 세 번째 인수 timeout은 시간 제한으로 생략할 수 있으며 이 함수는 10us에서 3분까지 측정할 수 있습니다. 이러한 거리 측정은 0.3초 간격으로 반복됩니다.



Posted by Nature & Life
Embedded Lecture/Arduino2017. 4. 11. 21:10


아두이노 라이브러리는 다른 언어와 마찬가지로 코드의 재사용성을 높혀 유지관리를 쉽게 만들어줍니다. 물론 상업용 업체에서는 코드를 컴파일하여 제공하므로써 우선 코드 암호화를 통한 보안 등에 더 관심을 갖을 수는 있지만, 아두이노(Arduino)나 리눅스(Linux)처럼 컴파일된 파일 혹은 실행 파일과 더불어 소스 코드도 제공하는 경우에는 사용자가 코드의 디버그(debug)와 같은 효율성 및 유지관리 측면에서 보다 촛점을 맞출 수 있습니다.


라이브러리(Library)는 기본적으로 사용자가 작성한 코드로 자주 사용하는 코드를 모듈화하여 추후에 쉽게 불러다 사용할 수 있도록 소스 코드 자체나, 컴파일하여 목적코드(object) 형태로 저장한 것입니다. 사실 사용자 뿐만 아니라 소프트웨어 환경제공자나 제조업체에 의해서 제공되는 경우가 많습니다. 이러한 라이브러리는 재사용이 가능한 루틴이나 함수 등으로 구성되며, 이 루틴이나 함수의 내부를 모르고도 설명만으로 누구나 헤더파일에 선언하여 컴파일 시 쉽게 가져다 사용할 수 있습니다.


여기서는 인터넷상에서 다운로드한 아두이노용 라이브러리를 설치하는 방법과 직접 라이브러리를 작성하는 방법에 대해서 설명합니다. 우선 Arduino IDE를 설치하면 {내문서}\Arduino\libraris 폴더가 생성되고, 여기에 다운로드 받은 라이브러리를 복사하거나 사용자가 직접 작성한 라이브러리를 저장합니다. 컴파일을 수행할 때 이 폴더는 자동으로 'include'되어서 여기에 있는 라이브러리들이 링크하게 됩니다.


라이브러리는 기본적으로 폴더 단위로 저장되어야 하는데, 예를 들어 'MyLib' 라는 이름(통상 라이브러리명은 대문자로 시작함)으로 라이브러리를 작성한다면, {내문서}\Arduino\libraris 폴더 밑에 MyLib라는 폴더가 있어야 하고, 이 폴더 밑에 MyLib.h 헤더 파일과 MyLib.cpp 파일이 있어야 합니다. 만일 예제 파일을 제공하고 싶다면 별도의 example 폴더 밑에 예제 파일의 이름과 같은 폴더를 두고 그 밑에 .ino 파일을 위치해 두어야 합니다. 이와 같은 폴더 구조를 유지하는 것이 다수의 사용자들에게 통일되고 익숙하기 때문입니다.



통상 라이브러리는 클래스나 변수 등을 선언한 헤더 파일(*.h)과 클래스 멤버함수의 정의부가 있는 *.cpp 파일로 나뉩니다. 그리고 헤더 파일에는 MyLib 클래스가 선언되어 있어야 합니다. 보통은 헤더 파일과 *.cpp 파일로 구분되지만 모든 선언과 정의를 헤더 파일에 둘 수도 있습니다만, 헤더 파일에는 클래스의 선언부를 작성하고 그것을 구현한 *.cpp 파일을 별도로 두는 것이 더 일반적이고 바람직하다는 것입니다. 이렇게 구성하면 이 라이브러리를 사용하는 스케치 파일에서는 헤더 파일만 include 해서 사용할 수 있고, 여러 곳에서 중복해서 사용할 경우에도 헤더 파일만 포함하면 되기 때문입니다.


아두이노 홈페이지에서 다양한 아두이노 라이브러리를 제공하고 있습니다. 사용자는 여기에서 필요한 라이브러리를 다운로드 받아서 설치할 수 있습니다. 예를 들어 OneWire 라는 라이브러리를 다운로드 받았다고 가정하면, 다운로드 폴더에 OneWire.zip 파일이 다운로드 되어 있을 것입니다. 이 압축파일에는 OneWire.h, OneWire.cpp 파일이 들어 있습니다. 이 경우 이 압축파일을 이용하여 바로 사용자 라이브러리 폴더에 복사할 수 있는데 다음과 같이 'Sketch>Include library > Add .ZIP Library' 메뉴를 이용하여 압축파일을 선택해 주면 됩니다.


아두이노 라이브러리


이 경우에, OneWire.zip 파일을 선택하면 자동으로 사용자 라이브러리 폴더에 압축이 해제되서 복사하게 됩니다. 만일에 인터넷 상에서 특정 라이브러리의 특정 버젼을 필요로 하는 경우에는 사용자가 직접 압축을 해제한 후에 필요한 폴더 혹은 파일만을 사용자 라이브러리 폴더에 수동으로 복사해도 무방하다는 것입니다.



'Embedded Lecture > Arduino' 카테고리의 다른 글

인터럽트의 처리(2)  (0) 2017.05.08
초음파 센서를 이용한 거리 측정 예제  (0) 2017.04.16
아날로그 출력(PWM)  (0) 2017.03.20
아날로그 입력 및 온도계 예제  (0) 2017.03.19
아날로그 입력  (0) 2017.03.18
Posted by Nature & Life
Embedded Lecture/Arduino2017. 3. 20. 21:03


Arduino Uno 보드의 디지털 핀은 '1'과 '0'의 진리값만을 출력할 수 있습니다. Uno 보드의 전원 전압이 5V이라면 'HIGH' 값으로 5V 그리고 'LOW' 값으로 0V를 출력한다는 것입니다. 그렇다면 아날로그 물리량은 나타낼 수가 없는 것일까요? 디지털 값을 아날로그 물리량으로 나타낼 수 있는 것이 있는데, 이를 DAC(Digital to Analog Converter)라 부릅니다. 하지만 Uno 보드의 메인칩인 ATmega328은 이 DAC를 탑재하지 않습니다(정교한 아날로그 물리량의 표현이 요구된다면 외장 DAC를 사용할 수 있습니다).


그렇다면 외부에서 DAC를 사용해야 하지만 정밀한 아날로그 물리량을 요구하지 않는 경우에, Uno 보드의 PWM 기능을 이용하여 아날로그 물리량을 표현할 수 있다는 것입니다. PWM(Pulse Width Modulation, 펄스 폭 변조)이란 5V와 0V를 교대로 이루어진 구형파(펄스) 신호를 출력하고 5V인 구간의 폭을 구형파 주기 내에서 변화시킴으로서 외부에서는 마치 0~5V까지 변하는 것처럼 보이게 한다는 것입이다.



위 그림에서 모든 구형파의 주기(T)는 동일하지만 출력이 HIGH인 구간인 구형파의 폭(width)이 점점 증가합니다. 만일 외부에서 디지털 출력 핀을 바라다 볼 때, 구형파의 주기(T)가 충분히 작거나 혹은 주파수(f=1/T)가 충분히 높다면, 구형파의 HIGH인 구간의 폭을 증가시킴에 따라 출력 전압도 증가하는 것처럼 보일 것입니다. 


즉, 이 구형파의 주파수를 높게 하면 상대적으로 반응 속도가 느린 모터 등과 같은 기계 장치는 이것을 아날로그 전압으로 착각하게 된다는 것입니다. 이 주파수는 Uno인 경우 490Hz 혹은 980Hz이며, HIGH인 구간 대비 LOW인 구간의 비율을 듀티비(Duty)라 부릅니다. Arduino의 모든 핀이 PWM 출력을 낼 수 있는 것은 아닙니다. Uno 보드의 경우 3, 5, 6, 9, 10, 11번 핀이 PWM 출력을 낼 수 있으며 보드상에 '~'로 표시됩니다. 이와 같은 디지털 핀의 펄스 폭 변조는 굳이 메인칩이 PWM 기능을 제공하지 않더라도 펌웨어 상에서 구현할 수 있습니다. 그러나 인터럽트나 CPU 타임을 일정부분 할애하기 때문에 코딩이 복잡해지고 효율성이 떨어지게 됩니다.



Uno 보드의 PWM의 동작 주파수는 다음과 같습니다. 

        • 3, 9, 10, 11번 핀 - 490Hz

        • 5, 6번 핀 – 980Hz


Uno 보드의 PWM 기능을 사용하기 위해서는 analogWrite() 함수를 이용하는데, 첫 번째 인수는 아날로그 핀의 번호(3, 5, 6, 9, 10, 11 중 하나)이며 두번째 인수는 0~255 사이의 정수로써 256 레벨의 듀티비를 의미합니다.



Posted by Nature & Life
Embedded Lecture/Arduino2017. 3. 19. 10:07


아날로그 입력 핀에 온도 센서를 연결하여 실시간으로 Arduino IDE 환경의 PC 모니터링 창에 실시간으로 현재의 온도를 일정 시간 간격으로 보여주는 예제입니다. 사용할 온도 센서는 LM35-DZ인데, 이는 3단자 소자로 전원공급 단자, GND 그리고 출력단자로 구성되며 제조사가 제공하는 간단한 응용회로는 다음과 같습니다.


 


이 소자는 0~100˚C를 측정할 수 있는 센서로 5V의 전원전압을 공급하면 섭씨 1˚C 변화에 10mV의 전압을 출력합니다. 따라서 현재 온도가 섭씨 100˚C라면 출력전압은 100˚C*10mV=1V가 됩니다. 결국 온도 센서는 LM35-DZ는 0~100˚C 사이의 섭씨 온도 변화에 대해서 비례적으로 0~1V의 출력전압을 내보내게 됩니다.


void setup() {

    Serial.begin(9600);

}


void loop() {

    Serial.println(getTemp());

    delay(1000); // 1s에 시간지연을 줍니다.

}


float getTemp() {

    short sVal = analogRead(A0); // Default 모드로 최대 5V를 1024 레벨의 값으로 읽습니다.

    float voltage = sVal*5.0/1024; // 실제로 읽어드린 아날로그 값으로 변환합니다.

    return voltage*100; // 섭씨 온도로 환산하여 반환합니다.

}


위 코드는 결국 온도 센서를 이용하여 매 1s마다 측정된 섭씨 온도를 Arduino IDE 환경의 PC 모니터 창에 지속적으로 보여주게 됩니다.


<1초마다 온도를 감지하여 표시하는 결과 화면>


만일 ADC의 기준 전압으로 내부 전압인 1.1V를 사용한다면 분해능(resolution)은 약 5배 정도 개선되는 효과를 얻을 수 있으므로 위의 코드를 기준 전압을 내부 전압으로 사용하는 코드로 수정하였습니다. 단, 온도 센서는 100˚C까지만 유효하고 따라서 센서의 최대 전압은 1V까지만 유효하게 됩니다.


#define SUPPLY_VOLTAGE 1.1


void setup() {

    Serial.begin(9600);

    analogReference(INTERNAL); // ADC의 기준 전압을 내부 전압 1.1V로 사용함을 지정합니다.

}


void loop() {

    Serial.println(getTemp());

    delay(1000);

}


float getTemp() {

    short sVal = analogRead(A0);

    float voltage = sVal*SUPPLY_VOLTAGE/1024;

    return voltage*100;

}



'Embedded Lecture > Arduino' 카테고리의 다른 글

아두이노 라이브러리의 설치  (0) 2017.04.11
아날로그 출력(PWM)  (0) 2017.03.20
아날로그 입력  (0) 2017.03.18
인터럽트와 volatile 지시자  (1) 2017.03.18
아두이노의 TWI(I2C) 통신  (0) 2017.03.18
Posted by Nature & Life
Embedded Lecture/Arduino2017. 3. 18. 17:19


Arduino Uno 보드의 A0~A5까지 6개의 핀을 이용하여 아날로그 입력을 받을 수 있습니다. 디지털 핀은 '0'과 '1'의 논리적 값만을 받을 수 있는 것과는 달리, 아날로그 입력은 전압을 그대로 읽고 이를 1024개의 레벨로 구분하고 0~1023 중의 정수값으로 읽어들인다는 것입니다.



이것이 가능한 이유는 ATmega328 칩 내부에는 10bit A/D 컨버터(analog-to-digital converter, ADC)를 내장하고 있기 때문이며, 2^10=1024개의 레벨로 구분할 수 있다는 것은 이 컨버터의 분해능(resolution)이 전원전압 5V를 기준으로 0.0049V(4.9mV = 5V/1024)라는 말이며, 아날로그 전압을 읽어들이는 명령은 analogRead() 함수입니다.


ananlogRead(pin)


인수로는 아날로그 입력 핀의 번호를 지정하고 반환값은 int 형으로 0~1023 값 중의 하나가 됩니다. 이때 입력 핀은 0~5 혹은 A0~A5, 14~19로 지정할 수 있으며, 여기서 핀 번호 0과 A0, 14는 같은 핀을 의미합니다. A/D 변환 시간은 100us인데, 이는 사실 느린 편으로 간단한 비교만을 원할 때는 칩 내부에 포함된 빠른 비교기(comparator)를 대신 사용하게 됩니다. 아날로그 핀은 디지털 핀과 달리 기본적으로 입력으로 설정되어 있으므로 별도로 입력을 설정하는 과정이 필요 없습니다.


아날로그 핀에 연결된 A/D Converter(혹은 ADC)의 기준 전압을 바꿀 수 있는데, 이때 analogReference() 함수를 사용합니다.


ananlogReference(type)

  

A/D Converter의 기준 전압은 아날로그 입력값이 1023으로 읽히는 최대 전압 값을 의미합니다. 위 함수로 지정하지 않으면 디폴트(DEFAULT)로 Arduino의 동작 전압이며, 외부 전압(EXTERNAL)을 사용할 경우에는 아날로그 핀의 전압을 읽기 전에 반드시 미리 설정해야 합니다. 내부 전압(INTERNAL) 1.1V를 사용할 경우에 가장 안정된 기준전압을 제공함을 기억해야 합니다.


DEFAULT : Arduino의 동작 전압(Uno는 5V 이고 보드에 따라서 3.3V일 수도 있습니다.)

INTERNAL : 내장 전압 (Uno는 1.1V)

EXTERNAL : AREF핀에 인가된 전압 (0~ 5V 사이어야 함니다)


INTERNAL로 설정되어 기준 전압이 1.1V이면 디폴트보다 더 높은 분해능(0.0011V)을 얻을 수 있습니다. 만일 3.3V를 기준 전압으로 사용하고 싶다면 Arduino Uno 보드상의 3.3V핀과 AREF 핀을 연결 후 EXTERNAL 옵션을 설정하면 됩니다. 이 경우에 분해능은 0.0032V로 분해능이 개선될 수 있습니다. 보드의 3.3V 핀은 7~12V의 외부 전원을 연결한 경우 뿐만 아니라 USB만 연결한 경우에도 레귤레이터(regulator)를 사용하여 정확히 3.3V 전압을 출력합니다. 만일 Uno 보드의 동작 전압인 5V보다 높은 전압을 읽을 경우에는 외부에서 [정밀] 저항으로 voltage divider를 결선해야 합니다.



'Embedded Lecture > Arduino' 카테고리의 다른 글

아날로그 출력(PWM)  (0) 2017.03.20
아날로그 입력 및 온도계 예제  (0) 2017.03.19
인터럽트와 volatile 지시자  (1) 2017.03.18
아두이노의 TWI(I2C) 통신  (0) 2017.03.18
아두이노의 시리얼 통신  (0) 2017.03.18
Posted by Nature & Life
Embedded Lecture/Arduino2017. 3. 18. 16:33


AVR 칩을 사용하는 Arduino Uno 보드 혹은 다른 임베디드(Embedded) 시스템에서 전역 변수(globlal variable)는 주메모리인 SRAM에 저장되고, 지역 변수(local variable)는 레지스터(register) 영역에 저장됩니다. 그러나 인터럽트 서비스 루틴(ISR)에서의 지역 변수는 SRAM의 Stack 영역에 저장되므로 빠른 인터럽트 처리를 위해서 루틴 내에 잦은 지역 변수의 사용은 바람직하지 않다는 것입니다.


사용자의 코드를 컴파일러(Compiler)는 나름데로 최적화시키는데, 예를 들어 사용자가 전역 변수 지정을 하였지만 그 값이 시종일관 변하지 않는 변수는 컴파일러가 임의로 상수처럼 여겨 레지스터 영역에 저장하게 한다는 것입니다. 물론 한정된 레지스터 영역의 리소스를 낭비하는 것 아니냐고 생각할 수 있지만, 컴파일러 입장에서는 빠른 실행 속도를 위해서 SRAM 보다는 레지스터 영역에 저장한다는 것입니다.


그러나 임베디드 시스템의 경우에 인터럽트 서비스 루틴에서 전역 변수의 갱신이 있었다 하더라도, 컴파일러는 실제 런타임 시 처럼 외부에서 인터럽트가 걸리는 상황이 아니므로 그 값은 변하지 않는다고 생각하여 결국 레지스터에 저장하고 주메모리 상에 변경된 값의 반영이 없다는 것입니다.


이런 상황을 위해서 지시자(directive)인 'volatile'의 선언은 변수형 앞에 두어 컴파일러가 임의로 판단하여 그 변수를 레지스터에 할당하지 못하도록 한다는 것입니다. 즉, 어떤 변수를 volatile로 지정하면 그 변수는 레지스터의 임시 저장소가 아니라 SRAM에서 직접 읽어오도록 컴파일한다는 것입니다.


결론적으로 Arduino 코딩의 경우 보통은 volatile로 정의할 필요는 없으나 인터럽트 서비스 루틴 내부에서 그 값이 변경되는 변수는 반드시 volatile로 선언해야 실시간으로 변경되는 데이터 값을 ISR 루틴 외부에서 읽어올 수 있게 된다는 것입니다.


int pin = 13;

volatile int state = LOW;


void setup()

{

  pinMode(pin, OUTPUT);

  attachInterrupt(0, blink, CHANGE);

}


void loop()

{

  digitalWrite(pin, state);

}


void blink()

{

  state = !state;

}


위의 예제에서 blink() 함수는 인터럽트 서비스 루틴으로 0번 인터럽트 핀의 상태가 변할 때마다 13번에 연결된 LED를 교대로 점멸하게 됩니다. 만일 state 변수에 volatile 선언이 없었다면, 비록 blink() 함수 내에 state 값이 변하더라도 이 함수를 벗어나서는 state 값이 갱신되지 않기 때문에 0번 인터럽트의 상태 변화에 LED의 토글(toggle) 현상이 보이지 않는다는 것입니다.



'Embedded Lecture > Arduino' 카테고리의 다른 글

아날로그 입력 및 온도계 예제  (0) 2017.03.19
아날로그 입력  (0) 2017.03.18
아두이노의 TWI(I2C) 통신  (0) 2017.03.18
아두이노의 시리얼 통신  (0) 2017.03.18
인터럽트의 처리(1)  (0) 2017.03.12
Posted by Nature & Life
Embedded Lecture/Arduino2017. 3. 18. 14:53


USART와 같은 시리얼 통신이 1:1 통신 규약인 반면, TWI(I2C)는 1:n 통신 규약으로 하나의 마스터(master) 기기가 여러 개의 슬레이브(slave) 디바이스들과 통신을 할 수 있다는 장점이 있습니다. Wire 라이브러리는 아두이노의 표준 라이브러리로서 Arduino IDE에 기본으로 내장되어 있습니다. 따라서 아두이노로 TWI 통신을 위해서는 다음의 Wire 라이브러리를 지정하여야 합니다.


#include <Wire.h>


연결 구성은 하나의 마스터와 다수의 슬레이브로 구분되고 슬레이브들은 7bit 혹은 8bit 아이디(숫자)로 서로를 구분합니다. TWI는 모든 통신 주도권을 master가 가진다. 슬레이브에서 데이터를 읽어올 때도 마스터에서 요구하고 슬레이브로 데이터를 보낼 때도 마찬가지로 마스터에서 통신을 요구한 후 보냅니다. 마스터에서는 다음과 같이 초기화 함수를 호출합니다.


Wire.begin(); // 마스터의 TWI 초기화


슬레이브에서는 다음과 같이 초기화 함수를 아이디를 가지고 호출한다.


Wire.begin(n); // 슬레이브의 TWI 초기화


Wire.begin(n) 함수는 n번 슬레이브의 TWI 초기화하는 명령이며, 아이디 n을 가지는 연결된 마스터에서 기기간에 구별을 하게 됩니다.


마스터에서 슬레이브로 데이터 보낼 때는 Wire.beginTransmission(id) 함수로 아이디가 id인 슬레이브와 TWI 통신을 시작합니다. 그리고 Wire.write(byte) 함수로 byte형 데이터를 보내고, Wire.endTransmission() 함수로 통신을 끝냅니다. Wire.write() 함수는 입력 변수의 자료형에 따라 다음과 같이 3가지가 전송할 수 있습니다.


Wire.write(byte by); // byte형 데이터 하나를 전송

Wire.write(string str); // 문자열 str을 전송

Wire.write(const byte *ptrByte, int n); // byte형 포인터에 저장된 byte 배열에서 n개의 데이터를 전송


슬레이브 쪽에서는 마스터에서 데이터를 보냈을 때 호출되는 이벤트 함수를 다음과 같이 등록해 두면 데이터가 보내졌을 때 이 함수가 호출되어 처리가 가능하게 됩니다. Wire.onReceive() 함수를 이용해서 사용자가 지정한 receiveEvent() 함수를 데이터가 보내졌을 때 호출될 함수로 등록합니다.


void setup() {

    Wire.onReceive(receiveEvent);

}

....

....

void receiveEvent(int iNum) 

{

    byte by[iNum];

    for(int n=0; n<iNum; n++)

        by[n] = Wire.read();

}

위와 같이 등록된 이벤트 핸들러는 넘겨받은 바이트 수를 입력으로 건네주게 되고, Wire.read() 함수를 이용하여 by 변수에 저장하게 할 수 있습니다.


마스터가 슬레이브에서 데이터를 받을 때에도 마스터에서 먼저 요구하며 그 요구에 슬레이브가 반응하도록 되어 있는데, 마스터에서 슬레이브에 데이터를 요구하고 읽어오는 과정은 다음과 같습니다.


byte by[n], m=0;

Wire.requestFrom(id, n); // id를 가지는 슬레이브에 n 바이트를 보내기를 요구한다.

while( Wire.available() ) // 읽어올 데이터가 버퍼에 남아있다면

{

    by[m++]=Wire.read(); // 데이터를 읽는다.

}


슬레이브 측에서는 마스터에서 요구가 올 때 처리할 이벤터 핸들러를 다음과 같이 등록해 두면 됩니다. 다음 예를 마스터의 전송 요구시 호출된 사용자 지정의 requestEvent() 함수를 등록합니다. 결론적으로 마스터와 슬레이브 사이에 통신을 할 경우 마스터에서 슬레이브 쪽으로 통신을 하겠다는 요구를 먼저 하게 되어 있다는 것입니다.


Wire.onRequest( requestEvent ); 

...

...

void requestEvent()

{

    Wire.write(100); // (예를 들어) 100이라는 숫자를 전송한다.

}



'Embedded Lecture > Arduino' 카테고리의 다른 글

아날로그 입력  (0) 2017.03.18
인터럽트와 volatile 지시자  (1) 2017.03.18
아두이노의 시리얼 통신  (0) 2017.03.18
인터럽트의 처리(1)  (0) 2017.03.12
LED 깜박이기  (0) 2017.03.12
Posted by Nature & Life
Embedded Lecture/Arduino2017. 3. 18. 12:51


사람도 다른 사람과 의사소통을 위해서 말을 주고받듯이 Arduino Uno도 주변 장치와 소통을 위해서 소위 통신이라는 것을 해야 합니다. Ardunio Uno 보드가 PC나 주변 장치와 유선 통신을 하기 위해서는 지원하는 시리얼 통신(serial communication) 방법이 있습니다. '시리얼'이란 직렬로 병렬 통신과 구분되는 용어로, 병렬(parallel) 통신은 과거 PC가 프린터와 통신하던 방법으로 다수의 선을 이용하여 통신하기에 단위 시간에 많은 데이터를 주고 받을 수 있는 장점이 있습니다.


그러나 병렬 통신은 도로의 폭에 비유할 수 있는 다수의 선을 이용하기에 장치와 거리가 먼 경우에 비용이 커지게 된다는 것입니다. 하지만 최소한의 선로로 빠르게 데이터의 송수신이 가능하다면 가성비는 커지기에 근래에 시리얼 통신 방법이 보다 다양하게 사용되고 있다는 것입니다. Arduino Uno 보드에서는 몇가지 시리얼 통신 방법을 지원하는데 우선 USART를 이용하는 방법입니다. 시리얼 통신은 크게 동기와 비동기 방식으로 구분됩니다.


동기 통신이란 데이터를 전송시 클럭(clock)을 함께 전송하여 받는 쪽에서 이 클럭을 기준으로 미리 정해진 통신 규격(protocol)에 따라 데이터를 취하는 방식입니다. 따라서 데이터의 양방향 통신을 위해서 송신과 수신에 각각 한가닥의 선을 할당하고 클럭을 위한 선을 고려하면 3가닥으로 직관적인 통신이 가능하다는 것입니다. 이를 USRT(Universal Synchronous Receiver and Transmitter)라 명명합니다.


뿐만 아니라 비동기 방식이 있는데 이는 클럭이 필요없이 오직 2가닥으로만 통신하는 방법으로 선로에 대한 비용을 더 감소시킬 수 있지만 하드웨어가 복잡해지는 단점이 있습니다. 그러나 동기식 처럼 수신 쪽에서 항상 대기할 필요 없이 다른 일을 하다가 데이터의 송수신이 가능하므로 MCU의 속도가 빠르게 개선되는 요즈음 필요한 방식이라는 것입니다. 이를 UART(Universal Asynchronous Receiver and Transmitter)라 부릅니다.


USART는 USRT와 UART 방식을 모두 지칭하는 것으로 Arduino Uno 보드는 동기식과 비동기식을 모두 지원합니다. 참고로 RS232, RS485는 시리얼 통신을 위한 전기적 혹은 하드웨어의 규격을 나타내는 말입니다. 이 보드에 사용하는 UART는 주로 Arduino IDE와 같은 PC와의 통신에 사용됩니다. 0번(Rx)과 1번(Tx) 핀을 사용하며 데이터는 MCU로부터 USB 통신을 담당하는 칩을 경우하여 USB 신호로 상호 변환된 후 PC와 송수신하게 됩니다. 또한 아두이노가 PC와의 통신을 수행하고 있다면 이 핀들을 다른 용도로 사용하면 안되며, 통신을 수행할 때에는 TX, RX라고 표시된 LED가 점멸함을 확인할 수 있습니다.



UART와 관련되 아두이노의 라이브러리는 Serial 클래스를 확인하시기 바랍니다. 다음은 UART의 예제입니다. PC에서 문자 하나를 받아서 그것이 '0'이면 LED를 끄고 '1'이면 LED를 켜는 프로그램으로, 아두이노는 데이터가 사용자로부터 들어올 때까지 대기 상태로 있다가 데이터가 입력되면 수행하게 됩니다.


#define LED 13
  void setup() {
 pinMode(LED, OUTPUT);
 Serial.begin(9600);
}
void loop() {
 if ( Serial.available() ) {
   char command = Serial.read();
   if (command == '0') {
     digitalWrite(LED, LOW);
     Serial.println("LED off.");
   }
   else if (command == '1') {
     digitalWrite(LED, HIGH);
     Serial.println("LED on.");
   }
     else {
     Serial.print("Wrong command :");
     Serial.println(command);
     }
  }
}


Serial.begin(long baud_rate) 함수는 UART 통신을 초기화 시키고, 통신 속도(baud rate)를 지정합니다.


Serial.available() 함수는 수신되어 내부 버퍼(64 byte)에 저장된 데이터의 개수를 반환합니다. 만일 버퍼가 비어있다면 0을 반환합니다.


Serial.read() 함수는 수신되어 내부 버퍼(64 byte)에 저장된 데이터 중 가장 첫 번째 데이터(ASCII코드)를 읽어서 반환합니다. 이 함수가 수행되면 내부 버퍼의 크기는 하나씩 줄어들며, 내부 버퍼가 비었다면 -1을 반환합니다.


Serial.print(val) 함수는 입력값을 ASCII값으로 변환하여 PC에 출력하며, 전송된 데이터의 바이트 수를 반환합니다. 비동기 통신 방식이므로 데이터가 전송되기 전에 반환하게 됩니다. 인수 val은 어떤 데이터 타입도 가능합니다.다. 예를 들면,


Serial.print(78) -> "78"

Serial.print(1.23456) -> "1.23456"

Serial.print('N') -> "N"

Serial.print("Hello world.") -> "Hello world."


두 번째 인수로 출력 형식을 지정할 수도 있습니다. 예를 들면,


Serial.print(78, BIN) gives "1001110"

Serial.print(78, OCT) gives "116"

Serial.print(78, DEC) gives "78"

Serial.print(78, HEX) gives "4E"

Serial.println(1.23456, 0) gives "1"

Serial.println(1.23456, 2) gives "1.23"

Serial.println(1.23456, 4) gives "1.2346"


Serial.println() 함수는 출력 문자열의 끝에 줄바꿈 기호 '\r\n' 가 자동으로 붙는다는 점 외에는 Serial.print()함수와 동일한 동작을 수행합니다.


이 예제에서 내부 버퍼란 전송된 데이터가 일시적으로 저장되는 내부 메모리를 말하며 데이터가 전송된 순서대로 저장됩니다. Arduino Uno의 내부 버퍼의 크기는 64 byte인데, 수신된 데이터를 사용하려면 내부 버퍼에서 이 데이터를 읽어내야 하는데 이때 사용되는 함수가 Serial.read() 함수입니다. 가장 먼저 전송된 데이터 하나를 읽어낸 후 그 데이터는 버퍼에서 삭제되며, 만약 버퍼가 비었다면 -1을 반환합니다. 따라서 버퍼에 읽어낼 데이터가 있는지 없는지를 먼저 검사하는 것이 일반적이고 이때 사용되는 함수가 Serial.available()입니다.



'Embedded Lecture > Arduino' 카테고리의 다른 글

인터럽트와 volatile 지시자  (1) 2017.03.18
아두이노의 TWI(I2C) 통신  (0) 2017.03.18
인터럽트의 처리(1)  (0) 2017.03.12
LED 깜박이기  (0) 2017.03.12
아두이노(Arduino) 코딩의 시작  (0) 2017.03.12
Posted by Nature & Life