:::spoiler 麥克風測試
```--C/C++
#include <driver/i2s.h> // 引入 I2S 數位音訊介面函式庫
// INMP441 I2S 腳位定義
#define I2S_WS 25 // I2S Word Select (LRCLK) 腳位,用於標示左右聲道或字元邊界
#define I2S_BCLK 26 // I2S Bit Clock 腳位,用於同步每個資料位元
#define I2S_SD 32 // I2S Serial Data (資料輸入) 腳位,實際音訊資料線
#define I2S_PORT I2S_NUM_0 // 使用 ESP32 的 I2S 埠 0
#define SAMPLE_RATE 16000 // 音訊取樣率:每秒取樣 16000 次,這是語音常用的頻率
#define BUFFER_LEN 64 // 每次讀取/處理的音訊樣本數量 (緩衝區大小)
void setup() {
Serial.begin(115200); // 初始化序列埠,用於顯示音量數值
// I2S 初始化:設定 ESP32 如何接收音訊
const i2s_config_t i2s_config = {
// 設定為 I2S 主模式 (ESP32 提供時脈) 和 接收模式 (從麥克風接收)
.mode = (i2s_mode_t) //填入(I2S_MODE_MASTER | I2S_MODE_RX)或(I2S_MODE_MASTER | I2S_MODE_TX)
.sample_rate = SAMPLE_RATE, // 音訊取樣率,需與麥克風輸出相符
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // 每個音訊樣本使用 16 位元來表示
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,//設定為單聲道
.communication_format = I2S_COMM_FORMAT_I2S_MSB,//I2S通訊格式,通常是MSB最高有效位元優先
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // 中斷分配旗標,設定中斷優先級
.dma_buf_count = 8, // DMA 緩衝區的數量,DMA 讓音訊資料高效傳輸,不佔用 CPU
.dma_buf_len = 64, // 每個 DMA 緩衝區的長度 (樣本數)
.use_apll = false, // 不使用 APLL (音訊專用 PLL),節省資源
.tx_desc_auto_clear = false, // 接收模式下,傳輸描述符不自動清除 (不相關)
.fixed_mclk = 0 // 不固定 MCLK 頻率,由 I2S 驅動自動計算
};
// 不輸出的腳位填I2S_PIN_NO_CHANGE
const i2s_pin_config_t pin_config = {
.bck_io_num = , // BCLK 腳位
.ws_io_num = , // WS/LRCLK 腳位
.data_out_num = , // 接收模式下,沒有資料輸出,設為不變
.data_in_num = // SDATA 腳位,用於接收麥克風的資料
};
i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL); // 安裝 I2S 驅動程式
i2s_set_pin(I2S_PORT, &pin_config); // 設定 I2S 腳位
i2s_zero_dma_buffer(I2S_PORT); // 清空 DMA 緩衝區,準備接收新資料\
Serial.println("🎙 麥克風收音測試啟動...");
}
void loop() {
int16_t buffer[BUFFER_LEN]; // 建立一個緩衝區來存放讀取到的音訊樣本
size_t bytesRead; // 記錄實際讀取到的位元組數
// 從 I2S 介面讀取音訊資料 (麥克風收音)
// portMAX_DELAY 表示程式會一直等待,直到讀取到足夠的資料
i2s_read(I2S_PORT, &buffer, sizeof(buffer), &bytesRead, portMAX_DELAY);
// 計算音量 (簡單的平均絕對值,用於顯示)
uint32_t sum = 0;
for (int i = 0; i < BUFFER_LEN; i++) {
sum += abs(buffer[i]); // 累加每個樣本的絕對值 (絕對值代表聲音的強度)
}
int avgVolume = sum / BUFFER_LEN; // 計算平均音量
Serial.print("🎚 音量:"); // 序列埠輸出音量
Serial.println(avgVolume);
delay(50); // 短暫延遲
}
```
:::
:::spoiler 喇叭測試
```--C/C++
#include <driver/i2s.h> // 引入 I2S 數位音訊介面函式庫
// I2S 設定 (喇叭輸出腳位)
#define I2S_DOUT 27 // I2S Data Out (資料輸出) 腳位,實際音訊資料線
#define I2S_BCLK 26 // I2S Bit Clock 腳位
#define I2S_LRC 25 // I2S Left/Right Clock (LRCLK) 腳位
#define SAMPLE_RATE 16000 // 音訊取樣率,需與傳送端一致
#define BUFFER_LEN 64 // 每次寫入的音訊樣本數量 (緩衝區大小)
// 簡單的正弦波數據,用於測試喇叭播放
// 實際對講機中,這裡會是接收到的音訊數據
int16_t test_tone[BUFFER_LEN];
void setupI2S() { // 將 I2S 初始化獨立成一個函式,方便呼叫
const i2s_config_t i2s_config = {
//設定為 I2S 主模式 (ESP32 提供時脈) 和 傳輸模式 (輸出到喇叭)
.mode = (i2s_mode_t) //填入(I2S_MODE_MASTER | I2S_MODE_RX)或(I2S_MODE_MASTER | I2S_MODE_TX)
.sample_rate = SAMPLE_RATE, // 音訊取樣率,需與傳送端一致
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // 每個音訊樣本使用 16 位元
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // 設定為單聲道
.communication_format = I2S_COMM_FORMAT_I2S_MSB, // I2S 通訊格式
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // 中斷分配旗標
.dma_buf_count = 8, // DMA 緩衝區數量
.dma_buf_len = 64, // 每個 DMA 緩衝區的長度
.use_apll = false, // 不使用 APLL
.tx_desc_auto_clear = true, // 傳輸模式下,傳輸描述符自動清除,以便持續傳輸數據
.fixed_mclk = 0 // 不固定 MCLK 頻率
};
// 不輸出的腳位填I2S_PIN_NO_CHANGE
const i2s_pin_config_t pin_config = {
.bck_io_num = , // BCLK 腳位
.ws_io_num = , // WS/LRCLK 腳位
.data_out_num = , // SDATA 腳位,用於輸出資料到喇叭
.data_in_num = // 傳輸模式下,沒有資料輸入,設為不變
};
i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL); // 安裝 I2S 驅動程式
i2s_set_pin(I2S_NUM_0, &pin_config); // 設定 I2S 腳位
i2s_zero_dma_buffer(I2S_NUM_0); // 清空 DMA 緩衝區,準備寫入新資料
}
void setup() {
Serial.begin(115200);
setupI2S(); // 呼叫 I2S 設定函式
// 生成一個簡單的正弦波測試音
for (int i = 0; i < BUFFER_LEN; i++) {
test_tone[i] = (int16_t)(sin(i * 2 * PI / BUFFER_LEN * 5) * 10000); // 5個週期,振幅10000
}
Serial.println("🔊 喇叭播放測試啟動...");
}
void loop() {
size_t bytesWritten;
// 將測試音數據寫入 I2S 輸出到喇叭
i2s_write(I2S_NUM_0, test_tone, sizeof(test_tone), &bytesWritten, portMAX_DELAY);
delay(10); // 稍微延遲,避免播放過快
}
```
:::
:::spoiler ESP-NOW發送
```--C/C++
#include <WiFi.h>
#include <esp_now.h>
// 按鈕腳位
#define BUTTON_PIN 33
// 模擬音訊資料長度(int16_t 單位)
#define BUFFER_LEN 64
// 接收端 MAC 地址(請依實際修改)
uint8_t peerMac[] = {0xA4, 0xE5, 0x7C, 0xD3, 0xB4, 0x5C};
void setup() {
Serial.begin(115200);
delay(1000);
// 設定按鈕腳位
pinMode(BUTTON_PIN, INPUT_PULLUP);
// 初始化 WiFi 為 STA 模式(ESP-NOW 需要)
WiFi.mode(WIFI_STA);
delay(100); // 等待 WiFi 模式穩定
// 初始化 ESP-NOW
if (esp_now_init() != ESP_OK) {
Serial.println("❌ ESP-NOW 初始化失敗");
while (true);
}
// 設定對等端(接收端)
esp_now_peer_info_t peerInfo = {};
memcpy(peerInfo.peer_addr, peerMac, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;
if (esp_now_add_peer(&peerInfo) != ESP_OK) {
Serial.println("❌ 加入 ESP-NOW 對等端失敗");
while (true);
}
Serial.print("📡 傳送端 MAC 地址:");
Serial.println(WiFi.macAddress());
Serial.println("🚀 傳送端啟動完成,等待按鈕觸發...");
}
void loop() {
int16_t buffer[BUFFER_LEN];
// 模擬音訊資料(這裡用簡單的正弦波或隨機值)
for (int i = 0; i < BUFFER_LEN; i++) {
buffer[i] = random(-1000, 1000); // 模擬音訊樣本
}
// 音量計算(平均絕對值)
uint32_t sum = 0;
for (int i = 0; i < BUFFER_LEN; i++) {
sum += abs(buffer[i]);
}
int avgVolume = sum / BUFFER_LEN;
Serial.print("🎚 模擬音量:");
Serial.print(avgVolume);
// 按鈕觸發傳送
if (digitalRead(BUTTON_PIN) == LOW) {
esp_err_t result = esp_now_send(peerMac, (uint8_t*)buffer, sizeof(buffer));
if (result == ESP_OK) {
Serial.println(" → 傳送成功");
} else {
Serial.println(" → 傳送失敗");
}
} else {
Serial.println("(未傳送)");
}
delay(100); // 控制傳送頻率
}
```
:::
:::spoiler 接收
```--C/C++
#include <WiFi.h>
#include <esp_now.h>
// ==== 音量處理參數 ====
#define NOISE_THRESHOLD 30 // 音量低於此值視為靜音
#define FILTER_TAP_COUNT 2 // 移動平均濾波器抽頭數
// 接收資料的 callback
void onDataReceive(const esp_now_recv_info_t *info, const uint8_t *data, int len) {
int16_t *samples = (int16_t *)data;
int sampleCount = len / sizeof(int16_t);
// 計算平均音量
uint32_t sum = 0;
for (int i = 0; i < sampleCount; i++) {
sum += abs(samples[i]);
}
int avgVolume = sum / sampleCount;
// 閾值靜音處理
if (avgVolume < NOISE_THRESHOLD) {
Serial.println("🔇 音量過低,視為靜音");
return;
}
// 移動平均濾波器(簡單實作)
for (int i = 0; i < sampleCount; i++) {
long sum_filter = 0;
int count_filter = 0;
for (int j = 0; j < FILTER_TAP_COUNT; j++) {
if (i - j >= 0) {
sum_filter += samples[i - j];
count_filter++;
}
}
samples[i] = (int16_t)(sum_filter / count_filter);
}
// 印出處理後的音量資訊
Serial.print("📥 接收到 ");
Serial.print(len);
Serial.print(" bytes,平均音量:");
Serial.println(avgVolume);
}
void setup() {
Serial.begin(115200);
delay(100);
// 設定為 STA 模式
WiFi.mode(WIFI_STA);
WiFi.disconnect(); // 確保不連接 AP
delay(100);
Serial.print("📡 接收端 MAC 地址:");
Serial.println(WiFi.macAddress());
// 初始化 ESP-NOW
if (esp_now_init() != ESP_OK) {
Serial.println("❌ ESP-NOW 初始化失敗");
return;
}
// 註冊接收 callback
esp_now_register_recv_cb(onDataReceive);
Serial.println("🎧 接收端啟動,等待資料傳入...");
}
void loop() {
}
```
:::
音量增益
```c
// ===== 新增:音量增益處理 =====
float gain = 2.0; // 設定增益係數,例如 2.0 代表音量加倍
for (int i = 0; i < sampleCount; i++) {
// 確保不會超過 16 位元整數的最大值
long amplified_sample = (long)samples[i] * gain;
if (amplified_sample > 32767) {
samples[i] = 32767;
} else if (amplified_sample < -32768) {
samples[i] = -32768;
} else {
samples[i] = (int16_t)amplified_sample;
}
}
```
```cpp
#include <WiFi.h>
#include <esp_now.h>
#include <driver/i2s.h> // 引入 I2S 數位音訊介面函式庫
// ==== 音量處理參數 ====
#define NOISE_THRESHOLD 20 // 音量低於此值視為靜音
#define FILTER_TAP_COUNT 2 // 移動平均濾波器抽頭數
#define I2S_DOUT 27 // I2S Data Out (資料輸出) 腳位,實際音訊資料線
#define I2S_BCLK 26 // I2S Bit Clock 腳位
#define I2S_LRC 25 // I2S Left/Right Clock (LRCLK) 腳位
#define SAMPLE_RATE 16000 // 音訊取樣率,需與傳送端一致
#define BUFFER_LEN 64 // 每次寫入的音訊樣本數量 (緩衝區大小)
void setupI2S() { // 將 I2S 初始化獨立成一個函式,方便呼叫
const i2s_config_t i2s_config = {
//設定為 I2S 主模式 (ESP32 提供時脈) 和 傳輸模式 (輸出到喇叭)
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), //填入(I2S_MODE_MASTER | I2S_MODE_RX)或(I2S_MODE_MASTER | I2S_MODE_TX)
.sample_rate = SAMPLE_RATE, // 音訊取樣率,需與傳送端一致
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // 每個音訊樣本使用 16 位元
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // 設定為單聲道
.communication_format = I2S_COMM_FORMAT_I2S_MSB, // I2S 通訊格式
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // 中斷分配旗標
.dma_buf_count = 8, // DMA 緩衝區數量
.dma_buf_len = 64, // 每個 DMA 緩衝區的長度
.use_apll = false, // 不使用 APLL
.tx_desc_auto_clear = true, // 傳輸模式下,傳輸描述符自動清除,以便持續傳輸數據
.fixed_mclk = 0 // 不固定 MCLK 頻率
};
// 不輸出的腳位填I2S_PIN_NO_CHANGE
const i2s_pin_config_t pin_config = {
.bck_io_num = I2S_BCLK, // BCLK 腳位
.ws_io_num = I2S_LRC, // WS/LRCLK 腳位
.data_out_num = I2S_DOUT, // SDATA 腳位,用於輸出資料到喇叭
.data_in_num = I2S_PIN_NO_CHANGE // 傳輸模式下,沒有資料輸入,設為不變
};
i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL); // 安裝 I2S 驅動程式
i2s_set_pin(I2S_NUM_0, &pin_config); // 設定 I2S 腳位
i2s_zero_dma_buffer(I2S_NUM_0); // 清空 DMA 緩衝區,準備寫入新資料
}
// 接收資料的 callback
void onDataReceive(const esp_now_recv_info_t *info, const uint8_t *data, int len) {
int16_t *samples = (int16_t *)data;
int sampleCount = len / sizeof(int16_t);
// 計算平均音量
uint32_t sum = 0;
for (int i = 0; i < sampleCount; i++) {
sum += abs(samples[i]);
}
int avgVolume = sum / sampleCount;
// 閾值靜音處理
if (avgVolume < NOISE_THRESHOLD) {
Serial.println("🔇 音量過低,視為靜音");
return;
}
// 移動平均濾波器(簡單實作)
for (int i = 0; i < sampleCount; i++) {
long sum_filter = 0;
int count_filter = 0;
for (int j = 0; j < FILTER_TAP_COUNT; j++) {
if (i - j >= 0) {
sum_filter += samples[i - j];
count_filter++;
}
}
samples[i] = (int16_t)(sum_filter / count_filter);
}
// ===== 新增:音量增益處理 =====
float gain = 4.0; // 設定增益係數,例如 2.0 代表音量加倍
for (int i = 0; i < sampleCount; i++) {
// 確保不會超過 16 位元整數的最大值
long amplified_sample = (long)samples[i] * gain;
if (amplified_sample > 32767) {
samples[i] = 32767;
} else if (amplified_sample < -32768) {
samples[i] = -32768;
} else {
samples[i] = (int16_t)amplified_sample;
}
}
// 印出處理後的音量資訊
Serial.print("📥 接收到 ");
Serial.print(len);
Serial.print(" bytes,平均音量:");
Serial.println(avgVolume);
size_t bytesWritten;
// 將測試音數據寫入 I2S 輸出到喇叭
i2s_write(I2S_NUM_0, samples, len, &bytesWritten, portMAX_DELAY);
// delayMicroseconds(1000); // 稍微延遲,避免播放過快
}
void setup() {
Serial.begin(115200);
delay(100);
// 設定為 STA 模式
WiFi.mode(WIFI_STA);
WiFi.disconnect(); // 確保不連接 AP
delay(100);
Serial.print("📡 接收端 MAC 地址:");
Serial.println(WiFi.macAddress());
// 初始化 ESP-NOW
if (esp_now_init() != ESP_OK) {
Serial.println("❌ ESP-NOW 初始化失敗");
return;
}
// 註冊接收 callback
esp_now_register_recv_cb(onDataReceive);
setupI2S();
Serial.println("🎧 接收端啟動,等待資料傳入...");
}
void loop() {
}
```