Forum: Mikrocontroller und Digitale Elektronik PIC16F ADC Problem


von pks (Gast)


Lesenswert?

Hallo,
ich versuche mit einem PIC16F1513 mehrere Spannungen zu messen. Leider 
bekomme ich vom ADC nicht(immer) die erwarteten Werte.
Der PIC wird mit 3,3V und internen 16MHz betrieben.

Mein Code sieht auzugsweise wie folgt aus:
1
#include <htc.h>            /* HiTech General Includes */
2
#include <stdint.h>         /* For uint8_t definition */
3
#include <stdbool.h>        /* For true/false definition */
4
5
#include <pic16f1513.h>
6
#include "pic16f151x_api.h"
7
#include "pic16f151x_defines.h"
8
#include "interrupts.h"
9
10
/******************************************************************************/
11
/* Defines                                                                    */
12
/******************************************************************************/
13
14
#define PORT_ID_A 0x0
15
#define PORT_ID_B 0x1
16
#define PORT_ID_C 0x2
17
#define PORT_ID_E 0x3
18
19
#define ADC_CHAN_NONE 0xff
20
21
#define ADC_CHAN_RA0 0x0
22
#define ADC_CHAN_RA1 0x1
23
#define ADC_CHAN_RA2 0x2
24
#define ADC_CHAN_RA3 0x3
25
#define ADC_CHAN_RA4 ADC_CHAN_NONE
26
#define ADC_CHAN_RA5 0x4
27
#define ADC_CHAN_RA6 ADC_CHAN_NONE
28
#define ADC_CHAN_RA7 ADC_CHAN_NONE
29
30
#define ADC_CHAN_RB0 0xc
31
#define ADC_CHAN_RB1 0xa
32
#define ADC_CHAN_RB2 0x8
33
#define ADC_CHAN_RB3 0x9
34
#define ADC_CHAN_RB4 0xb
35
#define ADC_CHAN_RB5 0xd
36
#define ADC_CHAN_RB6 ADC_CHAN_NONE
37
#define ADC_CHAN_RB7 ADC_CHAN_NONE
38
39
#define ADC_CHAN_RC0 ADC_CHAN_NONE
40
#define ADC_CHAN_RC1 ADC_CHAN_NONE
41
#define ADC_CHAN_RC2 0xe
42
#define ADC_CHAN_RC3 0xf
43
#define ADC_CHAN_RC4 0x10
44
#define ADC_CHAN_RC5 0x11
45
#define ADC_CHAN_RC6 0x12
46
#define ADC_CHAN_RC7 0x13
47
48
#define ADC_CHAN_RE3 ADC_CHAN_NONE
49
50
/* Port register struct */
51
typedef struct
52
{
53
    volatile unsigned char* pTris; /* Tristate Register */
54
    volatile unsigned char* pPort; /* State Register */
55
    volatile unsigned char* pAnsel;/* Analog Select Register */
56
    volatile unsigned char* pLat;  /* Latch Register*/
57
}portRegisterStr;
58
59
/* Pin info struct*/
60
typedef struct
61
{
62
    bool canOut;      /* Pin can be output? */
63
    bool canAnalogIn; /* Pin can be analog input? */
64
    uint8_t portId;   /* ID of dedicated port */
65
    uint8_t bitOffs;  /* Pin offset in port registers */
66
    uint8_t adcChId;  /* ID of dedicated ADC channel */
67
}pinInfoStr;
68
69
/* List of ports */
70
static const portRegisterStr portInfoArr[4] =
71
{
72
    {&TRISA, &PORTA, &ANSELA, &LATA}, /* Port A */
73
    {&TRISB, &PORTB, &ANSELB, &LATB}, /* Port B */
74
    {&TRISC, &PORTC, &ANSELC, &LATC}, /* Port C */
75
    {&TRISE, &PORTE, 0, 0}  /* Port E */
76
};
77
78
/* List of pins */
79
static const pinInfoStr pinInfoArr[25] =
80
{
81
    /* Port A */
82
    {true, true,  PORT_ID_A, 0, ADC_CHAN_RA0}, /* Pin RA0 */
83
    {true, true,  PORT_ID_A, 1, ADC_CHAN_RA1}, /* Pin RA1 */
84
    {true, true,  PORT_ID_A, 2, ADC_CHAN_RA2}, /* Pin RA2 */
85
    {true, true,  PORT_ID_A, 3, ADC_CHAN_RA3}, /* Pin RA3 */
86
    {true, false, PORT_ID_A, 4, ADC_CHAN_RA4}, /* Pin RA4 */
87
    {true, true,  PORT_ID_A, 5, ADC_CHAN_RA5}, /* Pin RA5 */
88
    {true, false, PORT_ID_A, 6, ADC_CHAN_RA6}, /* Pin RA4 */
89
    {true, false, PORT_ID_A, 7, ADC_CHAN_RA7}, /* Pin RA5 */
90
    /* Port B */
91
    {true, true,  PORT_ID_B, 0, ADC_CHAN_RB0}, /* Pin RB0 */
92
    {true, true,  PORT_ID_B, 1, ADC_CHAN_RB1}, /* Pin RB1 */
93
    {true, true,  PORT_ID_B, 2, ADC_CHAN_RB2}, /* Pin RB2 */
94
    {true, true,  PORT_ID_B, 3, ADC_CHAN_RB3}, /* Pin RB3 */
95
    {true, true,  PORT_ID_B, 4, ADC_CHAN_RB4}, /* Pin RB4 */
96
    {true, true,  PORT_ID_B, 5, ADC_CHAN_RB5}, /* Pin RB5 */
97
    {true, false, PORT_ID_B, 6, ADC_CHAN_RB6}, /* Pin RB6 */
98
    {true, false, PORT_ID_B, 7, ADC_CHAN_RB7}, /* Pin RB7 */
99
    /* Port C */
100
    {true, false, PORT_ID_C, 0, ADC_CHAN_RC0}, /* Pin RC0 */
101
    {true, false, PORT_ID_C, 1, ADC_CHAN_RC1}, /* Pin RC1 */
102
    {true, true,  PORT_ID_C, 2, ADC_CHAN_RC2}, /* Pin RC2 */
103
    {true, true,  PORT_ID_C, 3, ADC_CHAN_RC3}, /* Pin RC3 */
104
    {true, true,  PORT_ID_C, 4, ADC_CHAN_RC4}, /* Pin RC4 */
105
    {true, true,  PORT_ID_C, 5, ADC_CHAN_RC5}, /* Pin RC5 */
106
    {true, true,  PORT_ID_C, 6, ADC_CHAN_RC6}, /* Pin RC6 */
107
    {true, true,  PORT_ID_C, 7, ADC_CHAN_RC7}, /* Pin RC7 */
108
    /* Port E */
109
    {false, false, PORT_ID_E, 3, ADC_CHAN_RE3}, /* Pin RE3 */
110
};
111
112
/* Timer 0 overflow indcation (set by irq callback)*/
113
static bool timer0Irq = false;
114
115
/*** Set Configuration words ***/
116
/* Disable watchdog, select internal oscillator, disable MCLRE */
117
/* Enable Brown-out reset*/
118
__CONFIG(WDTE_OFF & FOSC_INTOSC & MCLRE_OFF & BOREN_ON);
119
/* Enable low voltage programming,  Set Brown-out high trip point*/
120
__CONFIG(LVP_ON & BORV_HI);
121
122
/* Callback function for timer 0 overflow*/
123
void timer0Callback(void)
124
{
125
    timer0Irq = true;
126
}
127
128
/* Waits for up to 256 us*/
129
void picWait_us(uint8_t usVal)
130
{
131
    TMR0 = ~usVal;
132
    INTCON |= _INTCON_TMR0IE_MASK;
133
    timer0Irq = false;
134
    while(!timer0Irq)
135
        ;
136
}
137
138
/* Init controller */
139
void picInit()
140
{
141
    /* Configure oscillator */
142
    /* Set Internal oscillator to 16 MHz */
143
    OSCCONbits.SCS = 0x2;
144
    OSCCONbits.IRCF = 0xf;
145
    /* Wait for clock stable flag */
146
    while(!(OSCSTAT & _OSCSTAT_HFIOFS_MASK))
147
        ;
148
149
    /**** Configure Timer 0 ****/
150
    /* Connect to internal instruction clock */
151
    OPTION_REG &= ~_OPTION_REG_TMR0CS_MASK;
152
    /* Enable timer prescaler */
153
    OPTION_REG &= ~_OPTION_REG_PSA_MASK;
154
    /* Set prescaler to "001" (1:4) */
155
    OPTION_REGbits.PS = 0x1;
156
157
    /**** Configure IRC ****/
158
    /* Global interrupt enable */
159
    INTCON |= _INTCON_GIE_MASK;
160
    /* Timer 0 interrupt enable */
161
    INTCON |= _INTCON_T0IE_POSN;
162
163
    /**** Configure internal voltage reference ****/
164
    /* Set voltage to 2.048V */
165
    FVRCONbits.ADFVR = 0x2;
166
    /* Fixed volatage enable */
167
    FVRCONbits.FVREN = 1;
168
169
    /**** Clear Port Register ****/
170
    PORTA = 0;
171
    PORTB = 0;
172
    PORTC = 0;
173
    PORTE = 0;
174
175
    /**** Configure ADC ****/
176
    /* Result format: right aligned */
177
    ADCON1 = _ADCON1_ADFM_MASK;
178
    /* Conversion clock: Fosc/64 */
179
    ADCON1bits.ADCS = 0x6;
180
    /* Reference voltage: internal vref */
181
    ADCON1bits.ADPREF = 0x3;
182
183
    /* Register callback function for timer 0 overflow */
184
    registerIsrCallback(timer0Callback);
185
}
186
187
/* Configure pin*/
188
void picConfigPin(uint8_t pinId, uint8_t pinSet)
189
{
190
    switch(pinSet)
191
    {
192
        /* Logical input */
193
        case LOGIC_IN:
194
            /* Set TRIS register bit to 1 (input) */
195
            *(portInfoArr[pinInfoArr[pinId].portId].pTris)
196
                    |= (1<<pinInfoArr[pinId].bitOffs);
197
            break;
198
        /* Logical output */
199
        case LOGIC_OUT:
200
            /* Set TRIS register to 0 (output) */
201
            *(portInfoArr[pinInfoArr[pinId].portId].pTris)
202
                    &= ~(1<<pinInfoArr[pinId].bitOffs);
203
            break;
204
        /* Analog input */
205
        case ANALOG_IN:
206
            /* Set TRIS register bit to 1 (input) */
207
            *(portInfoArr[pinInfoArr[pinId].portId].pTris)
208
                    |= (1<<pinInfoArr[pinId].bitOffs);
209
            /* Set ANSEL register bit to 1 */
210
            *(portInfoArr[pinInfoArr[pinId].portId].pAnsel)
211
                    |= (1<<pinInfoArr[pinId].bitOffs);
212
            break;
213
        default : ;
214
    }
215
}
216
217
218
/* Get adc value (0...1023) of selected pin */
219
uint16_t picReadAdc(uint8_t pinId)
220
{
221
    int16_t res = 0;
222
    uint8_t pin = pinId;
223
224
    /* Select adc channel */
225
    ADCON0bits.CHS = pinInfoArr[pin].adcChId;
226
227
    /* Enable adc */
228
    ADCON0bits.ADON = 1;
229
    
230
    /* Acquisition time */
231
    picWait_us(10);
232
233
     /* Start conversion */
234
    ADCON0bits.ADGO = 1;
235
236
237
    /* Wait for adc completion */
238
    while(ADCON0bits.ADGO)
239
        ;
240
241
    /* Read result high/low */
242
    res = ((int16_t)ADRESH << 8);
243
    res |= (int16_t)ADRESL;
244
245
    ADCON0bits.ADON = 0;
246
    return res;
247
}

Ich rufe zuerst picInit() auf, dann picConfigPin() für alle Pins. 
Anschließend dann picReadAdc() für die entprechenden Pins...
Was mache ich falsch?
Der Aufruf picWait_us(10); führt bei mir zu einer deutlich längeren Zeit 
> 100us. Ich weiß noch nicht warum, aber
eine längere Zeit sollte für die Messung kein Problem sein, oder?

von Kein Name (Gast)


Lesenswert?

Die Acquisition time besser zwischen CHS und ADON . Bzw. Erst mit der 
Konvertierung beginnen, nachdem der interne Sample-and-Hold Kondensator 
aufgeladen ist.

von Kein Name (Gast)


Lesenswert?

Upps... Entschuldigung, verkuckt. Ist ja zwischen CHS und ADGO.

von pks (Gast)


Lesenswert?

Kein Name schrieb:
> Die Acquisition time besser zwischen CHS und ADON . Bzw. Erst mit der
> Konvertierung beginnen, nachdem der interne Sample-and-Hold Kondensator
> aufgeladen ist.


Hmm, in der Spec steht:

1. Configure Port:
• Disable pin output driver (refer to the TRIS
register)
• Configure pin as analog (refer to the ANSEL
register)
2. Configure the ADC module:
• Select ADC conversion clock
• Configure voltage reference
• Select ADC input channel
• Turn on ADC module                         <--
3. Configure ADC interrupt (optional):
• Clear ADC interrupt flag
• Enable ADC interrupt
• Enable peripheral interrupt
• Enable global interrupt(1)
4. Wait the required acquisition time(2).    <--
5. Start conversion by setting the GO/DONE bit.
6. Wait for ADC conversion to complete by one of
the following:
• Polling the GO/DONE bit
• Waiting for the ADC interrupt (interrupts
enabled)
7. Read ADC Result in ADRES0H and ADRES0L.

von Marcus (Gast)


Lesenswert?

Wie groß ist denn der Quellwiderstand des angelegten Signals?

Siehe Datenblatt, Abschnitt 16.4


Noch eine kleine Anmerkung: Bei solch aufwendiger Array-Arithmetik wie
1
           *(portInfoArr[pinInfoArr[pinId].portId].pTris)
2
                    |= (1<<pinInfoArr[pinId].bitOffs);

kann es sein, dass der HITECH nicht besonders tollen Code erzeugt, d.h. 
er ist dann ziemlich langsam. So etwas liegt einfach nicht in der Natur 
der PIC16.

von pks (Gast)


Lesenswert?

Marcus schrieb:
> Wie groß ist denn der Quellwiderstand des angelegten Signals?
>
> Siehe Datenblatt, Abschnitt 16.4

Der liegt bei 2k, sollte also kein Problem sein.

>
> Noch eine kleine Anmerkung: Bei solch aufwendiger Array-Arithmetik wie
>            *(portInfoArr[pinInfoArr[pinId].portId].pTris)
>                     |= (1<<pinInfoArr[pinId].bitOffs);
>
> kann es sein, dass der HITECH nicht besonders tollen Code erzeugt, d.h.
> er ist dann ziemlich langsam. So etwas liegt einfach nicht in der Natur
> der PIC16.

Das ist mir mittleweile auch aufgefallen :-)
Wenn ich einen Ausgang toggle, indem ich einfach das Port-Register 
schreibe, komme ich auf eine Pulsbreite im Bereich von unter einer us. 
Mache ich es mit obigem Aufruf, liege ich bei über 100 us...
Daraus ergab sich auch die Vermutung, dass die wait-Funktion nicht 
funktioniert, die habe ich nämlich auf diese Art kontrolliert.

von pks (Gast)


Lesenswert?

Jetzt habe ich noch eine interessante Entdeckung gemacht. Wenn ich in 
meinem Programm eine Spannung zyklisch messe, und nach der Messung einen 
Breakpoint setze, ist nach jeder Messung der Wert ein wenig kleiner. Das 
geht so lange, bis der Wert da ankommt, wo die Spannung tatsächlich 
liegt. Da bleibt er dann stabil...

von Martin H. (martinhaag)


Lesenswert?

Falls nicht schon gemacht: Mit einem Oszilloskop die Versorgungsspannung 
(oder Referenzspannung) sowie direkt am PIC die zu messende Spannung 
kontrollieren. Eventuell arbeitet der PIC korrekt, aber deine Schaltung 
arbeitet nicht wie vorausgesetzt.
Grüsse  Martin

EDIT: ich hatte auch schon PIC's bei denen ein ADC eingang nicht ging, 
vieleicht den PIC mal tauschen

von pks (Gast)


Lesenswert?

Gibt es eine Möglichkeite die interne Referenzspannung auf einen Pin zu 
legen?

von Marcus (Gast)


Lesenswert?

pks schrieb:
> Wenn ich in
> meinem Programm eine Spannung zyklisch messe, und nach der Messung einen
> Breakpoint setze, ist nach jeder Messung der Wert ein wenig kleiner. Das
> geht so lange, bis der Wert da ankommt, wo die Spannung tatsächlich
> liegt. Da bleibt er dann stabil...

Das hört sich wirklich sehr nach einem Sampling-Problem an, d.h. der 
S&H-Kondensator wird zu langsam auf das korrekte Potential gebracht. Du 
kannst ja mal folgendes probieren:

1. ADON nur einmal am Anfang des Programmes setzen, nicht jedesmal, wenn 
Du den ADC nutzten möchtest. Das versaut dann aber etwas die 
durchschnittliche Leistungsaufnahme des PIC, was aber evtl. unwichtig 
ist.

2. Warte mal länger zwischen dem Setzen von CHS und dem Setzen von GO, 
z.B. 10ms. Wenn es dann geht, stimmen Deine 2k Quellwiderstand nicht. 
Abhilfe wäre dann z.B. ein Entkoppelkondensator von ca. 22nF in der Nähe 
des Pins. Oder den Quellwiderstand wirklich auf 2k bringen 
(Fehlbestückung, anderes externes Hardware-Problem).

von pks (Gast)


Lesenswert?

Leider ist das Problem immer noch nicht gelöst.
Interessanterweise beobachte ich das gleicher Verhalten, wenn ich als 
Referenz die Versorgungsspannung(3,3V) wähle und als "Mess-Spannung" die 
FVR(2,048V). Der ADC-Wert ist erstmal zu klein und steigt langsam an, 
bis er den erwarteten Wert erreicht. Mit langsam ist gemeint, dass das 
Ganze mehrere Minuten dauert. Ich habe das jetzt schon mit mehreren 
PICs/Baugruppen ausprobiert und immer das gleiche Ergebnis. Der gleich 
Code mit einem andere PIC16 (allerdings mit 5V VDD) auf einem Evalboard 
zeigt dieses Verhalten nicht.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.