定点简单低通滤波器

Simple low pass filter in fixed point

我有一个简单的电路设置,可以通过 LDR 将光照水平读取到 Arduino 中。我正在尝试对读入的数据实施一个简单的低通滤波器。鉴于 analogRead() returns 一个无符号整数,如何最好地解决这个问题。

我已经尝试实现一个简单的定点表示,但不确定这是否是正确的方法。

这是一个代码片段:

#define WLPF 0.1
#define FIXED_SHIFT 4

ldr_val = ((int)analogRead(A0)) << FIXED_SHIFT;
 while (true) {
    int newval = (int)analogRead(A0) << FIXED_SHIFT;
    ldr_val += WLPF*(newval - ldr_val);
    Serial.println(ldr_val >> FIXED_SHIFT, DEC);
}

注意 ADC 的分辨率是 10 位,我正在使用 8 位 Arduino Micro。

坚持整数运算:

#define WLPF 9

filtered = ((long)filtered * WLPF + newValue) / (WLPF + 1);

在没有 FPU 的设备上,而不是乘以 0.1(这在任何情况下都是浮点而非定点实现),您应该除以 10:

#define WLPF_DIV 10

...

ldr_val += (newval - ldr_val) / WLPF_DIV;

然而,8 位处理器上的除法通常很昂贵(尽管可能与循环中 Serial.println() 的执行时间相形见绌 - 但这是一个不同的问题)。相反,select 2 的幂更有效,这样可以通过右移执行除法。

#define WLPF_SHIFT 3  // divide by 8

...

ldr_val += (newval - ldr_val) >> WLPF_SHIFT ;

使用有符号 int 是有问题的,因为有符号类型的右移是未定义的行为。在这种情况下,可以通过将代码更改为:

来解决此问题
#define WLPF_DIV 8

... 

ldr_val += (newval - ldr_val) / WLPF_DIV ;

编译器很可能会发现二次方常数,并在任何情况下使用算术右移生成代码。但是,您最好重新考虑数据类型。

您在 Serial.println() 调用中仍然有右移,但也可以用除以 16 代替:

#define WLPF_DIV 8
#define FIXED_MUL  16

ldr_val = (int)analogRead(A0) * FIXED_MUL  ;

for(;;)
{
    int newval = (int)analogRead(A0) * FIXED_MUL ;
    ldr_val += (newval - ldr_val) / WLPF_DIV 
    Serial.println(ldr_val / FIXED_MUL, DEC);
}

基于每个样本的非确定性数据输出不会产生非常准确的滤波器,并且在任何情况下都会支配时序,因此您几乎无法控制频率响应并且它不会稳定.这也使得之前的性能优化变得毫无意义。如果它在您的应用程序中很重要,您可能需要考虑一下 - 但这是一个不同的问题。

我从 Hal Chamberlin 的书 "Musical Applications of Microprocessors" 中解释,第 438 页:

如果您允许累加器中有大数,那么您可以制作一个具有一次乘法和一些右移的一阶低通滤波器。

  out = accum >> k
  accum = accum - out + in

选择'k'更改截止频率。移位越多,低通截止越低,但累加器中的值越大。使用来自 analog_read() 的 10 位值,您可以轻松地右移 4 个位置,并且在累加器中仍然有 2 位的余量(如上面提到的@datafiddler)。

赛普拉斯为其 PSOC 芯片提供了一些应用说明,其中包含类似的方程式并使用移位。我记得有一个很好的 table 将移位次数与截止频率相关联。 近似截止频率是采样频率除以 2-pi 乘以增益因子:

f0 ~ fs / (2 pi a)

其中 'a' 是二的幂。

保持信号顺畅!