mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik ISR -> ADC Problem


Autor: walter (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.
void adcInit( uint8_t mux ) {
    uint16_t result;
    // ADMUX &= ~(1<<REFS1) | (1<<REFS0);
    ADMUX = mux;
    // Choose ADC channel and the ref
    ADMUX = ADC_VREF_TYPE | (1 << ADLAR);
    // Start ADC and choose of the prescaler
    ADCSRA = ADC_PRESCALER | (1 << ADEN);
    // Dummy read out
    result = adcRead(0);
    // Delete ADC Interrupt Flag
    //ADCSRA |= (1<<ADIF);
}

void adcStart( void ) {
    // Choose ADC channel and the ref
    ADMUX = ADC_CHANNEL_0 | ADC_VREF_TYPE;
    // Start ADC 
    ADCSRA |= (1 << ADSC);
}

uint16_t adcGetValue( void ) {
    // Wait for conversation
    while ( ADCSRA & (1 << ADSC) ) ;
    return ADCW;
}

void timer0Init( void ) {
    //Waveform Generation Mode
    TCCR0A |= ( 1<<WGM01 );  // ctc mode
    // Set Presaclor
    TCCR0B |= TIMER_PRESCALER;
    // Set Timer
    TCNT0 = 0;  
    OCR0A = DEF_OCR0A; 
    // Set Compare Interrupt
    TIMSK0 |= COMPARE_BIT;
    // Activate global interrupts
    sei();
}

ISR (TIMER0_COMPA_vect) { 
    adcStart();
    globalCount++;
}

int main ( void ) {
    uint16_t adcCounter = 0, adcSu = 0;

    uartInit(MY_UBRR);
    adcInit(0);
    timer0Init();
    adcZeroRate = adcZeroUpdate();

    for(;;){
        adcSu += adcGetValue();
        adcCounter++;
        if(adcCounter >= 9) {
            //printf(" AdcCounter  %d\n", adcCounter);
            printf(" GlobalCount %d\n", globalCount);          
            adcSu = adcSu / (adcCounter+1);
            
            adcCounter =0;
            globalCount = 0;
            adcSu = 0;
        }
    }
    return 0;
}

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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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 ....

Autor: walter (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Timo S. (kaffeetas)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.