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) 초기화
초기화 순서
- UART 비활성화 (UARTEN = 0)
- TX (송신) 및 RX (수신) 활성화 (TXE = 1, RXE = 1)
- 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로 설정하여 플래그를 초기화.
'SW 개발 공부 > OS 개발 프로젝트' 카테고리의 다른 글
[나빌로스] 10.컨텍스트 스위칭 (0) | 2025.04.02 |
---|---|
[나빌로스] 8.태스크 (0) | 2025.04.02 |
[나빌로스] 7.타이머 (0) | 2025.04.02 |
[나빌로스] 5-1. UART printf 구현하기 (0) | 2025.04.01 |