如何对基于Arduino的金属探测器进行编程

电子说

1.3w人已加入

描述

第1步:基本知识-时序,定时器,中断。..

一般思想

金属探测器的基本原理是,线圈中的电感/信号会随着目标靠近线圈而改变。识别这些变化的常用方法是测量频移,衰减时间,选定时间点的电压变化等。基于基于ATmega328的Arduino的功能,这些输入可以通过模拟读取,数字读取来测量

此指令将覆盖这些输入。

要获得灵敏的检测器,不幸的是,需要非常快速地测量输入。以16MHz运行的ATmega似乎运行得很快,但是在许多情况下,这太慢了,无法使用基于标准Arduino的例程。这里将提供各种“外部”标准Arduino例程“外部”的方法,以提供尽可能高的速度

定时,定时,定时。..。.

基于线圈的金属探测器的关键要素是时候了。通常,信号是快速的,因此时序上的小误差会导致读数错误,从而难以获得稳定的读数。

Arduino时序的标准功能是诸如millis(),micros(), delay()和delaymicroseconds()。在大多数应用中,这些例程运行良好,对于金属探测器设计,它们的性能并不理想。

根据参考,millis()的分辨率为4µS。另一方面,当查看Arduino的振荡频率时,两个脉冲在16MHz处的延迟为0,0625µS,是分辨率的64倍。为了获得对此16MHz频率的访问以进行计时,最方便的方法是使用ATmega内部定时器。共有三个计时器(timer0,timer1和timer2)。

可以通过预分频器(分频器)将定时器设置为以16MHz或更低的频率运行。定时器具有不同的模式,但在最简单的模式下,它们仅从0计数到给定值,触发中断服务程序(ISR –小程序)并重新开始计数。这种操作模式称为比较时清除定时器(CTC)。在计时器计数期间,在程序的任何时间都可以访问计数器值并将其复制到变量中。

在Arduino运行的大多数时间里都是如此。不幸的是,不仅定时器触发中断,而且许多其他事件也触发中断。因此,即使在执行ISR期间,中断也可能在相似的时间发生。由于每个中断都会延迟当前代码的执行,因此会再次导致较小的误读甚至完全丢失值。为了使ATmega本身具有某种顺序,可以对优先级不同的中断进行处理。

Vector Addr Source Interrupts definition

1 0x0000 RESET External Pin, Power-on Reset, Brown-out Reset and Watchdog System Reset

2 0x0002 INT0 External Interrupt Request 0

3 0x0004 INT1 External Interrupt Request 0

4 0x0006 PCINT0 Pin Change Interrupt Request 0

5 0x0008 PCINT1 Pin Change Interrupt Request 1

6 0x000A PCINT2 Pin Change Interrupt Request 2

7 0x000C WDT Watchdog Time-out Interrupt

8 0x000E TIMER2_COMPA Timer/Counter2 Compare Match A

9 0x0010 TIMER2_COMPB Timer/Coutner2 Compare Match B

10 0x0012 TIMER2_OVF Timer/Counter2 Overflow

11 0x0014 TIMER1_CAPT Timer/Counter1 Capture Event

12 0x0016 TIMER1_COMPA Timer/Counter1 Compare Match A

13 0x0018 TIMER1_COMPB Timer/Coutner1 Compare Match B

14 0x001A TIMER1_OVF Timer/Counter1 Overflow

15 0x001C TIMER0_COMPA Timer/Counter0 Compare Match A

16 0x001E TIMER0_COMPB Timer/Coutner0 Compare Match B

17 0x0020 TIMER0_OVF Timer/Counter0 Overflow

18 0x0022 SPI STC SPI Serial Transfer Complete

19 0x0024 USART_RX USART Rx Complete

20 0x0026 USART_UDRE USART Data Register Empty

21 0x0028 USART_TX USART Tx Complete

22 0x002A ADC ADC Conversion Complete

23 0x002C EE READY EEPROM Ready

24 0x002E ANALOG COMP Analog Comparator

25 0x0030 TWI 2-wire Serial Interface (I2C)

26 0x0032 SPM READY Store Program Memory Ready

稳定时序编程的目标是尽可能使用最高优先级的中断并找到解决方法防止在ISR期间发生其他中断。这导致了处理中断的第一条规则:减小ISR! (。.我将定期违反该规则……)。

由于标准Arduino函数也了解这些计时器,因此许多例程和库都在使用它们。这意味着,如果我们使用计时器并更改其预设值,则某些标准例程将不再起作用。

延迟,音调,传感器,步进,PWM功能以及通信可能不再正常工作。

步骤2:如何使用计时器

有关如何使用计时器的说明非常棒。我使用了这个Instructable的参考,这里只会给出一些摘要。如果您想更深入地了解计时器,请参阅说明“ Arduino计时器中断:6个步骤(带有图片)”。

基本原理是,首先您要告诉timer0,timer1或timer2

它应该运行多快(寄存器TCCR0B,TCCR2B,TCCR2B中的预分频器)

要计算的值(寄存器OCR0A,OCR1A,OCR2A中的值)

此时要执行的操作(TCCR0A,TCCR1A,TCCR2A中的设置-》重置并重新启动,触发PWM信号等…)

将计数器设置为一个给定的值(从TCNT0,TCNT1,TCNT2中的值开始)

启用相关中断(在TIMSK0,TIMSK1, TIMSK2)→这将调用ISR

,然后使用中断向量名称e定义ISR。 G。 ISR(TIMER0_COMPA_vect)并定义在此ISR期间应执行的操作。再次,此代码应保持简短,因为此代码可随时被其他中断打断,这将破坏值或导致程序崩溃。

另一部分信息是,计时器0和计时器2可以计数到255,计时器1可以计数到65535。

如何将计时器用于金属探测器

如前所述,计时对于金属探测器至关重要。因此,所有与时间相关的工作都将通过使用计时器来完成。

通常,向线圈提供一个初始脉冲。之后,测量线圈的反应(可以是相同的线圈,也可以是不同的线圈)。在测量期间,应避免所有其他中断!在这段时间内中断会导致值略有下降,值损坏,值丢失。

我将计时器用于3种不同目的:

timer0用于主事件(例如用于脉冲感应检测器的脉冲)

timer1用于数据采集(例如,频移检测,模数转换的时序)

timer2用于音调/音量生成。

timer0

脉冲感应(PI)检测器的主周期包括两个阶段。首先是为线圈供电的脉冲,然后是进行数据采集的静音状态。对于PI检测器,脉冲的正常持续时间约为250µS,脉冲后的静音应足以进行数据采集,处理和更新输出。

因此,首先要一方面将timer0设置为所需的“事件速度”,以提供接近250µS的脉冲和可用的静音时间。对于200Hz PI检测器,这将是1024的预分频器(aka每周期16MHz/1024 = 15.625kHz→64µS),脉冲的比较计数器为“ 4”,而静默计数器为“ 72”。

// set timer0 - taking care of the pulse to the coil and the pause between two pulses

// Pulse composes of 4.672ms “off” (72) and 0.32ms “on”(4)

// separate times for OCR0A will be set in the interrupt routine

// Resulting frequency is 200Hz

cli();

TCCR0A = 0; // set entire TCCR0A register to 0

TCCR0B = 0; // same for TCCR0B

TCNT0 = 0; // initialize counter value to 0

// set compare match register to required pulse with

OCR0A = 72; // = (4672µS/(0.0625µS * 1024)) - 1 (must be 《256)

// turn on Clear Timer on Compare (CTC) mode

TCCR0A |= (1 《《 WGM01);

// Set CS01 and CS00 bits for 1024 prescaler

TCCR0B |= (1 《《 CS02) | (1 《《 CS00);

// enable timer compare interrupt

TIMSK0 |= (1 《《 OCIE0A); sei();

由于信号非常不对称,因此可以在每次中断时将值设置为4和72。

ISR基本看起来像这样(带有全局易失性布尔“ toggle”):

ISR(TIMER0_COMPA_vect){

if(toggle){

cli();

OCR0A = 72; // set for a long “off-time” -》 next interrupt timer0 in 4.672ms

sei();

}

else{

cli();

OCR0A = 4; // set for a short “on-time” -》 next interrupt timer0 in 320µS

sei();

}

toggle=!toggle;

}

当然,在if和else期间,还有一些其他代码可用于

timer1

由于timer1的计数器值最高(65535),因此可以用来高精度地测量“长”时间段。以最大速度(16MHz),超限之前的最长事件为4.1ms。如果事件的时间变化很小,则超限甚至可以忽略。然后,最大定时分辨率为0.0625µs!那就是后台timer1用于数据采集。

首先将计时器设置为最大速度

cli();

TCCR1A = 0; // set entire TCCR1A register to 0

TCCR1B = 0; // same for TCCR1B

TCNT1 = 0; // initialize counter value to 0

// set compare match register

OCR1A = timer1MaxValue; // just a value to start with -》 set to a long period

// to prevent unwanted interrupt interference

// turn on Clear Timer on Compare (CTC) mode

TCCR1B |= (1 《《 WGM12);

// Set CS10 no prescaler → running at full 16MHz

TCCR1B |= (1 《《 CS10);

// enable timer compare interrupt → calling the ISR

TIMSK1 |= (1 《《 OCIE1A);

sei();

要开始数据采集,timer0重新启动通过将timer1的计数器设置为0(TCNT1 = 0),在每个脉冲后将timer1设置为1。现在有两个数据采集选项:

等待事件发生,并通过读取计数器TCNT1,使用timer1查看事件发生的时间。

事件,例如在脉冲后的特定时间(使用timer1的预设比较值)读取模拟信号。

在第一种情况下,ISR(例如,模拟比较器或引脚变化)将读取TCNT1,例如g。

ISR(ANALOG_COMP_vect){ // for analog comparator @ pin D6 and D7

Toggles[toggleCounter]=TCNT1;

toggleCounter++;

}

在第二种情况下,比较值OCR1A设置为将调用timer1比较ISR并执行模拟读取的值。在ISR期间,可以将比较值OCR1A更改为新值,以重复执行模数转换(ADC)周期e。 g。

ISR(TIMER1_COMPA_vect){

cli();

OCR1A = timer1PauseValue; // set the next interrupt to the value where a

// next ADC will give a usefull value

ADCSRA |= (1 《《 ADSC); // ADSC -》 start the cycle -》 will be cleared

// after the conversion is done

sei();

}

timer2

指示目标的最简单方法是通过声音。在寻找宝藏时,您的眼睛通常会集中在寻找地点。因此,它们不能用于显示或LED。使用声音可以轻松地在观看线圈并同时收听时找到确切的位置。为了给目标提供精确度和某种感觉,音调应根据信号强度改变其音量。因此,我认为应该为扬声器实现音量调制。

这只是通过向扬声器发送32kHz PWM信号来完成的。标准扬声器应缓慢将32kHz信号转换为音调。 32kHz被“解释”,而不是模拟值。通过提供一个“开”脉冲由32kHz信号组成的可听频率,该信号具有不同的PWM比例,可改变可听音的音量。

Timer2的优点是它是硬连线的D3和D11引脚的PWM功能

可以通过设置寄存器TCCR2A的COM2x1(COM2A1和COM2B1)中的位将这些引脚激活为PWM。寄存器/输出A为D11,寄存器/输出B为D3。通过将“如何表现”设置为“快速PWM”,可以将PWM设置为直接驱动引脚,而无需任何ISR!比较值OCR2A设置PWM比(OCR2A = 0→0%正波→0V; OCR2A = 255→100%正波→5V)。

OCR2A,因此PWM比可以设置为任意值在代码中放置以更改音量。

现在有了音量,我们仍然需要创建可听到的声音/频率。可以使用其ISR中的其他定时器之一来完成此操作(例如,在启动脉冲时将OCR2A设置为“ volume”,将OCR2A = 0设置为“ 0”;在启动脉冲时会产生200Hz的可听音)。

第3步:使用数字和模拟引脚

Arduino

模拟读取,但速度非常快。

通过使用AnalogRead()可以以良好的精度和稳定性读取10Bit值。不幸的是,最大采样率约为10kHz。这部分归因于AnalogRead()中的一些附加代码,部分归因于AD转换的时钟预分频器。

幸运的是,可指导的“ Girino-快速Arduino示波器”提供了有关如何进行获得更高的采样率。只需取消相关行的注释,就可以设置模数转换(ADC)速度的采样。

// ADCSRA |= (1 《《 ADPS2) | (1 《《 ADPS0); // 32 prescaler for 38.5 KHz

ADCSRA |= (1 《《 ADPS2); // 16 prescaler for 76.9 KHz

// ADCSRA |= (1 《《 ADPS1) | (1 《《 ADPS0); // 8 prescaler for 153.8 KHz

要使用ADC,有以下三种基本方法:

自由运行模式–每次转换完成后都会调用特定的ISR。

带中断的单次转换–转换完成后将调用ISR

单次转换和“延迟”直到值可用(例如在analogRead()中)

尽管自由运行模式在给定时间内可以最终获得最多样本,但我不建议使用用于金属检测。为什么?由于中断的优先级较低(请参见中断优先级表):优先级为22!前几个读数很可能会精确计时。此后,其他中断将开始产生干扰并稍微延迟ADC。

所以我真正建议使用的是通过带有中断的单次转换触发的timer1触发测量。

因此,timer1的每个ISR只需设置

ADCSRA |= (1 《《 ADSC);

大约270个(理论上为208个)xtal周期即可触发ADC,“ I-am-finish-with-the-Analog”到数字转换” ISR(ADC_vect)被调用,并且可以读取该值。在16位预分频器上,只能使用10位分辨率中的8位,这是因为较低的两个字节不会在高速下给出精确值(请参见数据手册)。

因为ADC将在在硬件级别上具有背景知识,因此此时间范围可用于在ISR期间执行一些命令以存储值和限制测量值的数量等

对于某些应用,信号的动态范围与预期会产生什么样的效果(在一次AD转换中看到完整的波形)。但这似乎并不是一个大问题。这很可能是由于ATmega芯片内部的内部采样和保持电路工作得很好!

数字端口的严重位限制

在某些情况下,必须设置Arduino。这可以通过使用digitalWrite()轻松完成。仍然,这些函数还有一些额外的开销代码,这使它们变慢,因此需要相当长的周期。

由于某些输出在ISR期间发生了变化,因此对端口进行位冲击是一个更好的选择

基本功能是:

PORTD = PORTD | B00000100;//将D2设置为高电平,而不更改其他端口

PORTD = PORTD&B11111011;//将D2设置为低电平而不更改其他端口

PIND = PIND | B00000100;//如果从低到高切换D2;如果为高→低

端口分配按位分配

PORTD D7 D6 D5 D4 D3 D2 D1(TXD) D0(RXD)

PORTB N.A. N.A. D13 D12 D11 D10 D9 D8

PORTC A0 A1 A2 A3 A4 A5 Reset N.A.

为线圈供电,应将端口直接设置为“高”或“低” ,对于音频输出,PIND命令(切换)提供了一个简洁的功能:多音目标识别。

如果在一个循环中有两个点切换了扬声器的引脚,我们可以决定是否根据目标,在两个点或仅在一个点之间切换销钉。这样,我们可以达到高音(每个周期200Hz的两个切换)或低音(每个周期100Hz的一个切换)。

使用引脚进行测试

尤其是在测试新开发的电路和新代码非常有用,它可以驱动一个额外的引脚来指示代码中正在发生的事情。它既可以用来触发示波器,也可以用来显示代码中某个例程花费多长时间。通过在例程开始时将引脚设置为高电平,然后在例程之后将其设置为低电平,可以很好地看出这段代码是否干扰了中断或其他信号,或者可以比较哪个版本的代码更快或更慢。

附带的是模拟信号(红色)和表示AD转换需要多长时间的引脚信号的图片。

步骤4:接口和数据输出

Arduino

Arduino

Arduino

探测器的主要目标是找到一个目标,然后向用户提供有关目标的一些信息。指示目标的一种方式是使用扬声器,如计时器2所述。

串行输出。

这主要用于测试和实验,但是它是无与伦比的!

Serial.print ()和兄弟姐妹的速度非常快(如果设置为Serial.begin(115200))。因此,在尝试电路并获得读数的感觉时,Serial.print()可用于向计算机发送大量数据。如果以适当的方式格式化(例如,值之间的“空格”,循环之间的“返回”)格式,则可以通过将串行监视器的输出复制到Excel或类似的电子表格程序中来传输大量数据,以便以后进行分析(我正在使用Libre Office )。

我广泛使用了此功能,一个探测器项目将合并使用此功能,以便在以后的某个时间将数据打印到16x2 LCD。

16x2 LCD

我认为这是提供有关目标的其他信息的好方法。这可以合并信号强度,但同时提供菜单以浏览电位设置(灵敏度,自动平衡,功率,辨别力)。

有两种简单的方法来驱动典型的16x2 LCD

使用I2C背包传输4位直接接线

我知道,也有UART封装,但是它们不像I2C背包那样常见。

直接接线是直接的,但是很麻烦,因为要使用许多端口,并且需要一些接线。因此,使用16x2显示屏的最简单方法是使用I2C背包。应该!!不幸的是,这里有一些真实的话题:

它似乎在启动过程中正在使用timer0

I2C的运行速度非常慢!

1。。在lcd.begin(16,2)期间;显然,使用了delay()和同级(很有趣,仅在那里)。这意味着,如果我们希望像其他操作一样将这些计时器用于其他目的,则必须在设置计时器之前调用lcd.begin(16,2)(这花了我一点时间。..)。 LCD上的其他呼叫不使用计时器,或者至少可以在修改的计时器设置下使用。

2。 最大的缺点绝对是I2C的速度。在将所有信息发送到LCD的第一个实现过程中,LCD根本不显示任何内容,而只是挂起。我意识到发送包括lcd.clear()在内的32个字符太多了,所以我减少了2个字符的数量。但是,即使发送两个字符也要花费大约3毫秒,这太长了。

在200Hz的工作频率下,该周期包括一个300µs的脉冲和4.7ms的静音。这种“静音”用于数据采集(在我的情况下约为2.5毫秒),数据处理(在我的情况下为1毫秒)为LCD输出留出约1.1毫秒的时间。实验表明,即使是一个字符也要花费1.5毫秒。为了解决此问题,例程Wire.setClock(400000)非常有用。

通常,默认情况下,I2C时钟设置为100kHz。通过使用Wire.setClock(400000),可以将时钟设置为400kHz。在wire.begin()中设置了默认的100kHz。在lcd.begin()期间调用此例程。因此必须在lcd.begin()之后调用Wire.setClock(400000)(…。再受挫几个小时)。

仍然必须解决该问题,即每个字符只能发送一个字符周期。为了解决这个问题,创建了一个数组(总共16x2个字符→总共32个字符),并填充了所有需要显示的信息。然后,该数组每个周期读取一个字符并发送到显示器。

3。。默认情况下,I2C连接到A4和A5。在测试过程中显而易见的是,驱动I2C总线正在使模拟引脚上的电压不稳定(可能是所有引脚的电压都不稳定,但是模拟引脚对ADC的影响非常敏感)。这就导致了时序问题,所有I2C传输都应与任何敏感的ADC周期完全隔离。

因此,通过将I2C时钟速度设置为400kHz并在每个周期内仅发送一个字符来进行通信。在将下一个脉冲发送到线圈之前,可以将LCD缩小为完成状态。因此,在脉冲后从引脚A0开始数据采集时,A4和A5相当不错。因此,LCD的刷新率是200Hz/32个字符→6.25Hz

声音

如有关计时器的步骤中所述,timer2用于为显示器产生动态声音输出。探测器。一个简单的扬声器通过100欧姆电阻连接到端口A(D11)或端口B(D3),可以使您感觉到信号强度和目标类型(如果使用了多音目标识别)。要实现此目的,代码并不是很好,但是可以完成工作。

在代码中的两个位置,由timer0计时,例如在dataCrunching之后,有两个由布尔“声音”驱动的代码片段和“音高”

if((sound)&&(highPitch)) { // if the speaker should sound and at high pitch

if(OCR2A)

OCR2A=0;

else

OCR2A=volume;

} if(sound){ // if the speaker should make noise

if(OCR2A)

OCR2A=0;

else

OCR2A=volume;

}

else

OCR2A=0;

设置布尔值“ sound”和“ pitch”应该在代码中的其他位置进行,因为它们不是时间紧迫的。

将变量“ volume”作为无符号字符或字节进行相同计数。

LED

由于通常在所有Arduino板上D13处都存在一个LED LED垫片,通过上一步中所述的位敲击来设置销钉。在测试过程中,这给人留下了很好的印象,供以后使用,该引脚可用于驱动外部LED。

附有来自不同时序的屏幕截图,可通过外部引脚看到。可以看到初始脉冲,而不是一些剧烈的振荡。在振荡期间,进行数据采集。数据采集后,数据处理开始(黄色信号设置为高电平)。在所有数据处理和数据传输结束时,黄色信号设置为低电平。图片显示了以下持续时间的差异:

仅数据处理

数据处理,并通过Serial.print()发送50个值

数据处理并通过I2C @ 100kHz将仅1个字符发送到LCD

步骤5:数据处理

Arduino

Arduino

Arduino

Arduino

在理想情况下,接收到的信号将非常清晰,与参考值相比最小的变化将指示目标。不幸的是,这个世界并不理想,并且金属探测器中的信号嘈杂,肮脏(尤其是当我使用很少的外部组件时)。为了过滤掉数据的相关部分,尝试了一些方法来过滤接收到的数据,甚至找到了一些有用的方法。

优化的第一要点是电路!

如果电路和线圈是报废的,那么最好的滤波算法就无法为您提供帮助。

有一些通用规则,例如使用大型电容器。屏蔽电路也将有所帮助,应进行试验。

真正使事情变得困难的一件事是信号尖峰。如果信号高于5V或低于0V,则有一些内部电路可防止ATmega爆炸。仅当电流保持较低时,此方法才有效。为了保护起见,这很好用,对于程序的稳定性而言,则不是那么好。

已经处理了相当“肮脏”的信号,代码产生了错误,ADC周期出现了严重的误读。这可能会导致读数延迟小,读数遗漏或无法解释的原始值。优化电路是获得合格数据的第一步。

这里需要对电路进行一些单独的测试。方法是:

使连接保持短路

使用电容器稳定电源电压

防止Arduino引脚上的大负载

防止靠近敏感零件/连接的大电流

防止组件加热/冷却

屏蔽组件,连接,完整电路

双绞线

防止移动/丢失电线

知道自己在做什么……。.

如何解释测量值

最简单的方法识别被测信号变化的方法是将信号与信号的参考值进行比较。如果测量值不同于该参考值,则您发现了一些东西。

事实证明与众不同。

实际上,您的读数会有很小的偏差。

这些偏差是由于来自主电网的50/60Hz信号的信号干扰,屏蔽不良的设备(交流适配器,计算机)的高频信号,对其他无线信号(Wifi,GSM)的干扰引起的或仅仅是由于电路设计而产生的振荡。这些干扰可能会偏离参考值,幅度如此之大,以致使检测器的灵敏度变得毫无用处。

在接下来的部分中,将介绍用于处理噪声信号的不同方法。

创建平均值

在最后几个值上创建平均值是一个不错的起点。这样,可以消除正负方向上的小偏差。根据噪声的大小和外观,可以选择不同的值来创建平均值。

最简单的方法是定义一个数组,然后将获得的值填充到该数组中,并按每个周期递增。在每个循环中,您将所有值相加,然后将它们与参考值进行比较。小偏差将被消除。这可以通过全局计数器轻松实现,全局计数器每个周期递增一次。如果达到“ maxValue”,则将其设置为0。

每个循环,for循环从0到“ maxValue”计数以求和。可以将总和除以maxValue确实没有意义。参考值可以直接与总和进行比较。

优点:易于实现,快速代码,易于阅读。

缺点:延迟小(可能无关紧要) ),仅适用于噪音很小

#define maxValue 20

int valueCounter=0;

int valueSamples[maxValue];

void dataCrunching(void ){

int i;

double average;

valueSamples[valueCounter]=nextValue;

valueCounter++;

if(valueCounter》(maxValue-1))

valueCounter=0;

average=0;

for(i=0; i average=average+valueSamples[i];

}

观察边界

如果信号太不稳定,则可以采用其他方法查看最小值或最大值在一个数组中。如果信号倾向于在一个方向上特别偏离,这将特别有用。再次使用数组,每个周期填充一个值。每个循环检查数组的e。 G。最低值。然后将该值与参考值进行比较。

创建e。 G。可以通过给变量一个高的值开始(例如255),然后在for循环中检查给定的数组值是否较小

if(minValueminValue=array[i];

循环minValue具有数组的最小值。

优点:即使嘈杂的信号也可以很好地分析

缺点:数组需要足够大(可以减慢反应速度)速度)

#define maxValue 20

int valueCounter=0;

int valueSamples[maxValue];

void dataCrunching(void){

int i;

int minValue;

valueSamples[valueCounter]=nextValue;

valueCounter++;

if(valueCounter》(maxValue-1))

valueCounter=0;

minValue=32767;

for(i=0;i if(minValueminValue=valueSamples[i];

}

}

忽略值

有时信号中会出现一些毛刺或明显的误读。如果将这些值添加到数组,它们将完全破坏对数组的分析。为了避免这种情况,可以通过设置“期望边界”来滤除“不可信”的值。最简单的方法是查看数组中的平均值,并且仅接受平均值的+/-范围内的值。我不建议这样做!为什么不?可能导致平均值“卡住”的情况。如果读数缓慢上升,然后突然回到“正常”,则平均值将停留在较高的值,因为正常值可能超出+/-范围。

如果相对于数组中的参考值信号不在+/-范围内,则旧值将保留在数组中。这将导致在相同范围内充满值的数组。优点:将阵列的平均值与参考值进行比较将对微小的变化非常敏感。

优点:过滤掉毛刺和严重误读的好方法,即使在噪声信号中也可以检测到很小的变化。

缺点:足够的值需要在该范围内,参考值和范围应谨慎选择。

#define maxValue 20

int valueCounter=0;

int valueSamples[maxValue];

int limit=10;

int referenceValue; // needs to be created somewhere in the program!!!

void dataCrunching(void){

int i;

int diff;

diff=abs(nextValue-referenceValue) // create the difference

if(diff valueSamples[valueCounter]=nextValue;

valueCounter++;

if(valueCounter》(maxValue-1))

valueCounter=0;

average=0;

for(i=0;i average=average+valueSamples[i];

}

平均单个值

一种滤除小偏差的方法是使用前一个值乘以一个因子,再加上新值,然后除以(factor + 1)。

优点:非常易于实现,滤除细小的噪声

缺点:即使细微变化是持久的,也忽略不了细微变化(新值需要偏差超过“因素”以影响除法后的结果)

#define maxValue 20

int valueCounter=0;

int valueSamples[maxValue];

int factor=3;

void dataCrunching(void){

int i;

double filterValue;

filterValue=valueSamples[valueCounter];

filterValue=filterValue*factor;

filterValue=filterValue+nextValue;

valueSamples[valueCounter]=filterValue/(factor+1);

valueCounter++;

if(valueCounter》(maxValue-1))

valueCounter=0;

average=0;

for(i=0; i average=average+valueSamples[i];

}

使用较高/较低的计数器

在这种情况下,会将测量值与先前创建的平均值进行比较。如果该值较大,则平均值diffdiffer将增加。如果该值较小,则diffCounter将减少。如果diffCounter达到最大值maxDiffCounter,则将平均值增加,并将diffCounter设置为0。如果diffCounter降至0以下,则将减小平均值,并将diffCounter设置为maxDiffCounter。

优点:甚至

缺点:高/低的分布应该“稳定”,偏差的指示可能很慢

#define maxDiffCounter 30

int diffCounter=0;

int referenceValue; // needs to be created elsewhere in the program!!!

int sampleValue;

int average=0;

void dataCrunching(void){

int i;

double average;

if(nextValue》referenceValue) // if larger than the reference

diffCounter++; // increase the diffCounter

else if(nextValue diffCounter--; // decrease the difCounter

if(diffCounter》maxDiffCounter){ // maximum value reached

average++; // correct the average

diffCounter=0; // restart at 0

}

else if(diffCounter《0){ // minimum value reached

average--; // correct the average

diffCounter=maxDiffCounter; // restart at maximum value

}

}

创建参考值

数据处理中最简单的部分是采用不同的方法来滤除噪声。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分