드디여 하드웨어 장치를 다루게 될 차례인데

가장 먼저 UART를 소개하고 있다.

 

이유는 UART로 콘솔 입출력을 하기 위함이며

지금 QEMU에서 사용하는 징차인 RealViewPB에는 PL011이라는 UART 하드웨어를 사용하고 있다고 한다.

 

가장 먼저 할 일은 레지스터를 코드로 구현한다는데

레지스터 헤더를 구조체로 추상화해서 쓴걸 깃헙에서 다운받아서 쓰라고 한다.

 

hal/rvpb/Uart.h 인데

리얼뷰PB에 종속적인 코드라

여러 플랫폼을 고려해서 디렉토리를 이런 구조로 구현한다고 한다.

 

https://github.com/navilera/Navilos/blob/master/hal/rvpb/Uart.h

 

GitHub - navilera/Navilos: RTOS for various embedded platform

RTOS for various embedded platform. Contribute to navilera/Navilos development by creating an account on GitHub.

github.com

 

 

헤더를 만들고

UART 하드웨어를 제어할 변수 코드

hal/rvpb/Regs.c

구조체 접근 규칙으로 접근하면 되니 베이스 주소만 가져오면되나보다.

#include <stdint.h>
#include "Uart.h"

volatile PL011_t* Uart = (PL011_t*)UART_BASE_ADDRESS0;

 

 

지금 계속 HAL 폴더에서 작업하고 있는데 

Hardware Abstraction Layer의 약어로

각 하드웨어 플랫폼마다 구현 방식은 다르더라도

공통적인 API를 통해 사용할수 있도록 추상화한 계층이라고 한다.

 

지금까지는 rvpb에 종속적인 코드를 구현했지만

이번에는 여러 하드웨어에서 사용할수 있는 공용 HAL 인터페이스를 구현하면

 

hal/HalUart.h

라즈베리파이든, realviewpb이든 각 플랫폼 별로 구현해두면 다양한 UART 하드웨어를

공용 인터페이스로 쓴다는 내용인거 같고

uart를 쓰려면 초기화해야되느 init과 가장 간단하게 쓰기 위해 putchar

타깃 플랫폼이 rvpb니 hal/rvpb/Uart.c에서 이 두 공용 인터페이스를 구현하는데

#ifndef HAL_HALUART_H_
#define HAL_HALUART_H_

void Hal_uart_init(void);
void Hal_uart_put_char(uint8_t ch);

#endif /* HAL_HAALUART_H_ */

 

hal/rvpb/Uart.c

rvpb의 Uart 구현코드는 이런데

PL011_t 타입의 Uart 포인터 변수의

uartcr이란 구조체에 접근해서 안에 있는 UARTEN = 0한 상태에서

TXE=1, RXE=1 로 송수신 인에이블해주고

설정을 마치면 다시 UARTEN=1으로 인에이블 해준다는 소린거 같다.

 

 

아래의 putchar 로직은

uart 출력 버퍼 (uartfr.bits.txff)가 0이되면 = 출력버퍼가 비워지면

uartdr.all = (ch & 0xff), dr 데이터 레지스터로 내보낸다는 의미라고한다.

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

extern volatile PL011_t* Uart;

void Hal_uart_init(void)
{
	//enable UART
	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);
}

 

 

일단 init 부분만 좀 더보면

rvpb의 Uart.h에서 PL011_t는 다음의 형태를 가지고 있다.

typedef struct PL011_t
{
    UARTDR_t    uartdr;         //0x000
    UARTRSR_t   uartrsr;        //0x004
    uint32_t    reserved0[4];   //0x008-0x014
    UARTFR_t    uartfr;         //0x018
    uint32_t    reserved1;      //0x01C
    UARTILPR_t  uartilpr;       //0x020
    UARTIBRD_t  uartibrd;       //0x024
    UARTFBRD_t  uartfbrd;       //0x028
    UARTLCR_H_t uartlcr_h;      //0x02C
    UARTCR_t    uartcr;         //0x030
    UARTIFLS_t  uartifls;       //0x034
    UARTIMSC_t  uartimsc;       //0x038
    UARTRIS_t   uartris;        //0x03C
    UARTMIS_t   uartmis;        //0x040
    UARTICR_t   uarticr;        //0x044
    UARTDMACR_t uartdmacr;      //0x048
} PL011_t;

 

PL011_t의 각 공용체 의미는 데이터 시트를 제대로 봐야하니 넘어가고

아까 init에서 쓴 uarticr만 보면

이런식으로 되어있다. 데이터시트 없이 무슨뜻인지 알겠는 부분도 있지만 안봐서 제대로는 모르겠다.

typedef union UARTCR_t
{
    uint32_t all;
    struct {
        uint32_t UARTEN:1;      // 0
        uint32_t SIREN:1;       // 1
        uint32_t SIRLP:1;       // 2
        uint32_t Reserved1:4;   // 6:3
        uint32_t LBE:1;         // 7
        uint32_t TXE:1;         // 8
        uint32_t RXE:1;         // 9
        uint32_t DTR:1;         // 10
        uint32_t RTS:1;         // 11
        uint32_t Out1:1;        // 12
        uint32_t Out2:1;        // 13
        uint32_t RTSEn:1;       // 14
        uint32_t CTSEn:1;       // 15
        uint32_t reserved2:16;
    } bits;
} UARTCR_t;

 

 

이제 UART 코드를 구현했으니 

boot/Main.c에서 uart를 쓰도록 구현하면

 

이런 식이고

굳이 Hw_init을 추가한건

지금은 uart 초기화만 하고 있지만 나중에 다른 하드웨어 초기화 코드도 같이 포함시켜서

이 함수 하나로 동작시키기 위한다고 한다.

 

코드 내용은 간단하게 N을 100번 출력

#include <stdint.h>

#include "HalUart.h"

static void Hw_init(void);

void main(void)
{
	Hw_init();

	uint32_t i = 100;
	while(i--)
	{
		Hal_uart_put_char('N');
	}
}

static void Hw_init(void)
{
	Hal_uart_init();
}

 

 

이제 hal api랑 rvpb에 종속적인 코드를 추가했으니 이를 반영하도록

Makefile을 수정하는데

 

Makefile 복습해야하는 부분들좀 다시보긴 해야할거같다.

인글루드 디렉토리가 여러개 늘었고

CFLAGS가 따로 빠진거

run 시 -nographic 옵션을 주는데 이러면 시리얼 포트가 콘솔과 연결되서 바로 볼수 있다고 한다.

 

 

ARCH=armv7-a
MCPU=cortex-a8

TARGET=rvpb

CC=arm-none-eabi-gcc
AS=arm-none-eabi-as
LD=arm-none-eabi-ld
OC=arm-none-eabi-objcopy

LINKER_SCRIPT=./navilos.ld
MAP_FILE=build/navilos.map

ASM_SRCS=$(wildcard boot/*.S)
ASM_OBJS=$(patsubst boot/%.S, build/%.os, $(ASM_SRCS))

VPATH = boot \
		hal/$(TARGET)

C_SRCS	=	$(notdir $(wildcard boot/*.c))
C_SRCS	+=	$(notdir $(wildcard hal/$(TARGET)/*.c))
C_OBJS	=	$(patsubst %.c, build/%.o, $(C_SRCS))

INC_DIRS	=	-I include		\
				-I hal			\
				-I hal/$(TARGET)

CFLAGS = -c -g -std=c11

navilos=build/navilos.axf
navilos_bin=build/navilos.bin

.PHONY: all clean run debug gdb

all:$(navilos)

clean:
	@rm -fr build

run: $(navilos)
	qemu-system-arm -M realview-pb-a8 -kernel $(navilos) -nographic

debug: $(navilos)
	qemu-system-arm -M realview-pb-a8 -kernel $(navilos) -S -gdb tcp::1234,ipv4

gdb:
	gdb-multiarch

$(navilos):$(ASM_OBJS) $(C_OBJS) $(LINKER_SCRIPT)
	$(LD) -n -T $(LINKER_SCRIPT) -o $(navilos) $(ASM_OBJS) $(C_OBJS) -Map=$(MAP_FILE)
	$(OC) -O binary $(navilos) $(navilos_bin)

build/%.os: %.S
	mkdir -p $(shell dirname $@)
	$(CC) -mcpu=$(MCPU) $(INC_DIRS) $(CFLAGS) -o $@ $<

build/%.o: %.c
	mkdir -p $(shell dirname $@)
	$(CC) -mcpu=$(MCPU) $(INC_DIRS) $(CFLAGS) -o $@ $<

 

 

그래서 런을 돌리면 바로 아까 메인함수에 구현한대로

uart를 초기화하고 N이 100번출력하는 결과가 나온다.

 

 

소스 트리 구조

 

 

+ Recent posts