View previous topic :: View next topic |
Author |
Message |
mictel
Joined: 24 May 2014 Posts: 14
|
RMS Value Calculation Using 12 bit ADC |
Posted: Sat Aug 23, 2014 4:45 pm |
|
|
I changed code example ex_rmsdb.c from 8bit to 12bit ADC using the 16F1789. The algorithm squares the samples then sums them up so the sum gets very large very quick but thought I was OK since the limit on floating point values is 3.4E38 and I am no where near that. but now I am getting screwy results.
So I loaded ex_float.c to learn more about floating point numbers. I noticed that you get bogus results for larger numbers:
Enter first number: 42949650.0
Enter second number: 2.0
The following are formatted with %E
A= 4.294964E+07
B= 2.000000E+00
a + b = 4.294965E+07
a - b = 4.294964E+07
a * b = 8.589929E+07
a / b = 2.147482E+07
The following are formatted with %f
A= -20.48
B= 2.00
a + b = -15.36
a - b = -20.48
a * b = -40.95
a / b = 21474826.24
Note the values are bogus for %f formatted output except for a/b. And a/b is bogus if the numerator is any larger than 42949650.0 when divided by 2.
The CCS help file says float variables can be as large as 3.4e38 obviously not; what am I missing. Searched but could not find the answer. How are large numbers handled, especially when summing 12 bit ADC values?
Thanks, |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9229 Location: Greensville,Ontario
|
|
Posted: Sat Aug 23, 2014 5:56 pm |
|
|
hmmm.. do you get the same 'screwy' numbers if you print using the 'e' format(exponential) instead of 'f' floating ?
also might be a compiler bug, so you should say which version you're using.
hth
jay |
|
|
mictel
Joined: 24 May 2014 Posts: 14
|
|
Posted: Sat Aug 23, 2014 8:02 pm |
|
|
The complier version is PCM 5.026
Both versions of the format are shown. The %E format is rounded but not totally bogus. The problem is when I try to use the %e format in my main program, the complier says I am out of memory! Don't know what is up with that, put it back to %f and all is fine with memory but the numbers are bogus.
Thanks for taking a look. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19520
|
|
Posted: Sun Aug 24, 2014 12:50 am |
|
|
The first thing you've discovered, is that FP maths is large....
There were problems with quite a number of compiler versions if you use %f, without specifying a _width_.
Try %8.0f
I thought these had been fixed in the newer V5 compilers, but it may be that there are still problems when values go over a particular size.
Remember that with FP, you only have just over 6 digits of actual 'accuracy' (actually 21bits), so it will not take much for smaller values to be completely lost when summed to sets that have a large value in them. FP, is a 'shortcut' to allow a wide range of values, but comes at a cost of massively reduced actual accuracy. Since your values fit into a fixed range (as read from the ADC 0 to 4095), you will be much better in terms of space, _and_ accuracy, to do the summing in integer.
Square, is just a value multiplied by itself.
So read the ADC, multiply the reading by itself, and sum this in an int32.
Since the largest value you can have is 4095, you can sum up to 256 values in an int32, and have proper accuracy on this, with less maths space.
Then perform your division (so now the largest value is just 16769025 - which can accurately be represented by the 21 bits available in a float), and a FP square root of this (just cast the integer in the conversion).
The square root code will still be very bulky. You could consider using an integer square root as well (do a search for 'fast integer square root'). This will be much smaller and faster.
Then use fp to actually display the result, or (better...), generate a scaled integer (for instance mV in integer), which can then be output without involving float at all. |
|
|
mictel
Joined: 24 May 2014 Posts: 14
|
|
Posted: Mon Aug 25, 2014 4:46 am |
|
|
Thank you for the explanation and suggestions. Interestingly, it all seems to be working fine even with the analog input voltage up to maximum, 5V.
The large value of the sum of the squares displays wrong but when divided by the ADC resolution (5/2^12) the value is fairly accurate. When divided by the number of samples and the square root is done, the value is also fairly accurate (within 3%) for the test cases I tried with sine, triangle and square waveforms.
Still verifying performance. Good to know alternatives if problems found. Still puzzled why the code does not compile when changing the format from %f to %E saying I am out of memory.
Out of ROM. A segment or the program is too large decode_command
Change back to %f and all is well with plenty of ROM and RAM remaining. |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9229 Location: Greensville,Ontario
|
|
Posted: Mon Aug 25, 2014 5:33 am |
|
|
re: ....
Out of ROM. A segment or the program is too large decode_command
'decode_command' might be 'spanning' two segments of memory.'functions' usually must be within one contiguous 'segment' of memory. At least that's the way it was 'in the beginning'. Perhaps the newer compiler version can handle it ?
It's be interesting to know how LONG it takes to do the FP math vs. scaled integers. If your application is 'time sensitive', FP is NOT the way to go !!
hth
jay |
|
|
mictel
Joined: 24 May 2014 Posts: 14
|
|
Posted: Mon Aug 25, 2014 5:58 am |
|
|
Not sure why changing the format in a printf statement from %f to %E would change memory requirements to be out of ROM.
I did not measure how long it takes for the FP RMS calculation but can say the result is displayed immediately after sending the command without any notable delay. The only thing going on when measuring the RMS voltage is measuring the RMS voltage so speed is not an issue for my application provided the RMS voltage is displayed quickly WRT the user. The processor is running at 32MHz internal clock which of course helps. The frequency of the signal is between 10Hz and 500Hz so that too helps.
I am impressed with the results so far. Input signal; Vs = 100Hz Sine
Vpk=1.0V, Vrms=0.697 to 0.707
Vpk=1.5V, Vrms=1.057 to 1.067
Vpk=2.0V, Vrms=1.407 to 1.417
Vpk=2.5V, Vrms=1.757 to 1.767
Vpk=5.0V, Vrms=3.497 to 3.507 (~1% error) |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19520
|
|
Posted: Mon Aug 25, 2014 7:00 am |
|
|
Remember that you can still have plenty of memory, but run 'out of ROM'.
As Temtronic says, I'd suspect that you will find 'decode_command, is just a few bytes short of a segment size. Then it only has to grow a tiny amount, to make the problem appear. %E, will be a fraction bulkier in several areas, and the change is just enough to trigger the problem....
Just try doing the sum and division in integer. Dead easy to code, and 'pointless' not to. |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9229 Location: Greensville,Ontario
|
|
Posted: Mon Aug 25, 2014 7:13 am |
|
|
re: code...
changing the 'format' of the displayed results, means the compiler uses different code,which can either be bigger or smaller. Think of it as writing on a cereal box. A phrase in English might be 10 words, in French could be 15 and longer words!
Now, if the code is longer...oops...it exceeds the 'segment' length and the 'error' message appear. .From what I understand, individual functions cannot exceed segment size. The entire 'subroutine' must fit into a segment
I like the <1% error ! well with 'spec' of an old guy like me !!
You should try what Mr. t suggest cause sooner or later, you'll really run out of memory or speed....nature of the beast !!
jay |
|
|
newguy
Joined: 24 Jun 2004 Posts: 1908
|
|
Posted: Mon Aug 25, 2014 9:29 am |
|
|
As others have said, abandon floating point entirely. Why? Try creating a blank project that does nothing - use any PIC you wish. Make note of the ROM/RAM used. Then simply declare a float variable and recompile. What happens to the ROM/RAM used?
Remember, the more ROM used, the more instructions that must be executed by the processor, and the slower it will perform. Do yourself a favour and learn how to do it exclusively with integers and your code will be both faster and more precise.
Here's working code that performs a sqrt on an integer:
Code: | unsigned int32 my_sqrt(unsigned int32 n) {
unsigned int32 root = 0;
unsigned int32 bit, trial;
if (n >= 0x10000) {
bit = 0x40000000;
}
else {
bit = 0x4000;
}
do {
trial = root + bit;
if (n >= trial) {
n -= trial;
root = trial + bit;
}
root >>= 1;
bit >>= 2;
} while (bit != 0);
return (root);
} |
|
|
|
mictel
Joined: 24 May 2014 Posts: 14
|
|
Posted: Tue Aug 26, 2014 6:31 am |
|
|
You folks are great! I really appreciate the suggestions and encouragement to consider switching over to integer math. I presently lack the knowledge on how to do that so took the easy way out; FP. Thanks to your guidance and example code that will soon change. |
|
|
mictel
Joined: 24 May 2014 Posts: 14
|
|
Posted: Fri Sep 05, 2014 4:25 pm |
|
|
Ditched the FP math thanks to the knowledge shared here. Now have the 12 bit ADC RMS measurement working with integer math. Did not do a comparison on speed but here is the results for accuracy:
Vs= 100Hz Sine wave
Vpk=1.0V, Vrms = 0.699 to 0.705
Vpk=1.5V, Vrms = 1.0579 to 1.062
Vpk=2.0V, Vrms = 1.4038 to 1.4086
Vpk=2.5V, Vrms = 1.7467 to 1.7626
Vpk=5.0V, Vrms = 3.4948 to 3.5161
WC error was ~1.15% using integer math for a single measurement. Averaging measurements will lower error even further.
The 12 bit ADC takes 52 samples of the 100Hz sine wave operating at 32MHz. Maximum frequency tested was 1kHz; only 6 samples taken and the error goes up to ~7.8%
Thanks again for sharing the knowledge. |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9229 Location: Greensville,Ontario
|
|
Posted: Fri Sep 05, 2014 4:43 pm |
|
|
You should try 'binary numbers' of samples like 8,16,32 maybe 48.
The 'math' should be a lot faster to get average and smaller code as well.
Just food for thought..
jay |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19520
|
|
Posted: Sat Sep 06, 2014 12:35 pm |
|
|
As a comment consider using a modified binary approach.
As you add up (say) 34 values, also record the largest and the smallest value. Then when you have your 34 value sum, subtract the recorded largest and smallest value from the sum, so you have the sum of the 32 values ignoring the largest and smallest. Then divide this by 32.
This gives the 'Olympic' approach (commonly used when scoring to 'throw away' any unusually high or low scores), combined with a binary average. |
|
|
asmboy
Joined: 20 Nov 2007 Posts: 2128 Location: albany ny
|
|
Posted: Sat Sep 06, 2014 2:45 pm |
|
|
i've watched this thread as you made progress, but realized that it is NOT trivial, and really perhaps not even possible to get a correct RMS value
with your code for anything BUT a near perfect sine wave.
Even then, differing frequencies may throw off the result.
As my hero Richard Feynman once famously said:
"the easiest person to fool is always yourself" ;-))
True RMS for any waveform, both uni or bipolar
evaluates to the integrated area under the curve,
for the absolute value of the waveform under measurement.
Or viewed another way: it's the heat value if it were seen across a pure resistor.
Without seeing your circuitry, You appear to be using only the positive PEAK value and finessing the math on the leading assumption its a pure sine.
were you to attempt this on an audio stream, an asymmetric sine wave with a dc offset or an intermittent ac signal with a high peak to average ratio,in all these cases you will find your method to
be wildly wrong, typically on the HIGH side of correct. |
|
|
|