嵌入式系统中用于减少电池电压显示波动的平滑函数

7
在嵌入式设备中读取电池电压。然而,实际电压会因系统负载的不同而变化很大。我们需要一种方法来减少电压波动以显示最佳值。
目前,我们正在使用滚动/移动平均值。然而,在过去的15次读数中,结果仍然波动太大。
在阅读有关平滑算法的文章时,似乎b样条、核滤波器或其他平滑算法是理想的选择。然而,我找不到一个简单的例子,它不使用numpy或mathcad内置函数之类的东西。
有人知道一个简单易实现的函数可以帮助解决这个问题吗?这是一个C++项目(使用Qt 4.5),只有最基本的库。我更喜欢保持在整数域内(以毫伏为单位显示电压,范围为3300-4200)。
谢谢! Mike
7个回答

7
嗯,如果没有您的具体情况,很难确定您需要什么。例如,您的传感器采样率是多少?您试图消除的传感器波动和噪声如何表现?
但是,如果您已经实现了移动平均值,我建议尝试使用移动中位数。 (最近 n 个样本的中位数,而不是平均值。)这将倾向于减少从正常输出中大的短期异常的影响。
如果您可以找到适合的参数,最好使用某种形式的离散时间低通滤波器以满足CPU和内存要求。这些非常容易实现,并且只需要知道先前的输出值和当前输入即可计算当前输出。例如:
Y = Y[n-1] + A * (X - Y[n-1])

(其中Y是当前输出,Y[n-1]是上一次计算的输出,而X是您最新的传感器读数。) A实际上是低通滤波器的时间常数,但它是离散时间的,因此它取决于采样率。具体来说,A = dt / tau,其中dt为您的采样周期(以秒为单位),而tau大致相当于连续时间的时间常数。

这很完美。快速,易于计算。花了一点时间来弄清楚我的系数,但效果很好。 - Mike Crowe
@Mike:像Matlab(以及其免费的克隆品FreeMat和Octave)这样的工具具有为特定带宽要求计算IIR滤波器系数的功能。虽然在这种情况下,只有一个系数,但经验法可能已经足够了(而且可能比学习Matlab更快)。您的移动平均方法是FIR滤波器的简单示例(所有系数均相等)。 - Clifford
谢谢,正是我要找的。这是我基于Arduino编写的函数://压缩器 int compreSSor(int signal,int lastSignal){int result = lastSignal + 0.2 * (signal - lastSignal); return result;} - Macumbaomuerte

2

1

要深入研究信号处理技术和复杂数学是完全可能的,但你必须问自己这是否真的有必要呢?

如果这个显示只是简单的瞬时数值输出,仅用于“指示”而不是连续的图形或数据记录(即不需要重建信号),那么通常可以接受以周期平均代替移动平均。由于不需要存储历史数据,你可以对任意数量的样本进行平均,具体取决于所需的显示更新频率。

虽然这并不“聪明”,但通常已经足够完成任务。下面是一个示例和其使用的测试模拟。

class cPeriodicMean
{
    public :
        cPeriodicMean( int period ) : m_mean(0), 
                                      m_period(period),
                                      m_count(0),
                                      m_sum(0)
        { 
            // empty
        }

        void addSample( int sample )
        {
            m_sum += sample ;
            m_count++ ;
            if( m_count == m_period )
            {
                m_mean = m_sum / m_period ;
                m_count = 0 ;
                m_sum = 0 ;
            }
        }

        int getMean() 
        { 
            return m_mean ; 
        }

    private :
        int m_mean ;
        int m_period ;
        int m_count ;
        int m_sum ;
} ;

// Test Simulation
#include <cstdlib>
#include <cstdio>
#include <windows.h>  // for Sleep to simulate sample rate
int main()
{
    // Average over 100 samples
    cPeriodicMean voltage_monitor( 100 ) ;

    for(;;)
    {
        // Simulate 4000mV +/- 50mV input
        int sample = 4000 + (std::rand() % 100) - 50 ;
        voltage_monitor.addSample( sample ) ;

        // Simulate 100Hz sample rate
        Sleep(10) ;

        // Current output
        int millivolts = voltage_monitor.getMean() ;
        printf( "\r%d millivolts    ", millivolts ) ;
    }
}

这种技术的改进可以产生更加平滑的输出,但是以相同的频率生成结果,方法是将周期均值输出作为移动平均滤波器的输入。如果您使用我的每秒100个样本的示例,并将其通过15个样本的移动平均值,您将使用15秒的采样数据,同时仍然每秒获得一个结果,而且内存使用量很小。

显然,您可以更改周期、移动平均长度和采样率,以获得所需的更新频率的结果。我建议您在需要更新的周期内尽可能多地采样,然后根据您的预算设置移动平均长度。


1

你考虑过对该值应用斜率限制吗?

new_val = Read_From_HW();
diff = new_val - prev_val;

if (diff > SKEW_LIMIT)
    diff = SKEW_LIMIT;
else if (diff < -SKEW_LIMIT)
    diff = -SKEW_LIMIT;

reported_val = prev_val + diff;
prev_val = reported_val;

0

这听起来像是硬件问题。它是Li-Io还是NiMH电池?放电曲线是什么样的?在电池单元和ADC之间有哪些组件?在实施各种数字滤波器之前,你需要了解这些信息。


0

我知道这并没有直接回答你的问题,但是平均柱状图会有帮助吗?换句话说,可以在15秒的时间窗口内显示最小值/最大值/平均值/中位数,而不仅仅是平均值。


-2

如果你找不到答案,这是一个很好的方法,在机器人上打印出一些东西,比如 pololu 3pi。

{
    int bat = read_battery_millivolts();

    clear();
    print_long(bat);
    print("mV");
    lcd_goto_xy(0,1);
    print("Press B");

    delay_ms(100);
}


请注意,当回答已有很多答案的旧问题时要小心,特别是当其中一个答案被接受时。您需要解释为什么您的答案比现有答案更好。 - APC
这个答案完全离题了。问题是如何过滤/平滑传感器读数并去除峰值… 而这个答案基本上只展示了一个int读数的循环打印… - Angry 84

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接