AD Wandlung mit IRQ

Gast #4487691
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 ?
Gast #4487703
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.
Gast #4487710
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ß.
Gast #4487826
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 ?
Gast #4488603
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.
Gast #4488612
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.
Gast #4488628
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.
Gast #4489088
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
}
Gast #4489164
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.
#4489541
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.
Gast #4489555
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.
Gast #4489562
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 ;)

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren