Forum: Mikrocontroller und Digitale Elektronik Problem mit ADC Auto-Trigger interrupt


von Markus (Gast)


Lesenswert?

Hallo Leute,

ich habe ein Problem mit dem ADC im Auto-Trigger Mode auf meinem 
ATmega168. Der Auto-Trigger ist auf Compare Match A mit Timer 1 gelegt. 
Dadurch wird eine Wandlung gestartet und bei deren Ende wird ein 
Interrupt ausgeführt.

Hier mal die Init des ADCs:
1
void Init_adc(void)
2
{
3
  // PORT C als Eingang
4
  DDRC = 0x00;
5
6
  // Interne 1,1V als Referenz
7
  ADMUX |= (1<<REFS0) | (1<<REFS1);         
8
9
  // Frequenzvorteiler auf 64 setzen (288 kHz)
10
  ADCSRA |= (1<<ADPS2) | (1<<ADPS1) | (1<<ADATE) | (1<<ADIE) | (1<<ADEN);
11
12
  // Auto-Trigger on Timer/Counter0 Compare Match A
13
  ADCSRB |= (1<<ADTS0) | (1<<ADTS1);
14
}

In der ISR lese ich 4 ADC Kanäle aus und addiere diese Werte in einem 
Array zur Mittelwertbildung auf. Jedesmal wenn ich einen Kanal 
ausgelesen habe, inkrementiere ich die Laufvariable "channel" damit ich 
so den nächsten ADC-Kanal auswählen kann um im nächsten Interrupt diesen 
Kanal auszulesen und an entsprechender Position im Array abzulegen. Wenn 
ich meine 4 Messungen habe, so wir mit einer weiteren Laufvariable "i" 
ein Flag gesetzt und in der main ausgewertet.

Mein Problem ist nun, dass meine Laufvariable "channel" nur ein einziges 
mal inkrementiert wird. Sie wird mit 0 initialisiert und ist global als 
volatile unsigned char deklariert. Sie bleibt also auf dem Wert 1 
stehen. Zum debuggen habe ich mal eine kleine LED benutzt und diese an 
verschiedenen Positionen der IF-Abfragen eingeschaltet, dadurch konnte 
ich herausfinden dass "channel" nur den Wert 1 erreicht.

Das Array ist übrigens auch global als volatile unsigned int deklariert.

Könnt ihr vielleicht ein Fehler im Code sehen? Ich blick schon gar nicht 
mehr durch :)

LG Markus

Hier noch der Code des Interrupts:
1
ISR(ADC_vect)
2
{
3
  ADC_channel[channel] += ADCW;    // ADC-Kanäle auslesen (Wichtig: "channel" muss mit 0 initialisiert werden!)
4
5
  channel++;
6
7
  if(channel > 3)
8
  {
9
    ADMUX |= (0<<0);    // ADC0-Kanal wählen
10
    i++;        // Laufvariable zur Zählung der Messungen
11
    channel = 0;
12
  }
13
  else
14
  {
15
    ADMUX |= (channel<<0);    // Setzen des nächsten ADC-Kanals
16
  }
17
18
  if(i == 4)
19
  {
20
    strom_irq = 1;      // Flag setzen zur Auswertung von mehreren Messungen
21
  }
22
}

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Erstmal würde ich die Zählvariable static in der ISR definieren. Sonst 
könnte der compiler denken das diese nur lokal in der ISR genutzt wird. 
Da die ISR aber nie (direkt) aufgerufen wird ist der Wert dort immer 1!

Was macht das i dort? und wie ist das definiert? Und das schieben um 0 
an verschiedenen Stellen ist auch unsinnig.

von Karl H. (kbuchegg)


Lesenswert?

Mal abgesehen von dem Murks den du da mit ADMUX aufführst:
Hast du schon abgeklärt, ob
  der Timer regelmässig seine ISR auslöst
  als Folge davon der ADC gestartet wird und der wiederrum
  seinerseits die zugehörige ISR auslöst.

von X- R. (x-rocka)


Lesenswert?

Ich hätte eher getippt, dass der ADMUX immer auf Kanal 3 stehen bleibt, 
oder wo werden die MUX bits vorm odern genullt?

ADMUX |= (0<<0);
ADMUX |= (channel<<0);

oh ja, "i"s sind ja schon gefährlich, globale gehen gar nicht!
und das i muss auch zurückgesetzt werden!

von Karl H. (kbuchegg)


Lesenswert?

Also sind wir wieder mal beim üblichen:

Poste den kompletten Code!

(Ich schwör: irgendwann bau ich ein Expertensystem, das eine Anfrage 
analysiert und wenn es um Programmieren geht, den Code durch den 
Compiler jagt und sobald da irgendwelche undefined References auftauchen 
die automatische Meldung "Poste kompletten Code" als Antwort einstellt 
und den Thread sperrt.)

von Markus (Gast)


Lesenswert?

Danke für die Antworten.

Hab die Laufvariablen als static deklariert, hat leider auch nicht 
geholfen.

"i" ist ebenfalls nur eine Laufvariable und, jetzt, auch als static 
volatile unsigned char deklariert.

Ja, den Timer und den ADC habe ich schon getestet und sie funktionieren. 
Ebenso wird der Interrupt regelmäßig aufgerufen. Das einzige Problem das 
ich feststellen konnte, war das nicht vorhandene inkrementieren meiner 
Laufvariable.

Hehe, ja ich weiß, dass das teilweise recht unsinnig ist :) jedoch 
sollte es so eigentlich auch funktionieren und keine Fehler verursachen. 
Zumindest nehme ich das mal an. Sobald der Code funktioniert wird er 
bereinigt und ordentlich geschrieben.

Ich verstehe aber nach wie vor nicht warum die Laufvariable nicht 
inkrementiert wird.

von Karl H. (kbuchegg)


Lesenswert?

Markus schrieb:

> Ich verstehe aber nach wie vor nicht warum die Laufvariable nicht
> inkrementiert wird.

Wir auch nicht.
Bitte!
Beschreib nicht deinen Code! Poste ihn einfach. Copy&Paste gibt es jetzt 
seit fast 15 Jahren. Das ist für dich einfacher und weniger Tipparbeit 
und für uns ist es auch einfacher.

> Sobald der Code funktioniert wird er bereinigt und ordentlich
> geschrieben

Falsche Vorgehensweise.
Schreib ihn gleich ordentlich und bereinigt.
Eine nicht unbeträchtliche Anzahl an Fehlern entsteht nur dadurch, dass 
der Code eben nicht ordentlich und bereinigt ist.

von spess53 (Gast)


Lesenswert?

Hi

>(Ich schwör: irgendwann bau ich ein Expertensystem, das eine Anfrage
>analysiert und wenn es um Programmieren geht, den Code durch den

Gute Idee. Hoffentlich nicht nur für C.

MfG Spess

von Markus (Gast)


Lesenswert?

Immer mit der Ruhe, ein alter Mann ist kein D-Zug :)

Hier der Rest des Codes:

Die globalen Variablen:
1
static volatile unsigned char strom_irq = 0, channel = 0, i = 0;
2
static volatile unsigned int ADC_channel[4];


Die Init des Timer 0 zur PWM-Generierung:
1
void PWM_init(void)
2
{
3
  // Form generation mode using WGM bits and Compare output mode using COM bits. Phase correct Mode 1
4
  TCCR0A |= (1<<WGM00) | (1<<COM0A1) | (1<<COM0B1);
5
6
  // Set the Clock select bits to determine Clock frequency. Prescaler = 1
7
  TCCR0B |= (1<<CS00);
8
9
  // Enable OC0A and OC0A_Reverse as outputs = 1
10
  DDRD |= (1<<PD5) | (1<<PD0);
11
}

Die Funktion die den Motor antreibt:
1
void motor_pwm(unsigned char SPD_M, char DIR_M)
2
{
3
  if(DIR_M)              // BACKWARD
4
  {
5
    PORTD |= (1<<PD0);
6
    OCR0A = ~SPD_M;
7
  }
8
  else                  // FORWARD
9
  {
10
    PORTD &= ~(1<<PD0);
11
    OCR0A = SPD_M;
12
  }
13
}

Und die Main:
1
int main (void)
2
{
3
  unsigned int strom = 0;
4
5
  PWM_init();
6
  Init_adc();            // Initialisierung des ADC
7
8
  motor_pwm(127,FORWARD);          // FORWARD = 0, BACKWARD = 1
9
10
  for(;;)              // Endless loop
11
  {
12
    if(strom_irq == 1)
13
    {
14
      ADC_channel[0] /= 4;      // Mittelwert aus vier Messungen bilden für ADC0-Kanal
15
      strom = (ADC_channel[0] * 1100) / 1024;  // Stromwert berechnen
16
      strom_irq = 0;
17
    }
18
  }
19
20
  return 0;
21
}

Mehr als das gibt es bisher noch nicht. Möchte noch eine Übertragung 
meiner gemessenen Werte per UART realisieren, aber das hat noch Zeit.

von Markus (Gast)


Lesenswert?

Hat keiner eine Idee warum es nicht funktionieren könnte? :(

Habe schon mehrere Varianten ausprobiert, aber er zählt meine 
Laufvariable einfach nicht hoch.

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Du wurdest gebeten, den kompletten Quelltext zu schicken.
Das würde ich an deiner Stelle machen z.B. als Anhang.

Wenn du ein Projekt mit hochgeheimen Nebenfunktionen hast, kannst du ja 
diese entfernen. Dabei darauf achten, dass der Fehler nicht verschwindet 
bzw. wäre das Verschwinden des Fehlers bereits ein hilfreiches Indiz.

von Karl H. (kbuchegg)


Lesenswert?

Hmm. Du hast den Auto Trigger auf den Compare Match vom Timer 0 gelegt.
D.h. der ADC wird mit der steigenden Flanke des zugehörigen Interrupt 
Flags gestartet.
Da du aber keine Compare Match ISR hast, wird dieses Compare Match 
Interrupt Flag nie zurückgesetzt und das Flag ist beim nächsten Compare 
Match schon gesetzt. Damit hast du aber keine steigende Flanke mehr und 
damit auch keinen Trigger für den ADC

Ich sagte doch: Hast du abgeklärt, ob die ADC-ISR regelmässig 
aufgerufen wird? Wenn dein Debug-Test nicht spezifisch genug ist, dann 
verwechselst du einen einmaligen Aufruf mit einem regelmässigen Aufruf. 
Die ISR wird offenbar nur einmal aufgerufen und dann nie wieder.

von spess53 (Gast)


Lesenswert?

Hi

>Du hast den Auto Trigger auf den Compare Match vom Timer 0 gelegt.
>D.h. der ADC wird mit der steigenden Flanke des zugehörigen Interrupt
>Flags gestartet.
>Da du aber keine Compare Match ISR hast, wird dieses Compare Match...

Dessen braucht es auch nicht. Es reicht das Flag im ADC-Interrupt 
zurückzusetzen.

Datenblatt:
A conversion can thus be triggered without causing an interrupt. 
However, the Interrupt Flag must be cleared in order to trigger a new 
conversion at the next interrupt event.

MfG Spess

von Karl H. (kbuchegg)


Lesenswert?

spess53 schrieb:
> Hi
>
>>Du hast den Auto Trigger auf den Compare Match vom Timer 0 gelegt.
>>D.h. der ADC wird mit der steigenden Flanke des zugehörigen Interrupt
>>Flags gestartet.
>>Da du aber keine Compare Match ISR hast, wird dieses Compare Match...
>
> Dessen braucht es auch nicht. Es reicht das Flag im ADC-Interrupt
> zurückzusetzen.

Ist auch eine Möglichkeit.
Aber auf jeden Fall muss er sich um das Flag kümmern.

> Datenblatt:
> A conversion can thus be triggered without causing an interrupt.
> However, the Interrupt Flag must be cleared in order to trigger a new
> conversion at the next interrupt event.

Den Teil hab ich wiederrum im DB überlesen und indirekt erschlossen :-)

von Markus (Gast)


Lesenswert?

Danke Karl heinz, genau das war das Problem. Daran hatte ich überhaupt 
nicht mehr gedacht.

LG Markus

von Markus (Gast)


Lesenswert?

Habe zu spät gesehen dass es schon neue Beträge gab, als ich meinen 
geschrieben hatte.
1
TIFR0 |= (1<<OCF0A);

Genauso habe ich es auch gemacht. Ich setze das Flag innerhalb des 
ADC-Interrupts einfach zurück.

von Karl H. (kbuchegg)


Lesenswert?

Markus schrieb:
> Habe zu spät gesehen dass es schon neue Beträge gab, als ich meinen
> geschrieben hatte.
>
>
1
TIFR0 |= (1<<OCF0A);
>
> Genauso habe ich es auch gemacht. Ich setze das Flag innerhalb des
> ADC-Interrupts einfach zurück.

Falsch.
Interrupt Flags werden nicht so zurückgesetzt, sondern so
1
   TIFR0 = (1<<OCF0A);

von Markus (Gast)


Lesenswert?

Beide Varianten funktionieren, zumindest bei mir.

Ich muss das Flag auf 1 setzen und dies tun beide Varianten. Der Status 
der anderen Flags dieses Registers interessieren mich nicht da ich sie 
nicht benötige. Verstehe nicht wirklich wo der Unterschied sein soll. 
Könntest du mich bitte aufklären? :)

von Karl H. (kbuchegg)


Lesenswert?

Markus schrieb:
> Beide Varianten funktionieren, zumindest bei mir.
>
> Ich muss das Flag auf 1 setzen und dies tun beide Varianten. Der Status
> der anderen Flags dieses Registers interessieren mich nicht da ich sie
> nicht benötige. Verstehe nicht wirklich wo der Unterschied sein soll.
> Könntest du mich bitte aufklären? :)

Der Unteschied ist:

Bei deiner Variante werden ALLE Interrupt Flags in diesem Register 
gelöscht :-) Auch die, die an dieser Stelle gar nicht gelöscht
werden sollen.
Bei meiner nur das OCF0A Flag. So wie es sein soll.

(Interrupt Flags werden gelöscht, indem man an das Register eine Maske 
zuweist, die genau bei den Flags eine 1 aufweist, die zurückgesetzt 
werden sollen.

In
   TIFR0 =  TIFR0  |  (1<<OCF0A);
enthält die Maske aber eine 1 für jedes momentan gesetzte Interrupt Flag
)

von Markus (Gast)


Lesenswert?

Du schiebst im Prinzip eine 1 an die Stelle von OCF0A und setzt damit 
das Flag zurück. Aber bei meiner Variante geschieht doch das gleiche?

Ich verknüpfe das gesamte Register und seinen aktuellen Wert per ODER 
mit einem Wert der genau an der Stelle von OCF0A eine 1 hat. Durch das 
ODER bleibt doch der Rest des Registers erhalten, nur OCF0A auf 1 
gesetzt. Durch die Verknüpfung der anderen Stellen mit einer 0 bleibt 
das alles erhalten?

Oder steh ich jetzt komplett auf dem Schlauch? :)

von Karl H. (kbuchegg)


Lesenswert?

Markus schrieb:
> Du schiebst im Prinzip eine 1 an die Stelle von OCF0A und setzt damit
> das Flag zurück. Aber bei meiner Variante geschieht doch das gleiche?

Nein.
Du hast

   TIFR0 =  TIFR0  |  (1<<OCF0A);

der Teil rechts vom = hat eine 1 sowhl an der Stelle OCF0A als auch an 
allen Stellen in denen TIFR0 schon eine 1 hatte

> Ich verknüpfe das gesamte Register und seinen aktuellen Wert per ODER
> mit einem Wert der genau an der Stelle von OCF0A eine 1 hat.

Eben.
Genau das willst du nämlich NICHT!

> Durch das
> ODER bleibt doch der Rest des Registers erhalten, nur OCF0A auf 1
> gesetzt.

Richtig.
Und genau das willst du nicht.
Erinnere dich: Überall dort, wo du ein 1 Bit hast, wird das Interrupt 
Flag zurückgesetzt.

Angenommen TIFR0 enthalte  0b01001110
OCF0A sei meinetwegen Bit Nr 7 (spielt im Grunde keine Rolle)
D.h. TIFR0 | (1<<OCF0A) wird zu 0b11001110

Jetzt nimmst du das TIFR0          0b01001110

und setzt alle Bits auf 0
bei denen hier                     0b11001110
ein 1 Bit ist

Was kommt raus?                    0b00000000

Ähm.Da ist ein bischen mehr auf 0 gegangen. Nicht nur Bit 7.

Das sind keine normalen Zuweisungen. Der AVR verhält sich hier anders! 
Und nebenbei bemerkt, wenn das eine normale Zuweisung wäre, dann würde 
man das Flag ja wohl so löschen

  TIFR0 &= ~(1<<OCF0A);

> Oder steh ich jetzt komplett auf dem Schlauch? :)

Sieht so aus.

von Markus (Gast)


Lesenswert?

Ach ja, natürlich. Hast recht, jetzt versteh ich was du meintest.

Vielen Dank nochmal :)

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.