Forum: Mikrocontroller und Digitale Elektronik Abtastzeit ADC


von BoJack (Gast)


Lesenswert?

Hallo,

ich verwende einen Atmega 328p zum Erfassen von Messwerten über den ADC. 
Nun möchte ich die Abtastzeit bestimmen.

Rechnerisch bin ich wie folgt vorgegangen:

f_osz= 16MHz
N_prescaler=128

f_s= f_osz / (N_prescaler*13) = 9615,38

Also eine Abtastzeit T_s = 104µs


Bei den Messungen habe ich allerdings festgestellt, dass diese Zeit 
nicht stimmen kann. Also wollte ich einen Ausgangspin toggeln, um die 
Laufzeit des Schleife zu bestimmen.
Dazu habe ich folgenden Code verwendet:

while(1)
{
adcval = ADC_Read(0);
PINB = (1<<PB0);
...
}

Die Zeit zwischen den Flanken am Oszi beträgt 3,24ms was mir ziemlich 
lang erscheint.

Daher wollte ich fragen, ob mir jemand einen Hinweis geben kann, wo mein 
Rechen- bzw. Denkfehler liegt und wie ich vielleicht den Wert 
rechnerisch, sowie Messtechnisch ermitteln kann.

Vielen Dank schon mal

von Markus (Gast)


Lesenswert?

BoJack schrieb:
> adcval = ADC_Read(0);

Was tut denn diese Funktion?  Gibt's da Quellcode dazu?

> PINB = (1<<PB0);

Das macht bestenfalls eine einzige Flanke, nicht mehrere.

von Mein grosses V. (vorbild)


Lesenswert?

BoJack schrieb:
> while(1)
> {
> adcval = ADC_Read(0);
> PINB = (1<<PB0);
> ...
> }

Außer, daß du weisst, wie man effektiv toggelt, sagt das ja nun gar 
nichts aus.

> ADC_Read(0); Was ist das das? Arduino-Zeugs?

Was du auf deinem Oszi siehst, ist nicht die Zeit der Wandlung, sondern 
die Ausführungszeit dieser ominösen Funktion. Daß die Arduino-Funktionen 
einem das erwartete Timing versauen, ist ja nun nichts neues.

Du mußt deine Wandlung mit Interrupt komplett selbst programmieren. Dann 
wirst du in der ISR auch das angestrebte Timing sehen.

Markus schrieb:
>> PINB = (1<<PB0);
>
> Das macht bestenfalls eine einzige Flanke, nicht mehrere.

Aber sicher macht es das.

: Bearbeitet durch User
von Dietrich L. (dietrichl)


Lesenswert?

BoJack schrieb:
> Daher wollte ich fragen, ob mir jemand einen Hinweis geben kann, wo mein
> Rechen- bzw. Denkfehler liegt und wie ich vielleicht den Wert
> rechnerisch, sowie Messtechnisch ermitteln kann.

Die Rechnung ist für mich erstmal plausibel.
Was mir als mögliche Ursachen einfällt:
- läuft der µC wirklich mit 16MHz?
- Zeitbedarf der Software. Das hängt auch davon ab, wie der ADC 
initialisiert ist bzw. wie ADC_Read() arbeitet.

Gruß Dietrich

von BoJack (Gast)


Lesenswert?

Hey, danke für dei Antworten. Die ADC_Read ist hier aus dem Tutorial und 
liest jeweils einen Einzelwert ein:

uint16_t ADC_Read( uint8_t channel )
{
  // Kanal waehlen, ohne andere Bits zu beeinflußen
  ADMUX = (ADMUX & ~(0x1F)) | (channel & 0x1F);
  ADCSRA |= (1<<ADSC);            // eine Wandlung "single conversion"
  while (ADCSRA & (1<<ADSC) ) {   // auf Abschluss der Konvertierung 
warten
  }
  return ADCW;                    // ADC auslesen und zurückgeben
}

wie kann ich die Taktfrequenz des Controllers prüfen?

Dass mit den 104µs etwas nicht stimmt hab ich festgestellt, da die 
Messwerte aufsummiert werden sollen und ich die Zeit mitgestoppt habe 
und dann mit der LCD-Ausgabe verglichen.

Beim toggeln des Pins bekomme ich abwechselnde Low und High-PEgel am 
Oszi. Wie sollte ansonsten getoggelt werden? mit bitweisem xor 
vermutlich oder?

von Sascha (Gast)


Lesenswert?

Richtiges Vorgehen an der Stelle: ADC auf free running modus setzen, ADC 
Conversion Complete Interrupt aktivieren, die dazugehörige ISR in den 
Code setzen und dort dann einen Pin toggeln.

Dann muss man auch nicht mehr rätselraten wie die Implementierung von 
ADC_Read() ist. Bei 3,24ms scheint das nicht nur ein blockierender 
Aufruf zu sein, da passiert wohl noch was anderes.

Weiterhin:

PINB = (1<<PB0);

Das ist unverständlicher Code. Solltest du in Zukunft vermeiden. Lieber 
ganz normale Bitoperationen.

von Sascha (Gast)


Lesenswert?

"while (ADCSRA & (1<<ADSC) ) {   // auf Abschluss der Konvertierung "

Die Zeile macht dir deine Zeitmessung komplett kaputt.

von Mein grosses V. (vorbild)


Lesenswert?

BoJack schrieb:
> // Kanal waehlen, ohne andere Bits zu beeinflußen
>   ADMUX = (ADMUX & ~(0x1F)) | (channel & 0x1F);
>   ADCSRA |= (1<<ADSC);            // eine Wandlung "single conversion"
>   while (ADCSRA & (1<<ADSC) ) {   // auf Abschluss der Konvertierung

Das ist zwar idiotensicher, aber nicht sehr effektiv. Der ADC muß 
ständig laufen und die Abfrage muß in einer ISR erfolgen.

BoJack schrieb:
> wie kann ich die Taktfrequenz des Controllers prüfen?

Indem du dir die Einstellungen der Fuses ansiehst. Wenn du da noch nie 
etwas verändert hast, läuft der Controller mit 1 MHz.

Überprüfen kannst du das mit einer blinkenden LED.
1
while(1)
2
{
3
  PINX = (1 << Y);
4
  _delay_ms(500);
5
}

Wenn das Blinken im Sekundentakt erfolgt, stimmt die tatsächliche 
Taktfrequenz mit deinen Erwartungen überein. Kannst natürlich auch ein 
kürzeres toggeln auf dem Oszi anzeigen.

BoJack schrieb:
> Wie sollte ansonsten getoggelt werden? mit bitweisem xor
> vermutlich oder?

Da hat einer noch nicht mitbekommen, daß die Erde sich nach dem Launch 
des Atmega8 weitergedreht hat.

von Oliver S. (oliverso)


Lesenswert?

Sascha schrieb:
> PINB = (1<<PB0);
>
> Das ist unverständlicher Code.

Da kann der Code aber nichts dafür, denn der ist einwandfrei, und an der 
Stelle absolut richtig.

Oliver

von Mein grosses V. (vorbild)


Lesenswert?

Sascha schrieb:
> Das ist unverständlicher Code. Solltest du in Zukunft vermeiden. Lieber
> ganz normale Bitoperationen.

Warum soll man das vermeiden? Weil du es nicht verstehst?

von Dietrich L. (dietrichl)


Lesenswert?

BoJack schrieb:
> wie kann ich die Taktfrequenz des Controllers prüfen?

z.B. mit:
1
#define F_CPU 16000000
2
#include <avr/io.h>
3
#include <util/delay.h>
4
5
int main()
6
{
7
  DDRB = ( 1 << PB4 );
8
9
  while( 1 )
10
  {
11
    PORTB |= ( 1 << PB4 );
12
    _delay_ms( 1000 );
13
    PORTB &= ~( 1 << PB4 );
14
    _delay_ms( 1000 );
15
  }
16
}

Hier muss PB4 jede Sekunde toggeln. Wenn das nicht stimmt ist F_CPU 
nicht der tatsächliche Wert des µC-Taktes.

Gruß Dietrich

: Bearbeitet durch User
von heintje (Gast)


Lesenswert?

Sascha schrieb:
> Weiterhin:
>
> PINB = (1<<PB0);
>
> Das ist unverständlicher Code. Solltest du in Zukunft vermeiden. Lieber
> ganz normale Bitoperationen.

Grööööl.

von BoJack (Gast)


Lesenswert?

Vielen Dank schon mal für die vielen Antworten.

Wenn ich den adc im free running mode betreibe brauche ich also gar 
keinen Aufruf mehr oder? Das heißt nach jeder Wandlung wird ein 
Interrupt ausgelöst, in welchem ich dann den Pin toggeln kann und dieser 
sollte dann am Oszi auch die rechnerischen 104µs anzeigen.

Die 16Mhz stimmen soweit. Hab ich so ähnlich schon an anderer Stelle 
überprüft.


Mein grosses V. schrieb:

> Da hat einer noch nicht mitbekommen, daß die Erde sich nach dem Launch
> des Atmega8 weitergedreht hat.

hahaha :D

von Sascha (Gast)


Lesenswert?

Mein grosses V. schrieb:
> Sascha schrieb:
>> Das ist unverständlicher Code. Solltest du in Zukunft vermeiden. Lieber
>> ganz normale Bitoperationen.
>
> Warum soll man das vermeiden? Weil du es nicht verstehst?

Natürlich verstehe ich das, aber nur weil ich extra das DB gelesen habe 
und dann da steht, dass es funktioniert.

Code sollte unabhängig von der Hardware Sinn ergeben.

von Oliver S. (oliverso)


Lesenswert?

Sascha schrieb:
> Code sollte unabhängig von der Hardware Sinn ergeben.

Was bei Code, der Hardware direkt ansteuert, aber nunmal nicht geht. Und 
ja, da muß man dann dazu das Datenblatt lesen...

Oliver

von Dietrich L. (dietrichl)


Lesenswert?

Mein grosses V. schrieb:
> Sascha schrieb:
>> Das ist unverständlicher Code. Solltest du in Zukunft vermeiden. Lieber
>> ganz normale Bitoperationen.
>
> Warum soll man das vermeiden? Weil du es nicht verstehst?

Mein Vorschlag zur Entspannung: man könnte ja im Kommentar vermerken, 
dass diese Operation bei diesem µC den Pin toggelt. Dann versteht ein 
fremder Leser dies auch.

Gruß Dietrich

von Mein grosses V. (vorbild)


Lesenswert?

BoJack schrieb:
> Das heißt nach jeder Wandlung wird ein
> Interrupt ausgelöst, in welchem ich dann den Pin toggeln kann und dieser
> sollte dann am Oszi auch die rechnerischen 104µs anzeigen.

Richtig. Der ADC lässt sich damit sogar als Timer benutzen.

von Mein grosses V. (vorbild)


Lesenswert?

Sascha schrieb:
> Code sollte unabhängig von der Hardware Sinn ergeben.

Was hast du denn gegen den Code? Da wird etwas in ein Register 
geschrieben. Was das bewirkt, ist von der spezifischen Hardware abhängig 
und steht im Datenblatt. Also, was ist dein Problem?

Wobei es richtig eingesetzt natürlich
1
PINB |= (1<<PB0);


lauten muß.

: Bearbeitet durch User
von Carsten R. (kaffeetante)


Lesenswert?

Zuerst

Abtastzeit ist ein etwas unglücklich gewählter Begriff. Ohne gesonderte 
Definition hätte ich damit eher die sample and hold Zeit darunter 
verstanden.

Richtig ist:

Wenn der ADC schon kofiguriert und aktiviert ist, so benötigt die reine 
Wandlung des Analogwertes 13 ADC-Takte bis die Informatiion im 
ADC-Register steht. Was dann die ISR etc so macht, kann man so pauschal 
nicht vorhersagen. Um es exakt durchzugehen müsste man schon auf 
Maschinencode bzw Assembler gehen. Und auch da wird es schwierig.

Ich würde ohne Oszi "messen".

Du kannst einen Timer mitlaufen lassen und seinen Wert zu Beginn der 
Wandlung und unmittelbar danach auslesen. Die Differenz ist dann deine 
Dauer, inklusive eines kleinen Overheads für die Auswertung des Timers.

Alternativ, so ähnlich wie du es nun machst mit dem Toggeln.

Gehe in den free running modus und lasse die ISR zusätzlich eine 
Variable hochzählen. Nach einer bestimmten Zeit wertest du diese 
Variable aus und kannst die Zeit pro Wandlung berechnen. Dazu muß die 
Variable natürlich einen hinreichend großen Wertebereich haben, damit es 
nicht zu einem überlauf kommt.

Du kannst auch eine Schleife x-mal eine Wandlung durchführen lassen und 
mit einem Timer wie oben mitlaufen lassen und damit die für die ganze 
Schleife benötigten Takte zählen. Auch hier ist natürlich wieder zu 
beachten, daß der Timer einen Überlauf produzieren kann. Den kann man 
aber auch wieder mitzählen.

: Bearbeitet durch User
von Mein grosses V. (vorbild)


Lesenswert?

Carsten R. schrieb:
> Richtig ist:

Schön, was du da schreibst. So schön wie unsinnig.

Der ADC-IRQ kommt alle 104µs. Diese 104µs entsprechen ca. 1600 
CPU-Takten. Das ist die Zeit, die zur Verfügung steht, um den aktuellen 
Wert zu verarbeiten. Dann kommt nämlich der nächste. Aber 1600 Takte 
sind verdammt viel.

Um das auf dem Oszi zu zeigen, setzt man am Anfang und am Ende der ISR 
jeweils ein Toggle, ausgehend von Port = Low und bekommt eine PWM 
angezeigt. Die Periode beträgt dabei 104µs, also knapp 10KHz und der 
"Duty", der die Dauer der ISR repräsentiert, sollte, wenn der Controller 
sich auch noch um andere Dingen kümmern soll, möglichst kurz sein.

von BoJack (Gast)


Lesenswert?

kurzes Update:

Betreibe den adc jetzt im free running mode. Die ISR sieht 
folgendermaßen aus:

ISR(ADC_vect)
{
  PINB = (1<<PB0);
  ADCSRA |= (1<<ADSC);
}

Bekomme ein PWM-Signal am Oszi, das eine Pulsbreite von 56µs hat, also 
eine Periode von 112µs.

Sollte dabei aber nicht eine Pulsbreite 112µs herauskommen? Schließlich 
ändert sich der Pin ja bei jedem Interrupt. Oder wo steckt mein 
Denkfehler?

Danke schon mal

von BoJack (Gast)


Lesenswert?

Initialisierung des ADC:

int main(void)
{
DDRB = 0x01;

ADMUX |= (1<<REFS0) | (1<<MUX0);

ADCSRA |= (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADIE);
sei();
ADCSRA |= (1<<ADSC);

while(1)
{
}

}

von S. Landolt (Gast)


Lesenswert?

> ADCSRA |= (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADIE)
Also Vorteiler=64. Ergibt für den ADC 52 us, der Rest wird für den 
Interruptaufruf benötigt.

von BoJack (Gast)


Lesenswert?

ich hab jetzt noch eine BErechnung in die ISR eingefügt, dadurch ist die 
Pulsbreite nochmal gestiegen auf 92µs.

Bedeutet das, dass die Dauer der ISR letztendlich meiner Abtastzeit 
entspricht an der ich mich orientieren muss?

von S. Landolt (Gast)


Lesenswert?

Wie sieht die ISR jetzt aus?

von spess53 (Gast)


Lesenswert?

Hi

>Betreibe den adc jetzt im free running mode. Die ISR sieht
>folgendermaßen aus:

>ISR(ADC_vect)
>{
>  PINB = (1<<PB0);
>  ADCSRA |= (1<<ADSC);
>}

Das ADCSRA |= (1<<ADSC); ist nur einmal zum Starten der ersten 
Wandlung notwendig (außerhalb der ISR).

MfG Spess

von S. Landolt (Gast)


Lesenswert?

Dazu muss aber ADATE in ADCSRA gesetzt werden.

von Mein grosses V. (vorbild)


Lesenswert?

BoJack schrieb:
> ADCSRA |= (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADIE);

Das ist Prescaler = 64. Passt doch.
1
int main(void)
2
{
3
   DDRB = 0x01;
4
   ADMUX |= (1<<REFS0) | (1<<MUX0);
5
   ADCSRA |= (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADIE)|(1 << ADATE);
6
   //ADATE = Auto Trigger Enable
7
   sei();
8
   ADCSRA |= (1<<ADSC);
9
}
10
ISR(ADC_vect)
11
{
12
  unsigned int adc = ADC:
13
  PINB = (1<<PB0);
14
  _delay_us(5);
15
  PINB = (1<<PB0);
16
}


Damit erhälst du an PB0 eine PWM mit ca. 10% Duty Cycle. Das Delay 
simuliert dabei die Zeit für die Bearbeitung des gemessenen Wertes.

Der Interrupt kommt alle 52µs.

Auf dem Oszi siehst du jetzt grün auf grau, wann etwas passiert und wie 
lange das dauert.

: Bearbeitet durch User
von spess53 (Gast)


Lesenswert?

Hi

>Dazu muss aber ADATE in ADCSRA gesetzt werden.

Wenn das nicht gesetzt ist, dann ist das eh kein (echter) 
Free-Running-Mode.

MfG Spess

von S. Landolt (Gast)


Lesenswert?

Stimmt auch wieder, die andere Version ist ja nur eine kontinuierliche 
Wandlung.

von BoJack (Gast)


Lesenswert?

ja mit dem free running hab ich grad auch gesehen. hab noch ADATE 
gesetzt.

In der ISR steht jetzt:
1
ISR(ADC_vect)
2
{
3
  PINB = (1<<PB0);
4
  
5
  U_e = ((adcval+44.09)*5050)/1024;
6
  
7
}

Also das Toggeln + die Umrechnung meiner ADC-Werte

von S. Landolt (Gast)


Lesenswert?

Und, wie sieht's aus? Schafft es der C-Compiler, diese Berechnung in den 
gut 800 Takten unterzubringen?

von Carsten R. (kaffeetante)


Lesenswert?

Mein grosses V. schrieb:
> Carsten R. schrieb:
>> Richtig ist:
>
> Schön, was du da schreibst. So schön wie unsinnig.
>
> Der ADC-IRQ kommt alle 104µs. Diese 104µs entsprechen ca. 1600
> CPU-Takten. Das ist die Zeit, die zur Verfügung steht, um den aktuellen
> Wert zu verarbeiten. Dann kommt nämlich der nächste. Aber 1600 Takte
> sind verdammt viel.

Da hast du dich etwas weit aus dem Fenster gelehnt. Ich schrieb daß es 
13 ADC-Takte sind, nicht CPU-Takte Da kommt noch der Prescaler hinzu. 
Damit bestätigte ich nur die überschlägige Rechnung im Eingangspost. So 
findet man es auch im Datenblatt.

Deinen Daten entsprechend hast du einen CPU-Takt von 16 MHz  und den 
Prescaler auf maximum, nämlich 128. Das ergibt 13*128=1664 CPU-Takte pro 
Wandlung, bzw rund 9615 Wandlungen pro sekunde im free running Modus.

Den Prescaler kann man verringern, insbesondere wenn der CPU-Takt 
niedriger ist, und somit die Geschwindigkeit des ADC erhöhen. Allerdings 
sollte dabei beachten, daß der daraus resultierende ADC-Takt im Rahmen 
bleibt. Wird er zu hoch, werden zuerst die letzten Bits ungenau. Wird er 
sehr hoch, so verläßt man die Spezifikation. Was das bedeutet sollte 
jedem klar sein. Die Spezifikationen stehen im Datenblatt.

Wenn die ISR innerhalb dieser 1664 Takte abgearbeitet werden kann, so 
kann beim free running Modus die Softwareauswertung parallel zur 
nächsten Hardwarewandlung erfolgen. Das heißt die 
Hardwaregeschwindigkeit kann ausgereizt werden.

Ist die Software komplexer, so ist sie der begrenzende Faktor.

Geht es um einzenle ADC-Wandlungen, so addieren sich Hardware- und 
Softwareausführungszeiten, da sie nacheinander ablaufen. Um dann die 
Latenz exakt auszurechnen, müßte man jeden Maschinenbefehl der Software 
kennen. Eine Hochsprache hilft da nicht.

Oder man mißt per Oszi bzw. zählt per Timer die Takte, auch wenn 
letzteres einen kleinen zusätzlichen Softwareoverhead bedeutet.

Die Frage ist was man wissen will.

Die Latenz, bis die Software das Ergebnis ausspuckt oder die maximale 
Samplingrate, die ausgewertet werden kann?

: Bearbeitet durch User
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.