ADC 24 bit HX711, ADS1232, ADS1220, ADS1256: so sánh nhiễu, tốc độ và chọn loại nào
So sánh ADC 24 bit HX711, ADS1232, ADS1220, ADS1256 theo nhiễu datasheet, gain, tốc độ, giao tiếp; tính độ phân giải trên load cell, chọn theo ứng dụng, code ESP32 đo nhiễu HX711.
Module ADC 24 bit nào cũng ghi "24 bit", nhưng HX711 giá vài chục nghìn đồng và ADS1256 giá gấp nhiều lần cho kết quả rất khác nhau, tùy việc cần đo. Bài này so sánh bốn chip ADC 24 bit hay gặp: HX711, ADS1232, ADS1220, ADS1256. Các chip được so theo nhiễu thật trong datasheet, tốc độ, gain, giao tiếp. Bài tính xem mỗi chip phân biệt được bao nhiêu mức trên một load cell, gợi ý chọn theo ứng dụng, và có chương trình ESP32 đo nhiễu thật của module HX711 trên mạch của bạn.
1. "24 bit" không có nghĩa là dùng được 24 bit
Cả bốn chip đều là ADC delta-sigma: lấy mẫu rất nhanh với độ phân giải thô, rồi lọc số và giảm tốc độ để ra một số 24 bit. Con số 24 bit là độ dài của kết quả, không phải độ chính xác. Với HX711 ở gain 128, một bước (LSB) chỉ khoảng 2 nV, trong khi nhiễu của chip đã cỡ 50 nV. Vì vậy vài bit cuối luôn nhảy.
Thông số quyết định là nhiễu quy về đầu vào (nV RMS), datasheet ghi theo từng gain và tốc độ. Từ nhiễu tính ra số bit hiệu dụng hoặc số bit không nhiễu; cách tính, ý nghĩa của ENOB và bit không nhiễu giải thích ở bài độ phân giải ADC cùng cụm chủ đề. Bài này dùng cách của TI: nhiễu đỉnh-đỉnh lấy bằng 6,6 lần nhiễu RMS.
2. Bốn chip ADC 24 bit hay gặp

- HX711 (Avia Semiconductor): làm riêng cho cân. Kênh A gain 128 hoặc 64, kênh B gain 32; 10 hoặc 80 mẫu/giây; nhiễu 50 nV RMS ở 10 SPS, 90 nV ở 80 SPS. Có sẵn mạch ổn áp cấp điện kích cho cầu. Giao tiếp hai dây riêng (DT, SCK), không phải I2C. Module HX711 rẻ, nhiều thư viện, là lựa chọn mặc định cho cân tự làm.
- ADS1232 (TI): cùng mục đích với HX711 nhưng ít nhiễu hơn khoảng 3 lần: 17 nV RMS ở 10 SPS, 44 nV ở 80 SPS (gain 128). Ở 10 SPS chặn đồng thời nhiễu 50 Hz và 60 Hz, có cảm biến nhiệt trong chip. Giao tiếp hai dây; gain và tốc độ chọn bằng chân, không có thanh ghi.
- ADS1220 (TI): ADC đa dụng cho cảm biến nhỏ: gain 1–128, 20–2000 SPS, tham chiếu trong 2,048 V, hai nguồn dòng 10 µA–1,5 mA để kích RTD (PT100, PT1000), cảm biến nhiệt trong chip, giao tiếp SPI. Nhiễu ở gain 128: 0,09 µV RMS ở 20 SPS, 0,18 µV ở 90 SPS.
- ADS1256 (TI): nhanh nhất trong nhóm, 2,5 tới 30.000 SPS, 8 đầu vào đơn hoặc 4 vi sai, giao tiếp SPI. Gain lớn nhất chỉ 64, tham chiếu tối đa khoảng 2,6 V. Nhiễu ở gain 64: 61 nV RMS ở 10 SPS, 0,49 µV ở 1000 SPS. Module ADS1256 hay đi kèm Raspberry Pi.
Dòng cuối của bảng là ADS1115 16 bit để so sánh: ở thang nhỏ nhất ±0,256 V, mỗi bước 7,8 µV. Tín hiệu load cell vài mV chỉ chiếm vài trăm bước, quá thô; nhưng với tín hiệu cỡ vôn (cảm biến áp suất có khuếch đại sẵn, chiết áp, cảm biến 0–3,3 V) thì ADS1115 đủ dùng. Cách dùng ở bài đo điện áp 16 bit bằng ADS1115 với ESP32.
3. So trên cùng một load cell

Load cell 5 kg loại 1 mV/V, kích 5 V cho tín hiệu toàn tải chỉ 5 mV. Chia 5 mV cho nhiễu đỉnh-đỉnh của từng chip ra số mức phân biệt được không nhiễu. Kết quả:
- ADS1232 ở 10 SPS tốt nhất: khoảng 44.600 mức, tức 0,11 g trên load cell 5 kg.
- HX711 ở 10 SPS: khoảng 15.200 mức (0,33 g), đủ cho cân bếp, cân hàng nhỏ.
- ADS1220 chỉ bằng HX711 ở 80 SPS khi đọc load cell, vì nhiễu ở gain 128 cao hơn. Thế mạnh của chip này là RTD, cặp nhiệt, nhiều tốc độ.
- ADS1256 ở gain 64 với tham chiếu 2,5 V có thang ±78 mV; tín hiệu 5 mV chỉ dùng 6 % thang. Chip này mạnh ở đo nhanh nhiều kênh, không phải cân tĩnh.
Thêm một khác biệt: HX711 và ADS1232 dùng chính điện áp kích cầu làm tham chiếu (đo tỉ lệ). Điện áp nguồn trôi thì tín hiệu và tham chiếu trôi cùng tỉ lệ, số đọc không đổi. ADS1220 dùng tham chiếu trong thì không có lợi thế này, trừ khi nối chân tham chiếu ngoài vào điện áp kích.
Script dưới đây tính lại cho load cell của bạn (tải, mV/V, điện áp kích):
# PYWORLD.VN - So sánh ADC 24 bit (HX711, ADS1232, ADS1220, ADS1256) khi đọc load cell
# PYWORLD.VN - Nhiễu RMS lấy từ datasheet; đỉnh-đỉnh = 6,6 x RMS như cách TI tính số bit không nhiễu
# PYWORLD.VN - Chạy: python tinh_chon_adc_24bit.py [--tai 5000] [--mv-v 1.0] [--kich 5.0]
"""Tính độ phân giải không nhiễu của từng ADC trên tín hiệu một load cell.
Tín hiệu toàn tải = độ nhạy (mV/V) x điện áp kích. Số mức không nhiễu = tín hiệu toàn tải / nhiễu đỉnh-đỉnh.
Độ phân giải = tải định mức / số mức không nhiễu. Đây là giới hạn do nhiễu của chip, chưa tính nhiễu của mạch,
dây, nguồn và trôi theo nhiệt độ.
"""
from __future__ import annotations
import argparse
import math
HE_SO_DINH_DINH = 6.6 # nhiễu đỉnh-đỉnh xấp xỉ 6,6 lần RMS (99,9 % mẫu nằm trong khoảng này)
# (tên, tốc độ SPS, nhiễu RMS nV, gain, toàn thang ± mV tính từ điện áp tham chiếu, ghi chú)
# Toàn thang: HX711 ±0,5·AVDD/128; ADS1232 ±0,5·VREF/128; ADS1220 ±VREF/128; ADS1256 ±2·VREF/64.
ADC = [
("HX711", 10, 50, 128, lambda kich: 0.5 * kich / 128 * 1000, "tham chiếu = điện áp kích"),
("HX711", 80, 90, 128, lambda kich: 0.5 * kich / 128 * 1000, "tham chiếu = điện áp kích"),
("ADS1232", 10, 17, 128, lambda kich: 0.5 * kich / 128 * 1000, "tham chiếu = điện áp kích"),
("ADS1232", 80, 44, 128, lambda kich: 0.5 * kich / 128 * 1000, "tham chiếu = điện áp kích"),
("ADS1220", 20, 90, 128, lambda kich: 2.048 / 128 * 1000, "tham chiếu trong 2,048 V"),
("ADS1220", 90, 180, 128, lambda kich: 2.048 / 128 * 1000, "tham chiếu trong 2,048 V"),
("ADS1256", 10, 61, 64, lambda kich: 2 * 2.5 / 64 * 1000, "tham chiếu 2,5 V, bật bộ đệm"),
("ADS1256", 100, 169, 64, lambda kich: 2 * 2.5 / 64 * 1000, "tham chiếu 2,5 V, bật bộ đệm"),
("ADS1256", 1000, 486, 64, lambda kich: 2 * 2.5 / 64 * 1000, "tham chiếu 2,5 V, bật bộ đệm"),
]
def tinh(tai_g: float, mv_v: float, kich_v: float) -> list[dict]:
tin_hieu_mv = mv_v * kich_v
ket_qua = []
for ten, sps, nhieu_nv, gain, toan_thang, ghi_chu in ADC:
thang_mv = toan_thang(kich_v)
dinh_dinh_nv = HE_SO_DINH_DINH * nhieu_nv
so_muc = tin_hieu_mv * 1e6 / dinh_dinh_nv
ket_qua.append({
"ten": ten, "sps": sps, "nhieu_nv": nhieu_nv, "gain": gain, "thang_mv": thang_mv,
"dung_thang": tin_hieu_mv / thang_mv, "so_muc": so_muc, "bit": math.log2(so_muc),
"do_phan_giai_g": tai_g / so_muc, "vuot_thang": tin_hieu_mv > thang_mv, "ghi_chu": ghi_chu,
})
return ket_qua
def main() -> None:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--tai", type=float, default=5000, help="tải định mức của load cell, gam (mặc định 5000)")
ap.add_argument("--mv-v", type=float, default=1.0, help="độ nhạy load cell, mV/V (mặc định 1,0)")
ap.add_argument("--kich", type=float, default=5.0, help="điện áp kích cầu, V (mặc định 5,0)")
a = ap.parse_args()
print(f"Load cell {a.tai:g} g, {a.mv_v:g} mV/V, kich {a.kich:g} V: tin hieu toan tai {a.mv_v * a.kich:g} mV")
print(f"{'ADC':<8} {'SPS':>5} {'RMS nV':>7} {'gain':>5} {'thang mV':>9} {'dung thang':>10} "
f"{'muc khong nhieu':>16} {'bit':>5} {'g/muc':>7}")
for r in tinh(a.tai, a.mv_v, a.kich):
canh_bao = " VUOT THANG" if r["vuot_thang"] else ""
print(f"{r['ten']:<8} {r['sps']:>5} {r['nhieu_nv']:>7} {r['gain']:>5} {r['thang_mv']:>9.1f} "
f"{r['dung_thang'] * 100:>9.0f}% {r['so_muc']:>16,.0f} {r['bit']:>5.1f} {r['do_phan_giai_g']:>7.2f}{canh_bao}")
print("Lay trung binh N mau lien tiep: nhieu RMS giam khoang can(N) lan, toc do giam N lan.")
if __name__ == "__main__":
main()
Chạy python tinh_chon_adc_24bit.py --tai 20000 --mv-v 2 --kich 4.3 cho load cell 20 kg loại 2 mV/V trên module kích 4,3 V. Đây là giới hạn do nhiễu của chip; nhiễu của mạch in, dây, nguồn và độ trôi theo nhiệt độ còn làm kết quả thực tế kém hơn.
4. Chọn theo việc cần đo

- ADC 24 bit giao tiếp I2C cho load cell: NAU7802 (Nuvoton) có gain tới 128 và I2C, tiện khi đã có nhiều thiết bị I2C trên bus.
- ADC 24 bit 192 kHz là ADC âm thanh, giao tiếp I2S. Loại này thường chặn thành phần một chiều và không có gain lớn, nên không đo được load cell, cảm biến nhiệt.
- ADC 24 bit 1 MSPS trở lên dùng cho đo rung, âm học, thiết bị đo chuyên dụng; giá cao và cần vi điều khiển, FPGA đủ nhanh để nhận dữ liệu. Với cảm biến đổi chậm (cân, nhiệt độ, áp suất) thì tốc độ thấp lại có lợi: ít nhiễu hơn và chặn được 50 Hz.
5. Đo nhiễu thật của module HX711 bằng ESP32
Con số 50 nV là của chip trong điều kiện thử của hãng. Module thật với nguồn, dây, load cell của bạn thường nhiễu hơn. Chương trình dưới đây đọc HX711 trực tiếp, không cần thư viện. Nó lấy 160 mẫu khi load cell không tải rồi in nhiễu RMS (số đếm và nV), số mức không nhiễu, gam trên mỗi mức; in cả kết quả khi lấy trung bình 8 mẫu.
Nối dây như bài làm cân điện tử với ESP32, HX711 và load cell: module HX711 VCC vào 3V3, GND vào GND, DT vào GPIO32, SCK vào GPIO33; load cell vào E+, E−, A−, A+. Đo điện áp giữa E+ và E− bằng đồng hồ rồi sửa DIEN_AP_KICH; sửa tải định mức và độ nhạy theo load cell.
// PYWORLD.VN - Đo nhiễu thật của module HX711 + load cell khi không tải: RMS (số đếm, nV), số mức không nhiễu, gam/mức
// PYWORLD.VN - Đọc HX711 trực tiếp (25 xung = kênh A, gain 128), không cần thư viện; DT = GPIO32, SCK = GPIO33, VCC = 3V3
// PYWORLD.VN - Đo điện áp giữa E+ và E- bằng đồng hồ rồi sửa DIEN_AP_KICH; sửa TAI_DINH_MUC_G, DO_NHAY_MV_V theo load cell
const int CHAN_DT = 32;
const int CHAN_SCK = 33;
const float DIEN_AP_KICH = 3.0f; // V, điện áp đo được giữa E+ và E- của module (cũng là AVDD của HX711)
const float TAI_DINH_MUC_G = 5000.0f; // tải định mức của load cell, gam
const float DO_NHAY_MV_V = 1.0f; // độ nhạy load cell, mV/V (ghi trên nhãn hoặc tài liệu)
const int SO_MAU = 160; // 16 giây ở 10 SPS
const int NHOM = 8; // lấy trung bình mỗi 8 mẫu liên tiếp để so sánh
const float HE_SO_DINH_DINH = 6.6f;
int32_t mau[SO_MAU];
portMUX_TYPE khoaHx = portMUX_INITIALIZER_UNLOCKED;
bool choSanSang(uint32_t choMs) {
uint32_t batDau = millis();
while (digitalRead(CHAN_DT) == HIGH) { // DT xuống mức thấp khi có số liệu mới
if (millis() - batDau > choMs) return false;
delay(1);
}
return true;
}
int32_t docTho() {
uint32_t giaTri = 0;
// Mỗi xung SCK đẩy ra một bit, bit cao trước; SCK giữ mức cao quá 60 us thì HX711 tắt nguồn,
// nên khóa ngắt trong lúc phát 25 xung (khoảng 50 us)
portENTER_CRITICAL(&khoaHx);
for (int i = 0; i < 24; i++) {
digitalWrite(CHAN_SCK, HIGH);
delayMicroseconds(1);
giaTri = (giaTri << 1) | (uint32_t)digitalRead(CHAN_DT);
digitalWrite(CHAN_SCK, LOW);
delayMicroseconds(1);
}
digitalWrite(CHAN_SCK, HIGH); // xung thứ 25: lần đọc sau là kênh A, gain 128
delayMicroseconds(1);
digitalWrite(CHAN_SCK, LOW);
portEXIT_CRITICAL(&khoaHx);
if (giaTri & 0x800000) giaTri |= 0xFF000000; // số bù hai 24 bit sang 32 bit
return (int32_t)giaTri;
}
void thongKe(const double *x, int n, double &tb, double &doLech, double &nhoNhat, double &lonNhat) {
double tong = 0;
nhoNhat = x[0];
lonNhat = x[0];
for (int i = 0; i < n; i++) {
tong += x[i];
if (x[i] < nhoNhat) nhoNhat = x[i];
if (x[i] > lonNhat) lonNhat = x[i];
}
tb = tong / n;
double tongBp = 0;
for (int i = 0; i < n; i++) tongBp += (x[i] - tb) * (x[i] - tb);
doLech = sqrt(tongBp / (n - 1));
}
void inKetQua(const char *ten, double doLech, double nhoNhat, double lonNhat, double nvMoiDem, double demToanTai) {
double soMuc = demToanTai / (HE_SO_DINH_DINH * doLech);
Serial.printf("%s: RMS %.1f dem = %.0f nV, max-min %.0f dem\n", ten, doLech, doLech * nvMoiDem, lonNhat - nhoNhat);
Serial.printf(" muc khong nhieu tren toan tai: %.0f (%.1f bit), %.2f g/muc\n",
soMuc, log2(soMuc), TAI_DINH_MUC_G / soMuc);
}
void setup() {
Serial.begin(115200);
pinMode(CHAN_DT, INPUT);
pinMode(CHAN_SCK, OUTPUT);
digitalWrite(CHAN_SCK, LOW);
delay(500);
Serial.println("Do nhieu HX711: de load cell khong tai, khong cham vao mach.");
}
void loop() {
// 1 LSB = AVDD / (128 x 2^24); tín hiệu toàn tải tính theo số đếm không phụ thuộc điện áp kích
const double nvMoiDem = DIEN_AP_KICH * 1e9 / (128.0 * 16777216.0);
const double demToanTai = DO_NHAY_MV_V * 1e-3 * 128.0 * 16777216.0;
if (!choSanSang(1000)) {
Serial.println("Khong thay HX711: kiem tra VCC, GND, DT, SCK.");
delay(2000);
return;
}
docTho(); // bỏ lần đọc đầu (còn theo cấu hình cũ)
uint32_t batDau = millis();
for (int i = 0; i < SO_MAU; i++) {
if (!choSanSang(1000)) {
Serial.println("Mat tin hieu HX711 giua chung.");
return;
}
mau[i] = docTho();
}
float tocDo = SO_MAU * 1000.0f / (millis() - batDau);
static double x[SO_MAU];
static double tbNhom[SO_MAU / NHOM];
for (int i = 0; i < SO_MAU; i++) x[i] = mau[i];
for (int k = 0; k < SO_MAU / NHOM; k++) {
double tong = 0;
for (int j = 0; j < NHOM; j++) tong += x[k * NHOM + j];
tbNhom[k] = tong / NHOM;
}
double tb, doLech, nhoNhat, lonNhat;
thongKe(x, SO_MAU, tb, doLech, nhoNhat, lonNhat);
Serial.printf("\n%d mau, toc do %.1f SPS, trung binh %.0f dem, 1 dem = %.2f nV\n", SO_MAU, tocDo, tb, nvMoiDem);
inKetQua("Tung mau", doLech, nhoNhat, lonNhat, nvMoiDem, demToanTai);
thongKe(tbNhom, SO_MAU / NHOM, tb, doLech, nhoNhat, lonNhat);
inKetQua("Trung binh 8 mau", doLech, nhoNhat, lonNhat, nvMoiDem, demToanTai);
delay(3000);
}
- Mỗi xung SCK đẩy ra một bit, bit cao trước; xung thứ 25 chọn kênh A, gain 128 cho lần đọc sau. Nếu SCK giữ mức cao quá 60 µs, HX711 tắt nguồn, nên đoạn phát xung khóa ngắt trong khoảng 50 µs.
- Một bước (LSB) = điện áp kích ÷ (128 × 224). Tín hiệu toàn tải tính theo số đếm thì không phụ thuộc điện áp kích, nhờ đo tỉ lệ.
- Đa số module HX711 để chân RATE ở mức thấp, chạy 10 SPS, nên 160 mẫu mất khoảng 16 giây.

Lấy trung bình 8 mẫu làm nhiễu RMS giảm khoảng √8 ≈ 2,8 lần, đổi lại mỗi giây chỉ còn hơn một số đọc. Cân cần số ổn định thì lấy trung bình; cần bắt lực thay đổi nhanh thì không. Cách hiệu chuẩn hệ số của cân có ở bài thực hành hiệu chuẩn load cell với HX711 và ESP32.
6. Giảm nhiễu trước khi đổi chip
- Dây load cell ngắn, xoắn từng cặp (E+ với E−, A+ với A−); dây dài thì dùng cáp có lưới chống nhiễu, nối lưới về GND ở một đầu.
- Cấp nguồn sạch: tránh cấp chung với relay, động cơ; thêm tụ lọc gần module.
- Đặt module xa phần Wi-Fi, ăng-ten và bộ nguồn xung.
- Dùng tốc độ thấp nhất đủ cho việc đo (10 SPS) và lấy trung bình.
- Đo lại nhiễu bằng chương trình ở mục 5 sau mỗi thay đổi để biết thay đổi nào có tác dụng.
Nếu nhiễu đo được đã gần mức của chip (khoảng 50–90 nV với HX711) mà vẫn chưa đủ, lúc đó mới cần chuyển sang ADS1232 hoặc load cell nhạy hơn.
7. Lỗi hay gặp
| Hiện tượng | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
| Số đọc nhảy vài trăm đếm dù không tải | Dây dài, không xoắn; nguồn nhiễu; chạy 80 SPS | Xử lý theo mục 6, đo lại nhiễu |
| Số đọc trôi chậm một chiều | Load cell, module nóng lên sau khi cấp nguồn; keo gauge rão | Chờ ổn định vài phút; che gió, nắng; trừ điểm 0 định kỳ |
| Luôn đọc 8388607 hoặc −8388608 | Tín hiệu vượt thang (nối sai dây, load cell hỏng) | Kiểm tra màu dây, đo điện trở các cặp dây load cell |
| Đọc ra 0 hoặc −1 liên tục | Sai chân DT, SCK; module không có nguồn | Kiểm tra dây, đo VCC và E+ |
| Dùng ADS1256 cho cân mà kết quả kém HX711 | Gain tối đa 64, tham chiếu thấp, tín hiệu chỉ dùng vài phần trăm thang | Dùng HX711 hoặc ADS1232 cho cân |
8. Xem thêm
- Làm cân điện tử với ESP32, HX711 và load cell
- Cảm biến lực (load cell): cấu tạo, nguyên lý, cách chế tạo
- Đo điện áp 16 bit bằng module ADS1115 với ESP32
- Các nguồn sai số khi đo lực bằng load cell
Bài cùng cụm chủ đề: độ phân giải ADC, ENOB và bit không nhiễu; cầu Wheatstone và cách mắc strain gauge.
Xem thêm