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. 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