Forum: Mikrocontroller und Digitale Elektronik Atmega8 und ADC [noob]


von Matthias (Gast)


Lesenswert?

Guten Abend,

ich versuche mich gerade an ADC mti dem Atmega8, es will mir aber nicht 
ganzgelingen.
Ich habe versucht mich an 
Beitrag "ATmega8 ADC-Durchschnittsberechnung" zu orientieren.
Meine Schaltung ist einfach

+3,3V (da über Raspberry versorgt)
_ 3,3V PB0 & AREF
 |
 |
330Ohm
 |
 |
 |-- PC1(ADC1)
 |
 |
PT100
 |
 |
_ GND

Die Genauigkeit der Messschaltung ist mir aktuell noch egal, ADC an sich 
ist interessant

mein Code:
1
#include <avr/io.h>
2
#include <stdlib.h>
3
4
#ifndef F_CPU
5
#define F_CPU 1000000UL
6
#endif
7
#include <util/delay.h>
8
#include "LCD.h"
9
10
11
#define    V_REF    16500    //Referenzspannung 4,5854V
12
#define    MAXVALUE  1024    //Maximaler Wert des ADC
13
14
//////////////////////////////////////////////////////////////////////////
15
void init_ADC()
16
{
17
  ADMUX &= ~(1<<REFS1) & ~(1<<REFS0) & (1<<MUX0);
18
  ADCSRA |= (1<<ADEN) | (1<<ADSC) | (1<<ADPS2) | (1<<ADPS0);
19
  
20
  ADCSRA |= (1<<ADSC);        //eine ADC-Wandlung
21
  while (ADCSRA & (1<<ADSC) )          // auf Abschluss der Konvertierung warten
22
  {}
23
  (void) ADCW;
24
}
25
26
uint16_t ADC_Read( uint8_t channel )
27
{
28
  ADCSRA |= (1<<ADSC);            // eine Wandlung "single conversion"
29
  while (ADCSRA & (1<<ADSC) ) {   // auf Abschluss der Konvertierung warten
30
  }
31
  return ADCW;                    // ADC auslesen und zurückgeben
32
}
33
34
int main(void)
35
{  
36
  
37
  uint16_t a;
38
  char str[16];
39
  
40
  DDRB |= (1<<PB0);
41
  init_ADC(0x01);
42
    while(1)
43
    {
44
    init();          //LCD initialisieren
45
    
46
    PORTB |= (1<<PB0);
47
    
48
    a=ADC_Read(0x01);
49
    itoa(a,str,10);
50
    message(str);
51
    message(" ");
52
    a=ADC_Read(0x01);
53
    itoa(a,str,10);
54
    message(str);
55
    message(" ");
56
    a=ADC_Read(0x01);
57
    itoa(a,str,10);
58
    message(str);
59
    message(" ");
60
    cmd(0xC0,0);
61
    message("->");
62
    itoa(ADMUX,str,10);
63
    message(str);
64
    
65
    PORTB &= ~(1<<PB0);
66
    
67
    _delay_ms(5000);    //warte damit Display nicht so flackert
68
    }
69
}

Die Ausgabe beinhaltet drei zahlen in der ersten und eine in der zweiten 
Zeile des LCD-Displays.
Die Fragen die ich hätte:

1. Die erste Zahl schwankt zwischen 180/240 und 270. 180 nur am Anfang, 
danach halt 240. Das wundert mich nicht wirklich, ich habe nicht einen 
Kondensator verbaut und rien gar nichts gegen eine Eigenerwärmung des 
PT100 getan. Der Effekt stört mich auch noch nicht. Aber Zahl 2 und 3 
zeigen dauerhaft 1023 an. Warum ist das so? Die Read-Funktion ist ja die 
gleiche.

2. in der zweiten Display-Zeile will ich mir das Register ADMUX ansehen. 
Ich würde also eine 1 erwarten, da das LSB des Registers auf 1 gesetzt 
wurde. Das Display zeigt aber 0. Spreche ich das Register falsch an? 
Funktioniert eine "gesamtheitliche Ausgabe" überhaupt oder muss ich 
jedes Bit einzeln lesen wenn ich das sehen will?


Liebe Grüße
Matthias

von Walter S. (avatar)


Lesenswert?

Matthias schrieb:
> ADMUX &= ~(1<<REFS1) & ~(1<<REFS0) & (1<<MUX0);

die Zeile solltest du mal überdenken

von Matthias (Gast)


Lesenswert?

Walter S. schrieb:
> Matthias schrieb:
>> ADMUX &= ~(1<<REFS1) & ~(1<<REFS0) & (1<<MUX0);
>
> die Zeile solltest du mal überdenken

ja nach kurzem Nachdenken seh ich ein, dass ich da Handlungsbedarf habe.
Aber: das LSB ist bei allen drei Thermen 1

Und während ich schreibe merke ich, durch das &= ist es egal was ich an 
Thermen anfüge, wenn ADMUX vorher 0 ist, dann ist es danach auch 0. 
Richtig?

von Roland (Gast)


Lesenswert?

Genau, zu 0 kannst du 1 oder 0 hinzuverunden, es bleibt immer 0. ;-)

von M. K. (sylaina)


Lesenswert?

Matthias schrieb:
> ADMUX &= ~(1<<REFS1) & ~(1<<REFS0) & (1<<MUX0);

Ich schätz mal du wolltest
1
ADMUX &= ~((1 << REFS1) | (1 << REFS0) | (1 << MUX0));
schreiben. Was ich mich dabei frage: Warum glaubst du diese Bits löschen 
zu müssen? Die sind doch eh schon 0.

Dann fällt mir deine Funktion
1
uint16_t ADC_Read( uint8_t channel )
bei der du den Parameter channel gar nicht verwendest. Warum nicht?
Hast du dir das Tutorial hier auf der Seite zum ADC mal angeschaut? Und 
nachvollzogen?

von Matthias (Gast)


Lesenswert?

Michael K. schrieb:
> Ich schätz mal du wolltestADMUX &= ~((1 << REFS1) | (1 << REFS0) | (1 <<
> MUX0));schreiben. Was ich mich dabei frage: Warum glaubst du diese Bits
> löschen
> zu müssen? Die sind doch eh schon 0.

Ich wollte:
1
ADMUX |= ~(1<<REFS1) & ~(1<<REFS0) & (1<<MUX0);

von M. K. (sylaina)


Lesenswert?

Matthias schrieb:
> Michael K. schrieb:
>> Ich schätz mal du wolltestADMUX &= ~((1 << REFS1) | (1 << REFS0) | (1 <<
>> MUX0));schreiben. Was ich mich dabei frage: Warum glaubst du diese Bits
>> löschen
>> zu müssen? Die sind doch eh schon 0.
>
> Ich wollte:
1
ADMUX |= ~(1<<REFS1) & ~(1<<REFS0) & (1<<MUX0);

Also eigentlich wolltest du nur
1
ADMUX |= (1 << MUX0);
schreiben? Denn eine 0 zu verodern macht ja mal gar keinen Sinn.

: Bearbeitet durch User
von Walter S. (avatar)


Lesenswert?

Matthias schrieb:
> Ich wollte:ADMUX |= ~(1<<REFS1) & ~(1<<REFS0) & (1<<MUX0);

ich rate mal was du wolltest:
ADMUX &= ~(1<<REFS1) & ~(1<<REFS0);
ADMUX |= (1<<MUX0);

damit löscht du die Bits REFS0 und 1
und setzt Bit MUX0
ist aber auch nicht ganz logisch denn was ist mit MUX1 2 3 ?

von M. K. (sylaina)


Lesenswert?

Walter S. schrieb:
> ist aber auch nicht ganz logisch denn was ist mit MUX1 2 3 ?

Sind alle 0. Er ist im Init und Init-Value von ADMUX ist 0 bzw. 
0b00000000. Deshalb macht es ja keinen Sinn Bits zu löschen, sind ja eh 
alle gelöscht.

EDIT:

Also die Funktion ist ja nur zum Initialisieren. Der Aufruf der Funktion 
ist auch Quatsch, der Compiler müsste eigentlich meckern denn der Aufruf 
erfolgt so:
1
init_ADC(0x01);

Was soll das 0x01? Die Funktion hat keine Parameter, die man ihr 
übergeben könnte. Das sollte zum Fehler führen.

: Bearbeitet durch User
von Matthias (Gast)


Lesenswert?

ich vermisse "letzter post löschen" ;)
Was ich wollte ist, dass die Bits die ich definiere auch genau den Wert 
haben und alle anderen 0 sind. Da denke ich aber selber nochmal drüber 
nach ;)

Michael K. schrieb:
> Dann fällt mir deine Funktion
> uint16_t ADC_Read( uint8_t channel )bei der du den Parameter channel gar
> nicht verwendest. Warum nicht?

Ich hatte in der Funktion erst noch das Umschalten der Kanäle mit drin, 
dazu dann auch die Variable "channel". Nachdem es nicht funktioniert hat 
habe ich das erstmal entfernt.

Michael K. schrieb:
> Hast du dir das Tutorial hier auf der Seite zum ADC mal angeschaut? Und
> nachvollzogen?

ja, aber ehr überflogen. Nachdem ich oben erwähnten Beitrag als Vorlage 
hatte wollte ich ehr versuchen meine Anpassungen anhand des Datenblattes 
vom Atmega8 zu machen.


Nach Anpassung der oben erwähnten Zeile kommt die Ausgabe von

von Matthias (Gast)


Lesenswert?

Matthias schrieb:
> ich vermisse "letzter post löschen" ;)

.... auf meinen post bezogen

von Matthias (Gast)


Lesenswert?

ich habe den Code mal ein bisschen zusammen geschoben
1
#include <avr/io.h>
2
#include <stdlib.h>
3
4
#ifndef F_CPU
5
#define F_CPU 1000000UL
6
#endif
7
#include <util/delay.h>
8
9
#include "LCD.h"
10
11
#define    V_REF    16500    //Referenzspannung 4,5854V
12
#define    MAXVALUE  1024    //Maximaler Wert des ADC
13
14
//////////////////////////////////////////////////////////////////////////
15
void init_ADC(void){
16
  ADMUX |= (1 << MUX0);
17
  ADCSRA |= (1<<ADEN) | (1<<ADSC) | (1<<ADPS2) | (1<<ADPS0);
18
  while (ADCSRA & (1<<ADSC)) {         // auf Abschluss der Konvertierung warten
19
  }
20
  (void) ADCW;
21
}
22
23
uint16_t ADC_Read(void){
24
  ADCSRA |= (1<<ADSC);            // eine Wandlung "single conversion"
25
  while(ADCSRA & (1<<ADSC)) {   // auf Abschluss der Konvertierung warten
26
  }
27
  return ADCW;                    // ADC auslesen und zurückgeben
28
}
29
30
int main(void)
31
{  
32
  uint16_t a;
33
  char str[16];
34
  
35
  DDRB |= (1<<PB0);
36
  init_ADC();
37
  
38
    while(1)
39
    {
40
    init();          //LCD initialisieren
41
    PORTB |= (1<<PB0);    //Stromversorgung für Schaltung ein
42
    
43
    a=ADC_Read();
44
    itoa(a,str,10);
45
    message(str);
46
    message(" ");
47
    a=ADC_Read();  itoa(a,str,10);    message(str);  message(" ");
48
    a=ADC_Read();  itoa(a,str,10);    message(str);  message(" ");
49
    cmd(0xC0,0);
50
    message("->");
51
    itoa(ADMUX,str,10);  
52
    message(str);      //Ausgabe Register ADMUX
53
    
54
    PORTB &= ~(1<<PB0);    //Stromversorgung für Schaltung aus
55
    _delay_ms(5000);    //warte 5s
56
    }
57
}

Die 0en für mein Verständnis habe ich auch rausgeschmissen.
Mir ist auch aufgefallen, dass ADSC in der init_ADC() zweimal direkt 
hintereinander gesetzt wurde. Das war im Beispiel zwar auch so, macht 
aber keinen Sinn. Nachdem das gesetzt wurde bleibt es ja eh 1 bis die 
Conversion abgeschlossen ist. Korrekt?

Die Frage nach der Ausgabe von Register ADMUX hat sich damit erledigt, 
das funktioniert.

Die erste Zeile ist allerdings sehr konstant "0 1023 1023".

Soweit als Update, ich suche mal weiter ;)

von Michael (Gast)


Lesenswert?

Das schaut fast gut aus.
1. Der Max Value von ADC ist 1023, nicht 1024. Er kennt 1024 Zustände 
aber 0 ist ja auch ein zustand ;)
2. Du setzt keinen der REFS in ADMUX, damit wird der AREF-Pin bzw die 
Spannung an diesem als Referenz benutzt(glaube ich). Hast du den da auch 
eine Referenzquelle angeschlossen?

von Matthias (Gast)


Lesenswert?

Michael schrieb:
> 1. Der Max Value von ADC ist 1023, nicht 1024. Er kennt 1024 Zustände
> aber 0 ist ja auch ein zustand ;)

hast du recht, danke für den Hinweis.

Michael schrieb:
> 2. Du setzt keinen der REFS in ADMUX, damit wird der AREF-Pin bzw die
> Spannung an diesem als Referenz benutzt(glaube ich). Hast du den da auch
> eine Referenzquelle angeschlossen?

Matthias schrieb:
> +3,3V (da über Raspberry versorgt)
> _ 3,3V PB0 & AREF
>  |
>  |
> 330Ohm
>  |
>  |
>  |-- PC1(ADC1)
>  |
>  |
> PT100
>  |
>  |
> _ GND

oben bei den 3,3V

Ich würde ja ein Bild von meinem Steckbrett machen aber das bringt aus 
meiner Sicht überhaupt keinen Mehrwert! Kraut&Rüben

Ich rätsel weiter, lese mir jetzt doch das ADC-Tutorial nochmal genauer 
durch.

Wie gesagt:
- keinerlei Kondensatoren auf dem ganzen Steckbrett. Ich gehe aber davon 
aus, dass das nur für die Signalqualität notwendig ist.

von Matthias (Gast)


Lesenswert?

Matthias schrieb:
> ADMUX |= (1 << MUX0);
>   ADCSRA |= (1<<ADEN) | (1<<ADSC) | (1<<ADPS2) | (1<<ADPS0);

Fehler 1
Auszug aus dem Datenblatt:
"The ADC is enabled by setting the ADC Enable bit, ADEN in ADCSRA. 
Voltage
reference and input channel selections will not go into effect until 
ADEN is set."
Die beiden Zeilen tauschen sollte also reichen?

Ich suche weiter ;)

von Michael (Gast)


Lesenswert?

Ah, das mit AREF hatte ich nicht gesehen. ;)

von Karl H. (kbuchegg)


Lesenswert?

SChmeiss mal das init() aus der Hauptschleife raus. Das hat da nichts 
verloren.
Dinge werden am Programmanfang initialisiert und danach lässt du sie in 
Ruhe.
Statt dessen siehst du in deinen LCD Funktionen nach, ob du eine 
Funktion hast, mit der du den Cursor wieder in die erste Zeile schicken 
kannst.
1
....
2
    while(1)
3
    {
4
    init();          //LCD initialisiere
5
...


Was soll eigentlich das sein?
1
    cmd(0xC0,0);
Ich vermute mal, dass das irgendein Commando fürs LCD ist. Allerdings 
bin ich jetzt zu faul rauszusuchen, welches. Aber egal welches, es hat 
es sich auf jeden Fall verdient, dass du es zu deinen LCD Funktionen 
unter einem sprechenden Namen hinzufügst.


Bau dir doch erst mal einen Satz von Hilfsfunktionen für dein LCD. Das 
ist doch Unsinn, wenn du bei jedem neuen Projekt das Rad immer wieder 
neu erfinden musst. Du willst Zahlen ausgeben. Nun, das soll vorkommen. 
Das soll sogar ziemlich oft vorkommen. Das soll so oft vorkommen, dass 
es sich schon verdient hätte, wenn du in deinem Vorrat an LCD Funktionen 
eine Funktion eigens nur dafür abstellen würdest. Du hast ja auch eine 
Funktion, die einen String ausgeben kann. Warum dann nicht eine für 
Zahlen?
1
void lcd_putu( uint16_t zahl )
2
{
3
  char txt[10];
4
5
  utoa( zahl, txt, 10 );
6
  message( txt );
7
}

(und jetzt sieht man auch, dass der Funktionsname 'message' nicht so 
glücklich gewählt ist. Denn 'message' eignet sich nicht besonders gut um 
damit eine Reihe von Funktionen zu beginnen. Funktionen die Strings, 
Character, Zahlen unsigned, Zahlen signed, Zahlen als Hexadezimalzahl, 
.... ausgeben. message an sich ist kein schlechter Name. Aber in der 
Reihe lcd_putc, lcd_puts, lcd_putu, lcd_puti erkennt man am letzten 
Buchstaben sofort, was diese Funktion ausgeben kann. lcd_put.. sagt mir 
das diese Funktionenfamilie dafür steht etwas asuzugeben, der letzte 
Buchstabe sagt mir, was diese Funktion ausgeben kann. c steht für char, 
s für String, u für unsigned, i für int, ....
Oh und by the way. das i in itoa steht für int. Du hast keinen int. Du 
hast einen unsigned int. Die Funktion dafür heisst utoa. Selbes Prinzip. 
Eine ganze Familie von Funktionen, bei denen 1 Buchstabe Auskunft 
darüber gibt, wofür die Funktion gedacht ist.)

: Bearbeitet durch User
von Matthias (Gast)


Lesenswert?

Karl H. schrieb:
> SChmeiss mal das init() aus der Hauptschleife raus. Das hat da nichts
> verloren.
> Dinge werden am Programmanfang initialisiert und danach lässt du sie in
> Ruhe.

Da hast du Pauschal recht, das Kommando gibt es bestimmt irgendwo, muss 
ich aber raussuchen .... das setzte ich mal auf die ToDo-Liste, aber mit 
geringerer Prio als die Funktion des ADC.

Karl H. schrieb:
> Was soll eigentlich das sein?    cmd(0xC0,0);

Zeilenumbruch

Zu dem Rest wo das Zitat jetzt zu lang wäre:
Ja du hast recht ich gebe es zu. Aber auch das schiebe ich mal nach 
hinten.

Ach wäre das schön wenn ein Tag 34 Stunden hätte ;)

von Karl H. (kbuchegg)


Lesenswert?

Matthias schrieb:
> Karl H. schrieb:
>> SChmeiss mal das init() aus der Hauptschleife raus. Das hat da nichts
>> verloren.
>> Dinge werden am Programmanfang initialisiert und danach lässt du sie in
>> Ruhe.
>
> Da hast du Pauschal recht, das Kommando gibt es bestimmt irgendwo, muss
> ich aber raussuchen .... das setzte ich mal auf die ToDo-Liste, aber mit
> geringerer Prio als die Funktion des ADC.

Nein. Nicht mit geringerer Prio. Sondern jetzt.

Denn für die reproduzierte 0 in deiner ersten Zeile gibt es nur eine 
Erklärung.
Deine LCD Beschreibere ist Mist.

Dein ADC zählt sicher nicht mit und denkt sich: Ooooch, da liefere ich 
bei jedem dritten mal auslesen einfach mal 0 um den Matthias zu ärgern.

Bau dir einen ordentlichen und verlässlich funktionierenden Satz an 
Hilfsfunktionen für dein LCD. Du brauchst Funktionen um
* den Cursor an eine bestimmte Position zu schicken
* das LCD löschen zu können
* wenn du willst, kann man auch eine 'Cursor Home' Funktion manchmal 
gebrauchen
* Funktionen für alle möglichen Ausgabefälle.

Das alles ist keine Hexerei und keine Raketentechnik. Es gibt genügend 
LCD-'Bibliotheken' bei denen man sich abschauen kann, was die so an 
Basisvorrat anbieten und wie einfach diese Funktionen zu schreiben sind.

: Bearbeitet durch User
von Matthias (Gast)


Lesenswert?

erneut ein Auszug aus dem Datenblatt:
"• Bits 2:0 – ADPS2:0: ADC Prescaler Select Bits
These bits determine the division factor between the XTAL frequency and 
the input clock to the
ADC."

Ich habe keinen Quarz dran. Ich gehe davon aus, dass man von der 
"build-in"-Frequenz auch von XTAL spricht?

Der eingestellte Divisor (0b101 bzw. 31,25kHz)  war auf jeden Fall 
außerhalb der empfohlenen 50-200kHz. Ohne Erfolg geändert auf 0b011 bzw 
125kHz. Mit 0b100 / 125kHz habe ich es auch versucht, alles ohne 
Veränderung.

von Matthias (Gast)


Lesenswert?

Und es gibt Fortschritte!

Weil es meistens die billigsten Sachen sind habe ich einfach mal die 
3,3V nicht vom PB0 genommen sondern direkt an Plus gesteckt.
Oh Wunder: "306 293 305" in der ersten Zeile des Displays.

Hat jemand eine Erklärung dafür?

von M. K. (sylaina)


Lesenswert?

Matthias schrieb:
> Hat jemand eine Erklärung dafür?

Wackelkontakt auf dem Steckbrett. Das sind eigentlich die ersten Dinge, 
die man prüft. Liegt auch wirklich an jedem Pin des uC das Signal an, 
das ich meine dass es anliegt.

von Matthias (Gast)


Lesenswert?

Michael K. schrieb:
> Matthias schrieb:
>> Hat jemand eine Erklärung dafür?
>
> Wackelkontakt auf dem Steckbrett. Das sind eigentlich die ersten Dinge,
> die man prüft. Liegt auch wirklich an jedem Pin des uC das Signal an,
> das ich meine dass es anliegt.

liegt an ja.

Da ich kein Oszi habe kann ich meine Theorie nicht wirklich bestätigen, 
aber ich habe das Gefühl, dass der Raspberry als Versorgung für die 
Schaltung langsam zu klein wird.

Als nächste Projekte sollte ich mir wohl mal einen ISP-Adapter für den 
PC und eine ausreichend starke Strom-/Spannungsquelle bauen.

Selbst wenn es das dann nicht ist, ist es auf jeden Fall schonmal ein 
Fehler weniger.

von Walter S. (avatar)


Lesenswert?

Matthias schrieb:
> Oh Wunder: "306 293 305" in der ersten Zeile des Displays.
>
> Hat jemand eine Erklärung dafür?

das ist kein Wunder:
du schaltest aref und die zu messende Spannung unmittelbar vor der 
Messung ein, das taugt nicht, aref sollte stabil anliegen.
An aref gehört deshalb ein Kondensator.
Ich verstehe auch nicht warum du scheinbar stolz darauf bist keine 
Kondensatoren zu verwenden, an Vcc gehört auf jeden Fall auch einer.

von M. K. (sylaina)


Lesenswert?

Matthias schrieb:
> liegt an ja.

Ja offensichtlich lags ja nicht an, oder glaubst du der AREF-Pin sieht 
von wo die 3V3 kommen? Das ist dem total egal ob die vom Nachbar-Pin 
kommen oder von Alpha Centauri. Wichtig ist nur dass sie auch (stabil) 
anliegen.

Daher auch der Einwand von Walter: Kondensatoren immer auch da einsetzen 
wo es das Datenblatt empfiehlt. Verkehrt ist das nie.

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.