View previous topic :: View next topic |
Author |
Message |
Guest
|
Math Filter issue |
Posted: Sun Feb 11, 2007 3:29 pm |
|
|
Ooofffff!
I have an issue with some math going wrong in the PIC.
I am using a PIC18F2520. I am implementing the 10 Bit ADC it has.
I am trying to filter the new value from the ADC by using the previous value as a reference. I am trying to make sure the new value from the ADC is not more or less than 5% of the previous value from the ADC.
Here is what I noticed. When I wrote the code without multiplying the filter value. It gives fine values.
Here is the code:
Code: |
int16 FinalOutput;
signed int32 output1;
signed int 32 output2;
signed int32 filter =5;
Output1 = Read_ADC() *100;
Output2 = FinalOutput *100; // Here I am transferring my previous final
output into a 32 bit value.
FinalOutput= (Output2+ ((Output1-Output2)))/100; // This returns me
back a 16 bit value
(notice I dont have
the filter
multipied)
|
But once I multiplied the filter value!! The final output started fluctuating really big numbers.
Here is the code with the filter multiplied:
Code: |
int16 FinalOutput;
signed int32 output1;
signed int 32 output2;
signed int32 filter =5;
Output1 = Read_ADC() *100;
Output2 = FinalOutput *100; // Here I am transferring my previous final
output into a 32 bit value.
FinalOutput= (Output2+ (filter*(Output1-Output2)))/100; // This returns
me back a 16 bit
filtered value.
|
Any Idea !!!!
Thanks. |
|
|
Ttelmah Guest
|
|
Posted: Sun Feb 11, 2007 4:14 pm |
|
|
The obvious fault, would apply to both versions!...
Output2 = FinalOutput *100;
FinalOutput, is an int16 value. There is nothing in the arithmetic part larger than an int16, so the sum will be performed using int16 arithmetic. Once the value goes over 655, the arithmetic will wrap, feeding a 'silly' number into the next part.
You need:
Output2 = (int32)FinalOutput *100;
Tis forces the value to be converted to an int32 _before_ the multiplication.
Though the fault would be present in both, the effect will be made larger by the factor multiplication.
Best Wishes |
|
|
newguy
Joined: 24 Jun 2004 Posts: 1911
|
|
Posted: Sun Feb 11, 2007 4:16 pm |
|
|
Code: | #include <18F4580.h>
#device adc=10
#use delay(clock=20000000,RESTART_WDT)
#fuses HS, BROWNOUT, BORV46, PUT, STVREN, NOLVP
#include "c:lcd_driver.c" // change this to point at your lcd driver file
#BYTE ADCON0 = 0xfc2
signed int16 result = 0;
signed int32 old_result;
signed int32 new_read;
signed int32 filter = 5;
int1 reading_ready = FALSE;
#int_RTCC
RTCC_isr() {
ADCON0 = ADCON0 | 0x02; // set GO bit, and start the conversion
}
#int_AD
AD_isr() {
new_read = read_adc(ADC_READ_ONLY);
reading_ready = TRUE;
}
void main() {
setup_adc_ports(AN0);
setup_adc(ADC_CLOCK_INTERNAL);
setup_psp(PSP_DISABLED);
setup_spi(FALSE);
setup_wdt(WDT_ON);
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_8);
setup_timer_1(T1_DISABLED);
setup_timer_2(T2_DISABLED,0,1);
setup_timer_3(T3_DISABLED|T3_DIV_BY_1);
enable_interrupts(INT_RTCC);
enable_interrupts(INT_AD);
enable_interrupts(global);
set_tris_e(0);
lcd_init();
set_adc_channel(0);
result = read_adc();
while (TRUE) {
restart_wdt();
if (reading_ready) {
reading_ready = FALSE;
old_result = result;
result = old_result + (filter * (new_read - old_result))/100;
printf(lcd_putc,"\f%ld %ld",new_read,old_result);
printf(lcd_putc,"\n%ld ",result); // this is stable
}
}
} |
|
|
|
Guest
|
Exponential filter |
Posted: Sun Feb 18, 2007 1:00 pm |
|
|
I just cant make this code work. I have tried a zillion different combinations!! I will appreciate any help.
I am trying to write code such that while reading from the ADC my new value is not more or less than 4 % of my previous value from the ADC .
I have a 10 bit ADC. But I need my final value to be a non-signed 16 bit value.
In other words:
(Non-signed int16) Final = ((old Value)*96%) + ((newValue)*4%)
I seem to have issues with signs, and my values keep fluctuating as huge numbers. I am getting values such as -2000 or 5000 instead of values around 450. |
|
|
future
Joined: 14 May 2004 Posts: 330
|
|
Posted: Sun Feb 18, 2007 1:29 pm |
|
|
I don't how did you get this code to compile!?
~4% of 100 is: x = 100*11/256;
~96% of 100 is: x = 100*246/256; |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Sun Feb 18, 2007 2:01 pm |
|
|
Quote: | I just cant make this code work. |
Post a short test program, similar in size to Newguy's code.
Make sure it's compilable. We should be able to copy and paste it
right into MPLAB and press the compile button and have it compile
with no errors.
Also post your compiler version. |
|
|
newguy
Joined: 24 Jun 2004 Posts: 1911
|
|
Posted: Sun Feb 18, 2007 3:09 pm |
|
|
It shouldn't matter whether the final filtered value is declared as a signed or unsigned int16 because the A/D can only give you 10 bits of precision anyway. It's absolutely critical that all variables used in your calculation be declared as signed. The reason for this is because you can have negative "intermediate" values in your calculations which then have huge consequences on the final value. Remember that a negative number is the same thing as a "huge" unsigned number, and that is why your values fluctuate.
When I wrote & tested the program I posted above, my values would fluctuate too, until I declared the final filtered variable to be signed. At that point, everything was okay as I stated in the program with the "// this is stable" comment. Even though the variable is declared as signed, the output is always positive. Declaring a variable as signed only changes how the compiler treats the contents of the variable - it doesn't change how it's stored. |
|
|
Storic
Joined: 03 Dec 2005 Posts: 182 Location: Australia SA
|
|
Posted: Sun Feb 18, 2007 3:24 pm |
|
|
I had filter issues due to EMF noise on site, (installed product) I did not realy over come these however I did do several reads and applied the average. I used "EX_ADMM.C" to do the average did 5 reads added together and then divided by 5.
I will agree with newguy code, I did not do
Code: | #int_RTCC
RTCC_isr() {
ADCON0 = ADCON0 | 0x02; // set GO bit, and start the conversion
}
#int_AD
AD_isr() {
new_read = read_adc(ADC_READ_ONLY);
reading_ready = TRUE;
} |
in my code, of which I will add. ( did not make stable each of my analog value read) even with the average result, there are unexplained results, however not as bad as a single read.
see http://www.ccsinfo.com/forum/viewtopic.php?t=19509&highlight=filter
on another note: I had discussed with another person re filter and he does something slightly different, he take 3 reads Read[0], Read[1] and read[2]. sorts them out from hi to low and uses Read[1]. (middle value only) you could even extend this to 5 sort and take the middle as your result.
there are so many methods you can use within your code, however depend on chip size, you may need to consider an analog opp amp before the input to reduce code?? maybe something like "TC913 Auto-Zero Op Amp" from microchip or any other op amp on the market that is suited to your application.
I am still learning on analog signals and filters. and yes I have learned something new reading this post
Andrew _________________ What has been learnt if you make the same mistake? |
|
|
Guest
|
|
Posted: Sun Feb 18, 2007 3:29 pm |
|
|
The code is part of a real big program. My code works well if I don�t try to filter the data. I get correct values in that case. I have pasted the part of the code that I am apllying the filter on.
Code: |
int16 FinalOutput;
signed int32 output1;
signed int 32 output2;
signed int32 filter =4;
signed int32 factor=100;
output1 = (signed int32 )Read_ADC();
output2 = (signed int32) FinalOutput; // Here I am transferring my previous final
output into a signed 32 bit value.
FinalOutput= (int16)(((output1*filter)+((factor-filter)*(output2)))/factor);
|
COMPILER VERSION: 3.249, 33336
!!!! |
|
|
Guest
|
|
Posted: Sun Feb 18, 2007 3:43 pm |
|
|
I am using I2C to send this data to another pic. And it needs me to send the data as non-signed int16. It gives me wrong values if I do it the other way!! |
|
|
mickent
Joined: 18 Feb 2007 Posts: 22 Location: TN, USA
|
|
Posted: Sun Feb 18, 2007 4:47 pm |
|
|
I don't think you can have a space between int and 32? _________________ Mick |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Sun Feb 18, 2007 4:52 pm |
|
|
See the program shown below. This is what I mean by a test program.
It has an array of sample data that you can feed into it and then you can
observe what the program does with your data. You can run this program
in the MPLAB Simulator and use "Uart1" to display the output in the
Output Window.
Here's the output:
Code: |
A/D Output
5 0
100 4
35 5
1000 44
800 74
400 87
200 91
100 91
200 95
210 99
|
Code: |
#include <16F877.H>
#fuses XT, NOWDT, NOPROTECT, BROWNOUT, PUT, NOLVP
#use delay(clock=4000000)
#use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7, ERRORS)
int16 sample_data[10] =
{
5,
100,
35,
1000,
800,
400,
200,
100,
200,
210
};
//=======================
void main()
{
int8 i;
int16 FinalOutput;
signed int32 output1;
signed int32 output2;
signed int32 filter = 4;
signed int32 factor = 100;
output2 = 0;
for(i = 0; i < 10; i++)
{
output1 = sample_data[i]; // (signed int32)Read_ADC();
output2 = ((output1 * filter) + ((factor - filter) * output2) ) /factor;
FinalOutput = (int16)output2;
printf("%4lu %4lu\n", sample_data[i], FinalOutput);
}
while(1);
} |
I'm not sure I understand your original request. You said you wanted
the new value to be less than 5% of the old value. But what if the A/D
value starts at 0, and goes up 100, 200, 300, 400, etc. ? That's a lot
more than a 5% increase. I guess you want it to display this:
Code: |
A/D Output
0 0
100 0
200 105
300 210
400 315
500 420 |
Is that correct ? |
|
|
Neutone
Joined: 08 Sep 2003 Posts: 839 Location: Houston
|
|
Posted: Sun Feb 18, 2007 5:28 pm |
|
|
Code: |
FinalOutput= (Output2+ (filter*(Output1-Output2)))/100;
|
Reading = ((old value)*.95)+((new value)*.5))
This can be re-arranged to a form that will compile well using just unsigned integer math. The value reported will never change more that full scale/filter.
if full scale is 1024 (10-bit) and filter is 32 (5-bit) that would mean the reading would never change more than 32 (10-5 = 5 bit)
Ok lets assume you want a values that range from 0 to 1024. You can use this:
Reading=(Old_Reading-(Old_Reading/filter))+(Read_ADC()/filter)
Old_Reading=Reading;
Now this has poor performance because it throws away 5 bits of data right off the top.
A better version that would return a 16 bit reading would look like this.
Reading=(Old_Reading-(Old_Reading/filter))+(Read_ADC()*64/filter)
Old_Reading=Reading;
First the ADC is scaled to a 16 bit value and then divided by the filter value. Now if you have a fixed filter value of 32 (1/32=0.03125) the reading will not change more than 03.125% of full scale and you can simpilify the math.
Reading=(Old_Reading-(Old_Reading/32))+(Read_ADC()*2)
Old_Reading=Reading;
All of this works with unsigned integer math and gets the results I think you are looking for. |
|
|
Guest
|
|
Posted: Mon Feb 19, 2007 9:21 pm |
|
|
Dear PCM Programmer,
Here is what I am trying. The objective to ensure the new value from the sensor (ADC) is not more or less that 5 % of the previous value from the sensor (ADC) is to smoothen the output curve from the sensor. It helps remove sudden spikes from the sensor. It does add some lag, but it helps smoothen the curve. |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Mon Feb 19, 2007 9:31 pm |
|
|
If you want a "spike filter", then use a median filter. Here is sample
code. This code was used in a tachometer project in which the speed
was determined every 100 ms -- so the filter algorithm didn't have to
be very fast.
http://www.ccsinfo.com/forum/viewtopic.php?t=3462&highlight=medianfilter
A median filter will remove "flyers" or "spikes" from the input data. |
|
|
|