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(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(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(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(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
  1. UTRSTAT_TX_EMPTY(0x4), 즉 모두 전송하게 되어 'Transmitter empty' 비트가 1 이 되면, 루프를 빠져 나온다.
  2. 버퍼에 인자로 받은 문자를 넣는다.
  3. 만일 '\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. 수신 버퍼에 데이터가 있어서 해당 비트가 1 로 바뀌면, 루프문을 빠져나온다.
  2. 버퍼를 읽어서, 수신된 데이터를 리턴한다.
  • computer/rtcclab/printf_핵심가이드.txt
  • Last modified: 3 years ago
  • by likewind