Forum: Mikrocontroller und Digitale Elektronik ADC mit NTC am Atmega32


von HansDampf (Gast)


Lesenswert?

Hallo, ich hoffe mir kann jemand einen Tipp geben was ich falsch gemacht 
habe. Ich will einen NTC mit einem Atmega32 auswerten. Am Pin PA0 liegen 
momentan ca. 2.3V.
Mein Programm sieht etwa so aus: (Vereinfachte Version, damit sich 
keiner durch meinen gesamten Code wühlen muss.)

void Init_ADC1(void)
{
  ADMUX |= (1<<REFS0);       //Referenzspannung
  ADMUX |= (1<<ADLAR);       //linksbündig Schreiben (8bit mode)
}

int main(void)
{
 DDRA = 0b00000000;
 Init_ADC1();

 sei();

while(1)
  {    ADCSRA |= ((1<<ADSC)|(1<<ADIF)); }

ISR (ADC_vect)

{
  analog1 = ADCH;
}

Eigentlich müsste doch so eine Messung gestartet werden und sobald sie 
beendet ist wird der Interrupt ausgelöst und das Register an die 
Variable übergeben werden???? Der Wert der Variablen verändert sich aber 
nicht.

von Franz Schlüter (Gast)


Lesenswert?

Damit der Interrupt für den AD Wandler aktiv wird, musst Du noch 
zusätzlich das ADIE Flag im ADCSRA aktivieren.

Deine Endlosschleife startet ständig neue Konvertierungen ohne auf das 
Ende zu warten. Das ständige Setzen von ADIF könnte den Interrupt auch 
ausschalten. Also ADIF und ADSC erst wieder setzen, wenn die 
Konvertierung abgeschlossen ist.

von Franz Schlüter (Gast)


Lesenswert?

Und noch ein Hinweis: Die Variable analog1 muss unbedingt als volatile 
deklariert sein. Sonst könnten Aktualisierungen verloren gehen.

von HansDampf (Gast)


Lesenswert?

Ach ja, hatte ADIF und ADIE verwechselt. Danke.

Im eigentlichen Programm wird die Messung nur ca. alle 4sec ausgelöst.

Mit dem Thema Messgenauigkeit usw. beschäftige ich mich erst wenn ich 
weiß dass es so schon mal "funktioniert".

Aber das tut es leider immer noch nicht.

von Karl M. (Gast)


Lesenswert?

Hallo,

ADC Enable fehlt und einiges mehr.

Mit welchen ADC-Takt läuft der ADC ?
Wird über die Bits ADPS2:0 eingestelt.

Welcher Datentyp hat analog1 ?

/ADC Control and Status Register A – ADCSRA/
ADEN ADSC ADATE ADIF ADIE ADPS2 ADPS1 ADPS0
ADCSRA.ADEN = 1
ADCSRA.ADPS2:0 = xyz  -- mit 50kHz <= ADC-Takt <= 200kHz

ADCSRA = ADCSRA -- clear pendig adc interrupt
ADCSRA.ADIE = 1 -- enable adc interrupt

/Special FunctionIO Register – SFIOR/
ADTS2 ADTS1 ADTS0 – ACME PUD PSR2 PSR10
SFIOR.ADTS2:0 = 0b000 --  Free Running mode

/ADC Multiplexer Selection Register – ADMUX/
REFS1 REFS0 ADLAR MUX4 MUX3 MUX2 MUX1 MUX0
ADMUX.MUX4:0 = 0b00000 -- ADC channel 'ADC0'
ADMUX.REFS0 = 1 -- AVCC with external capacitor at AREF pin
ADMUX.ADLAR = 1 -- left adjust

ADCSRA.ADSC = 1 -- ADC Start Conversion

von Karl M. (Gast)


Lesenswert?

Da kann man sich nur Frage, liest denn keiner mehr das Datenblatt ?

von Falk B. (falk)


Lesenswert?

@ HansDampf (Gast)

>Mein Programm sieht etwa so aus: (Vereinfachte Version, damit sich
>keiner durch meinen gesamten Code wühlen muss.)

NÖ!

https://www.mikrocontroller.net/articles/Netiquette#.C3.84u.C3.9Fere_Form

"Quelltext nie abtippen, sonder immer das direkt kopierte Original 
posten, anderenfalls schleichen sich neue Fehler ein oder die 
existierenden werden nicht abgeschrieben."


>Eigentlich müsste doch so eine Messung gestartet werden und sobald sie
>beendet ist wird der Interrupt ausgelöst und das Register an die
>Variable übergeben werden???? Der Wert der Variablen verändert sich aber
>nicht.

Fang mal deutlich kleiner an, ohne Interrupts. EInfach auf das Ende der 
Wandlung per Schleife warten. Außerdem stimmt deine Initialisierung 
nicht.

1
void Init_ADC(void) {
2
3
  ADMUX  = (1<<REFS0) | (1<<ADLAR);  // ref=VCC, left adujst
4
  ADSRA  = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);                // ADC einschalten, prescaler 128
5
}
6
7
uint8_t get_adc(uint8_t channel) {
8
   
9
  ADMUX = (ADMUX & ~0x1F) | (channel & 0x1F);
10
  ADCSRA |= (1<<ADSC);
11
  while (ADCSRA & (1<<ADSC));
12
  return ADCH;
13
}
14
15
int main(void) {
16
  uint8_t x;
17
18
  DDRA = 0b00000000;
19
  Init_ADC();
20
21
  sei();
22
23
  while(1) {
24
    x = get_adc(0);
25
  }
26
}

von HansDampf (Gast)


Lesenswert?

Karl M. schrieb:
> Da kann man sich nur Frage, liest denn keiner mehr das Datenblatt ?

Ich habe mich lange mit dem Datenblatt beschäftigt, natürlich. Aber 
anscheinend habe ich wohl ein paar Sachen übersehen was bei einem 
Datenblatt mit 346 Seiten schon mal passieren kann, wie ich finde.

von Falk B. (falk)


Lesenswert?

@ HansDampf (Gast)

>anscheinend habe ich wohl ein paar Sachen übersehen was bei einem
>Datenblatt mit 346 Seiten schon mal passieren kann, wie ich finde.

Das Kapitel über die ADC-Register ist DEUTLICH kürzer. Eagl.

von Jakob (Gast)


Lesenswert?

Temperaturmessungen macht man sinnvollerweise nur im Abstand von
mehreren Sekunden. Wenn andere Aufgaben des µC wirklich zeitkritisch
sind, sollten DIESE mit Interrupts erledigt werden!

Alle paar Sekunden mal im Hauptprogramm eine Warteschleife
für den Temperatur-ADC kostet effektiv "keine" Zeit und stört die
wirklich zeitkritischen Reaktionen des µC nicht.

von HansDampf (Gast)


Lesenswert?

Ok vielen Dank. Werde eure Vorschläge jetzt mal in Ruhe durcharbeiten. 
Da bin ich erst mal wieder ein paar Stunden beschäftigt.

von HansDampf (Gast)


Lesenswert?

Also Initialisierung des ADC habe ich jetzt verstanden (mit den vier 
Seiten aus dem Datenblatt -- ihr habt ja recht, so viel ist es gar 
nicht)

Ich versuche jetzt nur noch diesen Teil deines Codes zu verstehen:
korrigiere mich wenn ich falsch liege.

Falk B. schrieb:

> uint8_t get_adc(uint8_t channel) {
>
>   ADMUX = (ADMUX & ~0x1F) | (channel & 0x1F);
>   ADCSRA |= (1<<ADSC);
>   while (ADCSRA & (1<<ADSC));
>   return ADCH;
> }

Messung wird gestartet, es wird gewartet bis die Messung fertig ist und 
der Inhalt des ADCH-Registers wird an get_adc übergeben.??

Aber was hat es mit der ADMUX-Zeile und diesem channel auf sich? Ich 
verstehs leider nicht.

von Falk B. (falk)


Lesenswert?

@ HansDampf (Gast)

>Messung wird gestartet, es wird gewartet bis die Messung fertig ist und
>der Inhalt des ADCH-Registers wird an get_adc übergeben.??

Richtig.

>Aber was hat es mit der ADMUX-Zeile und diesem channel auf sich? Ich
>verstehs leider nicht.

Das ist Bitmanipulation. Nicht zu verwechseln mit 
Bitcoinmanipulation ;-)

ADMUX = (ADMUX & ~0x1F) | (channel & 0x1F);

(ADMUX & ~0x1F) ADMUX mit dem invertierten (~) 0x1F UND verknüpfen, auf 
gut Deutsch, lösche die unteren 5 Bits MUX0-4.

 0x1F = 0b00011111
~0x1F = 0b11100000

(channel & 0x1F); channel mit 0x1F UND verknüpfen, auf gut Deutsch, 
lösche alles außer den untersten 5 Bits. Damit ein Aufruf der Funktion 
mit einem channel >31 keinen Unsinn macht.

Teilergebnisse ODER verknüpfen.

Die ganze Aktion bewirkt praktisch, daß channel auf 0-31 begrenzt wird 
und diese 5 Bits in die untersten 5 Bits von ADMUX kopiert werden.

von Wolfgang (Gast)


Lesenswert?

Jakob schrieb:
> Temperaturmessungen macht man sinnvollerweise nur im Abstand von
> mehreren Sekunden.

Das kommt wohl ein bisschen drauf an, was sich an dem Temperatursensor 
so tut und wie groß dessen thermische Trägheit ist. Bei ein 
Pt-Drahtsensor z.B. kann es durchaus lohnend sein, im Abstand von 10ms 
zu messen.

von HansDampf (Gast)


Lesenswert?

Falk B. schrieb:
> Die ganze Aktion bewirkt praktisch, daß channel auf 0-31 begrenzt wird
> und diese 5 Bits in die untersten 5 Bits von ADMUX kopiert werden.

Aber mit MUX0-4 lege ich doch nur fest an welchem PIN mein analog-Signal 
hängt. Wozu brauche ich da so eine Begrenzung und wenn ich alle Bits in 
ADMUX lösche läuft ja gar nichts mehr.

Da kann ich jetzt überhaupt nicht mehr folgen.

von Falk B. (falk)


Lesenswert?

@HansDampf (Gast)

>Da kann ich jetzt überhaupt nicht mehr folgen.

Dann lies es noch einmal in Ruhe und denk drüber nach.

von HansDampf (Gast)


Lesenswert?

Falk B. schrieb:
> Dann lies es noch einmal in Ruhe und denk drüber nach.

Mach ich immer und immer wieder, nebenbei noch Datenblatt lesen um das 
irgendwie zu verstehen.

Bevor die Messung gestartet wird, werden also MUX0..4 gelöscht und durch 
Channel neu gesetzt. Meine Methode wäre gewesen MUX0..4 bereits beim 
Initialisieren zu setzten, je nach dem welchen PIN ich verwende aber 
dann könnte ich die anderen ja nicht mehr benutzen. Ich hoffe das ist 
soweit richtig.

Aber um "Channel" zu verwenden müsste ich doch erstmal definieren 
welchen PIN ich für die nächste Messung verwenden möchte??

Bitte noch ein kleiner TIP, im Tutorial ist es leider auch nicht so 
erklärt das es für mich Sinn ergibt. Und ich will es ja verstehen. Bin 
kein Fan von copy and paste.

von Karl M. (Gast)


Lesenswert?

Hallo,

vielleicht mal so, Falk Brunner hat dir eine Basis Funktion für fast 
alle ADC-Wendungen geliefert.

*Table 84. Input Channel and Gain Selections*
Dabei entspricht
ADC0 = 0b00000 = 0
ADC1 = 0b00001 = 1
usw.

Interessanter weise stimmen die Zahlen mit der ADC-Kanalnummer im 
"Single Ended Input" Modus zusammen.

Also ADC7 = 0b00111 = 7 !


Man könnte auch die eine Funktion uint8_t get_adc(uint8_t); in zwei 
Funktionen aufspalten.
1
uint8_t get_adc(uint8_t channel) {
2
  ADMUX = (ADMUX & ~0x1F) | (channel & 0x1F);
3
  ADCSRA |= (1<<ADSC);
4
  while (ADCSRA & (1<<ADSC));
5
  return ADCH;
6
}
1
void set_adcchannel(uint8_t channel) {
2
  ADMUX = (ADMUX & ~0x1F) | (channel & 0x1F);
3
}
4
5
uint8_t read_adcvalue(void) {
6
  ADCSRA |= (1<<ADSC);
7
  while (ADCSRA & (1<<ADSC));
8
  return ADCH;
9
}

Das könnte deiner Vorstellung evtl. entsprechen und stellt immer noch 
eine saubere funktionale Programmierung dar.

von Karl M. (Gast)


Lesenswert?

Nachtrag, zu den Pinzuordnungen,
siehe *Figure 1. Pinout ATmega32*

PA0 (ADC0)
PA1 (ADC1)
PA2 (ADC2)
PA3 (ADC3)
PA4 (ADC4)
PA5 (ADC5)
PA6 (ADC6)
PA7 (ADC7)

von HansDampf (Gast)


Lesenswert?

Karl M. schrieb:
> void set_adcchannel(uint8_t channel) {
>   ADMUX = (ADMUX & ~0x1F) | (channel & 0x1F);
> }

Heißt das dann wenn ich z.B. ADC1 verwende, müsste ich schreiben:

> void set_adcchannel(uint8_t channel = 1) {
>   ADMUX = (ADMUX & ~0x1F) | (channel & 0x1F);
> }

von Axel S. (a-za-z0-9)


Lesenswert?

HansDampf schrieb:
> Karl M. schrieb:
>> void set_adcchannel(uint8_t channel) {
>>   ADMUX = (ADMUX & ~0x1F) | (channel & 0x1F);
>> }
>
> Heißt das dann wenn ich z.B. ADC1 verwende, müsste ich schreiben:
>
>> void set_adcchannel(uint8_t channel = 1) {
>>   ADMUX = (ADMUX & ~0x1F) | (channel & 0x1F);
>> }

Nein.

Du solltest lernen, was eine Funktion in C ist, insbesondere was 
Funktionsparameter sind. Falls dir das aus irgendwelchen Gründen
nicht behagt (z.B. weil das C-Buch zu viele Seiten für deinen
Geschmack hat) dann schlage ich vor, du läßt das mit dem Program-
mieren ganz und wählst ein etwas anspruchsloseres Hobby.

Vielleicht etwas mit Blumen. Oder Briefmarken.

von Falk B. (falk)


Lesenswert?

@  Axel Schwenke (a-za-z0-9)


>Vielleicht etwas mit Blumen. Oder Briefmarken.

;-)))))

von Falk B. (falk)


Lesenswert?

@  HansDampf (Gast)

>Bevor die Messung gestartet wird, werden also MUX0..4 gelöscht und durch
>Channel neu gesetzt.

Stimmt. Ist das nicht toll?

> Meine Methode wäre gewesen MUX0..4 bereits beim
>Initialisieren zu setzten, je nach dem welchen PIN ich verwende aber
>dann könnte ich die anderen ja nicht mehr benutzen.

Eben.

>Ich hoffe das ist soweit richtig.

Ja.

>Aber um "Channel" zu verwenden müsste ich doch erstmal definieren
>welchen PIN ich für die nächste Messung verwenden möchte??

Ja, beim Aufruf der Funktion. Was glaubst du wohl, was diese Zeile hier 
macht?
1
x = get_adc(0);

>Bitte noch ein kleiner TIP, im Tutorial ist es leider auch nicht so
>erklärt das es für mich Sinn ergibt.

Hmm. Dann ist entweder das Tutorial schlecht oder dein Verständnis.

> Und ich will es ja verstehen.

Schon mal eine löbliche Einstellung.

von M. K. (sylaina)


Lesenswert?

Franz Schlüter schrieb:
> Und noch ein Hinweis: Die Variable analog1 muss unbedingt als volatile
> deklariert sein. Sonst könnten Aktualisierungen verloren gehen.

Falsche Begründung für das richtige Ziel. Ohne volatile gehen keine 
Aktualisierungen verloren sondern, je nach Compiler-Einstellung, kann es 
passieren, dass der Compiler die Variable weg optimiert da er nicht 
sieht dass sie woanders auch noch gebraucht wird. Mit volatile sagt man 
dem Compiler also: "Diese Variable nicht optimieren sondern so benutzen 
wie es im Quellcode steht." ;)

von Karl M. (Gast)


Lesenswert?

Hallo,

ich habe mit meinem ersten Beitrag auch noch nicht erkannt, dass 
HansDampf noch in den Anfängen steckt.

#msmiamouz sagt dazu "nullpe" das hier nicht als Beleidigung zitiert 
wird, nur als neu deutsches Wort "Anfänger".

So muss man als Anfänger viel lesen, probieren und lernen.
Es kann schon mal 2 Jahre dauern, bis man ein wenig "Durchblick" 
erreicht hat.

In der Zeit ohne Internet, waren die Lernphasen noch viel Länger, da die 
Quellen und die Hilfestellungen nicht so einfach verfügbar waren.

Also HansDampf, sei ehrlich mit Dir und bei deinen Beiträgen hier im 
Forum. Dann kann sich jeder auf deine Lernkurve einstellen, der es will.

von HansDampf (Gast)


Lesenswert?

Karl M. schrieb:
> Also HansDampf, sei ehrlich mit Dir und bei deinen Beiträgen hier im
> Forum. Dann kann sich jeder auf deine Lernkurve einstellen, der es will

Natürlich bin ich ein Neuling auf dem Gebiet, ich habe ja auch nie was 
anderes behauptet. Vor drei Wochen hatte ich zum ersten Mal einen 
Mikrocontroller in der Hand. Und meine C-Kenntnisse waren bis dahin auch 
nur Grundlagen.

Es ist auch nicht mein Plan Programmierung zu meinem Hobby zu machen. 
Freizeitgestaltung findet bei mir eher an der frisch Luft statt. Ich 
mach halt ein kleines Projekt um meinen Horizont zu erweitern und wer 
sich lieber mit Gleichgesinnten Profis unterhalten will braucht ja meine 
teilweise "naiven" Fragen nicht zu beantworten.

Allen die meine Fragen ernst genommen haben und ihre wertvolle Zeit 
geopfert haben sag ich vielen Dank.

von HansDampf (Gast)


Lesenswert?

Falk B. schrieb:
> Ja, beim Aufruf der Funktion. Was glaubst du wohl, was diese Zeile hier
> macht?
> x = get_adc(0);

Jetzt hab ich es wenigstens kapiert: Die (0) ist ja mein Pin0 und wird 
als Funktionsparameter an die Funktion get_adc übergeben. Und Channel 
entspricht dann 0x00; je nach dem welchen Pin man auswerten will.

Das Ergebnis wird dann an x "zurückgeschickt".

Ich hatte nur keine Ahnung von Funktionsparametern, tja jetzt weiß ich 
es und kann weiter basteln.

Danke noch mal.

Das ist halt meine Methode: learning by doing :-)

von Karl M. (Gast)


Lesenswert?

HansDampf schrieb:
> Die (0) ist ja mein Pin0

Sorry, falsch.

Ich hatte Dir doch die Zuordnungen hingeschrieben !

0 entspricht ADC0 und dieser Eingang liegt an welchem Pin ?

von Dietrich L. (dietrichl)


Lesenswert?

Karl M. schrieb:
> 0 entspricht ADC0 und dieser Eingang liegt an welchem Pin ?

Das sollte aber leicht im Datenblatt zu finden sein: ganz am Anfang 
unter "Pin Configurations". Die Pin-Nr. hängt dann noch von dem 
verwendeten Gehäuse ab.

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.