定点简单低通滤波器
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' 是二的幂。
保持信号顺畅!
我有一个简单的电路设置,可以通过 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' 是二的幂。
保持信号顺畅!