Loa trợ lý AI trên ESP32-S3: cấu tạo phần cứng, cách hoạt động, độ trễ, quyền riêng tư, thử âm thanh

Loa trợ lý AI tự làm bằng ESP32-S3 hoạt động thế nào: micro I2S, mạch khuếch đại, phần chạy trên chip và trên máy chủ, vì sao có độ trễ, dữ liệu đi đâu, chương trình thử ghi âm, phát lại.

Những chiếc loa trợ lý AI nhỏ gọn tự làm từ ESP32-S3 ngày càng phổ biến: nói "Hi ESP", hỏi một câu bằng tiếng Việt, vài giây sau loa trả lời bằng giọng nói. Một vi điều khiển vài trăm KB RAM làm sao "trò chuyện" được? Câu trả lời: loa chỉ làm phần nghe, gửi và phát; phần hiểu câu và nghĩ câu trả lời nằm ở máy chủ. Bài này giải thích cấu tạo phần cứng, luồng xử lý một câu hỏi, vì sao có độ trễ, dữ liệu đi đâu, và kèm chương trình thử khối âm thanh (ghi âm từ micro, phát lại qua loa) trên ESP32-S3.

1. Cấu tạo phần cứng

Sơ đồ: micro I2S INMP441 gửi tiếng 16 kHz vào ESP32-S3 N16R8; nút và đèn LED nối GPIO; ESP32-S3 phát I2S tới mạch khuếch đại MAX98357A rồi ra loa 4 ohm 3 W; pin 18650 với mạch sạc, tăng áp 5 V hoặc nguồn USB cấp điện
Micro, mạch khuếch đại và loa nối I2S với ESP32-S3.
  • ESP32-S3 có PSRAM (ví dụ N16R8): Wi-Fi, đủ RAM để đệm âm thanh, chạy được từ đánh thức tại chỗ.
  • Micro I2S INMP441: micro số, nối thẳng vào chip, ít nhiễu hơn micro analog.
  • Mạch khuếch đại I2S MAX98357A và loa 4 Ω, 3 W: nhận âm thanh số qua I2S, không cần DAC rời.
  • Nút, đèn LED (hoặc màn hình nhỏ): bấm để nói, báo trạng thái đang nghe, đang nghĩ.
  • Nguồn: USB 5 V, hoặc pin 18650 với mạch sạc và tăng áp. Loa phát to kéo dòng đỉnh lớn: nguồn yếu làm chip khởi động lại.

2. Một câu hỏi đi qua những đâu

Trên loa: từ đánh thức nghe tại chỗ, ghi âm câu nói và nén, gửi qua Wi-Fi, phát câu trả lời; trên máy chủ: nhận dạng tiếng nói thành chữ, mô hình ngôn ngữ nghĩ câu trả lời, tổng hợp tiếng nói thành âm thanh gửi về loa
Chỉ từ đánh thức chạy hoàn toàn trên chip.
  1. Từ đánh thức chạy ngay trên ESP32-S3 (như bài nhận lệnh giọng nói bằng ESP-SR cùng loạt): loa không gửi gì đi khi chưa nghe thấy.
  2. Ghi âm và nén: âm thanh 16 kHz, 16 bit là 256 kbit/s; nén (ví dụ chuẩn Opus dành cho giọng nói) còn vài chục kbit/s, nhẹ cho Wi-Fi.
  3. Gửi lên máy chủ qua một kết nối giữ liên tục (thường là WebSocket).
  4. Máy chủ: nhận dạng tiếng nói thành chữ, đưa vào mô hình ngôn ngữ để có câu trả lời, rồi tổng hợp thành giọng nói. Tiếng Việt được hỗ trợ ở phía máy chủ, không phụ thuộc vào chip.
  5. Phát: loa nhận âm thanh, giải nén, phát qua mạch khuếch đại.

3. Vì sao phải chờ vài giây

Thanh thời gian minh họa: người nói xong, loa nhận ra hết câu; gửi phần cuối, nhận dạng chữ; mô hình nghĩ ra câu trả lời chiếm phần dài nhất; tổng hợp tiếng, gửi về; bắt đầu phát
Tỉ lệ minh họa, không phải số đo.

Mỗi bước cộng thêm thời gian chờ. Các thiết kế tốt rút ngắn bằng cách gửi âm thanh liên tục trong lúc người còn nói, máy chủ trả lời từng đoạn và loa phát ngay đoạn đầu, chọn máy chủ ở gần, giữ Wi-Fi ổn định.

4. Dữ liệu đi đâu

Bảng: âm thanh trước từ đánh thức ở lại trên loa, không ra ngoài; câu nói sau từ đánh thức gửi lên máy chủ; câu trả lời từ máy chủ về loa; khóa dịch vụ nên đặt ở máy chủ trung gian, không ghi trong loa
Điều cần biết về quyền riêng tư khi đặt loa trong nhà.

Đặt đèn báo khi loa đang gửi âm thanh, có nút tắt micro, và chọn dịch vụ máy chủ có chính sách dữ liệu rõ ràng. Khóa dịch vụ không nên ghi thẳng trong loa (xem phần bảo vệ khóa trong bài ESP32 gọi API mô hình ngôn ngữ cùng loạt).

5. Thử khối âm thanh: ghi âm và phát lại

Trước khi ghép cả hệ thống, nên thử riêng "tai" và "miệng" của loa. Nối micro INMP441: SCK GPIO4, WS GPIO5, SD GPIO6, VDD 3V3, GND, L/R nối GND. Nối MAX98357A: BCLK GPIO15, LRC GPIO16, DIN GPIO7, VIN 5V, GND; hai dây loa vào cổng ra. Chương trình chỉ dùng thư viện ESP_I2S có sẵn trong gói ESP32; trong Arduino IDE chọn bo ESP32-S3 và bật PSRAM.

// PYWORLD.VN - Khối âm thanh của loa trợ lý trên ESP32-S3: giữ nút BOOT để ghi âm từ micro INMP441, thả nút để phát lại
// PYWORLD.VN - Hai bộ I2S: một nhận từ micro (16 kHz), một phát ra mạch khuếch đại MAX98357A; tự chỉnh âm lượng trước khi phát
// PYWORLD.VN - Micro: SCK GPIO4, WS GPIO5, SD GPIO6; loa: BCLK GPIO15, LRC GPIO16, DIN GPIO7

#include "ESP_I2S.h"

const int CHAN_NUT = 0;                      // nút BOOT, nhấn = mức thấp
const int MIC_SCK = 4, MIC_WS = 5, MIC_SD = 6;
const int LOA_BCLK = 15, LOA_LRC = 16, LOA_DIN = 7;
const uint32_t TAN_SO = 16000;
const uint32_t GIAY_TOI_DA = 5;
const size_t SO_MAU_TOI_DA = TAN_SO * GIAY_TOI_DA;
const int32_t DINH_MONG_MUON = 20000;        // biên độ đích sau khi chỉnh âm lượng (tối đa 32767)
const float HE_SO_TOI_DA = 8.0f;

I2SClass mic;
I2SClass loa;
int16_t *boDem = nullptr;

void setup() {
  Serial.begin(115200);
  pinMode(CHAN_NUT, INPUT_PULLUP);
  boDem = (int16_t *)ps_malloc(SO_MAU_TOI_DA * sizeof(int16_t));   // ưu tiên PSRAM
  if (!boDem) boDem = (int16_t *)malloc(SO_MAU_TOI_DA * sizeof(int16_t));
  if (!boDem) {
    Serial.println("Khong du bo nho cho bo dem am thanh");
    while (true) delay(1000);
  }

  mic.setPins(MIC_SCK, MIC_WS, -1, MIC_SD);
  mic.begin(I2S_MODE_STD, TAN_SO, I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_MONO, I2S_STD_SLOT_LEFT);
  mic.configureRX(TAN_SO, I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_MONO, I2S_RX_TRANSFORM_32_TO_16);

  loa.setPins(LOA_BCLK, LOA_LRC, LOA_DIN);
  loa.begin(I2S_MODE_STD, TAN_SO, I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO);
  Serial.println("Giu nut BOOT de noi, tha ra de nghe lai (toi da 5 giay)");
}

void loop() {
  if (digitalRead(CHAN_NUT) == HIGH) {
    delay(10);
    return;
  }

  // Ghi âm trong lúc giữ nút
  size_t daGhi = 0;
  while (digitalRead(CHAN_NUT) == LOW && daGhi < SO_MAU_TOI_DA) {
    size_t conLai = SO_MAU_TOI_DA - daGhi;
    size_t lanNay = conLai < 512 ? conLai : 512;
    size_t soByte = mic.readBytes((char *)(boDem + daGhi), lanNay * sizeof(int16_t));
    daGhi += soByte / sizeof(int16_t);
  }

  // Tìm đỉnh, tự chỉnh âm lượng để giọng nói đủ to mà không bị méo
  int32_t dinh = 0;
  for (size_t i = 0; i < daGhi; i++) {
    int32_t a = abs((int32_t)boDem[i]);
    if (a > dinh) dinh = a;
  }
  float heSo = dinh > 0 ? (float)DINH_MONG_MUON / dinh : 1.0f;
  if (heSo > HE_SO_TOI_DA) heSo = HE_SO_TOI_DA;
  for (size_t i = 0; i < daGhi; i++) {
    int32_t v = (int32_t)(boDem[i] * heSo);
    boDem[i] = (int16_t)(v > 32767 ? 32767 : (v < -32768 ? -32768 : v));
  }
  Serial.printf("Ghi %.2f giay, dinh %ld, he so am luong %.1f\n", daGhi / (float)TAN_SO, (long)dinh, heSo);

  loa.write((uint8_t *)boDem, daGhi * sizeof(int16_t));
  Serial.println("Phat lai xong");
  while (digitalRead(CHAN_NUT) == LOW) delay(10);
}
Serial Monitor: giữ nút BOOT để nói, thả ra để nghe lại; ghi 2,37 giây, đỉnh 3120, hệ số âm lượng 6,4; phát lại xong
Độ dài, đỉnh là ví dụ; hệ số âm lượng tính đúng công thức.
  • Hai bộ I2S chạy cùng lúc: một nhận từ micro, một phát ra mạch khuếch đại.
  • Bộ đệm 5 giây (160 KB) đặt trong PSRAM nếu có.
  • Chương trình tự chỉnh âm lượng: tìm biên độ lớn nhất của đoạn ghi, nhân lên để đỉnh đạt khoảng 20000 (tối đa 8 lần), tránh méo tiếng.
  • Nghe lại rõ, không rè: micro và loa đã sẵn sàng cho bước tiếp theo.

6. Ghép thành loa hoàn chỉnh

  • Thêm từ đánh thức bằng ESP_SR thay cho nút bấm.
  • Thêm phần nén âm thanh và kết nối WebSocket tới máy chủ; máy chủ gọi các dịch vụ nhận dạng tiếng nói, mô hình ngôn ngữ, tổng hợp tiếng nói.
  • Cách nhanh hơn: dùng một dự án mã nguồn mở dựng sẵn cho loa trợ lý ESP32-S3 (có cả chương trình cho loa và phần máy chủ). Chọn dự án còn được duy trì, đọc kỹ chính sách dữ liệu và chi phí dịch vụ đi kèm.

7. Lỗi hay gặp

Hiện tượngNguyên nhân thường gặpCách xử lý
Tiếng hú, rè khi vừa nghe vừa phátMicro thu lại tiếng của chính loaĐặt micro xa loa, hướng khác nhau; dùng khử vọng âm (AEC) với kênh tham chiếu
Phát rất nhỏChân GAIN của MAX98357A, loa trở kháng caoĐổi cách nối GAIN theo tài liệu; dùng loa 4 Ω
Chip khởi động lại khi phát toNguồn yếu, dây USB dàiNguồn 5 V đủ dòng, thêm tụ lớn gần mạch khuếch đại
Ghi được toàn số 0Chân L/R chưa nối, sai chân SD/WSNối L/R vào GND, kiểm tra dây
Báo không đủ bộ nhớChưa bật PSRAMBật PSRAM đúng loại của bo

8. Xem thêm

  • Nguồn pin: mô-đun pin 18650 ra 5 V, khay pin 18650; lắp thử với dây cắm.
  • Các bài cùng loạt AI trên vi điều khiển: nhận lệnh giọng nói bằng ESP-SR, ESP32 gọi API mô hình ngôn ngữ, TinyML là gì; bài micro INMP441 trong loạt module, bài phát âm thanh I2S trong loạt ESP32 nâng cao.

Tin tức liên quan

Jack DC 5.5×2.1 và 5.5×2.5: phân biệt, cực tính, chọn jack gắn hộp và dây nguồn DC
Jack DC 5.5×2.1 và 5.5×2.5: phân biệt, cực tính, chọn jack gắn hộp và dây nguồn DC

Jack DC 5.5×2.1 và 5.5×2.5 khác nhau ở lỗ trong: cách phân biệt khi không có nhãn, ký hiệu tâm dương, đo cực tính adapter, lắp jack gắn hộp và nối nguồn vào mạch an toàn.

Từ Scratch sang Python cho trẻ: khi nào chuyển, khối nào ứng với lệnh nào
Từ Scratch sang Python cho trẻ: khi nào chuyển, khối nào ứng với lệnh nào

Hướng dẫn học sinh chuyển từ Scratch sang Python: dấu hiệu sẵn sàng, cài Thonny, bảng khối Scratch ứng với lệnh Python, hai dự án đố vui phép nhân và con rùa vẽ đa giác.

FreeRTOS trên ESP32: task, độ ưu tiên, hai nhân, vTaskDelay và vTaskDelayUntil cho người mới
FreeRTOS trên ESP32: task, độ ưu tiên, hai nhân, vTaskDelay và vTaskDelayUntil cho người mới

Task trong FreeRTOS là gì, bốn trạng thái, độ ưu tiên, chọn nhân với xTaskCreatePinnedToCore, stack tính bằng byte, vTaskDelay và vTaskDelayUntil, chương trình ba task trên hai nhân.

Cảm biến quang: thu phát, phản xạ gương, khuếch tán, triệt nền – cấu tạo, nguyên lý và chế tạo
Cảm biến quang: thu phát, phản xạ gương, khuếch tán, triệt nền – cấu tạo, nguyên lý và chế tạo

Cảm biến quang hoạt động thế nào: bốn kiểu thu phát, phản xạ gương, khuếch tán, triệt nền; điều chế ánh sáng chống nhiễu, cấu tạo, cách chế tạo và làm cảm biến phản xạ có điều chế với ESP32.

Modbus TCP/IP (phần 2): ESP32 làm máy khách và cổng chuyển đổi Modbus TCP sang RTU
Modbus TCP/IP (phần 2): ESP32 làm máy khách và cổng chuyển đổi Modbus TCP sang RTU

Hướng dẫn dùng ESP32 làm máy khách Modbus TCP đọc thanh ghi của một thiết bị trong mạng, và làm cổng chuyển đổi Modbus TCP sang Modbus RTU qua RS-485: cách đổi khung, mã thiết bị, thời gian chờ, mã lỗi 0x0B.

Lập trình nuôi dưỡng tư duy sáng tạo của trẻ: bài toán nhiều lời giải, dự án mở, vòng học sáng tạo
Lập trình nuôi dưỡng tư duy sáng tạo của trẻ: bài toán nhiều lời giải, dự án mở, vòng học sáng tạo

Vì sao học lập trình nuôi dưỡng tư duy sáng tạo của trẻ: bằng chứng nghiên cứu, bài toán nhiều lời giải, học qua làm sản phẩm, vòng học sáng tạo, bốn tiêu chí và thử thách mở.

Cảm biến mô men xoắn: cấu tạo, nguyên lý strain gauge 45°, cách chế tạo và bàn thử công suất
Cảm biến mô men xoắn: cấu tạo, nguyên lý strain gauge 45°, cách chế tạo và bàn thử công suất

Cảm biến mô men xoắn hoạt động thế nào: strain gauge xiên 45° trên trục, vành trượt, biến áp quay, đo góc xoắn, từ giảo, cách chế tạo và bàn thử mô men, công suất với ESP32.

Pin mặt trời mini cho ESP32: chọn tấm pin, mạch sạc, pin 18650, ngủ sâu và tính năng lượng mỗi ngày
Pin mặt trời mini cho ESP32: chọn tấm pin, mạch sạc, pin 18650, ngủ sâu và tính năng lượng mỗi ngày

ESP32 chạy bằng pin mặt trời: các khối tấm pin, mạch sạc, pin 18650, ổn áp; tính năng lượng tiêu thụ và thu được mỗi ngày; chương trình ngủ sâu đo điện áp pin, tấm pin; lắp đặt ngoài trời.


Bình luận
Đã thêm vào giỏ hàng