728x90
반응형
UART Communication

UART(Universal asynchronous recevier/transmitter)

범용 비동기화 수신기

 

병렬 데이터의 형태를 직렬 방식으로 전호나하여 데이터를 전송하는 컴퓨터 하드웨어의 일종

UART는 일반적으로 EIA RS-232, RS-422, RS-485와 같은 통신 표준과 함께 사용한다.

UART의 U는 범용을 가리키는데 이는 자료형태나 전송 속도를 직접 구성할 수 있고

실제 전기 신호 수준과 방식이 일반적으로 UART 바깥의 특정한 드라이버 회로를 통해 관리를 받는다는 뜻이다.

 

통신 데이터는 메모리 또는 레지스터에 들어 있어 이것을 차례대로 읽어 직렬화 하여 통신하다.

최대 8비트가 기본 단위 이다.

 

UART는 일반적으로 컴퓨터나 주변 기기의 일종으로 병렬 데이터를 직렬화하여 통신하는 개별 집적 회로이다.

비동기 통신 이므로 동기 신호가 전달 되지 않는다.

따라서 수신 쪽에서 동기 신호를 찾아내어 데이터의 시작과 끝을 시간적으로 알아 처리할 수 있도록 약속되어 있다.

디지털 회로는 자체의 클럭 신호를 추가로 사용하여 정해진 속도로 수신 데이터로부터 비트 구간을 구분하고

그 비트의 논리 상태를 결정하여 데이터를 통신하는 USRT(Universal Sychronous receiver/transmitter) 도 사용한다.

 

UART는 보통 마이크로컨트롤러에도 포함되어 있다.

듀얼 UART 곧, DUART는 두 개의 UART를 하나의 칩에 합친 것이다.

수많은 현대의 집적회로(IC)는 동기화 통신인 USRT도 함께 지원한다.

---

 

직렬(Serial) 통신: 1개의 입출력 핀을 통해 8개 비트를 한번에 전송하는 방법

병렬(Parallel) 통신: n 비트의 데이터를 전송하기 위해 n개의 입출력 핀을 사용하는 방법

 

MCU에서는 흔히 직렬(Serial) 통신 방식이며, 그 중 많이 쓰이는 방법 중 하나가 UART 이다.

 

시리얼 통신을 사용하기 위해서는 보내는 쪽(TX)과 받는쪽(RX)에서 약속을 정해야 하는데,

이를 프로토콜(Protocol)이라고 한다.

 

MCU에서는 0과 1의 값만을 처리할 수 있으므로 0은 GND, 1은 VCC로 데이터를 전송하고,

받는쪽에서는 GND와 VCC를 다시 0과 1의 이진값으로 변환하여 사용한다.

 

보내는 쪽(TX)과 받는 쪽(RX)이 원활하게 데이터를 교류하려면,

데이터를 보내는 속도에 대하여 약속(프로토콜, Protocol)이 정해져 있어야 한다.

 

---

UART 통신이란 Serial 통신으로, 데이터 전송 혹은 수신 핀이 하나인 통신이다.
한번에 1byte 씩 보내며, 각 bit는 차례(직렬)로 전송 된다.

MCU(ex.arduino)에서 많이 쓰이는 방식이며, 
TX(데이터를 보내는 핀)과 RX(데이터를 받는 핀)이 존재한다.

회로적으로 보았을 때, 각 bit의 0과 1은 MCU에선 GND와 VCC로 생각할 수 있으며,
전달 받은 신호를 해석하면 다시 bit에서 byte로 전환이 가능하다.

데이터를 주고 받는 것에 있어서 보내는 쪽과 받는 쪽의 데이터 전송 속도가 동일해야 한다.
이러한 통신 속돌를 baud rate라고 하며, baudrate 에는 115200, 9600 등 다양하게 존재한다.

UART는 start bit(GND, 0)으로 시작하고,
그 뒤 8개의 Data bit, 그리고 Stop bit(VCC, 1)로 끝난다.
따라서 한번에 10개의 bit를 전송하고 받는게 일반적이다.

 

데이터 송/수신 형태

 

UART 통신 구조

가장 일반적으로 각 데이터 비트의 시간에 대해 16/64배 빠른 클럭 신호를 이용하여 시작 비트로부터 세어 각 비트의 경계를 찾아낸다.

이 클럭 신호는 자체적인 내부 클럭 디지털 회로에 의해 발생한다.

보드 설정에 따라 주 클럭으로부터 타이머 등을 써서 설정한 속도의 클럭 신호를 만든다.

이것은 프로그래밍에 의한 레지스터 설정에 따라 클럭 신호의 주파수가 바뀐다.

통신 양쪽에서 설정을 미리 약속하고 클럭 신호 발생 부분부의 레지스터를 같은 속도로 설정해야 통신이 원활하게 이루어진다.

 

- 시작 비트: 통신의 시작을 의미하며 한 비트 시간 길이만큼 유지한다. 지금부터 정해진 약속에 따라 통신을 시작한다.

- 데이터 비트: 5-8비트의 데이터 전송을한다. 몇 비트를 사용할 것인지는 해당 레지스터 설정에 따라 결정된다.

- 패리티 비트: 오류 검증을 하기 위한 패리티 값을 생성하여 송신하고 수신쪽에서 오류 판단을 한다.

사용안함, 짝수, 홀수 패리티 등의 세가지 옵션으로 해당 레지스터 설정에 따라 선택할 수 있다. '사용안함'을 선택하면 이 비트가 제거된다.

- 종료 비트: 통신 종료를 알린다. 세가지의 정해진 비트만큼 유지해야 한다. 1, 1.5, 2비트로 해당 레지스터 설정에 따라 결정된다.

 

---

 

UART에서는 보내는 쪽(TX)과 받는 쪽(RX)에서 데이터를 보내는 속도를 보율(Baud rate)로 정하고 있다.

 

 

보내는 쪽(TX)과 받는 쪽(RX)이 동일한 속도로 데이터를 주고받는다고 해서 정확하게 통신이 이루어지는 것은 아니다.

 

보내는쪽(TX)은 항상 데이터를 보내는 것이 아니며, 필요한 경우에만 데이터를 보낸다.

따라서, 받는 쪽(RX)은 언제 보내는 쪽(TX)이 데이터를 보내는지,

그리고 어디서부터가 보내는 쪽(TX)에서 보낸 데이터의 시작인지 알아낼 수 있는 방법이 필요한데,

 

이를 위해서, UART에서는 '0'의 시작비트(Start bit)와 '1'의 정지 비트(Stop bit)를 사용한다.

 

UART는 바이트 단위 통신을 주로 사용하며,

시작 비트(Start bit)와 정지 비트(Stop bit)가 추가 되어, 10비트 데이터를 전송하는 것이 일반적이다.

(패리티 비트를 사용하지 않을 경우에 해당된다.)

 

 

UART 통신은 전이중 방식(full duplex)통신으로 송신과 수신을 동시에 진행할 수 있으며,

이를 위해서 2개의 범용 입출력 핀이 필요하다. (시리얼포트→ TX, RX)

예를 들어, 컴퓨터와 ATMEGA16을 연결하는 경우,

컴퓨터와의 연결에 있어서 RS232 연결을 사용하며, RS232에서 사용하는 신호 레벨은

UART의 신호레벨(TTL)과 달라 별도의 변환 장치를 사용하여 레벨을 변환시켜주어야만 사용이 가능하다.

 

Packet

UART 통신으로는 한번에 8bit

즉, 64bit 운영체제 기준 1byte만 전송된다.

하지만 실제 데이터를 보낼 때는 1byte 이상의 데이터를 전송해야 한다.

이런 다수의 데이터를 전송할 때 보내고 받는 데이터의 형식을 명시해서 주고 받는 것이다.

 

간단한 packet 구조

처음 2byte를 데이터가 시작되었다고 알리는 Header로 고정한다.

그 후 앞으로 Data + Check sum의 수를 알려주는 1 byte를 보낸 뒤 Data를 보내고 Check sum을 보낸다.

여기서 Header란 약속된 0-255 사이의 숫자이지만 check sum 은 앞의 데이터가 중간에 깨지지 않고 

잘 갔는지 확인하기 위한 byte 이다.

 

Check sum을 만드는 법은 간단하다.

앞의 데이터를 가지고 만들 규칙을 정하는 거다.

예를 들어 앞의 데이터들을 모두 XOR한다거나, 모두 더한다거나 하는 약속을 가지고 만들면 된다.

그러면 보내는 측에서 보낸 데이터를 기반으로 Check sum을 만들어서 보내고 받는 측에선 받은 데이터들을

조합해서 Check sum을 만든 후 받은 Check sum과 비교해서 데이터가 정상적으로 잘 들어왔는지 확인하며 된다.

 

UART 통신 예제

UART 통신, Serial 통신하면 가장 많이 떠오르는게 아두이노이다.

아두이노에는 TX / RX 핀이 UNO에는 1개, Mega에는 3개가 존재하며 SoftwareSerial.h 헤더를 통해 만들 수 있다.

여기서 아두이노의 0번 TX /RX 핀은 USB - B type으로 연결된 기기와

통신(업로드, 시리얼모니터, 시리얼 플로터 등)을 하는데 사용된다.

 

컴퓨터에서 아두이노 IDE를 사용하지 않고 아두이노 TX / RX 0을 통해 통신하는 방법은 간단하다.

 

https://github.com/ladianchad/communication

 

GitHub - ladianchad/communication: communcation( Serial, Socket, I2C ...) c++ pakage

communcation( Serial, Socket, I2C ...) c++ pakage. Contribute to ladianchad/communication development by creating an account on GitHub.

github.com

 

Arduino가 linux 환경의 컴퓨터와 연결되면 /dev/ttyACM8 과 같은 파일이 생성된다.

기본적으로 OS는 통신을 하던 뭘하던 간에 File Descriptor로 설명이 되기 때문에 아두이노와 통신을 하기 위해서는

이 파일을 통해야 한다. 따라서 이 파일에 대한 접근 권한이 필요한데 /dev에 들어있는 파일의 경우 관리자 권한이

필요하기 때문에 사용자 권한으로 풀어 준다.

$sudo chmod 777 /dev/ttyACM0

 

그럼 이제 통신을 위한 코드를 다음과 같이 작성해 보자.

/* Arduino IDE */

int num;

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  if(Serial.available())
  {
    num = Serial.parseInt();
    Serial.print(num * 2);
  }
}
// Linux C++

#include "communication/Serial.cpp"
#include "iostream"

using namespace std;

int main(int argc, char** argv)
{
  Serial.my_serial("dev/ttyACM0", 9600);  // port & baudrate
  char buf = '\0'; // NULL

  while(buf != 'q')
  {
    cout <<"Input Nuber(Quit is 'q') : ");
    cin >> buf;
    if(my_serial.write(&buf))
    {
      cout <<"Send data." << endl;
    }
    else
    {
      cout <<"Send Error"<< endl;
    }

    if(my_serial.read(&buf, sizeof(char)))
    {
      cout<<"Received data." << (int)buf << endl;
    }
    else
    {
      cout << "Receive Error." << endl;
    }
  }

  return 0;
}

 

현재는 1byte의 데이터만 주고 받는 것이므로 256이상의 데이터를 주고 받는 것은 불가능하다.

패킷을 사용한 예시는 다음과 같다.

 

/* Arduino IDE */

#define HEADER 0xFF
#define DATA_LEN 6

int num = 0;
char send_buf[10];
char receive_buf[10];

send_buf[0] = HEADER;
send_buf[1] = HEADER;
send_buf[2] = DATA_LEN;

void setup()
{
	Serial.begin(9600);
}

void loop()
{
	num++;
	send_packet();
}

void send_packet()
{
	send_buf[3] = (num * 2 >> 8) & 0x00FF; // int upper bit
	send_buf[4] = (num * 2) & 0x00FF;      // int lower bit

	for (int i = 0; i < DATA_LEN - 1; i++)
	{
		send_buf[5] ^= send_buf[i];
	}
	Serial.write(send_buf, DATA_LEN)
}
// Linux C++

#include "Serial.cpp"
#include <iostream>
#define HEADER 0xFF

using namespace std;
int main(int argc, char** argv)
{
	Serial my_serial("/dev/ttyACMO", 9600); // port & baudrate
	char buf[10];
	bool header = false;
	char* buf_pointer = buf;
	int buf_inde = 0;

	while (true)
	{
		if (my_serial.sread(buf_pointer, sizeof(char))
		{
			if (!header)
			{
				if (buf_index == 0)
				{
					if (*buf_pointer == HEADER)
					{
						buf_index++;
						buf_pointer++;
					}
				}

				else if (buf_index == 1)
				{
					if (*buf_pointer == HEADER)
					{
						buf_index++;
						buf_pointer++;
						header = true;
					}
					else
					{
						buf_pointer = buf;
						buf_index = 0;
					}
				}
			}
			else
			{
				buf_index++;
				buf_pointer++;
				
				if (buf_index == buf[3])
				{
					char check_sum;
					
					for (int i = 0; i < buf_index - 1; i++)	// Check sum "check"
					{
						check_sum ^= buf[i];
					}
					
					if (check_sum == buf[buf_index - 1]) // Rebuild data
					{
						int Data = buf[4] << 8 + buf[5];
						cout << "Data :" << Data << endl;
					}
					else
					{
						cout << "Data loss." << endl;
					}
				}
			}
		}
	}

	return 0;
}

 

위와 같이 아두이노와 TX / RX 통신하는 법을 하였는데 여러 센서들을 사용하다 보면 센서 Data 출력이

UART 통신으로 나오는 경우가 있다.

이때 컴퓨터와 바로 연결해서 통신 값을 받아보기 위해서는

USB to TTL 레벨 컨버터가 필요하다. 이를 사용해서 연결하면 위의 linux cpp 코드로 센서와 통신이 가능하다.

 

 

 

 

 

 

 

728x90
반응형

'Network' 카테고리의 다른 글

TCP / IP (Socket programming)  (0) 2022.12.01
HTTP Protocol  (0) 2022.12.01
Protocol  (0) 2022.12.01
Serial Communication  (0) 2022.12.01
SSID  (0) 2022.11.25

+ Recent posts