2
\$\begingroup\$

I am trying to build a simple ohmmeter by creating a voltage divider and then using an analog pin on a PIC18F2550 to read the output voltage and determine the ohmic value of one of the resistors. I am using a 20 Mhz oscillator for the PIC's clock, and the whole circuit runs off of a LM7805 regulator. I use a permanent 10K ohm resistor as resistor 2 in my divider, and solve for the value of the first resistor. I am reading the value off of PIN AN0. The problem is that the value read are wildly inaccurate and I am unsure what the problem is. What could I do to get more accurate readings?

Here is my code for XC8 compiler

#include <stdio.h>
#include <stdlib.h>
#include <xc.h>
#include <string.h>

#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator (HS))
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor disabled)
#pragma config IESO = OFF       // Internal/External Oscillator Switchover bit (Oscillator Switchover mode disabled)
#pragma config PWRT = OFF       // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOR = OFF        // Brown-out Reset Enable bits (Brown-out Reset disabled in hardware and software)
#pragma config BORV = 0         // Brown-out Reset Voltage bits (Maximum setting)
#pragma config VREGEN = OFF     // USB Voltage Regulator Enable bit (USB voltage regulator disabled)
#pragma config WDT = OFF        // Watchdog Timer Enable bit (WDT disabled (control is placed on the SWDTEN bit))
#pragma config CCP2MX = OFF      // CCP2 MUX bit (CCP2 input/output is multiplexed with RC1)
#pragma config PBADEN = OFF      // PORTB A/D Enable bit (PORTB<4:0> pins are configured as analog input channels on Reset)
#pragma config LPT1OSC = OFF    // Low-Power Timer 1 Oscillator Enable bit (Timer1 configured for higher power operation)
#pragma config MCLRE = OFF      // MCLR Pin Enable bit (RE3 input pin enabled; MCLR pin disabled)
#pragma config STVREN = OFF     // Stack Full/Underflow Reset Enable bit (Stack full/underflow will not cause Reset)
#pragma config LVP = OFF        // Single-Supply ICSP Enable bit (Single-Supply ICSP disabled)
#pragma config XINST = OFF      // Extended Instruction Set Enable bit (Instruction set extension and Indexed Addressing mode disabled (Legacy mode))
#pragma config CP0 = OFF        // Code Protection bit (Block 0 (000800-001FFFh) is not code-protected)
#pragma config CP1 = OFF        // Code Protection bit (Block 1 (002000-003FFFh) is not code-protected)
#pragma config CP2 = OFF        // Code Protection bit (Block 2 (004000-005FFFh) is not code-protected)
#pragma config CP3 = OFF        // Code Protection bit (Block 3 (006000-007FFFh) is not code-protected)
#pragma config CPB = OFF        // Boot Block Code Protection bit (Boot block (000000-0007FFh) is not code-protected)
#pragma config CPD = OFF        // Data EEPROM Code Protection bit (Data EEPROM is not code-protected)
#pragma config WRT0 = OFF       // Write Protection bit (Block 0 (000800-001FFFh) is not write-protected)
#pragma config WRT1 = OFF       // Write Protection bit (Block 1 (002000-003FFFh) is not write-protected)
#pragma config WRT2 = OFF       // Write Protection bit (Block 2 (004000-005FFFh) is not write-protected)
#pragma config WRT3 = OFF       // Write Protection bit (Block 3 (006000-007FFFh) is not write-protected)
#pragma config WRTC = OFF       // Configuration Register Write Protection bit (Configuration registers (300000-3000FFh) are not write-protected)
#pragma config WRTB = OFF       // Boot Block Write Protection bit (Boot block (000000-0007FFh) is not write-protected)
#pragma config WRTD = OFF       // Data EEPROM Write Protection bit (Data EEPROM is not write-protected)
#pragma config EBTR0 = OFF      // Table Read Protection bit (Block 0 (000800-001FFFh) is not protected from table reads executed in other blocks)
#pragma config EBTR1 = OFF      // Table Read Protection bit (Block 1 (002000-003FFFh) is not protected from table reads executed in other blocks)
#pragma config EBTR2 = OFF      // Table Read Protection bit (Block 2 (004000-005FFFh) is not protected from table reads executed in other blocks)
#pragma config EBTR3 = OFF      // Table Read Protection bit (Block 3 (006000-007FFFh) is not protected from table reads executed in other blocks)
#pragma config EBTRB = OFF      // Boot Block Table Read Protection bit (Boot block (000000-0007FFh) is not protected from table reads executed in other blocks)

#define _XTAL_FREQ 20000000

int main()
{
    ADCON0 = 0b00000001;
    ADCON1 = 0b00001110;
    ADCON2 = 0b10001010;
    TRISA0 = 1;

    while(1)
    {   
        GO_DONE = 1;
        while(GO_DONE);
        unsigned int adc = ((ADRESH<<2) | ADRESL);
        const float maxAdcBits = 1023.0f;
        const float vin = 5.0f;
        const float resistance2 = 10000.0f;
        float voltsPerBit = (vin / maxAdcBits);
        float vout = adc * voltsPerBit;
        float ohms = ((resistance2 * vin) - (resistance2 * vout)) / vout;
    }
}
\$\endgroup\$
  • \$\begingroup\$ How inaccurate is "wildly inaccurate"? Numbers please. How many samples are you averaging? Are you making ratiometric measurements? Have you looked at the data sheet of the ADC input to see what errors are likely to be seen such as offset, gain, INL etc.? \$\endgroup\$ – Andy aka Mar 22 '15 at 13:12
  • \$\begingroup\$ A 10K resistor consistently reads as over 30K ohms. And a 470 ohm resistor reads as 10k ohms. I would not expect to see this level of error on a 10 bit adc. \$\endgroup\$ – popgalop Mar 22 '15 at 13:23
  • \$\begingroup\$ Majenko's answer suggests that the problem is in the software. So simplify it until you get something that works right, then build back up. For example, instead of using a voltage divider, hook a voltage source to the input. Once you've gotten the code to read input voltages correctly, go back to the voltage divider and add the code to figure out resistance. \$\endgroup\$ – Pete Becker Mar 22 '15 at 16:31
3
\$\begingroup\$

You are joining your HIGH and LOW byte values together wrong:

    unsigned int adc = ((ADRESH<<2) | ADRESL);

You have a 10-bit result, 2 bits in ADRESH and 8 bits in ADRESL.

Say the two values are

ADRESH = 0b00000010
ADRESL = 0b10101010

You left shift the high one by 2 places, so it becomes:

ADRESH = 0b00001000
ADRESL = 0b10101010

Now you OR the two values together.

ADRESH = 0b00001000
ADRESL = 0b10101010
    OR = 0b10101010

No wonder the values are wrong.

You need to first convert the two values to 16-bit (to ensure the compiler knows to work with 16 bits of value, not 8):

unsigned int hval = ADRESH;
unsigned int lval = ADRESL;

That makes the values:

hval = 0b0000000000000010
lval = 0b0000000010101010

Then you need to shift the high portion by 8 bits so it falls to the left of the lower value:

hval = 0b0000001000000000
lval = 0b0000000010101010

And then finally OR them together:

hval = 0b0000001000000000
lval = 0b0000000010101010
  OR = 0b0000001010101010

So your code for generating the full value might look something like this:

unsigned int hval = ADRESH;
unsigned int lval = ADRESL;
unsigned int adc = (hval << 8) | lval;

Of course, that is slightly wasteful on variables, and you could compress it into one line using casting to ensure enough shift room:

unsigned int adc = ((unsigned int)ADRESH << 8) | ADRESL;

Oh, and while you're at it, drop your dependence on floating point. It causes your program to be both very resource hungry, and to be somewhat slow. Work in fixed point (integer) values instead.

For instance, calculate the millivolts, not the volts:

unsigned long mv = 5000 * adc / 1023;

Then from that you calculate the resistor's value:

unsigned long ohms = ((10000 * 5000) - (10000 * mv)) / mv;

And all without a single floating point value.

\$\endgroup\$
  • \$\begingroup\$ I tried this code, but the value are still wildly inaccurate. \$\endgroup\$ – popgalop Mar 22 '15 at 13:49
  • \$\begingroup\$ Try printing the raw ADC values instead of your calculated ones. Connect a 10KΩ resistor as R1 and see what result you get - it should be around 512-ish. \$\endgroup\$ – Majenko Mar 22 '15 at 13:52
  • \$\begingroup\$ With your formula I get s 506 adc reading, but an ohmic value of 113015350. \$\endgroup\$ – popgalop Mar 22 '15 at 14:00
  • 1
    \$\begingroup\$ It may be integer wrap-around that you're getting now. Change all literals to have "UL" on the end (so 5000UL, 10000UL, 1023UL, etc) \$\endgroup\$ – Majenko Mar 22 '15 at 15:01
  • \$\begingroup\$ @Majenko: Yeah, 5000 * adc is going to overflow a 16-bit unsigned int unless adc is 13 or less (and will go negative, because 5000 is a signed int constant, unless it is 6 or less, which will screw up the division because of sign extension). Using 5000UL * adc should fix it. \$\endgroup\$ – Ilmari Karonen Mar 22 '15 at 19:02

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.