Điều khiển nhiệt độ lò nung bằng PID và SSR trên ESP32: cặp nhiệt K, biểu đồ nhiệt, ngắt an toàn
Bộ điều khiển nhiệt độ lò nung bằng PID và SSR trên ESP32: cặp nhiệt K qua MAX6675, SSR theo tỉ lệ thời gian, biểu đồ nhiệt nhiều đoạn có chờ lò bắt kịp, khởi động từ và các lớp ngắt an toàn.
Lò nung gốm, lò ủ nhiệt nhỏ, lò sấy dùng dây đốt điện đều cần giữ nhiệt độ theo một biểu đồ: tăng chậm ở đầu để vật nung thoát ẩm, tăng nhanh hơn ở giữa, giữ ở nhiệt độ đích một thời gian rồi tắt để nguội. Bài này hướng dẫn làm bộ điều khiển nhiệt độ lò nung bằng PID và SSR trên ESP32: đo bằng cặp nhiệt loại K qua MAX6675, SSR đóng cắt dây đốt theo tỉ lệ thời gian, chạy biểu đồ nhiều đoạn, tự dừng tăng khi lò không theo kịp, và các lớp ngắt an toàn cần có khi làm việc với điện 220 V và nhiệt độ cao.
Nguyên lý PID, cách đọc cặp nhiệt và cách chọn giữa relay với SSR có ở các bài cùng cụm (chỉnh PID, MAX6675 với ESP32, relay hay SSR). Bài này ghép chúng thành một bộ điều khiển lò hoàn chỉnh.
1. Phần điều khiển: ESP32, MAX6675, SSR, relay

- MAX6675 đọc cặp nhiệt loại K từ 0 tới 1023,75 °C, bước 0,25 °C, có bù mối lạnh sẵn. Mỗi lần chuyển đổi mất khoảng 220 ms nên chương trình đọc 4 lần mỗi giây; đọc nhanh hơn sẽ làm chip bắt đầu lại phép đo. Lò cần trên 1000 °C thì dùng MAX31855 và cặp nhiệt chịu được nhiệt độ đó.
- SSR loại DA (vào DC, ra AC; ghi 3–32 V DC ở phía điều khiển) nhận thẳng 3,3 V từ GPIO26. Xem dòng ngõ vào trong datasheet của SSR: cần quá khoảng 15 mA thì kích qua transistor. Điện trở 10 kΩ từ GPIO26 xuống GND giữ SSR tắt trong lúc ESP32 khởi động hoặc bị reset.
- Module relay 1 kênh ở GPIO27 đóng cuộn hút của khởi động từ, tạo đường cắt thứ hai. Module của bài module relay kích mức thấp nhận mức thấp là đóng; chương trình có hằng
RELAY_KICH_MUC_THAPđể đổi.
2. Phần động lực 220 V

- SSR hỏng thường ở trạng thái chập: dây đốt có điện liên tục, ESP32 tắt chân điều khiển cũng không ngắt được. Vì vậy lò cần thêm khởi động từ nối tiếp, cuộn hút của nó đi qua relay của ESP32 và qua một bộ bảo vệ quá nhiệt riêng (bộ điều nhiệt cơ hoặc cầu chì nhiệt) không phụ thuộc chương trình.
- Chọn SSR có dòng định mức gấp 1,5–2 lần dòng dây đốt (dây đốt 2 kW ở 220 V khoảng 9 A) và bắt lên tản nhiệt có keo tản nhiệt: điện áp rơi trên SSR khoảng 1,2–1,6 V nên mỗi ampe tỏa khoảng 1,2–1,6 W. So sánh kỹ hơn relay và SSR có ở bài cùng cụm về relay hay SSR.
- Dây dẫn, aptomat, cầu chì chọn theo dòng của dây đốt; vỏ lò và tản nhiệt nối đất. Cách chọn công tắc, cầu chì có ở bài công tắc, nút nhấn, cầu chì và relay. Phần 220 V nên nhờ thợ điện kiểm tra trước khi cấp điện.
3. SSR theo tỉ lệ thời gian
PID tính ra công suất từ 0 tới 100 %. Lò có quán tính nhiệt rất lớn nên không cần điều chỉnh pha từng chu kỳ lưới; cách đơn giản là tỉ lệ thời gian: chia thời gian thành các cửa sổ 4 giây, công suất 30 % thì bật SSR 1,2 giây đầu cửa sổ rồi tắt. Với SSR có mạch đóng ở điểm 0 (zero-cross), 4 giây là 400 nửa chu kỳ lưới 50 Hz, mỗi 1 % công suất ứng với 4 nửa chu kỳ, đủ mịn cho lò. Cửa sổ ngắn hơn làm SSR đóng cắt nhiều hơn mà không giúp nhiệt độ đều hơn.
4. Chương trình ESP32
/*
PYWORLD.VN - Bộ điều khiển lò nung điện bằng ESP32: cặp nhiệt K qua MAX6675, SSR đóng cắt dây đốt theo tỉ lệ thời gian, PID có chống bão hòa
PYWORLD.VN - Chạy biểu đồ nhiệt nhiều đoạn (tốc độ °C/giờ, nhiệt đích, phút giữ); lò chậm hơn điểm đặt quá 15 °C thì dừng tăng chờ lò bắt kịp
PYWORLD.VN - Lệnh (115200, New Line): r = chạy, x = dừng, s 300 = giữ 300 °C, p 5, i 0.004, d 0; tắt SSR và khởi động từ khi hở cặp nhiệt, quá nhiệt
*/
#include <SPI.h>
#include <esp_task_wdt.h>
const int CHAN_CS = 5; // MAX6675: SCK GPIO18, SO GPIO19, CS GPIO5
const int CHAN_SSR = 26; // ngõ vào DC (+) của SSR, điện trở 10 kΩ kéo xuống GND
const int CHAN_KHOI_DONG_TU = 27; // module relay đóng cuộn hút khởi động từ cấp điện cho SSR
const bool RELAY_KICH_MUC_THAP = true;
const uint32_t CUA_SO_MS = 4000; // SSR: mỗi 4 giây bật một phần theo % công suất
const float DT = 1.0f; // chu kỳ PID và biểu đồ, giây
const float NHIET_TOI_DA = 1000.0f; // MAX6675 đo tới 1023,75 °C
const float CHO_BAT_KIP = 15.0f; // lò chậm hơn điểm đặt quá mức này thì dừng tăng
const float SAI_SO_GIU = 5.0f; // bắt đầu tính giờ giữ khi nhiệt cách đích dưới 5 °C
const uint32_t KIEM_TRA_TANG_S = 600; // công suất 100 % suốt 10 phút mà nhiệt tăng dưới 5 °C là có lỗi
const float TANG_TOI_THIEU = 5.0f;
struct Doan {
float tocDo; // °C/giờ
float nhietDich; // °C
float giuPhut; // phút
};
const Doan BIEU_DO[] = {{100, 200, 30}, {200, 600, 0}, {150, 900, 15}}; // ví dụ, sửa theo vật nung
const int SO_DOAN = sizeof(BIEU_DO) / sizeof(BIEU_DO[0]);
enum CheDo { DUNG, CHAY, GIU_TAY, LOI };
CheDo cheDo = DUNG;
float Kp = 5.0f, Ki = 0.004f, Kd = 0.0f; // % trên °C; % trên °C·s; % trên °C/s
float nhiet = NAN, diemDat = 0, tichPhan = 0, nhietTruoc = 0, congSuat = 0;
int doan = 0, soLanDocLoi = 0;
bool dangGiu = false, dangCho = false;
float giayGiu = 0;
uint32_t giayDayCongSuat = 0;
float nhietLucDay = 0;
SPISettings caiDatSpi(1000000, MSBFIRST, SPI_MODE0);
// MAX6675: khung 16 bit, bit 14..3 là nhiệt độ (0,25 °C), bit 2 = 1 khi hở cặp nhiệt, bit 15 và bit 1 luôn 0
bool docMax6675(float &t) {
SPI.beginTransaction(caiDatSpi);
digitalWrite(CHAN_CS, LOW);
delayMicroseconds(1);
uint16_t v = SPI.transfer16(0);
digitalWrite(CHAN_CS, HIGH);
SPI.endTransaction();
if (v & 0x8002) return false; // không có module hoặc sai dây
if (v & 0x0004) return false; // hở cặp nhiệt
t = (v >> 3) * 0.25f;
return true;
}
void datKhoiDongTu(bool bat) {
digitalWrite(CHAN_KHOI_DONG_TU, bat == RELAY_KICH_MUC_THAP ? LOW : HIGH);
}
void tatLo(const char *lyDo) {
cheDo = LOI;
congSuat = 0;
digitalWrite(CHAN_SSR, LOW);
datKhoiDongTu(false);
Serial.printf("# LOI: %s. Da tat SSR va khoi dong tu; go x de xoa loi\n", lyDo);
}
void batDau(CheDo moi, float dat) {
cheDo = moi;
diemDat = dat;
nhietTruoc = nhiet;
tichPhan = 0;
doan = 0;
dangGiu = dangCho = false;
giayGiu = 0;
giayDayCongSuat = 0;
datKhoiDongTu(true);
}
// Tiến điểm đặt theo biểu đồ, gọi mỗi DT giây
void buocBieuDo() {
const Doan &d = BIEU_DO[doan];
if (!dangGiu) {
float buoc = d.tocDo / 3600.0f * DT;
bool cho = false;
if (diemDat < d.nhietDich) {
if (diemDat - nhiet <= CHO_BAT_KIP) diemDat = fminf(d.nhietDich, diemDat + buoc);
else cho = true;
} else if (diemDat > d.nhietDich) { // đoạn hạ nhiệt có kiểm soát
if (nhiet - diemDat <= CHO_BAT_KIP) diemDat = fmaxf(d.nhietDich, diemDat - buoc);
else cho = true;
}
if (cho != dangCho) {
dangCho = cho;
Serial.println(cho ? "# Lo cham hon diem dat, dung tang cho lo bat kip" : "# Lo da bat kip, tang tiep");
}
if (diemDat == d.nhietDich && fabsf(nhiet - d.nhietDich) < SAI_SO_GIU) {
dangGiu = true;
giayGiu = 0;
Serial.printf("# Doan %d/%d: giu %.0f C trong %.0f phut\n", doan + 1, SO_DOAN, d.nhietDich, d.giuPhut);
}
} else {
giayGiu += DT;
if (giayGiu >= d.giuPhut * 60) {
dangGiu = false;
if (++doan >= SO_DOAN) {
cheDo = DUNG;
congSuat = 0;
datKhoiDongTu(false);
Serial.println("# Xong bieu do, tat lo de nguoi tu nhien");
} else {
Serial.printf("# Doan %d/%d: %.0f C/gio toi %.0f C\n", doan + 1, SO_DOAN, BIEU_DO[doan].tocDo, BIEU_DO[doan].nhietDich);
}
}
}
}
float buocPID() {
float e = diemDat - nhiet;
float u = Kp * e + Ki * tichPhan - Kd * (nhiet - nhietTruoc) / DT; // D theo giá trị đo: không giật khi điểm đặt đổi
nhietTruoc = nhiet;
bool baoHoa = (u >= 100 && e > 0) || (u <= 0 && e < 0);
if (!baoHoa) tichPhan += e * DT; // chống bão hòa tích phân
return constrain(u, 0.0f, 100.0f);
}
// Công suất 100 % lâu mà nhiệt không tăng: dây đốt đứt, SSR hỏng hở, cặp nhiệt nằm ngoài lò
void kiemTraNhietTang() {
if (congSuat < 99.9f) {
giayDayCongSuat = 0;
return;
}
if (giayDayCongSuat == 0) nhietLucDay = nhiet;
if (++giayDayCongSuat >= KIEM_TRA_TANG_S) {
if (nhiet - nhietLucDay < TANG_TOI_THIEU) tatLo("cong suat 100% ma nhiet khong tang, kiem tra day dot, SSR, cap nhiet");
giayDayCongSuat = 0;
}
}
void xuLyLenh() {
String s = Serial.readStringUntil('\n');
s.trim();
if (s.length() < 1) return;
char c = tolower(s.charAt(0));
float so = s.substring(1).toFloat();
if (c == 'x') {
cheDo = DUNG;
congSuat = 0;
datKhoiDongTu(false);
} else if (cheDo == LOI) {
Serial.println("# Dang loi: go x truoc");
return;
} else if (c == 'r' && !isnan(nhiet)) {
batDau(CHAY, nhiet); // tăng từ nhiệt độ hiện tại
} else if (c == 's' && !isnan(nhiet)) {
batDau(GIU_TAY, constrain(so, 0.0f, NHIET_TOI_DA - 20));
} else if (c == 'p') {
Kp = so;
} else if (c == 'i' && so > 0) {
tichPhan = tichPhan * Ki / so; // giữ nguyên phần tích phân của công suất khi đổi Ki
Ki = so;
} else if (c == 'd') {
Kd = so;
}
Serial.printf("# che do %d, dat %.1f C, Kp %.3f, Ki %.4f, Kd %.1f\n", (int)cheDo, diemDat, Kp, Ki, Kd);
}
void setup() {
Serial.begin(115200);
pinMode(CHAN_SSR, INPUT); // gói esp32 3.x: ghi sẵn mức tắt rồi mới chuyển sang ngõ ra
digitalWrite(CHAN_SSR, LOW);
pinMode(CHAN_SSR, OUTPUT);
pinMode(CHAN_KHOI_DONG_TU, INPUT);
datKhoiDongTu(false);
pinMode(CHAN_KHOI_DONG_TU, OUTPUT);
pinMode(CHAN_CS, OUTPUT);
digitalWrite(CHAN_CS, HIGH);
SPI.begin(); // ESP32: SCK 18, MISO 19 (MAX6675 không dùng MOSI)
esp_task_wdt_add(NULL); // vòng loop treo quá 5 giây thì ESP32 tự khởi động lại, SSR về tắt
Serial.println("# Lo nung PID: r = chay bieu do, s 300 = giu 300 C, x = dung");
}
void loop() {
static uint32_t lucDoc = 0, lucDK = 0, dauCuaSo = 0;
uint32_t bayGio = millis();
esp_task_wdt_reset();
if (Serial.available()) xuLyLenh();
if (bayGio - lucDoc >= 250) { // MAX6675 cần khoảng 220 ms cho mỗi lần chuyển đổi
lucDoc = bayGio;
float t;
if (docMax6675(t)) {
nhiet = t;
soLanDocLoi = 0;
} else if (++soLanDocLoi >= 3 && cheDo != LOI) {
tatLo("ho cap nhiet hoac mat ket noi MAX6675");
}
if (!isnan(nhiet) && nhiet > NHIET_TOI_DA && cheDo != LOI) tatLo("qua nhiet do toi da");
}
if (bayGio - lucDK >= (uint32_t)(DT * 1000)) {
lucDK = bayGio;
if (cheDo == CHAY) buocBieuDo();
if (cheDo == CHAY || cheDo == GIU_TAY) {
congSuat = buocPID();
kiemTraNhietTang();
} else {
congSuat = 0;
}
Serial.printf("dat:%.1f,nhiet:%.2f,cong_suat:%.1f\n", diemDat, nhiet, congSuat);
}
if (bayGio - dauCuaSo >= CUA_SO_MS) dauCuaSo += CUA_SO_MS; // tỉ lệ thời gian: bật phần đầu của cửa sổ
bool chay = (cheDo == CHAY || cheDo == GIU_TAY);
digitalWrite(CHAN_SSR, chay && (bayGio - dauCuaSo) < congSuat / 100.0f * CUA_SO_MS ? HIGH : LOW);
}
Những điểm chính của chương trình:
- Biểu đồ nhiệt khai báo trong mảng
BIEU_DO: mỗi đoạn có tốc độ (°C/giờ), nhiệt đích, số phút giữ. Ba đoạn trong chương trình chỉ là ví dụ; biểu đồ nung thật lấy theo hướng dẫn của loại đất, men hoặc vật liệu cần ủ. Đoạn có nhiệt đích thấp hơn đoạn trước là đoạn hạ nhiệt có kiểm soát. - Điểm đặt đi dần theo tốc độ của đoạn, mỗi giây một bước. PID bám theo điểm đặt này chứ không nhảy thẳng lên nhiệt đích, nên lò tăng đều và ít vọt lố.
- Chờ lò bắt kịp: khi nhiệt độ thấp hơn điểm đặt quá 15 °C (dây đốt yếu, cửa lò hở, lò nặng tải), điểm đặt đứng lại. Thời gian giữ chỉ bắt đầu tính khi nhiệt độ thật đã cách đích dưới 5 °C.
- PID dùng thành phần D theo giá trị đo (không giật khi điểm đặt đổi) và chỉ cộng tích phân khi công suất chưa bão hòa, tránh vọt lố lớn sau những lúc chạy 100 %. Đổi Ki bằng lệnh
ithì phần tích phân được quy đổi để công suất không nhảy. - Lệnh qua Serial:
rchạy biểu đồ từ nhiệt độ hiện tại,s 300giữ cố định 300 °C (dùng khi thử và chỉnh PID),xdừng hoặc xóa lỗi,p,i,dđổi hệ số. Dòngdat:…,nhiet:…,cong_suat:…in mỗi giây, mở Serial Plotter của Arduino IDE để xem đồ thị.

5. Thử trên mô hình lò trước khi nung thật
Lò nung thật cần nhiều giờ cho mỗi lần thử, nên trước khi cấp điện nên chạy thuật toán trên một mô hình. Script Python dưới đây dùng đúng thuật toán của chương trình (PID, chống bão hòa, cửa sổ SSR, chờ lò bắt kịp, bước đọc 0,25 °C của MAX6675) với mô hình lò 2 kW gồm hai khối nhiệt (dây đốt và buồng lò), tổn thất ra ngoài tăng theo nhiệt độ. Thông số lò là giả định để minh họa; đo thời gian tăng nhiệt của lò bạn rồi sửa lại cho gần.
# PYWORLD.VN - Mô phỏng lò nung điện 2 kW điều khiển bằng PID và SSR tỉ lệ thời gian, chạy biểu đồ nhiệt nhiều đoạn có chờ lò bắt kịp
# PYWORLD.VN - Cùng thuật toán với sketch ESP32_LO_NUNG_PID_SSR: PID mỗi giây, tích phân chống bão hòa, cửa sổ SSR 4 giây, đọc MAX6675 mỗi 250 ms
# PYWORLD.VN - Chạy: python mo_phong_lo_nung.py (mô hình hai khối nhiệt; số liệu lò là giả định để minh họa)
import math
# ---- Mô hình lò (giả định): dây đốt + vách trong, buồng lò + vật nung, tổn thất ra ngoài tăng theo nhiệt độ
P_MAX = 2000.0 # W
C1, C2 = 3000.0, 17000.0 # J/°C: khối dây đốt, khối buồng lò
G12 = 20.0 # W/°C: truyền nhiệt dây đốt → buồng lò
T_MOI_TRUONG = 25.0
TAU_CAP_NHIET = 10.0 # s: cặp nhiệt trong ống bảo vệ phản ứng chậm
def ton_that(t: float) -> float:
d = t - T_MOI_TRUONG
return 0.9 * d + 4e-4 * d * d # W
# ---- Bộ điều khiển (giống sketch)
CUA_SO_S, DT = 4.0, 1.0
CHO_BAT_KIP, SAI_SO_GIU = 15.0, 5.0
BIEU_DO = [(100, 200, 30), (200, 600, 0), (150, 900, 15)] # °C/giờ, °C đích, phút giữ
KP, KI, KD = 5.0, 0.004, 0.0
class DieuKhien:
def __init__(self, nhiet: float, kp=KP, ki=KI, kd=KD, bieu_do=BIEU_DO):
self.kp, self.ki, self.kd, self.bd = kp, ki, kd, bieu_do
self.diem_dat, self.tich_phan, self.nhiet_truoc = nhiet, 0.0, nhiet
self.doan, self.dang_giu, self.giay_giu, self.xong, self.giay_cho = 0, False, 0.0, False, 0.0
def buoc_bieu_do(self, nhiet: float):
toc_do, dich, giu = self.bd[self.doan]
if not self.dang_giu:
buoc = toc_do / 3600 * DT
if self.diem_dat < dich:
if self.diem_dat - nhiet <= CHO_BAT_KIP:
self.diem_dat = min(dich, self.diem_dat + buoc)
else:
self.giay_cho += DT
elif self.diem_dat > dich:
if nhiet - self.diem_dat <= CHO_BAT_KIP:
self.diem_dat = max(dich, self.diem_dat - buoc)
else:
self.giay_cho += DT
if self.diem_dat == dich and abs(nhiet - dich) < SAI_SO_GIU:
self.dang_giu, self.giay_giu = True, 0.0
else:
self.giay_giu += DT
if self.giay_giu >= giu * 60:
self.doan, self.dang_giu = self.doan + 1, False
if self.doan >= len(self.bd):
self.xong = True
def buoc_pid(self, nhiet: float) -> float:
e = self.diem_dat - nhiet
d = (nhiet - self.nhiet_truoc) / DT
self.nhiet_truoc = nhiet
u = self.kp * e + self.ki * self.tich_phan - self.kd * d
bao_hoa = (u >= 100 and e > 0) or (u <= 0 and e < 0)
if not bao_hoa:
self.tich_phan += e * DT
return min(100.0, max(0.0, u))
def mo_phong(kp=KP, ki=KI, kd=KD, bieu_do=BIEU_DO, buoc=0.1, t_toi_da=40000.0, sau_xong=1800.0):
"""Trả về danh sách (giây, điểm đặt, nhiệt đo, công suất %) mỗi 10 s và tổng điện năng (kWh)."""
t1 = t2 = tc = T_MOI_TRUONG
dk = DieuKhien(tc, kp, ki, kd, bieu_do)
cong_suat, nhiet_do, kq, wh, t, luc_xong = 0.0, tc, [], 0.0, 0.0, None
n = 0
while t < t_toi_da:
if n % round(0.25 / buoc) == 0: # MAX6675: mỗi 250 ms, bước 0,25 °C
nhiet_do = math.floor(tc / 0.25) * 0.25
if n % round(DT / buoc) == 0:
if not dk.xong:
dk.buoc_bieu_do(nhiet_do)
cong_suat = 0.0 if dk.xong else dk.buoc_pid(nhiet_do)
if dk.xong and luc_xong is None:
luc_xong = t
if n % round(10 / buoc) == 0:
kq.append((t, dk.diem_dat, nhiet_do, cong_suat))
bat = (t % CUA_SO_S) < cong_suat / 100 * CUA_SO_S
p = P_MAX if bat else 0.0
wh += p * buoc / 3600
q12 = G12 * (t1 - t2)
t1 += (p - q12) / C1 * buoc
t2 += (q12 - ton_that(t2)) / C2 * buoc
tc += (t2 - tc) / TAU_CAP_NHIET * buoc
t += buoc
n += 1
if luc_xong is not None and t - luc_xong > sau_xong:
break
return kq, wh / 1000, dk, luc_xong
def danh_gia(kq, luc_xong, bieu_do=BIEU_DO):
"""Vọt lố lớn nhất so với nhiệt đích các đoạn và độ chậm lớn nhất so với điểm đặt, tính tới lúc xong biểu đồ."""
kq = [k for k in kq if k[0] <= luc_xong]
dich = {b[1] for b in bieu_do}
vot = max(n - d for _, d, n, _ in kq if d in dich)
lech = max(d - n for _, d, n, _ in kq)
return vot, lech
def p_giu(t: float) -> float:
"""Công suất (%) cần để giữ lò ở nhiệt độ t (trạng thái ổn định)."""
return ton_that(t) / P_MAX * 100
if __name__ == "__main__":
for t in (200, 600, 900):
print(f"Giu {t} C can khoang {p_giu(t):.0f} % cong suat; toc do tang toi da o {t} C: {(P_MAX - ton_that(t)) / (C1 + C2) * 3600:.0f} C/gio")
kq, kwh, dk, luc_xong = mo_phong()
vot, lech = danh_gia(kq, luc_xong)
print(f"Kp {KP}, Ki {KI}, Kd {KD}: xong bieu do sau {luc_xong / 3600:.2f} gio, dien nang {kwh:.1f} kWh")
print(f" vot lo lon nhat {vot:.1f} C, cham hon diem dat toi da {lech:.1f} C, thoi gian cho bat kip {dk.giay_cho / 60:.0f} phut")
for t, d, n, u in kq[::180]:
print(f" {t / 3600:5.2f} h dat {d:6.1f} do {n:6.1f} cong suat {u:5.1f} %")

- Công suất cần để giữ nhiệt tăng theo nhiệt độ (khoảng 8 % ở 200 °C, 55 % ở 900 °C trong mô hình), vì vậy lò bắt buộc phải có thành phần tích phân; chỉ dùng P thì lò luôn thấp hơn điểm đặt.
- Vọt lố xảy ra ở lúc chuyển từ tăng sang giữ, khoảng 8 °C ở 900 °C trong mô hình. Vật nung nhạy với vọt lố thì thêm một đoạn tăng chậm trong 30–50 °C cuối.
- Kp 5, Ki 0,004 là điểm khởi đầu của mô hình này, không dùng thẳng cho lò khác. Cách tìm hệ số bằng thử rơ le có ở bài cùng cụm về chỉnh PID; nguyên lý P, I, D có ở bài giữ tốc độ động cơ DC bằng PID trên ESP32.

Không có chức năng chờ, điểm đặt tới 900 °C sớm trong khi lò mới khoảng 850 °C, đồng hồ giữ 15 phút bắt đầu chạy và lò tắt khi vật nung chưa kịp ngấm nhiệt. Với chức năng chờ, tốc độ thật bằng tốc độ lò làm được.
6. Ngắt an toàn

- Hở cặp nhiệt là lỗi nguy hiểm nhất của bộ điều khiển tự làm: số đo sai thường thấp, PID tăng công suất lên 100 %. Chương trình kiểm tra bit hở mạch của MAX6675, đọc lỗi 3 lần liền là tắt.
- Công suất 100 % suốt 10 phút mà nhiệt tăng chưa tới 5 °C nghĩa là nhiệt không vào lò (dây đốt đứt, SSR hỏng hở, cặp nhiệt rơi ra ngoài buồng lò): chương trình tắt và báo lỗi. Ở gần nhiệt độ tối đa lò làm được, kiểm tra này cũng có thể báo; khi đó lò không đủ công suất cho biểu đồ.
- Watchdog: vòng
loop()treo quá 5 giây thì ESP32 tự khởi động lại; chân SSR về trạng thái vào và điện trở 10 kΩ giữ SSR tắt. Sau khởi động lại, chương trình ở trạng thái dừng, không tự chạy tiếp biểu đồ. - Không để lò chạy không người trông khi chưa thử đủ các lỗi trên: rút cặp nhiệt ra khỏi MAX6675 lúc đang giữ nhiệt thấp để chắc SSR và khởi động từ cùng tắt.
7. Lỗi hay gặp
| Hiện tượng | Nguyên nhân | Cách xử lý |
|---|---|---|
| Nhiệt độ đọc giảm khi lò nóng lên | Đấu ngược cực cặp nhiệt | Đổi hai dây ở MAX6675 (theo mã màu ANSI, dây đỏ là cực âm của cặp nhiệt K) |
| Số đo nhảy khi SSR đóng | Dây cặp nhiệt đi chung với dây động lực, đầu cặp nhiệt chạm vỏ lò | Đi dây riêng, dùng dây bù loại K, cách điện đầu đo với vỏ |
| SSR có đèn sáng nhưng dây đốt không nóng | Khởi động từ chưa hút, SSR hỏng hở, hoặc nhầm SSR ngõ ra DC (loại DD) | Kiểm tra khởi động từ; dùng SSR loại DA cho tải AC |
| Dây đốt nóng cả khi công suất 0 | SSR chập (kiểu hỏng hay gặp nhất) | Thay SSR; khởi động từ cắt khi dừng hoặc lỗi |
| Bút thử điện báo có điện ở dây đốt khi SSR tắt | Dòng rò nhỏ qua mạch RC bên trong SSR | Bình thường, không làm dây đốt nóng; vẫn cắt khởi động từ trước khi chạm vào lò |
| Nhiệt dao động quanh điểm đặt | Kp, Ki quá lớn so với độ trễ của lò | Giảm Ki trước, rồi Kp; thử bằng lệnh s |
| Luôn thấp hơn điểm đặt khi đang tăng | Lò không đủ công suất cho tốc độ yêu cầu | Giảm tốc độ của đoạn; chức năng chờ sẽ giữ thời gian giữ đúng |
| ESP32 reset khi khởi động từ đóng ngắt | Nhiễu từ cuộn hút, nguồn yếu | Xem bài đóng relay làm ESP32 bị reset; thêm mạch dập RC ở cuộn hút |
Mạch dùng trong bài: ESP32 DevKit V1 30 chân, module relay 1 kênh. Cấu tạo cặp nhiệt điện và cách chọn loại cặp nhiệt có ở bài cảm biến nhiệt độ: cặp nhiệt điện, RTD, NTC.
Bài cùng cụm chủ đề: MAX6675 và MAX31855 với ESP32; relay, SSR hay transistor; chỉnh PID bằng Ziegler–Nichols và thử rơ le.
Xem thêm