|
|
View previous topic :: View next topic |
Author |
Message |
luisjoserod
Joined: 10 Jan 2019 Posts: 16 Location: Maracaibo, Venezuela
|
About RMS Calculation without offset |
Posted: Sun Aug 11, 2019 11:19 am |
|
|
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: 19537
|
|
Posted: Sun Aug 11, 2019 12:01 pm |
|
|
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: 9241 Location: Greensville,Ontario
|
|
Posted: Sun Aug 11, 2019 3:14 pm |
|
|
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
|
|
Posted: Sun Aug 11, 2019 4:39 pm |
|
|
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:
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.
For any DC constant input printing individual samples levels and
printing samples with different arbitrary levels substracted gives consistent results...OK.
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 ?
_________________ Luis,
IEEE Member at University of Zulia
Electronics Engineering Student at URBE |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9241 Location: Greensville,Ontario
|
|
Posted: Sun Aug 11, 2019 5:50 pm |
|
|
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
|
|
Posted: Sun Aug 11, 2019 5:52 pm |
|
|
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:
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: 19537
|
|
Posted: Mon Aug 12, 2019 12:12 am |
|
|
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
|
|
Posted: Mon Aug 12, 2019 1:31 am |
|
|
You're right. That was a typo by me. |
|
|
luisjoserod
Joined: 10 Jan 2019 Posts: 16 Location: Maracaibo, Venezuela
|
|
Posted: Mon Aug 12, 2019 2:44 am |
|
|
Thanks guys! Problem solved
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: 9241 Location: Greensville,Ontario
|
|
Posted: Mon Aug 12, 2019 4:33 am |
|
|
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: 19537
|
|
Posted: Mon Aug 12, 2019 7:03 am |
|
|
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
|
|
Posted: Wed Aug 14, 2019 5:20 am |
|
|
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: 19537
|
|
Posted: Wed Aug 14, 2019 8:48 am |
|
|
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
|
|
Posted: Wed Aug 14, 2019 3:40 pm |
|
|
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:
Also tried NUM_DATA_POINTS=3000 with same results.
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: 9241 Location: Greensville,Ontario
|
|
Posted: Wed Aug 14, 2019 3:55 pm |
|
|
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 |
|
|
|
|
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
|