01
לפני שמחברים: מפת המערכת
בכל צד יש ארבעה בלוקים:
- ESP32 — המוח והרשת.
- INMP441 — האוזן הדיגיטלית.
- MAX98357A + רמקול — הפה.
- כפתור PTT — ההחלטה אם לדבר או להקשיב.
בנו צד אחד בלבד עד שתשמעו צפצוף ותראו READY. רק אז העתיקו לצד השני. הסדר הזה חוסך מצב שבו יש שתי תקלות זהות ואין מערכת תקינה להשוואה.
ההצלחה הראשונה נעשית עם USB מהמחשב. הניידות מגיעה בסוף עם סוללת גיבוי USB 5V סגורה ותקנית.
02
טבלת החיווט המלאה
| INMP441 | ESP32 |
|---|---|
| VDD | 3V3 |
| GND | GND |
| L/R | GND |
| SCK | GPIO25 |
| WS | GPIO33 |
| SD | GPIO32 |
| MAX98357A | ESP32 |
|---|---|
| VIN | 5V/VIN (כאשר הלוח מוזן ב‑USB) |
| GND | GND |
| BCLK | GPIO26 |
| LRC | GPIO27 |
| DIN | GPIO14 |
| SPK+ / SPK− | שני חוטי הרמקול |
| כפתור PTT | |
|---|---|
| רגל אחת | GPIO4 |
| רגל שנייה | GND |
לפני חיבור USB עברו על הרשימה מלמעלה למטה. כל ה‑GND משותפים. INMP441 מקבל 3.3V; המגבר מקבל 5V/VIN. אף יציאת רמקול אינה מתחברת ל‑GND.
03
Arduino IDE בלי ניחושים
- התקינו Arduino IDE 2.
- פתחו Preferences והוסיפו ל‑Additional Boards Manager URLs:
https://espressif.github.io/arduino-esp32/package_esp32_index.json - ב‑Boards Manager התקינו “esp32 by Espressif Systems” בגרסת 3.3.x.
- בחרו Tools → Board → ESP32 Arduino → ESP32 Dev Module.
- בחרו את ה‑Port שמופיע כשמחברים את הלוח.
- העלו קודם Blink. רק אחרי Done uploading עברו לאודיו.
הקוד במדריך קומפל בהצלחה מול Arduino‑ESP32 3.3.11. בגרסאות 2.x ממשק I2S שונה, ולכן אין לערבב דוגמאות ישנות שמייבאות driver/i2s.h.
04
בדיקת הרמקול לבדה
העלו את הקוד המצורף כשהמיקרופון והכפתור עדיין אינם חשובים. הוא מפעיל רק את MAX98357A והרמקול.
מה אמור לקרות:
- Serial Monitor במהירות 115200 מציג AUDIO TEST READY.
- נשמע צפצוף קצר וחוזר.
אם יש I2S ERROR, בדקו לוח וגרסת חבילה. אם יש הודעת READY אבל אין קול, הבעיה נמצאת ב‑VIN/GND/BCLK/LRC/DIN או בחיבור הרמקול.
#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);
}05
הקוד המלא לשני המכשירים
העלו את אותו קוד בדיוק לשני הלוחות. אין מספר “מכשיר א׳” או “מכשיר ב׳”: בזמן לחיצה כל לוח משדר, ובזמן שחרור הוא מקשיב.
הקוד מקבע את שני הצדדים לערוץ Wi‑Fi 6, קורא אודיו ב‑16kHz ושולח מנות של 200 בייט — מתחת לגודל מנת ESP‑NOW. callback הקליטה רק מכניס נתונים לתור; הניגון מתבצע ב‑loop כדי לא לחסום את משימת הרשת.
אחרי העלאה פתחו 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));
}
}06
טקס ההפעלה הראשונה
- הניחו את המכשירים במרחק כשני מטרים, כדי שהמיקרופון לא ישמע מיד את הרמקול.
- חברו USB לשניהם וחכו ל‑READY.
- לחצו PTT במכשיר א׳, המתינו כחצי שנייה ואמרו: “בדיקה אחת שתיים”.
- שחררו את הכפתור.
- רק עכשיו לחצו במכשיר ב׳ וענו.
זה חצי־דופלקס: אין דיבור בו־זמני. השהיה קטנה או אובדן הברה ראשונה אינם תקלה; לכן מתרגלים ללחוץ רגע לפני שמתחילים לדבר.
07
אבחון לפי מה שרואים ושומעים
אין Port במחשב
החליפו לכבל USB נתונים; נסו שקע אחר.
Done uploading לא מופיע
בחרו ESP32 Dev Module ו‑Port נכון; נסו להחזיק BOOT בתחילת ההעלאה.
AUDIO TEST READY אבל שקט
בדקו VIN=5V/VIN, GND משותף, BCLK=26, LRC=27, DIN=14 ושני חוטי הרמקול.
READY בשני הצדדים אבל אין קול
ודאו שהקוד זהה ו‑ESPNOW_CHANNEL הוא 6 בשניהם; בדקו שהכפתור באמת מוריד GPIO4 ל‑GND.
רעש חזק
בדקו L/R→GND במיקרופון, קצרו חוטי SCK/WS והרחיקו את המיקרופון מהרמקול.
אתחולים
נתקו מיד. חפשו קצר או ספק USB חלש. חברו את המגבר ל‑5V/VIN ואת המיקרופון רק ל‑3V3.
08
ניידות ומארז בלי סיכון מיותר
להפעלה ניידת חברו כל ESP32 בכבל USB קצר לסוללת גיבוי USB 5V תקנית שמסוגלת לספק לפחות 1A. בדקו שהיא אינה נכבית אוטומטית בזמן המתנה.
מקמו את קצה האנטנה של ה‑ESP32 ליד דופן פלסטיק, רחוק מהסוללה, מהרמקול ומחלקי מתכת. צרו פתחים נפרדים למיקרופון ולרמקול ואל תכוונו אותם זה אל זה.
לגרסה הראשונה קופסת פרויקט מוכנה או קרטון קשיח עדיפים על קובץ STL שלא נבדק. אחרי שתמדדו את הרכיבים שבידיכם אפשר לתכנן מארז מותאם. לעולם אל תסגרו חוט חשוף או תא ליתיום לא מוגן בתוך מארז.