메인 콘텐츠로 건너뛰기

3장 아두이노·ESP32 C/C++ 스케치 언어

로버트
로버트
조회수 10

생성형 AI 도구를 활용하여 작성 및 편집된 노트입니다.

다시 보는 ESP32

개요

아두이노와 ESP32는 C/C++을 기반으로 한 "스케치(Sketch)"라는 프로그래밍 방식으로 동작합니다. 스케치 코드는 setup()loop()라는 두 개의 기본 함수로 구성되며, 이 구조만 이해해도 대부분의 입문 프로젝트를 구현할 수 있습니다.

Generated Image

또한 아두이노·ESP32 환경은 다양한 라이브러리를 활용해 센서, 모터, 디스플레이, 네트워크 등 복잡한 기능을 쉽게 사용할 수 있도록 설계되어 있습니다. 이 노트에서는 스케치 구조, C/C++ 기반 언어 특징, 기본·외부 라이브러리, 라이브러리 설치 방법, 그리고 실제 프로젝트 개발 과정을 차근차근 정리합니다.


아두이노 스케치 기본 구조: setup()과 loop()

아두이노 스케치 프로그램은 항상 두 개의 함수 setup()loop()로 시작합니다. setup()은 전원을 켜거나 리셋한 직후 "딱 한 번만" 실행되는 초기화 함수이고, loop()는 그 이후 계속해서 "반복해서" 실행되는 메인 루프입니다.

예를 들어, 13번 핀에 연결된 LED를 1초 간격으로 깜박이게 하는 가장 기본적인 예제는 다음과 같습니다.

void setup() {
  // 여기의 코드는 전원 켜질 때 한 번만 실행
  pinMode(13, OUTPUT); // 13번 핀을 출력 모드로 설정
}

void loop() {
  // 여기의 코드는 계속 반복 실행
  digitalWrite(13, HIGH); // 13번 핀에 HIGH 출력 (LED 켜기)
  delay(1000);            // 1초 대기 (1000ms)
  digitalWrite(13, LOW);  // 13번 핀에 LOW 출력 (LED 끄기)
  delay(1000);            // 1초 대기
}

setup() 안에서는 보통 핀 모드 설정, 시리얼 통신 초기화, 라이브러리 초기 설정과 같은 "준비 작업"을 합니다. loop() 안에서는 센서 읽기, 값 계산, 출력 제어 등 프로그램이 실제로 수행해야 할 "반복 작업"을 작성합니다.


C/C++ 기반 스케치 언어의 특징과 Serial 객체

아두이노 스케치는 겉으로 보기에는 단순한 특별한 언어처럼 보이지만, 실제로는 "C/C++ 코드"입니다. 따라서 C 언어에서 사용하는 자료형(int, float, char, bool), 조건문(if, switch), 반복문(for, while) 등을 그대로 사용할 수 있습니다.

또한 아두이노 환경은 C++의 객체지향 개념도 지원합니다. 대표적인 예가 바로 시리얼 통신을 담당하는 Serial 객체입니다. Serial은 C++ 클래스의 인스턴스로, 메서드 호출 형태로 통신을 제어합니다.

void setup() {
  Serial.begin(9600);  // 시리얼 통신 시작, 속도(baud rate)를 9600으로 설정
}

void loop() {
  Serial.println("Hello, Arduino!"); // PC로 문자열을 전송 (줄바꿈 포함)
  delay(1000);                       // 1초마다 메시지 전송
}

위 예제에서 Serial.begin()은 통신을 초기화하는 "메서드"이고, Serial.println()은 데이터를 전송하는 "메서드"입니다. 이런 객체 지향 방식 덕분에 라이브러리들은 주로 "클래스 + 객체" 형태로 설계되어, 직관적인 코드로 복잡한 기능을 쉽게 다룰 수 있습니다.


아두이노 주요 핵심 함수들

아두이노는 초보자가 하드웨어를 쉽게 제어하도록, 자주 쓰는 기능을 간단한 함수들로 제공하고 있습니다. 대표적으로 다음과 같은 함수들이 있습니다.

  • pinMode(pin, mode) : 핀의 동작 모드 설정 (INPUT, OUTPUT 등)

  • digitalWrite(pin, value) : 디지털 핀에 HIGH/LOW 출력

  • digitalRead(pin) : 디지털 핀의 상태를 읽음 (버튼 입력 등)

  • analogWrite(pin, value) : PWM 출력 (0~255, 밝기/속도 제어 등)

  • analogRead(pin) : 아날로그 입력값 읽기 (0~1023, 센서 값 등)

  • delay(ms) : 지정한 시간(ms) 동안 프로그램 잠시 멈춤

이 함수들을 적절히 조합하면, 대부분의 간단한 센서·액추에이터 제어를 구현할 수 있습니다.


기본 제공 라이브러리 개요와 예제

아두이노 IDE에는 자주 사용하는 라이브러리들이 기본으로 포함되어 있습니다. 여기서는 Wire, SPI, Servo, EEPROM 네 가지를 살펴봅니다.

Wire 라이브러리 (I2C 통신)

Wire 라이브러리는 I2C(Inter-Integrated Circuit) 통신을 다루기 위한 라이브러리입니다. 두 개의 신호선(SDA, SCL)만으로 여러 개의 장치(온도 센서, OLED, IMU 등)를 연결할 수 있어 매우 많이 쓰입니다.

#include <Wire.h>

void setup() {
  Wire.begin();                 // I2C 마스터 초기화
  Wire.beginTransmission(8);    // 주소가 8인 슬레이브 장치로 전송 시작
  Wire.write("Hello!");         // 데이터 전송
  Wire.endTransmission();       // 전송 마무리
}

void loop() {
  // 반복 동작이 필요한 경우 여기에 작성
}

여기서 숫자 8은 I2C 버스에서 슬레이브 장치에 부여된 "주소"입니다.


SPI 라이브러리 (SPI 통신)

SPI(Serial Peripheral Interface)는 I2C보다 더 빠른 속도로 데이터를 주고받을 수 있는 통신 방식입니다. SD 카드, 고속 디스플레이, 일부 센서 등이 SPI를 사용합니다.

#include <SPI.h>

void setup() {
  SPI.begin();  // SPI 버스 초기화
}

void loop() {
  SPI.transfer(0xFF); // 한 바이트(0xFF)를 전송
  delay(1000);        // 1초마다 전송
}

SPI에서는 클럭, 데이터 송출/수신, 칩 선택(CS) 등 여러 라인을 사용하며, 사용하는 모듈에 맞게 핀과 모드를 맞춰야 합니다.


Servo 라이브러리 (서보 모터 제어)

Servo 라이브러리는 서보 모터를 각도로 제어할 수 있게 해 줍니다. 로봇 관절, 카메라 짐벌, 각도 조절 장치 등에서 많이 사용됩니다.

#include <Servo.h>

Servo myServo;  // 서보 객체 생성

void setup() {
  myServo.attach(9);  // 9번 핀에 서보 모터 연결
}

void loop() {
  myServo.write(90);  // 90도 위치로 이동
  delay(1000);
  myServo.write(0);   // 0도 위치로 이동
  delay(1000);
}

write(각도)에 0~180 사이의 값을 주면, 해당 각도 위치로 서보가 이동하게 됩니다(서보 종류에 따라 범위는 조금 다를 수 있음).


EEPROM 라이브러리 (내장 EEPROM 사용)

EEPROM은 전원이 꺼져도 데이터가 유지되는 비휘발성 메모리입니다. 설정 값, 보정 값, 카운터 등 "영구 저장"이 필요한 데이터를 저장할 때 사용합니다.

#include <EEPROM.h>

void setup() {
  Serial.begin(9600);

  EEPROM.write(0, 42);        // 주소 0번에 숫자 42 저장
  int value = EEPROM.read(0); // 주소 0번에서 데이터 읽기

  Serial.println(value);      // 읽은 값을 출력
}

void loop() {
  // 반복 동작이 없다면 비워둘 수 있음
}

EEPROM 쓰기는 수명이 제한되어 있기 때문에, 너무 자주(매 초 수천 번 등) 쓰지 않도록 주의해야 합니다.


외부 추가 라이브러리의 용도와 예제

기본 라이브러리 외에도, 전 세계 개발자와 회사들이 만든 수많은 외부 라이브러리를 사용할 수 있습니다. 여기서는 Adafruit Sensor, FastLED, LiquidCrystal, PubSubClient 네 가지를 다룹니다.

Adafruit Sensor 라이브러리 (다양한 센서 지원)

Adafruit Sensor는 여러 종류의 Adafruit 센서들을 통합적으로 다룰 수 있도록 만든 추상화 라이브러리입니다. BME280 같은 온습도·압력 센서와 함께 자주 사용됩니다.

#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

Adafruit_BME280 bme;

void setup() {
  Serial.begin(9600);
  bme.begin(0x76);  // I2C 주소 0x76으로 BME280 센서 초기화
}

void loop() {
  float temp = bme.readTemperature(); // 온도 읽기
  Serial.println(temp);               // 온도 출력
  delay(1000);                        // 1초마다 갱신
}

이 라이브러리를 사용하면 센서별 복잡한 레지스터 설정을 직접 하지 않고, 메서드 호출만으로 온도, 습도, 압력 등을 읽을 수 있습니다.


FastLED 라이브러리 (네오픽셀·WS2812 LED 제어)

FastLED는 WS2812(네오픽셀) 같은 개별 제어 가능한 RGB LED 스트립을 다루는 데 특화된 라이브러리입니다. 각 LED의 색을 자유롭게 설정하여 다양한 조명 효과를 만들 수 있습니다.

#include <FastLED.h>

#define NUM_LEDS 60
#define DATA_PIN 6

CRGB leds[NUM_LEDS];

void setup() {
  FastLED.addLeds<WS2812, DATA_PIN, GRB>(leds, NUM_LEDS); // LED 스트립 초기화
}

void loop() {
  leds[0] = CRGB::Red;  // 첫 번째 LED를 빨간색으로 설정
  FastLED.show();       // 실제 LED에 반영
  delay(500);
}

인덱스를 바꾸거나 루프를 돌리면, 여러 개의 LED에 다양한 패턴(무지개, 점등 애니메이션 등)을 쉽게 적용할 수 있습니다.


LiquidCrystal 라이브러리 (문자 LCD 제어)

LiquidCrystal은 16x2, 20x4 같은 문자형 LCD 모듈을 제어하는 라이브러리입니다. 센서 값, 상태 메시지 등을 실시간으로 화면에 보여 주고 싶을 때 사용합니다.

#include <LiquidCrystal.h>

// (RS, E, D4, D5, D6, D7) 핀 연결 순서
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

void setup() {
  lcd.begin(16, 2);          // 16x2 LCD 초기화
  lcd.print("Hello, World!"); // 첫 번째 줄에 문자 출력
}

void loop() {
  // 필요하다면 커서를 옮기거나 다른 데이터 출력
}

lcd.setCursor(col, row) 등을 사용하면 특정 위치에 값을 표시할 수 있습니다.


PubSubClient 라이브러리 (MQTT 기반 IoT 통신)

PubSubClient는 MQTT 프로토콜을 위한 클라이언트 라이브러리입니다. Wi-Fi 기능이 있는 ESP32/ESP8266 등과 함께 사용하여, 센서 데이터를 MQTT 브로커(예: Mosquitto, 클라우드 MQTT 서비스)로 보내거나, 원격 명령을 구독할 수 있습니다.

#include <PubSubClient.h>

// 실제로는 WiFi 설정, MQTT 서버 주소, 콜백 함수 등을 함께 설정해야 함
void setup() {
  // WiFi 연결 및 MQTT 서버 연결 설정 코드 작성
}

void loop() {
  // 주기적으로 MQTT 메시지 전송/수신 처리
}

실전에서는 WiFi 라이브러리(예: WiFi.h)와 함께 사용하여, 네트워크 연결 후 MQTT 서버에 접속하고, client.publish(), client.subscribe() 등으로 클라우드와 데이터를 주고받습니다.


아두이노·ESP32 라이브러리 설치 방법

외부 라이브러리를 사용하려면 먼저 아두이노 IDE에 설치해야 합니다. 대표적인 방법은 두 가지입니다.

1. 라이브러리 관리자 이용

  1. 아두이노 IDE 상단 메뉴에서 스케치 > 라이브러리 포함하기 > 라이브러리 관리...를 선택합니다.

  2. 나타나는 라이브러리 관리자 창에서 검색 창에 라이브러리 이름을 입력합니다. 예: Adafruit BME280, FastLED, PubSubClient 등.

  3. 목록에서 원하는 라이브러리를 선택하고 "설치" 버튼을 누릅니다.

  4. 설치 후, 스케치 상단에 #include <라이브러리이름.h>를 추가하여 사용합니다.

이 방법은 버전 관리와 업데이트가 간편하다는 장점이 있습니다.


2. ZIP 파일로 라이브러리 추가

GitHub나 공식 웹사이트에서 라이브러리를 ZIP 파일 형태로 제공하는 경우, 다음 순서로 설치합니다.

  1. 원하는 라이브러리의 GitHub 페이지 등에서 "Download ZIP"으로 압축 파일을 다운로드합니다.

  2. 아두이노 IDE 메뉴에서 스케치 > 라이브러리 포함하기 > .ZIP 라이브러리 추가...를 클릭합니다.

  3. 다운로드한 ZIP 파일을 선택하면 자동으로 설치됩니다.

  4. 설치 후에는 마찬가지로 #include <라이브러리이름.h>를 통해 사용할 수 있습니다.

직접 ZIP을 설치할 때는, 라이브러리 내부 폴더 구조가 깨지지 않도록 원본 그대로 사용하는 것이 좋습니다.


아두이노 스케치 프로그램 개발 과정

아두이노·ESP32 프로젝트를 체계적으로 진행하려면, 아래와 같은 단계로 개발 흐름을 잡는 것이 도움이 됩니다.

1. 목표 정의 및 설계

먼저 "무엇을 만들 것인가?"를 명확하게 정의합니다. 예를 들어, "온도 센서를 읽어 LCD에 표시", "버튼으로 LED 밝기 조절", "센서 값을 MQTT로 전송" 같은 형태로 목표를 구체화합니다.

목표가 정해지면 필요한 하드웨어(보드 종류, 센서, 모터, 디스플레이, 전원 등)를 목록으로 정리하고, 각 부품이 어떤 핀에 연결될지 간단한 회로/블록 다이어그램을 만듭니다.


2. 개발 환경 설정

  1. IDE 설치 Arduino 공식 사이트에서 아두이노 IDE를 설치합니다.

  2. 보드 및 드라이버 설치

    • UNO, Mega 같은 기본 보드는 IDE에 기본 포함되어 있습니다.

    • ESP32, ESP8266 등은 "보드 매니저"를 이용해 추가 보드를 설치해야 합니다.

    • 필요한 경우, 보드 드라이버도 설치합니다.

  3. 보드/포트 설정

    • 도구 > 보드에서 사용 중인 보드(예: Arduino Uno, ESP32 Dev Module)를 선택합니다.

    • 도구 > 포트에서 연결된 포트를 선택합니다(윈도우에서는 COM 포트, 맥/리눅스에서는 /dev/tty... 형식).

  4. 라이브러리 설치 사용할 센서·모듈에 필요한 라이브러리를 라이브러리 관리자나 ZIP 설치 방식으로 추가합니다. 예: Adafruit Sensor, LiquidCrystal, FastLED, PubSubClient 등.


3. 스케치 구조 이해 및 기본 코드 틀 만들기

프로젝트를 시작할 때는 항상 setup()loop()만 있는 기본 틀부터 작성합니다.

void setup() {
  // 초기 설정: 핀 모드, 시리얼, 라이브러리 초기화 등
}

void loop() {
  // 메인 로직: 센서 읽기, 계산, 출력 제어 등
}

여기에 필요한 라이브러리를 #include하고, 전역 변수와 객체(예: Servo myServo;, Adafruit_BME280 bme;)를 선언하면서 구조를 잡아 나갑니다.


4. 하드웨어 연결

보드와 컴퓨터를 USB 케이블로 연결한 후, 센서와 액추에이터를 지정한 핀에 연결합니다. 이때 다음 사항을 특히 주의합니다.

  • GND(접지)는 모든 장치가 공통으로 연결되었는지 확인

  • 전압 레벨(5V/3.3V)이 모듈 사양에 맞는지 확인

  • 데이터 핀(I2C, SPI, PWM 등)이 라이브러리 예제에서 사용한 핀과 일치하는지 확인

잘못 연결하면 동작 오류뿐 아니라 부품 손상 위험이 있으므로, 데이터시트나 예제 회로를 꼭 확인합니다.


5. 코드 작성 (단계적 개발)

처음부터 복잡한 기능을 모두 넣기보다는, 가장 간단한 동작부터 하나씩 추가하는 방식이 좋습니다.

  • 1단계: LED 한 개를 켜고 끄는 코드

  • 2단계: 버튼으로 LED 제어

  • 3단계: 센서값을 읽고 시리얼 모니터로 출력

  • 4단계: 센서값을 LCD나 LED 스트립에 표시

  • 5단계: 네트워크로 전송(MQTT, HTTP 등)

각 단계에서 충분히 테스트한 후 다음 기능을 더해 가면, 문제가 발생했을 때 어디에서 잘못됐는지 찾기 쉬워집니다. 코드에는 꼭 주석을 달아 나중에 보아도 이해할 수 있도록 합니다.


6. 컴파일 및 업로드

코드를 작성했다면, 아두이노 IDE 상단의 "검증(✔)" 버튼으로 컴파일 오류가 없는지 먼저 확인합니다. 오류가 있다면 IDE가 보여주는 오류 메시지를 보고, 오타나 잘못된 타입, 누락된 세미콜론, 잘못된 라이브러리 이름 등을 수정합니다.

오류가 없으면, "업로드(→)" 버튼을 눌러 보드에 코드를 업로드합니다. 업로드 중에는 보드의 TX/RX LED가 깜박이며, 업로드가 완료되면 보드가 자동으로 리셋되어 setup()부터 다시 실행됩니다.


7. 테스트 및 디버깅 (시리얼 모니터 활용)

업로드 후, 실제 동작이 의도대로 되는지 확인합니다. 문제가 있을 때는 시리얼 모니터를 적극적으로 활용하면 좋습니다.

void setup() {
  Serial.begin(9600);
  pinMode(A0, INPUT);
}

void loop() {
  int sensorValue = analogRead(A0);
  Serial.print("Sensor: ");
  Serial.println(sensorValue);
  delay(500);
}

도구 > 시리얼 모니터에서 값을 확인하면서, 센서가 정상적으로 입력되고 있는지, 조건문이 제대로 동작하는지 점검할 수 있습니다. 필요하다면 특정 조건에서만 디버깅 메시지를 출력하도록 하여, 과도한 로그 출력으로 인한 혼란을 줄입니다.


8. 프로젝트 확장, 최적화 및 배포

기본 기능이 정상적으로 동작하면, 다음과 같은 방향으로 프로젝트를 확장해 볼 수 있습니다.

  • 코드 구조화: 함수 분리, 파일 분리, 클래스 사용 등

  • 성능 최적화: 불필요한 delay() 줄이고 millis() 기반 논리 사용

  • 전력 최적화: 슬립 모드 활용, 센서 전원 제어 등

  • 통신 확장: MQTT, HTTP, BLE 등을 통해 클라우드/스마트폰과 연동

마지막으로, 완성된 코드는 백업하고, 실제 사용할 환경에 맞게 하드웨어를 고정·보호한 후 사용합니다. 다른 사람이 프로젝트를 이해할 수 있도록 회로도·코드·설명 문서를 함께 정리해 두면 좋습니다.


이 노트만 이해해도, 아두이노·ESP32 기반의 대부분 초급~중급 프로젝트(센서 읽기, 모터 제어, 디스플레이 출력, 간단한 IoT 통신)를 스스로 구현할 수 있는 기초가 마련됩니다. 앞으로는 관심 있는 센서나 통신 방식의 예제 코드를 직접 열어 보고, 라이브러리의 함수와 객체 구조를 살펴보면서 한 단계씩 확장해 나가면 됩니다.

이 노트는 요약·비평·학습 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.