UART(Universal Asynchronous Receiver-Transmitter)는 데이터 전송에 널리 사용되는 시리얼 통신 프로토콜입니다. 간단한 구조와 효율성 덕분에 임베디드 시스템과 같은 다양한 환경에서 필수적인 기술로 자리 잡고 있습니다. 이 기사에서는 C 언어를 사용해 UART 통신을 구현하는 방법과 이를 활용한 데이터 송수신 실습을 통해, UART 통신의 기본 원리와 실무 활용 방안을 알아봅니다.
UART와 시리얼 통신의 개념
UART는 비동기식 시리얼 통신을 담당하는 하드웨어 모듈로, 데이터를 직렬로 전송하며 전송 속도와 데이터 형식을 사전에 설정해야 합니다. 시리얼 통신은 데이터를 한 비트씩 전송하며, 간단한 연결로 양방향 데이터 전송이 가능합니다.
UART의 동작 원리
UART는 송신 측에서 병렬 데이터를 직렬 데이터로 변환한 뒤 전송하며, 수신 측에서는 이를 다시 병렬 데이터로 변환합니다. 동기 신호 없이도 동작하므로 클럭 신호가 필요하지 않지만, 송신자와 수신자가 동일한 통신 속도(baud rate)를 설정해야 합니다.
UART의 주요 특징
- 단순성: 하드웨어 요구 사항이 적고, 두 개의 핀(TX, RX)만으로 데이터 전송 가능
- 효율성: 저속 통신에서 간단하고 안정적으로 작동
- 범용성: 임베디드 시스템, 컴퓨터, 센서 등 다양한 장치에서 지원
UART는 간단한 설계와 범용성 덕분에 임베디드 시스템과 컴퓨터 주변 기기에서 널리 사용됩니다.
UART의 주요 구성 요소
UART는 하드웨어와 소프트웨어적으로 구성되며, 각 구성 요소는 데이터 송수신에 중요한 역할을 합니다.
하드웨어 구성 요소
TX(Transmit)
송신 핀으로, 데이터를 직렬로 전송합니다. 송신된 데이터는 수신 측의 RX로 전달됩니다.
RX(Receive)
수신 핀으로, 송신 측에서 보내온 직렬 데이터를 수신합니다.
클럭 회로
UART 모듈 내부에서 데이터 전송 속도를 제어하기 위한 클럭을 생성합니다. 송신자와 수신자의 클럭 속도가 동일해야 데이터가 올바르게 전송됩니다.
전압 변환 회로
UART 신호는 TTL(Transistor-Transistor Logic) 레벨(3.3V 또는 5V)이나 RS-232 표준(-12V~+12V)으로 전송되며, 장치 간 호환성을 위해 전압 변환이 필요합니다.
소프트웨어 구성 요소
Baud Rate 설정
송수신 속도를 정의하며, 양쪽 장치에서 동일하게 설정해야 합니다. 일반적인 baud rate는 9600, 115200 등입니다.
프레임 포맷 설정
- 데이터 비트: 데이터의 비트 수(일반적으로 8비트)
- 패리티 비트: 오류 검출을 위한 추가 비트
- 정지 비트: 데이터 프레임의 종료를 나타냄
버퍼 관리
UART 통신에서는 하드웨어 또는 소프트웨어 버퍼를 사용하여 데이터의 송수신을 관리합니다.
UART 구성의 중요성
각 구성 요소가 올바르게 설정되어야만 안정적이고 정확한 데이터 전송이 가능합니다. 송수신 장치 간 설정이 불일치하면 데이터 손실이나 왜곡이 발생할 수 있습니다.
C 언어에서 UART 설정
UART 통신을 시작하려면 특정 설정이 필요하며, 이를 C 언어로 구현할 수 있습니다. 주요 설정 항목은 Baud Rate, 데이터 비트, 패리티 비트, 정지 비트 등입니다.
UART 설정을 위한 주요 파라미터
Baud Rate
데이터 전송 속도로, 송신기와 수신기가 동일한 값으로 설정되어야 합니다.
데이터 비트
전송되는 데이터의 비트 수를 설정합니다. 일반적으로 8비트를 사용합니다.
패리티 비트
오류 검출을 위해 추가로 사용되는 비트로, 없거나(Odd/Even)로 설정할 수 있습니다.
정지 비트
데이터 프레임의 끝을 나타내는 비트로, 1비트 또는 2비트로 설정 가능합니다.
C 코드로 UART 설정하기
아래는 UART를 설정하고 초기화하는 C 언어 코드 예제입니다.
#include <avr/io.h> // AVR MCU를 사용하는 경우
void UART_init(unsigned int baud_rate) {
// Baud Rate 계산
unsigned int ubrr = F_CPU / 16 / baud_rate - 1;
// Baud Rate 설정
UBRR0H = (unsigned char)(ubrr >> 8); // 상위 비트
UBRR0L = (unsigned char)ubrr; // 하위 비트
// 송신과 수신 활성화
UCSR0B = (1 << RXEN0) | (1 << TXEN0);
// 데이터 프레임 설정: 8비트 데이터, 1비트 정지
UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}
설정 설명
UBRR0H
,UBRR0L
: Baud Rate 레지스터로, 통신 속도를 설정합니다.RXEN0
,TXEN0
: UART의 송수신 기능을 활성화합니다.UCSR0C
: 데이터 프레임 구조를 설정합니다(데이터 비트, 패리티, 정지 비트).
UART 초기화의 중요성
올바른 설정은 데이터 전송의 신뢰성을 보장하며, 잘못된 초기화는 데이터 손실이나 통신 장애를 유발할 수 있습니다. UART 초기화 코드는 플랫폼에 따라 다르므로 데이터 시트를 반드시 참고해야 합니다.
송신 데이터와 수신 데이터 처리
UART 통신에서 데이터 송수신은 송신기(TX)와 수신기(RX)를 통해 이루어지며, C 언어로 이를 효율적으로 구현할 수 있습니다. 데이터 송수신 코드를 작성하기 위해 송신과 수신의 동작 원리를 이해하는 것이 중요합니다.
UART 송신 데이터 처리
송신 데이터는 버퍼에 쓰여진 후 직렬로 전송됩니다. 송신이 완료되었는지 확인하는 플래그를 사용하여 프로세스를 제어합니다.
void UART_transmit(unsigned char data) {
// 송신 가능 플래그 대기
while (!(UCSR0A & (1 << UDRE0))); // 송신 버퍼가 비었는지 확인
// 데이터 레지스터에 데이터 쓰기
UDR0 = data;
}
코드 설명
UCSR0A
: 송신 상태를 나타내는 레지스터입니다.UDRE0
: 송신 버퍼가 비었음을 나타내는 플래그입니다.UDR0
: 데이터 송신 레지스터로, 송신할 데이터를 여기에 씁니다.
UART 수신 데이터 처리
수신 데이터는 수신 버퍼에 저장되며, 데이터가 들어오면 이를 읽어올 수 있습니다.
unsigned char UART_receive(void) {
// 수신 완료 플래그 대기
while (!(UCSR0A & (1 << RXC0))); // 데이터 수신 완료 확인
// 수신된 데이터 반환
return UDR0;
}
코드 설명
RXC0
: 데이터 수신 완료 플래그로, 수신 버퍼에 데이터가 들어왔는지 확인합니다.UDR0
: 수신된 데이터가 저장된 레지스터로, 데이터를 읽습니다.
송수신 구현 시 고려사항
- 오버런 오류(Overrun Error): 수신 버퍼가 꽉 찬 상태에서 새로운 데이터가 들어오면 이전 데이터가 손실될 수 있습니다.
- 프레임 오류(Frame Error): 잘못된 프레임 구조로 인해 데이터가 손상될 수 있습니다.
- 속도 일치: 송신기와 수신기의 Baud Rate가 일치하지 않으면 데이터 손실이나 왜곡이 발생합니다.
예제: 문자 송수신
아래는 문자 데이터를 송신하고 수신하는 예제입니다.
int main(void) {
UART_init(9600); // Baud Rate 9600으로 초기화
// "A" 문자 송신
UART_transmit('A');
// 수신된 문자 읽기
unsigned char received = UART_receive();
while (1) {
// 수신된 문자 재송신
UART_transmit(received);
}
}
요약
C 언어로 UART 송수신을 구현할 때는 송신 플래그와 수신 플래그를 적절히 활용해 데이터 처리를 효율적으로 수행할 수 있습니다. 안정적인 데이터 통신을 위해 오류 처리와 속도 동기화 설정을 반드시 고려해야 합니다.
실습: 간단한 UART 데이터 전송
이 실습에서는 C 언어를 활용하여 UART를 통해 텍스트 데이터를 송수신하는 간단한 예제를 구현합니다. 이 과정은 UART 통신의 기본 개념을 이해하고 실습을 통해 응용할 수 있도록 돕습니다.
실습 환경 설정
- 하드웨어 준비:
- UART를 지원하는 마이크로컨트롤러(예: AVR, STM32 등)
- USB-UART 변환기 또는 UART 모듈
- PC에서 UART 데이터를 확인하기 위한 시리얼 모니터 소프트웨어(예: Tera Term, PuTTY 등)
- 소프트웨어 요구 사항:
- 마이크로컨트롤러 개발 환경(예: Atmel Studio, STM32CubeIDE 등)
- C 컴파일러
UART 송수신 코드
다음은 UART를 통해 데이터를 송수신하는 간단한 C 코드입니다.
#include <avr/io.h>
#include <util/delay.h>
void UART_init(unsigned int baud_rate) {
unsigned int ubrr = F_CPU / 16 / baud_rate - 1;
// Baud Rate 설정
UBRR0H = (unsigned char)(ubrr >> 8);
UBRR0L = (unsigned char)ubrr;
// 송신과 수신 활성화
UCSR0B = (1 << RXEN0) | (1 << TXEN0);
// 데이터 프레임: 8비트 데이터, 1비트 정지
UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}
void UART_transmit(unsigned char data) {
while (!(UCSR0A & (1 << UDRE0))); // 송신 버퍼가 비었는지 확인
UDR0 = data; // 데이터 전송
}
unsigned char UART_receive(void) {
while (!(UCSR0A & (1 << RXC0))); // 데이터 수신 완료 대기
return UDR0; // 수신 데이터 반환
}
int main(void) {
UART_init(9600); // UART 초기화 (Baud Rate: 9600)
while (1) {
unsigned char received = UART_receive(); // 데이터 수신
UART_transmit(received); // 수신된 데이터 재송신
}
return 0;
}
실행 방법
- 코드 작성 및 컴파일:
위 코드를 개발 환경에 작성하고 컴파일합니다. - 마이크로컨트롤러 업로드:
컴파일된 바이너리를 마이크로컨트롤러에 업로드합니다. - 시리얼 모니터 실행:
PC에서 시리얼 모니터를 열고 Baud Rate를9600
으로 설정합니다. - 데이터 전송 및 확인:
시리얼 모니터에서 데이터를 입력하면, 입력된 데이터가 그대로 되돌아옵니다(에코 기능).
결과 확인
시리얼 모니터에 “Hello”를 입력하면, “Hello”가 다시 출력됩니다. 이는 데이터 송수신이 정상적으로 이루어졌음을 보여줍니다.
응용 팁
- 송신 데이터를 변환하거나 처리하려면
UART_receive
와UART_transmit
사이에 로직을 추가할 수 있습니다. - 데이터 전송 형식을 확장하여 문자열이나 바이너리 데이터를 송수신할 수도 있습니다.
요약
이 실습을 통해 UART 초기화, 데이터 송수신, 시리얼 모니터 사용법을 익힐 수 있습니다. 이러한 기본 과정을 이해하면 임베디드 시스템에서 복잡한 통신 응용도 구현할 수 있습니다.
오류 처리 및 디버깅
UART 통신 중 발생할 수 있는 다양한 오류를 처리하고, 문제를 디버깅하는 것은 안정적인 통신을 위해 필수적입니다. 이 섹션에서는 UART 통신에서 발생하는 주요 오류와 이를 해결하는 방법을 설명합니다.
주요 UART 통신 오류
1. 오버런 오류 (Overrun Error)
- 원인: 수신 버퍼가 가득 찬 상태에서 새로운 데이터가 수신되었을 때 발생합니다.
- 해결 방법:
- 수신 데이터를 빠르게 읽어오도록 설계합니다.
- 더 큰 버퍼를 사용하거나, 인터럽트를 활용하여 데이터를 실시간으로 처리합니다.
2. 프레임 오류 (Frame Error)
- 원인: 데이터 프레임의 종료 비트(Stop Bit)가 예상과 일치하지 않을 때 발생합니다.
- 해결 방법:
- Baud Rate 및 데이터 비트 설정을 송수신 장치 간에 일치시킵니다.
- 신호 간섭이나 노이즈를 줄이기 위해 케이블 품질을 개선합니다.
3. 패리티 오류 (Parity Error)
- 원인: 패리티 비트를 사용하여 전송된 데이터의 오류를 검출할 때, 패리티 값이 일치하지 않을 경우 발생합니다.
- 해결 방법:
- 송수신 장치의 패리티 설정(Odd/Even/None)을 동일하게 맞춥니다.
- 패리티 검사를 비활성화할 경우, 데이터 무결성 검증이 줄어들 수 있음을 유의합니다.
4. 데이터 손실
- 원인: 송신 속도가 수신 속도보다 빠르거나, 처리 속도가 느릴 때 발생합니다.
- 해결 방법:
- 소프트웨어 또는 하드웨어 버퍼를 사용해 데이터를 일시적으로 저장합니다.
- 통신 속도를 적절히 조정합니다.
UART 디버깅 방법
1. 시리얼 모니터 사용
- PC의 시리얼 모니터를 사용하여 송신 데이터와 수신 데이터를 실시간으로 확인합니다.
- 데이터가 정상적으로 송수신되는지 검증할 수 있습니다.
2. 상태 레지스터 확인
- UART 상태 레지스터를 읽어 오류 플래그를 확인합니다. 예:
if (UCSR0A & (1 << FE0)) {
// 프레임 오류 발생
}
if (UCSR0A & (1 << DOR0)) {
// 오버런 오류 발생
}
if (UCSR0A & (1 << UPE0)) {
// 패리티 오류 발생
}
3. 인터럽트 활용
- 인터럽트를 설정하여 실시간으로 오류를 감지하고 처리할 수 있습니다.
- 데이터 누락을 방지하기 위해 송수신 시 인터럽트를 사용하는 것이 효과적입니다.
4. 로직 애널라이저 또는 오실로스코프 사용
- 로직 애널라이저나 오실로스코프를 사용하여 TX와 RX 핀의 신호를 분석합니다.
- 데이터의 정확성과 전송 타이밍을 시각적으로 확인할 수 있습니다.
코드 예제: 오류 플래그 확인
void UART_checkErrors(void) {
if (UCSR0A & (1 << FE0)) {
// 프레임 오류 처리
UART_transmit('F'); // 디버깅용 메시지
}
if (UCSR0A & (1 << DOR0)) {
// 오버런 오류 처리
UART_transmit('O');
}
if (UCSR0A & (1 << UPE0)) {
// 패리티 오류 처리
UART_transmit('P');
}
}
결론
UART 통신의 오류를 효과적으로 처리하고, 디버깅 도구를 활용하여 문제를 해결하는 것은 안정적인 데이터 전송을 보장합니다. 이러한 과정은 하드웨어와 소프트웨어 간의 호환성을 강화하며, 시스템의 신뢰성을 높이는 데 기여합니다.
고급 기능: 인터럽트를 활용한 UART 통신
UART 통신에서 인터럽트를 활용하면 효율적이고 안정적인 데이터 송수신을 구현할 수 있습니다. 인터럽트를 사용하면 CPU가 송수신 완료를 기다리지 않아도 되므로, 실시간 처리가 요구되는 시스템에서 매우 유용합니다.
인터럽트 기반 UART의 개념
인터럽트는 특정 이벤트(예: 데이터 송신 완료 또는 수신 완료)가 발생했을 때 CPU에 이를 알리고, 즉시 해당 이벤트를 처리할 수 있도록 도와줍니다. UART 통신에서는 다음 두 가지 주요 인터럽트가 사용됩니다.
- 송신 완료 인터럽트(TX Complete Interrupt): 데이터 송신이 완료되었을 때 발생합니다.
- 수신 완료 인터럽트(RX Complete Interrupt): 데이터 수신이 완료되었을 때 발생합니다.
UART 인터럽트 활성화
UART 인터럽트를 활성화하려면 다음 설정이 필요합니다.
- 송신 및 수신 인터럽트 활성화
- 전역 인터럽트 활성화
코드 예제: 인터럽트 초기화
#include <avr/io.h>
#include <avr/interrupt.h>
void UART_init(unsigned int baud_rate) {
unsigned int ubrr = F_CPU / 16 / baud_rate - 1;
// Baud Rate 설정
UBRR0H = (unsigned char)(ubrr >> 8);
UBRR0L = (unsigned char)ubrr;
// 송신, 수신 및 인터럽트 활성화
UCSR0B = (1 << RXEN0) | (1 << TXEN0) | (1 << RXCIE0);
// 데이터 프레임: 8비트 데이터, 1비트 정지
UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
// 전역 인터럽트 활성화
sei();
}
수신 및 송신 인터럽트 처리
1. 수신 인터럽트 처리
수신 인터럽트는 새로운 데이터가 수신되었을 때 호출됩니다. 데이터를 버퍼에 저장하거나 즉시 처리할 수 있습니다.
volatile unsigned char received_data;
ISR(USART_RX_vect) {
received_data = UDR0; // 수신된 데이터 읽기
}
2. 송신 인터럽트 처리
송신 인터럽트는 데이터 전송이 완료된 후 호출됩니다. 송신 대기 중인 데이터를 계속 전송하거나 송신을 종료할 수 있습니다.
ISR(USART_UDRE_vect) {
// 필요한 경우 송신 데이터를 추가 처리
UDR0 = 'A'; // 예: 문자 'A'를 송신
}
예제: 인터럽트를 활용한 에코 프로그램
수신된 데이터를 인터럽트를 통해 처리하고, 이를 송신하는 간단한 에코 프로그램입니다.
#include <avr/io.h>
#include <avr/interrupt.h>
volatile unsigned char received_data;
void UART_init(unsigned int baud_rate) {
unsigned int ubrr = F_CPU / 16 / baud_rate - 1;
UBRR0H = (unsigned char)(ubrr >> 8);
UBRR0L = (unsigned char)ubrr;
UCSR0B = (1 << RXEN0) | (1 << TXEN0) | (1 << RXCIE0);
UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
sei(); // 전역 인터럽트 활성화
}
ISR(USART_RX_vect) {
received_data = UDR0; // 수신 데이터 저장
UDR0 = received_data; // 에코 송신
}
int main(void) {
UART_init(9600); // Baud Rate 9600으로 초기화
while (1) {
// 메인 루프는 다른 작업 수행 가능
}
}
인터럽트 활용의 장점
- CPU 사용 효율성: 메인 루프가 다른 작업을 수행할 수 있음.
- 빠른 반응 속도: 데이터가 수신되거나 송신이 완료되었을 때 즉시 처리 가능.
- 복잡한 데이터 처리: 송수신 데이터의 실시간 처리에 적합.
주의 사항
- 인터럽트 처리 함수는 가능한 한 짧게 유지해야 CPU 사용률을 최적화할 수 있습니다.
- 다중 버퍼를 사용하거나, 버퍼 오버플로우를 방지하기 위한 추가 처리가 필요할 수 있습니다.
결론
인터럽트를 활용한 UART 통신은 실시간 처리가 필요한 시스템에서 강력한 솔루션을 제공합니다. CPU 자원을 효율적으로 활용하면서도 안정적이고 신뢰성 높은 통신을 구현할 수 있습니다.
UART 통신의 응용 예제
UART는 간단한 데이터 송수신뿐만 아니라 다양한 임베디드 시스템에서 실질적인 응용이 가능합니다. 이번 섹션에서는 UART 통신의 실제 응용 사례를 통해 실무에서 이를 활용하는 방법을 소개합니다.
1. 센서 데이터 수집 및 전송
UART를 사용하여 센서 데이터를 수집하고 전송하는 시스템은 IoT 및 임베디드 환경에서 일반적입니다. 예를 들어, 온도 및 습도 센서 데이터를 UART를 통해 마이크로컨트롤러로 전송하고, 이를 PC 또는 클라우드 서버로 송신할 수 있습니다.
센서 데이터를 UART로 전송하는 예제
#include <avr/io.h>
void sendSensorData(int sensor_value) {
char buffer[10];
sprintf(buffer, "Value: %d\r\n", sensor_value);
for (int i = 0; buffer[i] != '\0'; i++) {
UART_transmit(buffer[i]); // 문자 단위로 송신
}
}
2. 장치 간 통신
UART는 마이크로컨트롤러 간 통신, 모듈 간 데이터 교환에 적합합니다. 예를 들어, GPS 모듈, GSM 모듈 등과의 통신은 대부분 UART를 통해 이루어집니다.
GPS 데이터를 UART로 수신
void receiveGPSData() {
char gps_data[100];
int i = 0;
while (1) {
char received = UART_receive(); // 데이터 수신
if (received == '\n') break; // 데이터 끝 확인
gps_data[i++] = received;
}
gps_data[i] = '\0';
// 수신한 GPS 데이터를 처리
UART_transmit('G'); // 확인용 송신
}
3. 디버깅 및 로깅
UART는 디버깅 메시지를 출력하거나 시스템 상태를 로깅하는 데 유용합니다. 특히, 실시간으로 데이터 상태를 확인할 수 있어 문제 해결에 도움을 줍니다.
디버깅 메시지 출력
void debugMessage(const char* message) {
for (int i = 0; message[i] != '\0'; i++) {
UART_transmit(message[i]); // 메시지 송신
}
UART_transmit('\r');
UART_transmit('\n');
}
4. 블루투스 통신
UART는 블루투스 모듈과 통신하는 데 자주 사용됩니다. 예를 들어, HC-05 또는 HC-06 블루투스 모듈을 통해 스마트폰과 데이터를 송수신할 수 있습니다.
블루투스 데이터 전송 예제
void sendBluetoothCommand(const char* command) {
for (int i = 0; command[i] != '\0'; i++) {
UART_transmit(command[i]); // 명령 송신
}
UART_transmit('\r'); // 명령 종료
UART_transmit('\n');
}
5. 펌웨어 업데이트
UART는 펌웨어를 업데이트하는 프로세스에서도 활용됩니다. 이를 통해 디바이스가 USB나 네트워크 연결 없이 간단히 업데이트할 수 있습니다.
응용 사례 요약
- IoT 디바이스: 센서 데이터 수집 및 전송
- 모듈 통합: GPS, GSM, 블루투스와 같은 모듈 간 통신
- 시스템 모니터링: 디버깅 및 실시간 상태 확인
- 펌웨어 관리: UART를 활용한 업데이트
결론
UART는 다양한 임베디드 환경에서 필수적인 통신 기술입니다. 이 섹션에서 다룬 사례를 기반으로, 사용자는 자신의 프로젝트에 적합한 응용 방식을 설계할 수 있습니다. UART는 간단하면서도 강력한 기능을 제공하며, 하드웨어와 소프트웨어의 결합을 효율적으로 지원합니다.
요약
본 기사에서는 UART 통신의 기본 개념부터 C 언어를 활용한 설정 및 데이터 송수신 방법, 인터럽트를 사용한 고급 구현, 다양한 응용 사례까지 자세히 다루었습니다. UART는 센서 데이터 수집, 장치 간 통신, 디버깅, 블루투스 연결, 펌웨어 업데이트 등 다양한 임베디드 환경에서 필수적인 역할을 합니다.
C 언어로 UART를 구현함으로써 안정적인 통신과 시스템 효율성을 확보할 수 있으며, 이를 통해 복잡한 프로젝트에서도 효과적으로 활용할 수 있습니다. UART 통신은 실무와 학습 모두에서 강력한 도구가 될 것입니다.