--- title: "[會議記錄]20250702-暑期例行性會議" description: image: tags: - 會議記錄 - 社課 - 社團博覽會 - 電資創客營 --- # 暑期例行性會議 :::warning ## 會議資訊 日期:2025/07/02 時間:13:00~:00 地點:社辦、Discord線上 ::: 列席人員: - ... 出席人員: - 陳柏屹 - 游凱崴 - 羅崧瑋 - 陳威翰 - 方健宇 - 蔡詠竣 - 黃郁寧 - 張乃文 - 張登評 缺席人員: - ... ## 📚 會前須知 :::info **文件** - [教學組內會議](https://hackmd.io/@ttussc/rJw2jAgBxe) - [教育優先區會議-第零籌](https://hackmd.io/@ttussc/rkBa2RO4xl) ::: :::spoiler 課表 上學期 - 9/11 期初大會 - 9/18 開源 + 社課規劃 - 9/25 Markdown - 10/2 Shell + Vim - 10/9 程式語言 - 10/16 程式語言 - 10/23 讀書會 - 10/30 期中考周 - 11/6 吃東西(社遊) - 11/13 Discord Bot - 11/20 Linux - 11/27 資安 - 12/4 資安 (CTF) or ~~Bad USB~~ - 12/11 期末大會 - 12/18 讀書會 - 12/25 期末考週 下學期 - 2/26 期初大會 - 3/5 小專題分組與說明 - 3/12 Git - 3/19 Git - 3/26 前端 - 4/2 前端 - 4/9 讀書會 - 4/16 期中考週 - 4/23 社遊 - 4/30 後端 - 5/7 Docker - 5/14 ? - 5/21 ? - 5/28 期末大會 + 小專題發表會 - 6/4 讀書會 - 6/11 期末考週 ::: ### 本次預計進度 - ... ## 📣 議程 - 下學期社課 - 攝影營隊、電資創客營確認進度 - 教育優先區營隊與另外兩校線上會議時間 - 臨時動議 - 散會 ## ℹ️ 報告事項 > 從這裡開始 ## 📝 會議紀錄 ### 社課規劃 表定 9:00,但有可能只上到 8:30 ~~早點下課~~ 上學期 - 9/11 期初大會 社長 - 9/18 開源+社課規劃 社長 - 9/25 Markdown 詠竣 - 10/2 Shell+Vim 詠竣 - 10/9 Python 乃文 - 10/16 Python 乃文 - 10/23 讀書會 - 10/30 期中考周 - 11/6 吃東西(社遊) - 11/13 Discord Bot 詠竣 - 11/20 Linux 詠竣 - 11/27 資安 崧瑋 - 12/4 資安 (CTF) or ~~Bad USB~~ 崧瑋 - 12/11 期末大會 - 12/18 讀書會 - 12/25 期末考週 下學期 - 2/26 期初大會 - 3/5 小專題分組與說明 - 3/12 Git - 3/19 Git - 3/26 前端 - 4/2 前端 - 4/9 讀書會 - 4/16 期中考週 - 4/23 社遊 - 4/30 後端 - 5/7 Docker - 5/14 專題討論(暫定) or 社員意願調查 - 5/21 專題討論(暫定) or 社員意願調查 - 5/28 期末大會+小專題發表會 - 6/4 讀書會 - 6/11 期末考週 ==8/25== 前講師繳企劃書給黃郁寧 出去玩:六度空間玩槍戰(平日、2小時:390 元/人) > :::info > 攝影營隊 > ::: > 兒童心理學 7/4 18:00~20:00 102教室 > 7/12、13 排桌椅 > 原本的便當一直遲到,看要不要換築本屋訂90塊便當 > 講師:大倫跟Leo > 凱崴要抓細流給工作人員 > :::info > 電資創客營 > ::: > 204不能借、200要問電機系主任,其他都可以 > 原價2500、早鳥2000、低收700 (成本1700) > 講師:還在找 > 7/7 (一) 文案:芋泥 > 發宣傳文 (ig、fb):芋泥 > esp32之前的bug: > ``` > E (9) esp_clock_output: esp_clock_output_start(207): Selected io is already mapped by another signal > E (9) i2s(legacy): i25_check_set_mclk (1878): mclk configure failed > E (13) i2s(legacy): i2s_set_pin (1947): mclk config failed 解決了,但是在傳輸上還是會出現![截圖 2025-07-02 下午2.26.48](https://hackmd.io/_uploads/ryJcZIGBlx.png) Gemini連結:https://g.co/gemini/share/503b7f2efa7e ::: spoiler **這是現在整合用的程式:** ``` #include <WiFi.h> #include <esp_now.h> #include <driver/i2s.h> // ====================================== 腳位設定 ====================================== // INMP441 麥克風 (I2S0 - Master) #define I2S_MIC_WS 25 // 麥克風的 WS 腳位,同時接到喇叭的 LRC #define I2S_MIC_BCLK 26 // 麥克風的 BCLK 腳位,同時接到喇叭的 BCLK #define I2S_MIC_SD 32 // 麥克風的數據輸出 (SD) #define I2S_MIC_MCLK 4 // 麥克風 Master 模式下 MCLK 輸出腳位 (可選,若晶片需要) // MAX98357A 喇叭 (I2S1 - Slave) // 注意:喇叭的 BCLK 和 LRC 將會從麥克風的 BCLK 和 WS 腳位接收訊號! #define I2S_SPK_LRC 13 // 喇叭的 LRC 腳位 (接到麥克風的 WS) #define I2S_SPK_BCLK 14 // 喇叭的 BCLK 腳位 (接到麥克風的 BCLK) #define I2S_SPK_DIN 12 // 喇叭的數據輸入 (DIN) // Slave 模式下不需要定義 I2S_SPK_MCLK,因為不產生 MCLK #define BUTTON_PIN 33 // 按鈕腳位 #define SAMPLE_RATE 20000 // 取樣率 #define BUFFER_LEN 64 // DMA 緩衝區長度(每個緩衝區的樣本數) #define NOISE_THRESHOLD 60 // 靜音閾值 #define FILTER_TAP_COUNT 2 // 移動平均濾波器抽頭數 #define I2S_MIC_PORT I2S_NUM_0 // 麥克風使用 I2S 0 #define I2S_SPK_PORT I2S_NUM_1 // 喇叭使用 I2S 1 //uint8_t peerMac[] = {0xA8, 0x48, 0xFA, 0x0B, 0x89, 0x3C}; // ⚠️ 請替換為對方 ESP32 的 MAC 地址 uint8_t peerMac[] = {0xA4, 0xE5, 0x7C, 0xD3, 0xB4, 0x5C}; // ====================================== I2S 初始化函數 ====================================== // 麥克風 I2S 初始化 (Master 模式) void setupMicI2S() { Serial.println("初始化麥克風 I2S (Master 模式)..."); const i2s_config_t mic_config = { .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX), // Master 模式接收 .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_MSB, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .dma_buf_count = 8, .dma_buf_len = BUFFER_LEN, .use_apll = true, // Master 模式通常使用 APLL 產生精確時脈 .tx_desc_auto_clear = false, .fixed_mclk = I2S_MIC_MCLK // Master 在此腳位輸出 MCLK (例如 GPIO4) }; const i2s_pin_config_t mic_pins = { .bck_io_num = I2S_MIC_BCLK, // 麥克風 BCLK 輸出 .ws_io_num = I2S_MIC_WS, // 麥克風 WS/LRC 輸出 .data_out_num = I2S_PIN_NO_CHANGE, // 麥克風是 RX,無數據輸出 .data_in_num = I2S_MIC_SD, // 麥克風數據輸入 // .mclk_io_num 已經被移除,因為 I2S_PIN_CONFIG_T 結構體中不再有此成員 }; esp_err_t err = i2s_driver_install(I2S_MIC_PORT, &mic_config, 0, NULL); if (err != ESP_OK) { Serial.printf("❌ 麥克風 I2S 驅動安裝失敗: %d\n", err); while(true); } err = i2s_set_pin(I2S_MIC_PORT, &mic_pins); if (err != ESP_OK) { Serial.printf("❌ 麥克風 I2S 腳位設定失敗: %d\n", err); while(true); } i2s_zero_dma_buffer(I2S_MIC_PORT); Serial.println("✅ 麥克風 I2S 初始化完成 (Master 模式)"); } // 喇叭 I2S 初始化 (Slave 模式) void setupSpkI2S() { Serial.println("初始化喇叭 I2S (Slave 模式)..."); const i2s_config_t spk_config = { .mode = (i2s_mode_t)(I2S_MODE_SLAVE | I2S_MODE_TX), // Slave 模式傳送 .sample_rate = SAMPLE_RATE, // 喇叭的取樣率需與 Master (麥克風) 相同 .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, .communication_format = I2S_COMM_FORMAT_I2S_MSB, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .dma_buf_count = 8, .dma_buf_len = BUFFER_LEN, .use_apll = false, // Slave 模式不需要 APLL (因為不產生時脈) .tx_desc_auto_clear = true, .fixed_mclk = I2S_PIN_NO_CHANGE // Slave 模式不輸出 MCLK }; const i2s_pin_config_t spk_pins = { .bck_io_num = I2S_SPK_BCLK, // 喇叭 BCLK 輸入 (將連接到麥克風的 BCLK 輸出) .ws_io_num = I2S_SPK_LRC, // 喇叭 WS/LRC 輸入 (將連接到麥克風的 WS 輸出) .data_out_num = I2S_SPK_DIN, // 喇叭數據輸出 .data_in_num = I2S_PIN_NO_CHANGE, // 喇叭是 TX,無數據輸入 // .mclk_io_num 已經被移除,因為 I2S_PIN_CONFIG_T 結構體中不再有此成員 }; esp_err_t err = i2s_driver_install(I2S_SPK_PORT, &spk_config, 0, NULL); if (err != ESP_OK) { Serial.printf("❌ 喇叭 I2S 驅動安裝失敗: %d\n", err); while(true); } err = i2s_set_pin(I2S_SPK_PORT, &spk_pins); if (err != ESP_OK) { Serial.printf("❌ 喇叭 I2S 腳位設定失敗: %d\n", err); while(true); } i2s_zero_dma_buffer(I2S_SPK_PORT); Serial.println("✅ 喇叭 I2S 初始化完成 (Slave 模式)"); } // ====================================== ESP-NOW 回調函數 ====================================== // 接收音訊資料後播放 void onDataReceive(const esp_now_recv_info_t *recv_info, const uint8_t *incomingData, int len) { size_t bytesWritten; int16_t *samples = (int16_t *)incomingData; // 將 incomingData 轉換為 int16_t 指針 int sampleCount = len / sizeof(int16_t); // 計算音量 (平均絕對值) - 用於閾值判斷和除錯 uint32_t current_sum_abs = 0; for (int i = 0; i < sampleCount; i++) { current_sum_abs += abs(samples[i]); } int avgVolume = current_sum_abs / sampleCount; // ==== 雜訊過濾處理 ==== // 1. 閾值降噪 (Noise Gating) if (avgVolume < NOISE_THRESHOLD) { // 如果平均音量低於閾值,則將所有樣本設為 0 (靜音) for (int i = 0; i < sampleCount; i++) { samples[i] = 0; } // Serial.println("📥 接收音訊:靜音中 (低於閾值)"); // 太多訊息時可註解 } else { // 2. 移動平均濾波器 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.printf("📥 接收音訊:%d bytes,音量:%d\n", len, avgVolume); // 太多訊息時可註解 } // 將處理後的音訊資料寫入 I2S 輸出 i2s_write(I2S_SPK_PORT, samples, len, &bytesWritten, portMAX_DELAY); } // ====================================== 任務函數 ====================================== // 麥克風讀取並傳送任務 void micSendTask(void *pvParameters) { int16_t buffer[BUFFER_LEN]; size_t bytesRead; Serial.println("📤 麥克風傳送任務啟動"); while (true) { // 從麥克風讀取音訊 i2s_read(I2S_MIC_PORT, buffer, sizeof(buffer), &bytesRead, portMAX_DELAY); // 計算音量 uint32_t sum = 0; for (int i = 0; i < BUFFER_LEN; i++) sum += abs(buffer[i]); int avg = sum / BUFFER_LEN; // 如果按鈕按下 (LOW) 就傳送 if (digitalRead(BUTTON_PIN) == LOW) { esp_err_t result = esp_now_send(peerMac, (uint8_t *)buffer, sizeof(buffer)); if (result == ESP_OK) { Serial.printf("📤 傳送中,音量:%d\n", avg); } else { Serial.println("⚠️ 傳送失敗"); } } else { // Serial.println("🎤 靜待傳送 (按鈕未按下)"); // 太多訊息時可註解 } vTaskDelay(pdMS_TO_TICKS(4)); // 短暫延遲 } } // 接收任務 (僅為 FreeRTOS 任務結構,實際處理在 onDataReceive 回調中) void recvTask(void *pvParameters) { Serial.println("🎧 接收任務啟動 (等待 ESP-NOW 數據)"); while (true) { vTaskDelay(pdMS_TO_TICKS(10)); // 緩衝 CPU } } // ====================================== Setup 和 Loop ====================================== void setup() { Serial.begin(115200); delay(100); // 給序列埠一點時間啟動 Serial.println("====================================="); Serial.println(" ESP32 雙向對講機啟動中... "); Serial.println("====================================="); pinMode(BUTTON_PIN, INPUT_PULLUP); // 設定按鈕腳位為輸入上拉 // 初始化 I2S 驅動 setupMicI2S(); // 麥克風 (Master) setupSpkI2S(); // 喇叭 (Slave) // 初始化 WiFi 和 ESP-NOW WiFi.mode(WIFI_STA); // 設定 WiFi 模式為站點模式 delay(100); // 等待 WiFi 模組初始化 // 打印本機 MAC 地址 Serial.print("本機 MAC 地址: "); Serial.println(WiFi.macAddress()); if (esp_now_init() != ESP_OK) { Serial.println("❌ ESP-NOW 初始化失敗!"); while (true); // 初始化失敗則停在此處 } Serial.println("✅ ESP-NOW 初始化成功"); // 添加 ESP-NOW 對等端 esp_now_peer_info_t peerInfo = {}; memcpy(peerInfo.peer_addr, peerMac, 6); peerInfo.channel = 0; // 頻道必須與對方匹配 peerInfo.encrypt = false; // 不加密 if (!esp_now_is_peer_exist(peerMac)) { // 檢查對等端是否已存在 if (esp_now_add_peer(&peerInfo) != ESP_OK) { Serial.println("❌ 加入 ESP-NOW 對等端失敗!"); while (true); // 加入失敗則停在此處 } Serial.println("✅ ESP-NOW 對等端添加成功"); } else { Serial.println("✅ ESP-NOW 對等端已存在"); } // 註冊 ESP-NOW 接收回調函數 esp_now_register_recv_cb(onDataReceive); Serial.println("✅ ESP-NOW 接收回調函數註冊成功"); // 創建 FreeRTOS 任務 // 麥克風傳送任務運行在 Core 0 xTaskCreatePinnedToCore(micSendTask, "MicSendTask", 4096, NULL, 1, NULL, 0); // 接收任務運行在 Core 1 (主要處理 ESP-NOW 回調,保持獨立) xTaskCreatePinnedToCore(recvTask, "RecvTask", 4096, NULL, 1, NULL, 1); Serial.println("====================================="); Serial.println(" ✅ 雙向對講機啟動完成! "); Serial.println("====================================="); Serial.println("請確認 I2S 腳位接線,特別是 BCLK 和 WS/LRC 的共享!"); } void loop() { // loop 函數可以保持為空,所有邏輯都在 FreeRTOS 任務中處理 vTaskDelay(pdMS_TO_TICKS(1000)); // 防止看門狗定時器超時 } ``` ::: > :::info > 社團博覽會 > ::: > **設計:** > - 限動、貼文 -> Jenny > - 海報 -> 昭妤 > - 更新網站 -> 詠竣 > - 桌巾 -> 小方 > - 關東旗(大/小) -> 小方 > - 宣傳簡報更新(平板) -> 柏屹 > - 各系社團茶會宣傳 -> 柏屹、凱崴 > - 新生輔導宣傳 -> 各系較熟的去 > - 宣傳單沿用 -> ? > - 檢查QRcord(名片、宣傳單) > :::info > 討論事項 > ::: > > 討論過程紀錄... :::danger 15:11 會議結束~ ::: :::danger 下次會議時間:7/16 檢查事項: - 關東旗、桌巾、傳單、海報、~~社服~~的報價 - 電資創客營 - 報名、講師 ::: ## 🔑 會議總結 > 從這裡開始