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 로 바뀌면, 루프문을 빠져나온다.
- 버퍼를 읽어서, 수신된 데이터를 리턴한다.