---
title: 2025 電資創客營 - 對講機製作共筆
tags: 電資創客營
---
:::info
# 2025 電資創客營 - 對講機製作共筆
**時間**:2025/08/12 9:00 ~ 2025/08/13 16:55
**地點**:挺生大樓 A3 - 200 教室
:::
## 目錄
**上午**
- ESP32 介紹
- 開發環境介紹
- ESP-IDF
- FreeRTOS
- WiFi 模式
- MAC 位址
- ESP - NOW
- 數位類比
- 時脈
- 主機從機
- 周邊介面介紹
- 元件介紹
**下午**
# 上午
## ESP32 介紹

- 開發版
- 豐富社群資源
- 適合初學者使用
|功能|Arduino|ESP32|
|-|-|-|
|處理速度|16 MHz|高達 240 MHz|
記憶體|2 kB SRAM|約 520 KB SRAM
WiFi / BLE | :x:|內建
多核心|:x:|雙核心
模擬輸出|:x:|內建 DAC
感應器支援|基本|更強大
## 開發環境介紹
- Arduino IDE
- 提供更簡化介面
- 更簡化程式語法
## ESP-IDF
Espressif IoT Development Framework
- ESP32指的是晶片,電路板上的微控制器
- 開發版指的是搭載 ESP32 的電路板
- 工具箱
- 核心引擎
- FreePTOS:內建即時作業系統,管理 ESP32 上的所有任務的執行、排程、資源分配、同步
- 無線通訊能力
- WiFi
- Bluetooth
- ESP - NOW
- 周邊介面與功能(輸出介面)
- GPIO
- ADC/DAC
- I2C/SPI/UART
- I2S
- PWM
## FreeRTOS
- 開源的即時**作業系統**
- 輕量級,專為嵌入式系統設計的,廣泛應用於微控制器
- 沒有像 Linux、Windows 一樣的先進特徵
- 簡潔與速度,因為太過輕量化,有時候被認為是執行緒函式庫而非作業系統
### POTS 即時作業系統
- 是一種能在嚴格時間內完成工作的作業系統,並根據優先順序快速切換,確保關進任務準時執行
### 微控制系統
- 小型電腦,被設計來執行特定任務,常見於嵌入式系統
- 通常整合了處理器、記憶體、與輸出入介面於一塊
### 嵌入式系統
- 一種類似被遷入在其他設備中的電腦系統
- 可以針對電器、被遷入機器的同客製化功能
## WiFi 模式
- STA(station 無線終端)
- 定位:無限收發器
- 運作:是一個專心對講機頻道(訊號接收)的機器
- AP (Access Point)
- 定位:網路中繼站
- 運作:專心做網路通訊中繼站
## MAC 位址
- MAC 位址(Media Access Control Address)
- 定義:裝置在硬體層面的**實體位址**或硬體位址
- 格式:由**12 個十六進位數字**組成,用冒號分隔
例:00:1A:2B:3C:4D:5E
用途:
主要用於區域網路 (LAN) 內的數據傳輸,確保數據包在同一個網段內能**被正確的裝置接收**,在 Wi-Fi 通訊中,AP 和 STA 之間就是透過 MAC 位址來識別和建立連接的。
> 就像送信要知道對方的地址一樣,才能讓信送到正確的人那裡。
## ESP - NOW
- 專為 ESP32 的發明的通訊協議
- 獨立於 WiFi 網路,可直接透過 WiFi 協議進行通訊,不需要經過路由器
- 基於 MAC 位址即可建立傳輸或連接
## 數位類比
- 類比(Analog)
類比訊號是==連續==的,可以有很多種值
例如:自然現象(聲音、光線、溫度)
類比訊號:麥克風(聲音)、溫度感測器的電壓輸出(溫度->電壓)
> 圖案看起來像是**溜滑梯(slide)**
- 數位(Digital)
數位訊號是==離散==的,只有特定幾種值(例如 0 和 1)
電腦、微控制器只能處理數位訊號(還記得嗎?電腦只認識0101、也就是**二進位**)
數位訊號:開跟關、二進位資料
> 圖案看起來像是**階梯(stairs)**
## 時脈
時脈(clock):電腦的節拍器
- 週期(Period)
- 一次幾秒
- 頻率(frequency)是一秒幾次
- 脈波(Clock Pulse)
- 協調各個元件的動作時機
> 沒有穩定的時脈,數位系統就無法協調運作
## 主機從機
主機(Master)和從機(Slave)的概念
- 核心思想是在描述一種不對等的控制與被控制、主動與被動的關係
簡單來說:
- 主機(Master):**ESP32**
- 是系統中的主導者,負責發起命令、控制和管理整個系統的運作
- 通常是主動的一方,決定什麼時候發起通訊、傳輸什麼資料,並協調從機行為
- 從機(Slave):**麥克風、音訊模組、按鈕、喇叭**
- 是系統中的執行者,它負責接收主機的指令並執行
- 從機通常是被動的一方,等待主機發出命令,然後根據命令進行**資料回傳**或**執行特定操作**
<!-- 應該是 -->
## 周邊介面介紹
<!-- 他目錄是錯的 -->
### GPIO
- General-Purpose Input/Output, 通用輸入/輸出
- 可透過軟體程式來**控制**這些引腳的**行為**
依現實考量可作為通用輸入(GPI)或通用輸出(GPO)或通用輸入與輸出(GPIO)
### SPI
> 串行:一次只傳輸一個位元
串行外設介面(Serial Peripheral Interface Bus,SPI),是一種用於**晶片通訊**的同步串行通訊介面規範,主要應用於**單晶片系統**中的訊號傳遞
由 Motorola 在 1980 年代提出,廣泛應用於感測器、記憶體、顯示器、音訊晶片、SD 卡等設備與微控制器之間
SPI匯流排規定了4個保留邏輯訊號介面:
- SCLK(Serial Clock):串行時脈,由主機發出
- MOSI(Master Output, Slave Input):主機輸出,從機輸入訊號(資料由主機發出)
- MISO(Master Input, Slave Output):主機輸入,從機輸出訊號(資料由從機發出)
- SS(Slave Select):片選訊號,由主機發出,一般是低電位有效
> 共用 SCLK 時脈訊號

### I2C
I²C 或 I2C(Inter-Integrated Circuit)是IC間**傳輸控制與資料**的一種序列介面標準
由飛利浦半導體在 1982 年設計,廣泛應用於感測器、記憶體、顯示器、實時時鐘(RTC)、音訊編碼器等設備與微控制器之間
特色:兩條訊號線進行通訊
SCL(Serial Clock Line):由主機產生的時鐘訊號,控制資料傳輸節奏
SDA(Serial Data Line):雙向資料線,負責傳送與接收資料
### I2S
I²S 或 I2S (Inter-IC Sound)是IC間傳輸數位**音訊**資料的一種介面標準
由飛利浦半導體在1986年提出,廣泛應用於音訊編碼器、數位音訊處理器、微控制器與 DAC/ADC 等設備之間
ESP32 的 ESP-IDF 提供了完整的 I2S 驅動程式,可以設定為:
傳送(TX)或接收(RX)模式
多個時鐘信號: 通常包含時鐘線 (BCLK)、字時鐘線 (WS 或 LRCLK) 和數據線 (SD 或 DATA),確保數據的精確同步
| 訊號線 | 功能 |
| -------------------- | ------------------------------------- |
| SCK(Serial Clock) | 又稱 BCLK,控制每個 bit 的傳輸時序 |
| WS(Word Select) | 又稱 LRCLK,決定目前傳輸的是左聲道還是右聲道 |
| SD(Serial Data) | 傳輸音訊資料的位元流 |
| MCLK(Master Clock) | 系統中頻率最高的時鐘,保持時脈準確性(非必須) |
## 核心(Core)
什麼是核心?
- 核心(Core) 是 CPU 中能夠獨立執行程式指令的處理單元
- 就像是一間工廠裡的一個工人,有自己的一套工具(ALU、控制單元、暫存器等),能夠自成為一個完整的生產線
那 CPU core 和 CPU 晶片的關係是什麼?
- CPU core 和 CPU 晶片的關係就像是工人和工廠
- 每個工人(核心)都能獨立地接訂單、生產產品
- 工廠內會提供共用的資源(快取記憶體、記憶體控制器等)
### ESP32 的雙核心架構
ESP32 是一個為物聯網(IoT)設計的微控制器,更強調協作和專門化
- 工人 0 號:通訊與後勤核心(Core 0, Protocol_CPU)
角色:專注於通訊協議的專家
例:處理系統底層的任務,像是Wi-Fi、藍牙、FreeRTOS等
- 工人 1 號:應用程式核心(Core 1 或稱為 Application_CPU)
角色:專注於執行你的應用程式的工程師
例:負責使用者的應用程式邏輯,處理你的業務邏輯、運算、數據處理等
## 元件介紹
- ESP32開發板
- 麵包板
- INMP441 麥克風
- MAX98357A 音訊模組
# 下午
## 標頭檔
我們這兩天會用到的標頭檔
- <WiFi.h>:處理 WiFi 相關功能,用來抓 MAC 位址
- <esp_now.h>:ESP-NOW 無線通訊協定函式庫
- <driver/i2s.h>:I2S 數位音訊介面函式庫
使用以下兩行得知你的MAC位址
```Ariduino=
WiFi.mode(WIFI_STA) //設定WiFi模式
Serial.println(WiFi.macAddress()) //印出MAC位址
```
## i2s初始化
初始化的東西放在setup裡面。
有兩個**結構**負責定義許多參數:
- i2s_config_t
例如:工作模式(mode)、取樣率(sample_rate)、位元深度(bits_per_sample)、通道格式(channel_format)
- i2s_pin_config_t
負責指定 I2S 的訊號線的 GPIO 腳位:
.bck_io_num:連接 I2S 的 BCLK (位元時脈) 腳位
.ws_io_num:連接 I2S 的 WS (字元選擇/左右聲道時脈) 腳位
.data_in_num:連接 I2S 的輸入腳位,連接SD
.data_out_num:連接 I2S 的輸出腳位
> config_t 是指 configuration type
> 只有mode、usr_apll、tx_desc_auto_clear、fixed_mclk我們會修改到。
### 初始化的最後三劍客
- i2s_driver_install:安裝 I2S 驅動的主要函式
- i2s_set_pin:設定 I2S 所使用的 GPIO 腳位
- i2s_zero_dma_buffer:清空緩衝區,避免殘值
例如:
i2s_driver_install ( I2S_PORT, &i2s_config, 0, NULL );
i2s_set_pin ( I2S_PORT, &pin_config);
i2s_zero_dma_buffer ( I2S_PORT );
### i2s_config_t設定
- .mode:
- 讀取:(i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
- 輸入:(i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
- .use_apll:今天都是false
- .tx_desc_auto_clear:
- 輸入:true
- 不輸入:false
- .fixed_mclk:今天都是0
### 腳位設定
利用 #define 設定:
使用格式:#define 名稱 值
:::spoiler 詳細設定
#define I2S_WS 25 // I2S Word Select (LRCLK) 腳位
#define I2S_BCLK 26 // I2S Bit Clock 腳位
#define I2S_SD 32 // I2S Serial Data (資料輸入) 腳位
#define BUTTON_PIN 33 // 定義按鈕連接的 ESP32 腳位
#define I2S_PORT I2S_NUM_0 // 使用 I2S 端口 0
#define SAMPLE_RATE 16000 // 音訊取樣率 (每秒取樣次數),16kHz 是語音常用頻率
#define BUFFER_LEN 64 // 每次讀取/傳送的音訊樣本數量 (緩衝區大小)
:::
## i2s read跟write
## esp_now指令
## 濾波處理
## ESP-NOW 接收 (更新版)
```
#include <WiFi.h>
#include <esp_now.h>
/* ==== 音量處理參數 ==== */
#define NOISE_THRESHOLD 30 // 音量低於此值視為靜音
#define FILTER_TAP_COUNT 2 // 移動平均濾波器抽頭數
void filter_data(int16_t *samples, int sampleCount){
/* 移動平均濾波器(簡單實作)*/
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);
}
}
void sound_update(int16_t *samples, int sampleCount){
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;
}
}
}
void wifi_mode_setup(){
/* 設定為 STA 模式 */
WiFi.mode(WIFI_STA);
WiFi.disconnect(); // 確保不連接 AP
delay(100);
Serial.print("📡 接收端 MAC 地址:");
Serial.println(WiFi.macAddress());
}
void espInit_setup(){
// 初始化 ESP-NOW
if (esp_now_init() != ESP_OK) {
Serial.println("❌ ESP-NOW 初始化失敗");
return;
}
}
void onDataReceive(const esp_now_recv_info_t *info, const uint8_t *data, int len) {
/* Step 1 */
int16_t *samples = (int16_t *)data;
int sampleCount = len / sizeof(int16_t);
/* Step 2 */
/* 計算平均音量 */
uint32_t sum = 0;
for (int i = 0; i < sampleCount; i++) {
sum += abs(samples[i]);
}
int avgVolume = sum / sampleCount;
/* Step 3 */
if (avgVolume < NOISE_THRESHOLD) { // 如果平均音量低於閾值,則將所有樣本設為 0 (靜音)
for (int i = 0; i < sampleCount; i++) {
samples[i] = 0;
}
Serial.println("📥 接收音訊:靜音中 (低於閾值)");
} else {
/* Step 4 */
//filter_data(samples, sampleCount);
}
/* Step 5 */
/* 印出處理後的音量資訊 */
Serial.print("📥 接收到 ");
Serial.print(len);
Serial.print(" bytes,平均音量:");
Serial.println(avgVolume);
}
void setup(){
/* Step 1 */
Serial.begin(115200);
delay(1000);
/* Step 2 */
wifi_mode_setup();
/* Step 3 */
espInit_setup();
/* Step 4 */
esp_now_register_recv_cb(onDataReceive);
/* Step 5 */
Serial.print("接收端初始化啟動...");
}
void loop(){
}
```
// 麥克風測試Day3
```
#include <driver/i2s.h> // 引入 I2S 數位音訊介面函式庫
/* I2S 設定 (喇叭輸出腳位) */
#define I2S_DOUT 27 // I2S Data Out (資料輸出) 腳位,實際音訊資料線
#define I2S_BCLK 26 // I2S Bit Clock 腳位
#define I2S_WS 25 // I2S Left/Right Clock (LRCLK) 腳位
#define I2S_PORT I2S_NUM_0
#define SAMPLE_RATE 16000 // 音訊取樣率,需與傳送端一致
#define BUFFER_LEN 64 // 每次寫入的音訊樣本數量 (緩衝區大小)
// 簡單的正弦波數據,用於測試喇叭播放
int16_t test_tone[BUFFER_LEN];
void setupI2S(){
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通訊格式,通常是MSB最高有效位元優先
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // 中斷分配旗標,設定中斷優先級
.dma_buf_count = 8, // DMA 緩衝區的數量,DMA 讓音訊資料高效傳輸,不佔用 CPU
.dma_buf_len = 64, // 每個 DMA 緩衝區的長度 (樣本數)
.use_apll =false ,
.tx_desc_auto_clear = true,
.fixed_mclk = 0
};
const i2s_pin_config_t pin_config = { // 不使用的腳位填I2S_PIN_NO_CHANGE
.bck_io_num = I2S_BCLK, // BCLK 腳位
.ws_io_num = I2S_WS, // WS/LRCLK 腳位
.data_out_num = I2S_DOUT, // 資料輸出腳位
.data_in_num = I2S_PIN_NO_CHANGE // 腳位,用於接收麥克風的資料
};
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 緩衝區,準備接收新資料
}
void sin_tone(){
/* 生成一個簡單的正弦波測試音 */
for (int i = 0; i < BUFFER_LEN; i++) {
test_tone[i] = (int16_t)(sin(i * 2 * PI / BUFFER_LEN * 5) * 10000); // 5個週期,振幅10000
}
}
void loop_print(){
uint32_t sum = 0;
for(int i=0; i<BUFFER_LEN; i++) sum+=abs(test_tone[i]);
int avgVolume = sum / BUFFER_LEN;
Serial.print("音量:");
Serial.println(avgVolume);
}
void setup() {
/* Step 1 */
Serial.begin(115200);
/* Step 2 */
setupI2S();
/* Step 3 */
sin_tone();
Serial.println("🔊 喇叭播放測試啟動...");
}
void loop() {
/* Step 1 */
size_t bytesWritten;
/* Step 2 */
i2s_write(I2S_NUM_0, test_tone, sizeof(test_tone), &bytesWritten, portMAX_DELAY);
/* Step 3 */
loop_print();
/* Step 4 */
delay(10); // 稍微延遲,避免播放過快
}
```