0

I have a simple circuit setup to read the light level via an LDR into an Arduino. I'm trying to implement a simple low pass filter to data read in. How best to tackle this given that analogRead() returns an unsigned int.

I have tried to implement a simple fixed point representation but am unsure if this is the correct approach.

Here's a code snippet:

#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);
}

Note the resolution of the ADC is 10 bits and I am working with an 8-bit Arduino Micro.

too honest for this site
  • 12,050
  • 4
  • 30
  • 52
CatsLoveJazz
  • 829
  • 1
  • 16
  • 35
  • 10 bits shifted by 4 yields 14 bits, that's still positive. Your low pass formula looks strange to me, though – datafiddler Aug 12 '16 at 13:45
  • I'm confident in the formula but not sure about my implementation of fixed point. – CatsLoveJazz Aug 12 '16 at 13:47
  • What's the ADC resolution? What values can `analogRead` return? If it returns an unsigned int, then why do you cast it to signed int? Overall, why do you use signed ints in an embedded system? That's just sloppy and bug-prone. Use the stdint.h types. If you shift data into the sign bits of a signed type, your program may halt and catch fire. – Lundin Aug 12 '16 at 13:50
  • 2
    Also, using floating point arithmetic on a sluggish 8-bit MCU is just nonsense. It is not a PC. I doubt it even got a FPU. You either have no need for float numbers (most likely) or you picked the wrong MCU for the task. – Lundin Aug 12 '16 at 13:52
  • @Lundin this is why I am looking for help on the implementation, I'm not sure of best practices for embedded systems. The ADC resolution is 10 bit and analogRead returns an unsigned int as is clearly stated in the question. – CatsLoveJazz Aug 12 '16 at 13:52
  • [There are many good answers to your question over at Electrical Engineering](http://electronics.stackexchange.com/questions/30370/fast-and-memory-efficient-moving-average-calculation). – Lundin Aug 12 '16 at 14:02
  • WLPF is a double precision floating point value, so this implementation is far from "fixed point". A correct filter will need accurate timing; here your timing is likely to be dominated by the time taken to execute `Serial.println()` which is likely to be variable - as will the performance of the filter therefore. – Clifford Aug 12 '16 at 15:42
  • Thanks, can you provide any pointers on fixed point filters for embedded systems? – CatsLoveJazz Aug 12 '16 at 15:43
  • Note the timing and frequency response requirements for the desired filter are very loose, I'm just looking to slow down the analog input so it takes a second or two to reach its baseline – CatsLoveJazz Aug 12 '16 at 15:44
  • And again: Arduino is not C! – too honest for this site Aug 12 '16 at 16:02
  • @Olaf I'm building my project using embedXcode where I am writing pure C code. – CatsLoveJazz Aug 12 '16 at 16:04
  • Isn't the arduino "language" essentially a library and some preprocessing in C++ anyway? – CatsLoveJazz Aug 12 '16 at 16:05
  • @CatsLoveJazz: Yes. But how is that related to the **different** language C? – too honest for this site Aug 12 '16 at 16:07
  • It isn't but the filter implementation I am planning to write will be in pure C, hence the tag – CatsLoveJazz Aug 12 '16 at 16:08
  • @CatsLoveJazz: The code above clearly is not C! It apparently is Arduino, or at least uses an Arduino library. Whatever, it does not compile with a C compiler! So either you use C only (including C compiler, remove the Arduino tag and stick with AVR, or you do have to use C++ resp. Arduino. – too honest for this site Aug 12 '16 at 16:10
  • Understood. thanks – CatsLoveJazz Aug 12 '16 at 16:12
  • 1
    @CatsLoveJazz : You can in many cases compile code written as C code using a C++ compiler; but it then becomes C++ code. As it happens this code will not compile as C because the use of the Serial object makes it C++ only. – Clifford Aug 12 '16 at 16:32
  • Okay, fully understood now! – CatsLoveJazz Aug 12 '16 at 16:34
  • 1
    To add to what @Clifford wrote: Even if you have code with identical syntax (thus it will compile as both languages), it may have different semantics (thus behave differently). There is enough to be found about the differences between C and C++. One simple example is the `const` qualifier. Others are more subtle. – too honest for this site Aug 12 '16 at 16:37

3 Answers3

4

I'm paraphrasing from the book "Musical Applications of Microprocessors" by Hal Chamberlin, page 438:

If you allow large numbers in the accumulator, then you can make a first-order low-pass filter with one multiplication and some right-shifts.

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

Choose 'k' to change the cutoff frequency. The more shifts, the lower the low-pass cutoff, but the larger the value in the accumulator. With a 10-bit value from analog_read(), you can easily right-shift 4 places, and still have 2 bits of headroom in the accumulator (as @datafiddler noted above).

Cypress has some app-notes for their PSOC chips with similar equations, and using shifts. I remember one had a nice table that related number of shifts to the cutoff frequency. The approximate cutoff frequency is the sampling frequency divided by 2-pi times the gain factor:

f0 ~ fs / (2 pi a)

where 'a' is that power of two.

Keep smoothin' those signals!

Bence Kaulics
  • 7,066
  • 7
  • 33
  • 63
Mike
  • 41
  • 1
3

On a device with no FPU rather then multiplying by 0.1 (which in any case make this a floating not fixed point implementation) you should divide by 10:

#define WLPF_DIV 10

...

ldr_val += (newval - ldr_val) / WLPF_DIV;

However division on an 8 bit processor is often expensive (although probably dwarfed by the execution time of Serial.println() in the loop - but that is a different issue). Instead it is more efficient to select a power of two so that the division can be performed with a right-shift.

#define WLPF_SHIFT 3  // divide by 8

...

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

The use of signed int is problematic since right-shift of a signed type is undefined behaviour. In this case this can be resolved by changing the code to:

#define WLPF_DIV 8

... 

ldr_val += (newval - ldr_val) / WLPF_DIV ;

The compiler will most likely spot the power-of-two constant and generate the code using an arithmetic-shift-right in any case. However you would probably do better to reconsider the data type.

You still have a right-shift in the Serial.println() call, but that too could by replaced with a divide-by-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);
}

Non-deterministic output of the data on a per sample basis is not going to make for a very accurate filter and will dominate the timing in any case so you have little control over the frequency response and it will not be stable. It also makes the previous performance optimisations rather pointless. You may want to think about that if it is important in your application - but that is a different question.

Clifford
  • 88,407
  • 13
  • 85
  • 165
  • Thank you for your very clear answer. The requirements of the filter are very loose as I'm just looking to slow down a value so it takes 1-2 secs to reach baseline. However, I will perhaps look into using interrupts for a more precise timing. – CatsLoveJazz Aug 12 '16 at 16:23
0

Stick with integer arithmetics:

#define WLPF 9

filtered = ((long)filtered * WLPF + newValue) / (WLPF + 1);
datafiddler
  • 1,755
  • 3
  • 17
  • 30
  • 1
    in an 8 bit Arduino with 16 bit integer arithmetics, take care not to exceed the range (especially if you apply your FIXED_SHIFT) *** const unsigned long WLPF=9; – datafiddler Aug 12 '16 at 14:08
  • This code will perform really badly on AVR. No offence, but embedded software is a very specific field (at least what the questions asks for). If you are not experienced with it and know the architecture well, it is better not to answer. – too honest for this site Aug 12 '16 at 16:03
  • Thanks @Olaf, why would this code perform badly on an AVR? – CatsLoveJazz Aug 12 '16 at 16:10
  • Multiply by 0.1 is the same as divide by 10 so `0.1 * (newval - ldr_val)` is the same as `(newval - ldr_val) / 10` - your expression does not appear to be equivalent to the original code. In any case division and to a lesser extent multiplication and the use of `long` will be relatively expensive on an 8 bit processor - though not as expensive as the floating point operation and it is all largely irrelevant given the `Serial.println()` call in the loop. – Clifford Aug 12 '16 at 16:23
  • 1
    analogRead() is by far the slowest part, and, to really get a low pass filter, you need a defined cycle time (or take the actual cycle time into account) So Olaf's proposal saves a few division cycles in favour of wait cycles. If it would matter, Olaf were really so right. – datafiddler Aug 12 '16 at 16:26
  • @CatsLoveJazz: That's too broad. If you really need to optimise code, you first should know the underlying architecture very well. But I'm afraid, this also requires quite some practice. There is a reason school does not just take one year, but many of them. – too honest for this site Aug 12 '16 at 16:40
  • @Olaf thanks, can you perhaps point me in the direction of an implementation that would perform well on AVR? – CatsLoveJazz Aug 12 '16 at 16:41
  • @CatsLoveJazz: Let apart such recommendations are off-topic here, I had to search for it, too. How about you find out yourself and - as you're at it - learn how to profile code to determine if it is **fast enough**? There is no to little use in over-optimising code, be it for speed or sizes. – too honest for this site Aug 12 '16 at 16:56