[출처]
http://control.cntc.ac.kr/cpu/ezboard/ezboard.cgi?db=qa_avr_2004&action=read&page=111&num=2756&dbf=200501220005&depth=3
위의 질문을 받고 WinAVR에서 printf 함수를 사용하는 방법에 대하여 조사를 했습니다. 다행히 현재 우리가 사용하는 WinAVR에서는 이 기능을 아주 잘 만들어 놓아서 다른 MCU에서 보다 사용하기가 상당히 편리하게 되어 있네요...
현재 저는 "AVR ATmega128 마스터" 책의 제2판을 작업하고 있는데 이 내용을 포함시켜야 겠습니다. 우선 아래에 골자를 요약하여 올립니다.
이에 관련된 내용은 avr-libc Reference Manual의 5.13 Standard IO facilities에 잘 나와 있으며, 헤더 파일 stdio.h의 안에도 자세한 설명이 있고, avrfreaks.com의 AVR GCC 포럼에도 많은 질문과 답변이 있습니다... 이는 avr-libc의 V1.0(AvrEdit 3.6에서 사용하고 있는 것)에서 처음 지원하는 기능으로 보입니다. 그 이전에는 안되는 것으로 이야기되고 있는 것을 보면...
그런데, 매뉴얼에 보면 이 기능은 아직 안정된 것이 아니기 때문에 앞으로 얼마든지 수정될 수 있으니 주의하라고 되어 있습니다. 하지만, 최근에 avr-libc의 V1.2.0이 나왔는데 이 매뉴얼에도 보면 이 기능은 아직 바뀌지 않고 그대로였습니다. 제가 보기에도 아주 잘 만들어진 기능이라서 수정할 필요가 없어보입니다만...
WinAVR에서 printf 함수를 사용하는 핵심은 사용자가 임의로 1문자를 출력하는 함수를 만들고 이를 fdevopen() 함수로 인식시켜야 한다는 것입니다. 보통 다른 MCU의 C컴파일러에서는 이 함수로 반드시 putchar()라는 함수를 사용하여야 했는데 여기서는 임의로 이름을 지어도 좋군요.
fdevopen() 함수는 일반 C언어에서 fopen()에 해당하는 것으로, 파라미터가 3개 있는데 첫번째는 출력장치 함수(STDOUT, STDERR), 두번째는 입력장치 함수(STDIN)를 지정하며, 마지막 파라미터는 사용하지 않으므로 항상 0으로 줍니다.
(1) 우선 먼저 프로그램의 서두에서 헤더파일 stdio.h를 인클루드시킵니다.
(2) 이제 printf를 ATmega128의 USART0로 출력하는 것으로 가정하면 당연히 이 직렬포트를 초기화하여야 합니다. 아래 예제에서는 이를 USART0_initialize() 함수로 만들었습니다.
(3) 다음에는 USART0 에 1문자를 출력하는 함수를 만듭니다. printf 함수는 항상 내부적으로 vprintf 함수를 참조하고 이 vprintf 함수는 항상 STDOUT 장치를 참조하여 이 1문자 출력함수를 사용하게 됩니다. 아래 예제에서는 USART0_putchar() 함수로 만들었습니다. 이 함수의 return 형은 반드시 int로 하고 파라미터는 char 형으로 해야 합니다. 만약, 다른 형으로 하면 에러로 처리됩니다...
(4) 이제 fdevopen() 함수의 첫번째 파라미터로 1문자 출력함수를 STDOUT 디바이스로 할당합니다. 만약, scanf() 함수로 입력하는 기능이 필요하면 두번째 파라미터에도 1문자 입력함수를 만들어 할당하면 됩니다만 여기서는 생략합니다. 아래 예제에서는 fdevopen(USART0_putchar,0,0)으로 하였습니다.
(5) printf 함수를 사용합니다. 우리가 알고 있는 모든 %서식이 사용될 수 있습니다.
(6) 그런데 이 소스를 컴파일할 때는 반드시 printf 함수에 해당하는 라이브러리를 링크시켜주어야 합니다. WinAVR 패키지에 보면 이들 라이브러리 함수가 있습니다. 여기에는 정수까지만 출력할 수 있는 libprintf_min.a가 있고 부동소수점 형식까지 출력할 수 있는 libprintf_flt.a가 있습니다. 정수만 처리하는데 부동소수점 기능까지 포함되면 오브젝트 코드 전체가 쓸데없이 길어지기 때문에 이렇게 한 듯합니다.
만약 정수 출력만 한다면 이를 컴파일할 때 링커 옵션에 ,-Wl,-u,vfprintf -lprintf_min 을 추가하면 됩니다. 하지만, 아래의 예제에서처럼 부동소수점 포맷을 사용하면 반드시 링커 옵션에 ,-Wl,-u,vfprintf -lprintf_flt -lm 이라고 해주어야 합니다. 이 링커 옵션에서는 컴마 사용에 주의하시고, l은 숫자 1이 아니라 모두 L의 소문자라는 것에 유의하십시오...
아래의 예제를 실행하면 직렬통신으로 PC의 화면에 메시지와 숫자가 1초 간격으로 증가하면서 출력되는 것을 볼 수 있습니다.
이 예제에서는 USART0를 사용하였습니다만, 마찬가지 방법으로 USART1로 수정하여 사용할 수도 있죠. 또한, 직렬포트가 아니라 LCD 모듈로 서식지정하여 출력을 내보내는 것으로 쉽게 수정할 수 있다는 것을 짐작하실 수 있겠습니다.
제가 아직 실험을 해보지는 않았지만 WinAVR에서는 더 놀라운 기능이 가능할 듯합니다. 즉, fdevopen() 함수에서 1문자 출력함수를 임의로 지정할 수 있기 때문에 하나의 프로그램 안에서 수시로 printf에 의하여 출력되는 장치를 변경할 수 있을 듯하다는 것입니다. 다른 C의 경우에는 이 함수가 putchar()로 고정되어 있으므로 불가능하거든요... 그러나, WinAVR에서는 처음에 fdevopen(USART0_putchar,0,0)으로 하면 printf 함수가 USART0로 출력하며, 다시 fclose()로 이 장치를 닫고 fdevopen(USART1_putchar,0,0)라고 하면 이번에는 printf 함수가 USART1으로 출력하고, 이를 다시 LCD 모듈로 출력하도록 또 변경할 수도 있을 것이라는 거죠... 놀랍지 않습니까? 거의 PC 수준이 되는 것이죠...
어쨌든 궁금하신 분들은 잘 사용하시기 바랍니다.
/* =========================================================== */
/* Usage of printf Function */
/* =========================================================== */
/* Designed and programmed by Duck-Yong Yoon in 2005. */
#include < avr/io.h >
#include < stdio.h >
#include "c:\AvrEdit\OK128c\OK128.h"
void USART0_initialize(void) /* initialize USART0 */
{
UBRR0H = 0; // 19200 baud
UBRR0L = 51;
UCSR0A = 0x00; // asynchronous normal mode
UCSR0B = 0x18; // Rx/Tx enable, 8 data
UCSR0C = 0x06; // no parity, 1 stop, 8 data
}
int USART0_putchar(char c) /* print a character */
{
if(c == '\n') // process carriage return
USART0_putchar('\r');
loop_until_bit_is_set(UCSR0A,UDRE0); // Tx ready ?
UDR0 = c;
return 0;
}
int main(void)
{ unsigned char i = 0;
double x = 0.0;
MCU_initialize(); // initialize MCU
Delay_ms(50); // wait for system stabilization
LCD_initialize(); // initialize text LCD module
LCD_string(0x80,"printf function "); // display title
LCD_string(0xC0," to USART0 ");
USART0_initialize(); // initialize USART0
fdevopen(USART0_putchar,0,0); // STDOUT and STDERR device open
while(1)
{ printf("This is printf test message !\n");
printf("Integer number : %3d\n",i);
printf("Float number : %7.3f\n\n",x);
i++;
x += 0.101;
Beep();
Delay_ms(1000);
}
}