Forum: Mikrocontroller und Digitale Elektronik ADC Messwert springt


von Ludwig P. (lprade)


Lesenswert?

Hallo,
ich habe an den ADC-Eingang eines AtMega8 (AVR-P28 von Olimex) mit einen 
Spannungsteiler einen Transitor als Temperatursensor angeschlossen 
(siehe 
http://de.wikipedia.org/w/index.php?title=Datei:NPN_Thermometer_Schaltung.svg&filetimestamp=20090330185439)
das auslesen funktioniert ganz gut, man sieht auch wie sich der Messwert 
ändert, wenn man den Transistor erwärmt.
Allerdings liefert der ADC immer abwechslend einen plausiblen Messwert 
(hier 42) und einmal 0. Dazwischen springt er immer hin und her. Lese 
den ADC per Interrupt 5 mal pro Sekunde aus, Mittelung mache ich keine. 
Liegt das an schlechter Verdrahtung am ADC-Pin (kann gut sein) oder hab 
ich irgendwo den ADC falsch eingestellt? Orientiert habe ich mich am 
Assembler-Tutorial
Viele Grüße,
Ludwig

von smoerre (Gast)


Lesenswert?

Ludwig P. schrieb:
> oder hab
> ich irgendwo den ADC falsch eingestellt?

Glaskugel sagt: Nein.

Quellcode!!!!

von spess53 (Gast)


Lesenswert?

Hi

ADC-Takt im grünen Bereich?

MfG Spess

von Ludwig P. (lprade)


Lesenswert?

Quellcode hab ich grad nicht da, kommt sobald ich zuhause bin^^
ADC-Takt liegt bei 62,5kHz (8MHz Takt und 128-Prescaler)

von Ludwig P. (lprade)


Lesenswert?

So, hier ist der Quelltext
1
/*
2
 * AVRAssembler2.asm
3
 *
4
 *  Created: 30.12.2011 12:08:33
5
 *   Author: Ludwig
6
 */ 
7
.include "m8def.inc"
8
 
9
.def temp1 = r16
10
.def temp2 = r17
11
.def temp3 = r18
12
.def temp4 = r19
13
.def counter = r20
14
.def led = r21
15
16
17
.org 0x0000
18
  rjmp main
19
.org OVF0addr
20
  rjmp timer0_overflow
21
22
main:
23
 
24
    ldi temp1, LOW(RAMEND)      ; LOW-Byte der obersten RAM-Adresse
25
    out SPL, temp1
26
    ldi temp1, HIGH(RAMEND)     ; HIGH-Byte der obersten RAM-Adresse
27
    out SPH, temp1
28
 
29
  ldi temp1, 0xFF
30
    out DDRC, temp1    ; Port C = Ausgang
31
32
  ldi temp1, 0xFF    ; Port D = Ausgang
33
    out DDRD, temp1
34
35
  ldi temp1, (1<<CS00) | (1<<CS02)
36
  out TCCR0, temp1
37
  ldi temp1, (1<<TOIE0)      ; TOIE0: Interrupt bei Timer Overflow
38
    out TIMSK, temp1
39
  ldi led, 0xFF
40
  ldi counter, 0
41
    sei
42
43
  ldi     temp1, (1<<REFS0) | (1<<ADLAR)                 ; Kanal 0, interne Referenzspannung 5V
44
    out     ADMUX, temp1
45
    ldi     temp1, (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0)
46
    out     ADCSRA, temp1
47
 
48
           rcall lcd_init     ; Display initialisieren
49
loop:
50
51
       rjmp loop
52
53
timer0_overflow:
54
  push temp1
55
  push temp2
56
57
  inc counter
58
  cpi counter, 5 ;nur bei jeden 5. Mal
59
  brne exit
60
  ldi counter, 0
61
62
  ldi temp2, 0
63
64
  out PORTC, led ;LED blinken lassen
65
  com led
66
67
;Ab hier die ganzen LCD Routinen wie aus dem Tutorial, nur mit push und pop Befehlen ergänzt
68
69
  rcall lcd_clear
70
  sbi ADCSRA, ADSC
71
wait_adc:
72
  sbic    ADCSRA, ADSC        ; wenn der ADC fertig ist, wird dieses Bit gelöscht
73
    rjmp    wait_adc
74
  in temp2, adch
75
76
  ldi temp1, '0' - 1
77
hundert:
78
  inc temp1
79
  subi temp2, 100
80
  brcc hundert
81
    rcall lcd_data
82
    subi temp2, -100
83
84
  ldi temp1, '0' - 1
85
zehn:
86
  inc temp1
87
  subi temp2, 10
88
  brcc zehn
89
    rcall lcd_data
90
  subi temp2, -10
91
92
  ldi temp1, '0'
93
  add temp1, temp2
94
  rcall lcd_data
95
96
exit:
97
pop temp1
98
pop temp2
99
  reti
100
101
  ;sendet ein Datenbyte an das LCD
102
lcd_data:
103
  push temp2
104
    
105
    mov temp2, temp1             ; "Sicherungskopie" für
106
                                        ; die Übertragung des 2.Nibbles
107
    swap temp1                   ; Vertauschen
108
    andi temp1, 0b00001111       ; oberes Nibble auf Null setzen
109
    sbr temp1, 1<<4              ; entspricht 0b00010000 (Anm.1)
110
    out PORTD, temp1             ; ausgeben
111
    rcall lcd_enable             ; Enable-Routine aufrufen
112
                                        ; 2. Nibble, kein swap da es schon
113
                                        ; an der richtigen stelle ist
114
    andi temp2, 0b00001111       ; obere Hälfte auf Null setzen 
115
    sbr temp2, 1<<4              ; entspricht 0b00010000
116
    out PORTD, temp2             ; ausgeben
117
    rcall lcd_enable             ; Enable-Routine aufrufen
118
    rcall delay50us              ; Delay-Routine aufrufen
119
  pop temp2
120
    ret                          ; zurück zum Hauptprogramm
121
 
122
 ; sendet einen Befehl an das LCD
123
lcd_command:                            ; wie lcd_data, nur RS=0
124
  push temp2
125
    mov temp2, temp1
126
    swap temp1
127
    andi temp1, 0b00001111
128
    out PORTD, temp1
129
    rcall lcd_enable
130
    andi temp2, 0b00001111
131
    out PORTD, temp2
132
    rcall lcd_enable
133
    rcall delay50us
134
  pop temp2
135
    ret
136
 
137
 ; erzeugt den Enable-Puls
138
 ;
139
 ; Bei höherem Takt (>= 8 MHz) kann es notwendig sein, 
140
 ; vor dem Enable High 1-2 Wartetakte (nop) einzufügen. 
141
 ; Siehe dazu http://www.mikrocontroller.net/topic/81974#685882
142
lcd_enable:
143
    sbi PORTD, 5                 ; Enable high
144
    nop                          ; mindestens 3 Taktzyklen warten
145
    nop
146
    nop
147
    cbi PORTD, 5                 ; Enable wieder low
148
    ret                          ; Und wieder zurück                     
149
 
150
 ; Pause nach jeder Übertragung
151
delay50us:                              ; 50µs Pause (bei 4 MHz)
152
  push temp1
153
    ldi  temp1, $42
154
delay50us_:dec  temp1
155
    brne delay50us_
156
  pop temp1
157
    ret                          ; wieder zurück
158
 
159
 ; Längere Pause für manche Befehle
160
delay5ms:                               ; 5ms Pause (bei 4 MHz)
161
  push temp1
162
  push temp2
163
    ldi  temp1, $21
164
WGLOOP0:   ldi  temp2, $C9
165
WGLOOP1:   dec  temp2
166
    brne WGLOOP1
167
    dec  temp1
168
    brne WGLOOP0
169
170
  pop temp1
171
  pop temp2
172
    ret                          ; wieder zurück
173
 
174
 ; Initialisierung: muss ganz am Anfang des Programms aufgerufen werden
175
lcd_init:
176
    ldi  temp3,50
177
powerupwait:
178
    rcall  delay5ms
179
    dec  temp3
180
    brne powerupwait
181
    ldi temp1, 0b00000011        ; muss 3mal hintereinander gesendet
182
    out PORTD, temp1             ; werden zur Initialisierung
183
    rcall lcd_enable             ; 1
184
    rcall delay5ms
185
    rcall lcd_enable             ; 2
186
    rcall delay5ms
187
    rcall lcd_enable             ; und 3!
188
    rcall delay5ms
189
    ldi temp1, 0b00000010        ; 4bit-Modus einstellen
190
    out PORTD, temp1
191
    rcall lcd_enable
192
    rcall delay5ms
193
    ldi temp1, 0b00101000        ; 4Bit / 2 Zeilen / 5x8
194
    rcall lcd_command
195
    ldi temp1, 0b00001100        ; Display ein / Cursor aus / kein Blinken
196
    rcall lcd_command
197
    ldi temp1, 0b00000100        ; inkrement / kein Scrollen
198
    rcall lcd_command
199
    ret
200
 
201
 ; Sendet den Befehl zur Löschung des Displays
202
lcd_clear:
203
    ldi temp1, 0b00000001   ; Display löschen
204
    rcall lcd_command
205
    rcall delay5ms
206
    ret
207
 
208
 ; Sendet den Befehl: Cursor Home
209
lcd_home:
210
    ldi temp1, 0b00000010   ; Cursor Home
211
    rcall lcd_command
212
    rcall delay5ms
213
    ret
Probehalbe Multiplier auf 64 setzen hat auch nicht geholfen.
Nochmal Danke!
Ludwig

von Krapao (Gast)


Lesenswert?

Du liest nie ADCL aus, machst du das bewusst?
Teste vielleicht mal:
1
  ; ADC Messung durchführen
2
  sbi ADCSRA, ADSC
3
wait_adc:
4
  sbic ADCSRA, ADSC ; wenn der ADC fertig ist, wird dieses Bit gelöscht
5
  rjmp wait_adc
6
  in temp2, ADCH ; <=== msb (wird verworfen)
7
  in temp2, ADCL ; <=== lsb (wird ausgegeben)

von Krapao (Gast)


Lesenswert?

Achtung zuerst ADCL dann ADCH lesen!

Beim Auslesen der ADC-Register ist zu beachten: Immer zuerst ADCL und 
erst dann ADCH auslesen. Beim Zugriff auf ADCL wird das ADCH Register 
gegenüber Veränderungen vom ADC gesperrt. Erst beim nächsten Auslesen 
des ADCH-Registers wird diese Sperre wieder aufgehoben. Dadurch ist 
sichergestellt, daß die Inhalte von ADCL und ADCH immer aus demselben 
Wandlungsergebnis stammen, selbst wenn der ADC im Hintergrund 
selbsttätig weiterwandelt. Das ADCH Register muss ausgelesen werden!
http://www.mikrocontroller.net/articles/AVR-Tutorial:_ADC#Die_Ergebnisregister_ADCL_und_ADCH
1
  in temp2, ADCL ; <=== lsb (wird ausgegeben)
2
  in temp1, ADCH ; <=== msb (wird verworfen)

von spess53 (Gast)


Lesenswert?

Hi

>Du liest nie ADCL aus, machst du das bewusst?

Es ist ADLAR gesetzt. Da braucht man das nicht.

MfG Spess

von olaf (Gast)


Lesenswert?

.....ist aref konstant genug? 2,2mV/°C sollte man gut messen 
können,allerdings nur so exakt wie aref....

von GeraldB (Gast)


Lesenswert?

Du vertauschst in timer0_overflow und delay5ms die gesicherten 
Register.

Wenn man mehrere Werte auf den Stack legt,
1
push temp1
2
push temp2
muß man sie in der umgekehren Reihenfolge wieder runterholen.
1
pop temp2
2
pop temp1

von Ludwig P. (lprade)


Lesenswert?

Wenn ich ADCL zuerst auslese, und dann gleich verwerfe, ändert das 
leider gar nichts. Sollte meiner Meinung auch keinen Unterschied machen, 
da ich die Wandlung ja mit
1
sbi ADCSRA, ADSC
anstoße, dann mit
1
wait_adc:
2
  sbic    ADCSRA, ADSC        ; wenn der ADC fertig ist, wird dieses Bit gelöscht
3
    rjmp    wait_adc
warte, bis das Ergebnis da ist (also bis ADSC wieder gecleared ist) und 
dann erst das Ergebnis mit
1
in temp2, adch
auslese.
Zur Erklärung: Ich möchte nur 8 Bit ausgeben. Deshalb ist auch ADLAR 
gesetzt, und ADCL wird nicht ausgwertet, und wurde deshalb auch nicht 
ausgelesen

von Ludwig P. (lprade)


Lesenswert?

GeraldB schrieb:
> Du vertauschst in timer0_overflow und delay5ms die gesicherten
> Register.
>
> Wenn man mehrere Werte auf den Stack legt,
>
1
> push temp1
2
> push temp2
3
>
> muß man sie in der umgekehren Reihenfolge wieder runterholen.
>
1
> pop temp2
2
> pop temp1
3
>

Danke, habe ich auch korrigiert! Leider wieder ohne das Problem zu lösen

von Ludwig P. (lprade)


Lesenswert?

Wenn ich das ADC-Pin offen lasse, krieg ich übrigends immer 255 und 0 
als Wandelergebniss...das versteh ich nun wirklich nicht :-(

von spess53 (Gast)


Lesenswert?

Hi

>Danke, habe ich auch korrigiert! Leider wieder ohne das Problem zu lösen

Hat auch keinen Einfluss, da die Register nicht im 'loop' verwendet 
werden.

Vielleicht solltest du doch mal deine Hardware überprüfen. Was sagt das 
Multimeter am ADC-Pin?

MfG Spess

von Ludwig P. (lprade)


Lesenswert?

Mh, das hüpft recht munter zwischen 4,2V und 4,9V umher. Was mich aber 
sehr wundert, ist das wenn ich den Pin offenlasse, der Wert auch hin und 
her springt

von Ralph (Gast)


Lesenswert?

Noch so ein Freak der unbedingt Assembler schreibt wo es nur wirklich 
nicht sein muss.
Fällt doch schon fast unter Sado/Maso.

Naja es gibt auch Leute die von Hamburg nach München zu Fuß gehen, nur 
weil es geht statt zu fahren......

von Ludwig P. (lprade)


Lesenswert?

Problem gefunden! Wenn ich die LED nicht blinken lasse, springt auch der 
ADC-Wert nicht.
Bitte um ein Herz für Anfänger :-P

von Ralph (Gast)


Lesenswert?

Ludwig P. schrieb:
> Problem gefunden! Wenn ich die LED nicht blinken lasse, springt auch der
> ADC-Wert nicht.

Nicht das Problem, nur ein Symptom......

Die Ursache kennst du noch nicht. ==> weitersuchen

von olaf (Gast)


Lesenswert?

....und,warum ist das so? ;-)

von smoerre (Gast)


Lesenswert?

Es gibt ein Race Condition zwischen dem timer und dem ADC + LCD init. 
Wenn der timer reinkommt bevor die initialisierung abgeschlossen ist, 
kracht es.
Ein Fix könnte sein das "sei" direkt vor die (leere) mainloop zu 
verschieben.

von Ludwig P. (lprade)


Lesenswert?

Ich denke, das auf den Olimex Board die Referenzspannung nicht 
ausreichend stabilisiert ist (z.B. fehlt die Induktivität völlig). Und 
da ich immer genau dann den ADC auslese, wenn ich die LED schalte nehme 
ich an, dass das auch die Referenzspannung in Mitleidenschaft zieht. Auf 
jeden Fall funktionierts jetzt einwandfrei

von Ludwig P. (lprade)


Lesenswert?

smoerre schrieb:
> Es gibt ein Race Condition zwischen dem timer und dem ADC + LCD init.
> Wenn der timer reinkommt bevor die initialisierung abgeschlossen ist,
> kracht es.
> Ein Fix könnte sein das "sei" direkt vor die (leere) mainloop zu
> verschieben.

Woran man doch alles nicht denkt, ihr seit echt gut :-D

von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Lesenswert?

Ludwig P. schrieb:

...
> Und
> da ich immer genau dann den ADC auslese, wenn ich die LED schalte nehme
> ich an, dass das auch die Referenzspannung in Mitleidenschaft zieht. Auf
> jeden Fall funktionierts jetzt einwandfrei
>
Das sich das so stark auswirkt, hätte ich nicht gedacht. Siehe Seite 196 
Punkt 4 im Datenblatt.

Analog Noise
Canceling Techniques
...
4. If any ADC [3..0] port pins are used as digital outputs, it is 
essential that these do not switch while a conversion is in progress. 
However, using the Two-wire Interface (ADC4 and ADC5) will only affect 
the conversion on ADC4 and ADC5 and not the other ADC channels

Bernd_Stein

von Hannes L. (hannes)


Lesenswert?

Ludwig P. schrieb:
>   out PORTC, led ;LED blinken lassen
>   com led

Damit invertierst Du alle Bits des PortC, was zur Folge hat, dass Du den 
PullUp-Widerstand des ADC-Eingangs ein/ausschaltest. Dieser sorgt dann 
für falsche Werte.

Bernd Stein schrieb:
> Das sich das so stark auswirkt, hätte ich nicht gedacht.

Ich auch nicht. Aber das wirkt sich ja nicht so aus, die Ursache ist ja 
der toggelnde interne PullUp.

...

von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Lesenswert?

Hannes Lux schrieb:
> Ludwig P. schrieb:
>>   out PORTC, led ;LED blinken lassen
>>   com led
>
> Damit invertierst Du alle Bits des PortC, was zur Folge hat, dass Du den
> PullUp-Widerstand des ADC-Eingangs ein/ausschaltest. Dieser sorgt dann
> für falsche Werte.
> ...
>
Vielleicht habe ich es ja übersehen, irre mich oder versteh wieder mal 
etwas nicht richtig. Aber um überhaupt einen ADC-Kanal nutzen zu können 
muß dieser doch als Eingang geschaltet sein bzw. um den Pull-Up 
einschalten zu können muß ich doch den Portpin als Eingang schalten
( DDRCx = 0 ) und dann den Portpin setzten ( PortCx = 1 ).

Wo geschieht dies im Programm ?

Habe nur gefunden, das der komplette PortC als Ausgang geschaltet wird.

main:
     ...

    ldi temp1, 0xFF
    out DDRC, temp1    ; Port C = Ausgang

Bernd_Stein

von Hannes L. (hannes)


Lesenswert?

Bernd Stein schrieb:
> Wo geschieht dies im Programm ?

   out PORTC, led ;LED blinken lassen
   com led        ;invertiert das ganze Byte und damit beim nächsten mal
                  ;den ganzen Port, also auch den ADC-Eingang...

>
> Habe nur gefunden, das der komplette PortC als Ausgang geschaltet wird.

Das habe ich nun wieder übersehen, da ich nicht weiter gesucht hatte, 
nachdem ich Obiges gesehen hatte. Aber das ist ja noch schlimmer, damit 
hat der Sensor ja gar keine Chance, seine Analogspannung messen zu 
lassen. Das Programm tut also genau das, was sein Autor programmiert 
hat, auch wenn der Autor eigentlich was ganz Anderes programmieren 
wollte.

Das Verwenden der Aliasnamen Temp1 bis Temp4 suggeriert mir, dass hier 
unverstanden Teile des Tutorials kopiert wurden und diese dann mit 
"Anfänger-Naivität" mit eigenem Code ergänzt wurden. Das ist ansich 
nichts Falsches. Trotzdem wäre es sinnvoll, sich erstmal zum Thema "Bits 
& Bytes" Gedanken zu machen. Ein Anlaufpunkt könnte Bitmanipulation 
sein.

...

von spess53 (Gast)


Lesenswert?

Hi

>Das habe ich nun wieder übersehen, da ich nicht weiter gesucht hatte,
>nachdem ich Obiges gesehen hatte.

Nein, du hast schon Recht. Bei eingeschalteten ADC wird der ausgewählte 
Kanal zum Eingang. Und dann greift der Pull-Up.

MfG Spess

von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Lesenswert?

spess53 schrieb:
> Hi
...
>
> Nein, du hast schon Recht. Bei eingeschalteten ADC wird der ausgewählte
> Kanal zum Eingang. Und dann greift der Pull-Up.
>

Ich habe vor kurzem aus dem Datenblatt des ADC des ATtiny26 eine
" Übersetzung " gemacht. Entweder habe ich dies überlesen, nicht richtig 
verstanden, es steht dort nicht oder ich habe es schlicht weg wieder 
vergessen. Könntest Du hier schreiben, auf welcher Seite dies beim 
ATmega8 im Datenblatt steht ?

Nach dieser " Übersetzung " habe ich erstmal keine Lust alles im DB des 
ATmega8 noch selber abzusuchen.

Wäre nett von Dir.

Bernd_Stein

von spess53 (Gast)


Lesenswert?

Hi

Diese Abhängigkeiten findet man unter I/O-Ports->Alternate Port 
Funktions.

MfG Spess

von spess53 (Gast)


Lesenswert?

Hi

>Nein, du hast schon Recht. Bei eingeschalteten ADC wird der ausgewählte
>Kanal zum Eingang. Und dann greift der Pull-Up.

Also ich habe mir das noch mal genau angesehen und kurz getestet. Und 
ich hatte Unrecht. Es wird nur der Analogeingang zugeschaltet. 
Portausgang und Pull-Up bleiben je nach DDRx erhalten und verfälschen 
das Ergebnis.
Passt auch zu seinen Ergebnissen. Bei der Schaltung aus dem Ausganspost 
sollte die gemessene Spannung irgendwo zwischen 0,6 und 0,7V liegen und 
nicht bei 4,xxV.

MfG Spess

von Bernd S. (Firma: Anscheinend Corner-Cases ;-)) (bernd_stein)


Lesenswert?

spess53 schrieb:
> Hi
>
> Diese Abhängigkeiten findet man unter I/O-Ports->Alternate Port
> Funktions.
>
> MfG Spess
>
Danke,

dort ist im Bild 25. Alternate Port Functions deutlich zu sehen das das 
AIOxn-Signal direkt von den Eingangspin abgenommen wird und die andere 
Seite wahrscheinlich direkt auf den Input MUX des ADCs geht ( Bild 90 ).

Somit erklärt sich ja auch sein Problem, da er ja den kompletten PortC 
als Ausgang konfiguriert hat und diesen auch noch regelmäßig invertiert, 
liest er natürlich das PINC0 immer mit ein, und dies ist ja mal High und 
mal Low.


Bernd_Stein

von spess53 (Gast)


Lesenswert?

Hi

>dort ist im Bild 25. Alternate Port Functions deutlich zu sehen das das
>AIOxn-Signal direkt von den Eingangspin abgenommen wird und die andere
>Seite wahrscheinlich direkt auf den Input MUX des ADCs geht ( Bild 90 ).

War mir schon klar. Ich war allerdings der (falschen) Meinung, das noch 
andere Steuerungen des Ports überschrieben werden, wie es z.B. UART,TWI 
... machen.

MfG Spess

von Ludwig P. (lprade)


Lesenswert?

Danke für die vielen Antworten.
ja ich habe Teile des Sourcecodes einfach aus dem Tutorial übernommen, 
und obwohl ich dachte, dass ich ihn schon verstanden habe, habe ich ihn 
dennoch nicht selbst geschrieben, und das rächt sich jetzt. Man muss 
halt schon schaun, wie verschiedene Programmteile sich gegenseitig 
beinflussen, vorallem in Assembler

von Hannes L. (hannes)


Lesenswert?

Ludwig P. schrieb:
> Man muss
> halt schon schaun, wie verschiedene Programmteile sich gegenseitig
> beinflussen, vorallem in Assembler

Dazu kommt noch, dass jeder ASM-Programmierer einen etwas anderen Stil 
hat. Kopiert man nun die Routinen von verschiedenen Quellen oder Autoren 
zusammen, dann wird es richtig haarig, dann gibt es Stilbruch...

Daher ist es meist besser, nur die Algorithmen zu verstehen und zu 
übernehmen und seinen eigenen Code im eigenen Stil zu schreiben. 
Zumindest bin ich damit bisher immer ganz gut zurechtgekommen. Die 
Lernkurve ist zwar flacher, das erworbene Wissen aber solider.

Bit- & Bytebruch,
Hannes...

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.