This message contains an entire topic ported from a separate forum. The original message and all replies are in this single message. We have seeded this new forum with selected information that we expect will be of value to you as you search for answers to your questions.
Posted: Tue Jul 19, 2005 10:28 pm
I'm looking for a way of 'damping' one of the ADC values from an HCS12. I am using the full 10-bits and am reading the value from a NTC thermistor. But the value is not steady, it 'wobbles' a fraction of a degree all the time. I could increase the value of the filter capacitor on the board, but I would rather have an adjustable value in software that would allow me to tweak the damping, rather than a hardware based solution.
Anyone have any ideas on how to implement this ?
Posted: Tue Jul 19, 2005 11:09 pm
In an old implementation, I start by finding the diff of new value from previous. I decide how many bits are stable, and have a variable to store the value of that size. If the diff is insignificant, I don't update the variable.
I wonder if there is some new way to do it like with fuzzy logic or something that takes less effort.
Posted: Tue Jul 19, 2005 11:13 pm
To get precision in the cut-off frequency, you can assume all the numbers are fractions and use:
FilteredVAlue += ( CurrentReading - FilteredValue ) * f ;
Is the equivalent of an RC filter for f < 1. The only question is what value to use for f.
If you want to avoid the multiply and can settle for less precision in the cutoff frequency use:
FilteredValue += ( CurrentReading - FilteredValue ) >> k ;
Which is the same effect but restricts you to f = 1 / ( 2 >> k )
Jack Crenshaw had a column or two on this several years ago in Embedded Systems Programming, but I don't remember the exact date.
It is still helpful to put in a real analog filter ahead of the ADC with a cutoff below <sampling rate> / 2 to remove frequencies above the sampling rate. That noise cannot be removed in the digital part of the system.
Posted: Tue Jul 19, 2005 11:28 pm
Thanks for this. I'm going to appear really stupid and old fashioned now, but can you explain what this is doing please. I don't know C, as I write all my code in assembler. If you could explain what the C commands are doing, I'm sure I can code it in assembler.
Posted: Wed Jul 20, 2005 1:31 am
Sorry, I have to ask why are you coding in "just assembler" when you are dealing with numbers from an analog system?
Translations from C
The C statement:
FilteredValue += ( CurrentReading - FilteredValue ) * f ;
Has the += operator means that the right hand side should be added to the current value of the left hand side and stored back in the left hand side.
In BASIC and many other languages you would have to write:
FilteredValue = FilteredValue + ( CurrentReading - FilteredValue ) * f ;
In algebra you would write something like:
NextFilteredValue = LastFilteredValue + ( CurrentReading - LastFilteredValue ) * f
The Cosmic compiler generates this code for 16 bit integers:
( The extra ( int )( ... ) and ( long )( ... ) are to cause the multiply result to be treated as a 16 bit fraction rather than an integer. )
54 ; 14 FilteredValue
54 ; 15 += (int )(
54 ; 16 ( long )( (CurrentReading - FilteredValue ) * f )
54 ; 17 >> 16
54 ; 18 ) ;
56 000c fc0002 ldd _CurrentReading
57 000f b30004 subd _FilteredValue
58 0012 fd0000 ldy _f
59 0015 13 emul
60 0016 cd0010 ldy #16
61 0019 L01:
62 0019 47 asra
63 001a 56 rorb
64 001b 0436fb dbne y,L01
65 001e f30004 addd _FilteredValue
66 0021 7c0004 std _FilteredValue
81 ; 21 FilteredValue += ( CurrentReading - FilteredValue ) >> 3 ;
83 0031 fc0002 ldd _CurrentReading
84 0034 b30004 subd _FilteredValue
85 0037 47 asra
86 0038 56 rorb
87 0039 47 asra
88 003a 56 rorb
89 003b 47 asra
90 003c 56 rorb
91 003d f30004 addd _FilteredValue
92 0040 7c0004 std _FilteredValue
The HC12 does not provide a neat method of doing shifts of 16 bit quantities, so you have to do it one bit at a time with the asra, rorb sequence. As they are both 1 byte instructions, its still pretty fast.
(The subject has been discussed at considerable length here in the past.)
C is a slightly perverse form algebra that can be converted into machine instructions by a compiler.
The advantages of C and other compiler languages are that they have much better consistency checking, and are shorter ways of expressing code.
This combines to give a much lower error rate when writing code, and much faster completion of projects.
Even if you are programming for fun, completing projects faster should translate into more fun.
(End of Sermon, more available on request.)
Posted: Wed Jul 20, 2005 11:55 am
I don't understand your question 'why are you coding in "just assembler" when you are dealing with numbers from an analog system?'
I code in assembler because thats about all I know ! I keep meaning to learn C, but have never got around to it yet.
I've tried the code below, and I can't seem to get it to work. I'm confused by the 16 Right Shifts. This would seem to make all the bits in D be whatever bit15 is, why is this ?
Perhaps i've got the value of f wrong, any suggestions? Does a larger value of f give more or less filtering ?
Here is the code I've used, so you can see if I've done something stupid !
LDD RawVBat ; 10bit Result from ADC
SUBD BatDamp ; 16bit Storage Register
Posted: Wed Jul 20, 2005 2:14 pm
> I don't understand your question 'why are you coding in "just
> assembler" when you are dealing with numbers from an analog system?'
> I code in assembler because thats about all I know ! I keep meaning
> to learn C, but have never got around to it yet.
Don't be afraid of C. Assembler is the one that's hard to deal with. You've got to just let it flow. For example, when you see '+=' don't panic, just take it for what it looks like--add and store.
Maybe you can tell I learned much of C just looking at examples.
Posted: Wed Jul 20, 2005 5:48 pm
Sorry, my mistake! See notes below.
>I don't understand your question 'why are you coding in "just
>assembler" when you are dealing with numbers from an analog system?'
My thinking is that if you are reading analog values that you will likely end up doing a lot more with the values than just smoothing them. It is a lot easier and less error-prone to just write the expressions and let the compiler do the work than hand-compiling the expressions you want to compute.
>I code in assembler because thats about all I know ! I keep meaning
>to learn C, but have never got around to it yet.
I feel very strongly that its well worth your while to get comfortable with a few high-level languages.
>I've tried the code below, and I can't seem to get it to work. I'm
>confused by the 16 Right Shifts. This would seem to make all the
>bits in D be whatever bit15 is,
You are correct, it does.
> why is this ?
Because I didn't test the code, and the code was wrong!
Here is the corrected (and now tested) code:
53 3f022 L12:
54 ; 14 FilteredValue
54 ; 15 += (int )(
54 ; 16 ( long )( (
CurrentReading - FilteredValue ) ) * f
54 ; 17 >> 16
54 ; 18 ) ;
56 3f022 fc0804 ldd _CurrentReading
57 3f025 b30806 subd _FilteredValue
58 3f028 fd0800 ldy _f
59 3f02b 1813 emuls
60 3f02d b764 tfr y,d
61 3f02f f30806 addd _FilteredValue
62 3f032 7c0806 std _FilteredValue
63 ; 12 for ( i = 1 ; i < 50 ; i++ )
Notice that when I get the statement right to force C to treat the integers as fractions ( the binary point between the sign bit and bit 15 ), the compiler realizes that the shifts aren't necessary. It puts in a tfr y,d instead.
>Perhaps i've got the value of f wrong, any suggestions ? Does a larger value
>of f give more or less filtering ?
f is the fraction of the current reading that is taken at each step.
Before the simplification to get 1 multiply and 1 subtract and 1 add the formula was:
Filtered Value = ( 1 - f ) * Filtered Value + f * Current
for 0 < f < 1.
Think about the response to a sudden step in the input value to a new constant value.
If f is close to 1, the filtered value becomes very close to the input is in one step. If f is close to 0, the filtered value will take a long time to drift up the new value of the input.
If f is 1/2 then the filtered value will be halfway to the new input value in one step, 3/4 of the way to the new input value in 2 steps, etc.
Probably a value in the range 1/4 to 1/20 will turn out to be what you want.
>Here is the code I've used, so you can see if I've done something stupid !
> LDD RawVBat ; 10bit Result from ADC
> SUBD BatDamp ; 16bit Storage Register
> LDY #500
> LDY #16
> DBNE Y,L01
> ADDD BatDamp
> STD BatDamp
As noted above, #500 is probably too small. Something like #8000, which would correspond to an f value of 8000 / 32768 would probably be better.
Posted: Wed Jul 20, 2005 6:26 pm
Thanks for this, I was beginning to think I was going out of my mind ! I will try and learn C at some point. I know VB & Delphi, but some how missed out ever learning C.
The code you show does work much better. However it does seem to suffer from a serious 'backlash' the more you increase the filtering. With an f value of 4000, I can get alter the ADC input by around 250mV before the 'filtered value' changes, and it always lags behind. If you come up towards say 2.5V from 1V, you get a very different reading than if you came down at towards 2.5V from 4V. Are we loosing resolution here somehow ?
Posted: Wed Jul 20, 2005 7:04 pm
I suspect that some of my initial fine print got lost.
I intended to emphasize that all the quantities involved were binary fractions.
If your ATD results are "right justified" that would explain effect that you see. Make sure the results are "left justified".
On a MC9S12DP256 that is controlled by the DJM bit in the ATDCTL5 register. 0 causes "left justification".
This makes the values unsigned fraction of full scale and gives you the maximum accuracy.
Since my example was with signed integers, the binary point was between bit 15 and bit 14.
If you use unsigned arithmetic then the binary point is to the left of bit 15, and you will want to us EMUL instead of EMULS. Check that the downstream code also expects unsigned fraction.
It turns out that using binary fractions for dealing with real world quantities as fractions of full scale works quite well with fixed point arithmetic. It keeps the maximum possible number of bits, and multiplies of two fractions won't cause overflow.
Some C compilers for embedded systems even support a "fract" type, which avoids all the ugly decorations I put into my C statement.
If you can afford it, using floating point is MUCH easier.