Forum: Mikrocontroller und Digitale Elektronik Interrupt wenn ADC Signal ausfällt


von Nils Balmer (Gast)


Lesenswert?

Hallo

Ich bin mir nicht sicher ob das geht was ich möchte. Es gibt ja einen
Interrupt (AIG_ADC) der anspricht, sobald eine ADC Wandlung
abgeschlossen ist (wenn so definiert). Nun, wäre es auch denkbar einen
Interrupt auszulösen, sobald eine zu messende Spannung abfällt?
Oder muss ich in der while Schlaufe konstant eine Messung vornehmen, um
dies festzustellen?

Danke und Gruss
Nils

von Hagen R. (hagen)


Lesenswert?

Statt ADC nimmste den Analog Comparator. An AIN- per Spannungsteiler die
Referenzspannung eingestellt an AIN+ deine zu überwachende Spannung. Den
AC kannst du nun so programmieren das er beim Unterscheiten der
Meßspannung einen Interrupt auslösst.

Gruß Hagen

von Hagen R. (hagen)


Lesenswert?

Über das ADMUX Register kannst du als AIN+ Eingang jeden beliebigen ADC
Eingang auswählen. Somit kannst du auch beides machen. Über AC die
Spannung überwachen und von Zeit zu Zeit per ADC den genauen Wert der
Spannung messen.

Gruß Hagen

von Nils Balmer (Gast)


Lesenswert?

Danke für deine Antwort Hagen! Weisst du wie der entsprechende Interrupt
heisst?

Leider habe ich keinerlei Erfahrung mit dem AC. Aber das wird schon
irgendwie klappen...

von Rahul (Gast)


Lesenswert?

Manche Controller (z.B. Mega64) haben die Möglichkeit, den ADC-Vorgang
aufgrund eines externen Ereignisses auszulösen.
Sowas ist im Datenblatt ("ADC-Trigger" oder so) beschrieben...
Die Variante von Hagen ist eine Möglichkeit wenn man eine konstante
Spannung überwachen will, die kontinuierliche AD-Wandlung eine
Möglichkeit, Tendenzen in der zu messenden Spannung zu überwachen...

von Nils Balmer (Gast)


Lesenswert?

Leider geht das bei mir nicht. Ich habe gerade gelesen, dass ich die
Bits PB0 und PB1 für den AC brauche. Diese sind jedoch (verwene
RN-Board 1.4) durch einen Motortreiber besetzt, den ich leider
verwende.

von Hagen R. (hagen)


Lesenswert?

Es gibt sehr verschiedene Möglichkeiten den Analog Comparator zu
benutzen. Als erstes solltest du dir klar darüber werden was du messen
möchtest. Den AC könntest du zb. so konfigurieren das AIN- intern an
der Bandgap hängt == 1.23V oder so. Und AIN+ kannst du über das ADMUX
Register auf jeden beliebigen ADCx Pin legen. Dh. eventuell brauchst du
nicht die beiden Pins PB0/1.

Allerdings verstehe ich dein Problem nicht so recht. Du misst ja eh
schon periodisch die Spannung über den ADC. Also kannst du auch gleich
in Software den ADC Wert auf zu geringe Spannung überprüfen, oder ?

>>>
Oder muss ich in der while Schlaufe konstant eine Messung vornehmen, um
dies festzustellen?
<<<

Das wird wohl das eigentliche Problem sein, du pollst den ADC in der
Mainloop. Man macht das anders:

1.) entweder ein Timer der per Interrupt periodisch aufgerufen wird
und dann eine Single ADC Konversion startet, ca. 23*ADC Prescaler Takte
lang.

2.) den ADC so eingestellt das man im Free Running Mode des ADCs
permament nacheinander den ADC laufen lässt. Bei meheren ADC Kanälen
kann dies ein bischen "verwirrend" werden wenn man die Kanäle im
ADMUX Register einstellen möchte.

In beiden Fällen musst du den ADC Interrupt benutzen. Bei Methode 1.)
startet die Timer ISR periodisch den ADC und der lösst je nach ADC
Prescaler nach einer bestimmten Taktanzahl die ADC ISR aus in der du
dann deine Spannung auswerten kannst.
Im zweiten Fall wird der AVR schon in der ADC ISR mit dem sampeln des
neuen Wertes anfangen. Du kannst aber über ADCH/ADCL den gerade
gesampelten Wert auswerten. Auf alle Fälle hast du im Free Running
Modus über den ADC Prescaler die Möglichkeit nicht nur die Genauigkeit
sondern auch das Interval einzustellen.

Somit läuft im Gegensatz zu deinem Polling in der Mainloop die
komplette ADC Wandlung, das Auslösen und das Auswerten per ISRs quasi
im Hintergrund ab.

Auch wenn du ein Anfänger bist lohnt es sich für dich, den Aufwand im
Verstehen und Lesen der Datenblätter jetzt und frühzeitig zu betreiben,
statt weiterhin mit Schleifen und Polling in der Mainloop zu arbeiten.
Ohne ISRs wirst du nicht weit kommen.

Gruß Hagen

von Nils Balmer (Gast)


Lesenswert?

OK, ich sehe, dass beide deiner Lösungen einiges eleganter sind! Ich
werde wohl deinen zweiten Vorschlag realisieren. Aber da stellt sich
die Frage ein die du angesprochen hast, wie ich das mit den
verschiedenen Kanälen handle?

von peter dannegger (Gast)


Lesenswert?

Da ja vom Fragesteller kein bestimmter MC genannt wure, der Silabs
C8051F120 kann das, der hat einen "ADC0 Programmable Window
Detector".

Man kann einen oberen und einen unteren Grenzwert festlegen und kriegt
je nach Konfiguration erst dann einen Interrupt, wenn der Wert
innerhalb oder außerhalb des Fensters liegt.
Die Wandlung kann also freilaufend völlig ohne CPU-Last erfolgen.


Peter

von Hagen R. (hagen)


Lesenswert?

Ich nehme an du willst einen AVR benutzen ?

Ok, der Free Running Mode wird einmalig gestartet und sampelt dann
kontinuierlich den/die ausgewählten ADC Kanäle. Jedesmal wenn der ADC
fertig ist wird ein ADC Interrupt ausgelösst. Die ADCH/ADCL Register
sind dabei quasi gepuffert. Sie enthalten also während der ISR den
letzten gesampelten Wert und im Hintergrund sampelt der ADC schon den
nächsten Wert. Als erstes musst du den Prescaler einstellen, je nach
gewünschter Genauigkeit. Je größer der Prescaler desto mehr Takte
braucht der ADC zum samplen und desto genauer wird es. Aber Vorsicht!
die Dauer der Messung hängt auch von deinem Signal ab, dh. wenn es sich
schnell ändert oder nicht sehr stabil ist sollte der Prescaler kleiner
sein -> schnellere Messung, weniger Auflösung.

Wenn du im Free Running Modus nur 1 ADC Kanal benutzen möchtest ist die
Sache ganz einfach. ADMUX auf den Kanal eingestellt und dann über ADCSR
den ADC im Free Running Modus gestartet. In der ISR dann ADCH/ADCL
einfach auslesen und auswerten.

Wenn du aber nun mehrere Kanäle auswerten möchtest musst du in der ADC
ISR ja den ADMUX Wert jedesmal ändern. Diese Änderung muß aus logischer
Sicht immer 1 ADC Samplezyklus vorraus sein. Wenn du also gerade den
ADMUX Kanal #1 eingestellt hast und die ADC ISR wird aufgerufen dann
heist dies das der ADC zu diesem Zeitpunkt also schon den Kanal #1
einliest. Änderst du nun in der ISR den ADMUX auf Kanal #2 so wird erst
ab dem nächsten ADC Interrupt dieser Kanal gesampelt. In diesem nächsten
ISR Aufruf steht also in ADCH/ADCL der Wert für Kanal #1 drinnen. Erst
im übernächsten Interrupt steht in ADCL/ADCH der Wert für Kanal #2.

Die Selektion des ADC Kanals in der ISR ist also zeitlich gesehen immer
1 ISR Zyklus vorraus. Wenn du also möchtest das beim nächsten ADC
Interrupt mit dem samplen des Kanales #5 begonnen werden soll dann
musst du dies jetzt im aktuellen ISR Aufruf schon einstellen. Der Kanal
#5 ist dann im  übernächsten ISR Aufruf im Register ADCH/ADCL.

Wenn du im Free Running Modus nur einfach und stupide ein Kanal nach
dem anderen Kanal einlesen möchtest (in deinem vorgewählten Muster),
dann ist das KEIN Problem.

Wenn du aber dieses Abarbeitungsmuster dynamisch an bestimmte
Ereignisse der Software/Hardware anpassen musst, sprich zb. einem INT0
muss unbedingt als nächstes sofort der Kanal #4 ausgelesen werden, dann
wird das Auswählen der Kanäle im Free Running Modus gedanklich ein
bischen komplizierter.

Es gibt dafür mehrere Möglichkeiten:

1.) in einer globalen Variable steht der ADC Kanal drinnen der beim
nächsten ADC ISR Aufruf in das ADMUX Register rein soll und somit im
übernächsten ADC ISR Aufruf fertig gesampelt wurde.
Diese globale Variable wird also in der ADC ISR in das ADMUX register
kopiert.

2.) Man könnte das ADMUX Register von aussen, zb. in der INT0 ISR
verändern. Das kann aber zu unschönen Resultaten führen, besonders wenn
diese Änderung des ADMUX Registers zeiltich gesehen ganz kurz nachdem
der ADC schon fertig gesampelt hat, erfolgte. Denn dann würde eine
Verzögerung von 1 ADC Zyklus entstehen, d.h. der ADC sampelt vorher
erstmal noch einen anderen Kanal und erst dann sampelt er den
gewünschten Kanal. Diese "Verzögerung" musst du dann aber in deiner
ADC ISR explizit berücksichtigen.

Ergo: ich empfehle dir Methode 1.) zu benutzen und in der ADC ISR
ausgehend vom Wert in dieser globalen Variablen den neuen Wert dieser
Kanalauswahl in der ADC ISR zu berechnen. Das heist die ADC ISR liest
als erstes ADCH/ADCL aus und speichert diesen Wert entsprechend dem
Inhalt im ADMUX Register zu einem Kanal. Danach setzt sie ADMUX auf den
Wert dieser globalen Variable. Und danach berechnet die ISR den neuen zu
sampelnden Kanal und speichert diesen in der globalen Variable. Eine
dynamische Anpassung des ADC Kanalmusters -> sprich welcher Kanal nun
gesampelt werden muß, kann nun extern durch zb. eine INT0 ISR auf
sichere Weise erfolgen indem man einfach die globale Variable ändert.

Gruß Hagen

von Hagen R. (hagen)


Lesenswert?

int8_t ADC_Next_Channel = 0;
int8_t ADC_Current_Channel = 0;

ISR(ADC_vect) {

// Wert zum ADC_Current_Channel auslesen
  ADC_Wert  = ADCL | (ADCH << 8);  // ADC_Wert = ADC; geht auch.

// nächsten ADC Kanal auswählen, wird exakt vor dem nächsten ISR
// Aufruf gestartet
  ADC_Current_Channel = ADC_Next_Channel;
  ADMUX = (ADMUX & ~0x07) | ADC_NextChannel;

// übernächsten Kanal berechnen, jedes beliebiges Muster möglich
  ADC_NextChannel = ADC_NextChannel +1;
}


ISR(INT0_vect) {

//  zb. Taste gedrückt, Kanal 5 muss eingelesen werden
   ADC_NextChannel = 5;
}

ADC_Current_Channel beinhaltet also den ADC Kanal der aktiv als
nächstes eingelesen wird. ADC_Next_Channel beinhaltet den Kanal der
voraus-bestimmt danach dran ist und kann noch bis zur nächsten ADC-ISR
sicher verändert werden.


Gruß Hagen

von Hagen R. (hagen)


Lesenswert?

Sorry, ist natürlich mal wieder falsch der obige Source ;)

int8_t ADC_Next_Channel = 0;
int8_t ADC_Current_Channel = 0;

ISR(ADC_vect) {

// Wert zum ADC_Current_Channel auslesen
  ADC_Wert  = ADCL | (ADCH << 8);  // ADC_Wert = ADC; geht auch.

// nächsten ADC Kanal auswählen, wird exakt vor dem nächsten ISR
// Aufruf gestartet
  ADC_Current_Channel = ADMUX & 0x07;
  ADMUX = (ADMUX & ~0x07) | ADC_Next_Channel;

// übernächsten Kanal berechnen, jedes beliebiges Muster möglich
  ADC_Next_Channel = ADC_Next_Channel +1;
}


ISR(INT0_vect) {

//  zb. Taste gedrückt, Kanal 5 muss eingelesen werden
   ADC_NextChannel = 5;
}

So ist es richtig.

Gruß Hagen

von Hagen R. (hagen)


Lesenswert?

Wenn du dir das nun mal durchdenkst so wirst du feststellen das bei
allerersten Start des ADCs im Free Running Modus der Kanal 0 zweimal
ausgelesen wird und danach erst reihum jeder Kanal nur noch
einmal.

Dies würde auch passieren wenn man dynmisch das Kanalmuster ändern muß
und eben nicht wie oben gezeigt arbeitet. Zusätzlich entstünde noch das
Problem das je nach Zeitpunkt wann wir ADMUX extern verändern
Verzögerungen in der Kanalselektion entstehen. Darauf in der ADC ISR
korrekt zu reagieren ist dann softwaretechnisch schwieriger als es, wie
oben gezeigt, von vorn herein per Softwarelogik auszuschließen.

Gruß Hagen

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.