PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Atmega 8 / 16 ADC mit 2 Kanälen



Destrono
17.08.2010, 15:02
Hallo,
ich habe Probleme bei meinem Avr den Adc mit zwei Kanälen zu betreiben. Habe jetzt schon 3 Tage damit herumgespielt und komme einfach nicht mehr weiter.
Ich poste am besten erst mal den C Code, der bei mir in einem sourcefile names timer.c untergebracht ist:



#include "include.h"

uint16_t current_adc_null =0;
uint16_t old_adc_null =0;
uint16_t current_adc_eins =0;
uint16_t old_adc_eins =0;



void adc_init (void)
{
ADCSRA = (1<<ADEN) |(1<<ADSC) | (1<<ADATE) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); // free running prescaler = 128
ADMUX = (1<<REFS1) | (1<<REFS0) | (1<<MUX0); // interne Referenzspannung, Mux0 = Pin ADC1
// auf Abschluss der Konvertierung warten
// ADCSRA |= (1<<ADSC);
// while(ADCSRA & (1<<ADSC)); geht nicht !


// einmalige Messung
current_adc_null = ADCL;
current_adc_null |= (ADCH<<8);
current_adc_null = 0;
}
uint16_t read_adc0 (void){

unsigned char sreg;
sreg = SREG;
cli ();
current_adc_null = ADC;
SREG = sreg;
return current_adc_null;}

uint16_t read_adc1 (void){
uint8_t admux;
uint8_t sreg;
sreg = SREG;
admux = ADMUX;


ADMUX = (1<<REFS1) | (1<<REFS0) | (1<<MUX1); //ADC2
while (!(ADCSRA & (1<<ADIF))){} //Wait until current conversion is complete
ADCSRA |= (1<<ADIF); //clear interrupt Flag again
while (!(ADCSRA & (1<<ADIF))){} //Wait until new conversion is complete
ADCSRA |= (1<<ADIF); //reset Flag
while (!(ADCSRA & (1<<ADIF))){} //Wait until new conversion is complete
cli ();
current_adc_eins = ADC;

ADMUX = admux;
SREG = sreg;
return current_adc_eins;
}


Der Code ist so aufgebaut, dass Adc0 ganz normal gelesen wird, während bei der Messung von Adc1 erst das Admux Register zum Einstellen des Kanals gesichert wird, dann auf den neuen Kanal von Adc1 umgestellt wird und danach der alte Zustand wiedergehellt wird.

Dazu habe ich gleich zwei Fragen:
1. Mein Kontroller kommt aus folgender while Schleife, die ich oben auskommentiert habe, nicht mehr raus:
// ADCSRA |= (1<<ADSC);
// while(ADCSRA & (1<<ADSC));
Ist das normal?
2. Warum muss ich folgende Anweisungen aus dem Code mindestens 3 mal hintereinander verwenden:
ADCSRA |= (1<<ADIF); //reset Flag
while (!(ADCSRA & (1<<ADIF))){} //Wait until new conversion is complete
Wenn ich die Anweisung nur zwei mal schreibe, hat current_adc_null den gleichen Wert wie current_adc_eins.

Da die Adc Werte bei interner Referenzspannung und Kondensator am Aref Pin immer noch zwischen Werte von +/- 5 schwanken habe ich in meiner Main.c folgende If Schleifen eingebaut, deren Funktionsrümpfe nur aufgerufen werden sollen, wenn sich die Adc Werte außerhalb der Schwankungen geändert haben:



// Adc0 einlesen
read_adc0();
if ((old_adc_null - 6) > current_adc_null || (old_adc_null + 6) < current_adc_null ) {

old_adc_null = current_adc_null;


}

//Adc1 einlesen
read_adc1();
if ((old_adc_eins - 6) > current_adc_eins || (old_adc_eins + 6) < current_adc_eins ) {

old_adc_eins = current_adc_eins ;


}


In den Schleifen lasse ich natülich auch noch andere Befehle ausführen, wie zum Beispiel das Beschreiben des Displays mit den aktuellen ADC Werten und das Setzen von OCP Timerwerten.
Nun zu meinem Problem: So wie oben gepostet funktioniert funktioniert das ganze nicht, denn current_adc_null hat den selben Wert wie current_adc_eins und die erste if Schleife wird auch nur aufgerufen, wenn sich current_adc_eins ändert.
Lasse ich die Anweisung " old_adc_eins = current_adc_eins ;" in der zweiten If Schliefe oben weg, werden beide Adc Werte korrekt auf dem Display ausgegeben. Es scheint also alles zu funktionieren, bis auf die Tatsache, dass die Schwankungen von current_adc_eins nicht ausgeblendet werden.
Ich versteh überhaupt nicht wie das sein kann. Normalerweise dürfte doch current_adc_eins mit current_adc_null überhaupt nicht zusammenhängen, oder?
Wenn ich die Bedingungen in der If Schleife zum Ausblenden der Schwankungen einfach weglasse und beide Werte permanent auf dem Display ausgeben lasse, funktioniert es auch wieder (auch mit old_adc_eins = current_adc_eins )

Letzte und wichtigste Frage:
Ist es normal, dass die Adc Werte um bis zu +/- 100 Schwanken, wenn ich am PC6 Pin über einen 100 Ohm widerstand die Basis eines Transistor schalte?

Ich hoffe man kann das nachvollziehen, da es wohl nicht so einfach ist sich hier reinzudenken. Ich wäre Euch trotzdem sehr dankbar, wenn mir jemand helfen könnte, da ich jetzt schon viel Zeit reininvestiert habe und das Teil endlich zum Laufen bekommen möchte/muss.

MfG
Destrono

Hubert.G
17.08.2010, 15:29
Ganz klar ist mir dieses Programm nicht.
Was soll das SREG sichern, lass das den Compiler machen, der weiss was er tut.
Was setzt du hier zurück? ADCSRA |= (1<<ADIF); //clear interrupt Flag again
Warum verwendest du nicht den Interrupt für den ADC.
Geringe Schwankungen gleicht man aus indem man mehrmals misst und den Mittelwert bildet.
Einen Transistor über 100 Ohm anschalten bedeutet, das dort schlagartig 40mA fließen. Das tut der Stromversorgung des ADC-Port sicher nicht gut.

Destrono
17.08.2010, 18:05
Hallo,
der Kommentar "clear interrupt again" steht an der falschen Stelle. Ich habe die Anweisungen zum Löschen des Flagbits an unterschiedlichen Stellen im Code zu testzwecken kopiert. Hatte aber alles nichts geholfen.
Auf Interrupts möchte ich sehr ungern zurückgreifen, weil ich die Timer schon viele Interrupts produzieren.
Was für einen Vorteil hätte ich, wenn ich da Interrupts einsetzen würde?
Die Anweisung "ADCSRA |= (1<<ADIF); " löscht das Flag. Wenn eine neue Messung erfolgt ist und das Datenregister akutalisiert wurde, wird dieses Flag wieder gesetzt. So steht das im Datenblatt.
Nur wieso reicht es nicht, die ganze Prozedur mit Flag Zurücksetzen und Warten bis ein neuer Wert ermittelt wurde zwei mal zu machen? Meine Tests haben ergeben, dass das mindestens drei mal nacheinander erfolgen muss, sonst wird nicht der Wert vom neuen Kanal genommen.
Die kleinen Schwankungen im Adc Wert wären mir eigentlich egal, es geht viel mehr darum, dass das Display nicht jedes mal mit einen neuen Wert aktualisiert werden soll, wenn sich am Poti nichts getan hat. Daher die zwei if schleifen.
Der Widerstand zwischen Basis vom Transistor und µc ist eigentlich ein Poti. Laut Stellung waren 100 Ohm, laut Messgerät sind es sogar 250 Ohm und der Kontroller wird nicht mit 5 Volt versorgt. Ich habe den Pin also nicht mehr wie mit den zulässigen 0.04 Ampere belastet.

MfG
Destrono

Hubert.G
17.08.2010, 19:58
Warum handelst du das Interrupt-Flag wenn du den Interrupt nicht benutzt.
Es genügt doch ADSC abzufragen.
Wenn du mit Interrupt arbeitest macht der Kontroller wärend der Conversion was anderes, so wartet er in der while-Schleife.
Es steht im Datenblatt das Schaltvorgänge am ADC-Port wärend einer Conversion das Messergebnis beeinflussen.

Gock
17.08.2010, 21:46
Zu 1 und 2: Der logische "und" Operator ist "&&".
Zu "wichtigste Frage": Ja. Sowas macht man nicht, zu hoher Strom.

Destrono
17.08.2010, 22:29
danke schon mal für die Antworten.
Ist der bitweise Und-Opterator hier falsch? Im ADC Tutorial wird auch nur der "&" verwendet und nicht der logische "&&".

MichaF
18.08.2010, 12:07
Ich verstehe hier ein paar Dinge nicht. Grundätzlich rate ich vom Free Running Mode ab. Du hast ihn hier zwar nicht aktiviert, aber die Kommentar

// free running prescaler = 128

lässt vermuten das du evtl. doch davon aus gehst. Das passended Bit wäre ADFR. Würde ich aber wie gesagt nicht machen.



1. Mein Kontroller kommt aus folgender while Schleife, die ich oben auskommentiert habe, nicht mehr raus:
// ADCSRA |= (1<<ADSC);
// while(ADCSRA & (1<<ADSC));
Ist das normal?

Definitiv nicht. Aber wie stellst du das fest? Und vor allem in welcher Funktion? Schon in der adc_init?


2. Warum muss ich folgende Anweisungen aus dem Code mindestens 3 mal hintereinander verwenden:
ADCSRA |= (1<<ADIF); //reset Flag
while (!(ADCSRA & (1<<ADIF))){}

Wer sagt, das du das musst? Mit dem ADIF Bit hast du eigentlich nicht wirklich was am Hut - selbst mit aktiviertem Interrupt nur in den seltensten Fällen. Daher verstehe ich auch nicht so recht, was du mit dieser Anweisung bewirken willst. Auf die fertige Wandlung warten? Das geht über das ADSC Bit, das wird automatisch auf 0 gesetzt wenn die Wandlung durch ist. ADIF macht nur sowas ähnliches, eignet sich hier aber kaum.

3 mal hintereinander musst du aber gar nichts machen. Lediglich die erste Messung nach dem ADEN gesetzt wurde, sollte verforfen werden, aber das machst du ja schon in der init. In den normalen Wandelroutinen hat so ein mehrfach-wandlung-auslösen nichts verloren.


current_adc_null = ADCL;
current_adc_null |= (ADCH<<8);
current_adc_null = 0;

Solche Spielereien lieber den Compieler erledigen lassen, der weiß wie er auf die 16 Bit Register zugreifen muss. Das hier tut es genau so:

current_adc_null = ADC;

Wo genau ist jetzt eiegntlich das Problem? Einerseits schreibst du, das du nicht aus einer while raus kommst, andererseits scheinst du schon irgendwelche Messwerte zu bekommen. Irgendwo passt hier was nicht ;)

Diese ganzen SREG Sachen solltest du im übrigen auch lieber sein lassen, das geht früher oder später schief. Der Compiler macht schon das nötige.

Destrono
18.08.2010, 15:32
Hallo,
also bevor wir aneinander vorbei reden, sollte ich vielleicht erst mal erwähnen, dass ich in diesem Fall den Atmega 16 verwende. Später soll die Aufgabe aber ein Atmega 8 erledigen.
Bei meinem Code habe ich mich an folgendes Datenblatt gehalten:
http://www.atmel.com/dyn/resources/prod_documents/doc2466.pdf


Du hast die Freerunningvariante hier zwar nicht aktiviert, der Kommentar(...)lässt vermuten das du evtl. doch davon aus gehst. Das passended Bit wäre ADFR.

Dieses ADFR Bit finde ich beim Atmega 16 nicht. So wie angegeben müsste ich eigentlich schon die Freerunning Variante gewählt haben. Siehe Datenblatt Seite 219 Bit5.
Zu meiner Frage 1. : Ich habe das festgestellt, weil der Kontroller in eine Endlosschleife fällt, wenn ich adc_init aufrufe und die jetzt auskommentierten Stellen ohne "//" schreibe.

Hier scheint irgendwie jeder gegen die Freerunning-Variante zu sein. Ich möchte sie trotzdem einsetzen, weil das für mich praktischer ist. Die Main Schleife läuft einfach durch und braucht nur noch den aktuellen Wert vom ADC kopieren.


Wer sagt, das du das musst? Mit dem ADIF Bit hast du eigentlich nicht wirklich was am Hut - selbst mit aktiviertem Interrupt nur in den seltensten Fällen. Daher verstehe ich auch nicht so recht, was du mit dieser Anweisung bewirken willst. Auf die fertige Wandlung warten? Das geht über das ADSC Bit, das wird automatisch auf 0 gesetzt wenn die Wandlung durch ist. ADIF macht nur sowas ähnliches, eignet sich hier aber kaum.

Laut Datenblatt müsste ich mit dem ADIF Flag richtiger sein. Zum ADSC Bit steht im Datenblatt "In Free Running Mode,
write this bit to one to start the first conversion."
Es geht hier also in der Freerunning-Variante nur um die erste Messung?
Unabhängig davon, ob ich jetzt das ADIF oder das ADSC Bit wähle, müsste der Code doch trotzdem funktionieren. Und über meine Tests, bei denen ich die zwei ADC Werte einfach auf dem Display ausgeben lasse, habe ich herausgefunden, dass ich folgende Anweisungen mindestens drei mal hintereinander schreiben muss, damit current_adc_ein nicht den selben Wert hat, wie current_adc_null :
ADCSRA |= (1<<ADIF); //reset Flag
while (!(ADCSRA & (1<<ADIF))){}
Und genau das verstehe ich nicht. Normalerweise müsste es doch reichen diese Anweisungen höchstens zwei mal hinereinander zu schreiben.



Wo genau ist jetzt eiegntlich das Problem? Einerseits schreibst du, das du nicht aus einer while raus kommst, andererseits scheinst du schon irgendwelche Messwerte zu bekommen. Irgendwo passt hier was nicht Zwinkern

Das Problem ist, dass der Code so wie oben gepostet nicht so funktioniert, wie er soll.
Er funktioniert einwandfrei, wenn ich die Anweisung "old_adc_eins = current_adc_eins ;" in der zweiten If Schleife der Main funktion (habe ich oben auch gepostet!) rauslasse. Aber auch das habe ich oben schon erwähnt.
Ohne diese Anweisung flackert der Wert aber auf dem Display, was ja auch logisch und von mir nicht erwünscht ist.

MfG
Destrono

MichaF
18.08.2010, 16:14
Ganz ehrlich, ich verstehe deinen Code nicht wirklich ;)

Aber mal ein paar Grundsätzliche Dinge zum ADC und den verschiedenen Modi.

Free Running: Es wird ständig gewandelt, und es steht auch zu jedem Zeitpunkt ein gültiges Ergebnis in ADC. Das heißt im Umkehrschluss, wenn du in irgend einer Funktion mit einer while Schleife auf etwas warten musst, ist grundlegend etwas verkehrt, denn warten musst du in keinem Fall. Du kannst jederzeit einfach ADC auslesen (Wenn das zu schnell geht, hast du logischerweise mehrmals ein Messwert von dem gleichen Zeitpunkt, der ADC ist ja nicht beliebig schnell)

Single Conversion: Du bestimmt, wann eine Messung gestartet wird. Jetzt musst du auch tatsächlich abfragen (oder ausreichend lange etwas anderes tun, Stichwort Timer) ob die Wandlung abgeschlossen ist, und kannst dann dein Ergebnis holen.

Du versuchst hier gerade eine Kombination aus beidem (Free Running + Warteschhleifen). Der Free Running Mode bringt diverse Stolpersteine mit sich, und ist in den allermeisten Fällen kontraproduktiv.

Beispiel Single Conversion:

void adc_init (void)
{
ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); //prescaler = 128
ADMUX = (1<<REFS1) | (1<<REFS0); // interne Referenzspannung

ADCSRA |= (1<<ADSC); //Erste Messung anschubsen
while (ADCSRA & (1<<ADSC)); //Auf abschluss der Messung warten
unsigned int dummy = ADC; //Und ersten Messwert wegwerfen
}


unsigned int read_adc (unsigned char channel){
ADMUX = (ADMUX & ~(0x1F)) | (channel & 0x1F); //Logisches geraffel damit die restlichen Bits in ADMUX erhalten bleiben
ADCSRA |= (1<<ADSC); //Messung anschubsen
while (ADCSRA & (1<<ADSC)); //warten...
return ADC;
}

int main(){
adc_init();

unsigned int ch1, ch2;

ch1 = read_adc(1); //ADC Channel 1 wandeln
ch2 = read_adc(2); //ADC Channel 2 wandeln
}

Code ungetestet, sollte aber das Prinzip veranschaulichen. Vom Free Running Mode würde ich die finger lassen.

Gock
18.08.2010, 19:12
Du musst das ADFR Bit setzen, damit Du den Freerunning Mode einleitest. FR wie FreeRunning :-D
Zu erst hatte ich etwas ganz anderes verstanden, ist etwas konfus, weil scheinbar wenig für diese Variante spricht...
Interrupts helfen wirklich.

MichaF
18.08.2010, 19:38
@Gock: Klingt total logisch. Ist auch bei den meisten AVRs so. Ein Blick ins Datenblatt bringt aber frecherweie zum Vorschein, das man das beim Mega16 tatsächlich anders gelöst hat. Da gibt es nur ein ADATE == "ADC Auto Trigger Enable". Und was diesen Trigger auslöst ist dann wiederum im SFIOR einzustellen. Ich war auch überrascht, eigentlich dachte ich den ADC der Megas recht gut zu kennen - man lernt eben nie aus ;)

Ändert imho nichts daran, das der FR Mode keinen Vorteil bringt wenn man in Controller Zeiten gerechnet nur all Schaltjahr eine Messung braucht. Insbesondere beim Kanalwechsel muss man dann doch wieder einige beachte, was einen im Single Conversion Mode nicht zu interessieren braucht. FR macht Sinn wenn man z.B. ein Signal kontinuierlich sampeln muss. Aber das erfordert dann eine sehr angepasste Programmierung, um die anfallende Datenmenge auch verarbeiten zu können.

Gock
18.08.2010, 19:53
Mega16 hab ich noch nie benutzt. Ich beschränke mich auf so wenige wie möglich, weil auch ich schon bemerkt habe, wie inkompatibel die AVRs auch bei sehr ähnlichen Modellen sein können.
Gruß

Destrono
20.08.2010, 16:26
Hallo,
vielen Dank für Eure Hilfe. Jetzt, wo ich auf den Single Conversion Mode umgestiegen bin, geht es einwandfrei. An meiner main.c (wo sich die beiden if Schleifen befinden) musste ich nicht einmal was verändern. Der Code von MichaF zur Initialisierung und der Adc abfrage läuft auch einwandfrei.
Ich habe sogar die Schwankungen, die beim Bestlasten des Portpins entstanden sind, raus bekommen, indem ich einen Kondensator am Messpin angeschlossen haben. Das hat vorher auch nicht funktioniert.
Ich wüsste trotzdem ganz gerne, warum das Ganze im Freerunning Mode zuvor nicht funktioniert hat. Denn in meinem ersten Code konnte ich bis auf ein paar "Schönheitsfehler" immer noch keinen gravierenden Fehler finden. Vielleicht muss man den Adc im Freerunning Mode erst anhalten, bevor man den Kanal wechsel ?

MfG
Destrono

MichaF
20.08.2010, 17:07
Dein Code war schlicht und ergreifend falsch. Vom ADIF Bit hat man die Finger zu lassen wenn man nicht genau weiß was man tut, das ADSC ist dein Freund. Vom Free Running Mode ebenso. Das Timing kann hierbei sehr komplex werden. Aus diesem Grund kann dir auch keiner genau sagen warum dein Code nicht funktioniert hat. Davon abgesehen das er aus oben genannten Gründen verkehrt ist kommt es darauf an, wie oft du deine read Funktionen aufgerufen hast.

chientech
02.09.2010, 19:24
Hi,
ich meine mal gelesen zu haben, dass die AVR's 'pipelining' betreiben.
http://de.wikipedia.org/wiki/Pipeline_%28Prozessor%29

soll heißen, dass der AVR beim einlesen des ADCSRA evtl. auf einen alten stand von ADCSRA zugreift. Bin mir allerdings nicht 100% sicher. Hier im RNetz kennt sich sicher jemand besser aus und kann erklären warum dass in diesem Fall nicht zutrifft. 8-[

Aber schalt mal die Optimirung aus und probier das:

ADCSRA |= (1<<ADIF); //reset Flag
;
;
;
;
while (!(ADCSRA & (1<<ADIF))){}

mfg ch

MichaF
02.09.2010, 22:34
Hier im RNetz kennt sich sicher jemand besser aus und kann erklären warum dass in diesem Fall nicht zutrifft. 8-[

Nunja, auch wenn ich nicht gerade der Experte auf diesem Gebiet bin, versuche mich trotzdem mal eine Antwort ;)

Praktisch jeder halbwegs moderne Controller verfügt mindestens über eine zweistufige Pipeline. So auch der AVR. Dabei handelt es sich aber nur um eine relativ simple fetch & execute pipeline. In der ersten Stufe wird der Befehl gelesen, in der 2. stufe wird er ausgeführt. Wobei die 2. Stufe innerhalb eines Taktes die Daten holt, durch die ALU jagt, und sie wieder zurück schreibt. Veraltete Daten sollten zumindest bei Befehlen die nur einen Takt brauchen, und das sind auf dem AVR sehr viele, nicht vorkommen. Wie das bei umfangreicheren Befehlen aussieht vermag ich nicht zu sagen. Aber selbst wenn die ADC Daten jetzt 4 Takte "zu alt" sind... das sollte nun wirklich nicht der Grund für eine Fehlfunktion sein.

chientech
08.09.2010, 18:03
Sind wir nicht alle Experten O:)


Praktisch jeder halbwegs moderne Controller verfügt mindestens über eine zweistufige Pipeline. So auch der AVR. Dabei handelt es sich aber nur um eine relativ simple fetch & execute pipeline. In der ersten Stufe wird der Befehl gelesen, in der 2. stufe wird er ausgeführt. Wobei die 2. Stufe innerhalb eines Taktes die Daten holt, durch die ALU jagt, und sie wieder zurück schreibt.

OK, ich bin bis jetzt davon ausgegangen, dass die AVR's 1Takt=1Operation nur im Mittel auf Grund von Pipelining erreichen.


@Destrono hast du das mal probiert.

Aber schalt mal die Optimirung aus und probier das:

ADCSRA |= (1<<ADIF); //reset Flag
;
;
;
;
while (!(ADCSRA & (1<<ADIF))){}