Forum: Mikrocontroller und Digitale Elektronik ADC Messung funktioniert noch nicht


von RevolT3c (Gast)


Angehängte Dateien:

Lesenswert?

Hi @ all,

ich bin gerade dabei mir eine Temperaturmessroutine zu schreiben.

Ein Temperaturfühler soll einen Wert auf einen LCD Display ausgeben.

Sie funktioniert soweit auch, ich bekomm eine Temperatur auf dem Display 
angezeigt jedoch springt diese immer wieder auf 0C zurück und dann 
wieder auf einen bestimmten Wert z.B 20C. Ich benütze nur das LOWByte 
des ADC.

Das LowByte wird ausgelesen und in r25 geschoben anschließend an das LCD 
ausgegeben. Anschließen wird wieder neu ausgewertet. Ich steh grad aufm 
Schlauch. Kann mich jemand aufklären?

Gruss Ralph

von Uwe (Gast)


Lesenswert?

Hi!
Da sind so einige Fehler drinn.:

>onADC:
-sollte eigentlich eine ISR sein, ist es aber nicht, weil
 in Int.Tabelle nicht eingetragen(reti)
-es geht noch schlimmer

onADC:  .
         .      ; hier wird der Analogwert eingelesen   sei
  inc  r17
  cpi  r17,255
  brne  lcdZahl
  rjmp  onADC

lcdZahl: .
         .
         ret

Du springst aus "mainloop" nach onADC, prüfst aber nicht ob die Wandlung
fertig ist. Liest 255 mal die selbe Zahl und lässt sie dann anzeigen.
Zwischendurch werden die Int. auch noch freigegeben(sei)
Was denkst du dir eigentlich dabei?
Irgendeinen Hintergrund sollte es doch geben?
Wenn nicht und blos abgeschrieben-> setzen 6, weil Datenbl.nicht gelesen

Viel Erfolg Uwe

von Stefan B. (Gast)


Lesenswert?

>mainloop:
>       ...
>  rcall   onADC
>       ...
>  rjmp  mainloop
>
> onADC: ; hier wird der Analogwert eingelesen und
>        ; nach einer Schleife von 255mal
>        ; an den LCD über lcdZahl ausgegeben
>
>       ; Beginn ADC Wert auslesen
>  cli
>  in  r26,ADCL
>  in  r27,ADCH
>  asr  r27
>  ror  r26
>  asr  r27
>  ror  r26
>  mov  r25,r26
>  sei
>       ; Ende ADC Wert auslesen
>
>  inc  r17
>  cpi  r17,255
>  brne  lcdZahl ; dort RET für rcall onADC
>  rjmp  onADC

Das ist fischig: Der Kommentar bei onADC passt nicht zur Aktion. Der 
Code springt 254 mal zu lcdZahl und 1 mal zu onADC.

Das ist aber nebensächlich zum eigentlichen logischen Bug: Du liest den 
ADC Wert in der Schleife aus ohne darauf zu achten, ob die ADC Wandlung 
auch abgeschlossen ist und ein ADC Wert bereit steht.

Ich sehe auch keine Initialisierung von r17. Schlimmer noch: In den des 
Displays die sich auf lcdZahl anschliessen wird r17 fleissig ohne 
PUSH/POP benutzt.

von RevolT3c (Gast)


Angehängte Dateien:

Lesenswert?

Erstmal danke für die Antworten :)

Das ist mein erstes Projekt das ich versuch umzusetzen.
Ich hab das myAVR WorkpadPlus. Dort gibt es vorgefertigte Skripts für 
verschiedene Anwendungen. Dot versuche ich mir was draus zu basteln und 
für mich umzukonstruieren. Dabei lerne ich glaub am schnellsten.

Das der ADC eine bestimmte Zeit brauch bis er fertig mit der Wandlung 
ist war mir auch neu. Wie gesagt mir is noch vieles Neu aber es wird.

Neue Version is angehängt. Werden immer noch Fehler drin sein aber darum 
frag ich ja nach :)


@ Stefan: Was meinst du mit Initialisierung von r17? Und warum muss ich 
ihn auf den Stack schieben wenn er immer neu beschrieben wird. Seh jetzt 
grad nicht wo der Blockübergreifend benötigt wird?

von Karl H. (kbuchegg)


Lesenswert?

RevolT3c schrieb:
> Erstmal danke für die Antworten :)
>
> Das ist mein erstes Projekt das ich versuch umzusetzen.
> Ich hab das myAVR WorkpadPlus. Dort gibt es vorgefertigte Skripts für
> verschiedene Anwendungen. Dot versuche ich mir was draus zu basteln und
> für mich umzukonstruieren. Dabei lerne ich glaub am schnellsten.

Mit Sicherheit nicht.
Du lernst dort nicht systematisch. Vom Einfachen zum Schwierigeren.

Durch 'Malen nach Zahlen' hat noch kein Mensch 'richtiges Malen' 
gelernt. Er hat zwar am Ende ein Bild, aber von Themen wie Farbwahl, 
Motivsuche, Bildaufbau hat er immer noch keine Ahnung.

Arbeite das AVR-Tutorial durch. Das bringt dir mehr.

von Stefan B. (Gast)


Lesenswert?

In diesem Teil

>  inc  r17
>  cpi  r17,255
>  brne  lcdZahl ; dort RET für rcall onADC
>  rjmp  onADC

benutzt du r17 als Zähler. Es ist keine Anweisung da, die r17 auf einen 
Startwert setzt, wenn onADC aus der mainloop aufgerufen wird.

An der Stelle

>  brne  lcdZahl ; dort RET für rcall onADC

geht es zu dem LCD Programmteil. In dem LCD Programmteil wird r17 
mehrfach verwendet und geändert. Wenn du aus dem LCD Programmteil per 
RET zurück in die mainloop kommst, kannst du dann mit Sicherheit den 
Wert von r17 beim nächsten Aufruf von onADC angeben?

von RevolT3c (Gast)


Lesenswert?

@kbuchegg: Das Tutorial habe ich durch. Zumindest paar mal gelesen. 
Jetzt hab ich ein Projekt und versuche dieses mit Hilfe des Tutorials zu 
lösen. Bin ja auch schon weiter Ausgabe an LCD funktioniert ja schon.



@Stefan:


Stimmt du hast recht :) Hab ich grad nicht gesehen weil ich es schon 
wieder rausgeschmissen habe. Wollte mit den Schleifendurchlauf eine 
Verzögerung hervorrufen.

Im Tutorial wird ja eine Variante gezeigt in der 254 Messungen 
aufaddiert werden und nacher wird ein Register verworfen. Das 
funktioniert ja aber nur bei 16bit.

Wie bekomm ich jetzt z.B. bei 50 Messungen in 8bit Form einen Mittelwert 
raus? Divisionsbefehlt kennt Assembler ja nicht?

von Karl H. (kbuchegg)


Lesenswert?

RevolT3c schrieb:
> @kbuchegg: Das Tutorial habe ich durch. Zumindest paar mal gelesen.

paar mal gelesen ist zuwenig.

> Wie bekomm ich jetzt z.B. bei 50 Messungen in 8bit Form einen Mittelwert
> raus? Divisionsbefehlt kennt Assembler ja nicht?

Dann wirst du dir eine Division machen müssen :-)

Jetzt weißt du auch, warum man auf Assemblerebene gerne bei so etwas auf 
2-er Potenzen geht: Divisionen werden dann trivial.
Wenn du anstelle von 50 Messungen deren 64 (oder 32) machst, ist die 
Division wieder ganz einfach.

von spess53 (Gast)


Lesenswert?

Hi

>Wie bekomm ich jetzt z.B. bei 50 Messungen in 8bit Form einen Mittelwert
>raus? Divisionsbefehlt kennt Assembler ja nicht?

Indirekt schon. Ein 'SHR' macht eine Division durch zwei. Wenn du für 
die Anzahl der Messwerte eine Zweierpotenz (2,4,8,16,32,64...) nimmst, 
reduziert sich die Division auf einfaches Rechtsschieben.

MfG Spess

von Ben _. (burning_silicon)


Lesenswert?

hauptsache du verrätst nicht wie!
(sechs rechtsshifts bzw. RORs)

64 finde ich aber unnötig viel, ich würde 32 vorziehen. spart die hälfte 
der zeit und sollte genau genug sein. ich verwende in meinem aktuellen 
projekt nur 16 wandlungen und 4 shifts, geht astrein.

von spess53 (Gast)


Lesenswert?

Hi

>64 finde ich aber unnötig viel, ich würde 32 vorziehen. spart die hälfte
>der zeit und sollte genau genug sein. ich verwende in meinem aktuellen
>projekt nur 16 wandlungen und 4 shifts, geht astrein.

Es kann aber noch andere Gründe haben, eine grössere Anzahl von 
Messwerten zu benutzen. Siehe AppNote AVR121.

MfG Spess

von Ben _. (burning_silicon)


Lesenswert?

ich hab nicht gesagt, daß es immer unnötig ist.

von Uwe (Gast)


Lesenswert?

Hi!
Ahja, es hat sich was geändert.
Es ist jetzt Singleconv.und du wartest bis fertig. Gut so.
Das "sei"/"cli" irritiert mich immernoch, macht zwar momentan keinen
Schaden, ist aber nicht sauber.
1
  in  r26,ADCL
2
  in  r27,ADCH
3
  asr  r27
4
  ror  r26
5
  asr  r27
6
  ror  r26
7
  mov  r25,r26

Du rollst den H-Teil nach L. Warum? Du könntest in ADMUX das Bit ADLAR
setzen und nur ADCH lesen, es wäre das gleiche.
1
         ldi  r16,10  ; wie bei den Hundertern
2
  cp  r1,r16
3
  brlo  einer
4
  sub  r1,r16
5
  inc  r2
6
  rjmp  zehner
Du wandelst nur 0-99. Sicher das der Wert nicht auch mal 100 sein kann?
Ich sehe jedenfalls keine Hunderter.
Achja:
1
wait5ms:
2
  ldi  r16,255
3
  ldi  r17,26
4
  push  r17
5
w5ms:
6
  dec  r16
7
  brne  w5ms
8
  dec  r17
9
  brne  w5ms
10
  pop  r17
11
  ret
push R17 ist sinnlos weil du es ja schon verändert hast, es sei denn du
willst das nach "call wait5ms" R17=26 ist. Ansonsten gehört es gleich an 
den Anfang.
Übrigens, was lässt sich denn besser lesen:
         ldi  r16,0b01000000
  out  ADMUX,r16          ; ACD Chanel
oder
         ldi  r16,(1<<REFS0)
  out  ADMUX,r16          ; ACD Chanel

Viel Erfolg, Uwe

von RevolT3c (Gast)


Angehängte Dateien:

Lesenswert?

Und nochmal überarbeitet :) Jetzt sind Hundertert dabei, ich lese nur 
das Highbyte aus und ich mache 128 Messungen mit 7 ror´s ;)

@Uwe: Ich dachte das sei und cli ist dazu da um Interrupts zu 
unterbinden während dem Wandlungsvorgang? Gut in meinem Fall hab ich 
keine Interrupts also könnte man es auch rausnehmen.

Zu der Sache mit dem besser lesen -----> (1<<REFS0). Ich wollte es so 
machen aber mein Programm kennt diese Art von schreibweise nicht.



Gruss Ralph

von spess53 (Gast)


Lesenswert?

Hi

>Und nochmal überarbeitet :) Jetzt sind Hundertert dabei, ich lese nur
>das Highbyte aus und ich mache 128 Messungen mit 7 ror´s ;)

Aua



>  in  r26,ADCH

>  add  r19,r26
Rate mal was passiert, wenn du z.B. 2x 128 addierst. Ein Register kann 
nur Werte von 0..255 enthalten. Dein Puffer muss also 2 Byte groß sein.

clr r0   ; Null
...
in r26,ADCH
add r19,r26
adc r20,r0   ; Übertrag verarbeiten


>  inc  r16
>  cpi  r16, 128
>  brne  ADC
Macht man besser so:

ldi r16,128
...
dec r16
brne ADC


>  ror  r19
>  ror  r19
>  ror  r19
>  ror   r19
>  ror  r19
>  ror  r19
>  ror  r19
Vollkommener Unsinn. 'ror' rotiert dein Register über das Carry-Flag 
wieder in dein Register. Da du sowieso 2 Register brauchst, befindet 
sich dein Ergebnis schon zu 7/8 im oberen Ergebnisregister. Da reicht 
ein:

lsl r19
rol r20

um das fehlende Bit nach r20 zu bringen.

MfG Spess

von Ben _. (burning_silicon)


Lesenswert?

jetzt kommt sein plan B - 256 messungen und das untere byte einfach 
wegwerfen ;)

von RevolT3c (Gast)


Lesenswert?

Wie ist der Teil zu verstehen:

clr r0   ; Null
...
in r26,ADCH
add r19,r26
adc r20,r0   ; Übertrag verarbeiten


?

Was macht der Befehl adc genau und was macht er mit dem r0, ist doch 
leer?

von Ben _. (burning_silicon)


Lesenswert?

ADC = ADd with Carry. er addiert dir das carry-flag mit rein. daher auch 
das leere R0. wenn R0 =1 wäre und carry gesetzt, würde der befehl 2 
addieren. willst du aber nicht, du willst nur das carry.

von spess53 (Gast)


Lesenswert?

Hi

>Was macht der Befehl adc genau und was macht er mit dem r0, ist doch
>leer

Wenn das Ergebnis der Addition 'add r19,r26' >255, also grösser 8 Bit 
ist, enthält r19 die unteren 8 Bit. Das 9.Bit befindet sich im 
Carry-Flag. Um dieses Bit zu r20 zu addieren wird mit 'adc r20,r0' 
0+Carry addiert. Die Null muss sich in einem Register befinden, da AVRs 
einen adc-Befehl mit einer Konstanten nicht besitzen.

MfG Spess

von spess53 (Gast)


Lesenswert?

Hi

Nachtrag:

Lade dir doch mal den ausführlichen Befehlssatz von Atmel herunter:

www.atmel.com/atmel/acrobat/doc0856.pdf

MfG Spess

von RevolT3c (Gast)


Lesenswert?

Ok verstehe:

onADC:  clr  r0
  clr  r16


ADC:      sbi  ADCSRA, 6

wait_adc:  sbic  ADCSRA, 6
  rjmp  wait_adc


  cli


  in  r26,ADCH


  add     r19, r26      ; addieren
      adc     r20, r0      ; addieren über Carry


      dec     r16
      brne    ADC



  sei


Jetzt habe ich einen Schleifendurchlauf von 256x.

Die 256 fache Addition des ADCH Registers liegt jetzt als 16Bit vor in 
den Registern r19 und r20. Jetzt kann ich das niederwertige Register r19 
verwerfen und nur mit r20 weiterarbeiten?

von RevolT3c (Gast)


Lesenswert?

Edith: Danke

von Ben _. (burning_silicon)


Lesenswert?

ja

von spess53 (Gast)


Lesenswert?

Hi

Noch etwas: Dein ADC läuft eigentlich zu langsam. Lt. Datenblatt wird 
ein ADC-Takt von 50-200 kHz empfohlen. Du hast einen Prescaler von 128, 
was bei deinem Controller-Takt ca. 29kHz für den ADC ergibt.

MfG Spess

von RevolT3c (Gast)


Lesenswert?

Danke hab ich jetzt erhöht.

Noch ne Frage: Ich geb jetzt z.B einen Dezimalwert 20 auf dem LCD aus. 
Wenn ich jetzt schlagartig 5V an den ADC0 Eingang lege dann sollte der 
Wert sich auf dem Display auf 255 ändern. Das tut er auch jedoch in die 
verkehrte Richtung. Er zählt von den 20 langsam runter bis er 
schließlich 00 anzeigt und danach 255.

Wie kann ich das ändern?

von Karl H. (kbuchegg)


Lesenswert?

Jetziges Program?

von RevolT3c (Gast)


Angehängte Dateien:

Lesenswert?

Dieses hier

von Karl H. (kbuchegg)


Lesenswert?

1
;------------------------------------
2
; ISR: ADC initialisieren
3
; PA: r27:r26 10Bit, r25 8Bit
4
onADC:  clr  r0
5
  clr  r16    
6
      
7
   
8
ADC:      sbi  ADCSRA, 6
9
      
10
wait_adc:  sbic  ADCSRA, 6
11
  rjmp  wait_adc     
12
13
14
  cli
15
  
16
  
17
  in  r26,ADCH
18
  
19
  
20
  add     r19, r26      ; addieren
21
      adc     r20, r0      ; addieren über Carry
22
      
23
      
24
      dec     r16         
25
      brne    ADC     
26
27
  
28
  
29
30
31
  
32
  
33
  sei  
34
        
35
36
lcdZahl:

Wer sorgt eigentlich dafür, dass die Summierung der einzelnen ADC Werte 
im Registerpaar r19/r20 auch tatsächlich bei 0 anfängt?

(Schmeiss doch die exzessiven Leerzeilen raus! Wenn du ohne Leerzeilen 
nicht weißt, was im nächsten Abschnitt passiert, dann weißt du es auch 
mit Leerzeilen nicht. Der einzig vernünftige Weg, sich da Übersicht 
reinzumachen, besteht darin einen Kommentar anzubringen, was im nächsten 
Abschnitt passiert. Aber doch nicht indem man sich den Code möglichst so 
in die Länge zieht, dass er ja nicht auf einen Blick zu überschauen 
ist.)

Wozu aktivierst du eigentlich die Interrupts beim ADC, wenn du sowieso 
keinen Interrupt Handler dafür hast?

Das ist doch sch....e, wenn du diese Schreibweise benutzen musst
  ldi  r16,0b01100000
  out  ADMUX,r16  ; ACD Chanel

bist du sicher, dass dein Assembler das hier nicht kann
  ldi  r16, (1<<REFS0) | (1<<ADLAR)

Da jedesmal im Datenblatt nachzusehen, welches Bit jetzt welchem Flag 
entspricht, ist doch extrem mühsam. Wenn dein Assembler wirklich nicht 
damit klarkommt, würden die meisten hier ganz schwer darüber nachdenken 
den Assembler zu wechseln. Das ist nämlich ein echtes Manko.

von RevolT3c (Gast)


Lesenswert?

Du hattest recht, das Leeren der Register war nötig:
1
onADC:  clr  r0
2
  clr  r16    
3
  clr  r19
4
  clr  r20  
5
   
6
ADC:      sbi  ADCSRA, 6  ; aktiviere ADC  
7
      
8
wait_adc:  sbic  ADCSRA, 6  ; warte bis Wandlung abgeschlossen ist
9
  rjmp  wait_adc     
10
11
  in  r26,ADCH  ; Einlesen des Wandlungsergebnis
12
  
13
  
14
  add     r19, r26     ; 256 fache Aufaddierung
15
      adc     r20, r0      ; addieren über Carry
16
      
17
      
18
      dec     r16         
19
      brne    ADC

Ob die Schreibweise (1<<ADLAR) nicht doch irgendiwe möglich ist werd ich 
mal Nachfragen weil ansonsten bin ich ganz zufrieden mit dem Programm.

von Karl H. (kbuchegg)


Lesenswert?

1
adcInit:  push  r16
2
  ldi  r16,0b01100000
3
  out  ADMUX,r16  ; ACD Chanel
4
  ldi  r25,0b11011100
5
  out  ADCSRA,r25  ; enable ADC, INT, start
6
  sei
7
  pop  r16
8
  ret
Entweder du machst es gleich richtig und pusht/popst alle Register, 
die in einer Funktion verändert werden, oder du sparst dir den ganzen 
Aufwand und ordnest jedem Register eine ganz bestimmte Funktionalität zu 
und hältst dich auch daran. Register gibt es ja genug im AVR. Aber so 
ein Mischmasch ist meistens nicht sehr gut: r16 wird gesichert und 
restauriert, r25 aber nicht.
Weiters solltest du mehr Selbstdisziplin an den Tag legen. Die Funktion 
heißt 'adcInit'. Ihre Aufgabe ist es den ADC zu initialisieren. Was 
macht da der sei da drinnen? Der hat nichts mit der Initialisierung des 
ADC zu tun! Solche eingestreute Anweisungen, die eigentlich da nicht 
hingehören können sich zum Alptraum ausarten, wenn du nämlich zb die 
Funktion in ein anderes Projekt kopierst und das übersiehst. Und 
plötzlich hast du das Problem, dass in der Hochfahrphase des Programms 
die adcInit aufgerufen wird und dir aus irgend einem Grund (den du dann 
natürlich nicht mehr weißt) die Interrupt zu früh zu feuern anfangen 
obwohl noch gar nicht alles initialisiert ist, weil nach dem Aufruf von 
adcInit nach andere Initialisierungen gemacht werden müssten. An solchen 
Fehlern kann man stundenlang suchen, ohne sie zu sehen.

Unter diesem Aspekt solltest du auch darüber nachdenken, ob es wirklich 
Aufgabe der Funktion onADC ist, den ermittelten Zahlenwert auf dem LCD 
auszugeben. Frag dich einfach: Angenommen ich will nicht auf dem LCD 
ausgeben, sondern auf der UART. Was an onADC müsste geändert werden?
In der Theorie gar nichts. Denn das Auslesen des ADC hat ja eigentlich 
nichts damit zu tun, wo man den Zahlenwert ausgibt. onADC ermittelt den 
Zahlenwert. Was weiter damit geschieht ist nicht mehr das Bier der 
onADC.
(Das ist allerdings zum jetzigen Zeitpunkt noch nicht tragisch. Aber in 
Zukunft solltest du dir eine Maxime setzen: Eine Funktion macht das, und 
nur das, was auch drauf steht)

von RevolT3c (Gast)


Lesenswert?

Ok du hast recht es könnte strukturierter sein. Sprich mehr mit "rcall" 
arbeiten und einzelne Blöcke abarbeiten.
Ich sag das auch nicht nur so ich werd das auch versuchen so umzusetzen.
Muss auch noch einen Dank aussprechen, bis jetzt habt ihr mir echt 
weitergeholfen ;)

von spess53 (Gast)


Lesenswert?

Hi

Das Verhalten kann ich auf die Schnelle auch nicht erklären. Aber noch 
ein paar Bemerkungen zu deinem Programm.

>call  lcdControl

Das Unterprogramm schaltet das Display ein. Ein ständiger Aufruf ist 
unnötig.

>rcall   lcdGoto

Das Unterprogramm erwartet in r16 die DDRAM-Adresse. Ist bei dir durch 
Aufruf von 'wait5ms' in 'lcdControl' zufällig immer 0.

>lcdZahl:
>  asr  r20

Damit macht du ein vorzeichenbehaftetes Rechtsschieben. An dieser Stelle 
unsinnig.

In den ganzen Unterprogrammen werden benutzte Register nicht mit 'push' 
gesichert und mit 'pop' restauriert. Das kann bei minimal grösseren 
Programmen sehr lustige Effekte hervorrufen.

MfG Spess

von Karl H. (kbuchegg)


Lesenswert?

spess53 schrieb:
> Hi
>
> Das Verhalten kann ich auf die Schnelle auch nicht erklären.

Das hat mich auf die Schnelle auch ein wenig verblüfft.
Des Rätsels Lösung besteht darin, dass er die Summierung nicht bei 0 
anfangen lies, sondern beim vorhergehend gemessenen Wert. Wenn wir uns 
dann noch vergegenwärtigen, dass 255 (der eigentlich gemessene Wert) ja 
vorzeichenbehaftet auch als -1 aufgefasst werden kann, hat die 
"Summierung" in Wirklichkeit immer nur 1 abgezogen.

  20 + 255  -> 19

von spess53 (Gast)


Lesenswert?

Hi

>>spess53 schrieb:
>> Hi
>> Das Verhalten kann ich auf die Schnelle auch nicht erklären.

Jetzt kann ich es auch.

MfG Spess

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.