Đo nhiệt độ bê tông khối lớn: bố trí cảm biến, ngưỡng chênh nhiệt, cảnh báo bằng ESP32 và DS18B20
Đo nhiệt độ bê tông khối lớn: ngưỡng chênh nhiệt 20 °C, mô đun 50 °C/m, bố trí cảm biến tâm, giữa, mặt; mô phỏng để trần và phủ giữ nhiệt; hệ đo nhiều điểm DS18B20 với ESP32 có còi cảnh báo.
Bê tông khi đông cứng tỏa nhiệt do phản ứng thủy hóa xi măng. Ở móng bè, đài cọc, đập, khối dày từ khoảng 1 m trở lên, nhiệt trong lõi không thoát kịp, lõi nóng lên 60–70 °C trong khi mặt ngoài nguội theo không khí. Chênh lệch nhiệt độ giữa tâm và mặt quá lớn làm mặt ngoài co lại trong khi lõi còn nở, sinh nứt nhiệt. Vì vậy thi công bê tông khối lớn phải đo nhiệt độ liên tục và cảnh báo khi chênh nhiệt vượt ngưỡng. Bài này hướng dẫn bố trí cảm biến, ngưỡng thường dùng, mô phỏng cho thấy vì sao phải phủ giữ nhiệt, và một hệ đo nhiều điểm bằng DS18B20 với ESP32, có còi cảnh báo.
Lưu ý: bài này nói về cách đo và theo dõi. Biện pháp kiểm soát nhiệt của từng công trình (ngưỡng, số điểm đo, cách bảo dưỡng) do thiết kế và chỉ dẫn kỹ thuật quyết định.
1. Ngưỡng cần theo dõi
- Chênh nhiệt tâm – mặt: TCVN 9341:2012 (bê tông khối lớn, thi công và nghiệm thu) yêu cầu chênh lệch nhiệt độ giữa các điểm trong khối không quá 20 °C.
- Mô đun chênh nhiệt: chênh nhiệt chia cho khoảng cách giữa hai điểm đo, không quá 50 °C/m theo cùng tiêu chuẩn.
- Nhiệt độ tâm lớn nhất: nhiều chỉ dẫn (ví dụ ACI 301) giới hạn khoảng 70 °C để tránh hư hại lâu dài của bê tông.
Nhiệt độ tăng nhanh trong 1–3 ngày đầu, chênh nhiệt lớn nhất thường rơi vào ngày thứ 3–7, nên đo liên tục từ lúc đổ tới khi chênh nhiệt giảm hẳn, thường 7–14 ngày.
2. Bố trí cảm biến

- Điểm tâm ở giữa chiều dày: nóng nhất. Điểm mặt cách bề mặt khoảng 5 cm: nguội nhanh nhất. Thêm một điểm giữa để thấy phân bố nhiệt, và một điểm đo không khí.
- Theo mặt bằng: cụm ở giữa khối (nóng nhất), giữa cạnh và góc (nguội nhanh do tỏa nhiệt hai, ba phía).
- Cảm biến buộc vào thanh thép đứng đúng cao độ, hoặc luồn trong ống chôn sẵn để thay được. Ghi lại mã của từng cảm biến và vị trí trước khi đổ.
3. Mô phỏng: vì sao phải phủ giữ nhiệt

Script dưới đây mô phỏng nhiệt độ theo chiều dày khối bằng phương pháp sai phân: nhiệt thủy hóa sinh ra trong khối, mặt trên tỏa nhiệt ra không khí (có dao động ngày đêm), đáy coi như không mất nhiệt. So ba cách làm:

# PYWORLD.VN - Mô phỏng nhiệt độ trong khối bê tông dày 2 m sau khi đổ: nhiệt thủy hóa xi măng, mặt trên tỏa nhiệt ra không khí, đáy coi như không mất nhiệt
# PYWORLD.VN - So ba cách: để trần, phủ vật liệu giữ nhiệt, phủ và hạ nhiệt độ bê tông khi đổ; tính chênh nhiệt tâm – mặt và mô đun chênh nhiệt (°C/m)
# PYWORLD.VN - Chạy: python mo_phong_nhiet_be_tong.py (mô hình một chiều, thông số điển hình; không thay cho tính toán nhiệt của thiết kế)
import math
DAY = 2.0 # m, chiều dày khối
DX = 0.05 # m
K, RHO_C = 2.5, 2.4e6 # W/(m·K), J/(m³·K)
T_DO, T_KK, BIEN_DO_NGAY = 30.0, 28.0, 4.0 # nhiệt độ khi đổ, không khí trung bình, dao động ngày đêm (°C)
T_DOAN_NHIET, A_NGAY = 45.0, 0.8 # tăng nhiệt đoạn nhiệt cuối cùng (°C), tốc độ tỏa nhiệt (1/ngày)
H_TRAN, R_PHU = 12.0, 0.5 # W/(m²·K) mặt để trần; nhiệt trở lớp phủ (m²·K/W)
SAU_CAM_BIEN_MAT = 0.05 # m, cảm biến mặt đặt dưới bề mặt 5 cm
def mo_phong(phu: bool, so_ngay: float = 14, dt_gio: float = 0.1, t_do: float = T_DO) -> list[tuple[float, float, float]]:
"""Trả về [(giờ, nhiệt độ tâm, nhiệt độ cách mặt 5 cm)] mỗi giờ."""
n = int(DAY / DX) + 1
t = [t_do] * n # nút 0 = đáy, nút n-1 = mặt trên
alpha = K / RHO_C * 3600 # m²/giờ
h = 1 / (1 / H_TRAN + R_PHU) if phu else H_TRAN
a = A_NGAY / 24
kq, gio = [], 0.0
i_tam, i_mat = n // 2, n - 1 - round(SAU_CAM_BIEN_MAT / DX)
for buoc in range(int(so_ngay * 24 / dt_gio) + 1):
if buoc % round(1 / dt_gio) == 0:
kq.append((gio, t[i_tam], t[i_mat]))
nguon = T_DOAN_NHIET * a * math.exp(-a * gio) * dt_gio # °C tăng do thủy hóa trong bước này
t_kk = T_KK + BIEN_DO_NGAY * math.sin(2 * math.pi * (gio - 9) / 24)
moi = t[:]
for i in range(n):
trai = t[i - 1] if i > 0 else t[1] # đáy đối xứng: không mất nhiệt
if i < n - 1:
phai = t[i + 1]
else: # nút ảo ở mặt trên theo điều kiện đối lưu
phai = t[i - 1] - 2 * DX * h / K * (t[i] - t_kk)
moi[i] = t[i] + alpha * dt_gio / DX ** 2 * (trai - 2 * t[i] + phai) + nguon
t = moi
gio += dt_gio
return kq
def danh_gia(kq) -> dict:
chenh = [(g, tam - mat) for g, tam, mat in kq]
g_max, d_max = max(chenh, key=lambda x: x[1])
return {"t_tam_max": max(tam for _, tam, _ in kq), "chenh_max": d_max, "luc": g_max,
"mo_dun": d_max / (DAY / 2 - SAU_CAM_BIEN_MAT)}
CACH = [("de tran, do 30 C", False, 30.0), ("phu giu nhiet, do 30 C", True, 30.0), ("phu giu nhiet, do 24 C", True, 24.0)]
if __name__ == "__main__":
for ten, phu, t_do in CACH:
k = danh_gia(mo_phong(phu, t_do=t_do))
print(f"{ten:24s}: tam cao nhat {k['t_tam_max']:.1f} C, chenh tam-mat lon nhat {k['chenh_max']:.1f} C "
f"luc {k['luc'] / 24:.1f} ngay, mo dun chenh nhiet {k['mo_dun']:.1f} C/m")
Kết quả cho thấy hai biện pháp bổ sung nhau: phủ giữ nhiệt (bạt, xốp, bao tải ẩm phủ nilon) để mặt không nguội nhanh; hạ nhiệt độ bê tông khi đổ (đổ ban đêm, dùng nước lạnh, đá lạnh, xi măng ít tỏa nhiệt) để tâm không quá nóng. Đừng gỡ lớp phủ sớm: mặt bê tông đang 50–60 °C gặp không khí 28 °C sẽ nguội đột ngột. Số trong bài là mô phỏng để hiểu xu hướng, không thay cho tính toán nhiệt của thiết kế.
4. Hệ đo nhiều điểm bằng DS18B20 và ESP32

DS18B20 đo được tới 125 °C, sai số khoảng ±0,5 °C, mỗi con có một mã ROM 64 bit riêng nên nhiều con nối chung một dây dữ liệu 1-Wire. Giao thức 1-Wire, cách đọc mã ROM có ở bài 1-Wire là gì: khe thời gian, mã ROM và cách đọc DS18B20. Ở đây chương trình thêm phần dò tìm nhiều thiết bị (lệnh Search ROM) và đọc từng cảm biến theo mã (lệnh Match ROM), không cần thư viện ngoài.
- Dây dữ liệu GPIO4, điện trở kéo lên 4,7 kΩ lên 3,3 V; cáp dài vài chục mét thì giảm xuống 2,2 kΩ. Nối 3 dây (cấp nguồn riêng), không dùng kiểu cấp nguồn ký sinh 2 dây khi cáp dài.
- Còi hoặc đèn báo ở GPIO25 (qua transistor nếu còi lấy dòng lớn).
- Lần đầu chạy, chương trình in danh sách mã ROM tìm thấy. Điền mã vào bảng
BANG_VI_TRIứng với vị trí tâm, giữa, mặt, không khí rồi nạp lại.
// PYWORLD.VN - Đo nhiệt độ bê tông khối lớn bằng nhiều DS18B20 trên một dây 1-Wire (giao thức tự viết, không cần thư viện ngoài), mỗi phút đo một lần
// PYWORLD.VN - Gán từng cảm biến vào vị trí theo mã ROM (tâm, giữa, mặt, không khí); tính chênh nhiệt tâm – mặt và mô đun °C/m, kêu còi khi vượt ngưỡng
// PYWORLD.VN - Dây dữ liệu GPIO4 kéo lên 4,7 kΩ (dây dài: 2,2 kΩ), cảm biến nối 3 dây; còi GPIO25. In dòng CSV ra Serial để ghi lại bằng máy tính
#define DQ_PIN 4
const int COI_PIN = 25;
const float NGUONG_CHENH = 20.0; // °C, chênh nhiệt tâm – mặt (lấy theo chỉ dẫn kỹ thuật của công trình)
const float NGUONG_MO_DUN = 50.0; // °C/m
const float NGUONG_TAM = 70.0; // °C, nhiệt độ tâm lớn nhất
const float KHOANG_TAM_MAT_M = 0.95; // m, khoảng cách cảm biến tâm tới cảm biến mặt
const uint32_t CHU_KY_MS = 60000;
struct ViTri { const char *rom; const char *ten; };
const ViTri BANG_VI_TRI[] = { // điền mã ROM chương trình in ra lúc khởi động
{"28FF000000000001", "tam"},
{"28FF000000000002", "giua"},
{"28FF000000000003", "mat"},
{"28FF000000000004", "khong_khi"},
};
const int SO_VI_TRI = sizeof(BANG_VI_TRI) / sizeof(BANG_VI_TRI[0]);
const int TOI_DA_CAM_BIEN = 12;
uint8_t dsRom[TOI_DA_CAM_BIEN][8];
int soCamBien = 0;
uint8_t romTim[8];
int lechCuoi = 0;
bool hetThietBi = false;
uint32_t lucDo = 0, soPhut = 0;
bool datLai() {
digitalWrite(DQ_PIN, LOW);
delayMicroseconds(480);
noInterrupts();
digitalWrite(DQ_PIN, HIGH);
delayMicroseconds(70);
bool coThietBi = digitalRead(DQ_PIN) == LOW;
interrupts();
delayMicroseconds(410);
return coThietBi;
}
void ghiBit(bool bit) {
noInterrupts();
digitalWrite(DQ_PIN, LOW);
delayMicroseconds(bit ? 6 : 60);
digitalWrite(DQ_PIN, HIGH);
delayMicroseconds(bit ? 64 : 10);
interrupts();
}
bool docBit() {
noInterrupts();
digitalWrite(DQ_PIN, LOW);
delayMicroseconds(6);
digitalWrite(DQ_PIN, HIGH);
delayMicroseconds(9);
bool bit = digitalRead(DQ_PIN);
interrupts();
delayMicroseconds(55);
return bit;
}
void ghiByte(uint8_t giaTri) {
for (int i = 0; i < 8; i++) ghiBit((giaTri >> i) & 1);
}
uint8_t docByte() {
uint8_t giaTri = 0;
for (int i = 0; i < 8; i++) if (docBit()) giaTri |= (1 << i);
return giaTri;
}
uint8_t tinhCrc8(const uint8_t *duLieu, int soByte) {
uint8_t crc = 0;
for (int i = 0; i < soByte; i++) {
uint8_t b = duLieu[i];
for (int k = 0; k < 8; k++) {
bool tron = (crc ^ b) & 0x01;
crc >>= 1;
if (tron) crc ^= 0x8C;
b >>= 1;
}
}
return crc;
}
// Search ROM (lệnh 0xF0): mỗi lần gọi trả về một mã ROM, đi theo cây nhị phân, nhánh chưa đi được nhớ ở lechCuoi
bool timTiep(uint8_t *ma) {
if (hetThietBi || !datLai()) return false;
ghiByte(0xF0);
int lechMoi = 0;
for (int viTriBit = 1; viTriBit <= 64; viTriBit++) {
bool b = docBit(), bBu = docBit();
if (b && bBu) return false; // không thiết bị nào trả lời
bool huong;
if (b != bBu) huong = b; // mọi thiết bị còn lại cùng bit này
else { // có thiết bị bit 0 và có thiết bị bit 1: chọn nhánh
if (viTriBit < lechCuoi) huong = (romTim[(viTriBit - 1) / 8] >> ((viTriBit - 1) % 8)) & 1;
else huong = viTriBit == lechCuoi;
if (!huong) lechMoi = viTriBit;
}
uint8_t mat = 1 << ((viTriBit - 1) % 8);
if (huong) romTim[(viTriBit - 1) / 8] |= mat;
else romTim[(viTriBit - 1) / 8] &= ~mat;
ghiBit(huong);
}
lechCuoi = lechMoi;
if (lechCuoi == 0) hetThietBi = true;
memcpy(ma, romTim, 8);
return tinhCrc8(romTim, 7) == romTim[7];
}
void romThanhChuoi(const uint8_t *rom, char *chuoi) {
for (int i = 0; i < 8; i++) sprintf(chuoi + 2 * i, "%02X", rom[i]);
}
bool docNhietDo(const uint8_t *rom, float *nhietDo) {
if (!datLai()) return false;
ghiByte(0x55); // Match ROM: chỉ cảm biến có mã này trả lời
for (int i = 0; i < 8; i++) ghiByte(rom[i]);
ghiByte(0xBE);
uint8_t d[9];
for (int i = 0; i < 9; i++) d[i] = docByte();
if (tinhCrc8(d, 8) != d[8]) return false;
*nhietDo = (int16_t)((d[1] << 8) | d[0]) / 16.0f;
return true;
}
float nhietDoTheoTen(const char *ten) { // NAN nếu chưa gán hoặc đọc lỗi
for (int v = 0; v < SO_VI_TRI; v++) {
if (strcmp(BANG_VI_TRI[v].ten, ten) != 0) continue;
for (int i = 0; i < soCamBien; i++) {
char chuoi[17];
romThanhChuoi(dsRom[i], chuoi);
float t;
if (strcmp(chuoi, BANG_VI_TRI[v].rom) == 0 && docNhietDo(dsRom[i], &t)) return t;
}
}
return NAN;
}
void setup() {
Serial.begin(115200);
pinMode(COI_PIN, OUTPUT);
digitalWrite(COI_PIN, LOW);
pinMode(DQ_PIN, INPUT); // gói esp32 3.x: ghi mức cao trước rồi mới chuyển cực máng hở
digitalWrite(DQ_PIN, HIGH);
pinMode(DQ_PIN, OUTPUT_OPEN_DRAIN);
while (soCamBien < TOI_DA_CAM_BIEN && timTiep(dsRom[soCamBien])) soCamBien++;
Serial.printf("Tim thay %d cam bien:\n", soCamBien);
for (int i = 0; i < soCamBien; i++) {
char chuoi[17];
romThanhChuoi(dsRom[i], chuoi);
const char *ten = "chua_gan";
for (int v = 0; v < SO_VI_TRI; v++) if (strcmp(chuoi, BANG_VI_TRI[v].rom) == 0) ten = BANG_VI_TRI[v].ten;
Serial.printf(" %s %s\n", chuoi, ten);
}
Serial.println("phut,tam,giua,mat,khong_khi,chenh_tam_mat,mo_dun_C_m,canh_bao");
}
void loop() {
if (soPhut > 0 && millis() - lucDo < CHU_KY_MS) return;
lucDo = millis();
datLai();
ghiByte(0xCC); // Skip ROM: mọi cảm biến cùng đo một lúc
ghiByte(0x44);
delay(750); // thời gian đo ở độ phân giải 12 bit
float tam = nhietDoTheoTen("tam"), giua = nhietDoTheoTen("giua"), mat = nhietDoTheoTen("mat"), kk = nhietDoTheoTen("khong_khi");
float chenh = tam - mat, moDun = chenh / KHOANG_TAM_MAT_M;
bool canhBao = (!isnan(chenh) && (chenh > NGUONG_CHENH || moDun > NGUONG_MO_DUN)) || (!isnan(tam) && tam > NGUONG_TAM);
digitalWrite(COI_PIN, canhBao ? HIGH : LOW);
Serial.printf("%u,%.2f,%.2f,%.2f,%.2f,%.2f,%.1f,%s\n", (unsigned)soPhut, tam, giua, mat, kk, chenh, moDun, canhBao ? "CO" : "khong");
soPhut++;
}

Dòng CSV chép vào bảng tính để vẽ đồ thị, hoặc ghi thẳng ra thẻ nhớ bằng module thẻ Micro SD, gửi lên mạng để kỹ sư xem từ xa. Công trường mất điện thường xuyên: cấp nguồn qua bộ lưu điện nhỏ hoặc pin, và ghi thời gian thực (module RTC) vào dữ liệu.
5. Lưu ý khi đo
| Vấn đề | Nguyên nhân | Cách xử lý |
|---|---|---|
| Không tìm thấy cảm biến nào | Thiếu điện trở kéo lên, đứt cáp | Đo điện áp dây dữ liệu khi nghỉ (phải gần 3,3 V); kiểm tra cáp |
| Tìm thiếu cảm biến, đọc lúc được lúc không | Cáp quá dài, kéo lên yếu, nhiễu | Kéo lên 2,2 kΩ, cáp xoắn có vỏ chống nhiễu, chia thành vài dây |
| Một điểm đọc 85 °C cố định | Đọc trước khi cảm biến đo xong, hoặc thiếu nguồn | Chờ đủ 750 ms; nối 3 dây |
| Cảm biến hỏng sau khi đổ | Đầm dùi va vào, nước vào mối nối | Luồn trong ống, bọc kín mối nối bằng ống co nhiệt có keo |
| Điểm mặt đọc gần bằng không khí | Đặt quá sát bề mặt hoặc lộ ra ngoài | Giữ cách mặt khoảng 5 cm, có bê tông bao phủ |
Mạch dùng trong bài: ESP32 DevKit V1 30 chân. Cấu tạo, nguyên lý các loại cảm biến nhiệt (cặp nhiệt, RTD, NTC) có ở bài cảm biến nhiệt độ: cấu tạo, nguyên lý; công trình lớn thường dùng cặp nhiệt hoặc cảm biến dây rung có bộ ghi chuyên dụng.
Bài cùng cụm chủ đề: bộ ghi dữ liệu đo nhiều kênh; DIN 4150-3 và đo rung công trình.
Xem thêm