Tư duy hệ thống qua lập trình: thành phần, quan hệ, vòng phản hồi và mô phỏng bể nước trên ESP32
Rèn tư duy hệ thống cho trẻ qua lập trình: thành phần, quan hệ, vòng phản hồi cân bằng và tăng cường, kèm mô phỏng bể nước có bơm tự động trên ESP32 không cần linh kiện.
Nhiều vấn đề trong đời sống không có một nguyên nhân duy nhất: giao thông ùn tắc, cây trong chậu héo, một bạn trong lớp ngày càng ngại học. Muốn hiểu chúng, cần nhìn thấy các thành phần, quan hệ giữa chúng và cách chúng thay đổi theo thời gian. Cách nhìn đó gọi là tư duy hệ thống.
Lập trình là nơi trẻ được "làm ra" hệ thống và thấy nó chạy. Bài viết giải thích tư duy hệ thống bằng ví dụ bể nước có bơm tự động, phân biệt hai loại vòng phản hồi, rồi hướng dẫn một mô phỏng chạy trên mạch ESP32 không cần linh kiện, để trẻ tự thí nghiệm và rút ra quy luật.
1. Tư duy hệ thống là gì
Theo Donella Meadows (Thinking in Systems, 2008), một hệ thống gồm các thành phần, các mối quan hệ nối chúng với nhau và một mục đích hay chức năng. Tư duy hệ thống khác cách nghĩ theo đường thẳng "A gây ra B": nó tìm các vòng mà B quay lại tác động lên A.

Bể nước có bơm tự động là một hệ thống nhỏ: bể chứa nước (trạng thái của hệ thống), bơm cho nước vào, vòi cho nước ra, cảm biến đo mực nước và chương trình ra quyết định. Mục đích: luôn có nước dùng nhưng không tràn.
2. Hai loại vòng phản hồi

- Vòng cân bằng giữ hệ thống quanh một mức: nước càng thấp thì bơm càng được bật, bơm chạy làm nước dâng lên. Tủ lạnh, máy điều hòa, cơ thể đổ mồ hôi khi nóng đều có vòng cân bằng.
- Vòng tăng cường làm thay đổi lớn dần: càng luyện càng giỏi, càng giỏi càng thích, càng thích càng luyện. Theo chiều ngược lại cũng vậy, và đó là lý do một khó khăn nhỏ lúc đầu có thể lớn dần nếu không ai can thiệp.
Với trẻ, chỉ cần tập trả lời hai câu: "cái này tăng thì cái kia tăng hay giảm?" và "vòng này giữ cho ổn định hay làm thay đổi lớn dần?".
3. Lập trình rèn tư duy hệ thống thế nào
- Chương trình là một hệ thống. Các biến là trạng thái, các hàm là thành phần, lời gọi hàm là quan hệ. Trẻ phải biết phần nào ảnh hưởng tới phần nào.
- Robot và mạch điện chạy theo vòng đọc, quyết định, hành động. Hàm
loop()của Arduino lặp mãi: đọc cảm biến, so sánh, điều khiển, rồi đọc lại. Đó chính là vòng phản hồi. - Thay một con số, cả hệ thống đổi cách chạy. Mô phỏng cho phép thử điều không làm được ngoài đời (bơm 1 giờ trong 6 phút, cho bể tràn mà không ướt sàn) và thấy hành vi theo thời gian trên đồ thị.
Về bằng chứng: nghiên cứu về tư duy hệ thống ở học sinh đã có từ lâu. Ví dụ, Orit Ben-Zvi Assaraf và Nir Orion (2005) mô tả các khả năng tư duy hệ thống của học sinh trung học cơ sở khi học về hệ Trái Đất. Cindy Hmelo-Silver và Merav Pfeffer (2004) thấy người mới học nhìn bể cá chủ yếu thấy các bộ phận, còn chuyên gia thấy hoạt động và chức năng của cả hệ thống. Tuy vậy, nghiên cứu đo trực tiếp tác động của việc học lập trình lên tư duy hệ thống ở trẻ nhỏ còn ít. Các hoạt động trong bài này là cách luyện các khả năng nói trên, chưa phải kết quả đã được đo đếm.
4. Thực hành: mô phỏng bể nước trên ESP32
Mô phỏng chạy trên một mạch ESP32 DevKit V1 cắm cổng USB máy tính, không cần thêm linh kiện:
- Mực nước, bơm, vòi là các con số trong chương trình; mỗi 100 ms thật ứng với 1 giây mô phỏng.
- Giữ nút BOOT trên mạch là mở thêm vòi (nước ra 4 lít/giây thay vì 2 lít/giây).
- Đèn trên mạch (GPIO2) sáng khi bơm đang chạy.
- Mở Tools → Serial Plotter ở 115200 baud để xem bốn đường: mực nước, bơm, ngưỡng bật, ngưỡng tắt.
| Lệnh | Tác dụng |
|---|---|
1 | Chế độ phản hồi: bơm bật, tắt theo mực nước |
2 | Chế độ hẹn giờ: bơm chạy 20 giây, nghỉ 20 giây, không nhìn mực nước |
b40, t80 | Đặt ngưỡng bật bơm, ngưỡng tắt bơm (lít) |
r | Làm lại từ đầu (bể 50 lít) |
p | Đổi giữa kiểu Serial Plotter và kiểu chữ có thống kê mỗi phút |
/*
PYWORLD.VN - Mô phỏng bể nước có bơm tự động trên ESP32: học tư duy hệ thống và vòng phản hồi
Không cần linh kiện: giữ nút BOOT (GPIO0) là mở thêm vòi xả, đèn trên mạch (GPIO2) sáng khi bơm chạy
Mở Tools > Serial Plotter ở 115200 baud; lệnh: 1 phản hồi, 2 hẹn giờ, b<số> ngưỡng bật, t<số> ngưỡng tắt, r, p
*/
#define CHAN_NUT_XA 0 // nút BOOT trên mạch ESP32 DevKit V1
#define CHAN_DEN_BOM 2 // đèn xanh trên mạch
#define CHU_KY_MS 100 // mỗi 100 ms thật là 1 giây trong mô phỏng
#define DUNG_TICH 100.0 // lít
#define LUU_LUONG_BOM 5.0 // lít mỗi giây khi bơm chạy
#define XA_THUONG 2.0 // lít mỗi giây: nước dùng hằng ngày
#define XA_MANH 4.0 // lít mỗi giây khi giữ nút BOOT (mở thêm vòi)
#define HEN_GIO_BAT 20 // chế độ hẹn giờ: bơm chạy 20 giây
#define HEN_GIO_TAT 20 // rồi nghỉ 20 giây, không nhìn mực nước
float mucNuoc = 50.0; // trạng thái của hệ thống: lượng nước trong bể
int nguongBat = 40; // dưới mức này thì bật bơm
int nguongTat = 80; // trên mức này thì tắt bơm
int cheDo = 1; // 1: phản hồi theo mực nước, 2: hẹn giờ
bool bomChay = false;
bool kieuPlotter = true;
unsigned long giay = 0; // thời gian mô phỏng
unsigned long lanTruoc = 0;
int soLanBat = 0, giayTran = 0, giayCan = 0; // thống kê trong mỗi phút mô phỏng
void inCheDo() {
Serial.printf("Che do: %s | nguong bat %d | nguong tat %d\n", cheDo == 1 ? "phan hoi" : "hen gio", nguongBat, nguongTat);
}
void xuLyLenh() {
if (!Serial.available()) {
return;
}
String lenh = Serial.readStringUntil('\n');
lenh.trim();
if (lenh.length() == 0) {
return;
}
char ma = lenh.charAt(0);
int so = lenh.substring(1).toInt();
if (ma == '1' || ma == '2') {
cheDo = ma - '0';
} else if (ma == 'b' && so >= 0 && so < nguongTat) {
nguongBat = so;
} else if (ma == 't' && so > nguongBat && so <= DUNG_TICH) {
nguongTat = so;
} else if (ma == 'r') { // làm lại từ đầu
mucNuoc = 50.0;
giay = 0;
bomChay = false;
soLanBat = giayTran = giayCan = 0;
} else if (ma == 'p') {
kieuPlotter = !kieuPlotter;
} else {
Serial.println("Lenh: 1 phan hoi | 2 hen gio | b<so> nguong bat | t<so> nguong tat | r lam lai | p doi kieu in");
}
inCheDo();
}
// Một bước mô phỏng ứng với 1 giây: quyết định bật/tắt bơm rồi tính lượng nước vào, ra
void buocMoPhong() {
float xa = (digitalRead(CHAN_NUT_XA) == LOW) ? XA_MANH : XA_THUONG;
bool truoc = bomChay;
if (cheDo == 1) {
// Vòng phản hồi: chương trình nhìn mực nước rồi mới quyết định
if (!bomChay && mucNuoc < nguongBat) {
bomChay = true;
} else if (bomChay && mucNuoc > nguongTat) {
bomChay = false;
}
} else {
// Không có phản hồi: bơm chạy theo đồng hồ, mặc kệ bể đầy hay cạn
bomChay = (giay % (HEN_GIO_BAT + HEN_GIO_TAT)) < HEN_GIO_BAT;
}
if (bomChay && !truoc) {
soLanBat++;
}
mucNuoc += (bomChay ? LUU_LUONG_BOM : 0.0) - xa;
if (mucNuoc >= DUNG_TICH) {
mucNuoc = DUNG_TICH; // nước tràn ra ngoài
giayTran++;
} else if (mucNuoc <= 0) {
mucNuoc = 0; // bể cạn, vòi không có nước
giayCan++;
}
digitalWrite(CHAN_DEN_BOM, bomChay ? HIGH : LOW);
giay++;
}
void setup() {
Serial.begin(115200);
pinMode(CHAN_NUT_XA, INPUT_PULLUP);
pinMode(CHAN_DEN_BOM, OUTPUT);
delay(200);
Serial.println();
Serial.println("Mo phong be nuoc 100 lit: bom 5 lit/giay, xa 2 lit/giay (giu BOOT: 4 lit/giay)");
inCheDo();
lanTruoc = millis();
}
void loop() {
xuLyLenh();
if (millis() - lanTruoc < CHU_KY_MS) {
return;
}
lanTruoc += CHU_KY_MS;
buocMoPhong();
if (kieuPlotter) {
// Bốn đường trên Serial Plotter; bơm vẽ ở mức 10 khi chạy cho dễ nhìn
Serial.printf("muc_nuoc:%.1f,bom:%d,nguong_bat:%d,nguong_tat:%d\n", mucNuoc, bomChay ? 10 : 0, nguongBat, nguongTat);
} else if (giay % 5 == 0) {
Serial.printf("Giay %4lu | muc nuoc %5.1f lit | bom %s\n", giay, mucNuoc, bomChay ? "CHAY" : "nghi");
}
if (giay % 60 == 0) {
if (!kieuPlotter) {
Serial.printf("Phut %lu: bom khoi dong %d lan, tran %d giay, can %d giay\n", giay / 60, soLanBat, giayTran, giayCan);
}
soLanBat = giayTran = giayCan = 0;
}
}
Nạp chương trình bằng Arduino IDE, chọn bo mạch DOIT ESP32 DEVKIT V1 và đúng cổng COM. Hàm buocMoPhong() là toàn bộ "luật" của hệ thống: quyết định bật hay tắt bơm, rồi cộng nước vào, trừ nước ra.
5. Năm thí nghiệm cho trẻ

- Quan sát vòng cân bằng. Để chế độ 1 chạy 2 phút. Mực nước lên xuống đều trong khoảng 40–80 lít. Hỏi trẻ: vì sao nước không bao giờ tràn, không bao giờ cạn?
- Nhiễu từ bên ngoài. Giữ nút BOOT khoảng 1 phút. Nước ra nhiều hơn nên bơm phải chạy lâu hơn mới đầy, nhưng hệ thống vẫn tự giữ được mực nước. Đây là sức mạnh của phản hồi.
- Bỏ phản hồi. Gõ
2. Bơm chạy theo đồng hồ, mặc kệ bể đầy hay vơi: sau vài chu kỳ bể tràn. Hỏi: bơm hẹn giờ thiếu điều gì so với chế độ 1? - Hai ngưỡng quá gần nhau. Gõ
1, rồib49vàt51. Mực nước rất ổn định nhưng bơm bật tắt khoảng 12 lần mỗi phút thay vì 2 lần. Bơm thật bật tắt liên tục sẽ mau hỏng, vì vậy bộ điều khiển thật luôn để hai ngưỡng cách nhau. - Giới hạn của hệ thống (học sinh lớn). Sửa
XA_MANHthành 6 rồi nạp lại. Giữ nút BOOT: nước ra nhiều hơn bơm đưa vào, mực nước tụt dần dù bơm chạy liên tục. Phản hồi tốt cũng không vượt được giới hạn của thiết bị.

p.Sau mỗi thí nghiệm, cho trẻ dự đoán trước rồi mới chạy, và vẽ lại đồ thị mực nước bằng tay. Dự đoán sai là cơ hội tốt nhất để hỏi "điều gì trong hệ thống làm nó chạy khác ý mình?".
6. Các khả năng tư duy hệ thống và cách luyện

Từ bể nước, mở rộng sang các hệ thống quen thuộc. Cho trẻ vẽ sơ đồ: thành phần nào, mũi tên nào, có vòng cân bằng hay tăng cường không?
| Hệ thống | Thành phần | Vòng phản hồi |
|---|---|---|
| Tủ lạnh | ngăn lạnh, cảm biến nhiệt, máy nén | Cân bằng: nóng lên thì máy nén chạy, đủ lạnh thì nghỉ |
| Cơ thể khi trời nóng | da, mồ hôi, não | Cân bằng: nóng thì đổ mồ hôi, mồ hôi bay hơi làm mát |
| Chậu cây | đất, nước, lá, ánh nắng | Đất khô thì người tưới; quên tưới thì lá héo |
| Một kênh video được nhiều người xem | video, người xem, đề xuất | Tăng cường: nhiều người xem thì được đề xuất nhiều hơn |
| Thói quen học | thời gian luyện, kết quả, hứng thú | Tăng cường: theo chiều tốt hoặc chiều xấu |
7. Xử lý lỗi thường gặp
| Hiện tượng | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
| Serial Plotter không có đường nào | Sai tốc độ baud; đang ở kiểu chữ | Chọn 115200 baud; gõ p để về kiểu Plotter |
| Gõ lệnh không có tác dụng | Serial Monitor chưa chọn "New Line" | Chọn kiểu xuống dòng "New Line" rồi gõ lại |
| Đèn trên mạch không sáng khi bơm chạy | Mạch dùng chân khác cho đèn | Đổi CHAN_DEN_BOM theo chân đèn in trên mạch |
Lệnh b hoặc t bị bỏ qua | Ngưỡng bật phải nhỏ hơn ngưỡng tắt | Đặt ngưỡng tắt trước khi hạ ngưỡng bật, hoặc ngược lại |
8. Nguồn tham khảo
- Meadows, D. H. (2008). Thinking in Systems: A Primer. Chelsea Green Publishing.
- Ben-Zvi Assaraf, O., & Orion, N. (2005). Development of system thinking skills in the context of earth system education. Journal of Research in Science Teaching, 42(5), 518–560.
- Hmelo-Silver, C. E., & Pfeffer, M. G. (2004). Comparing expert and novice understanding of a complex system from the perspective of structures, behaviors, and functions. Cognitive Science, 28(1), 127–138.
9. Xem thêm
- Bài đầu tiên với ESP32 cho học sinh: đèn nhấp nháy và mã Morse
- Chọn bo mạch lập trình đầu tiên cho học sinh
- Giữ tốc độ động cơ DC ổn định bằng PID trên ESP32 (vòng phản hồi trong điều khiển động cơ)
- Quy trình thiết kế kỹ thuật trong dự án STEM
Cùng nhóm bài: học lập trình từ nhỏ và bằng chứng nghiên cứu; lập trình rèn cách giải quyết vấn đề có hệ thống; lập trình nuôi dưỡng tư duy sáng tạo; điều kiện để học lập trình phát triển tư duy toàn diện.
Xem thêm