Đúng rồi. Bạn đang nhìn thấy một mẫu kiến trúc firmware rất phổ biến trong thiết bị điện tử: Fault Detection → Fault Code → Fault Manager → Display/UI.
Điểm quan trọng là: E0, E1, E2… không phải do màn hình tự “phát hiện lỗi”. Chính MCU/firmware phát hiện điều kiện bất thường, gán cho nó một mã lỗi, rồi yêu cầu mạch hiển thị đưa mã đó ra người dùng.
1. Cơ chế tổng quát
Hãy hình dung:
CẢM BIẾN / PHẦN CỨNG
│
▼
┌─────────────┐
│ MCU │
│ │
│ đọc sensor │
└──────┬──────┘
│
▼
┌─────────────┐
│Fault Detect │
│ │
│ Có bất thường?│
└──────┬──────┘
│
YES
│
▼
┌─────────────┐
│Fault Manager│
└──────┬──────┘
│
Fault Code
= E1
│
┌───────┴────────┐
▼ ▼
Display Buzzer
"E1" Beep...
Ví dụ bếp từ:
NTC
│
▼
ADC
│
▼
MCU
│
▼
Temperature > limit ?
│
YES
│
▼
FAULT_OVER_TEMP
│
▼
Error Code = E3
│
├────► LED/LCD hiển thị E3
│
└────► Tắt công suất
Một mã lỗi thường không chỉ để “báo cho người dùng”. Nó còn là kết quả của một quy trình xử lý an toàn.
2. Ví dụ cực kỳ đơn giản
Giả sử nhà sản xuất quy định:
E0 = không có nồi
E1 = cảm biến nhiệt lỗi
E2 = quá nhiệt
E3 = quá dòng
E4 = lỗi điện áp
Firmware có thể có:
typedef enum
{
FAULT_NONE = 0,
FAULT_NO_PAN,
FAULT_SENSOR,
FAULT_OVER_TEMP,
FAULT_OVER_CURRENT,
FAULT_OVER_VOLTAGE
} FaultCode_t;
Sau đó:
FaultCode_t fault = FAULT_NONE;
3. Firmware kiểm tra lỗi
Ví dụ:
void Safety_Check(void)
{
if (sensor.temperature > MAX_TEMP)
{
fault = FAULT_OVER_TEMP;
}
if (sensor.current > MAX_CURRENT)
{
fault = FAULT_OVER_CURRENT;
}
if (sensor.ntc_fault)
{
fault = FAULT_SENSOR;
}
}
Nhưng đến đây chưa phải toàn bộ cơ chế.
Ta còn cần một tầng:
Fault code nội bộ
↓
Fault Manager
↓
Xác định mức độ nghiêm trọng
↓
Shutdown / Recovery / Display
4. Đây mới là thứ rất giống sản phẩm thương mại
Ví dụ:
FAULT_OVER_TEMP
│
▼
Fault Manager
│
├── Disable Heater
│
├── Stop PWM
│
├── Start Cooling
│
├── Save fault
│
└── Display E2
Vậy nên khi bạn thấy:
E2
thì phía sau nó có thể đã xảy ra cả một chuỗi xử lý.
5. Tại sao nhà sản xuất lại dùng E0, E1, E2?
Bởi vì MCU không cần lưu một chuỗi dài như:
"OVER TEMPERATURE OF INDUCTION COIL"
Nó chỉ cần:
FAULT_OVER_TEMP
và tầng hiển thị chuyển nó thành:
E2
Điều này tiết kiệm bộ nhớ và làm firmware dễ quản lý hơn.
6. Một cách thiết kế tốt hơn
Ta có thể tách thành 3 tầng:
SENSOR
│
▼
┌──────────────┐
│ Fault Detect │
└──────┬───────┘
│
▼
FAULT INTERNAL
│
▼
┌──────────────┐
│Fault Manager │
└──────┬───────┘
│
▼
ERROR CODE
│
▼
┌──────────────┐
│ UI Manager │
└──────┬───────┘
│
┌────┴────┐
▼ ▼
Display Buzzer
Ví dụ:
FAULT_OVER_TEMP
↓
code 3
↓
"E2"
7. Nhưng một thiết bị có thể có hàng chục lỗi
Ví dụ điều hòa:
E1 → sensor lỗi
E2 → sensor lỗi
E3 → communication fault
E4 → over temperature
E5 → fan fault
E6 → inverter fault
...
Bếp từ:
E0 → no pan
E1 → sensor
E2 → over temperature
E3 → over current
E4 → voltage
...
Máy giặt:
E01 → door
E02 → water inlet
E03 → drain
E04 → motor
...
Lưu ý: ý nghĩa cụ thể của E0/E1/E2 không có chuẩn chung cho mọi hãng. E1 của một hãng có thể hoàn toàn khác E1 của hãng khác.
8. Điều thú vị là lỗi thường được chia thành nhiều loại
Không phải lỗi nào cũng:
FAULT → OFF ngay
Có thể chia:
Critical
Over current
Short circuit
Severe over temperature
→ tắt công suất ngay.
Serious
Sensor failure
Fan failure
→ dừng chức năng liên quan.
Warning
Filter dirty
Low water
→ vẫn có thể cho thiết bị hoạt động hạn chế.
Informational
No pan
Door open
→ chỉ thông báo.
9. Ví dụ bếp từ: “không có nồi”
Giả sử:
User → Power ON
MCU bắt đầu:
PAN_DETECTION
Nếu không phát hiện nồi:
PAN = FALSE
Firmware:
if (!pan_detected)
{
fault = FAULT_NO_PAN;
}
Sau đó:
FAULT_NO_PAN
↓
Display E0
↓
IGBT OFF
Nhưng đây có thể không được coi là một lỗi nghiêm trọng.
Nó có thể là một trạng thái:
WAIT_FOR_PAN
Đây là một điểm rất quan trọng:
Không phải tất cả những gì hiển thị bằng E đều có nghĩa là hệ thống đã hỏng.
10. Ví dụ cảm biến bị đứt
Giả sử NTC được nối vào ADC.
Bình thường:
ADC = 1800
Nhưng dây NTC bị đứt:
ADC = 4095
Firmware có thể:
if (adc_ntc > NTC_OPEN_THRESHOLD)
{
fault = FAULT_NTC_OPEN;
}
Sau đó:
NTC OPEN
↓
FAULT_NTC_OPEN
↓
E1
↓
HEATER OFF
11. Còn nếu NTC bị chập?
Ví dụ:
ADC ≈ 0
Firmware:
if (adc_ntc < NTC_SHORT_THRESHOLD)
{
fault = FAULT_NTC_SHORT;
}
→ một lỗi khác.
Vì vậy một cảm biến có thể tạo ra:
E1 = NTC open
E2 = NTC short
hoặc tùy thiết kế.
12. Lỗi còn có thể được phát hiện bằng “timeout”
Đây là cơ chế bạn sẽ gặp rất nhiều trong firmware.
Ví dụ hai MCU giao tiếp UART:
MCU A ───────── UART ───────► MCU B
A gửi:
PING
B phải trả lời:
ACK
Firmware:
PING
↓
Start timer
↓
Wait ACK
↓
ACK received?
Nếu:
100 ms
200 ms
500 ms
vẫn không có ACK:
COMMUNICATION_TIMEOUT
→ Error code.
Đây chính là lý do trong nhiều thiết bị bạn thấy lỗi kiểu:
Communication Error
13. Có một cơ chế còn quan trọng hơn: lỗi phải được “lọc”
Không phải cứ:
Temperature > limit
một lần là lập tức báo lỗi.
Sensor có thể nhiễu.
Ví dụ:
99.8
100.1
99.9
100.2
99.7
Nếu ngưỡng là 100°C mà báo lỗi ngay thì firmware sẽ báo lỗi liên tục.
Thay vào đó:
Temperature > 100°C
↓
Start fault timer
↓
Vẫn > 100°C?
↓
200 ms?
↓
500 ms?
↓
YES
↓
FAULT
Đây gọi là debounce / filtering / persistence time tùy ngữ cảnh.
14. Có thể dùng counter
Ví dụ:
if (temperature > MAX_TEMP)
{
over_temp_counter++;
if (over_temp_counter >= 10)
{
fault = FAULT_OVER_TEMP;
}
}
else
{
over_temp_counter = 0;
}
Nếu task chạy mỗi 100 ms:
10 lần × 100 ms = 1 giây
→ nhiệt độ phải vượt ngưỡng liên tục 1 giây mới báo lỗi.
15. Nhưng lỗi quá dòng có thể khác hoàn toàn
Đối với một sự kiện nguy hiểm:
SHORT CIRCUIT
không thể chờ:
1 giây
mà phải:
Current abnormal
↓
Hardware protection
↓
Gate OFF
sau đó MCU mới xử lý:
Fault interrupt
↓
FAULT_OVERCURRENT
↓
Display E3
Đây chính là sự khác biệt giữa:
Fast protection
và
Software diagnosis.
16. Một kiến trúc Fault Manager hoàn chỉnh
Tôi sẽ thiết kế như sau:
SENSOR
│
┌──────────────┼───────────────┐
▼ ▼ ▼
Temperature Current Voltage
│ │ │
└──────────────┼───────────────┘
▼
FAULT DETECTOR
│
┌───────┴────────┐
▼ ▼
Warning Critical
│ │
▼ ▼
Fault Manager Emergency Stop
│ │
└───────┬────────┘
▼
Fault Database
│
┌───────┼─────────┐
▼ ▼ ▼
Code Action UI
│ │ │
▼ ▼ ▼
E2 Heater OFF Display
17. Fault Database rất thú vị
Thay vì viết hàng trăm if, ta có thể định nghĩa:
typedef struct
{
FaultCode_t code;
uint8_t critical;
uint8_t shutdown;
uint16_t display_code;
} FaultInfo_t;
Ví dụ:
const FaultInfo_t fault_table[] =
{
{FAULT_NO_PAN, 0, 0, 0},
{FAULT_NTC_OPEN, 1, 1, 1},
{FAULT_OVER_TEMP, 1, 1, 2},
{FAULT_OVER_CURRENT, 1, 1, 3},
{FAULT_OVER_VOLTAGE, 1, 1, 4}
};
Sau đó:
FAULT_OVER_TEMP
↓
lookup table
↓
critical = YES
shutdown = YES
display = E2
Đây là cách thiết kế rất dễ mở rộng.
18. Và mã lỗi có thể được lưu vào Flash
Đây là một tính năng rất hay trong sản phẩm thực tế.
Ví dụ:
Thiết bị
↓
Over-current
↓
E3
MCU có thể lưu:
Last Fault = E3
Fault Counter = 17
vào Flash/EEPROM.
Sau khi người dùng tắt nguồn:
POWER OFF
rồi bật lại:
POWER ON
firmware vẫn có thể biết:
Previous fault = E3
Kỹ thuật viên bảo hành có thể đọc lịch sử lỗi.
19. Vì vậy một mã E3 bạn nhìn thấy trên màn hình thực ra chỉ là “đỉnh của tảng băng”
Ví dụ bạn thấy:
E3
Nhưng bên trong:
Current sensor
↓
ADC
↓
Filtering
↓
Current calculation
↓
Threshold comparison
↓
Fault persistence
↓
Fault Manager
↓
FAULT_OVER_CURRENT
↓
Emergency shutdown
↓
Save fault
↓
Display Manager
↓
SEGMENT "E"
↓
SEGMENT "3"
↓
LED/LCD
Đó mới là toàn bộ cơ chế.
20. Và điều này liên hệ trực tiếp với STM32 mà bạn đang học
Bạn đã học:
GPIO
Timer
ADC
UART
Interrupt
DMA
Bây giờ chúng ta có thể ghép chúng lại:
STM32
│
┌────────────┼────────────┐
│ │ │
ADC GPIO UART
│ │ │
Sensor Key Debug
│
▼
Fault Detection
│
▼
Fault Manager
│
▼
State Machine
│
├────────► Control
│
└────────► Display
Và đây chính là lúc các kiến thức rời rạc bắt đầu trở thành một firmware hoàn chỉnh.
Một điểm cuối cùng rất đáng nhớ
Khi học firmware, bạn có thể coi toàn bộ hệ thống lỗi theo công thức:
Detect → Validate → Classify → Protect → Record → Display → Recover
Ví dụ bếp từ:
Dòng điện quá lớn
↓
DETECT
↓
VALIDATE
↓
CLASSIFY = OVER CURRENT
↓
PROTECT = IGBT OFF
↓
RECORD = Fault E3
↓
DISPLAY = E3
↓
RECOVER hoặc LOCK
Và đây là một pattern có thể áp dụng cho bếp từ, nồi cơm, điều hòa, máy giặt, lò vi sóng, máy làm sữa hạt, quạt điều hòa… gần như mọi thiết bị nhúng có chẩn đoán lỗi.
Nếu tiếp tục dự án STM32F103 của chúng ta, bước rất đáng làm tiếp theo là tự xây một “Fault Manager” hoàn chỉnh: tạo khoảng 10 lỗi giả lập (E0–E9), dùng ADC để giả lập sensor, Timer để tạo timeout, Interrupt để giả lập lỗi khẩn cấp, rồi để STM32 tự phát hiện → khóa công suất → hiển thị E-code → ghi lịch sử lỗi → tự recovery. Đây sẽ là một bài tập cực kỳ sát với firmware sản phẩm thực tế.

