Ohmmeter funktioniert nicht wie erwartet

Ich versuche, ein einfaches Ohmmeter zu bauen, indem ich einen Spannungsteiler erstelle und dann einen analogen Pin an einem PIC18F2550 verwende, um die Ausgangsspannung zu lesen und den Ohmwert eines der Widerstände zu bestimmen. Ich verwende einen 20-MHz-Oszillator für die Uhr des PIC, und die gesamte Schaltung läuft von einem LM7805-Regler ab. Ich verwende einen permanenten 10K-Ohm-Widerstand als Widerstand 2 in meinem Teiler und löse nach dem Wert des ersten Widerstands auf. Ich lese den Wert von PIN AN0 ab. Das Problem ist, dass der gelesene Wert sehr ungenau ist und ich nicht sicher bin, was das Problem ist. Was kann ich tun, um genauere Messwerte zu erhalten?

Hier ist mein Code für den 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;
    }
}
Wie ungenau ist „wildly inaccurate“? Zahlen bitte. Wie viele Samples mittelst du? Führen Sie ratiometrische Messungen durch? Haben Sie sich das Datenblatt des ADC-Eingangs angesehen, um zu sehen, welche Fehler wahrscheinlich zu sehen sind, wie z. B. Offset, Verstärkung, INL usw.?
Ein 10K-Widerstand wird durchweg als über 30K Ohm angezeigt. Und ein 470-Ohm-Widerstand liest sich als 10 kOhm. Ich würde nicht erwarten, diese Fehlerstufe auf einem 10-Bit-ADC zu sehen.
Majenkos Antwort legt nahe, dass das Problem in der Software liegt. Vereinfache es also, bis du etwas hast, das richtig funktioniert, und baue es dann wieder auf. Anstatt beispielsweise einen Spannungsteiler zu verwenden, schließen Sie eine Spannungsquelle an den Eingang an. Sobald Sie den Code zum korrekten Lesen der Eingangsspannungen erhalten haben, gehen Sie zurück zum Spannungsteiler und fügen Sie den Code hinzu, um den Widerstand herauszufinden.

Antworten (1)

Sie verbinden Ihre HIGH- und LOW-Byte-Werte falsch miteinander:

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

Sie haben ein 10-Bit-Ergebnis, 2 Bits in ADRESH und 8 Bits in ADRESL.

Angenommen, die beiden Werte sind

ADRESH = 0b00000010
ADRESL = 0b10101010

Sie haben die obere Position um 2 Stellen nach links verschoben, sodass daraus Folgendes wird:

ADRESH = 0b00001000
ADRESL = 0b10101010

Jetzt ODER die beiden Werte zusammen.

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

Kein Wunder, dass die Werte falsch sind.

Sie müssen zuerst die beiden Werte in 16-Bit konvertieren (um sicherzustellen, dass der Compiler weiß, dass er mit 16-Bit-Werten arbeiten soll, nicht mit 8):

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

Das macht die Werte:

hval = 0b0000000000000010
lval = 0b0000000010101010

Dann müssen Sie den hohen Teil um 8 Bit verschieben, damit er links vom niedrigeren Wert liegt:

hval = 0b0000001000000000
lval = 0b0000000010101010

Und dann schließlich ODER sie zusammen:

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

Ihr Code zum Generieren des vollen Werts könnte also etwa so aussehen:

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

Das ist natürlich etwas verschwenderisch für Variablen, und Sie könnten es mit Casting in eine Zeile komprimieren, um genügend Verschiebungsraum zu gewährleisten:

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

Oh, und wenn Sie schon dabei sind, lassen Sie Ihre Abhängigkeit von Gleitkommazahlen fallen. Es bewirkt, dass Ihr Programm sowohl sehr ressourcenhungrig als auch etwas langsam ist. Arbeiten Sie stattdessen mit Festkommawerten (ganzzahligen Werten).

Berechnen Sie zum Beispiel die Millivolt, nicht die Volt:

unsigned long mv = 5000 * adc / 1023;

Daraus berechnen Sie dann den Wert des Widerstands:

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

Und das alles ohne einen einzigen Fließkommawert.

Ich habe diesen Code ausprobiert, aber die Werte sind immer noch sehr ungenau.
Versuchen Sie, die rohen ADC-Werte anstelle Ihrer berechneten zu drucken. Schließen Sie einen 10-kΩ-Widerstand als R1 an und sehen Sie, welches Ergebnis Sie erhalten - es sollte ungefähr 512 betragen.
Mit Ihrer Formel bekomme ich s 506 ADC-Lesung, aber einen ohmschen Wert von 113015350.
Es kann ein Integer-Wrap-Around sein, den Sie jetzt erhalten. Ändern Sie alle Literale so, dass sie "UL" am Ende haben (also 5000UL, 10000UL, 1023UL usw.)
@Majenko: Ja, 5000 * adcwird ein 16-Bit überlaufen, unsigned intes sei denn adc, es ist 13 oder weniger (und wird negativ, weil 5000es eine vorzeichenbehaftete intKonstante ist, es sei denn, es ist 6 oder weniger, was die Division wegen der Vorzeichenerweiterung durcheinander bringt). Die Verwendung 5000UL * adcsollte es beheben.