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
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
>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.
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?
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.
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?
@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?
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.
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
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.
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
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
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
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
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?
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.
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
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?
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
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?
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.
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)
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 ;)
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
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