Electronic – Ohmmeter not working as expected

adcanalogpicvoltage dividerxc8

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;
    }
}

Best Answer

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.