ESP32-S3プログラミング開発ガイド

XiaoZhi AIプロジェクトの実戦経験に基づき、本ガイドはESP32-S3のプログラミング開発について詳しく説明します。基本的な周辺機器制御から複雑なAIアプリケーションまでの完全な開発フローを紹介します。

一、開発環境の選択

1.1 開発フレームワーク比較

フレームワークESP-IDFArduino ESP32PlatformIO
複雑さ
パフォーマンス最優一般
機能完全性完全制限あり比較的完全
デバッグ能力
XiaoZhi推奨⭐⭐⭐⭐⭐⭐⭐⭐
推奨: XiaoZhi AIプロジェクトはESP-IDFを使用してコア開発を行い、最適なパフォーマンスと完全な機能サポートを確保

1.2 ESP-IDF開発環境構築

Windows環境設定

# 1. ESP-IDF 5.3.2をダウンロード
# https://dl.espressif.com/dl/esp-idf/ からダウンロード

# 2. 非Cドライブディレクトリにインストール
# 例:D:\Espressif\esp-idf

# 3. 環境変数設定(自動完了)
# デスクトップの「ESP-IDF 5.3 PowerShell」をダブルクリック

# 4. インストール確認
idf.py --version
# 出力:ESP-IDF v5.3.2

Linux/macOS環境

# 1. 依存関係インストール
sudo apt-get install git wget flex bison gperf python3-pip

# 2. ESP-IDFクローン
git clone -b v5.3.2 --recursive https://github.com/espressif/esp-idf.git

# 3. ツールチェーンインストール
cd esp-idf
./install.sh

# 4. 環境変数設定
. ./export.sh

1.3 Arduino開発環境(オプション)

# Arduino IDE 2.x設定
# ファイル -> 環境設定 -> 追加のボードマネージャのURL
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

# ツール -> ボード -> ボードマネージャ
# 「esp32」を検索してインストール

# ボード選択:ESP32S3 Dev Module

二、基本GPIO プログラミング

2.1 デジタルIO制御

LED制御例(ESP-IDF)

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

#define LED_PIN GPIO_NUM_48  // オンボードRGB LED

void app_main() {
    // GPIOを出力として設定
    gpio_reset_pin(LED_PIN);
    gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT);
    
    while(1) {
        gpio_set_level(LED_PIN, 1);  // LED点灯
        vTaskDelay(pdMS_TO_TICKS(500));
        gpio_set_level(LED_PIN, 0);  // LED消灯
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

ボタン入力処理

#include "driver/gpio.h"

#define BUTTON_PIN GPIO_NUM_0  // Bootボタン

void button_init() {
    gpio_reset_pin(BUTTON_PIN);
    gpio_set_direction(BUTTON_PIN, GPIO_MODE_INPUT);
    gpio_set_pull_mode(BUTTON_PIN, GPIO_PULLUP_ENABLE);
}

bool is_button_pressed() {
    return gpio_get_level(BUTTON_PIN) == 0;  // 押下時は低レベル
}

2.2 PWM調光制御

#include "driver/ledc.h"

#define PWM_CHANNEL LEDC_CHANNEL_0
#define PWM_TIMER   LEDC_TIMER_0
#define PWM_PIN     GPIO_NUM_2
#define PWM_FREQ    5000  // 5kHz
#define PWM_RESOLUTION LEDC_TIMER_8_BIT  // 8ビット解像度(0-255)

void pwm_init() {
    // タイマー設定
    ledc_timer_config_t timer_config = {
        .speed_mode = LEDC_LOW_SPEED_MODE,
        .timer_num = PWM_TIMER,
        .duty_resolution = PWM_RESOLUTION,
        .freq_hz = PWM_FREQ,
        .clk_cfg = LEDC_AUTO_CLK
    };
    ledc_timer_config(&timer_config);
    
    // チャネル設定
    ledc_channel_config_t channel_config = {
        .channel = PWM_CHANNEL,
        .duty = 0,
        .gpio_num = PWM_PIN,
        .speed_mode = LEDC_LOW_SPEED_MODE,
        .timer_sel = PWM_TIMER
    };
    ledc_channel_config(&channel_config);
}

void set_brightness(uint8_t brightness) {
    ledc_set_duty(LEDC_LOW_SPEED_MODE, PWM_CHANNEL, brightness);
    ledc_update_duty(LEDC_LOW_SPEED_MODE, PWM_CHANNEL);
}

三、オーディオシステムプログラミング

3.1 I2Sオーディオインターフェース設定

マイクロフォンインターフェース(INMP441)

#include "driver/i2s.h"

#define I2S_MIC_PORT    I2S_NUM_0
#define I2S_MIC_WS      GPIO_NUM_4
#define I2S_MIC_SCK     GPIO_NUM_5  
#define I2S_MIC_SD      GPIO_NUM_6
#define SAMPLE_RATE     16000
#define SAMPLE_BITS     I2S_BITS_PER_SAMPLE_32BIT

void i2s_mic_init() {
    i2s_config_t i2s_config = {
        .mode = I2S_MODE_MASTER | I2S_MODE_RX,
        .sample_rate = SAMPLE_RATE,
        .bits_per_sample = SAMPLE_BITS,
        .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
        .communication_format = I2S_COMM_FORMAT_STAND_I2S,
        .intr_alloc_flags = ESP_INTR_FLAG_LEVEL2,
        .dma_buf_count = 8,
        .dma_buf_len = 1024,
    };
    
    i2s_pin_config_t pin_config = {
        .ws_io_num = I2S_MIC_WS,
        .ck_io_num = I2S_MIC_SCK,
        .data_in_num = I2S_MIC_SD,
        .data_out_num = I2S_PIN_NO_CHANGE
    };
    
    i2s_driver_install(I2S_MIC_PORT, &i2s_config, 0, NULL);
    i2s_set_pin(I2S_MIC_PORT, &pin_config);
}

size_t read_microphone(int32_t* buffer, size_t samples) {
    size_t bytes_read;
    i2s_read(I2S_MIC_PORT, buffer, samples * sizeof(int32_t), 
             &bytes_read, portMAX_DELAY);
    return bytes_read / sizeof(int32_t);
}

スピーカーインターフェース(MAX98357A)

#define I2S_SPK_PORT    I2S_NUM_1
#define I2S_SPK_BCLK    GPIO_NUM_15
#define I2S_SPK_LRC     GPIO_NUM_16
#define I2S_SPK_DIN     GPIO_NUM_7

void i2s_speaker_init() {
    i2s_config_t i2s_config = {
        .mode = I2S_MODE_MASTER | I2S_MODE_TX,
        .sample_rate = 16000,
        .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
        .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
        .communication_format = I2S_COMM_FORMAT_STAND_I2S,
        .intr_alloc_flags = ESP_INTR_FLAG_LEVEL2,
        .dma_buf_count = 8,
        .dma_buf_len = 1024,
    };
    
    i2s_pin_config_t pin_config = {
        .ws_io_num = I2S_SPK_LRC,
        .ck_io_num = I2S_SPK_BCLK,
        .data_out_num = I2S_SPK_DIN,
        .data_in_num = I2S_PIN_NO_CHANGE
    };
    
    i2s_driver_install(I2S_SPK_PORT, &i2s_config, 0, NULL);
    i2s_set_pin(I2S_SPK_PORT, &pin_config);
}

void play_audio(const int16_t* audio_data, size_t samples) {
    size_t bytes_written;
    i2s_write(I2S_SPK_PORT, audio_data, samples * sizeof(int16_t),
              &bytes_written, portMAX_DELAY);
}

3.2 オーディオ信号処理

音量制御

#include <math.h>

void adjust_volume(int16_t* audio_buffer, size_t samples, float volume) {
    // volume範囲:0.0-2.0(0%〜200%)
    for(size_t i = 0; i < samples; i++) {
        float sample = audio_buffer[i] * volume;
        
        // オーバーフロー防止
        if(sample > 32767) sample = 32767;
        if(sample < -32768) sample = -32768;
        
        audio_buffer[i] = (int16_t)sample;
    }
}

四、ネットワーク通信プログラミング

4.1 Wi-Fi接続管理

Wi-Fiイベント処理

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

static EventGroupHandle_t wifi_event_group;
const int WIFI_CONNECTED_BIT = BIT0;

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 == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        esp_wifi_connect();
        ESP_LOGI("WiFi", "Wi-Fi再接続中...");
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI("WiFi", "接続成功、IP:" IPSTR, IP2STR(&event->ip_info.ip));
        xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

void wifi_init(const char* ssid, const char* password) {
    wifi_event_group = xEventGroupCreate();
    
    ESP_ERROR_CHECK(esp_netif_init());
    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_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, 
                                       &wifi_event_handler, NULL, NULL);
    esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
                                       &wifi_event_handler, NULL, NULL);
    
    wifi_config_t wifi_config = {};
    strcpy((char*)wifi_config.sta.ssid, ssid);
    strcpy((char*)wifi_config.sta.password, password);
    
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());
    
    // 接続待機
    xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_BIT, false, true, 
                       portMAX_DELAY);
}

4.2 WebSocketクライアント

XiaoZhi AI WebSocket通信

#include "esp_websocket_client.h"

static esp_websocket_client_handle_t client;

static void websocket_event_handler(void *handler_args, esp_event_base_t base,
                                   int32_t event_id, void *event_data) {
    esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
    
    switch (event_id) {
        case WEBSOCKET_EVENT_CONNECTED:
            ESP_LOGI("WebSocket", "接続成功");
            break;
            
        case WEBSOCKET_EVENT_DATA:
            ESP_LOGI("WebSocket", "データ受信: %.*s", data->data_len, (char*)data->data_ptr);
            // AIサーバーレスポンス処理
            handle_ai_response((char*)data->data_ptr, data->data_len);
            break;
            
        case WEBSOCKET_EVENT_ERROR:
            ESP_LOGE("WebSocket", "接続エラー");
            break;
            
        case WEBSOCKET_EVENT_DISCONNECTED:
            ESP_LOGI("WebSocket", "接続切断");
            break;
    }
}

void websocket_init(const char* uri) {
    esp_websocket_client_config_t websocket_cfg = {
        .uri = uri,
        .task_stack = 4096,
    };
    
    client = esp_websocket_client_init(&websocket_cfg);
    esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, 
                                 websocket_event_handler, (void *)client);
    esp_websocket_client_start(client);
}

void send_audio_data(const char* audio_base64) {
    char json_message[2048];
    snprintf(json_message, sizeof(json_message),
             "{\"type\":\"audio\",\"data\":\"%s\"}", audio_base64);
    
    esp_websocket_client_send_text(client, json_message, strlen(json_message), 
                                  portMAX_DELAY);
}

五、センサーインターフェースプログラミング

5.1 I2C通信

OLEDディスプレイ(SSD1306)

#include "driver/i2c.h"

#define I2C_MASTER_PORT     I2C_NUM_0
#define I2C_MASTER_SDA_IO   GPIO_NUM_41
#define I2C_MASTER_SCL_IO   GPIO_NUM_42
#define I2C_MASTER_FREQ_HZ  100000
#define SSD1306_ADDR        0x3C

void i2c_master_init() {
    i2c_config_t conf = {
        .mode = I2C_MODE_MASTER,
        .sda_io_num = I2C_MASTER_SDA_IO,
        .scl_io_num = I2C_MASTER_SCL_IO,
        .sda_pullup_en = GPIO_PULLUP_ENABLE,
        .scl_pullup_en = GPIO_PULLUP_ENABLE,
        .master.clk_speed = I2C_MASTER_FREQ_HZ,
    };
    
    i2c_param_config(I2C_MASTER_PORT, &conf);
    i2c_driver_install(I2C_MASTER_PORT, conf.mode, 0, 0, 0);
}

void ssd1306_write_command(uint8_t cmd) {
    i2c_cmd_handle_t cmd_handle = i2c_cmd_link_create();
    i2c_master_start(cmd_handle);
    i2c_master_write_byte(cmd_handle, SSD1306_ADDR << 1 | I2C_MASTER_WRITE, true);
    i2c_master_write_byte(cmd_handle, 0x00, true);  // コマンドモード
    i2c_master_write_byte(cmd_handle, cmd, true);
    i2c_master_stop(cmd_handle);
    i2c_master_cmd_begin(I2C_MASTER_PORT, cmd_handle, pdMS_TO_TICKS(1000));
    i2c_cmd_link_delete(cmd_handle);
}

六、デバッグと最適化

6.1 ログシステム

#include "esp_log.h"

// ログタグ定義
static const char* TAG_AUDIO = "AUDIO";
static const char* TAG_WIFI = "WIFI";
static const char* TAG_AI = "AI";

void debug_log_example() {
    // 異なるレベルのログ
    ESP_LOGE(TAG_AUDIO, "エラー:オーディオ初期化失敗");
    ESP_LOGW(TAG_WIFI, "警告:Wi-Fi信号弱(-75dBm)");
    ESP_LOGI(TAG_AI, "情報:AI応答時間 %dms", 1250);
    ESP_LOGD(TAG_AUDIO, "デバッグ:オーディオバッファ %d/%d", 512, 1024);
    ESP_LOGV(TAG_AI, "詳細:HTTPリクエストヘッダ完了");
    
    // 16進データ印刷
    uint8_t buffer[16] = {0x01, 0x02, 0x03, 0x04};
    ESP_LOG_BUFFER_HEX(TAG_AUDIO, buffer, 16);
}

// 実行時ログレベル設定
void set_log_levels() {
    esp_log_level_set("AUDIO", ESP_LOG_DEBUG);
    esp_log_level_set("WIFI", ESP_LOG_INFO);
    esp_log_level_set("AI", ESP_LOG_WARN);
}

6.2 パフォーマンス監視

#include "esp_timer.h"
#include "esp_system.h"

// 実行時間測定
uint64_t measure_execution_time(void (*function)()) {
    uint64_t start = esp_timer_get_time();
    function();
    uint64_t end = esp_timer_get_time();
    return end - start;  // マイクロ秒
}

// メモリ使用監視
void print_memory_usage() {
    size_t free_heap = esp_get_free_heap_size();
    size_t min_free = esp_get_minimum_free_heap_size();
    
    ESP_LOGI("MEMORY", "空きヒープメモリ:%d bytes", free_heap);
    ESP_LOGI("MEMORY", "最小空き:%d bytes", min_free);
}

// CPU使用率監視
void print_task_stats() {
    char buffer[1024];
    vTaskGetRunTimeStats(buffer);
    ESP_LOGI("TASKS", "タスク実行統計:\n%s", buffer);
}

次のステップ: