UART로 입력 받기
- 출력은 출력버퍼가 비어있는지 확인하고(TXFE, Transmit FIFO Empty), 비어있으면 레지스터를 통해 출력버퍼로 데이터를 보냈다
- 입력은 그 반대로, 입력버퍼가 채워져 있는지 확인하고(RXFE, Receive FIRO Empty), 데이터가 있으면 레지스터를 통해 데이터를 읽어오면 된다
우선 만들어보기
uint8_t Hal_uart_get_char(void)
{
uint8_t data;
while(Uart->uartfr.bits.RXFE);
if(Uart->uartdr.bits.BE || Uart->uartdr.bits.FE ||
Uart->uartdr.bits.OE || Uart->uartdr.bits.PE)
{
Uart->uartrsr.bits.BE = 1;
Uart->uartrsr.bits.FE = 1;
Uart->uartrsr.bits.OE = 1;
Uart->uartrsr.bits.PE = 1;
return 0;
}
data = Uart->uartdr.bits.DATA;
return data;
}
- 가장 직관적이고 읽기 쉬운 코드이다
- 그런만큼, 레지스터에 대한 접근과 비트 연산이 매우 많다
- BE, FE, OE, PE 에러 플래그를 개별적으로 접근
- BE, FE, OE, PE 에러 플래그를 개별적으로 클리어
- 위 코드를 역어셈블해보자
sijin@Sijin:~/embedded_os/build$ arm-none-eabi-objdump -d Uart.o
000000d4 <Hal_uart_get_char>:
d4: e52db004 push {fp} @ (str fp, [sp, #-4]!)
d8: e28db000 add fp, sp, #0
...
20c: e49db004 pop {fp} @ (ldr fp, [sp], #4)
210: e12fff1e bx lr
- 0x00d4에서 0x0210까지이므로, 입력 함수가 320byte, 80줄이다
[참고] UART 레지스터 약어
- DR (Data Register)
- SR (Status Register)
- UART의 에러, 송신/수신 상태 등 상태를 저장
- FR (Flag Register)
- UART의 플래그 상태(버퍼가 비었는지, 가득찼는지)를 나타냄
- PR (Prescaler Register)
- CR (Control Register)
- UART의 활성화, 송신/수신 활성화 등 제어
- RSR (Receive Status Register)
최적화하기
- 아래 두가지 요소에 대해 최적화 할 수 있다
- 비트마스킹을 활용
- 레지스터 접근, 비트연산, 비교연산을 줄일 수 있다
- HW 레지스터값을 로컬 변수로 저장
- 하드웨어 레지스터에 접근하는 것보다 스택 혹은 레지스터에 저장되는 로컬변수에 접근하는게 훨씬 빠름
uint8_t Hal_uart_get_char(void)
{
uint8_t data;
while(Uart->uartfr.bits.RXFE);
data = Uart->uartdr.all;
if(Uart->uartdr.all & 0x0F00)
{
Uart->uartrsr.all = 0xF;
return 0;
}
return (uint8_t)(data & 0xFF);
}
- 동일하게 역어셈블리해서 최적화되었는지 확인해보자
sijin@Sijin:~/embedded_os/build$ arm-none-eabi-objdump -d Uart.o
000000d4 <Hal_uart_get_char>:
d4: e52db004 push {fp} @ (str fp, [sp, #-4]!)
d8: e28db000 add fp, sp, #0
...
15c: e49db004 pop {fp} @ (ldr fp, [sp], #4)
160: e12fff1e bx lr
- 0x00d4에서 0x0160까지이므로, 입력함수가 144byte, 36줄이되었다
main에서 입력/출력
void main(void)
{
Hw_init();
int i = 100;
while(i--)
{
uint8_t ch = Hal_uart_get_char();
Hal_uart_put_char(ch);
}
}
- 문자를 입력받고 그대로 출력해보자

- 키보드로 입력한 문자가 그대로 출력하는것을 볼 수 있다
printf 만들기
- 일반 char 출력과 달리 printf는 format을 받을 수 있는점이 다르다
구현
- format 문자열을 읽어 변수를 알맞게 넣어주어야 한다
- format에 대한 파라미터는 가변적인데, 이런 경우 __builtin_va 관련 함수들을 사용할 수 있다
| 함수 종류 | 설명 |
|---|
| __builtin_va_start | 가변 인자 목록을 초기화, va_list 타입의 변수를 인자로 받아, 가변 인자 목록의 시작 위치를 설정 |
| __builtin_va_arg | va_list에서 다음 인자의 값을 지정한 타입으로 읽음 |
| __builtin_va_end | 가변 인자 처리가 끝난 후, va_list를 정리 |
| __builtin_va_copy | 한 va_list의 상태를 다른 va_list로 복사 |
#define va_start(v, l) __builtin_va_start(v, l)
#define va_end(v) __builtin_va_end(v)
#define va_arg(v, l) __builtin_va_arg(v, l)
uint32_t debug_printf(const char* format, ...)
{
va_list args;
va_start(args, format);
vsprintf(printf_buf, format, args);
va_end(args);
return putstr(printf_buf);
}
- printf라는 함수 이름을 그대로 쓰면 컴파일러가 최적화 과정에 puts로 바꿔버릴 수 있어 debug_printf 함수이름을 사용했다
빌드
- 여기서는 생략했지만, printf format을 처리할 때
%d 혹은 %x같은 format을 처리하기 위해 utoa 함수를 만들었다
- 이 과정에서 나눗셈 연산이 들어갔다
- 나눗셈 연산 때문에 아래와 같은 오류가 발생한다
/home/sijin/embedded_os/lib/stdio.c:92:(.text+0x374): undefined reference to `__aeabi_uidivmod'
arm-none-eabi-ld: /home/sijin/embedded_os/lib/stdio.c:98:(.text+0x3c8): undefined reference to `__aeabi_uidiv'
- arm에서는 기본적으로 나머지(%), 나누기(/)연산을 지원하는 HW가 없는 것을 가정한다
- HW가 없다면 gcc가 이를 SW로 구현한 라이브러리 함수로 자동으로 링킹한다
- HW가 있다면
__aeabi_uidivmod, __aeabi_uidiv 함수를 만들어 HW를 사용하는 코드를 작성해 링킹해야 한다
LD=arm-none-eabi-gcc
LDFLAGS = -nostartfiles -nostdlib -nodefaultlibs -static -lgcc
$(navilos): $(ASM_OBJS) $(C_OBJS) $(LINKER_SCRIPT)
$(LD) -n -T $(LINKER_SCRIPT) -o $(navilos) $(ASM_OBJS) $(C_OBJS) -Wl,-Map=$(MAP_FILE) $(LDFLAGS)
$(OC) -O binary $(navilos) $(navilos_bin)
- 링커 명령어가 arm-none-eabi-ld에서 arm-none-eabi-gcc로 변경했다
- gcc도 링킹이 가능하고 내부적으로 gcc가 ld를 호출한다
- 이 과정에서 몇가지 번거로운 옵션을 알아서 추가해준다
- LDFLAGS를 정의해, 표준 라이브러리를 빼고 -lgcc만 추가해주었다