1

I'm trying to smooth out values for my IR distance sensor through running median filtering and a running average. I've tried to do the averaging, but my values don't seem correct and I am confused about how I would do the median filtering. I am also using timer interrupts and I don't want to use libraries when calculating the average and median. I've attached the code where I attempted to calculate the average.

int analogpin = 5;
int sum = 0;
int index = 0;
int averead = 0;
const int numreadings = 9;
int i;
int timer1_counter;
int adc_val;
int int_flag;
int int_array[9];
float V;

void setup() { int_flag = 0; Serial.begin(9600); pinMode(analogpin, OUTPUT); for (i = 0; i <= 8; i++) { int_array[i] = 0; }

noInterrupts(); TCCR1A = 0; TCCR1B = 0; timer1_counter = 59286; TCNT1 = timer1_counter; TCCR1B |= (1 << CS12); TIMSK1 |= (1 << TOIE1); interrupts(); }

ISR(TIMER1_OVF_vect) { TCNT1 = timer1_counter; int_flag = 1; }

void loop() { if (int_flag == 1) { adc_val = analogRead(A2); V = 5.0 * adc_val / 1023.0; analogWrite(analogpin, adc_val / 4); } sum = sum - int_array[i]; int_array[i] = analogRead(A2); sum = sum + int_array[i]; index = index++; if (i == 9) { i = 0; } averead = sum / numreadings; Serial.print("\n"); Serial.print("Average Filter Reading: "); Serial.println(averead); delay(1); }

dda
  • 1,588
  • 1
  • 12
  • 17
eateat
  • 19
  • 1
  • 3
  • 3
    index=index++; does nothing. Either use index=index+1; of index++. Also, you are using i for the array-index, but only (try to) increment index. I think that should be i=i+1. Because nowhere else i is changed, you always overwrite the same element in the array. – Gerben Mar 30 '19 at 12:32
  • You're using int_flag in an ISR, shouldn't it be volatile? Also in loop() you never reset it to 0. This can't be right... – dda Jan 09 '24 at 15:21

3 Answers3

1

There are two main problems with your program. The first is noted in Gerben's comment: you (attempt to) increment index (with a bad syntax), while you should be incrementing i instead. The second problem is these lines:

for (i = 0; i <= 8; i++) {
    int_array[i] = 0;
}

Fix the index vs. i confusion, remove the lines above, and your program should work.

Explanation: The first point should be obvious, I hope. I am discussing here the second issue. At the end of the loop above, the index i has the value 9. Later, in loop(), and assuming you fixed the first issue, the program does

i++;
if (i == 9) {
    i = 0;
}

and i has the value 10. From there i will continue incrementing, and you are always writing outside of the array.

The root of the problem is that you used the same variable i for two different purposes. There are several solutions to this:

  • use different variable names for different purposes
  • use local variables whenever applicable (here: in setup())
  • be defensive in your tests: instead of if (i == 9) write if (i >= 9), but note that this alleviates but does not completely solve the problem
  • remove the initialization of the array altogether.

I suggest removing the initialization because the array is a global variable and, per the C++ standard, if not explicitly initialized it is implicitly initialized to zero, so your initialization is redundant.

Edgar Bonet
  • 43,033
  • 4
  • 38
  • 76
  • Ok thank you. I will try that. Also, would you be able to explain to me how I would be able to calculate a running median as well? I'm pretty confused about that. – eateat Mar 31 '19 at 15:36
  • The OP does not seem to actually increment i, which might be the real problem. Also, sum isn't initialzed with analog values, so in my understanding averead will just be the last analog value divided by 9. – Sim Son Apr 01 '19 at 08:05
  • @SimSon: Indeed. I edited my answer. – Edgar Bonet Apr 01 '19 at 15:46
  • @eateat: The simplest way to compute a running median is to copy the circular buffer into a temporary array, sort that array, and return the element in the middle. It may not be the most efficient way though. If you need a more detailed answer, look into existing running median libraries, or ask a proper question on this site. – Edgar Bonet Apr 01 '19 at 15:55
0

analogRead() returns an integer and therefore your calculation V=adc_val*(5.0/1023.0) will be casted as int as well. I suggest changing it to V=5.0*adc_val/1023.0.

In general a running mean can be calculated like this:

#define buffer_size 9
#define analog_pin 5

int ring_buffer[buffer_size];
uint8_t index=0;
float running_mean() {
  ring_buffer[index]=analogRead(analog_pin);
  float mean=0;
  for (uint8_t i=0;i<buffer_size;i++) {
    mean+=ring_buffer[i];
  }
  if (++index>=buffer_size) index=0;
  return mean/buffer_size;
}

You should flush the buffer during initialization, otherwise you don't get proper readings before the 9th iteration.

Sim Son
  • 1,859
  • 12
  • 18
  • Would that give a proper average? – eateat Mar 30 '19 at 03:24
  • You didn't post a complete code snipped, so I can't say. But the line I mentioned will disturb averaging as the resulting resolution will decrease by 200 times. – Sim Son Mar 30 '19 at 03:35
  • But there is no magic about averaging: store data in a ring buffer and to calculate the mean: accumulate this biffer and divide by the buffer length – Sim Son Mar 30 '19 at 03:37
  • I'm doing that, but the values I am getting are very different from the unfiltered A to D readings (ie. like 247 for unfiltered and 63 for filtered). – eateat Mar 30 '19 at 03:41
  • I've added my full code. – eateat Mar 30 '19 at 03:45
  • Ok, I saw that you even don't use V and there are other parts in that code that do not make a lot of sense (at least in this snippet). Provide a minimal example that reproduces the error and is complete so we can compile it. Also you should enable compiler warnings, I'm sure there are some – Sim Son Mar 30 '19 at 03:45
  • So when I run the code that I gave you, it returns this:
    Average Filter Reading: 61
    Average Filter Reading: 61
    
    Average Filter Reading: 60
    
    Average Filter Reading: 61
    
    Average Filter Reading: 61
    
    Average Filter Reading: 61
    
    Average Filter Reading: 61
    
    Average Filter Reading: 61
    
    – eateat Mar 30 '19 at 03:49
  • you should tidy up or rethink/reimplement your code, you will then eliminate some errors, as there are: you don't reset your flag; you do not accumulate sum as you add and substract from it; you don't increment i even though you check its value. Google for "ring buffer" and you will understand how to write to int_array. You then need a function that reads a new analog value, writes it to the array and calculates and returns the average of int_array. This function can somehow substitute the actual analogRead() function. – Sim Son Mar 30 '19 at 03:57
  • Ok thank you. I will look into that. Also, how would I calculate a running median? – eateat Mar 30 '19 at 04:00
  • I can't say by heart, but that will not be the problem and only need some small changes. Also I don't think it will make a change as you only have 9 elements. – Sim Son Mar 30 '19 at 04:05
  • I've been kind and edited my answer with a possible function. Please note that I have no chance to test it right now – Sim Son Mar 30 '19 at 04:17
  • 1
    Re “_your calculation V=adc_val*(5.0/1023.0) will be casted as int as well_”: No, this is incorrect. It will be floating point. – Edgar Bonet Mar 31 '19 at 09:01
  • Also note that, although the OP's original code has some errors, it uses an algorithm that is more efficient than the one you are proposing here. – Edgar Bonet Mar 31 '19 at 10:58
0

Here is a simple alternative suggestion: Assuming you already know the average distance d and a new reading v comes in. The difference v-d can be used to update the average. But since you want you want to smooth this out over several readings, you only update d by a fraction of the difference: d += 0.8*(v-d). The factor controls the smoothing.

To get this started, you estimate that the first value you read is the average. This does not require an array to keep past readings around.