2026年9月30日 星期三

ICS-43434 麥克風 ESP32 範例

環境:

Win10、Arduino IDE 2.3.10、ESP32-S3-WROOM-1-N16R8 開發板、ICS-43434 麥克風


線路接法:

3V3 ---------- 3.3V
GND ---------- GND
SCLK(Serial Clock, BCLK, Bit Clock) ---------- GPIO 5
DOUT ---------- GPIO 6
WS(Word Select, LRCK, Left/Right Clock) ---------- GPIO 7
SEL(L/R) ---------- GND


效果:

電腦和 ESP32 連上同一區域網路,瀏覽 ESP32 建立的 WebSocket 網頁,ESP32 將 ICS-43434 接收到的聲音,經由 WebSocket 傳送給電腦。


程式碼:

Arduino IDE
#include <WiFi.h>
#include <driver/i2s.h>
#include "mbedtls/sha1.h"
#include "mbedtls/base64.h"

const char* ssid = "wifi SSID";
const char* password = "wifi 密碼";

WiFiServer server(81);
WiFiClient client;

// I2S
#define I2S_WS 7   //CRCLK
#define I2S_SD 6   //DOUT
#define I2S_SCK 5  //BCLK
#define I2S_PORT I2S_NUM_0

bool wsConnected = false;
int tmp = 14;

// WebSocket GUID
const char* WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

// SHA1 + Base64
String generateAcceptKey(String key) {
  String combined = key + WS_GUID;

  unsigned char sha1_result[20];
  mbedtls_sha1((const unsigned char*)combined.c_str(), combined.length(), sha1_result);

  unsigned char base64_result[32];
  size_t olen;
  mbedtls_base64_encode(base64_result, sizeof(base64_result), &olen, sha1_result, 20);

  return String((char*)base64_result);
}

void setup() {
  Serial.begin(115200);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("\nWiFi OK");
  Serial.println(WiFi.localIP());

  server.begin();

  // I2S 設定
  i2s_config_t i2s_config = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate = 16000,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
    .communication_format = I2S_COMM_FORMAT_I2S,
    .intr_alloc_flags = 0,
    .dma_buf_count = 8,
    .dma_buf_len = 128,
    .use_apll = false
  };

  i2s_pin_config_t pin_config = {
    .bck_io_num = I2S_SCK,
    .ws_io_num = I2S_WS,
    .data_out_num = I2S_PIN_NO_CHANGE,
    .data_in_num = I2S_SD
  };

  i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
  i2s_set_pin(I2S_PORT, &pin_config);
}

void handleWebSocketHandshake() {
  String request = client.readStringUntil('\r');

  if (request.indexOf("GET") >= 0) {
    String key;

    while (client.available()) {
      String line = client.readStringUntil('\r');
      if (line.indexOf("Sec-WebSocket-Key:") >= 0) {
        key = line.substring(line.indexOf(":") + 2);
        key.trim();
      }
      if (line == "\n") break;
    }

    String acceptKey = generateAcceptKey(key);

    client.print(
      "HTTP/1.1 101 Switching Protocols\r\n"
      "Upgrade: websocket\r\n"
      "Connection: Upgrade\r\n"
      "Sec-WebSocket-Accept: "
      + acceptKey + "\r\n\r\n");

    wsConnected = true;
    Serial.println("WebSocket Connected");
  }
}

// 發送 binary frame
void sendWSBinary(uint8_t* data, size_t len) {
  if (!wsConnected) return;

  client.write(0x82);  // FIN + binary

  if (len < 126) {
    client.write(len);
  } else {
    client.write(126);
    client.write((len >> 8) & 0xFF);
    client.write(len & 0xFF);
  }

  client.write(data, len);
}

void loop() {
  if (!client || !client.connected()) {
    client = server.available();
    if (client) {
      Serial.println("Client connected");
      wsConnected = false;
    }
  }

  if (client && !wsConnected) {
    handleWebSocketHandshake();
  }

  if (wsConnected) {
    int32_t i2sData[256];
    int16_t audioData[256];
    size_t bytesRead;

    i2s_read(I2S_PORT, (void*)i2sData, sizeof(i2sData), &bytesRead, portMAX_DELAY);

    int samples = bytesRead / 4;

    for (int i = 0; i < samples; i++) {
      audioData[i] = i2sData[i] >> 14;
    }

    sendWSBinary((uint8_t*)audioData, samples * 2);
  }
}



HTML
<!DOCTYPE html>
<html>
<body>
<button onclick="start()">開始</button>

<script>
let ctx;
let ws;

function start() {
  ctx = new AudioContext({ sampleRate: 16000 });

  ws = new WebSocket("ws://192.168.100.4:81");
  ws.binaryType = "arraybuffer";

  ws.onmessage = (e) => {
    let int16 = new Int16Array(e.data);
    let float32 = new Float32Array(int16.length);

    for (let i = 0; i < int16.length; i++) {
      float32[i] = int16[i] / 32768;
    }

    let buffer = ctx.createBuffer(1, float32.length, 16000);
    buffer.copyToChannel(float32, 0);

    let src = ctx.createBufferSource();
    src.buffer = buffer;
    src.connect(ctx.destination);
    src.start();
  };
}
</script>
</body>
</html>


參考:


沒有留言:

張貼留言