3
\$\begingroup\$

I am trying to display the voltage value on my specific GPIO pin. I use PIC18F26K83. I can read ADC value. ( I tested it and it gives correct results.) ADC_read() function gives me 12 bit number. Now in order to find the voltage value I need to follow this calculation:

adc_value*(4.9)*(11) / 4096 

My voltage reference is 4.9 V and I have a voltage divider 1/11. So I need to multiply the adc_value I get, with this:
4.9*11/4096 = 0.01315917968.
But when I multiply them like this, I get something incorrect:

int adc_value= adc_Read(0);
adc_value=adc_value*0.01315917968;

I supposed to get something around 11. ADC_Value is around 1100. But the result of this calculation becomes 2.... Where am I wrong with this calculation?

\$\endgroup\$
  • \$\begingroup\$ What is the input voltage value? \$\endgroup\$ – Chu Apr 24 at 6:15
  • \$\begingroup\$ It is 15 V and I get 1140 from ADC_read(); function. But when I do the multiplication I get something incorrect. Maybe this value ( 0.01315917968), has too many fractional parts? That is why it cannot make the calculation? \$\endgroup\$ – Günkut Ağabeyoğlu Apr 24 at 6:19
  • 2
    \$\begingroup\$ Did you actually try the current answers and it solved the problem? The PIC can do arbitrary large floating point calculations just fine, that's what a processor does. Are you performing this calculation in the interrupt? \$\endgroup\$ – pipe Apr 24 at 7:38
  • \$\begingroup\$ Actually, I could not multiply with the value 0.01315917968, eventhough I chose adc_value to float or double. So I just decided to divide the adc_value by 76, that makes me lose some accuracy but that was the only option worked for me. \$\endgroup\$ – Günkut Ağabeyoğlu Apr 24 at 7:42
  • \$\begingroup\$ "Actually, I could not multiply with the value 0.01315917968" - but your question says you did, and the result was 2. What did you really see? \$\endgroup\$ – Dmitry Grigoryev Apr 24 at 11:55
8
\$\begingroup\$

Try dividing by 1/0.013159 => 76 instead.

8-bit microcontrollers are not too happy about floating point. Granted 76 will be a little off (14 instead of 14.47) so you could try rewriting your formula to integer operations and see if you cant get it better than just a plain 76. Divisions may also be a bit sketchy depending on your speed requirement.

You can also multiply with 10 or 100 to get more precision into your values. There is some margin on that 12 bit value stored in a 16 bit integer, enough to multiply by 10 to get a decimal digit on your final value.

\$\endgroup\$
  • 2
    \$\begingroup\$ A 12 bit value stored in a 16 bit integer may be multiplied with 10 or 16 without loosing bits of ADC data. But only 8 or 9 bits of ADC data may be multiplied by 100. \$\endgroup\$ – Uwe Apr 24 at 8:34
  • \$\begingroup\$ You are absolutely correct, I blame it being early in the morning when I answered. \$\endgroup\$ – r_ahlskog Apr 24 at 8:37
11
\$\begingroup\$

Where am I wrong with this calculation?

To add to the other answers, and to come at this from a different direction:

The PIC18F26K83 has a hardware multiplier, but no hardware divider, nor a floating-point unit. This means that any calculations involving division or non-integers will pull in library functions to do the maths in software. Compared to an equivalent calculation which avoids these things (if that's possible), this will increase the size of the program (unless the library functions are already used elsewhere) and make the calculation slower.

If space or time are important, then it's worth considering alternatives. In this instance we want to calculate:

adc_value*(4.9)*(11) / 4096 

For the top line, we can avoid losing precision by using tenths of a volt as the unit, rather than volts, giving us (with a little rearranging):

(adc_value * (10 * 4.9 * 11)) / 4096 

which is the same as:

(adc_value * 539) / 4096 

For the bottom line, we note that dividing by 4096 is the same as right-shifting by 12 (because 4096 = 2^12). This gives us:

(adc_value * 539) >> 12

(This will round down; if we want to round to the nearest integer, see below)

So now we have a calculation which avoids both non-integers and division.

Plugging in the values mentions in a comment to the question:

(1140 * 539) >> 12 = 150

150 in tenths of a volt = 15.0 V, which is the expected answer.

If we want the value in volts later, we can divide by 10 at that point - but not before. For internal calculations, it may be better to keep it in tenths of a volt to avoid losing any precision.


If we want to round to the nearest integer, the trick is to add to the top of the fraction half the value of the bottom of the fraction. In this case:

(x + 2048) >> 12

So if x is between 0 and 2047, this will output 0. If is x is between 2048 and (2047 + 4096), this will output 1. So the final calcuation is:

((adc_value * 539) + 2048) >> 12
\$\endgroup\$
  • 1
    \$\begingroup\$ Shifting rather than dividing is obfuscation and does not affect the actual output of the compiler if you are using a competent compiler. In fact, most of them have an unbelievable amount of number theory built in, specifically for avoiding actually executing divisions by most fixed divisors on all architectures. \$\endgroup\$ – Yet Another User Apr 24 at 21:49
9
\$\begingroup\$
adc_value=adc_value*0.01315917968;

This is a floating poínt calculation and you try to put the result in an int value. Better to work with a complete int calculation on such a small controller. If you really want to use float, you had to work with a float variable. e.g.

float   ADC_complete;
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.