printf 핵심가이드
SCVOS 에서 printf 함수를 호출하여 터미널 창에 문자가 출력될 때까지의 과정을 상세하게 설명한다.
가장 많이 호출되어 실행되는 루틴인 만큼 확실히 이해한다면, 전체적인 성능 향상에 도움이 될 것이다.
UART 초기화
printf 함수를 사용하기 위해서는 하드웨어적으로 UART 초기화가 우선되어져야 한다. 다음은 UART 초기화 코드이다.
case UART0: GPHCON = _GPHCON; ---- 1 ULCON0 = _ULCON0; ---- 2 UCON0 = _UCON0; ---- 3 UBRDIV0 = _UBRDIV0; ---- 4
UART 는 따로 하드웨어 형태로 존재하지 않는다. GPIO 핀들 중에 몇개를 지정하여 UART 전용핀으로 사용하는 것이다.
참고로 s3c2410 는 117 개의 GPIO 핀을 지원한다. 이를 각각 일정 갯수만큼 나눠서 port 라고 부른다. s3c2410 데이터시트를 보면, port A ~ H 까지 나누고 있다.
s3c2410 은 3 개의 UART 포트(UART0 ~ 2)를 지원하며, 이것은 모두 port H 에서 설정하여 사용 가능하다. SCVOS 에서는 이 중에서 첫번째인 UART0 을 사용한다.
GPHCON 레지스터 설정
GPHCON(0x56000070) 레지스터는 'Configure the pins of port H' 역할을 하며, _GPHCON(0x0016faaa) 값을 쓴다.
| GPHCON | Bit | Description |
| GPH10 | [21:20] | 01 = Output |
| GPH9 | [19:18] | 01 = Output |
| GPH8 | [17:16] | 10 = UCLK |
| GPH7 | [15:14] | 11 = nCTS1 |
| GPH6 | [13:12] | 11 = nRTS1 |
| GPH5 | [11:10] | 10 = RXD1 |
| GPH4 | [9:8] | 10 = TXD1 |
| GPH3 | [7:6] | 10 = RXD0 |
| GPH2 | [5:4] | 10 = TXD0 |
| GPH1 | [3:2] | 10 = nRTS0 |
| GPH0 | [1:0] | 10 = nCTS0 |
위의 값을 write 함으로서 port H 의 gpio 를 UART 로 사용하겠다는 설정이다.
ULCON0 레지스터 설정
ULCON0(0x50000000) 레지스터는 'UART channel 0 line control register' 역할을 하며, _ULCON0(0x3) 값을 쓴다.
| ULCONn | Bit | Description | etc |
| Infra-Red Mode | [6] | 0 = Normal mode operation | 적외선 모드 또는 일반 모드로 설정 |
| Parity Mode | [5:3] | 000 = No parity | UART 송수신의 패리티를 어떻게 생성하고 체크할 것인가를 설정 |
| Number of stop bit | [2] | 0 = One stop bit per frame | 프레임 당 stop 비트를 몇 개 사용할 것인가를 설정 |
| Word length | [1:0] | 11 = 8 bits | 프레임 당 보내거나 받을 데이터의 숫자를 설정 |
위의 값을 write 함으로서 UART0 에 대한 설정을 한다.
UCON0 레지스터 설정
UCON0(0x50000004) 레지스터는 'UART channel 0 control register' 역할을 하며, _UCON0(0x245) 값을 쓴다.
| UCONn | Bit | Description | etc |
| Clock selection | [10] | 0 = PCLK : UBRDIVn = (int)(PCLK / (bps * 16)) -1 | 사용할 클럭 선택 |
| Tx interrupt type | [9] | 1 = Level | Tx 인터럽트 방식 선택 |
| Rx interrupt type | [8] | 0 = Pulse | Rx 인터럽트 방식 선택 |
| Rx time out enable | [7] | 0 = Disable | Rx 타임 아웃 인터럽트 생성 선택 |
| Rx error status interrupt enable | [6] | 1 = Generate receive error status interrupt | Rx 에러 상태 인터럽트 생성 선택 |
| Loop-back Mode | [5] | 0 = Normal operation | 루프백 모드 사용 선택 |
| Send Break Signal | [4] | 0 = Normal operation | 1 프레임 동안 보내는 것을 break 하는 데 사용 |
| Transmit Mode | [3:2] | 01 = Interrupt request or polling model | 송신 모드 지정 |
| Receive Mode | [1:0] | 01 = Interrupt request or polling model | 수신 모드 지정 |
위에서 'Clock selection' 의 경우, FLCK 가 202.8 MHz 로 동작하고 PCLK 가 FCLK/4 비율로 동작한다면 다음과 같다.
UBRDIVn = (int)(50700000 / (115200 * 16)) - 1 = (int)27.5 - 1 = 27 - 1 = 26
송신과 수신 모두 'Interrupt request or polling mode' 를 선택했다.
UBRDIV0 레지스터 설정
UBRDIV0(0x50000028) 레지스터는 'Baud rate divisior register 0' 역할을 하며, _UBRDIV0(((50000000 / (115200 * 16)) - 1)) 값, 결국 26 을 쓴다.
| UBRDIVn | Bit | Description | etc |
| UBRDIV | [15:0] | 26 = ((50000000 / (115200 * 16)) - 1) | 전송 속도 결정 |
위 레지스터는 전송 속도를 결정한다.
printf 함수 호출
다음은 printf 함수 루틴이다. 여기서는 'printf(“hello\n”)' 와 같이 호출했다고 가정한다.
// 역할 : printk() 중 일부를 간단하게 구현.
// 매개 : fmt : printk()와 동일하나 "%s", "%c", "%d", "%x" 사용 가능.
// %d, %x의 경우에는 "%08x", "%8x"와 같이 나타낼 길이와 빈 공간을 0으로 채울지 선택 가능.
// 반환 : 없음.
// 주의 : 없음.
void printk(C8 *fmt, ...)
{
S32 i;
va_list args; // 인자의 가장 처음 h, 즉 0x68 값이 저장
C8 *s=fmt;
C8 format[10]; // fmt의 인자가 "%08lx"라면, "08l"를 임시로 기록.
VA_START(args, fmt); // 인자로 받은 문자열 fmt 를 문자열 배열 변수 args 에 저장
while (*s){
if (*s=='%')
{
s++;
// s에서 "%08lx"형식을 가져와 format에 기록. 나중에 출력함수에 넘겨줌.
format[0] = '%';
for (i=1; i<10;)
{
if (*s=='c' || *s=='d' || *s=='x' || *s=='s' || *s=='%')
{
format[i++] = *s;
format[i] = '\0';
break;
}
else {
format[i++] = *s++;
}
}
// "%s", "%c", "%d", "%x"를 찾아 출력할 함수 호출.
switch (*s++)
{
case 'c' :
PrintChar(format, VA_ARG(args, S32));
break;
case 'd' :
PrintDec(format, VA_ARG(args, S32));
break;
case 'x' :
PrintHex(format, VA_ARG(args, S32));
break;
case 's' :
PrintString(format, VA_ARG(args, C8 *));
break;
case '%' :
PrintChar("%c", '%');
break;
}
}
else {
PrintChar("%c", *s);
s++;
}
}
VA_END(args);
return;
}
UART 출력
printf 루틴 수행 후에 가장 마지막에 호출되는 것이 바로 UART 출력함수이다.
ErrorCode_t Serial_Output_Byte(const C8 c)
{
// FIFO . 에 데이타가 없을때까지 기다린다
while ((UTRSTAT0 & UTRSTAT_TX_EMPTY) == 0 ); ---- 1
UTXH0 = ((UL32)c & 0xFF); ---- 2
// c=='\n' , "\n\r" . 이면 실제로는 을 출력
if (c=='\n') Serial_Output_Byte('\r'); ---- 3
return NO_ERROR;
}
UTRSTAT0(0x50000010) 레지스터는 'UART channel 0 Tx/Rx status register' 역할을 한다.
| UTRSTATn | Bit | Description |
| Transmitter empty | [2] | UART 전송단 상태(0 = 전송되지 않은 데이터가 있음 1 = 전송단의 버퍼와 쉬프터가 모두 비어 있음) |
| Transmit buffer empty | [1] | 전송 버퍼 상태(0 = 전송되지 않은 데이터가 전송 버퍼에 있음 1 = Empty(전송 버퍼 비어 있음)) |
| Receive buffer data ready | [0] | 수신 버퍼 상태(0 = Empty 1 = 수신된 데이터 있음) |
UTXH0(0x50000020) 레지스터는 'UART channel 0 transmit buffer register' 역할을 한다.
| UTXHn | Bit | Description |
| TXDATAn | [7:0] | Transmit data for UARTn |
- UTRSTAT_TX_EMPTY(0x4), 즉 모두 전송하게 되어 'Transmitter empty' 비트가 1 이 되면, 루프를 빠져 나온다.
- 버퍼에 인자로 받은 문자를 넣는다.
- 만일 '\n' 이면 추가로 '\r' 을 보낸다.
문자 입력
이번에는 UART 를 통해 데이터를 입력받는 루틴을 살펴보겠다.
int getc(void)
{
// 한문자 대기
while( !Serial_ReadyChar() );
return Serial_GetChar() & 0xFF;
}
int Serial_ReadyChar( void )
{
// 수신된 데이타가 있는가를 확인한다.
if(UTRSTAT0 & 0x00000001) ---- 1
return 1;
return 0;
}
char Serial_GetChar( void )
{
// 수신된 데이타를 가져 온다.
return (char)URXH0; ---- 2
}
- 수신 버퍼에 데이터가 있어서 해당 비트가 1 로 바뀌면, 루프문을 빠져나온다.
- 버퍼를 읽어서, 수신된 데이터를 리턴한다.