• HOME
  • DỰ ÁN & MẠCH ĐIỆN
    • Lập trình
      • ARDUINO PROJECT
      • ESP8266 PROJECT
      • ESP32 PROJECT
      • RASPBERRY PI PROJECT
      • Vi điều khiển
    • Điện tử ứng dụng
      • Audio / Amplifiers
      • Nguồn điện
      • Pin sạc/Acquy và mạch sạc
      • Biến đổi AC và DC
      • Robotic
      • Cảm biến
      • LED
      • LCD
      • Động cơ bước
      • Mạch linh tinh
      • Test & Measurement
      • RF – FM
    • Nixie Clock
    • HOME AUTOMATION
    • Dân dụng
    • Công nghiệp
  • KIẾN THỨC CĂN BẢN
    • Điện tử cơ bản
    • Điện tử số
    • PCB
    • Nixie Tube
    • Raspberry Pi
    • Vi điều khiển
    • Arduino
    • IN 3D
  • DOWNLOAD
    • Phần mềm điện tử
    • Giáo trình
      • Giáo trình Điện – Điện tử
      • Giáo trình Tự Động Hóa
      • Giáo trình Viễn thông
    • Đề tài
      • Đề tài – Điện – Điện Tử
      • Đề tài – Tự Động Hóa
      • Đề tài – Viễn thông
    • Điện tử ứng dụng
    • Tài liệu nước ngoài
    • Hướng dẫn, sửa chữa
    • Sơ đồ, nguyên lý thiết bị
    • Tiêu chuẩn – Đo lường – Thử nghiệm
    • Datasheet
  • LIÊN HỆ
  • SẢN PHẨM

Mạch Điện Lý Thú

Sơ đồ nguyên lý, PCB, đồ án, tài liệu, DIY

Trang chủ » Kiến thức căn bản » Arduino » Chống nhiễu cho cảm biến bằng bộ lọc Kalman

Chống nhiễu cho cảm biến bằng bộ lọc Kalman

27/05/2025 by admin Để lại bình luận

Đã được đăng vào 05/04/2021 @ 14:04

Rõ ràng khi ta sử dụng cảm biến, giá trị trả về từ chúng luôn thay đổi quanh vị trí cân bằng dù là rất nhỏ và bạn biết nguyên nhân của hiện tượng này là do nhiễu, bạn luôn muốn loại bỏ nhiễu nhưng việc đó dường như ngoài tầm với của bạn…

Đừng lo, chúng ta đã có giải pháp, bấm đọc bài viết này thôi nào!

Xem thêm:

  • Đo cuộn cảm và tần số cộng hưởng mạch LC bằng Arduino
  • Hack hệ thống CAN bus trên ô tô
  • Cách tính giá trị tụ mắc song song, mắc nối tiếp

1. Nhiễu …

Mục lục hiện
1. Nhiễu …
2. Loại bỏ nhiễu bằng thuật toán lọc Kalman
Sử dụng bộ lọc Kalman
Code
Ghép tầng các bộ lọc ta thu được kết quả chính xác hơn, tất nhiên nó sẽ đáp ứng trễ hơn 1 tầng
Kết quả cho 4 tầng lọc
3. Ma thuật hay trí tuệ nhân tạo ?!!
4. Thuật toán Kalman trong C
5. Thư viện lọc Kalman đơn giản
Test thư viện với la bàn số
Test thực tế với cảm biến áp suất
Test thư viện với cảm biến gia tốc
6. Tạm kết
Một vài thư viện hay về bộ lọc Kalman
Tổng hợp toàn bộ tài liệu offline

Trong đo lường, do các yếu tố cả chủ quan lẫn khách quan mà kết quả đo đạc luôn chỉ được coi là tương đối so với giá trị thực cần đo.

Độ chênh lệch giữa giá trị đo được với giá trị thực được gọi là Sai số.

Sai số gây ra bởi nhiễu, chúng được phân làm 2 loại chính:

  • Sai số hệ thống
  • Sai số ngẫu nhiên

Giả sử ta tiến hành đo giá trị điện áp của một cục pin năng lượng mặt trời, kết quả thu được trên đồ thị như sau :

Giả sử giá trị điện áp thực của pin mặt trời là u0=1.9545 vol.

Do ảnh hưởng bởi nhiều yếu tố trong quá trình đo đạc, sai số xuất hiện là điều rất khó tránh khỏi

Kết quả là ta chỉ có thể đo giá trị điện áp bằng cách lấy tương đối kết quả trung bình các phép đo

Tức u0=u ± ∆ e =1.95 ± 1%,  (với ∆ e là sai số)

2. Loại bỏ nhiễu bằng thuật toán lọc Kalman

Phương pháp này được đề suất năm 1960 bởi nhà khoa học có tên Kalman.

Để chứng minh hiệu quả của phương pháp này chúng ta sẽ có một phép thử mô phỏng như sau:

float u0 = 100.0; // giá trị thực (không đổi)
float e; // nhiễu
float u; // giá trị đo được (có thêm nhiễu)
void setup()
{
    Serial.begin(9600);
}
void loop()
{
    randomSeed(millis());
    e = (float)random(-100, 100);
    u = u0 + e;
    Serial.println(u);
}

Nạp code cho arduino rồi sau đó mở cổng Serial Plotter để xem dưới dạng đồ thị:

Kết quả hiển thị trên Serial plotter:

Xem lại code bên trên ta thấy có vài điều như sau:

Gọi u0=100.0 là giá trị thực tế của vật thể, cũng là giá trị mà ta mong muốn thu được, vì u0 là hằng số (nếu như không có nhiễu).

Lý tưởng thì trên đồ thị ta sẽ thu được một đường thẳng song song với trục thời gian t.

Thường thì nhiễu chỉ dao động trong khoảng e=±10% giá trị thực đã được coi là rất nhiễu rồi (noise).

Để tăng độ khó, mình đã cố ý cho e=±100% u0 bằng hàm Random khiến cho giá trị đo bị nhiễu hoàn toàn và gần như rất khó để thu thập lẫn tính toán sau này.

Sử dụng bộ lọc Kalman

Như đã thống nhất, trong thực tế u0 là giá trị chúng ta không biết, việc sử dụng bộ lọc sẽ phải giúp ta loại bỏ các nhiễu, khi đó giá trị đo được phải gần đường u0=100 hơn .

Vì đây là mô phỏng nên giá trị u0 cần được cho trước (chỉ mình và bạn biết) để có thể kiểm chứng tính đúng đắn của kết quả trước và sau khi lọc (bằng cách trộn u0 với nhiễu rồi cho arduino lọc)

So với code bên trên, phần code này chỉ cần thêm một dòng lệnh duy nhất:

Gọi u_kalman  là giá trị đo đã qua bộ lọc Kalman:

u_kalman=bo_loc.updateEstimate(u);

Code

#include <SimpleKalmanFilter.h>
SimpleKalmanFilter bo_loc(2, 2, 0.001);
 
float u0 = 100.0; // giá trị thực (không đổi)
float e; // nhiễu
float u; // giá trị đo được (có thêm nhiễu)
float u_kalman; // giá được lọc nhiễu
void setup()
{
    Serial.begin(9600);
}
void loop()
{
    randomSeed(millis());
    e = (float)random(-100, 100);
    u = u0 + e;
    Serial.print(u);
    Serial.print(",");
    u_kalman = bo_loc.updateEstimate(u);
    Serial.print(u_kalman);
    Serial.println();
}

Và đây là kết quả khi sử dụng thêm bộ lọc:

Đường màu xanh: u.

Đường màu vàng: u_kalman.

Dừng lại một chút để quan sát đồ thị, hẳn bạn cũng đồng ý với mình thuật toán lọc Kalman tỏ ra rất hiệu quả, có những lúc nhiễu dồn ra biên cực đại (±100%u0) nhưng giá trị vẫn khá sát đường u0.

Ghép tầng các bộ lọc ta thu được kết quả chính xác hơn, tất nhiên nó sẽ đáp ứng trễ hơn 1 tầng

#include <SimpleKalmanFilter.h>
SimpleKalmanFilter bo_loc(2, 2, 0.001);
 
float u0 = 100.0; // giá trị thực (không đổi)
float e; // nhiễu
float u; // giá trị đo được (có thêm nhiễu)
float u_kalman; // giá được lọc nhiễu
void setup()
{
    Serial.begin(9600);
}
void loop()
{
    randomSeed(millis());
    e = (float)random(-100, 100);
    u = u0 + e;
    Serial.print(u);
    Serial.print(",");
    u_kalman = bo_loc.updateEstimate(u); // tầng 1
    u_kalman = bo_loc.updateEstimate(u_kalman); // tầng 2
    u_kalman = bo_loc.updateEstimate(u_kalman); // tầng 3
    u_kalman = bo_loc.updateEstimate(u_kalman); // tầng 4
    Serial.print(u_kalman);
    Serial.println();
}

Kết quả cho 4 tầng lọc

Bạn nào còn nghi ngờ kết quả giống mình thì copy code rồi test lại dùm mình nhé, thật khó tin phải không!

3. Ma thuật hay trí tuệ nhân tạo ?!!

Đây là thành quả nghiên cứu có từ 56 năm trước, chủ nhân của thuật toán là ông Rudolf (Rudy) E. Kálmán  

Bộ lọc này được sử dụng rộng rãi trong các máy tính kỹ thuật số của các hệ thống điều khiển, Hệ thống định vị, Hệ thống điện tử, RADA, vệ tinh dẫn đường để lọc nhiễu.

Bộ lọc hoạt động ổn định đến mức, nó đã được sử dụng trong chương trình Apollo, tàu con thoi của NASA, tàu ngầm Hải quân và xe tự hành không gian không người lái và vũ khí, tên lửa hành trình.

Với thành công đó, ông cũng đã nhận được rất nhiều giải thưởng lớn khác nhau.

4. Thuật toán Kalman trong C

Đây là thuật toán đơn giản mình tìm được, hãy vào địa chỉ bên dưới để hiểu được ý nghĩa của các tham số K (Kalman Gian) ,P,Q,R….

https://malcolmmielle.wordpress.com/2015/04/29/kalman-filter/

/*
* Need to tweak value of sensor and process noise
* arguments :
* process noise covariance
* measurement noise covariance
* estimation error covariance */
 
class Kalman_Filter_Distance {
protected:
    double _q; //process noise covariance
    double _q_init;
    double _r; //measurement noise covariance
    double _r_init;
    double _x; //value
    double _p; //estimation error covariance
    double _p_init;
    double _k; //kalman gain
 
public:
    /*
* Need to tweak value of sensor and process noise
* arguments :
* process noise covariance
* measurement noise covariance
* estimation error covariance */
    Kalman_Filter_Distance(double q, double r, double p)
        : _q(q)
        , _q_init(q)
        , _r(r)
        , _r_init(0)
        , _x(0)
        , _p(p)
        , _p_init(p)
        , _k(_p / (_p + _r)){};
    virtual void init(double x) { _x = x; }
    virtual void setProcessNoiseCovariance(double i)
    {
        _q = i;
        _q_init = i;
    }
    virtual void setMeasurementNoiseCovariance(double i)
    {
        _r = i;
        _r_init = i;
    }
    virtual void setEstimatiomErrorCovariance(double i)
    {
        _p = i;
        _p_init = i;
    }
    virtual double kalmanUpdate(double measurement);
    virtual void reset()
    {
        _q = _q_init;
        _r = _r_init;
        _p = _p_init;
    };
};
 
double Kalman_Filter_Distance::kalmanUpdate(double measurement)
{
    //prediction update
    //omit _x = _x
    _p = _p + _q;
 
    //measurement update
    _k = _p / (_p + _r);
    _x = _x + _k * (measurement - _x);
    _p = (1 - _k) * _p;
 
    return _x;
}

5. Thư viện lọc Kalman đơn giản

Của tác giả denyssene : SimpleKalmanFilter

Link dự phòng : https://drive.google.com/file/d/0BzM…

Test thư viện với la bàn số

#include <Wire.h>
#include <HMC5883L.h>
 
#include <SimpleKalmanFilter.h>
SimpleKalmanFilter pressureKalmanFilter(1, 1, 0.001);
HMC5883L compass;
 
float x_kalman;
void checkSettings()
{
}
void setup()
{
    Serial.begin(9600);
 
    // Initialize HMC5883L
    Serial.println("Initialize HMC5883L");
    while (!compass.begin()) {
        Serial.println("Could not find a valid HMC5883L sensor, check wiring!");
        delay(500);
    }
 
    compass.setRange(HMC5883L_RANGE_1_3GA);
    compass.setMeasurementMode(HMC5883L_CONTINOUS);
    compass.setDataRate(HMC5883L_DATARATE_15HZ);
    compass.setSamples(HMC5883L_SAMPLES_1);
 
    // Check settings
    checkSettings();
}
 
void loop()
{
    Vector raw = compass.readRaw();
    Vector norm = compass.readNormalize();
    x_kalman = pressureKalmanFilter.updateEstimate(raw.XAxis);
    Serial.print(raw.XAxis);
    Serial.print(",");
 
    Serial.print(x_kalman, 3);
    Serial.println();
    delay(100);
}

Test thực tế với cảm biến áp suất

#include <SimpleKalmanFilter.h>
#include <SFE_BMP180.h>
 
/*
 This sample code demonstrates how the SimpleKalmanFilter object can be used with a
 pressure sensor to smooth a set of altitude measurements. 
 This example needs a BMP180 barometric sensor as a data source.
 https://www.sparkfun.com/products/11824
 
 SimpleKalmanFilter(e_mea, e_est, q);
 e_mea: Measurement Uncertainty 
 e_est: Estimation Uncertainty 
 q: Process Noise
 */
SimpleKalmanFilter pressureKalmanFilter(1, 1, 0.01);
 
SFE_BMP180 pressure;
 
// Serial output refresh time
const long SERIAL_REFRESH_TIME = 100;
long refresh_time;
 
float baseline; // baseline pressure
 
double getPressure()
{
    char status;
    double T, P;
    status = pressure.startTemperature();
    if (status != 0) {
        delay(status);
        status = pressure.getTemperature(T);
        if (status != 0) {
            status = pressure.startPressure(3);
            if (status != 0) {
                delay(status);
                status = pressure.getPressure(P, T);
                if (status != 0) {
                    return (P);
                }
            }
        }
    }
}
 
void setup()
{
 
    Serial.begin(115200);
 
    // BMP180 Pressure sensor start
    if (!pressure.begin()) {
        Serial.println("BMP180 Pressure Sensor Error!");
        while (1)
            ; // Pause forever.
    }
    baseline = getPressure();
}
 
void loop()
{
 
    float p = getPressure();
    float altitude = pressure.altitude(p, baseline);
    float estimated_altitude = pressureKalmanFilter.updateEstimate(altitude);
 
    if (millis() > refresh_time) {
        Serial.print(altitude, 6);
        Serial.print(",");
        Serial.print(estimated_altitude, 6);
        Serial.println();
        refresh_time = millis() + SERIAL_REFRESH_TIME;
    }
}

Test thư viện với cảm biến gia tốc

#include <SimpleKalmanFilter.h>
#include <Wire.h>
#include "I2C.h"
#define RESTRICT_PITCH
SimpleKalmanFilter simpleKalmanFilter(1, 1, 0.001);
 
uint8_t i2cData[14]; // Buffer for I2C data
 
int16_t accX;
float accX_kalman;
 
void setup()
{
    Serial.begin(9600);
    Wire.begin();
#if ARDUINO >= 157
    Wire.setClock(400000UL); // Set I2C frequency to 400kHz
#else
    TWBR = ((F_CPU / 400000UL) - 16) / 2; // Set I2C frequency to 400kHz
#endif
 
    i2cData[0] = 7; // Set the sample rate to 1000Hz - 8kHz/(7+1) = 1000Hz
    i2cData[1] = 0x00; // Disable FSYNC and set 260 Hz Acc filtering, 256 Hz Gyro filtering, 8 KHz sampling
    i2cData[2] = 0x00; // Set Gyro Full Scale Range to ±250deg/s
    i2cData[3] = 0x03; // Set Accelerometer Full Scale Range to ±16g
    while (i2cWrite(0x19, i2cData, 4, false))
        ; // Write to all four registers at once
    while (i2cWrite2(0x6B, 0x01, true))
        ; // PLL with X axis gyroscope reference and disable sleep mode
}
void loop()
{
 
    while (i2cRead(0x3B, i2cData, 14)) {
        ;
    }
    accX = (int16_t)((i2cData[0] << 8) | i2cData[1]);
    accX_kalman = simpleKalmanFilter.updateEstimate((float)accX);
    Serial.print(accX);
    Serial.print(",");
    Serial.print(accX_kalman, 3);
    Serial.println();
}

6. Tạm kết

Ngay từ đầu mình đã bị lôi cuốn về khả năng của bộ lọc Kalman. Rất muốn hiểu nó  để chủ động áp dụng vào các trường hợp khác.

Tuy nhiên, ở trình độ hiện tại của mình, việc tiếp cận và hiểu về bộ lọc này còn rất mơ hồ nên việc đặc tả các thuật ngữ cùng lời giải thích thấu đáo về phương trình và cách thiết lập là rất khó (thậm chí với cả bạn).

Mình không thể bình luận hoặc viết được gì hơn ngoài chia sẻ những trang Web để tự học về bộ lọc Kalman mà mình đã sưu tầm trong suốt thời gian qua:

Một vài thư viện hay về bộ lọc Kalman

Của tác giả bachagas  :https://github.com/bachagas/Kalman…

Link dự phòng : https://drive.google.com/file/d/0BzM…

Thư viện lọc nhiễu do cảm biến góc PMU6050 :

Của tác giả  Lauszus : https://github.com/TKJElectronics/Ka…

Link dự phòng :https://drive.google.com/file/d/0BzM…

Thư viện Kalman đơn giản :

Của tác giả denyssene :  https://github.com/denyssene/SimpleK…

Link dự phòng : https://drive.google.com/file/d/0BzM…

Tổng hợp toàn bộ tài liệu offline

Nhấn để tải

https://drive.google.com/file/d/0BzM…

http://www.bzarg.com/p/how-a-kalman-…

Hy vọng trong tương lai, ai đó trong chúng ta sẽ quay trở lại để chia sẻ kiến thức của mình về cách tiếp cận bộ lọc Kalman, để bạn đọc có cơ hội học hỏi nhiều hơn nữa.

Mong bài viết này có ích cho mọi người ^^

Nguồn: arduino.vn

  • Share on Facebook
  • Tweet on Twitter

Thuộc chủ đề:Arduino, Vi điều khiển Tag với:arduino, bộ lọc Kalman, cảm biến, chống nhiễu, Kalman Filter

Bài viết trước « Nguyên tắc lắp mạch điện dùng nẹp và ống luồn dây điện
Bài viết sau Mạch sạc acquy 3 giai đoạn từ nguồn ATX »

Reader Interactions

Để lại một bình luận Hủy

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *

Sidebar chính

Zalo hỏi đáp 24/7

Theo dõi qua mạng xã hội

  • Facebook

Bạn đang tìm gì?

Bài viết mới nhất

Các loại nguồn xung thông dụng và nguyên lý hoạt động

Các loại nguồn xung thông dụng và nguyên lý hoạt động

15/06/2025

Relay điện tử - Sử dụng TRIAC để đóng cắt thiết bị

Relay điện tử – Sử dụng TRIAC để đóng cắt thiết bị

15/06/2025

Mạch ổn áp cố định, IC ổn áp

Mạch ổn áp cố định, IC ổn áp

15/06/2025

Giám sát nhiệt độ, độ ẩm (DHT11) thông qua Thingspeak bằng NodeMCU ESP8266

15/06/2025

Phát hiện mưa (Rain Sensor) sử dụng NodeMCU ESP8266

15/06/2025

Danh mục

  • DỰ ÁN & MẠCH ĐIỆN (241)
    • Công nghiệp (16)
    • Dân dụng (29)
    • Điện tử ứng dụng (178)
      • Audio / Amplifiers (34)
      • Biến đổi AC và DC (24)
      • Cảm biến (40)
      • Động cơ bước (5)
      • Kiểm thử và đo đạc (23)
      • LCD (15)
      • LED (20)
      • Mạch linh tinh (27)
      • Nguồn điện (42)
      • Pin sạc/Acquy và mạch sạc (24)
      • RF – FM (5)
      • Robotic (2)
    • HOME AUTOMATION (23)
    • Lập trình (82)
      • ARDUINO PROJECT (39)
      • ESP32 PROJECT (6)
      • ESP8266 PROJECT (17)
      • RASPBERRY PI PROJECT (9)
      • Vi điều khiển (24)
    • Nixie Clock (3)
  • Kiến thức căn bản (170)
    • Arduino (36)
    • Điện tử cơ bản (77)
    • Điện tử số (9)
    • IN 3D (9)
    • Nixie Tube (13)
    • PCB (18)
    • Raspberry Pi (10)
    • Vi điều khiển (16)

Footer

Bài viết mới nhất

  • Các loại nguồn xung thông dụng và nguyên lý hoạt động
  • Relay điện tử – Sử dụng TRIAC để đóng cắt thiết bị
  • Mạch ổn áp cố định, IC ổn áp
  • Giám sát nhiệt độ, độ ẩm (DHT11) thông qua Thingspeak bằng NodeMCU ESP8266
  • Phát hiện mưa (Rain Sensor) sử dụng NodeMCU ESP8266
  • Điều khiển thiết bị thông qua Cayenne Mydevices và NodeMCU ESP8266

Bình luận mới nhất

  • admin trong Nguyên lý cảm biến siêu âm chống nước JSN-SR04T và sơ đồ mạch
  • Rohan trong Nguyên lý cảm biến siêu âm chống nước JSN-SR04T và sơ đồ mạch
  • Tên gì kệ tui trong Mạch Ampli 19W dùng IC LA4440
  • admin trong Đồng hồ số hiển thị trên LED 7 đoạn dùng 89S52 và DS1307

Tìm kiếm

Tất cả nội dung trên website chỉ dùng để tham khảo. Chúng tôi không chịu trách nhiệm về thông tin thành viên đăng tải lên website và xóa bài viết khi có vi phạm bản quyền tác giả.