1. 初始化ESP32-S3开发环境与多媒体外设

任务目标

本案例完成ESP32-S3开发环境搭建,并初始化三类多媒体外设:I2S(音频)、LCD(显示)、摄像头(图像采集)。最终通过统一的外设自检函数验证各外设寄存器可访问、通信总线正常,为后续多媒体案例提供可复用的初始化底座。

环境依赖

  • ESP-IDF v5.2+(推荐v5.2.3),安装路径无中文与空格
  • 开发板:ESP32-S3-DevKitC-1(或兼容板,带LCD/摄像头接口)
  • LCD:ST7789 SPI接口,分辨率240×320
  • 摄像头:OV2640,DVP接口,XCLK 20MHz
  • I2S:ES8311音频编解码器(或直连I2S DAC),使用标准I2S引脚

实现代码

以下代码为完整可编译的单一源文件,包含外设初始化与自检逻辑。使用menuconfig配置引脚后编译烧录。

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "driver/i2s_std.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "esp_camera.h"

static const char *TAG = "mm_init";

// ========== 引脚配置(根据实际接线修改) ==========
// I2S
#define I2S_BCK     4
#define I2S_WS      5
#define I2S_DOUT    6
// LCD SPI
#define LCD_SPI_CLK 10
#define LCD_SPI_MOSI 11
#define LCD_SPI_CS  12
#define LCD_DC      13
#define LCD_RST     14
#define LCD_BL      15
// 摄像头
#define CAM_PWDN    -1
#define CAM_RESET   -1
#define CAM_XCLK    20
#define CAM_SIOD    21
#define CAM_SIOC    22
#define CAM_D7      23
#define CAM_D6      24
#define CAM_D5      25
#define CAM_D4      26
#define CAM_D3      27
#define CAM_D2      28
#define CAM_D1      29
#define CAM_D0      30
#define CAM_VSYNC   31
#define CAM_HREF    33
#define CAM_PCLK    34

// ========== I2S 初始化 ==========
static void i2s_init(void) {
    i2s_std_config_t std_cfg = {
        .clk_cfg = {
            .sample_rate_hz = 44100,
            .clk_src = I2S_CLK_SRC_DEFAULT,
        },
        .slot_cfg = {
            .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
            .slot_bit_width = I2S_SLOT_BIT_WIDTH_16BIT,
            .slot_mode = I2S_SLOT_MODE_STEREO,
            .slot_mask = I2S_STD_SLOT_LEFT | I2S_STD_SLOT_RIGHT,
        },
        .gpio_cfg = {
            .mclk = I2S_GPIO_UNUSED,
            .bclk = I2S_BCK,
            .ws = I2S_WS,
            .dout = I2S_DOUT,
            .din = I2S_GPIO_UNUSED,
            .invert_flags = {
                .mclk_inv = false,
                .bclk_inv = false,
                .ws_inv = false,
            },
        },
    };

    i2s_chan_handle_t tx_chan = NULL;
    i2s_new_channel(&(i2s_chan_config_t){
        .id = I2S_NUM_0,
        .role = I2S_ROLE_MASTER,
        .dma_desc_num = 6,
        .dma_frame_num = 240,
    }, &tx_chan, NULL);
    i2s_channel_init_std_mode(tx_chan, &std_cfg);
    i2s_channel_enable(tx_chan);
    ESP_LOGI(TAG, "I2S initialized");
}

// ========== LCD 初始化 ==========
static void lcd_init(void) {
    spi_bus_config_t bus_cfg = {
        .sclk_io_num = LCD_SPI_CLK,
        .mosi_io_num = LCD_SPI_MOSI,
        .miso_io_num = -1,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = 240 * 320 * 2,
    };
    spi_bus_initialize(SPI2_HOST, &bus_cfg, SPI_DMA_CH_AUTO);

    esp_lcd_panel_io_handle_t io_handle = NULL;
    esp_lcd_panel_io_spi_config_t io_config = {
        .dc_gpio_num = LCD_DC,
        .cs_gpio_num = LCD_SPI_CS,
        .pclk_hz = 40 * 1000 * 1000,
        .lcd_cmd_bits = 8,
        .lcd_param_bits = 8,
        .spi_mode = 0,
        .trans_queue_depth = 10,
    };
    esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &io_handle);

    esp_lcd_panel_handle_t panel = NULL;
    esp_lcd_new_panel_st7789(io_handle, &panel, &(esp_lcd_panel_dev_config_t){
        .reset_gpio_num = LCD_RST,
        .color_space = ESP_LCD_COLOR_SPACE_RGB,
        .bits_per_pixel = 16,
    });
    esp_lcd_panel_reset(panel);
    esp_lcd_panel_init(panel);
    esp_lcd_panel_invert_color(panel, true);
    esp_lcd_panel_disp_on_off(panel, true);
    gpio_set_level(LCD_BL, 1);
    ESP_LOGI(TAG, "LCD initialized");
}

// ========== 摄像头初始化 ==========
static void camera_init(void) {
    camera_config_t cam_cfg = {
        .pin_pwdn = CAM_PWDN,
        .pin_reset = CAM_RESET,
        .pin_xclk = CAM_XCLK,
        .pin_sccb_sda = CAM_SIOD,
        .pin_sccb_scl = CAM_SIOC,
        .pin_d7 = CAM_D7,
        .pin_d6 = CAM_D6,
        .pin_d5 = CAM_D5,
        .pin_d4 = CAM_D4,
        .pin_d3 = CAM_D3,
        .pin_d2 = CAM_D2,
        .pin_d1 = CAM_D1,
        .pin_d0 = CAM_D0,
        .pin_vsync = CAM_VSYNC,
        .pin_href = CAM_HREF,
        .pin_pclk = CAM_PCLK,
        .xclk_freq_hz = 20000000,
        .ledc_timer = LEDC_TIMER_0,
        .ledc_channel = LEDC_CHANNEL_0,
        .pixel_format = PIXFORMAT_JPEG,
        .frame_size = FRAMESIZE_QVGA,
        .jpeg_quality = 12,
        .fb_count = 2,
        .fb_location = CAMERA_FB_IN_PSRAM,
        .grab_mode = CAMERA_GRAB_WHEN_EMPTY,
    };
    esp_err_t err = esp_camera_init(&cam_cfg);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Camera init failed: 0x%x", err);
        return;
    }
    ESP_LOGI(TAG, "Camera initialized");
}

// ========== 外设自检 ==========
static void peripheral_self_test(void) {
    // I2S:检查通道是否启用(通过DMA描述符状态间接验证)
    i2s_chan_handle_t chan = NULL;
    esp_err_t ret = i2s_new_channel(&(i2s_chan_config_t){
        .id = I2S_NUM_0,
        .role = I2S_ROLE_MASTER,
    }, &chan, NULL);
    if (ret == ESP_ERR_INVALID_ARG) {
        ESP_LOGI(TAG, "[PASS] I2S channel already in use");
    } else {
        ESP_LOGE(TAG, "[FAIL] I2S not initialized");
        if (chan) i2s_del_channel(chan);
    }

    // LCD:读取面板ID寄存器(0x04)
    esp_lcd_panel_io_handle_t io = NULL;
    // 实际项目中此处应保存panel io句柄,这里通过重新初始化验证总线
    spi_device_handle_t spi_dev;
    spi_bus_add_device(SPI2_HOST, &(spi_device_interface_config_t){
        .mode = 0,
        .clock_speed_hz = 1 * 1000 * 1000,
        .spics_io_num = LCD_SPI_CS,
        .queue_size = 1,
    }, &spi_dev);
    uint8_t cmd = 0x04, data = 0;
    spi_transaction_t t = {
        .tx_buffer = &cmd,
        .rx_buffer = &data,
        .length = 8,
        .rxlength = 8,
    };
    spi_device_transmit(spi_dev, &t);
    spi_bus_remove_device(spi_dev);
    if (data != 0x00) {
        ESP_LOGI(TAG, "[PASS] LCD responds (ID=0x%02x)", data);
    } else {
        ESP_LOGW(TAG, "[WARN] LCD ID read 0x00 (may be normal for some panels)");
    }

    // 摄像头:检查SCCB总线上的传感器ID
    sensor_t *sensor = esp_camera_sensor_get();
    if (sensor != NULL) {
        int pid = sensor->id.PID;
        ESP_LOGI(TAG, "[PASS] Camera sensor PID=0x%02x", pid);
    } else {
        ESP_LOGE(TAG, "[FAIL] Camera sensor not found");
    }
}

void app_main(void) {
    ESP_LOGI(TAG, "=== ESP32-S3 Multimedia Peripheral Init ===");
    gpio_set_direction(LCD_BL, GPIO_MODE_OUTPUT);
    i2s_init();
    lcd_init();
    camera_init();
    vTaskDelay(pdMS_TO_TICKS(100)); // 等待外设稳定
    peripheral_self_test();
    ESP_LOGI(TAG, "=== Init sequence complete ===");
}

编译与烧录

idf.py set-target esp32s3
idf.py menuconfig  # 确认PSRAM已启用(摄像头需要)
idf.py build
idf.py -p /dev/ttyUSB0 flash monitor

验证方法

烧录后观察串口输出,满足以下条件即判定通过:

  • 依次出现I2S initialized、LCD initialized、Camera initialized日志,无红色错误输出
  • 自检段出现[PASS] I2S channel already in use(证明I2S通道已占用)
  • LCD自检输出[PASS] LCD responds或[WARN](WARN表示面板ID寄存器返回0,但总线通信正常)
  • 摄像头自检输出[PASS] Camera sensor PID=0x26xx(OV2640的PID为0x2642)

若摄像头初始化失败,检查PSRAM是否在menuconfig中启用(Component config → ESP32S3-Specific → Support for external, SPI-connected RAM),并确认SCCB上拉电阻已焊接。