Forum: Mikrocontroller und Digitale Elektronik AD Wandlung mit IRQ


von Endress (Gast)


Lesenswert?

Hallo,

ich bin mir nicht sicher, ob mein Problem mit Interrupt lösbar ist:

Ich möchte nacheinander alle 8 ADC Pins auswerten, aber nicht dauerhaft, 
sondern nur bei Bedarf. DIe Interruptroutine soll nur dann angesprungen 
werden, wenn sich ein Port eklatant ändert.

Ist das möglich, oder muss ich einfach :

AD Wandlung ein, Pin1 lesen, AD Wandlung aus, Pin2 auswählen, 
Einschalten, Auslesen .... ??

und halt die Differenz prüfen auf eine Änderung ?

von Ralf G. (ralg)


Lesenswert?

Endress schrieb:
> eklatant

Soll bei einer bestimmten Differenz eine Aktion ausgeführt werden?
 -> evtl. nur entsprechend eingestellter Komperator
Sollen nur größere Messwertdifferenzen zu einer Regelung o.ä. führen?
 -> kontinulierlich/ gelegentlich einlesen und per Software auswerten

von W.S. (Gast)


Lesenswert?

Wenn du den richtigen µC benutzt, dann scannt der ADC im Hintergrund 
alle Inputs ab und legt die MW in einem Feld von HW-Registern ab. Da 
brauchst du garkeine Interrupt-Routine, sondern kannst dir die aktuellen 
Werte einfach so aus der HW laden.

W.S.

von Sascha (Gast)


Lesenswert?

Was ist eine eklante Änderung für dich? Genug um einen Pin Change 
Interrupt auszulösen?

Ansonsten wie beschrieben: ADC Free Running Mode, in jeder 
Interruptroutine erst ADC in Variable speichern und dann MUX Register 
auf neuen Eingangspin setzen.
Ist imho die bessere Lösung auch wenns auf den ersten Blick nach Polling 
aussieht. Denn die meiste Zeit läuft der ADC im Hintergrund und die 
Interrupt-Routine ist nur ein paar Byte groß.

von Endress (Gast)


Lesenswert?

Sorry, ganz vergessen: Atmega324-PAPU ( Atmega32 nur mit zwei Seriellen 
Ports )

Ich habe bestimmte Bereiche, die erkannt werden müssen, die recht weit 
auseinanderliegen ( sind nur 3 oder 4 ) Also zwischen 0 und 2 Volt, 2 
und 3, etc

Kann ich die Pin Change IRQ überhaupt für die AD Wandler nutzen ? Und 
wenn ja : Die Werte schwanken ja immer ein wenig, löst das dann auch 
schon einen Pin Change aus ? Ich frage im Augenblick nur einen Pin ab, 
der testweise an einem Poti hängt und der schwankt um 1 Wert hin- und 
her.

Mir wäre es am liebsten, dass bei Änderung erst die IRQ Routine 
angesprungen wird und am allerliebsten, wenn es für jeden AD Pin eine 
eigene Routine gibt...

Das mit dem Free Running Mode habe ich nicht ganz verstanden. Laut 
Datenblatt: "Switching to Free Running mode... will not cause any 
trigger event..."

Dann dürfte ja die Interruptroutine garnicht mehr angesprungen werden ?

von Oldie (Gast)


Lesenswert?

Pin-Change ist digital und erkennt nur Wechsel im Logik-Pegel:
Deutlich GRÖSSER, oder KLEINER, als halbe Betriebsspannung.

Ob sich eine Eingangsspannung deutlich ändert, aber nicht gerade
von max. LOW-Spannung zu min. HIGH-Spannung springt, kannst du
nur durch "scannen" aller zu überwachenden ADC-Kanäle schaffen.

Z.B. mit einem Timerinterrupt alle ADC-Kanäle zyklisch auslesen
und abspeichern.
Vorm Abspeichern vergleichst du den neuen und alten ADC-Wert
des jeweiligen ADC-MUX-Kanals und setzt ein FLAG-Register, wenn
größere Unterschiede vorliegen.

Im Hauptprogramm beobachtest du das FLAG-Register und reagierst
entsprechend.

Die Reaktionszeit liegt damit vielleicht im Bereich von 1 ms.

von Wolfgang (Gast)


Lesenswert?

Endress schrieb:
> Ich möchte nacheinander alle 8 ADC Pins auswerten, aber nicht dauerhaft,
> sondern nur bei Bedarf. DIe Interruptroutine soll nur dann angesprungen
> werden, wenn sich ein Port eklatant ändert.

Und warum soll der ADC nicht regelmäßig messen und anhand der Ergebnisse 
festellen, ob sich ein Port eklatant geändert hat. Es gibt kein Geld 
zurück, wenn der µC seine Zeit in Warteschleifen auf Interrupts 
verbringt.

von Karl M. (Gast)


Lesenswert?

Endress schrieb:
> Ich möchte nacheinander alle 8 ADC Pins auswerten, aber nicht dauerhaft,
> sondern nur bei Bedarf. DIe Interruptroutine soll nur dann angesprungen
> werden, wenn sich ein Port eklatant ändert.

Kurz und knapp, nein.

Du hast noch nicht das System Interrupt und ADC, z.b. in Verbindung mit 
einem ADC-Sleepmode verstanden.

I.A. läuft ein Codestück und vergleicht über eine Hysterese die ADC 
Messwerte mit deinen Zielwerten.
Bei erreichen dieser löst man einen (Software) Event aus.

In der Mainloop wird dieser dann ausgewertet, bzw. man reagiert darauf.

von Endress (Gast)


Lesenswert?

Ok, ich die Interrupts bringen also nichts, dann werte ich die 
Analogpins halt nacheinander in regelmässigen Abständen aus, aber auch 
das funktioniert nicht.

Könnte bitte mal einer über den Code schauen ? Ich mache scheinbar einen 
furchtbaren Gedankenfehler...

Ich habe die Datenregister linksbündig ausgerichtet also muss ich doch 
ADCL und ADCH um 6 stellen nach rechts verschieben ?

Wenn ich ADC ersetze mit ADCL in der Zeile, funktioniert es garnicht. So 
wie reinkopiert kann ich den ersten Port abfragen. Frage ich aber den 
zweiten ab, bekomme ich immer nur einen Wert für beide Ports zurück.

Wäre toll, wenn mir jemand helfen könnte, ich kann noch so lange das 
Datenblatt durchlesen, ich seh einfach nicht wo der Fehler liegt.

1
#ifndef F_CPU
2
#define F_CPU 8000000UL
3
#endif
4
5
#include <avr/io.h>
6
#include <util/delay.h>
7
#include <avr/interrupt.h>
8
#include <string.h>
9
#include <stdlib.h>
10
#include "lcd.h"
11
12
void ADC_read(uint8_t portpin)
13
{
14
  int werte[7],zaehler,muxwert;
15
  char Ausgabe[3];
16
    
17
  muxwert = portpin & 0b00000111;
18
  ADMUX |= muxwert;
19
  
20
  ADCSRA |= (1<<ADSC);
21
  while(ADCSRA & (1<<ADSC));
22
  werte[portpin]=(ADC>>6);  // Wenn hier ADCL steht funktioniert es nicht...
23
  werte[portpin]|=(ADCH>>6);
24
  itoa(werte[portpin],Ausgabe,10);
25
  usart_puts(Ausgabe);
26
  usart_putc(' ');
27
}
28
29
30
int main(void)
31
{
32
  
33
  ADMUX = (1<<ADLAR)|(1<<REFS0);               // linksbündig; externe Referenzspannung
34
  // ADC Aktivieren; ADPS0-1 Prescaler = 128 => 62,5 KHz; IRQ aktiviert
35
  // ADCSRA = (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)|(1<<ADIE);    
36
  // ohne Interrupt
37
  ADCSRA = (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)|(0<<ADIE);    
38
                
39
  // 1. Konvertierung starten
40
  ADCSRA |= (1<<ADSC); 
41
  
42
  while (1)
43
  {
44
      ADC_read(0);
45
      // ADC_read(1);
46
      // ADC_read(2);
47
      _delay_ms(300);
48
      usart_putc(10);    
49
  }
50
}

von Eric B. (beric)


Lesenswert?

In ADC_read setzt du die Portpin bits nur (mit der Ver-oder-ung).
Sie werden nie gelöscht! Also:
1
  ADMUX = (ADMUX & 0b11111000) | muxwert;

: Bearbeitet durch User
von Sascha (Gast)


Lesenswert?

1
werte[portpin]=(ADC>>6);  // Wenn hier ADCL steht funktioniert es nicht...

In C reicht es ADC einzulesen, das wird beim Compilieren automatisch in 
ADCL und ADCH umgesetzt.
Falls Left adjusted mit ADLAR, dann reicht es ADCH zu lesen.
1
  while(ADCSRA & (1<<ADSC));

Busy wait. Mit ISR vermeidbar.

Hier mal ne knappe Version wie man per ISR mehrere ADC Pins einliest:
1
uint8_t ADCValues[8] = {0,0,0,0,0,0,0,0};
2
uint8_t ADCMux = 0;
3
//ADC Conversion complete ISR
4
ISR(ADC_vect)
5
{
6
  ADCValues[ADCMux] = ADCH;
7
  
8
  if(ADCMux < 1)
9
  {
10
    ADCMux += 1;
11
  }else
12
  {
13
    ADCMux = 0;
14
  }
15
  ADMUX &= ~(0b00011111);//Die MUX Bits löschen
16
  ADMUX |= ADCMux;//Die MUX Bits wieder setzen 
17
  
18
  ADCSRA |= (1<<ADSC);//Nächste Conversion starten
19
}
20
21
void ADCSetup()
22
{
23
  //ADC Ref=AVCC, Left adjust
24
  ADMUX |= ((1<<REFS0) | (1<<ADLAR));
25
  //Input Pin: ADC0 (PA0)
26
  ADMUX |= ((0<<MUX4) | (0<<MUX3) | (0<<MUX2) | (0<<MUX1) | (0<<MUX0));
27
  //ADC Enable, Start Conversion, ADC Interrupt enable, CLK/128 = 31,2kSps
28
  ADCSRA |= ((1<<ADEN) | (1<<ADSC) | (1<<ADIE) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0));
29
30
}

Und in deiner Main() kannst du dann einfach auf das Array ADCValues 
zugreifen.
Hat keinen Busy-Wait drin und belastet daher die CPU kaum.

In meiner Variante setze ich ADSC manuell, kannst aber auch den 
free-running mode aktivieren. Ausserdem nutze ich ADLAR, weil mir 8 Bit 
reichen.

von Endress (Gast)


Lesenswert?

@Eric: genau das war das Problem, vielen Dank !

@Sascha: Auch an dich vielen Dank ! Das hilft mir auch ein grosses Stück 
weiter. Teste ich in der Forum auf jeden Fall.

von Eric B. (beric)


Lesenswert?

Sascha schrieb:
> if(ADCMux < 1)

Soll das nicht < 8 sein? Dann doch einfacher:
1
ADCMux = (ADCMux + 1) & 7;

von Karl M. (Gast)


Lesenswert?

Eric B. schrieb:
> ADCMux

Eric B. schrieb:
> Sascha schrieb:
>> if(ADCMux < 1)
>
> Soll das nicht < 8 sein? Dann doch einfacher:
> ADCMux = (ADCMux + 1) & 7;

Da lohnt es sich doch immer im Datenblatt der Atmel AVR die Belegung der 
Bits im ADCMUX Register zu überprüfen.
Wie dargestellt löscht (=0) man alle höherwertigen Bits!
Also unsinning und fehlerträchtig!

von Ralf G. (ralg)


Lesenswert?

Karl M. schrieb:
> Da lohnt es sich doch immer im Datenblatt der Atmel AVR die Belegung der
> Bits im ADCMUX Register zu überprüfen.
:-)
1. Das Register heißt ADMUX
2. Die Zwischenvariable heißt ADCmux
> Wie dargestellt löscht (=0) man alle höherwertigen Bits!
3. Das ist so gewollt. Das muss so sein
> Also unsinning und fehlerträchtig!
Bei fehlerträchtig würde ich zustimmen. Aber nur bei der Bezeichnung der 
Variablen.

von Endress (Gast)


Lesenswert?

ADCMux ist doch nur eine Variable zum hochzählen der AD Pins ...

<1 vermutlich, weil ich in meinem Code auch nur den ersten abgefragt 
habe.

Allerdings hab ich doch noch die Variable als int definiert und beide 
Register ACDL, ACDH nacheinander ausgelesen. Bei dieser Variante bekommt 
man nur eine Auflösung von 8 bit.

Und der Vollständigkeit halber:

Mein ursprünglicher Code mit den beiden Registern war falsch. So muss es 
richtig heissen:

ADCValues[ADCMux] = (ADCL>>6);
ADCValues[ADCMux] |= (ADCH<<2);

Hab mich jetzt doch für die Interruptvariante entschieden, vielen Dank 
nochmal.

von Sascha (Gast)


Lesenswert?

Das ist tatsächlich gewollt und ja, ADCMux ist vielleicht als Name kein 
tolles Beispiel.

Und ja, man könnte sich ADCMux auch komplett sparen und direkt aufm 
Register rumrechnen.

Es ist ne funktionierende Lösung,  nichts tolles. Deswegen ja auch 
"knappe Version".

Wer davon nix hält, darf gern mal seine Version mit Unit Tests posten ;)

von Mein grosses V. (vorbild)


Lesenswert?

Endress schrieb:
> Mein ursprünglicher Code mit den beiden Registern war falsch. So muss es
> richtig heissen:
>
> ADCValues[ADCMux] = (ADCL>>6);
> ADCValues[ADCMux] |= (ADCH<<2);

Nicht wirklich.
1
ADCValues[ADCMux] = ADC >> 6;

Überlass das Gefriggel dem Compiler.

: 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.