본문 바로가기
전자공학/마이크로프로세서

[atmega1281] 실험 10. AVR의 UART를 사용한 비동기 직렬 통신

by TSpoons 2024. 12. 2.

실험 목적

- 비동기 직렬 통신의 원리를 이해한다. 

- ATmega1281의 UART의 동작 모드를 이해하고 사용법을 배운다. 

- UART를 사용하여 ATmega1281과 PC가 통신을 할 수 있도록 프로그래밍하는 방법을 익힌다.

 

실험 예비 과제

1. uart.c라는 새로운 파일을 만들어 다음에 설명하는 함수들을 추가하여라.

2. . USART0를 사용하여 BAUDRATE라는 이름으로 define한 속도로 직렬 통신을 하려고 한다. 다음은 이를 위한 초기화 작업을 하는 함수이다. 내용을 완성하여라.

void uart_init(void) 
{ 
    #define BAUDRATE    115200L 
     
    // UBRR0 값을 설정한다. (U2X0 == 1로 가정) 
    // U2X0를 1로 설정하고 
    // 송수신 데이터는 각각 8 비트, stop 비트는 1 비트, no parity 
    // 송신과 수신 모두 enable하도록 
    // UCSR0A, UCSR0B, UCSR0C 레지스터 값을 설정한다. 
}

 

3. 아래의 함수는 AVR UART가 데이터를 수신할 때까지 기다린 다음 수신한 값을 읽어 반환하는 함수이다.

int8_t uart_getch(void) 
{ 
    // 수신 버퍼가 찰 때까지 기다린다. 
    // 수신한 값을 return한다. 
}

 

UDR(Receive)에 모두 데이터가 차게 되면 읽게 되고, 그 때 RXC0clear(0)가 된다.

그리고 그때의 UDR 값을 return하면 uart_init에서 초기화 했던 대로 8bit 데이터가 출력된다.

 

4. 다음 함수는 한 바이트 길이의 값을 인자로 받아서 이 값을 UART를 통해 송신하기 위한 함수이다. 내용을 완성하여라.

void uart_putch(int8_t ch) 
{ 
    // 송신 버퍼가 빌 때까지 기다린다. 
    // 인자로 받은 ch 값을 송신한다. 
}

 

Data register empty PIN(UDRE)0이 될 때까지 기다리고 있다가 0이 되면 다시 UDR에 값을 넣는다.

 

 

5. 다음 함수는 문자열이 저장된 메모리 공간의 시작 주소를 인자로 받아서 그 문자열의 내용을 UART로 출력하는 일을 한다. (   )를 채워라. 

void uart_puts(int8_t *str) 
{ 
	while(   ) 
		uart_putch(   ); 
}

 

1) 문자열의 끝을 나타내는 NULL 문자('\0')를 기준으로 종료 조건을 설정하고,

2) 포인터의 후위 증가 연산자(*str++)를 사용해 현재 문자를 uart_putch로 출력한 뒤,

3) 자동으로 다음 메모리 주소로 이동하여 다음 문자를 처리하는 방식으로 동작

 

6. 8 비트 값을 인자로 받아서 그 값에 대응되는 ASCII 코드를 차례로 UART로 전송하는 함수 num2ascii()를 작성하려고 한다. 예를 들어 num2ascii(0x3f)을 실행하면 이 함수는 UART ‘3’ ‘F’를 차례로 전송해야 한다. 여기에서 ‘3’은 숫자 3이 아니라 문자 3 ASCII 코드이다.

AVR8bit MCU이므로 ASCII 코드에 해당하는 7bit를 두 개 표현할 수 있다. 임시로 함수 내에서 저장할 tmp를 선언하고, tmp의 상위 4bit의 값을 기준으로 0~9 인지 A~F인지를 판별하고, 1byte 단위로 값을 전달하므로 아스키 코드 값을 고려하여 표현했다. 두 번째는 하위 4bit를 해당하는 아스키 코드를 uart를 통해 반환하도록 하였다.

7. uart.c에서 정의한 각 함수를 다른 파일에서 호출하려면 각 함수의 원형을 선언한 헤더 파일이 필요하다. uart.h라는 이름의 헤더 파일을 만들고 uart.c에서 정의한 함수들의 원형을 선언하여라. (생략)

 

 

 


실험 과정

1. 직렬 포트를 이용해 PC와 통신을 하기 위해서는 PC에서 실행되는 통신용 터미널 프로그램이 필요하다. 터미널 프로그램을 사용하려면 직렬 통신 속도와 사용할 직렬 포트 번호 등의 동작 환경을 미리 정확하게 설정해야 한다. 어떤 종류의 터미널 프로그램을 사용해도 상관없지만 통신 파라미터는 아래와 같이 설정해야 한다.

 

2. 새로운 project를 만들고 필요한 파일과 코드를 모두 추가하여라. 또한 uart_init()을 포함하여 필요한 초기화 함수를 모두 호출하여라. 초기화 작업을 마친 후 while() 루프에 추가한 후 실행시켜 보고 그 결과를 설명하여라. 물론 main() 함수에서 변수 ch를 미리 선언해야 한다.

 

3.2 번의 코드에서 uart_putch(ch) 대신 uart_putch(ch+1)을 대입해서 실행시켜 보고 그 결과를 설명하여라.

 

2번과 다르게 아스키코드가 1 더한 값이 대응되어 출력 되므로 입력: a -> 출력: b 식으로 나온다.

 

 

4. 다시 2 번의 코드로 복원한 후 터미널에서 엔터키를 치면 어떤 현상이 나타나는지 살펴보아라. 엔터키를칠 때 RXD0 핀 (PE0)으로 수신되는 신호를 오실로스코우프로 포착하고 수신되는 값의 ASCII 코드를 찾아라. start bit을 찾기 위해서는 트리거의 경사를 하강 에지로 선택하고 트리거 모드를 ‘Normal’로 두면 된다.일단 파형을 포착한 다음에는 cursor를 x1과 x2 사이의 거리를 1/baud rate로 설정하고 두 커서 사이의 거리가 고정된 채 함께 움직이는 모드로 설정하고 옆으로 한 비트씩 옮겨가면 각 비트의 값을 쉽게 파악할수 있다. ASCII 문자 표에서 이 ASCII 값에 대응되는 문자가 무엇인지 찾아보아라.

 

 

UART로 전송되는 프레임은 다음과 같아서 0이 될 때, start bit이고, 1은 stop bit이다.

 

- 커서를 이용해서 구간의 신호의 주기를 파악 후 샘플링 과정을 거치면 다음과 같다.

0101100001 -> 0001101 = 13- vertical tap

 

 

5. 4 번에서 구한 값을 사용하여 사용자가 엔터 키를 쳤을 때에는 커서가 다음 줄 첫 번째 칸으로 이동하도록
2 번의 코드를 수정하고 그 내용을 설명하여라.

 

 

6. 사용자가 엔터 키를 치면 커서가 다음 줄 첫 번째 칸으로 이동한 후>> UCSR0B: 0x??[줄바꿈] 와 같은 내용을 출력하도록 5 번의 코드에 필요한 내용을 추가하여라. 즉 키보드에서 엔터를 치면 줄을 바꾼 다음 모니터 면 “>> UCSR0B: 0x”란 문자열을 출력하고 그 다음에 UCSR0B 레지스터의 값을 출력한 다음 다시 줄을 바꾸어 커서를 새 줄의 맨 앞에 위치시켜야 한다. 예비 과제에서 작성한 uart_puts() 함수와 num2ascii() 함수를 사용하면 된다.

 

 

7. 모니터라는 표준 출력 장치를 가진 PC와 달리 임베디드 시스템에서 printf() 함수를 사용하려면 어떤디바이스에 어떤 방식으로 내용을 출력해야 하는지 미리 설정해 주어야 한다. PC는 제조 업체와 상관없이 인터페이스가 표준화되어 있지만 임베디드 시스템은 그렇지 않기 때문이다. printf()와 scanf() 함수가 값을 출력하거나 값을 입력 받을 때 각각 uart_putch() 함수와 uart_getch() 함수를 사용하도록 쉽 게 등록할 수 있다. main() 함수에서 각 주변 장치를 초기화하는 곳에 fdevopen(uart_putch, uart_getch); 명령을 추가하여라. 이 함수를 호출하면 표준 출력과 표준 입력이 UART를 사용하도록 설정된다. 이 명령을 실행한 후에는 printf()와 scanf() 함수를 PC에서 사용하던 것과 같은 방법으로 사용할 수 있다. (한 가지 차이가 있다면 scanf() 함수를 실행시켰을 때 키보드로 입력한 내용이 화면에 표시되지는 않는다.) printf() 함수를 사용할 수 있다면 예비 과제에서 작성한 num2ascii() 함수는 더 이상 필요 없어진다. 6 번에서 추가한 내용을 printf() 함수를 사용하여 수정하고 내용을 설명하여라. printf(), scanf(), fdevopen() 함수의 원형은 모두 stdio.h에 선언되어 있으므로 이 헤더 파일을 include해야 한다.

 

8. 이제 UART 수신 기능을 인터럽트로 처리해 보자. 이를 위하여 USART0의 수신 완료 인터럽트를 활성화시키도록 uart_init() 함수를 수정하여라. 그리고 USART0의 수신 완료 인터럽트의 ISR을 파일에 추가하여라. USART0의 수신 완료 인터럽트를 사용하면 더 이상 uart_getch() 함수를 사용해서는 안 된다. 그 기능이 겹치기 때문이다. 2 번 코드의 while() 루프 내용을 모두 지우고 이 기능을 ISR로 구현하자. ISR에서 uart_putch() 함수를 사용하는 것은 바람직하지 않다. 아무 일도 하지 않고 기다리는 코드가 ISR에 들어가서는 안되기 때문이다. while() 루프 내용을 모두 지운 다음 ISR 에 다음 명령어만 추가한 후 UDR0 = UDR0 프로그램을 실행시켜 보고 그 결과를 자세하게 설명하여라. 왜 데이터를 보내기 전에 송신 버퍼가 비었는 지 여부를 검사하지 않아도 문제가 없는가? 

 

 

  • UDR0 = UDR0 명령은 수신된 데이터(오른쪽 UDR0)를 송신 레지스터(왼쪽 UDR0)에 즉시 복사
  • 수신 버퍼에서 송신 버퍼로 데이터를 직접 복사하는 하드웨어 레벨의 동작

 

 

 

저런 하드웨어적인 동작을 하게끔 안하면 다음과 같이 진행해야 한다.