[왕초보 임베디드-C 공부 일기] 11. 기본 자료형
C언어에서 자료형(Data Type) 은 메모리라는 거대한 창고에 데이터를 어떻게 해석할지 결정하는 그릇의 규격과 라벨 이다.
컴퓨터의 메모리 안에는 오직 0과 1밖에 없다. 똑같은 0100 0001이라는 데이터에도 이것을 '정수'로 만들지, '문자'로 읽을지에 따라 완전히 다른 의미가 된다. 위성이나 로버의 On-board 컴퓨터의 메모리는 데스크톱에 비해 용량이 매우 작기 때문에, 목적에 맞는 정확한 크기의 자료형을 선택하는 것이 매우 중요하다.
C언어의 기본 자료형 4가지를 정의와 간단한 예시를 통해 알아본다.
1. 정수형 (Integer Types) "소수점이 없는 정확한 숫자"
음수와 양수, 혹은 양수만 표현할 수 있는 정수를 담는 그릇이다.
- 기본적으로 char (1바이트), short (2바이트), int (4바이트), long (4바이트 또는 8바이트) 으로 나뉜다.
- 맨 앞에 unsigned를 붙이면 음수를 포기하는 대신, 양수 표현 범위를 2배로 늘릴 수 있다.
(예 : signed char -> -128 ~ 127, unsigned char -> 0 ~ 255)
임베디드 실무의 절대 규칙 : 의 사용
C언어의 int는 컴파일러나 CPU 아키텍처(8비트, 32비트, 64비트)에 따라 크기가 고무줄처럼 변한다. 우주선 코드가 컴퓨터마다 다르게 동작하면 큰 문제가 발생한다. 따라서 실무에서는 크기가 명확하게 고정된
표준 정수형(Standard Integer Types) 을 100% 사용한다.
int8_t, uint8_t (1바이트)
숫자를 다룰 때에는 표준 정수형을 사용하여 부호를 강제하고, 통신으로 들어온 한 글자 그대로('A', 'W')를 다룰 때에만 예외적으로 char을 사용한다.
int16_t, uint16_t (2바이트)
대부분의 현대 시스템에서 short는 2바이트이지만, int16_t를 사용하면 컴파일러에게 "무조건 16비트짜리 공간을 달라."고 요구하는 것이다.
int32_t, uint32_t (4바이트)
C언어 표준에서 int는 "그 CPU가 가장 처리하기 편한 기본 데이터 크기(Word Size)" 를 따라가도록 정의되어 있다.
- 32비트 CPU (STM32, 구형 PC) : int = 4바이트
- 64비트 CPU (최신 PC, 스마트폰) : int = 4바이트 (호환성을 위해 유지)
- 8비트 CPU (아두이노 AVR 등) : int = 2바이트
- 16비트 CPU (MSP430 등) : int = 2바이트
STM32(32비트)에서 int가 4바이트라고 믿고 10만 단위의 숫자를 다루는 코드를 작성한다. 나중에 이 코드를 초소형 위성에 들어가는 8비트 칩으로 그대로 복사해서 넣으면, 8비트 칩에서 int는 2바이트(최대 32,767)이므로 숫자가 넘쳐서 치명적인 오작동이 발생할 수 있다. 여기서 int32_t 를 쓰면, 8비트 칩이든 32비트 칩이든 컴파일러가 무조건 4바이트(32비트) 공간을 만들어준다.
활용 사례 : 하드웨어 레지스터 제어 및 센서 값
- 상황 : 로버의 바퀴 모터가 몇 바퀴 돌았는지 측정하는 '엔코더 펄스(Pulse)' 값을 저장해야 한다.
- 적용 : 펄스는 음수가 될 수 없고(후진하면 방향 핀으로 따로 확인), 값이 수십만까지 아주 빠르게 올라간다. 따라서 넉넉하고 안전한 4바이트 양수 전용 표준 정수형을 사용한다.
#include
uint32_t = encoder_pulse_count = 0; //0부터 4294967295까지 저장 가능
2. 실수형 (Floating-Point Types) : "소수점이 있는 정밀한 숫자"
소수점을 포함한 실수를 표현하는 자료형이다. IEEE 754라는 국제 표준 방식을 사용하며, 메모리를 '부호(Sign)', '지수(Exponent)', '가수(Fraction)' 세 부분으로 쪼개서 저장한다.
- float (4바이트) : 소수점 아래 약 6~7자리까지 정확도 유지
- double (8바이트) : 소수점 아래 약 15자리까지 정확도 유지
활용 사례 : 물리량 계산과 제어 알고리즘
- 상황 : 로버가 목적지를 향해 똑바로 가도록 PID 제어 알고리즘을 수행하거나, GPS 좌표(위도, 경도)를 계산해야 한다.
- 적용 : 각도, 온도, 속도, 위치 같은 아날로그 물리량은 소수점 계산이 필수이다.
float current_heading_angle = 45.5;
float target_angle = 90.0
//오차를 계산하여 조향 모터를 제어
float error = target_angle - current_heading_angle;
float steering_output = Kp * error; //kp도 float형 비례 상수
- 주의점 : MCU 중에는 실수 계산을 도와주는 하드웨어(FPU)가 없는 경우가 많다. 이때 float 계산을 남발하면 소프트웨어적으로 억지로 계산하느라 CPU 속도가 치명적으로 느려진다. 그래서 FPU 내장 여부를 확인한 뒤에 실수 계산을 하는 것이 필요하다.
3. 문자형(Character Type) : "글자 탈을 쓴 작은 정수"
char(1바이트)는 문자를 표현하는 자료형이다. 하지만 컴퓨터는 문자를 모른다. 대신 ASCII(아스키) 코드라는 번역표를 사용하여 문자를 정수 번호로 바꿔서 저장한다.
- 대문자 'A'를 저장하면 메모리에는 십진수 65 (2진수 0100 0001)가 저장된다.
- 본질적으로 1바이트 정수형(int8_t)과 완전히 똑같이 동작한다.
활용 사례 : 통신 프로토콜 파싱(parsing)
- 상황 : 지상국에서 로버로 명령어를 보낸다. 전진은 'W', 후진은 'S'이다.
- 적용 : UART 통신으로 들어오는 바이트 스트림을 문자형으로 받아서 직관적으로 처리한다.
char command = UART_Receive(); //통신으로 1바이트를 읽어옴
if (command == 'W') { // 내부적으로 if (command == 87) 과 똑같이 계산됨
drive_forward();
}
4. 무치형 (Void Type) : "비어있음, 혹은 형태가 정해지지 않음"
void는 데이터를 담는 자료형이 아니다. "데이터의 자료형이 없다." 또는 "아무것도 반환하지 않는다." 는 것을 명시적으로 알려주는 키워드이다.
활용 사례 : 함수의 입출력 제어 및 범용 포인터
- 인터럽트 서비스 루틴(ISR)이나 초기화 함수처럼, 누군가에게 값을 돌려주거나 받을 필요가 없는 함수에 쓰인다.
void SystemClock_Config(void) {
//입력도 없고(void), 반환값도 없음(void). 그냥 세팅만 해놓는 역할이다.
}
임베디드 펌웨어 작성 시 핵심 요약
- 0~255 사이의 작은 값ㅇ을 저장할 때 습관적으로 int(4바이트)를 쓰면 메모리가 낭비된다.
반드시 uint8_t 를 사용하는 것이 메모리 절약에 도움이 된다.
- 실무에서는 소스코드 위에
#include 를 가장 먼저 적고 시작하는 것이 기본 소양이다.
- 소수점 계산(float)은 CPU 성능을 깎아먹으므로, 꼭 필요한 곳(제어 수식 등)에서만 제한적으로 사용하는 것이 좋다.