Forum: Mikrocontroller und Digitale Elektronik ISR -> ADC Problem


von walter (Gast)


Lesenswert?

Hallo,

ich hätte bezüglich Timer / ISR und ADC eine Frage. Zu erst einmal meine 
Konfiguration. Habe ein ATmega88 der den Analog Wert eines Accelerometer 
auswerten soll, für den UART ist ein 14,7456 Quarz angeschlossen. Mein 
Timer läuft mit einer Frequenz von 1001Hz (geht soweit auch ganz gut 
nach 5 min laufen zeigen auch die Variablen die eine Uhr „anzeigen“ 5 
min), der ADC mit 115kHz, dieser  und der UART funktionieren ebenfalls.
Ich will nun alle ISR (ca. jede 1ms) eine ADC Wandlung starten, 10 Werte 
zusammenzählen und den Durchschnitt daraus bilden, sollte eigentlich 
schnell genug sein.
Zum Code: Es ist alles noch etwas chaotisch.
1
void adcInit( uint8_t mux ) {
2
    uint16_t result;
3
    // ADMUX &= ~(1<<REFS1) | (1<<REFS0);
4
    ADMUX = mux;
5
    // Choose ADC channel and the ref
6
    ADMUX = ADC_VREF_TYPE | (1 << ADLAR);
7
    // Start ADC and choose of the prescaler
8
    ADCSRA = ADC_PRESCALER | (1 << ADEN);
9
    // Dummy read out
10
    result = adcRead(0);
11
    // Delete ADC Interrupt Flag
12
    //ADCSRA |= (1<<ADIF);
13
}
14
15
void adcStart( void ) {
16
    // Choose ADC channel and the ref
17
    ADMUX = ADC_CHANNEL_0 | ADC_VREF_TYPE;
18
    // Start ADC 
19
    ADCSRA |= (1 << ADSC);
20
}
21
22
uint16_t adcGetValue( void ) {
23
    // Wait for conversation
24
    while ( ADCSRA & (1 << ADSC) ) ;
25
    return ADCW;
26
}
27
28
void timer0Init( void ) {
29
    //Waveform Generation Mode
30
    TCCR0A |= ( 1<<WGM01 );  // ctc mode
31
    // Set Presaclor
32
    TCCR0B |= TIMER_PRESCALER;
33
    // Set Timer
34
    TCNT0 = 0;  
35
    OCR0A = DEF_OCR0A; 
36
    // Set Compare Interrupt
37
    TIMSK0 |= COMPARE_BIT;
38
    // Activate global interrupts
39
    sei();
40
}
41
42
ISR (TIMER0_COMPA_vect) { 
43
    adcStart();
44
    globalCount++;
45
}
46
47
int main ( void ) {
48
    uint16_t adcCounter = 0, adcSu = 0;
49
50
    uartInit(MY_UBRR);
51
    adcInit(0);
52
    timer0Init();
53
    adcZeroRate = adcZeroUpdate();
54
55
    for(;;){
56
        adcSu += adcGetValue();
57
        adcCounter++;
58
        if(adcCounter >= 9) {
59
            //printf(" AdcCounter  %d\n", adcCounter);
60
            printf(" GlobalCount %d\n", globalCount);          
61
            adcSu = adcSu / (adcCounter+1);
62
            
63
            adcCounter =0;
64
            globalCount = 0;
65
            adcSu = 0;
66
        }
67
    }
68
    return 0;
69
}

So jetzt zum Problem. Die adcSu ist soweit Korrekt, wenn ich nichts 
mache ändert sich dieser auch nicht. Durch Bewegung ändert sich der 
Wert. Nur beim globalCount wird mir immer eine 0 ausgegeben, dabei 
sollte dort doch eigentlich etwas um die 100 stehen.  Wenn ich den 
adcCounter mit ausgebe kommt im globalCount eine 20 raus. o.O
Sehe ich das falsch, dass wenn ich adcStart() aufrufe und im Anschluss 
in der mainloop adcGetValue(),  dieser auf die Konvertierung  / Ergebnis 
wartet und somit die Schleife eigentlich nicht schneller laufen darf. 
Und wenn ja, warum wird mir der globalCount falsch ausgegeben? Bin mir 
nun leider nicht sicher ob ich wirklich eine Frequenz von ca. 1ms 
schaffe für eine ADC Wandlung. Evt hat einer von euch eine Idee oder ein 
Link wo ich mich weiter schlau machen kann.

Gruß Walter

von Karl H. (kbuchegg)


Lesenswert?

walter schrieb:

> Sehe ich das falsch, dass wenn ich adcStart() aufrufe und im Anschluss
> in der mainloop adcGetValue(),  dieser auf die Konvertierung  / Ergebnis
> wartet und somit die Schleife eigentlich nicht schneller laufen darf.

Üerleg einfach mal, was wohl passiert, wenn du adcGetValue() aufrufst 
und KEINE adc-Operation im Gange war.

> Und wenn ja, warum wird mir der globalCount falsch ausgegeben?

Hast du kontrolliert, ob der Timerinterrupt auslöst?
globalCount als volatile Variable definiert?

> Bin mir
> nun leider nicht sicher ob ich wirklich eine Frequenz von ca. 1ms
> schaffe für eine ADC Wandlung. Evt hat einer von euch eine Idee oder ein
> Link wo ich mich weiter schlau machen kann.

Machs doch anders.
Wenn du sowieso weißt, dass der ADC keine Millisekunde für die Wandlung 
benötigt, dann hol dir im Timer Interrupt den Wert vom ADC und starte 
eine neue Wandlung. Wenn der nächste Interrupt dann kommt, ist diese 
Wandlung bereits fertig, du holst dir das Ergebnis und startest die 
nächste ....

von walter (Gast)


Lesenswert?

Hallo Karl heinz Buchegger,

danke dir für deine Antwort. Ist schön, dass hier immer so schnell 
geholfen wird.

zu No.1
eigentlich muss der Code in der while-schleife gefangen sein, bis die 
ISR den ADC startet. Und da adcSu = adcSu / (adcCounter+1); den 
richtigen Wert bringt. Hoffentlich Irre ich mich nicht. Bin mir aber 
leider nicht sicher ob ich wirklich die Zeiten einhalte.

zu No.2
Jup, die ISR wird aufgerufen. Und natürlich sind die Globalen Variablen 
mit volatile initialisiert  ;)

zu No.3
Ich werde es mal versuchen. Hoffentlich kommt es zu keinen Verzögerungen 
:D
Teste es mal aus. Evt versuch ich es auch mit
   ISR (ADC_vect)
somit sollte ich sogar eine noch genauere Auflösung hinbekommen.


Danke dir.

von Timo S. (kaffeetas)


Lesenswert?

Hallo walter,

schau Dir mal im Datenblatt das ADCSRB Register an. Du kannst eine 
Wandlung automatisch bei auftreten des TimerInterupt auslösen lassen. 
Ich würde die Werte dann im ADC Interrupt summieren und alle 10 Werte 
ein Flag setzen, dass der neue Wert zur verfügung steht und abgeholt 
werden kann.

Grüße
 Timo

von Karl H. (kbuchegg)


Lesenswert?

walter schrieb:
> Hallo Karl heinz Buchegger,
>
> danke dir für deine Antwort. Ist schön, dass hier immer so schnell
> geholfen wird.
>
> zu No.1
> eigentlich muss der Code in der while-schleife gefangen sein, bis die
> ISR den ADC startet.

Warum sollte sie?
Dein Code wartet darauf, dass das ADSC Bit 0 wird bzw. 0 ist.
Was aber, wenn es sowieso schon 0 ist, weil der Timer Interrupt noch gar 
nicht da war, der es auf 1 gesetzt hätte?
Dann wird auf nichts gewartet, sondern sofort ADCW ausgelesen und 
zurückgekehrt.

Im Vergleich zu der Häufigkeit, mit der diese Funktion aufgerufen wird, 
kommt aber der Timer Interrupt nur alle heiligen Zeiten. Dadurch ergibt 
sich diese Sequenz

Timer-ISR                  Hauptprogramm
------------------------------------------------
startet
setzt ADSC auf 1

                           adcGetValue()
                           wartet auf ADSC == 0
       ADC wird fertig
       ADSC <- 0
                           return ADCW


                           adcGetValue()
                           ADSC ist bereits 0
                           return ADCW

                           adcGetValue()
                           ADSC ist bereits 0
                           return ADCW

                           adcGetValue()
                           ADSC ist bereits 0
                           return ADCW

startet
setzt ADSC auf 1

                           adcGetValue()
                           wartet auf ADSC == 0

        ADC wird fertig
        ADSC <- 0
                           return ADCW


                           adcGetValue()
                           ADSC ist bereits 0
                           return ADCW

Die meiste Zeit findet adcGetValue das ADSC Bit als nicht gesetzt vor, 
weil die Timer ISR den ADC noch gar nicht gestartet hat, seit 
adcGetValue zuletzt den Wert abgeholt hat. Des kümmert adcGetValue aber 
nicht, es liefert dann einfach den zuletzt gewandelten Wert noch einmal.

Das Problem ist, dass adcGetValue eben nicht auf das Beenden einer 
Wandlung wartet, sondern nur darauf, dass das bewusste Bit 0 ist.
Oder anders ausgedrückt: Wenn du 20 mal am Tag in deinen Postkasten 
schaust und ihn leer vorfindest, heisst das nicht, dass der Postler 20 
mal da war und nichts hineingelegt hat.

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.