CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

Math Filter issue

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
Guest








Math Filter issue
PostPosted: Sun Feb 11, 2007 3:29 pm     Reply with quote

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







PostPosted: Sun Feb 11, 2007 4:14 pm     Reply with quote

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: 1907

View user's profile Send private message

PostPosted: Sun Feb 11, 2007 4:16 pm     Reply with quote

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
PostPosted: Sun Feb 18, 2007 1:00 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Sun Feb 18, 2007 1:29 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Sun Feb 18, 2007 2:01 pm     Reply with quote

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: 1907

View user's profile Send private message

PostPosted: Sun Feb 18, 2007 3:09 pm     Reply with quote

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

View user's profile Send private message Send e-mail

PostPosted: Sun Feb 18, 2007 3:24 pm     Reply with quote

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. Question

I am still learning on analog signals and filters. and yes I have learned something new reading this post Smile

Andrew
_________________
What has been learnt if you make the same mistake? Wink
Guest








PostPosted: Sun Feb 18, 2007 3:29 pm     Reply with quote

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

!!!! Sad
Guest








PostPosted: Sun Feb 18, 2007 3:43 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Sun Feb 18, 2007 4:47 pm     Reply with quote

I don't think you can have a space between int and 32?
_________________
Mick
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Sun Feb 18, 2007 4:52 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Sun Feb 18, 2007 5:28 pm     Reply with quote

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








PostPosted: Mon Feb 19, 2007 9:21 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Mon Feb 19, 2007 9:31 pm     Reply with quote

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.
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group