3. platformio.ini配置
; platformio.ini
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
; 启用PSRAM(如果板子支持)
board_build.arduino.memory_type = qio_opi
; 增加闪存分区大小(可选)
board_build.partitions = default_16MB.csv
; 编译优化
build_flags =
-DBOARD_HAS_PSRAM
-mfix-esp32-psram-cache-issue
; 如果需要更多内存,可以禁用某些功能
; -DCONFIG_ARDUINO_ISR_IRAM=0
; 库依赖
lib_deps =
; 如果需要高级音频处理,可以添加这些库
; https://github.com/pschatzmann/arduino-audio-tools
; pschatzmann/ESP32-A2DP@^1.9.3
; 串口监视器配置
monitor_filters =
time
colorize
#include <Arduino.h>
#include "driver/i2s.h"
// 硬件引脚定义
#define I2S_WS_PIN 25 // Word Select (LRCLK)
#define I2S_BCK_PIN 26 // Bit Clock (BCLK)
#define I2S_DIN_PIN 33 // INMP441数据输出
#define I2S_DOUT_PIN 22 // MAX98357数据输入
#define BUTTON_PIN 27 // 控制按键
#define LED_PIN 2 // 状态指示灯
// I2S参数配置
#define SAMPLE_RATE 16000 // 采样率
#define SAMPLE_BITS 16 // 采样位数
#define BUFFER_LEN 1024 // 缓冲区长度
#define RECORD_SECONDS 5 // 录制时长(秒)
#define MAX_SAMPLES (SAMPLE_RATE * RECORD_SECONDS)
// 全局变量
int16_t *audio_buffer = nullptr;
volatile bool is_recording = false;
volatile bool is_playing = false;
volatile bool button_pressed = false;
size_t total_samples_recorded = 0;
// I2S初始化函数
void setupI2S() {
Serial.println("正在初始化I2S接口...");
// I2S输入配置(INMP441)
i2s_config_t i2s_config_in = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = SAMPLE_RATE,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = I2S_COMM_FORMAT_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 8,
.dma_buf_len = BUFFER_LEN,
.use_apll = false,
.tx_desc_auto_clear = false,
.fixed_mclk = 0
};
// I2S输出配置(MAX98357)
i2s_config_t i2s_config_out = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = SAMPLE_RATE,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = I2S_COMM_FORMAT_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 8,
.dma_buf_len = BUFFER_LEN,
.use_apll = false,
.tx_desc_auto_clear = false,
.fixed_mclk = 0
};
// 引脚配置
i2s_pin_config_t pin_config_in = {
.bck_io_num = I2S_BCK_PIN,
.ws_io_num = I2S_WS_PIN,
.data_out_num = I2S_PIN_NO_CHANGE,
.data_in_num = I2S_DIN_PIN
};
i2s_pin_config_t pin_config_out = {
.bck_io_num = I2S_BCK_PIN,
.ws_io_num = I2S_WS_PIN,
.data_out_num = I2S_DOUT_PIN,
.data_in_num = I2S_PIN_NO_CHANGE
};
// 安装并启动I2S驱动
esp_err_t err;
err = i2s_driver_install(I2S_NUM_0, &i2s_config_in, 0, NULL);
if (err != ESP_OK) {
Serial.printf("I2S输入驱动安装失败: %s\n", esp_err_to_name(err));
return;
}
i2s_set_pin(I2S_NUM_0, &pin_config_in);
err = i2s_driver_install(I2S_NUM_1, &i2s_config_out, 0, NULL);
if (err != ESP_OK) {
Serial.printf("I2S输出驱动安装失败: %s\n", esp_err_to_name(err));
return;
}
i2s_set_pin(I2S_NUM_1, &pin_config_out);
Serial.println("I2S初始化完成");
}
// 按键中断服务程序
void IRAM_ATTR buttonISR() {
button_pressed = true;
}
// 录制音频
void recordAudio() {
if (audio_buffer != nullptr) {
free(audio_buffer);
audio_buffer = nullptr;
}
// 分配内存(使用PSRAM如果可用)
#ifdef BOARD_HAS_PSRAM
audio_buffer = (int16_t*)ps_malloc(MAX_SAMPLES * sizeof(int16_t));
Serial.println("使用PSRAM分配音频缓冲区");
#else
audio_buffer = (int16_t*)malloc(MAX_SAMPLES * sizeof(int16_t));
Serial.println("使用内部RAM分配音频缓冲区");
#endif
if (audio_buffer == nullptr) {
Serial.println("内存分配失败!");
return;
}
Serial.println("开始录制...");
Serial.printf("录制时长: %d秒\n", RECORD_SECONDS);
is_recording = true;
digitalWrite(LED_PIN, HIGH);
size_t bytes_read = 0;
int16_t temp_buffer[BUFFER_LEN];
total_samples_recorded = 0;
unsigned long start_time = millis();
while (millis() - start_time < RECORD_SECONDS * 1000) {
// 从I2S读取数据
i2s_read(I2S_NUM_0, temp_buffer, BUFFER_LEN * sizeof(int16_t),
&bytes_read, portMAX_DELAY);
// 复制到主缓冲区
size_t samples_read = bytes_read / sizeof(int16_t);
if (total_samples_recorded + samples_read <= MAX_SAMPLES) {
memcpy(audio_buffer + total_samples_recorded,
temp_buffer, bytes_read);
total_samples_recorded += samples_read;
}
// 显示进度
static int last_percent = -1;
int percent = (millis() - start_time) * 100 / (RECORD_SECONDS * 1000);
if (percent != last_percent && percent % 10 == 0) {
Serial.printf("录制进度: %d%%\n", percent);
last_percent = percent;
}
}
is_recording = false;
digitalWrite(LED_PIN, LOW);
Serial.println("录制完成!");
Serial.printf("录制了 %d 个样本 (%d KB)\n",
total_samples_recorded,
total_samples_recorded * sizeof(int16_t) / 1024);
}
// 播放音频
void playAudio() {
if (audio_buffer == nullptr || total_samples_recorded == 0) {
Serial.println("没有音频数据可播放!");
return;
}
Serial.println("开始播放...");
is_playing = true;
digitalWrite(LED_PIN, HIGH);
size_t samples_played = 0;
size_t bytes_written = 0;
while (samples_played < total_samples_recorded) {
size_t remaining_samples = total_samples_recorded - samples_played;
size_t samples_to_write = min((size_t)BUFFER_LEN, remaining_samples);
i2s_write(I2S_NUM_1,
audio_buffer + samples_played,
samples_to_write * sizeof(int16_t),
&bytes_written,
portMAX_DELAY);
samples_played += bytes_written / sizeof(int16_t);
// 显示进度
static int last_percent = -1;
int percent = samples_played * 100 / total_samples_recorded;
if (percent != last_percent && percent % 20 == 0) {
Serial.printf("播放进度: %d%%\n", percent);
last_percent = percent;
}
}
is_playing = false;
digitalWrite(LED_PIN, LOW);
Serial.println("播放完成!");
}
// 显示系统信息
void showSystemInfo() {
Serial.println("\n=== ESP32语音录制回放系统 ===");
Serial.printf("采样率: %d Hz\n", SAMPLE_RATE);
Serial.printf("采样位数: %d 位\n", SAMPLE_BITS);
Serial.printf("最大录制时长: %d 秒\n", RECORD_SECONDS);
Serial.printf("最大样本数: %d\n", MAX_SAMPLES);
Serial.printf("缓冲区大小: %d 样本\n", BUFFER_LEN);
// 内存信息
Serial.printf("可用堆内存: %d KB\n", ESP.getFreeHeap() / 1024);
#ifdef BOARD_HAS_PSRAM
Serial.printf("PSRAM大小: %d KB\n", ESP.getPsramSize() / 1024);
Serial.printf("可用PSRAM: %d KB\n", ESP.getFreePsram() / 1024);
#endif
Serial.println("使用方法:");
Serial.println("1. 短按按键: 开始/停止录制");
Serial.println("2. 长按按键(>3秒): 播放录制内容");
Serial.println("3. 双按按键: 清除录制内容");
Serial.println("=============================\n");
}
// Arduino初始化函数
void setup() {
Serial.begin(115200);
delay(1000); // 等待串口初始化
Serial.println("系统启动中...");
// 初始化GPIO
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
// 设置按键中断(下降沿触发)
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, FALLING);
// 初始化I2S
setupI2S();
// 显示系统信息
showSystemInfo();
Serial.println("系统就绪,等待按键操作...");
}
// Arduino主循环
void loop() {
static unsigned long last_button_time = 0;
static int button_press_count = 0;
static unsigned long button_down_time = 0;
static bool button_was_down = false;
// 检查按键状态(非中断方式)
bool button_state = digitalRead(BUTTON_PIN) == LOW;
if (button_state && !button_was_down) {
// 按键按下
button_down_time = millis();
button_was_down = true;
}
else if (!button_state && button_was_down) {
// 按键释放
unsigned long press_duration = millis() - button_down_time;
if (press_duration < 3000) {
// 短按
button_press_count++;
if (millis() - last_button_time > 500) {
// 新的按键序列
if (button_press_count == 1) {
// 单次短按:切换录制状态
if (!is_recording && !is_playing) {
recordAudio();
} else if (is_recording) {
// 如果正在录制,停止录制
is_recording = false;
digitalWrite(LED_PIN, LOW);
Serial.println("录制已停止");
}
} else if (button_press_count == 2) {
// 双击:清除录制内容
if (audio_buffer != nullptr) {
free(audio_buffer);
audio_buffer = nullptr;
total_samples_recorded = 0;
Serial.println("录制内容已清除");
}
}
}
} else {
// 长按:播放
if (!is_recording && !is_playing && total_samples_recorded > 0) {
playAudio();
}
}
button_was_down = false;
}
// 重置按键计数
if (millis() - last_button_time > 1000 && button_press_count > 0) {
last_button_time = millis();
button_press_count = 0;
}
// 状态指示灯闪烁(系统空闲时)
static unsigned long last_blink_time = 0;
static bool led_state = false;
if (!is_recording && !is_playing) {
if (millis() - last_blink_time > 1000) {
led_state = !led_state;
digitalWrite(LED_PIN, led_state);
last_blink_time = millis();
}
}
delay(10); // 降低CPU占用
}
方案2:使用Arduino库的简化版本
如果需要更简单的实现,可以创建库目录结构:
lib/
└── AudioIO/
├── AudioIO.h
└── AudioIO.cpp
lib/AudioIO/AudioIO.h:
#ifndef AUDIOIO_H
#define AUDIOIO_H
#include <Arduino.h>
#include <driver/i2s.h>
class AudioRecorder {
public:
AudioRecorder(int ws_pin, int bck_pin, int din_pin, int dout_pin);
bool begin(int sample_rate = 16000);
bool record(int seconds, int16_t* buffer);
bool play(int16_t* buffer, size_t sample_count);
void end();
private:
int _ws_pin, _bck_pin, _din_pin, _dout_pin;
int _sample_rate;
bool _initialized = false;
bool setupI2S();
};
#endif
lib/AudioIO/AudioIO.cpp:
#include "AudioIO.h"
AudioRecorder::AudioRecorder(int ws_pin, int bck_pin, int din_pin, int dout_pin)
: _ws_pin(ws_pin), _bck_pin(bck_pin), _din_pin(din_pin), _dout_pin(dout_pin) {}
bool AudioRecorder::begin(int sample_rate) {
_sample_rate = sample_rate;
return setupI2S();
}
bool AudioRecorder::setupI2S() {
// I2S配置代码(类似上面的setupI2S函数)
// ...
_initialized = true;
return true;
}
bool AudioRecorder::record(int seconds, int16_t* buffer) {
if (!_initialized || buffer == nullptr) return false;
size_t total_samples = _sample_rate * seconds;
size_t samples_recorded = 0;
while (samples_recorded < total_samples) {
size_t bytes_read;
i2s_read(I2S_NUM_0, buffer + samples_recorded,
min(1024, (int)(total_samples - samples_recorded)) * sizeof(int16_t),
&bytes_read, portMAX_DELAY);
samples_recorded += bytes_read / sizeof(int16_t);
}
return true;
}
bool AudioRecorder::play(int16_t* buffer, size_t sample_count) {
if (!_initialized || buffer == nullptr) return false;
size_t samples_played = 0;
while (samples_played < sample_count) {
size_t bytes_written;
i2s_write(I2S_NUM_1, buffer + samples_played,
min(1024, (int)(sample_count - samples_played)) * sizeof(int16_t),
&bytes_written, portMAX_DELAY);
samples_played += bytes_written / sizeof(int16_t);
}
return true;
}
void AudioRecorder::end() {
i2s_driver_uninstall(I2S_NUM_0);
i2s_driver_uninstall(I2S_NUM_1);
_initialized = false;
}
简化主程序 (src/main.cpp):
#include <Arduino.h>
#include "AudioIO.h"
#define RECORD_SECONDS 3
#define MAX_SAMPLES (16000 * RECORD_SECONDS)
AudioRecorder recorder(25, 26, 33, 22);
int16_t audio_buffer[MAX_SAMPLES];
void setup() {
Serial.begin(115200);
delay(1000);
if (recorder.begin(16000)) {
Serial.println("音频系统初始化成功");
} else {
Serial.println("音频系统初始化失败");
while(1);
}
}
void loop() {
Serial.println("开始录制...");
if (recorder.record(RECORD_SECONDS, audio_buffer)) {
Serial.println("录制完成,开始播放...");
delay(1000);
recorder.play(audio_buffer, MAX_SAMPLES);
Serial.println("播放完成");
}
delay(2000);
}