מסדרים שתי ערכות זהות
הניחו על השולחן שתי ערימות: בכל אחת ESP32, מיקרופון INMP441, מגבר MAX98357A, רמקול וכפתור. בינתיים עובדים רק על ערימה אחת. כשהיא עובדת, מעתיקים את אותם חיבורים למכשיר השני.
בונים זוג מכשירי קשר שלוחצים ומדברים בהם באמת: קודם הצלחה בטוחה על USB, אחר כך שמע, קשר אלחוטי ומארז. בלי נתב, בלי אינטרנט ובלי קפיצה מסוכנת לסוללת ליתיום.
חינם · 11 צעדים· 5 שעות· ~₪213 חלקים
הפרויקט בנוי כמו מסלול בדיקות: מרכיבים מכשיר אחד, שומעים צפצוף, מעלים קוד שנבדק על Arduino‑ESP32 3.3.11, מעתיקים למכשיר השני ורק אחרי שהשיחה עובדת אורזים. ESP‑NOW יוצר קשר ישיר בין שני הלוחות; כפתור PTT הופך כל מכשיר לחצי־דופלקס — מדברים או מקשיבים, כמו מכשיר קשר אמיתי.
הכנה לבנייה
חלקו את הרכיבים לשתי ערימות זהות. בחרו מודולי INMP441 ו‑MAX98357A עם פינים מולחמים מראש, וכבל USB שמעביר נתונים ולא רק טעינה. ההפעלה הראשונה תמיד דרך USB; לניידות משתמשים בסוללת גיבוי USB 5V תקנית.
אוספים ומסמנים את החלקים
הניחו על השולחן שתי ערימות: בכל אחת ESP32, מיקרופון INMP441, מגבר MAX98357A, רמקול וכפתור. בינתיים עובדים רק על ערימה אחת. כשהיא עובדת, מעתיקים את אותם חיבורים למכשיר השני.
חברו את ה‑ESP32 הראשון למחשב בכבל USB שמתאים ללוח. אל תחברו כרגע שום סוללה ושום חוט לפין 3V3. נורית קטנה על הלוח אמורה להידלק.
התקינו Arduino IDE 2. הוסיפו ב‑Boards Manager את חבילת “esp32 by Espressif Systems” בגרסת 3.3.x, בחרו ESP32 Dev Module ואת ה‑Port של הלוח. פתחו File → Examples → 01.Basics → Blink והעלו.
כשה‑USB מנותק, חברו INMP441: VDD→3V3, GND→GND, L/R→GND, SCK→GPIO25, WS→GPIO33, SD→GPIO32. עברו חוט־חוט ובדקו את הכיתוב על המודול, לא את המיקום שלו בתמונה.
חברו MAX98357A: VIN→5V/VIN של לוח ה‑ESP32, GND→GND, BCLK→GPIO26, LRC→GPIO27, DIN→GPIO14. את שני חוטי הרמקול חברו רק ל‑SPK+ ול‑SPK− במגבר.
חברו שוב USB, פתחו סקיצה חדשה והעתיקו את audio_test_3x.ino. העלו ללוח. הרמקול אמור להשמיע צפצוף קצר וחוזר. אל תמשיכו לקשר האלחוטי עד שהבדיקה הזאת עובדת.
#include <Arduino.h>
#include <ESP_I2S.h>
constexpr int AMP_BCLK = 26;
constexpr int AMP_LRC = 27;
constexpr int AMP_DIN = 14;
constexpr uint32_t SAMPLE_RATE = 16000;
I2SClass Speaker(I2S_NUM_1);
void setup() {
Serial.begin(115200);
Speaker.setPins(AMP_BCLK, AMP_LRC, AMP_DIN);
if (!Speaker.begin(I2S_MODE_STD, SAMPLE_RATE, I2S_DATA_BIT_WIDTH_16BIT,
I2S_SLOT_MODE_MONO, I2S_STD_SLOT_BOTH)) {
Serial.println("I2S ERROR — check BCLK, LRC and DIN");
while (true) delay(1000);
}
Serial.println("AUDIO TEST READY");
}
void loop() {
for (int i = 0; i < 4000; ++i) {
const int16_t sample = (i / 10 % 2) ? 5000 : -5000;
Speaker.write(&sample, sizeof(sample));
}
delay(500);
}
נתקו USB. חברו רגל אחת של כפתור ה‑PTT ל‑GPIO4 ורגל שנייה ל‑GND. אם לכפתור ארבע רגליים, השתמשו בשתי רגליים שנמצאות בצדדים מנוגדים של המפסק ובדקו רציפות בלחיצה.
החליפו את סקיצת הבדיקה ב‑walkie_talkie_3x.ino שלמטה והעלו. פתחו Serial Monitor על 115200. כשהכול תקין תופיע READY — hold PTT to talk.
#include <Arduino.h>
#include <ESP_I2S.h>
#include <WiFi.h>
#include <esp_now.h>
#include <esp_wifi.h>
// החיווט חייב להתאים למדריך.
constexpr int MIC_SCK = 25;
constexpr int MIC_WS = 33;
constexpr int MIC_SD = 32;
constexpr int AMP_BCLK = 26;
constexpr int AMP_LRC = 27;
constexpr int AMP_DIN = 14;
constexpr int PTT_PIN = 4;
constexpr uint32_t SAMPLE_RATE = 16000;
constexpr uint8_t ESPNOW_CHANNEL = 6;
constexpr size_t FRAME_SAMPLES = 100; // 200 bytes — safely below an ESP-NOW packet.
struct AudioPacket {
int16_t samples[FRAME_SAMPLES];
};
I2SClass Microphone(I2S_NUM_0);
I2SClass Speaker(I2S_NUM_1);
QueueHandle_t receivedAudio;
volatile bool radioReady = true;
const uint8_t broadcastAddress[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
void onReceive(const esp_now_recv_info_t *, const uint8_t *data, int length) {
if (length != static_cast<int>(sizeof(AudioPacket))) return;
AudioPacket packet;
memcpy(&packet, data, sizeof(packet));
xQueueSend(receivedAudio, &packet, 0);
}
void onSent(const esp_now_send_info_t *, esp_now_send_status_t) {
radioReady = true;
}
bool beginRadio() {
WiFi.mode(WIFI_STA);
WiFi.disconnect();
if (esp_wifi_set_channel(ESPNOW_CHANNEL, WIFI_SECOND_CHAN_NONE) != ESP_OK) return false;
if (esp_now_init() != ESP_OK) return false;
if (esp_now_register_recv_cb(onReceive) != ESP_OK) return false;
if (esp_now_register_send_cb(onSent) != ESP_OK) return false;
esp_now_peer_info_t peer = {};
memcpy(peer.peer_addr, broadcastAddress, sizeof(broadcastAddress));
peer.channel = ESPNOW_CHANNEL;
peer.ifidx = WIFI_IF_STA;
peer.encrypt = false;
return esp_now_add_peer(&peer) == ESP_OK;
}
bool beginAudio() {
Microphone.setPins(MIC_SCK, MIC_WS, -1, MIC_SD);
Speaker.setPins(AMP_BCLK, AMP_LRC, AMP_DIN);
const bool micReady =
Microphone.begin(I2S_MODE_STD, SAMPLE_RATE, I2S_DATA_BIT_WIDTH_32BIT,
I2S_SLOT_MODE_MONO, I2S_STD_SLOT_LEFT);
const bool speakerReady =
Speaker.begin(I2S_MODE_STD, SAMPLE_RATE, I2S_DATA_BIT_WIDTH_16BIT,
I2S_SLOT_MODE_MONO, I2S_STD_SLOT_BOTH);
return micReady && speakerReady;
}
void setup() {
Serial.begin(115200);
pinMode(PTT_PIN, INPUT_PULLUP);
receivedAudio = xQueueCreate(4, sizeof(AudioPacket));
if (!receivedAudio || !beginAudio() || !beginRadio()) {
Serial.println("SETUP ERROR — check the wiring and restart");
while (true) delay(1000);
}
Serial.println("READY — hold PTT to talk");
}
void loop() {
if (digitalRead(PTT_PIN) == LOW) {
int32_t raw[FRAME_SAMPLES];
AudioPacket packet = {};
const size_t bytes = Microphone.readBytes(
reinterpret_cast<char *>(raw), sizeof(raw));
const size_t samples = min(bytes / sizeof(raw[0]), FRAME_SAMPLES);
for (size_t i = 0; i < samples; ++i) {
const int32_t scaled = raw[i] >> 13;
packet.samples[i] =
static_cast<int16_t>(constrain(scaled, -32768L, 32767L));
}
if (radioReady) {
radioReady = false;
if (esp_now_send(broadcastAddress,
reinterpret_cast<const uint8_t *>(&packet),
sizeof(packet)) != ESP_OK) {
radioReady = true;
}
}
return;
}
AudioPacket packet;
if (xQueueReceive(receivedAudio, &packet, pdMS_TO_TICKS(5)) == pdTRUE) {
Speaker.write(packet.samples, sizeof(packet.samples));
}
}
בנו את המכשיר השני לפי אותה טבלת חיווט והעלו אליו את אותו קוד. הרחיקו את שני הרמקולים זה מזה כשני מטרים. לחצו PTT במכשיר א׳, אמרו משפט קצר, שחררו ורק אז ענו ממכשיר ב׳.
רק אחרי ששיחה עובדת, סדרו כל מערכת בקופסת פרויקט קטנה או במארז קרטון קשיח. צרו פתחים לרמקול, למיקרופון, לכפתור ולכבל USB. הפעילו כל ESP32 מסוללת גיבוי USB 5V תקנית.
טענו את שתי סוללות הגיבוי, הפעילו את המכשירים ועשו שלוש בדיקות: שיחה באותו חדר, שיחה מחדר אחר ושיחה אחרי כיבוי והדלקה. בכל פעם: לוחצים, מחכים רגע, מדברים, משחררים.