How does this code snippet "low pass filter" ADC value? - Page 1
Forum section: electronics / general

author: MagicSmoker
author_rank: Super Contributor
timestamp: June 29, 2016, 03:20:28 pm
content: I am trying to understand a snippet of code that is supposed to reduce the effect of noise in ADC results:Code:// Save LP-filtered value.adc_res_lp -=(adc_res_lp + 0x20) >> 6;adc_res_lp += adc_res >> 6;Our programmer's explanation (which went right over my head) is that this adds hex 20 (decimal 32) to the old ADC reading, bit shifts that result 6 steps to the right to effectively divide by 64, then adds that result to the new ADC reading which has also been divided by 64.Try as I might, I just can't seem to figure out how this bit of mathematical sorcery averages 2 ADC readings together... Anyone care to explain?

author: tszaboo
author_rank: Super Contributor
timestamp: June 29, 2016, 03:24:19 pm
content: It is an IIR filter. You always divide the previous value with x and add it to the current one. So you have:S=S1 + S2/x+S3/x/x+S4/x/x/x and so on. Or something like that. I think you get the idea.

author: bktemp
author_rank: Super Contributor
timestamp: June 29, 2016, 03:29:53 pm
content: It replicates an analogue RC filter.The 0x20 is only for rounding, by adding 0.5 (32/64=0.5). If you don't care about this small rounding error, it can be omitted without affecting the filter function.

author: MagicSmoker
author_rank: Super Contributor
timestamp: June 29, 2016, 03:56:42 pm
content: I get it now; what I failed to see is that this function keeps operating infinitely. Pretty clever, though not exactly what I wanted. Thanks for the help!

author: mikerj
author_rank: Super Contributor
timestamp: June 29, 2016, 07:19:02 pm
content: Quote from: MagicSmoker on June 29, 2016, 03:20:28 pmThat appears to be a properly broken bit of code. I suppose it's possible you actually want to discard the lower 6 bits of the ADC value, but usualy you low pass filter an ADC result to reduce noise in the low order bits, not throw them away entirely.The usual format is something like this:Code:lp_acc = (lp_acc - lp_out) + new_sample;lp_out = lp_acc / x;Where x determines the cut off frequency of the filter.

author: dannyf
author_rank: Super Contributor
timestamp: June 29, 2016, 07:45:53 pm
content: Quoteexponential smoothing.It has infinite memory but weighs distant measurements less than recent measurements. So it is a little bit like a windowed average.The 0x20 is for rounding, as others have pointed out.This particular implementation isn't terribly efficient, and is problematic for measurements less than 64. There is a short discussion (me involved) here a while back talking about ways to get around that and proved a more efficient calculation.

author: macboy
author_rank: Super Contributor
timestamp: June 29, 2016, 09:18:02 pm
content: Quote from: dannyf on June 29, 2016, 07:45:53 pmHave you considered it is a 10 bit ADC, returning a left-justified value in a 16 bit register? Then it makes sense to shift right 6 bits to right justify the value.

author: Dielectric
author_rank: Regular Contributor
timestamp: June 30, 2016, 12:54:22 am
content: Quote from: macboy on June 29, 2016, 09:18:02 pmWhich architecture does that so I can avoid using it?Here's how I throw down a stupid IIR:result = (result + result_old*3)>>2;Actually, newer ADCs have the filtering built in so who cares anymore?

author: batteksystem
author_rank: Regular Contributor
timestamp: June 30, 2016, 02:43:36 am
content: Quote from: mikerj on June 29, 2016, 07:19:02 pmShifting is less accurate but much faster than multiplication and division.

author: AndersJ
author_rank: Frequent Contributor
timestamp: June 30, 2016, 05:59:47 am
content: QuoteNice simple filter, BUT small changes will be lost,regardless of the number of succeeding new entries.Example: result_old = 50. Next reading is 51.The new reading of 51 will be lost every time.I would like result_old to eventually become 51.How can that be accomplished?

author: bktemp
author_rank: Super Contributor
timestamp: June 30, 2016, 07:03:40 am
content: You need to keep more bits when dividing/right shifting. One easy way to do it is to multiply/left shift the ADC input values.This will reduce rounding errors and also increase the ADC resolution if there is enough noise (couple of LSBs) in the ADC data.

author: Jeroen3
author_rank: Super Contributor
timestamp: June 30, 2016, 08:26:52 am
content: It's an exponential averaging filter. The generic formula looks like this:y(k) = a * y(k-1) + (1-a) * x(k).Where a is the filter factor. Source is below.http://gregstanleyandassociates.com/whitepapers/FaultDiagnosis/Filtering/Exponential-Filter/exponential-filter.htmIt's a simple filter since you only need T-1 sample history and a multiplication. Compared to a FIR filter, which is better, but requires more memory and time to execute. Try it in excel.Left aligning ADC allows you to have full Q15 range (0.00f to 1.00f) and allows the filter to oversample and have the filtered output settle at a value that is not a direct step of the adc.A 2 bit adc left aligned to 4 bits would be able to have the codes [0, 4, 8, 12]. Yet, filtered the output of a noisy sequence (eg: 4,8, which is LSB noise) would be 6. Which is an output the ADC hardware can't do, but your filter can. Without left alignment you would have 1.5 which is an invalid integer.

author: Kalvin
author_rank: Super Contributor
timestamp: June 30, 2016, 08:36:37 am
content: If you have RAM to spare and want to make a windowed filter (FIR), just make a ring buffer and keep a separate running sum of the samples. When you get a new sample, pop the oldest sample from the buffer, subtract the value from the running sum, add the new sample to the running sum and push the new sample into the ring buffer and output the updated running sum as filtered value. Pretty simple and fast.

author: MagicSmoker
author_rank: Super Contributor
timestamp: June 30, 2016, 10:36:12 am
content: Quote from: Dielectric on June 30, 2016, 12:54:22 amAs I was just trying to understand a piece of code, and not trying to fix a problem, per se, I didn't include any application details, but in case it helps, signal filtering for noise and to prevent aliasing is done in hardware; the filtering in the code here is strictly to render some of those same process variables "human readable".The MCU is an atmega644p, so 8b, no division and yes, memory and CPU cycles are highly constrained. The ADC readings being averaged are parameters like switch duty cycle, supply voltage, output current, heatsink temp, etc. Some don't really require much averaging (e.g. - heatsink temp), some are all but rendered useless by averaging (e.g. - duty cycle) but they all get it anyway.

author: MagicSmoker
author_rank: Super Contributor
timestamp: June 30, 2016, 10:38:08 am
content: Quote from: dannyf on June 29, 2016, 07:45:53 pmI don't mind searching for the thread myself, but a keyword to search on besides your username would be appreciated. I tried "exponential", confined to this forum and your username, and only got this post as a result.

author: MagicSmoker
author_rank: Super Contributor
timestamp: June 30, 2016, 10:43:27 am
content: Quote from: Kalvin on June 30, 2016, 08:36:37 amThat is *exactly* how my non-programmer's brain envisioned doing this but the programmer said that required too much memory and instruction cycles. Let's just say that "feeping creaturism" has afflicted the poor 8b Atmel MCU in the product. We have since migrated to 32b ARM MCUs.

author: tszaboo
author_rank: Super Contributor
timestamp: June 30, 2016, 10:48:39 am
content: Quote from: MagicSmoker on June 30, 2016, 10:43:27 amIIR works well as written, only, you have less control over the impulse response, frequency response and other parameters. If it is not a metrology parameter, only for a simple actuator or self-check or anything like that, I would use it without even considering FIR. You just want to filter out the noise.Of course when I say IIR, because it is a fixed point calculation, it will be FIR, but that is beside the point.

author: danadak
author_rank: Super Contributor
timestamp: June 30, 2016, 10:51:18 am
content: Or use a processor, ARM Core, 20 bit DelSig, Digital Filter Block, DMA,reference, other analog (OpAmps.....) that handle the conversion and filterin HW and only SW burden is to get the answer......Cypress PSOChttp://www.cypress.com/products/32-bit-arm-cortex-m3-psoc-5lpRegards, Dana.

author: hamster_nz
author_rank: Super Contributor
timestamp: July 01, 2016, 12:57:31 am
content: Quote from: NANDBlog on June 30, 2016, 10:48:39 amAlso,* FIR filters can have latency that IIR filters don't (1/2th the kernel length),* Usually the phase delay of a IIR is all over the place, so if phase is important to you you may need to use a FIR* IIR filters can be unstable as you try to push the performance too far.

author: dannyf
author_rank: Super Contributor
timestamp: July 01, 2016, 09:57:07 am
content: QuoteThe basic idea is to keep track of the (weighted) sum:Code:adc_sum += (adc_current - (adc_sum / N));return (adc_sum / N);where (adc_sum / N) can be stored so it is calculated only once; and "/ N" can be done via shifts.1 / N is the weight you give to the current observation: Higher 1/ N means shorter "window" -> more distant observations are less influential on the output. It also provides noisier but faster-reacting output.