El ohmímetro no funciona como se esperaba

Estoy tratando de construir un ohmímetro simple creando un divisor de voltaje y luego usando un pin analógico en un PIC18F2550 para leer el voltaje de salida y determinar el valor óhmico de una de las resistencias. Estoy usando un oscilador de 20 Mhz para el reloj del PIC, y todo el circuito funciona con un regulador LM7805. Utilizo una resistencia permanente de 10K ohmios como resistencia 2 en mi divisor y resuelvo el valor de la primera resistencia. Estoy leyendo el valor del PIN AN0. El problema es que el valor leído es muy impreciso y no estoy seguro de cuál es el problema. ¿Qué podría hacer para obtener lecturas más precisas?

Aquí está mi código para el compilador XC8

#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;
    }
}
¿Qué tan inexacto es "tremendamente inexacto"? Números por favor. ¿Cuántas muestras está promediando? ¿Estás haciendo medidas radiométricas? ¿Ha mirado la hoja de datos de la entrada ADC para ver qué errores es probable que se vean, como compensación, ganancia, INL, etc.?
Una resistencia de 10K se lee constantemente como más de 30K ohmios. Y una resistencia de 470 ohmios se lee como 10k ohmios. No esperaría ver este nivel de error en un adc de 10 bits.
La respuesta de Majenko sugiere que el problema está en el software. Así que simplifique hasta que obtenga algo que funcione bien, luego vuelva a construir. Por ejemplo, en lugar de usar un divisor de voltaje, conecte una fuente de voltaje a la entrada. Una vez que haya obtenido el código para leer los voltajes de entrada correctamente, regrese al divisor de voltaje y agregue el código para calcular la resistencia.

Respuestas (1)

Estás uniendo mal los valores de byte ALTO y BAJO:

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

Tiene un resultado de 10 bits, 2 bits en ADRESH y 8 bits en ADRESL.

Digamos que los dos valores son

ADRESH = 0b00000010
ADRESL = 0b10101010

Desplazaste a la izquierda el alto por 2 lugares, por lo que se convierte en:

ADRESH = 0b00001000
ADRESL = 0b10101010

Ahora usted O los dos valores juntos.

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

No es de extrañar que los valores estén equivocados.

Primero debe convertir los dos valores a 16 bits (para asegurarse de que el compilador sepa trabajar con 16 bits de valor, no con 8):

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

Eso hace que los valores:

hval = 0b0000000000000010
lval = 0b0000000010101010

Luego, debe cambiar la parte alta en 8 bits para que caiga a la izquierda del valor más bajo:

hval = 0b0000001000000000
lval = 0b0000000010101010

Y luego finalmente OR juntos:

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

Entonces, su código para generar el valor completo podría verse así:

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

Por supuesto, eso es un desperdicio de variables, y podría comprimirlo en una sola línea utilizando la conversión para garantizar suficiente espacio de cambio:

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

Ah, y ya que estás en eso, deja de depender del punto flotante. Hace que su programa consuma muchos recursos y sea algo lento. Trabaje en valores de punto fijo (enteros) en su lugar.

Por ejemplo, calcule los milivoltios, no los voltios:

unsigned long mv = 5000 * adc / 1023;

Luego, a partir de eso, calcula el valor de la resistencia:

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

Y todo sin un solo valor de punto flotante.

Probé este código, pero el valor sigue siendo muy inexacto.
Intente imprimir los valores ADC sin procesar en lugar de los calculados. Conecte una resistencia de 10 KΩ como R1 y vea qué resultado obtiene: debería ser alrededor de 512-ish.
Con su fórmula obtengo una lectura de s 506 adc, pero un valor óhmico de 113015350.
Puede ser un ajuste de enteros lo que está obteniendo ahora. Cambie todos los literales para que tengan "UL" al final (por lo tanto, 5000UL, 10000UL, 1023UL, etc.)
@Majenko: Sí, 5000 * adcse desbordará un 16 bits unsigned inta menos que adcsea 13 o menos (y será negativo, porque 5000es una intconstante con signo, a menos que sea 6 o menos, lo que arruinará la división debido a la extensión del signo). Usar 5000UL * adcdebería arreglarlo.