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 ?
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
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.
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ß.
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 ?
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.
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.
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.
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 | }
|
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
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.
@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.
Sascha schrieb: > if(ADCMux < 1) Soll das nicht < 8 sein? Dann doch einfacher:
1 | ADCMux = (ADCMux + 1) & 7; |
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!
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.
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.
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 ;)
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.