Electronic Projects, forums and more.

Go Back   Electronic Circuits Projects Diagrams Free > Electronics Categories > Micro Controllers


Micro Controllers Discuss all aspects of micro controllers - building them, coding them, etc. All controllers are welcome - PIC, BASIC, Z8 Encore!, etc.

Reply
 
Thread Tools Display Modes
Old 3rd June 2008, 12:04 AM   (permalink)
Default Please comment/critique my little app note

Averaging Filters in Embedded Systems

Averaging filters are frequently used in embedded systems for a variety of reasons. A very common task in which they are used is to eliminate noise from an analog signal that has been sampled. Here we investigate how to make these computations faster and occupy less memory space.

The Wrong Way to Average

A very poor method of averaging, while mathematically correct, is to store the last (n-1) measured samples in an array to be averaged (where n is the order of the filter). The newest measured value would added to the previous (n-1) values, and then divided by a factor of n. Having accomplished this, the oldest stored value in the array would be discarded and the newest measured value shifted into the array for future calculations. The C code for a 4th order (n = 4) moving average filter implanted like this might look like the following:

int average(new_val) {

sum = new_val + previous_val[0] + previous_val[1] + previous_val[2];
avg = sum / 4;

previous_val[2] = previous_val[1];
previous_val[1] = previous_val[0];
previous_val[0] = new_val;

return avg;
}

This method is extremely poor in terms of both execution time and memory usage. In addition for the need to store (n-1) array elements, this function requires (n-1) values to be shifted through the array, (n-1) additions to be made, and a divide instruction which alone can take up to 38 instruction cycles on a PIC18F series device. All of the above must happen every time this function is called.

A Simpler Solution

A better method of removing noise from a signal uses an approximation of the moving average. This method is faster, uses less memory, and is significantly better for higher-order filters. This quasi-average is calculated by taking the most recent measured value, adding it to the previous average multiplied by (n-1), and divides that sum by n. In this method n does not represent the number of samples you are averaging over, but simply the order of the averaging filter. The C code for this algorithm with n = 10 would look like the following:

int average(new_val) {
avg = (new_val + 9 * avg) / 10;
return avg;
}

That’s a whole lot simpler, eh? It only requires one integer to be stored, one addition, one multiplication, and one division. It does not give a true moving average, but is still a very effective function for smoothing data and eliminating noise.

Getting Really Smart

The improved method above is much better, and is one you should remember when coding for any computer system where an averaging or smoothing filter is required. But for PIC microcontrollers, the divide and multiply instructions still cause a lengthy execution time. By being a little bit smarter, we can eliminate this source of inefficiency. This is accomplished by picking a power of two for the value of n, and using unsigned 8-bit numbers for all the operands.

Picking a power of two for the value of n allows for division to be accomplished with a series of bit shifts instead of a true divide. Each rightward bit shift uses only one execution cycle and effectively divides the number by two. By using one, two, three, or four bit shifts we can accomplish division by a factor of two, four, eight, or sixteen, respectively.

By using unsigned 8-bit integers for our operands, we can take advantage of the 8x8 hardware multiplier inherent in all PIC18F processors. If you have a particularly noisy signal the extra two or four bits provided by a 10 or 12 bit analog to digital converter may be buried in the noise, or you may simply not need that precision. In either case you can take advantage of the single-cycle 8x8 hardware multiply supported by the 18F series of PICs.

Below is what the C code might look like for a sixteenth order averaging filter:

unsigned short average(new_val){
avg = (new_val + 15 * avg) >> 4;
return avg;
}

One addition, a single-cycle multiplication, and four single cycle bit shifts. If you can live with a quasi-average and eight bit precision, the above method will yield much faster execution times and require much less system memory.

Remember this the next time you have to de-noise a signal, and you won’t be left wondering why your program takes so long to execute.

Mark Hayenga
© 2008

Last edited by speakerguy79; 3rd June 2008 at 12:09 AM.
speakerguy79 is online now   Reply With Quote
Old 3rd June 2008, 12:50 AM   (permalink)
3v0
Default

Looks good to me.

I would prefer to see
avg = (new_val + (15 * avg)) >> 4;
3v0 is offline   Reply With Quote
Old 3rd June 2008, 07:49 AM   (permalink)
Default

I just see a couple of typos:

Quote:
The newest measured value would be added to the previous (n-1) values. . .
Quote:
The C code for a 4th order (n = 4) moving average filter implemented like this might look like the following:
Quote:
In addition to the need to store (n-1) array elements,
Overall, I found that very informative. Good job!


Torben

Last edited by Torben; 3rd June 2008 at 07:50 AM.
Torben is offline   Reply With Quote
Old 3rd June 2008, 05:02 PM   (permalink)
Default

Oops--one more nitpick. The C example functions never declare their internal variables.

Told you it was a nitpick.


Torben
Torben is offline   Reply With Quote
Old 3rd June 2008, 08:45 PM   (permalink)
Default

They're all global.

Pththththththth

Actually I had all the decs and initializations in there, but I removed them for simplicity. I even thought about writing it in pseudo-code.

Thanks for all the suggestions and corrections guys!

Last edited by speakerguy79; 3rd June 2008 at 08:47 PM.
speakerguy79 is online now   Reply With Quote
Old 3rd June 2008, 09:39 PM   (permalink)
Default

Quote:
Originally Posted by speakerguy79 View Post
They're all global.

Pththththththth
I *knew* you were going to say that. hehe

Are you putting together a site for this stuff? I'd be interested in reading more app notes like that.


Torben
Torben is offline   Reply With Quote
Old 3rd June 2008, 11:15 PM   (permalink)
Default

Yes, I'll be putting up a site. I'm just now getting started with it.
speakerguy79 is online now   Reply With Quote
Old 4th June 2008, 02:28 AM   (permalink)
Default

Alright, suggestions and corrections incorporated, though variable decs left out for clarity.
Attached Files
File Type: pdf Averaging Filters in Embedded Systems.pdf (12.0 KB, 11 views)
speakerguy79 is online now   Reply With Quote
Old 4th June 2008, 08:37 PM   (permalink)
Default

in the brute force average case, you don't need to move the data in the array. just overwrite the oldest one in a circular fashion. increment the index mod N.
philba is offline   Reply With Quote
Old 5th June 2008, 03:04 AM   (permalink)
Default

Or forget about arrays and just add the 8bit samples to a 16bit variable and then divide (use shift for speed) the 16bit variable by the number of samples taken. The average result is in the lower 8bits of the 16bit variable.
__________________
--- The days of the digital watch are numbered. ---
kchriste is online now   Reply With Quote
Old 5th June 2008, 07:20 PM   (permalink)
Default

Quote:
Originally Posted by speakerguy79 View Post
By using unsigned 8-bit integers for our operands, we can take advantage of the 8x8 hardware multiplier inherent in all PIC18F processors.
Quote:
Originally Posted by speakerguy79 View Post
Below is what the C code might look like for a sixteenth order averaging filter:

unsigned short average(new_val){
avg = (new_val + 15 * avg) >> 4;
return avg;
}
Some compilers define the 'short' type as an 8-bit variable and the result would be wrong because of possible overflows. C18 defines the 'short' type as a 16-bit variable (it is the same as an 'int' variable). I would define 'avg' as an 'unsigned int' and 'new_val' as an 'unsigned char' in order to avoid the confusion caused by different conventions.

Code:
unsigned int avg; // 16 bits (to handle the result of the multiplication)
 
unsigned char average(unsigned char new_val){
avg = (new_val + 15 * (unsigned char)avg) >> 4;  // an explicit cast forces the compiler to do an 8x8 multiplication
return (unsigned char)avg;   // return an 8-bit variable
}
BTW, it's always a good idea to check the assembly code generated by the compiler
eng1 is offline   Reply With Quote
Reply

Bookmarks

Thread Tools
Display Modes


Similar Threads
Thread Thread Starter Forum Replies Latest
Tell your comment ! watzmann General Electronics Chat 9 18th December 2007 06:42 AM
Final project critique freeskier89 Electronic Projects Design/Ideas/Reviews 4 23rd July 2005 05:08 AM
Critique on circuit and code pls? Phasor Micro Controllers 0 12th July 2005 01:04 PM
Please critique my PWM code! bsodmike Micro Controllers 11 17th May 2004 12:16 PM
Critique my RS-232 asm code for PIC16F84A dak246 Micro Controllers 3 11th May 2004 05:36 PM



All times are GMT. The time now is 03:02 AM.


Electronic Circuits  |  Electronics Wiki
Powered by vBulletin® Version 3.7.0
Copyright ©2000 - 2008, Jelsoft Enterprises Ltd.