我有一个简单的电路设置,通过LDR读取光线电平到Arduino。我正在尝试实现一个简单的低通过滤器来读取数据。考虑到analogRead()返回一个未签名的int,如何最好地解决这个问题。
我试图实现一个简单的不动点表示,但我不确定这是否正确的方法。
下面是一个代码片段:
#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微芯片。
发布于 2016-08-12 16:14:23
在没有FPU的设备上,而不是乘以0.1 (在任何情况下,这都是浮点而不是定点实现),您应该除以10:
#define WLPF_DIV 10
...
ldr_val += (newval - ldr_val) / WLPF_DIV;
然而,8位处理器上的分割通常是昂贵的(尽管与循环中Serial.println()
的执行时间相比可能相形见绌--但这是一个不同的问题)。相反,更有效的方法是选择一个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);
}
在每个样本的基础上,数据的非确定性输出不会产生一个非常精确的滤波器,而且在任何情况下都会控制时间,所以你对频率响应几乎没有控制,而且它不会稳定。它还使以前的性能优化变得毫无意义。如果这在您的应用程序中很重要,您可能会想一想--但这是一个不同的问题。
发布于 2016-08-12 23:15:18
我是从哈尔·钱伯林( Hal Chamberlin )的“微处理器的音乐应用”(Musical Application of微处理器)一书中引证的,第438页:
如果你在累加器中允许大数,那么你可以用一个乘法和一些右移做一个一阶低通滤波器。
out = accum >> k
accum = accum - out + in
选择“k”以改变截止频率。移位越多,低通截止值越低,但累加器中的值越大.使用analog_read()中的10位值,您可以很容易地右移4个位置,并且在累加器中仍然有2位的空间(如上面所述的@datafiddler )。
Cypress有一些类似方程的PSOC芯片的应用程序说明,并且使用移位。我记得有一个很好的表格,与截止频率相关的移位数。近似截止频率是采样频率除以增益因子2-pi:
f0 ~ fs / (2πa)
“a”是二的力量所在。
保持平滑的信号!
发布于 2016-08-12 13:49:00
坚持整数运算:
#define WLPF 9
filtered = ((long)filtered * WLPF + newValue) / (WLPF + 1);
https://stackoverflow.com/questions/38918530
复制相似问题