Cảm biến đọc giá trị nhảy lung tung: đo nhiễu, sửa nguồn, mass, dây, chọn bộ lọc trung vị hay EMA

Cảm biến analog đọc nhảy lung tung trên ESP32, Arduino: đo nhiễu bằng độ lệch chuẩn, tìm nguyên nhân ở nguồn, mass, dây, Wi-Fi, sửa phần cứng, rồi chọn bộ lọc trung vị, trung bình, EMA.

Cảm biến analog cho số đọc nhảy lên xuống liên tục, thỉnh thoảng vọt lên một giá trị vô lý rồi trở lại, là lỗi rất hay gặp với ESP32, Arduino. Cách vội nhất là lấy trung bình trong code, nhưng nhiều khi nguyên nhân nằm ở nguồn, dây mass hay dây tín hiệu, và lọc sai loại còn làm số đọc tệ hơn. Bài này chỉ cách tìm nguyên nhân cảm biến đọc giá trị nhảy lung tung theo thứ tự: đo nhiễu bằng con số, sửa nguồn, mass, dây, rồi mới chọn bộ lọc phù hợp với loại nhiễu (trung vị, trung bình trượt, EMA). Có chương trình chẩn đoán cho ESP32 và mô phỏng so sánh các bộ lọc.

1. Đo nhiễu bằng con số trước khi sửa

"Nhảy nhiều" khó so sánh; độ lệch chuẩn của 500 lần đọc liên tiếp thì so được. Chương trình dưới đây có chế độ đo nhiễu (gõ n): in trung bình, độ lệch chuẩn, giá trị nhỏ nhất, lớn nhất và số gai. Đo một lần trước khi sửa, đo lại sau mỗi thay đổi; con số giảm là sửa đúng chỗ.

// PYWORLD.VN - Chẩn đoán cảm biến analog đọc nhảy trên ESP32: đo 500 mẫu, in trung bình, độ lệch chuẩn, nhỏ nhất, lớn nhất, số gai
// PYWORLD.VN - Đo trước và sau mỗi lần sửa (thêm tụ, đổi nguồn, đi lại dây mass) để biết sửa có tác dụng không
// PYWORLD.VN - Chế độ đọc thường: lọc trung vị 5 rồi EMA; báo lỗi khi số đọc dính sát 0 hoặc sát thang đo (đứt dây, chập)

const int CHAN = 34;                     // ADC1: dùng được cả khi Wi-Fi đang chạy (ADC2 thì không)
const int SO_MAU_DO = 500;
const float ALPHA = 0.2f;

int mau[SO_MAU_DO];

void doNhieu() {
  long tong = 0;
  int nho = 4095, lon = 0;
  for (int i = 0; i < SO_MAU_DO; i++) {
    mau[i] = analogRead(CHAN);
    tong += mau[i];
    nho = min(nho, mau[i]);
    lon = max(lon, mau[i]);
    delayMicroseconds(500);
  }
  float tb = (float)tong / SO_MAU_DO, ps = 0;
  for (int i = 0; i < SO_MAU_DO; i++) ps += (mau[i] - tb) * (mau[i] - tb);
  float lech = sqrtf(ps / (SO_MAU_DO - 1));
  int gai = 0;
  for (int i = 0; i < SO_MAU_DO; i++)
    if (fabsf(mau[i] - tb) > 5 * lech + 10) gai++;   // mẫu lệch quá 5 lần độ lệch chuẩn: coi là gai
  Serial.printf("TB %.1f, do lech chuan %.2f, nho nhat %d, lon nhat %d, gai %d / %d\n", tb, lech, nho, lon, gai, SO_MAU_DO);
}

float trungVi5(float moi) {
  static float vung[5] = {0};
  static int vt = 0, dem = 0;
  vung[vt] = moi;
  vt = (vt + 1) % 5;
  if (dem < 5) dem++;
  float s[5];
  memcpy(s, vung, sizeof(s));
  for (int i = 1; i < dem; i++)
    for (int j = i; j > 0 && s[j - 1] > s[j]; j--) {
      float t = s[j];
      s[j] = s[j - 1];
      s[j - 1] = t;
    }
  return s[dem / 2];
}

void setup() {
  Serial.begin(115200);
  analogSetPinAttenuation(CHAN, ADC_11db);
  Serial.println("Lenh: n = do nhieu 500 mau. Mac dinh in gia tri da loc moi 100 ms");
}

void loop() {
  if (Serial.available() && Serial.read() == 'n') doNhieu();
  static uint32_t lan = 0;
  static float loc = -1;
  if (millis() - lan < 100) return;
  lan = millis();
  int tho = analogRead(CHAN);
  float tv = trungVi5(tho);
  loc = (loc < 0) ? tv : loc + ALPHA * (tv - loc);       // EMA sau trung vị
  const char *canh = (tho < 10) ? " (sat 0: dut day? chap mass?)" : (tho > 4085) ? " (sat thang do: vuot dai, chap nguon?)" : "";
  Serial.printf("tho:%d,loc:%.1f%s\n", tho, loc, canh);
}
  • Nối tín hiệu cảm biến vào GPIO34 của ESP32 DevKit V1. GPIO32–39 thuộc ADC1, dùng được khi Wi-Fi đang chạy; các chân ADC2 không đọc được khi bật Wi-Fi.
  • Ở chế độ thường, chương trình in hai cột tho và loc cho Serial Plotter, và cảnh báo khi số đọc dính sát 0 (thường là đứt dây tín hiệu, chập mass) hoặc sát 4095 (vượt thang đo, chập nguồn).
  • Số đọc dao động vài đơn vị trên thang 4095 là bình thường với ADC của ESP32; độ lệch chuẩn vài chục đơn vị hoặc có nhiều gai mới cần sửa.

2. Tìm nguyên nhân

Bảng nguyên nhân cảm biến đọc nhảy: nguồn nhiễu USB mạch hạ áp, mass chung với tải lớn, dây tín hiệu dài không xoắn, trở kháng nguồn tín hiệu cao, Wi-Fi phát làm ADC2 hỏng, chân thả nổi dây lỏng; dấu hiệu và cách kiểm tra
Sửa nguồn gây nhiễu trước, lọc bằng code sau.
  • Nhảy theo nhịp tải (bơm, relay, LED công suất đóng là nhảy): dòng tải đi chung dây mass với cảm biến, làm điện áp mass của cảm biến lệch đi. Đi dây mass cảm biến riêng về chân GND của bo. Relay đóng làm mạch khởi động lại là cùng gốc: xem bài đóng relay làm ESP32 bị reset.
  • Đổi nguồn là đổi mức nhiễu (cổng USB máy tính, cục sạc rẻ, mạch hạ áp xung): đo bằng chế độ n với nguồn pin để so; thêm tụ lọc tại nguồn cảm biến.
  • Dây dài: dây tín hiệu như một ăng-ten nhận nhiễu 50 Hz từ dây điện. Rút ngắn, xoắn dây tín hiệu với dây GND.
  • Chạm tay vào dây thì số đổi: chân đang thả nổi hoặc dây lỏng.

3. Sửa phần cứng

Bảng sửa phần cứng giảm nhiễu: tụ 100 nF tại chân ADC, lọc RC 1 kΩ và 100 nF trước ADC, tụ 10 µF và 100 nF tại nguồn cảm biến, mass hình sao, dây xoắn và cáp chống nhiễu nối GND một đầu
Đo lại độ lệch chuẩn sau mỗi thay đổi.

Tụ 100 nF đặt sát chân ADC vừa lọc nhiễu tần số cao vừa giúp ADC lấy mẫu chính xác hơn khi nguồn tín hiệu có trở kháng lớn. Lọc RC 1 kΩ và 100 nF có tần số cắt khoảng 1,6 kHz: đủ nhanh cho cảm biến nhiệt độ, áp suất, độ ẩm, chặn được gai ngắn do Wi-Fi và tải đóng cắt.

4. Chọn bộ lọc theo loại nhiễu

Sau khi sửa phần cứng, phần nhiễu còn lại mới cần lọc bằng code. Script dưới đây so sánh bốn cách lọc trên một tín hiệu thử có nhiễu đều, 4 % mẫu là gai lớn và một bậc thay đổi thật:

# PYWORLD.VN - So sánh bộ lọc cho số đọc cảm biến bị nhảy: trung vị 5, trung bình trượt 10, EMA alpha 0,2, trung vị rồi EMA
# PYWORLD.VN - Tín hiệu thử có nhiễu ngẫu nhiên, gai (Wi-Fi phát, relay đóng) và một bậc thay đổi thật; đo sai số RMS và độ trễ theo bậc
# PYWORLD.VN - Chạy: python so_sanh_bo_loc.py
"""Sai số RMS tính trên đoạn ổn định; độ trễ = số mẫu từ lúc bậc xảy ra tới khi đi được 90 % độ cao bậc."""
from __future__ import annotations

import math
import random
from statistics import median

N, BAC_O, CAO = 300, 150, 200.0                     # bậc 1000 → 1200 ở mẫu 150


def tin_hieu(seed: int = 8):
    rnd = random.Random(seed)
    that, do = [], []
    for i in range(N):
        v = 1000.0 + (CAO if i >= BAC_O else 0)
        x = v + rnd.gauss(0, 8)
        if rnd.random() < 0.04:                     # 4 % mẫu là gai lớn
            x += rnd.choice([-1, 1]) * rnd.uniform(150, 400)
        that.append(v)
        do.append(x)
    return that, do


def trung_vi(x, n=5):
    return [median(x[max(0, i - n + 1):i + 1]) for i in range(len(x))]


def trung_binh(x, n=10):
    return [sum(x[max(0, i - n + 1):i + 1]) / len(x[max(0, i - n + 1):i + 1]) for i in range(len(x))]


def ema(x, a=0.2):
    y, ra = x[0], []
    for v in x:
        y += a * (v - y)
        ra.append(y)
    return ra


def danh_gia(that, y):
    on_dinh = [i for i in range(20, N) if not BAC_O <= i < BAC_O + 30]
    rms = math.sqrt(sum((y[i] - that[i]) ** 2 for i in on_dinh) / len(on_dinh))
    tre = next((i - BAC_O for i in range(BAC_O, N) if y[i] >= 1000 + 0.9 * CAO), None)
    return rms, tre


BO_LOC = {"Khong loc": lambda x: x, "Trung vi 5": trung_vi, "Trung binh 10": trung_binh, "EMA 0,2": ema,
          "Trung vi 5 + EMA 0,2": lambda x: ema(trung_vi(x))}


def main() -> None:
    that, do = tin_hieu()
    for ten, f in BO_LOC.items():
        rms, tre = danh_gia(that, f(do))
        print(f"{ten:22s} sai so RMS {rms:6.1f}   tre theo bac {tre} mau")


if __name__ == "__main__":
    main()
Đồ thị mẫu 100 đến 230: số đọc thô có nhiều gai, trung bình trượt 10 bị kéo theo gai, trung vị 5 bỏ được gai và theo kịp bậc 1000 lên 1200, trung vị 5 kèm EMA mượt nhất nhưng chậm hơn
Mô phỏng với tín hiệu thử.
Bảng kết quả bộ lọc: không lọc sai số RMS 50; trung vị 5 sai số 4,3 trễ 2 mẫu; trung bình trượt 10 sai số 19,1 trễ 7 mẫu; EMA 0,2 sai số 19,0 trễ 6 mẫu; trung vị 5 cộng EMA sai số 2,5 trễ 12 mẫu
Kết quả của script.
  • Có gai: trung vị 5 mẫu giảm sai số từ 50 xuống 4,3 và chỉ trễ 2 mẫu. Trung bình trượt và EMA không bỏ được gai mà trải gai ra nhiều mẫu (sai số còn khoảng 19).
  • Nhiễu đều (không gai): trung bình trượt hoặc EMA làm mượt tốt và tốn ít bộ nhớ.
  • Kết hợp: trung vị trước để bỏ gai, EMA sau để làm mượt; sai số nhỏ nhất (2,5) nhưng trễ nhiều nhất. Chương trình ESP32 ở mục 1 dùng cách này.

Lý thuyết và cách chọn hệ số cho từng bộ lọc có ở các bài cùng cụm về lọc trung bình trượt, lọc EMA và bộ lọc Kalman.

5. Cảm biến số cũng có thể đọc sai

  • Cảm biến giao tiếp số (DHT22, DS18B20, cảm biến I2C) không bị nhiễu kiểu analog, nhưng đọc lỗi sẽ cho giá trị vô lý (−127, 85, 0). Kiểm tra mã kiểm tra (checksum, CRC) và bỏ lần đọc lỗi thay vì đưa vào trung bình.
  • Dây I2C dài, kéo lên yếu làm đọc lúc được lúc không: xem bài cùng cụm về I2C không thấy thiết bị.

6. Bảng tra nhanh

Hiện tượngNguyên nhân thường gặpCách xử lý
Gai lớn từng lúcTải đóng cắt, Wi-Fi phátMass riêng, tụ, lọc trung vị
Gợn đều liên tụcNguồn nhiễu, nhiễu 50 Hz trên dây dàiTụ nguồn, dây xoắn, lọc trung bình
Số trôi chậm theo thời gianNhiệt độ, cảm biến chưa ổn địnhChờ ổn định, bù nhiệt; không phải nhiễu
Dính 0 hoặc dính tối đaĐứt dây, chập, vượt thang đoKiểm tra dây, cầu chia điện áp
Đọc được khi tắt Wi-Fi, hỏng khi bậtDùng chân ADC2Chuyển sang GPIO32–39

Bài cùng cụm chủ đề: lọc trung bình trượt; lọc EMA; bộ lọc Kalman một chiều; nhiễu 50 Hz và cách loại bỏ; vòng đất (ground loop); I2C không thấy thiết bị.


Tin tức liên quan

IC là gì: kiểu vỏ, đếm chân, đọc datasheet và tụ decoupling
IC là gì: kiểu vỏ, đếm chân, đọc datasheet và tụ decoupling

IC là gì, bên trong có gì, các kiểu vỏ DIP, SOIC, QFP, QFN và kiểu nào hàn tay được, cách tìm chân 1 và đếm chân, đọc mã in trên IC, các mục cần đọc trong datasheet và tụ decoupling 100 nF.

Giáo dục STEM là gì? STEM, STEAM khác nhau thế nào và vì sao giúp trẻ phát triển tư duy
Giáo dục STEM là gì? STEM, STEAM khác nhau thế nào và vì sao giúp trẻ phát triển tư duy

Giáo dục STEM là gì, gồm những lĩnh vực nào, STEM và STEAM khác nhau ra sao, giáo dục STEM ở trường phổ thông Việt Nam và cách bắt đầu STEM với trẻ ở nhà theo độ tuổi.

Độ không đảm bảo đo theo GUM: sáu bước tính, loại A, loại B và ví dụ ngân sách hiệu chuẩn áp kế
Độ không đảm bảo đo theo GUM: sáu bước tính, loại A, loại B và ví dụ ngân sách hiệu chuẩn áp kế

Cách tính độ không đảm bảo đo theo GUM: sáu bước, đánh giá loại A và loại B, phân bố chuẩn, chữ nhật, tam giác, ví dụ ngân sách hiệu chuẩn áp kế, cách làm tròn và lỗi hay gặp.

ESP32, ESP32-S3, C3, C6 khác nhau thế nào: so sánh thông số, chọn chip theo việc, lưu ý chuyển code
ESP32, ESP32-S3, C3, C6 khác nhau thế nào: so sánh thông số, chọn chip theo việc, lưu ý chuyển code

So sánh ESP32, ESP32-S3, ESP32-C3, ESP32-C6 về nhân CPU, Wi-Fi, Bluetooth, USB, chân, DAC; gợi ý chọn chip theo việc, những điểm dễ vấp khi chuyển code, chương trình in thông tin chip.

Mạch điều khiển bay của flycam: IMU, vòng PID, bộ lọc rung, chế độ bay, Betaflight, ArduPilot
Mạch điều khiển bay của flycam: IMU, vòng PID, bộ lọc rung, chế độ bay, Betaflight, ArduPilot

Mạch điều khiển bay của flycam: MCU, IMU, cảm biến, vòng điều khiển nhiều tầng, bộ trộn động cơ, PID, bộ lọc rung, các chế độ bay, Betaflight, INAV, ArduPilot, PX4 và khả năng dùng ESP32.

Jack DC 5.5×2.1 và 5.5×2.5: phân biệt, cực tính, chọn jack gắn hộp và dây nguồn DC
Jack DC 5.5×2.1 và 5.5×2.5: phân biệt, cực tính, chọn jack gắn hộp và dây nguồn DC

Jack DC 5.5×2.1 và 5.5×2.5 khác nhau ở lỗ trong: cách phân biệt khi không có nhãn, ký hiệu tâm dương, đo cực tính adapter, lắp jack gắn hộp và nối nguồn vào mạch an toàn.

Biến trở, quang trở và nhiệt điện trở NTC: cách dùng và đọc bằng ESP32
Biến trở, quang trở và nhiệt điện trở NTC: cách dùng và đọc bằng ESP32

Cấu tạo và cách mắc biến trở, biến trở B10K và A10K, quang trở LDR, nhiệt điện trở NTC, PTC, phương trình B, thí nghiệm đọc biến trở, LDR, NTC bằng ADC của ESP32 và chỉnh độ sáng LED.

Cảm biến quang: thu phát, phản xạ gương, khuếch tán, triệt nền – cấu tạo, nguyên lý và chế tạo
Cảm biến quang: thu phát, phản xạ gương, khuếch tán, triệt nền – cấu tạo, nguyên lý và chế tạo

Cảm biến quang hoạt động thế nào: bốn kiểu thu phát, phản xạ gương, khuếch tán, triệt nền; điều chế ánh sáng chống nhiễu, cấu tạo, cách chế tạo và làm cảm biến phản xạ có điều chế với ESP32.


Bình luận
Đã thêm vào giỏ hàng