3

I am working on a micro-controller and I want to implement a simple average filter on the resulted values to filter out noise (or to be honest to not let values dance on LCD!).

The ADC result is inserted into memory by DMA. I have (just for sake of ease of debugging) an array with size 8. To make life even easier I have wrote some defines to make my code editable with minimum effort:

#define FP_ID_POT_0             0  //identifier for POT_0
#define FP_ID_POT_1             1  //identifier for POT_1
#define FP_ANALOGS_BUFFER_SIZE  8 //buffer size for filtering ADC vals
#define FP_ANALOGS_COUNT        2  // we have now 2 analog axis
#define FP_FILTER_ELEMENT_COUNT  FP_ANALOGS_BUFFER_SIZE / FP_ANALOGS_COUNT
//means that the DMA buffer will have 4 results for each ADC

So, the buffer has size of 8 and its type is uint32_t. And I am reading 2 ADC channels. in the buffer I will have 4 result for Channel A and 4 result for Channel B (in a circular manner). A simple dump of this array is like:

INDEX    0      1      2       3     4      5      6    7
CHNL     A      B      A       B     A      B      A    B
VALUE   4017    62    4032    67    4035    64    4029  63  

It means that the DMA puts results for ChA and ChB always in a fixed place.

Now to calculate the average for each channel I have the function below:

uint32_t filter_pots(uint8_t which) {

  uint32_t sum = 0;
  uint8_t i = which;

  for( ; i < FP_ANALOGS_BUFFER_SIZE; i += FP_ANALOGS_COUNT) {
    sum += adc_vals[i];
  }

  return sum / (uint32_t)FP_FILTER_ELEMENT_COUNT;
}

If I want to use the function for chA I will pass 0 as argument to the funtion. If I want chB I pass 1...and if I happen to have chC I will pass 2 and so on. This way I can initiate the for-loop to point to the element that I need.

The problem is, at the last step when I want to return the result, I do not get the correct value. The function returns 1007 when used for chA and 16 when used for chB. I am pretty sure that the sum is calculated OK (I can see it in debugger). The problem, I beleive, is in the division by a value defined using #define. Even casting it to uint32_t does not help. The sum is being calculated OK, but I can no see what type or value FP_FILTER_ELEMENT_COUNT has been assigned to by compiler. Mybe its an overflow problem of dividing uint32 by uint8?

#define FP_FILTER_ELEMENT_COUNT  FP_ANALOGS_BUFFER_SIZE / FP_ANALOGS_COUNT
//means FP_FILTER_ELEMENT_COUNT  will be 8 / 2 which results in 4

What causes this behaviour and if there is no way that #define would work in my case, what other options I have?

The compiler is IAR Embedded Workbench. Platform is STM32F103

Dumbo
  • 13,555
  • 54
  • 184
  • 288
  • Seems like the returned value is the expected value divided by 4? In place of your current return statement, try `uint32_t average = sum / (uint32_t)FP_FILTER_ELEMENT_COUNT; return average;` Check the value of `average` in the debugger before returning. – Eric J. Oct 14 '15 at 01:52
  • @EricJ. Not really...take chB, sum will be 256 and divided by 4 should be 64 not 16...The sum is OK, but I can not see in the debugger what type or value FP_FILTER_ELEMENT_COUNT has – Dumbo Oct 14 '15 at 02:02
  • 1
    Looks like you were dividing by an extra 4 :-) Glad Russ was able to find a solution. – Eric J. Oct 14 '15 at 05:28
  • 1
    Just calculating the average won't stop values from "dancing", they'll just dance less frequently/drastically. You'll want to combine your average calculation with a _hysteresis_, meaning you only allow values to change if they change by a certain amount. – Lundin Oct 14 '15 at 08:31
  • @lundin yep that comes later in the code :p – Dumbo Oct 14 '15 at 13:39
  • @Lundin Can you please give me a feedback on this: I do this filter on DMA and I think while my function is reading the data the content of DMA changes...is my approach correct or not? – Dumbo Oct 14 '15 at 15:09
  • 1
    You should declare `adc_vals` as volatile so that the compiler doesn't decide to do crazy things - the compiler can likely not tell that the DMA will show data there at any given point in time. Ideally, you'll sync the copy of the array with the DMA write, so that you never read while the DMA writes. Maybe there's some flag in the DMA you can check, or maybe you can temporarily disable it while reading? – Lundin Oct 14 '15 at 15:48
  • 1
    Otherwise, most on-chip ADCs have an option where you can just read their most recent results from ADC data buffers on the fly, whenever you have time for it. In which case DMA is superfluous. – Lundin Oct 14 '15 at 15:51

2 Answers2

9

For fewer surprises, always put parenthesis around your macro definitions

#define FP_FILTER_ELEMENT_COUNT (FP_ANALOGS_BUFFER_SIZE / FP_ANALOGS_COUNT)

This prevents oddball operator precedence issues and other unexpected syntax and logic errors from cropping up. In this case, you're returning sum/8/2 (i.e. sum/16) when you want to return sum/4.

owacoder
  • 4,815
  • 20
  • 47
Russ Schultz
  • 2,545
  • 20
  • 22
  • 1
    @saeid: yet another chapter in the saga "Why Macros Are Evil". Think how much time you would have saved with `static const uint32_t FP_FILTER_ELEMENT_COUNT = FP_ANALOGS_BUFFER_SIZE / FP_ANALOGS_COUNT;` – rici Oct 14 '15 at 04:13
5

Parentheses will help, as @Russ said, but an even better solution is to use constants:

static const int FP_ID_POT_0 = 0;  //identifier for POT_0
static const int FP_ID_POT_1 = 1;  //identifier for POT_1
static const int FP_ANALOGS_BUFFER_SIZE = 8; //buffer size for filtering ADC vals
static const int FP_ANALOGS_COUNT = 2;  // we have now 2 analog axis
static const int FP_FILTER_ELEMENT_COUNT = FP_ANALOGS_BUFFER_SIZE / FP_ANALOGS_COUNT;

In C++, all of these are compile-time integral constant expressions, and can be used as array bounds, case labels, template arguments, etc. But unlike macros, they respect namespaces, are type-safe, and act like real values, not text substitution.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Another advantage of constants is that they end up with an identifier, so that you can actually view their values in a debugger, which is always handy. – Lundin Oct 14 '15 at 08:35