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 support@ccsinfo.com

About RMS Calculation without offset

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



Joined: 10 Jan 2019
Posts: 16
Location: Maracaibo, Venezuela

View user's profile Send private message Send e-mail Visit poster's website

About RMS Calculation without offset
PostPosted: Sun Aug 11, 2019 11:19 am     Reply with quote

Hello there,

I'm trying to adaptate the example ex_rmsdb.c for my current project, i managed to run perfectly this way:

Code:

#include <16F876A.h>
#fuses HS,NOWDT,NOLVP
#device adc=8
#use delay(clock=20000000)
#use rs232(baud=9600,xmit=PIN_C6,rcv=PIN_C7,parity=N)

#include <math.h>
int medicion;
int1 newval=FALSE;

#INT_AD
void AD_isr(){
  newval=TRUE;
  medicion=read_adc(ADC_READ_ONLY);
  clear_interrupt(INT_CCP2);
}

void main(){

   setup_port_a( AN0 );
   setup_adc( ADC_CLOCK_DIV_32 );
   set_adc_channel( 0 );
   delay_us(20);
   setup_timer_1( T1_INTERNAL | T1_DIV_BY_1 );
   setup_ccp2( CCP_COMPARE_RESET_TIMER );
   CCP_2=250;
   enable_interrupts( INT_AD );
   enable_interrupts( GLOBAL );

   const long NUM_DATA_POINTS = 60;
   long i;
   int value;
   float voltage;

   printf("Sampling:\r\n");


   while(TRUE)
   {
      voltage=0;
      i=0;
      while(i<NUM_DATA_POINTS)
      {
         if(newval)
         {
            newval=FALSE;
            voltage += (float)medicion*(float)medicion;
            i++;
         }
      }
      voltage /=2601.0;
      voltage = sqrt(voltage/(NUM_DATA_POINTS));
      printf("\r\nInput =  %f V     %f dB\r\n", voltage, 20*log10(voltage));
   }
}


I'm interested to calculate the RMS without the offset, for that i did these small modifications:


Code:

#include <16F876A.h>
#fuses HS,NOWDT,NOLVP
#device adc=8
#use delay(clock=20000000)    //one instruction=0.2us
#use rs232(baud=9600,xmit=PIN_C6,rcv=PIN_C7,parity=N)

#include <math.h>
int medicion;
int1 newval=FALSE;
#define dc_offset 128

#INT_AD
void AD_isr(){
  newval=TRUE;
  medicion=read_adc(ADC_READ_ONLY);
  clear_interrupt(INT_CCP2);
}

void main(){

   setup_port_a( AN0 );
   setup_adc( ADC_CLOCK_DIV_32 );
   set_adc_channel( 0 );
   delay_us(20);
   setup_timer_1( T1_INTERNAL | T1_DIV_BY_1 );
   setup_ccp2( CCP_COMPARE_RESET_TIMER );
   CCP_2=250;
   enable_interrupts( INT_AD );
   enable_interrupts( GLOBAL );

   const long NUM_DATA_POINTS = 60;
   long i;
   int value;
   float voltage;

   printf("Sampling:\r\n");


   while(TRUE)
   {
      voltage=0;
      i=0;
      while(i<NUM_DATA_POINTS)
      {
         if(newval)
         {
            newval=FALSE;
            medicion=medicion-dc_offset;
            voltage += (float)medicion*(float)medicion;
            i++;
         }
      }
      voltage /=2601.0;
      voltage = sqrt(voltage/(NUM_DATA_POINTS));
      printf("\r\nInput =  %f V     %f dB\r\n", voltage, 20*log10(voltage));
   }
}


This second code, doesn't work.

For an input -2.5 ~ 2.5 V , shifted to 0 ~ 5 V, i get values around 2.7 V, i think it should be around 1.77 V.
_________________
Luis,
IEEE Member at University of Zulia
Electronics Engineering Student at URBE
Ttelmah



Joined: 11 Mar 2010
Posts: 19195

View user's profile Send private message

PostPosted: Sun Aug 11, 2019 12:01 pm     Reply with quote

Are you sure your voltage is genuinely biased to 2.5v midpoint?. It'll
give too high a result if the bias is wrong.

Calculate back. I assume you are saying that it is giving a value
of 2.7 for the voltage value?. If so, then (2.7^2)*60 = 437.4
437.4 * 2601 = 1137677.
Divide this by the number of samples:
1137677/60 = 18961.
Square root of this is 137.
So it is averaging a reading of about 137 most of the time, which suggests
to me that the bias is not genuinely to the centre of the supply...
temtronic



Joined: 01 Jul 2010
Posts: 9081
Location: Greensville,Ontario

View user's profile Send private message

PostPosted: Sun Aug 11, 2019 3:14 pm     Reply with quote

Other possible areas of errors can be
... the actual AC to DC converter hardware. Remember you can only give the PIC ADC a positive voltage.

VDD shouldn't be used as the Vref source, it's best to use a voltage reference device. If you re using a simple 2 resistor divider, both should be 'matched' within .1%. The 1% resistors may be just a 'little' off so your 1/2VDD is just a 'little' high.

Check the power supply and Vref for noise over several minutes of operation. Perhaps there's some 'noise' aka EMI that's affecting the readings.

You're only using 8 bit mode so a bit is about 20mv. Wouldn't take much 'noise' to make a 2-3 bit error.
luisjoserod



Joined: 10 Jan 2019
Posts: 16
Location: Maracaibo, Venezuela

View user's profile Send private message Send e-mail Visit poster's website

PostPosted: Sun Aug 11, 2019 4:39 pm     Reply with quote

Wink Thanks for your beautiful answers,

By now i'm working only on the simulator so there's no noise related events.
Following your suggestions, i just have done a test.
Instead of calculating the rms, i'd sent via serial those 60 samples to a device.
The graph looks as it should be (biased on 2.5 V midpoint):



Please note:

Arrow There's a rail-to-rail Op-Amp based circuit pre-conditioning the signal for the ADC channel.
I've checked it's output with a virtual oscilloscope and looks like the one i'd attached here...OK.

Arrow For any DC constant input printing individual samples levels and
printing samples with different arbitrary levels substracted gives consistent results...OK.

Question For a 2.5V DC constant input the first code gives appropiate RMS value of 2.5V,
but the second code gives around 4.7 V RMS, but if i change 128 by 127, i get solid 0.27 V RMS. What does it mean ?

Rolling Eyes
_________________
Luis,
IEEE Member at University of Zulia
Electronics Engineering Student at URBE
temtronic



Joined: 01 Jul 2010
Posts: 9081
Location: Greensville,Ontario

View user's profile Send private message

PostPosted: Sun Aug 11, 2019 5:50 pm     Reply with quote

It probably means the simulator is busted !
Post the name/version of the 'simulator'.
If it's Proteus, for sure it's defective... well known to NOT simulate PICs very well.
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Sun Aug 11, 2019 5:52 pm     Reply with quote

Quote:
#INT_AD
void AD_isr(){
newval=TRUE;
medicion=read_adc(ADC_READ_ONLY);
clear_interrupt(INT_CCP2);
}

if(newval)
{
newval=FALSE;
medicion = medicion - dc_offset;
voltage += (float)medicion*(float)medicion;
i++;
}

How is this code above supposed to work ? Suppose read_adc() reads a value of 0 ?
Then, your math you do this:
Quote:
medicion = medicion - dc_offset;

which results in
medicion = 0 - 128;
So medicion is supposedly -128. But you have declared medicion as:
Quote:
int medicion;

In CCS, an 'int' is an unsigned 8-bit integer. It can only have positive
values from 0 to 255. It can't be negative. So your math is not correct.

If you want signed 8-bit values, then you must declare it like this in CCS:
Code:
signed int8 medicion;

Then it can be from -128 to +127.

But then, in your #define statement, you have this:
Quote:
#define dc_offset 128

If you are working with 8 bit signed values, 128 will not exist.
You need to do this:
Code:
unsigned int16 medicion;

Then you can have values from -32768 to +32767.
Ttelmah



Joined: 11 Mar 2010
Posts: 19195

View user's profile Send private message

PostPosted: Mon Aug 12, 2019 12:12 am     Reply with quote

And (of course) when the maths wraps, you get a +ve value. So 0-1
= 0xFF.
So this is what gives the much too large result.....

He actually needs:
Code:

signed int16 medicion;


Using unsigned, even one count of wrap, will result in huge numeric overflows.
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Mon Aug 12, 2019 1:31 am     Reply with quote

You're right. That was a typo by me.
luisjoserod



Joined: 10 Jan 2019
Posts: 16
Location: Maracaibo, Venezuela

View user's profile Send private message Send e-mail Visit poster's website

PostPosted: Mon Aug 12, 2019 2:44 am     Reply with quote

Thanks guys! Problem solved Very Happy

for a sinusoidal input @ 1khz without offset gives 1.82, 1.66, 1.77, 1.79. Any suggestion about how to make it more solid/stable?
_________________
Luis,
IEEE Member at University of Zulia
Electronics Engineering Student at URBE
temtronic



Joined: 01 Jul 2010
Posts: 9081
Location: Greensville,Ontario

View user's profile Send private message

PostPosted: Mon Aug 12, 2019 4:33 am     Reply with quote

Start at the source ! What's generating the 1KHz signal ? Scope and record that and see if IT is actually a stable signal.
If it's within your parameters, then thoroughly test the ADC 'front end' ( the AC to DC subsection). Again scope and record for several hours, sift through the data for 'bad' readings.
If that's OK, then check the PCB for correct grounding, filtering,stable power, etc.
If that's OK, then consider the code. 8 bit ADC doesn't have much room for minor error( only 256 bits), Using 10 bt mode should result in a better result IF you follow proper analog design proceedures. Done correctly you can reduce error rate to +-1 bit for a 16 bit ADC.
Jay
Ttelmah



Joined: 11 Mar 2010
Posts: 19195

View user's profile Send private message

PostPosted: Mon Aug 12, 2019 7:03 am     Reply with quote

The big issue, is that as currently written, the sampling is starting/ending
asynchronously to the waveform, and is sampling potentially for just
3 1/3rd cycles (of the incoming waveform). So you get different values,
depending 'where' in the cycle you start. There is also a potential timing
error (newval should really be cleared before starting).
The original example uses 3000 sampling points to reduce this effect to
almost nothing. The version he is posting has reduced this to just 60 samples.

Looking at the version he posts, I also doubt if the chip can actually perform
the floating point multiplication, in the time between te CCP triggers he
is giving. A float multiplication (without the casts), takes 179 instructions,
add time for the casts and to get into and out of the interrupt (a total of
perhaps another 100 instructions), and this is not going to fit in the 250
instruction times being allowed between CCP triggers....

Honestly, to ensure sampling time, don't use an interrupt handler, just loop
till INT_AD goes true, then clear this and read. Much faster.
luisjoserod



Joined: 10 Jan 2019
Posts: 16
Location: Maracaibo, Venezuela

View user's profile Send private message Send e-mail Visit poster's website

PostPosted: Wed Aug 14, 2019 5:20 am     Reply with quote

Thanks temtronic,

I will consider your recommendations when doing the practice on the ProjectBoard.

Ttelmah, you're right.
I'd been reading a lot these days, but not sure if i understand what do you suggest.
The idea is using the ADC interrupt without ADC interrupt handler or CCP?
I'd never done such a thing before, which post should i look into, or any little example?

Thanks.
_________________
Luis,
IEEE Member at University of Zulia
Electronics Engineering Student at URBE
Ttelmah



Joined: 11 Mar 2010
Posts: 19195

View user's profile Send private message

PostPosted: Wed Aug 14, 2019 8:48 am     Reply with quote

Use the CCP. It then starts the ADC converting. However don't enable the
ADC interrupt or generate an IRQ handler. Then in code:

Code:

    while (!interrupt_active(INT_ADC))
        ; //wait here for the ADC to trigger
    clear_interrupts(INT_ADC);
    //Now have the code to read the adc etc..


The key is that you get to the code after the while, at most just a couple of
instructions after the interrupt triggers, and without the cost of having to
call and return from the interrupt handler. Result about 70 instructions
faster!...

Then don't cast medicion to float, but int32. Do the square in int32.
I'd suggest summing this in an int32 called perhaps vsum.

So:
Code:

    while (!interrupt_active(INT_ADC))
        ; //wait here for the ADC to trigger
    clear_interrupts(INT_CCP);   
    clear_interrupts(INT_ADC);
    medicion=read_adc(ADC_READ_ONLY);
    vsum+=(int32)medicion*medicion; //single cast is all that is needed

    //then once you have the sum

    voltage=vsum/2601.0; //The .0 here forces float arithmetic to be used


Then if you increase the NUM_DATA_POINTS to perhaps 1000, you should
find the variable results disappear.
luisjoserod



Joined: 10 Jan 2019
Posts: 16
Location: Maracaibo, Venezuela

View user's profile Send private message Send e-mail Visit poster's website

PostPosted: Wed Aug 14, 2019 3:40 pm     Reply with quote

Cool Here's the optimized code:
Code:
#include <16F876A.h>
#fuses HS,NOWDT,NOLVP
#device adc=8
#use delay(clock=20000000)
#use rs232(baud=9600,xmit=PIN_C6,rcv=PIN_C7,parity=N)
#define dc_offset 128
#include <math.h>

void main(){
   setup_port_a( AN0 );
   setup_adc( ADC_CLOCK_DIV_32 );
   set_adc_channel( 0 );
   delay_us(20);
   setup_timer_1( T1_INTERNAL | T1_DIV_BY_1 );
   setup_ccp2( CCP_COMPARE_RESET_TIMER );
   CCP_2=250;
   enable_interrupts( GLOBAL );  // Is this necessary? i don't see any difference on the output
   signed int16 medicion;
   const long NUM_DATA_POINTS = 1000;
   long i;
   int32 vsum;
   float voltage;

   printf("Sampling:\r\n");
   while(TRUE)
   {
      vsum=0;
      i=0;
      while(i<NUM_DATA_POINTS)
      {
         while (!interrupt_active(INT_AD))
         ;
         clear_interrupt(INT_CCP2);   
         clear_interrupt(INT_AD);
         medicion=read_adc(ADC_READ_ONLY);
         medicion-=dc_offset;
         vsum+=(int32)medicion*medicion;
         i++;
      }
      voltage = vsum/2601.0;
      voltage = sqrt(voltage/(NUM_DATA_POINTS));
      printf("\r\nInput =  %f V RMS    %f dB\r\n", voltage, 20*log10(voltage));
   }
}

The output is more solid: 1.7, 1.71, and 1.72.
But still far away from 1.77.

Please note:
Exclamation Also tried NUM_DATA_POINTS=3000 with same results.
Exclamation Previous tests were checked again, the problem is only present when adding the line:
Quote:
medicion-=dc_offset;

_________________
Luis,
IEEE Member at University of Zulia
Electronics Engineering Student at URBE
temtronic



Joined: 01 Jul 2010
Posts: 9081
Location: Greensville,Ontario

View user's profile Send private message

PostPosted: Wed Aug 14, 2019 3:55 pm     Reply with quote

Check the source of the signal as well, record all variables going through the 'math'. That way you can download to say Excel (spreadsheet), do more math there and compare the numbers, step by step.
There could be some 'funny' math error, maybe due to compiler version, but you'll never see WHY, unless you look at all the numbers.

Hint: If you send the numbers in CSV format to a 'terminal program', it can store them without any loss or performance. Simply open the stored data file in Excel and 'magically' all the numbers appear in order making it easy to process.

Jay
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