Bài 118 · Công nghệ · Tin học · STEM
Nhà thông minh Xiaozhi
hỏi trước, bật đèn sau
Trợ lý giọng nói chạy trên ESP32-S3. Nhưng điểm đáng học của dự án này không phải chuyện nó nghe được tiếng người — mà là chuyện nó cố tình KHÔNG làm ngay. Mọi lệnh chạm vào phần cứng đều phải được xác nhận ở lượt thoại kế tiếp.
01 Mục tiêu và yêu cầu cần đạt
Đây là dự án cuối cùng và khó nhất của trang STEM ESP32. Nhưng lí do nó có mặt ở đây không phải vì độ khó kỹ thuật.
Học sinh bây giờ dùng trợ lý AI hằng ngày và mặc định tin nó. Dự án này cho các em tự tay xây một trợ lý — và tự tay xây luôn cái phanh cho nó. Sau khi viết cổng xác nhận, không em nào còn nghĩ rằng "AI làm gì cũng được là chuyện đương nhiên".
Sau dự án này học sinh làm được
- Phân biệt bus I2S thu âm và I2S phát âm, nối được cả hai trên cùng một bo.
- Giải thích luồng: micro → nén âm → máy chủ → nhận dạng lời nói → mô hình ngôn ngữ → lệnh trả về → loa.
- Hiểu tool calling: mô hình ngôn ngữ không tự bật đèn, nó chỉ đề nghị gọi một hàm; phần mềm của ta mới là bên quyết định.
- Thiết kế và cài đặt một máy trạng thái ba bước
chờ → xác nhận → tiêu thụ. - Lập luận được vì sao một hệ AI điều khiển phần cứng bắt buộc phải có bước xác nhận, và nêu ví dụ hậu quả nếu không có.
- Kiểm thử một hệ thống bằng kịch bản, kể cả các kịch bản cố tình phá hoại.
- Công nghệ 12 — Vi điều khiển; thiết kế mạch điện điều khiển; ngành nghề kĩ thuật điện – điện tử.
- Tin học 11–12 — mạng máy tính và Internet; giao thức truyền dữ liệu; ứng dụng trí tuệ nhân tạo; đạo đức và pháp luật trong môi trường số.
- Vật lí 11 — sóng âm: thu và phát âm thanh, lấy mẫu tín hiệu.
Trích nguyên văn yêu cầu cần đạt từ chương trình môn học trước khi đưa vào kế hoạch bài dạy.
02 Lí thuyết
Bảy chặng của một câu nói
Từ "bật đèn giúp tôi" đến lúc đèn sáng:
- Micro INMP441 → số, 16 nghìn mẫu/giây, qua bus I2S.
- ESP32-S3 nén luồng âm cho nhẹ đường truyền.
- Gửi lên máy chủ qua WebSocket có mã hóa.
- Máy chủ nhận dạng lời nói → chữ.
- Mô hình ngôn ngữ đề nghị gọi hàm
bat_den(). - Phần mềm của ta xét đề nghị đó — chặng quan trọng nhất.
- Đọc câu trả lời gửi về loa qua MAX98357A.
Ba dây mỗi bus: nhịp bit (SCK/BCLK), chọn kênh
trái phải (WS/LRC), và dữ liệu.
Thu và phát không dùng chung bus — đó là lí do sơ đồ có tới sáu chân chỉ cho âm thanh.
Tool calling — mô hình không tự làm gì cả
Hiểu lầm phổ biến nhất: người ta nghĩ AI "bật đèn". Không phải. Mô hình chỉ
sinh ra chữ. Biết có hàm bat_den, nó trả về một đoạn văn bản có
cấu trúc, đại ý "tôi đề nghị gọi hàm bat_den".
Gọi hay không là do phần mềm của ta quyết định. Đó là chỗ cài phanh, và là bài học lớn nhất của dự án: quyền quyết định nằm ở người thiết kế hệ thống, không nằm ở mô hình.
Vì sao phải có cổng xác nhận
Một hệ không có nó, đặt trong lớp học:
- Học sinh nói đùa "tắt hết đèn đi" — đèn tắt thật, giữa giờ kiểm tra.
- Loa máy tính đang phát video có câu "bật máy bơm" — bơm chạy.
- Nhận dạng nhầm "bật quạt" thành "bật lò".
Cả ba biến mất nếu hệ luôn hỏi lại và chỉ hành động khi có xác nhận ở lượt thoại kế tiếp.
- Chờ xác nhận — sinh mã ngẫu nhiên, hỏi lại người dùng.
- Đã xác nhận — người dùng đồng ý ở lượt kế, mã còn hạn.
- Đã tiêu thụ — thực hiện xong, hủy mã ngay. Nói "đồng ý" lần nữa không làm gì cả.
Ba chốt: mã dùng một lần, hết hạn sau 30 giây, và không nhận xác nhận trong cùng một lượt nói. Chốt thứ ba là cố ý — cho phép "bật đèn, đồng ý luôn" thì cổng xác nhận thành vô nghĩa.
Lệnh đọc và lệnh ghi
| Loại lệnh | Ví dụ | Có cần xác nhận |
|---|---|---|
| Đọc trạng thái | "Đèn đang bật hay tắt?" | Không. Thực hiện ngay |
| Đổi trạng thái | "Bật đèn" · "Mở van nước" | Bắt buộc |
Nguyên tắc chung của mọi hệ điều khiển: đọc thì tự do, ghi thì phải xin phép. Học sinh mang được nguyên tắc này sang mọi hệ thống khác.
03 Linh kiện
| Linh kiện | Mô tả, lưu ý khi mua | Số lượng · giá |
|---|---|---|
| ESP32-S3 DevKit N16R8 | 16 MB Flash, 8 MB PSRAM Octal. BẮT BUỘC là S3, xem hộp cạm bẫy | 1 · 150–250k |
| Micro I2S INMP441 | Chân L/R phải nối GND | 1 · 60–90k |
| Module MAX98357A | Vừa giải mã vừa khuếch đại âm thanh, giao tiếp I2S | 1 · 60–90k |
| Loa mini | 4 Ω, 3 W, đường kính 30–40 mm | 1 · 25–40k |
| Màn hình OLED SSD1306 | 0,96 inch, I2C, 128×64 | 1 · 45–80k |
| Module relay 5 V | Ưu tiên loại có cách li quang | 1 · 20–35k |
| Đèn LED 5 V hoặc đèn bàn 5 V | Tải minh họa. KHÔNG dùng đèn 220 V trong bài học sinh | 1 |
| Nguồn USB-C 5 V 2–3 A | Nguồn yếu là bo mạch reset khi loa kêu to | 1 · 80k |
| Breadboard và dây cắm | 1 bộ · 40k |
04 Lắp mạch và sơ đồ nối dây
| Khối | Nối dây | Nguồn |
|---|---|---|
| Micro INMP441 | SCK → GPIO41 · WS → GPIO42 · SD → GPIO2 · L/R → GND | 3,3 V |
| Loa MAX98357A | BCLK → GPIO15 · LRC → GPIO16 · DIN → GPIO7 | 5 V |
| OLED SSD1306 | SDA → GPIO8 · SCL → GPIO9 | 3,3 V |
| Relay | IN → GPIO4 · mặc định TẮT khi khởi động | 5 V |
Toàn cảnh mạch trên breadboard: ESP32-S3 ở giữa, micro INMP441 và module MAX98357A hai bên, OLED phía trên, relay phía dưới. Chụp thẳng từ trên xuống, thấy rõ toàn bộ dây.
Khung giữ chỗ — thay bằng ảnh chụp thật khi cóCận cảnh micro INMP441, chỉ rõ chân L/R đã nối xuống GND bằng một dây ngắn — chi tiết mà bỏ quên là micro chỉ thu được một nửa.
Khung giữ chỗ — thay bằng ảnh chụp thật khi cóCận cảnh module relay và bóng đèn 5 V nối vào ngõ ra, có nhãn dán ghi rõ đây là tải thấp áp.
Khung giữ chỗ — thay bằng ảnh chụp thật khi cóMàn hình OLED trong bốn trạng thái: đang kết nối Wi-Fi, đang nghe, đang chờ xác nhận, và đã thực hiện. Ghép bốn ảnh thành một hình.
Khung giữ chỗ — thay bằng ảnh chụp thật khi cóHọc sinh đang nói với thiết bị trong lớp, màn chiếu phía sau hiện nhật ký hội thoại và trạng thái relay.
Khung giữ chỗ — thay bằng ảnh chụp thật khi cóDự án này cần ESP32-S3 với 8 MB PSRAM. ESP32 cổ điển và ESP32-C3 không chạy được, vì thiếu bộ nhớ đệm cho luồng âm thanh và thiếu tập lệnh xử lý tín hiệu.
Khi mua, tìm đúng chữ ESP32-S3 N16R8. Con số đó nghĩa là 16 MB Flash và 8 MB PSRAM. Bản N8R2 rẻ hơn nhưng không đủ.
Đây là khoản tiền dễ mất nhất trong cả trang STEM này. Tôi biết vài thầy cô đã mua nhầm cả lô.
Thứ tự lắp — làm từng khối, thử từng khối
Đừng lắp hết rồi mới cắm điện. Bốn khối, lắp xong khối nào thử ngay khối đó:
- OLED trước. Dễ nhất, và nó là màn hình báo lỗi cho ba
khối sau. Quét bus I2C thấy
0x3Clà được. - Micro. Chạy chương trình thu 3 giây rồi in mức âm ra màn hình. Vỗ tay thấy số nhảy là đúng.
- Loa. Phát một file âm thanh mẫu có sẵn trong firmware.
- Relay cuối cùng. Và thử bằng đèn LED 5 V trước khi nối bất cứ thứ gì khác.
Module INMP441 có một chân ghi L/R. Nếu để thả nổi, micro không
biết mình là kênh trái hay phải, và bạn thu được một luồng âm thanh chỉ có
nhiễu hoặc im lặng hoàn toàn.
Nối chân L/R xuống GND. Một sợi dây ngắn, và nó là khác biệt giữa "micro chạy" với "ngồi dò lỗi cả buổi tối".
- Chỉ dùng tải 5 V trong bài học sinh. Đèn LED, quạt mini, còi. Tuyệt đối không đấu đèn 220 V — kể cả khi module relay có ghi chịu được 250 V.
- Relay phải TẮT khi bo mạch vừa khởi động. Kiểm tra bằng cách rút và cắm lại nguồn nhiều lần. Nếu đèn nháy một cái lúc khởi động thì logic của relay bị ngược — đảo lại, hoặc dùng chân NC.
- Nguồn phải khỏe. Loa kêu to hút dòng vọt lên; nguồn USB yếu làm bo mạch reset ngay giữa câu nói. Dùng adapter 5 V 2–3 A, đừng lấy điện từ cổng USB máy tính.
- GND chung giữa ESP32, module relay và nguồn tải.
05 Mã nguồn
Mã đầy đủ tại kho GitHub, thư mục cap-6/bai-84-xiaozhi/. Phần firmware viết bằng C++ trên ESP-IDF vì thư viện âm thanh I2S và mã hóa Opus chưa có bản MicroPython đủ nhanh. Phần máy chủ chạy trên hạ tầng VatLi365.
Firmware đầy đủ khá dài — có phần Wi-Fi, mã hóa âm thanh, WebSocket, quản lí phiên. Ở đây tôi chỉ in phần đáng học nhất: cổng xác nhận. Đây cũng là phần học sinh nên tự viết lại từ đầu thay vì chép.
Cổng xác nhận — máy trạng thái ba bước// ------------------------------------------------------------
// Bai 118 - Cong xac nhan truoc khi cham vao phan cung
// Moi lenh doi trang thai deu phai qua ba buoc:
// pending -> confirmed -> consumed
// Token chi dung mot lan va het han sau 30 giay.
// ------------------------------------------------------------
enum TrangThai { RANH, CHO_XAC_NHAN, DA_XAC_NHAN };
struct YeuCau {
TrangThai trang_thai = RANH;
uint32_t token = 0;
uint32_t het_han_ms = 0;
char hanh_dong[32];
};
static YeuCau yeu_cau;
const uint32_t HAN_XAC_NHAN_MS = 30000;
// Buoc 1: AI hieu duoc y dinh, nhung KHONG duoc lam ngay
void taoYeuCau(const char* hanh_dong) {
yeu_cau.trang_thai = CHO_XAC_NHAN;
yeu_cau.token = esp_random();
yeu_cau.het_han_ms = millis() + HAN_XAC_NHAN_MS;
strncpy(yeu_cau.hanh_dong, hanh_dong, sizeof(yeu_cau.hanh_dong) - 1);
noiRa("Ban co chac chan muon " + String(hanh_dong) + " khong?");
}
// Buoc 2: chi chap nhan o LUOT THOAI KE TIEP
bool xacNhan(uint32_t token_nhan) {
if (yeu_cau.trang_thai != CHO_XAC_NHAN) return false; // khong co gi cho
if (token_nhan != yeu_cau.token) return false; // token sai
if (millis() > yeu_cau.het_han_ms) { // het han
yeu_cau.trang_thai = RANH;
noiRa("Yeu cau da het han. Ban noi lai giup toi.");
return false;
}
yeu_cau.trang_thai = DA_XAC_NHAN;
return true;
}
// Buoc 3: thuc hien roi HUY token ngay lap tuc
void thucHien() {
if (yeu_cau.trang_thai != DA_XAC_NHAN) return;
dieuKhienRelay(yeu_cau.hanh_dong);
yeu_cau.trang_thai = RANH; // token da tieu thu, khong dung lai duoc
yeu_cau.token = 0;
} yeu_cau.token = esp_random()— mã ngẫu nhiên, không đoán được. Nếu dùng số đếm tăng dần thì kẻ biết quy luật có thể giả mạo.if (millis() > yeu_cau.het_han_ms)— hết hạn thì hủy. Một câu "đồng ý" nói vu vơ sau đó năm phút không được tính.yeu_cau.trang_thai = RANHngay sau khi thực hiện — token bị tiêu thụ, không dùng lại được.if (yeu_cau.trang_thai != DA_XAC_NHAN) return— hàm thực hiện tự nó kiểm tra lại lần nữa. Không tin vào việc bên gọi đã kiểm tra rồi.
Dòng cuối là một nguyên tắc lập trình an toàn đáng dạy: mỗi lớp tự bảo vệ mình, đừng giả định lớp trên đã làm đúng.
06 Hiệu chuẩn và kiểm thử
Kiểm thử theo kịch bản
Một hệ thống điều khiển phần cứng không thể "thử qua thấy chạy là được". Phải có danh sách kịch bản, chạy đủ, và ghi lại kết quả.
Năm kịch bản bắt buộc# Nam kich ban BAT BUOC phai thu truoc khi cho hoc sinh dung # Ghi lai ket qua tung kich ban vao so nhat ky thiet bi. 1. LUONG THUAN "Bat den giup toi" -> AI hoi lai, relay VAN TAT "Dong y, bat di" -> relay BAT Ket qua mong doi: den sang o luot thu hai, khong phai luot dau. 2. TU CHOI "Bat den giup toi" -> AI hoi lai "Khong, thoi dung bat" -> relay GIU NGUYEN Ket qua mong doi: den khong sang, va AI xac nhan da huy. 3. HET HAN 30 GIAY "Bat den giup toi" -> AI hoi lai (im lang 35 giay) "Dong y" -> BI TU CHOI vi token het han Ket qua mong doi: AI bao het han va yeu cau noi lai. 4. GOP HAI LUOT LAM MOT "Bat den, dong y luon" -> PHAI BI TU CHOI Ket qua mong doi: he thong van hoi lai. Day la chot an toan co y, khong phai loi. 5. MAT MANG GIUA CHUNG "Bat den giup toi" -> AI hoi lai (rut Wi-Fi 5 giay) Ket qua mong doi: relay KHONG doi trang thai. Khi co mang lai, yeu cau cu da bi huy.
Kịch bản 4 là kịch bản mà học sinh hay tranh cãi: "em nói liền một câu cho nhanh, sao máy không chịu?". Đây là lúc giải thích rằng đó là thiết kế có chủ ý, không phải lỗi — vì nếu cho phép gộp thì một câu nói vô tình cũng đủ để điều khiển thiết bị.
Danh sách kiểm phần cứng
- Quét I2C thấy OLED ở địa chỉ 0x3C.
- Micro thu được: vỗ tay thấy mức âm trên màn hình nhảy.
- Loa phát rõ tiếng, không rè khi âm lượng vừa.
- Relay TẮT ngay khi cấp nguồn, thử rút cắm năm lần.
- Nói to gần loa không làm bo mạch reset — nếu có thì nguồn yếu.
- OLED hiện đúng bốn trạng thái: kết nối, đang nghe, chờ xác nhận, đã thực hiện.
- Cả năm kịch bản trên đều cho kết quả đúng như mong đợi.
Với một thiết bị điều khiển bằng giọng nói, người dùng không biết máy đang nghĩ gì. Nên màn hình OLED không phải để trang trí — nó là phần an toàn của hệ thống.
Bốn trạng thái tối thiểu phải hiện được: đang kết nối, đang nghe, đang chờ bạn xác nhận, và đã thực hiện xong. Học sinh nhìn màn hình là biết máy đang ở đâu trong quy trình.
07 Kinh nghiệm và cạm bẫy
Bản đầu tiên không có nó. Nói "bật đèn" là đèn bật ngay. Rất mượt, và tôi khá tự hào.
Rồi tôi mang đi trình diễn. Đang giới thiệu, tôi nói với khán phòng: "ví dụ tôi muốn bật đèn thì tôi nói bật đèn" — và đèn bật. Tôi nói tiếp: "còn nếu tôi bảo tắt đèn" — đèn tắt.
Cả phòng cười. Nhưng tôi thì lạnh sống lưng. Vì nếu thay bóng đèn bằng một cái van nước, một cái bếp điện, hay một cánh cửa, thì câu nói vô tình ấy đã gây chuyện thật.
Buổi tối hôm đó tôi viết cổng xác nhận. Và hóa ra, phần "kém mượt" ấy lại là phần đáng dạy nhất của cả dự án.
Lần đầu cho học sinh dùng, chỉ trong mười phút các em đã thử: nói thật nhanh để gộp hai lượt; nhờ bạn khác nói "đồng ý" thay; mở video trên điện thoại có tiếng người nói "bật đèn"; và nói "đồng ý" liên tục xem có bật được hai lần không.
Tôi không cấm. Tôi biến nó thành một hoạt động chính thức: mười lăm phút "thử phá hệ thống", nhóm nào tìm được kẽ hở thì được điểm cộng.
Kết quả là các em tìm ra hai lỗ hổng mà tôi không nghĩ tới, và quan trọng hơn — các em hiểu vì sao một hệ thống thật cần được kiểm thử bởi người muốn phá nó. Đó là bài học an toàn thông tin đầu tiên và nhớ lâu nhất.
- Nguồn yếu. Bo mạch reset đúng lúc loa kêu to. Tôi mất một buổi tối nghi ngờ firmware trước khi đổi sang adapter 3 A.
- Micro thu toàn nhiễu. Chân L/R thả nổi. Một sợi dây.
- Wi-Fi trường có trang đăng nhập. Thiết bị không qua được. Phải dùng hotspot điện thoại — như đã nói ở Chương 10 của sách.
- Thiết bị gửi âm thanh lên máy chủ. Không xử lý tại chỗ. Nghĩa là mọi câu nói trong tầm micro đều rời khỏi phòng học. Phải nói rõ điều này với học sinh và phụ huynh trước khi dùng.
- Nhận dạng tiếng Việt còn sai. Nhất là với giọng địa phương và trong phòng ồn. Đây là hạn chế thật, đừng che giấu — và nó chính là lí do nữa để có cổng xác nhận.
- Không có mạng là không hoạt động. Toàn bộ phần thông minh nằm ở máy chủ.
08 Ứng dụng trong dạy học
Tin học — AI không phải phép màu
Học sinh dùng trợ lý AI mỗi ngày mà không biết bên trong có gì. Dự án này mở cái hộp ra: có nhận dạng lời nói, có mô hình ngôn ngữ, có một danh sách hàm mà lập trình viên khai báo, và có một đoạn mã do con người viết đứng ra quyết định.
Sau khi tự tay viết cổng xác nhận, học sinh hiểu rằng "AI tự làm" là cách nói không chính xác. Luôn có một người nào đó đã quyết định cho phép nó làm gì.
Công nghệ — hệ điều khiển có phản hồi và có chốt an toàn
Đây là dự án phức tạp nhất về mặt điều khiển trong cả trang: có đầu vào (giọng nói), có xử lí ở xa, có đầu ra công suất, và có nhiều lớp bảo vệ. Học sinh học được khái niệm hỏng an toàn: khi mất mạng, khi hết hạn, khi khởi động lại — mọi trường hợp bất thường đều phải đưa hệ về trạng thái tắt.
Vật lí — âm thanh từ sóng thành số
Micro lấy 16 nghìn mẫu mỗi giây. Đây là chỗ nối rất tự nhiên với bài lấy mẫu và định lý Nyquist: vì sao 16 kHz là đủ cho tiếng nói mà không đủ cho âm nhạc? Tiếng nói người tập trung dưới 8 kHz, nên 16 kHz vừa đủ gấp đôi.
Giáo dục công dân — quyền riêng tư và trách nhiệm
Ba câu hỏi tôi luôn đưa ra thảo luận, và không có đáp án sẵn:
- Thiết bị này nghe được mọi câu nói trong phòng. Ai được quyền đặt nó ở đâu, và có phải xin phép ai không?
- Nếu hệ nhận nhầm và bật một thiết bị gây hư hại, ai chịu trách nhiệm — người nói, người lập trình, hay công ty làm ra mô hình?
- Có nên lắp thiết bị này trong phòng ngủ, phòng y tế của trường không? Vì sao?
Hoạt động 1 — Xác định vấn đề. Trong phòng thực hành, tay học sinh thường bẩn hoặc đang bận cầm dụng cụ. Một trợ lý giọng nói sẽ tiện — nhưng phòng thực hành có bếp điện, có quạt hút, có thiết bị nguy hiểm. Làm sao để tiện mà vẫn an toàn?
Tiêu chí sản phẩm: điều khiển được ít nhất hai thiết bị 5 V; mọi lệnh đổi trạng thái đều phải xác nhận ở lượt kế tiếp; vượt qua cả năm kịch bản kiểm thử; trạng thái luôn hiển thị trên màn hình; và khi mất mạng hoặc khởi động lại thì mọi thiết bị đều về trạng thái tắt.
Hoạt động 2 — Kiến thức nền. I2S và lấy mẫu âm thanh; WebSocket; tool calling; máy trạng thái; chốt an toàn trong hệ điều khiển.
Hoạt động 3 — Đề xuất và bảo vệ phương án. Mỗi nhóm phải trả lời: lệnh nào được làm ngay, lệnh nào phải hỏi lại, và vì sao lại xếp loại như vậy. Đây là phần thảo luận đáng giá nhất — thường có tranh cãi thật.
Hoạt động 4 — Chế tạo, và mười lăm phút thử phá. Sau khi hệ chạy, các nhóm đổi thiết bị cho nhau và tìm cách phá hệ của nhóm bạn. Ghi lại mọi kẽ hở tìm được.
Hoạt động 5 — Báo cáo và vá lỗi. Mỗi nhóm trình bày kẽ hở bị người khác tìm ra và cách mình đã vá. Điểm cao dành cho nhóm vá được, không phải nhóm không bị tìm ra lỗi.
- Bài 120 — hỏi nhiệt độ và độ ẩm bằng giọng nói: thêm lệnh đọc, không cần xác nhận, để học sinh thấy rõ sự khác nhau giữa hai loại lệnh.
- Bài 122 — điều khiển nhà kính qua hội thoại: ghép với dự án tưới cây ở Bài 52.
- Bài 123 — trợ lý cảnh báo bất thường trong phòng lab.
- Thêm một nút bấm cứng làm "công tắc cho phép": AI chỉ được điều khiển khi nút đang giữ. Đây là mô hình an toàn dùng trong công nghiệp thật.
- Hướng thi Khoa học kĩ thuật: thiết bị hỗ trợ người khuyết tật vận động điều khiển đồ dùng trong nhà bằng giọng nói, với nhiều lớp xác nhận chống thao tác nhầm.