본문 바로가기

SW 개발 공부/OS 개발 프로젝트

[나빌로스] 5. UART

1. 개요

UART를 사용하기 위해서는 하드웨어의 레지스터를 코드로 구현해야 한다. 우리가 사용할 UART는 RealViewPB의 PL011이며, 해당 UART의 동작을 코드로 작성하는 방법을 알아본다.

2. 레지스터 접근 방법

ARM은 메모리 매핑된 I/O 방식을 사용하므로, 정의된 주소에 접근하여 각 레지스터를 제어할 수 있다. 이를 용이하게 하기 위해 다음과 같은 방법을 사용할 수 있다.

 

2.1 매크로를 이용한 방법

 

 

#define UART_BASE_ADDR 0x10009000 // PL011 base address

#define UARTDR_OFFSET 0x00
#define UARTDR_DATA (0)
#define UARTDR_FE (8) // 8번째 offset
#define UARTDR_PE (9) // 9번째 offset
...
#define UARTCR_OFFSET 0x30

 

코드적용 예

uint32_t *uartdr = (uint32_t*)(UART_BASE_ADDR + UARTDR_OFFSET);

*uartdr = (data) <<UARTDR_DATA;
bool fe = (bool)((*uartdr>>UARTDR_FE)&0x01));
bool pe = (bool)((*uartdr>>UARTDR_PE)&0x01));

 

2.2 구조체를 이용한 방법

 

 

typedef union UARTDR_t{
	uint32_t all;
    struct{
    uint32_t DATA:8;// 구조체 내부에서 비트필드 정의 : 8bit
    uint32_t FE:1;
    uint32_t PE:1;
    uint32_t BE:1;
    uint32_t OE:1;
    uint32_t reserved:20;
    }bits;
}UARTDR_t

 

* union을 사용하는 이유

 

- union은 메모리 공간을 절약하고 여러가지 형식으로 데이터를 다룰 수 있게 하기 위해서 사용한다.

union은 다른 데이터 타입을 동일한 메모리 공간에 저장할 수 있도록 허용한다.

 

코드적용 예

PL011_t* Uart = (PL011_t*)UART_BASE_ADDR;

Uart->uartdr.DATA = data & 0xff;

if(UART->uartdr.FE || UART->uartdr.PE || UART->uartdr.BE || UART->uartdr.OE )
{
// 에러 처리 코드 구현
}
실무에서 보통 레지스터 정의 코드(UART.h)는 보통 담당 부서에서 자동 생성하여 개발 부서로 전달해준다.

 

3. PL011 UART 동작 구현

이제 각 레지스터의 주소값을 define 해놓은 Reg.h 와 PL011의 레지스터들을 구조체로 define해놓은 Uart.h를 구현한 이후, 실제 Uart 동작을 Uart.c에 구현해보자.

 

동작은 다음과 같다.

 

3.1 초기화

#include "stdint.h"
#include "Uart.h"
#include "HalUart.h"

extern volatile PL011_t* Uart;

void Hal_uart_init(void)
{
        Uart->uartcr.bits.UARTEN=0;
        Uart->uartcr.bits.TXE=1;
        Uart->uartcr.bits.RXE=1;
        Uart->uartcr.bits.UARTEN=1;
}

void Hal_uart_put_char(uint8_t ch)
{
        while(Uart->uartfr.bits.TXFF);
        Uart->uartdr.all=(ch&0xFF);
}

uint8_t HAL_uart_get_char(void)
{
        uint8_t data;
        while(Uart->uartfr.bits.RXFE);

        data = Uart->uartdr.all;

        if(data & 0xFFFFFF00)
        {
                Uart->uartsr.all = 0xFF;
                return 0;
        }

        return (uint8_t)(data&0xFF);
}

 

1) 초기화

 

초기화 순서

  1. UART 비활성화 (UARTEN = 0)
  2. TX (송신) 및 RX (수신) 활성화 (TXE = 1, RXE = 1)
  3. UART 활성화 (UARTEN = 1)

이유

  • UART가 활성화된 상태에서 RX/TX를 변경하면 오류가 발생할 수 있음.
  • Reference Manual에 정의된 초기화 순서를 따름.

 

2) byte 전송

 

설명

  • TXFF(TX FIFO Full) 비트가 0이 될 때까지 대기.
  • TXFF == 1이면 FIFO가 가득 차 있어서 전송 불가능.
  • TXFF == 0이면 전송 가능하므로 UARTDR에 데이터 저장.

 

3) byte 수신

 

설명

  • RXFE(RX FIFO Empty) 비트가 0이 될 때까지 대기.
  • RX FIFO가 가득 차면 UARTDR에서 데이터를 읽음.
  • 데이터의 상위 비트를 확인하여 에러 여부 검사 (오버런, 패리티, 브레이크 에러 등).
  • 에러 발생 시 UARTSR를 1로 설정하여 플래그를 초기화.