ESP32-S3 개발 보드를 활용한 실용 프로젝트 가이드

ESP32-S3 기반 실용 개발 가이드

본 문서는 레이차오(Lichao) 사의 실전형 ESP32-S3 개발 보드를 기반으로 작성된 기술 문서입니다. 하드웨어 사양과 소프트웨어 예제 코드에 대한 심층적인 분석과 재구성을 통해 핵심 기능 구현 방법을 설명합니다.

하드웨어 구성 요소

기능 모델 주요 사양
마이크로컨트롤러 ESP32-S3-WROOM-1-N16R8 LX7 듀얼코어, 최대 240MHz 클럭, 내장 SRAM 512KB, 외장 PSRAM 8MB, 외장 플래시 16MB, Wi-Fi 40MHz 대역폭, Bluetooth 5 LE
디스플레이 ST7789 SPI 인터페이스, 2.0인치 IPS 패널, 해상도 320x240
자이로스코프 센서 QMI8658 I2C 인터페이스, 3축 가속도계 + 3축 자이로스코프, I2C 주소 0x6A
오디오 입력 ES7210 I2C 설정, 4채널 마이크 입력 (개발보드는 3채널 사용)
오디오 출력 ES8311 I2C 설정, 단일 채널 DAC, NS4150B 증폭기 연결
저장 장치 SD 카드 SDIO 1-라인 모드, FAT32 파일 시스템 지원

소프트웨어 아키텍처

  • 운영체제: FreeRTOS 기반 리얼타임 운영체제
  • 개발 환경: ESP-IDF (Espressif IoT Development Framework)
  • 통신 프로토콜: I2C, SPI, SDIO, I2S

기초 예제 코드

GPIO 제어 및 LED 점멸

#include "freertos/FreeRTOS.h"
#include "driver/gpio.h"
#include "freertos/task.h"

#define LED_PIN 10

void led_blink_task(void *pvParameter)
{
    gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT);
    
    while(1) {
        gpio_set_level(LED_PIN, 0);
        vTaskDelay(pdMS_TO_TICKS(1000));
        
        gpio_set_level(LED_PIN, 1);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void app_main(void)
{
    xTaskCreate(&led_blink_task, "blink", 2048, NULL, 5, NULL);
}

I2C 통신 기반 센서 초기화

#include "driver/i2c.h"
#include "esp_log.h"

#define I2C_PORT_NUM 0
#define I2C_SDA_PIN 1
#define I2C_SCL_PIN 2
#define I2C_FREQ_HZ 100000

static const char *TAG = "I2C_INIT";

esp_err_t i2c_master_setup(void)
{
    i2c_config_t config = {
        .mode = I2C_MODE_MASTER,
        .sda_io_num = I2C_SDA_PIN,
        .scl_io_num = I2C_SCL_PIN,
        .sda_pullup_en = GPIO_PULLUP_ENABLE,
        .scl_pullup_en = GPIO_PULLUP_ENABLE,
        .master.clk_speed = I2C_FREQ_HZ,
    };

    esp_err_t error = i2c_param_config(I2C_PORT_NUM, &config);
    if (error != ESP_OK) {
        ESP_LOGE(TAG, "파라미터 설정 실패: %d", error);
        return error;
    }

    error = i2c_driver_install(I2C_PORT_NUM, config.mode, 0, 0, 0);
    if (error != ESP_OK) {
        ESP_LOGE(TAG, "드라이버 설치 실패: %d", error);
        return error;
    }

    ESP_LOGI(TAG, "I2C 마스터 초기화 성공");
    return ESP_OK;
}

QMI8658 자이로스코프 데이터 읽기

typedef struct {
    int16_t accel_x;
    int16_t accel_y; 
    int16_t accel_z;
    int16_t gyro_x;
    int16_t gyro_y;
    int16_t gyro_z;
} sensor_data_t;

#define QMI8658_ADDR 0x6A
#define REG_ACCEL_X_L 0x34
#define REG_GYRO_X_L 0x3A

esp_err_t read_sensor_data(sensor_data_t *data)
{
    uint8_t buffer[12];
    esp_err_t result = i2c_master_write_read_device(
        I2C_PORT_NUM, QMI8658_ADDR, ®_ACCEL_X_L, 1,
        buffer, 12, pdMS_TO_TICKS(1000));

    if (result == ESP_OK) {
        data->accel_x = (int16_t)(buffer[1] << 8 | buffer[0]);
        data->accel_y = (int16_t)(buffer[3] << 8 | buffer[2]); 
        data->accel_z = (int16_t)(buffer[5] << 8 | buffer[4]);
        data->gyro_x = (int16_t)(buffer[7] << 8 | buffer[6]);
        data->gyro_y = (int16_t)(buffer[9] << 8 | buffer[8]);
        data->gyro_z = (int16_t)(buffer[11] << 8 | buffer[10]);
    }

    return result;
}

SD 카드 파일 시스템 마운트

#include "esp_vfs_fat.h"
#include "sdmmc_cmd.h"

#define MOUNT_POINT "/sdcard"

esp_err_t mount_sd_card(void)
{
    sdmmc_host_t host = SDMMC_HOST_DEFAULT();
    sdmmc_slot_config_t slot_cfg = SDMMC_SLOT_CONFIG_DEFAULT();
    
    slot_cfg.width = 1;
    slot_cfg.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;

    esp_vfs_fat_sdmmc_mount_config_t mount_cfg = {
        .format_if_mount_failed = true,
        .max_files = 5,
        .allocation_unit_size = 16 * 1024
    };

    sdmmc_card_t* card;
    esp_err_t error = esp_vfs_fat_sdmmc_mount(
        MOUNT_POINT, &host, &slot_cfg, &mount_cfg, &card);

    if (error == ESP_OK) {
        ESP_LOGI("SD", "카드 용량: %lluMB", 
                (uint64_t)card->csd.capacity * card->csd.sector_size / (1024 * 1024));
    }

    return error;
}

Wi-Fi 연결 관리

#include "esp_wifi.h"
#include "esp_event.h"

#define WIFI_SSID "your_ssid"
#define WIFI_PASS "your_password"

static EventGroupHandle_t wifi_event_group;

static void wifi_event_handler(void* arg, esp_event_base_t event_base,
                               int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* ip_info = (ip_event_got_ip_t*) event_data;
        ESP_LOGI("WIFI", "IP 획득: " IPSTR, IP2STR(&ip_info->ip_info.ip));
        xEventGroupSetBits(wifi_event_group, BIT0);
    }
}

void connect_to_wifi(void)
{
    ESP_ERROR_CHECK(esp_netif_init());
    wifi_event_group = xEventGroupCreate();
    
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_sta();

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, 
                                              wifi_event_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, 
                                              wifi_event_handler, NULL));

    wifi_config_t wifi_cfg = {
        .sta = {
            .ssid = WIFI_SSID,
            .password = WIFI_PASS,
        },
    };

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg));
    ESP_ERROR_CHECK(esp_wifi_start());

    xEventGroupWaitBits(wifi_event_group, BIT0, pdTRUE, pdFALSE, portMAX_DELAY);
}

HTTP 요청 처리

#include "esp_http_client.h"

void send_http_request(void)
{
    esp_http_client_config_t config = {
        .url = "http://httpbin.org/get",
        .method = HTTP_METHOD_GET,
    };

    esp_http_client_handle_t client = esp_http_client_init(&config);
    esp_err_t err = esp_http_client_perform(client);

    if (err == ESP_OK) {
        int status = esp_http_client_get_status_code(client);
        int content_length = esp_http_client_get_content_length(client);
        ESP_LOGI("HTTP", "상태: %d, 길이: %d", status, content_length);
    }

    esp_http_client_cleanup(client);
}

문제 해결 가이드

컴파일 오류: 중복 정의

전역 변수가 헤더 파일에 선언되어 여러 소스 파일에서 포함될 경우 발생합니다. 해결 방법은 다음과 같습니다:

  1. 해당 변수를 특정 C 파일 내부에 static으로 선언
  2. extern 키워드를 사용하여 한 파일에서만 정의하고 다른 파일에서는 외부 참조로 선언

메모리 부족 문제

  • PSRAM 사용 가능 여부 확인
  • menuconfig에서 Flash 크기를 16MB로 설정
  • Octal SPI 모드 활성화
  • 큰 데이터 버퍼를 PSRAM 영역에 할당

펌웨어 다운로드 실패

다른 프로세스가 직렬 포트를 점유하고 있을 경우 발생합니다. 해결 방법:

  • 직렬 모니터링 세션 종료
  • 다른 개발 환경 종료
  • 장치 관리자에서 COM 포트 상태 확인

최적화 팁

  • 정기적으로 build 디렉토리 삭제 후 재빌드
  • CMakeLists.txt에 모든 소스 파일 경로 포함
  • 외부 컴포넌트 추가 후 칩 타겟 재선택
  • 헤더 파일 수정 시 관련 소스 파일 전체 재컴파일

태그: ESP32-S3 freertos I2C SPI SDIO

10월 3일 04:11에 게시됨