[왕초보 임베디드-C 공부 일기] 5. 화면 출력(2) - Printf 말고 다른 방법

이전 글을 작성하던 중에 문득 이런 생각이 들었다. printf말고 화면에 출력하는 다른 방법은 있을까? 이 부분은 강의에서도 다루지 않기 때문에 검색과 Gemini를 통해 해결할 수 있었다. 찾아보니 많은 방법들이 있었고, 통신과 관련된 중요한 함수들도 있어서 찾아보길 잘했다는 생각이 들었다. 

I. puts() : 문자열 전용 출력 함수

   -문자열의 끝(\0)을 만날 때까지 문자를 출력
   -문자열 출력이 끝나면 자동으로 줄 바꿈을 진행함
   -성공 시 음수가 아닌 값을, 실패 시 EOF(End of file)를 반환
   -서식 지정자(%d, %x...)를 사용할 수 없다. 
사용 예시⬇️
char msg[] = "System Ready";
puts(msg); 
// 출력값 : System Ready(줄바꿈)

II. putchar() : 단일 문자 출력

   가장 기본 단위인 1byte(문자)를 출력하는 함수

   -입력받는 매개변수의 크기는 int(정수, 4byte, 32bit)인데, 실제 출력하는 크기는 char(문자, 1byte, 8bit)이다.
    그래서 putchar는 int맨 뒤에 1byte만 남기고 앞부분을 잘라 버린다.
    (이를 캐스팅(casting) 또는 절삭(Truncation)이라고 함.)

   -버퍼링 없이 즉시 출력(Direct Output)
    printf 같은 무거운 함수는 효율성을 위해 "버퍼(Buffer)"라는 대기실이 존재한다. 글자가 어느 정도 모이면 그때 한 번에 화면에 출력한다.
   (그래서 화면에 바로 안 뜨는 경우가 있음)
    putchar는 버퍼를 거치지 않고 하드웨어(통신 모듈)에 바로 데이터를 전송한다. (함수 실행 즉시 전기 신호가 전선을 타고 나간다.)

사용 사례)
   로봇이 배터리 부족으로 꺼지기 전에 마지막 로그를 보내야 할 때 printf를 위험하다. 버퍼에 담아두고 출력하기도 전에 꺼질 수 있기 때문이다.
   이때 putchar을 쓰면 꺼지기 직전까지 한 글자라도 더 전송할 수 있다. 
+개발 보드에서 printf를 사용하기 위해 통신 회로(UART)로 1byte를 전송하는 함수를 재정의(Retargeting)할 때, 내부적으로 가장 먼저 구현해야하는 함수이다. 

사용 예시⬇️
putchar('A');
putchar(0x41); //16진수 아스키코드 'A'
// 출력값 : AA


III. fprintf() : 목적지 지정 출력

   출력할 목적지(Stream)를 명시적으로 지정할 수 있는 서식화된 출력 함수

   ⬛첫 번째 인자 : 목적지
   -printf와 기능은 동일하나, 첫 번째 인자로 표준 목적지나 파일 주소(포인터)를 받는다.
   1)stdout(표준 출력) : 일반적인 정보 출력, 버퍼링이 있을 수 있다.
   2)stderr(표준 에러) : 오류 메시지 출력, 버퍼링 없이 즉시 출력된다.
   3)파일 주소(포인터) : 쓰기 모드로 설정된 파일("w(쓰기 전용)", "a(이어 쓰기)", "r+(맨 앞부터 덮어쓰기)")에 데이터 출력

사용 사례 : 데이터 로깅(Data logging)
              로버가 화성에서 탐사를 진행하고 있다. 화면(모니터)은 없으니 SD카드나 메모리에 센서 값을 기록해야 한다. 
              이때 fprintf를 사용해서 파일에 글자을 쓴다. 
데이터 로깅 예시⬇️
#include <stdio.h>
int main() {

   // 1. 파일 열기(목적지 생성)
   // "sensor_log.txt"파일로 가는 길 생성
   FILE *fp = fopen("sensor_log.txt", "w");

   if(fp == NULL){
      //파일 열기 실패 시
      return 1;
   }
   int temperature = 25;
   int battery = 88;

   // 2. 파일에 쓰기(fprintf 시용)
   // 화면(stdout)이 아니라, 방금 만든 목적지(fp)에 데이터를 전송
   fprintf(fp, "Time : 10:00, Temp: %d, Batt: %d%%\n", temperature, battery);
   fprintf(fp, "Status : OK\n");

   //3. 파일 닫기(닫지 않을 시 저장이 되지 않을 수 있음)
   fclose(fp);
   return();
}

일반 화면 출력 예시⬇️
int speed = 50;
fprintf(stdout, "Speed : %d\n", speed); //일반 로그
if(speed > 100){
   fprintf(stderr, "Error : Over Speed!\n"); //에러 로그(즉시 출력)
}

IV. snprintf : 메모리에 출력

   화면이 아닌 문자열 배열(메모리 공간)에 출력을 수행하는 함수이다.
   임베디드 통신 구현의 핵심 함수이다.
   -size인자를 통해 쓸 수 있는 최대 길이를 제한(메모리 용량 초과로 인한 주변 메모리 덮어쓰기 방지)
   -문자열 끝에 항상 \0(Null terminator)를 작성

사용 사례 : 로버의 온도 센서 값 기록(fprintf와 협력)
   1) snprintf(데이터 포장)
      -센서 값은 날 것의 정보이다.
      -이를 "Temp : 23C"라는 가독성 좋은 문자열로 만들어야 한다.
      -이때 RAM에서 snprintf로 조립한다.
   2) fprintf(저장 또는 전송)
      -RAM에 만들어진 buffer를 목적지(파일 또는 화면)로 전송

기록 예시⬇️
char buffer[50];
snprintf(buffer, 50, "Temp:%dC", 23);
//snprintf(임시 대기 메모리(버퍼), 점유할 메모리 크기, 텍스트)
fprintf(Tempfp, "%s\n", buffer);

기타 활용 사례 : LCD 디스플레이에 띄울 문자열을 조합하거나, 통신 모듈로 전송할 패킷(Packet)을 생성할 때 사용

찾아보니 많은 방법들이 있었고, 통신과 관련된 중요한 함수들도 있어서 찾아보길 잘했다는 생각이 들었다. 모니터에 출력을 하지 않고 메모리에 저장하는 형태이기 때문에 실제로 위성이나 로버에서 활용할 법한 함수들이라 정확하게 이해해보려고 노력했다.