Hi.Ich hab hier schon paarmal gelesen, das manche Leute den Hexwert in Dezimal dann auf das LCD ausgeben. Wie ist das möglich? Also wenn die Zahl zwischen 0-9 ist, dann wüßte ich schon, wie das geht...(glaube ich halt). Aber wie kann man ab der Zahl 9, den Hexwert auf das LCD ausgeben? Und beim AD Wandler, der läuft ja mit 10 Bit, wie kann ich dann das realisieren? Gruß Avus
Du solltest dir einmal überlegen, was du wie darstellen willst. In diesem Fall eine Zahl zwischen 0 und 1024. Für das LCD sind das also bis zu vier Zeichen. BCD ist dein Freund, wenn es darum geht die Zahl in einzelne Ziffern zu zerlegen.
Hm, das ist ja dann die voll große Rechenaufgabe, wenn man die 10 Bit des AD Wandlers auf LCD ausgeben will. Multiplizieren und Dividieren ist ja schon ein ganz großer Aufwand. Mal schaun, ob ich da was finde... Gruß Avus
"Multiplizieren und Dividieren ist ja schon ein ganz großer Aufwand." Aber überhaupt nicht ! Ein AVR kann schätzungsweise 10.000 16-Bit Divisionen je Sekunde machen, so schnell kannst Du die Werte gar nicht ablesen. Der einzige Aufwand ist eventuell für Dich, zu verstehen, wie diese Routinen funktionieren. Peter
Wo ist da das Problem? Ich messe mit dem ADC im Mega8, multipiziere das 10bit Ergebbnis mit z.B. 6 um den Messbereich anzupassen. Das Ergebnis sind mV. Zur Anzeige in Volt durch 100 dividieren für die Vorkommazahl, für die Nachkommazahl %100 und das Ergebnis jeweils über itoa umrechnen und mit lcd_puts() ausgeben. Sieht so aus: Ich hoffe es kommt von der Formatierung her einigermassen an. lcd_gotoxy(15,1); Wandelwert=max/100; /* Maximalwert Vorkommastellen berechnen*/ ltoa(Wandelwert,Wert1,10); /* Maximalwert in ASCII-Wert umwandeln*/ lcd_puts_p(ausg[1]); /* ausgabe "max"*/ lcd_puts(Wert1); /* Wert von max auf Display ausgeben */ lcd_puts_p(fuellzeichen[1]); /*Komma ausgeben*/ Wandelwert=max%100; /* Maximalwert Nachkommastellen berechnen*/ if (strlen(Wert1)==2){ /*Wenn anzeige zweistellig, dann Kommastelle nur einstellig*/ Wandelwert=Wandelwert/10; /* Wert auf 1 Stelle begrenzen */ if(Wandelwert == 0){ /* Wenn die ausgabe =0 */ lcd_puts_p(fuellzeichen[0]); /* eine 0 ausgeben */ } else{ ltoa(Wandelwert,Wert1,10); /*Wert auf ASCII wandeln */ lcd_puts(Wert1); } } else{ ltoa(Wandelwert,Wert1,10); /* Maximalwert in ASCII-Wert umwandeln*/ if (strlen(Wert1)==1){ /* wenn Nachkommastelle nur im hundertstelbereich */ lcd_puts_p(fuellzeichen[0]); /* dann eine Null vorher einfügen */ } lcd_puts(Wert1); /* auf Display ausgeben */ }
Tut mir leid Hubert, C kann ich nicht. Zu Peter, du hast recht, es ist schwierig zu verstehen... Wie sieht das dann aus, wenn ich dann z.B. die Dezimalzahl 1000 habe, also 0x03E8, wie kann ich dann das ans LCD schicken? Ist das nochmal ein großer Aufwand? Gruß Auvs
Wie würdest du das schriftlich rechnen?? (Dabei mal das "Kleine Einmaleins" nicht anwenden.) - 1000 subtrahieren, sooft es geht (positiv bleibt) = Tausender - vom Rest 100 subtrahieren, sooft es geht = Hunderter - vom Rest 10 subtrahieren, sooft es geht = Zehner - Rest = Einer Tausender, Hunderter, Zehner und Einer liegen dann binär vor. Ein Blick in die Zeichen-Tabelle des LCD wird dir zeigen, ob diese Zahlenwerte noch verändert werden müssen. Würdest du sie an ein anderes System senden, welches ASCII versteht (PC), dann müsstest du jeweils der Offset der ASCII-Tabelle (48) addieren um die Ziffern als (ASCII-) Textzeichen zu erhalten. ...
läßt sich bei reinen 8-bit werten auch einfach per division lösen, hat den vorteil, daß der divisionsrest in B stehenbleibt und gleich weiterverarbeitet werden kann. Wenn der umzuwandelnde Wert in A steht: mov b,#01100100b div ab mov r2,a ;Hunderter in r2 schreiben mov a,b ;Divisionsrest in A holen mov b,#00001010b div ab die hunderter sind jetzt in R2, die zehner in A Gruß Oliver und die Einer in B wiederzufinden
@avusgalaxy "Tut mir leid Hubert, C kann ich nicht." wie, du sprichst AVR-Assembler hoch und runter, und kannst kein C? Ok, meine erste Programmiersprache die ich mal in einem früheren Leben gelernt hatte war BASIC (schäm), aber dann ging es eigentlich erst mal mit "Hochsprachen" wie Fortran, Pascal, etc. weiter. Ich hab mich dann über diverse Assembler-Dialekte (8051, Z80, 80xx, 68000 etc) so langsam "runter gerobbt" an die Prozessor Register, bis ich irgendwann mal auf Slice-Prozessor-Ebene Microcode gecoded habe Na ja, vielleicht ist es ja bei dir anders rum: Vom Kleinen zum Großen...
Für BASIC braucht sich niemand schämen. Nur auf dem AVR ist es (BASCOM) zu weit von der Hardware entfernt. Assembler hat noch Niemandem geschadet. Für kleinere Programme (und ich schreibe bisher nur kleinere Programme) ist es genial. Ich denke schon, dass man ASM verstehen kann, ohne C zu verstehen, glaube aber nicht, dass man ohne ASM-Kenntnisse C (auf dem AVR!) wirklich kann. ...
Ja, da geb ich dir voll und ganz Recht: man kann ASM verstehen ohne C. Aber man (ich) kann (komplexe) Sachverhalte besser verstehen, wenn ich sie in einer flüssigeren Abstraktionsform schreibe (Hochsprache, oder Metasprache) als in einem registerorientierter Assembler-Befehlssatz versuche etwas auszudrücken. Na ja, meine C-Programme sehen dann auch immer ziemlich "prosaisch" aus, weil für mich 1 Zeile Dokumentation mehr Wert hat als 1 Zeile Programmiersprache. Aber es gibt durchaus auch Hardcore-Programmierer, welche Variablenbezeichner bäh finden, wenn diese mehr als 2 Buchstaben lang sind ....
Hallo nixo. Mir ist klar, das es bei dem 8051 mit div geht, nur bei den AVR's gibts kein div... @Martin S. Hast du nicht auch mal angefangen? Oder bist du als Genie auf die Welt gekommen? @Hannes. Danke, verstehe es jetzt ungefähr. Wenn ich jetzt z.B. 2450 dez. habe, dann kann ich "1000" zweimal abziehen usw... Das muß ich dann halt im 16 Bit Register machen, oder? R26 und R27 würde sich dafür eignen. Brauch ich da dann R28 und R29 auch, um 1000 zu subtrahieren? Gruß Avus Gruß Avus
@avusgalaxy Hat doch nix mit Genie zu tun. Eigentlich wollte ich dich ja loben, daß du "algorithmisch in Assembler denken kannst", scheint aber irgendwie nicht angekommen zu sein. Für mich war Assembler eher der "kniffligere" Teil, da dort halt manchmal "Tricks" angewendet werden können/müssen, welche halt nicht so augenfällig sind wie in einer Hochsprache. "ich persönlich" kann mir eher eine unbekannte Programmlogik in einer Hochsprache "erarbeiten" (womöglich ist er auch noch prima kommentiert), als das ganze in Assembler zu "erfrickeln". Beispiel: Irgendwelches Vektor-Grafik-Zeugs (wie zeichnet man effizient einen Kreis in eine Rastermatrix ?) mündet schlussendlich in Bildpunkten, welche in einen linear adressierbaren Speicher organisiert werden. Da "mach ich mir lieber meinen Kopf" in einer Hochsprache, und wenn das alles fertig überlegt ist, dann schnappe ich mir die am besten nutzbaren Register um das in Bitebene abzubilden. Möglicherweise muß ich dann sogar noch die trigonometrischen Funktionen ebenfalls in Assembler nachbilden, weil mein Prozessor keine passende Arithmetik kann. Hochachtung vor dem, der den "Hochsprachenschritt" direkt überspringen kann und das dann direkt in Assembler auszudrücken vermag.
@all sorry, habe nicht aufgepasst - war bei meinen mcs51 lieblingen.
Ich habe ja auch gehöhrt, das es in C alles leichter(kompakter) wäre, aber Assembler ist halt die Sprache, die den Maschinen am ähnlichsten ist. Und wenn ich in C mal nicht weiter weiß, dann brauch ich wieder Assembler. Gruß Avus
@Martin: Ich wollte dich nicht kritisieren. Auf dem PC (mit Betriebssystem!) schreibe ich auch nix in Assembler (hatte mal vor Jahren mit DEBUG einige kleine COM's gebaut, da kam es aber auf die geringe Dateigröße an). Allerdings schreibe ich da nicht in C sondern in BASIC. QB und VB sind meine Freunde, aber dafür schäme ich mich nicht. Beim AVR (andere MC's nutze ich nicht) hat man ja kein Betriebssystem, sondern hantiert man ja direkt an der Hardware. Diese ist im Datenblatt erklärt. Komplizierte Berechnungen muss ich nicht durchführen, ich habe da mehr Bits zu schubsen als Nummern. Das meiste sind nunmal I/O-Zugriffe. Daher genügt mir ASM, denn da sehe ich was sich tut. BASCOM mag ich nicht, weil es davon abhält, ins Datenblatt zu schaun. Da wird für jedes Hardwarefeature ein Config angeboten, was nix mehr mit dem Datenblatt zu tun hat. Außerdem ist man so weit von der Hardware entfernt, dass man (ich zumindest) den Überblick verliert. Dazu kommt noch, dass man mit der 2KB-Demo angefüttert wird und dann die Vollversion kaufen muss. Das muss nicht unbedingt sein. Durch die mitgelieferten Bibliotheken hat man zwar den "schnellen Erfolg", sitzt aber danach auch schnell in einer Sackgasse, wenn es mal etwas anders kommt (andere Hardware). C ist gut. Aber wie jede Hochsprache ist es etwas weiter von der Hardware entfernt. Dann kommt ja noch dazu, dass ANSI-C nicht ausreicht, mit dem AVR zu kämpfen. Da kommen ja noch die AVR-spezifischen LIBs dazu. Das ist mir (als Hobbybastler) einfach zuviel. Da bleibe ich (vorerst) lieber bei ASM. Das ist kostenfrei, zukunftssicher (in Bezug auf neue AVR-Typen) und bei meinen kleinen Programmen auch übersichtlicher. Ich hantiere übrigens mehr mit dem Tiny12/15 als mit dem Mega8/16/32. Reicht meistens für meine Projekte. Dies soll aber keinesfalls eine "Kriegserklärung" gegen C (oder Hochsprachen allgemein) sein, bitte nicht falsch verstehen. @Avus: Subtrahieren bis du im Minus bist, dann wieder einmal addieren. Dabei einen Zähler mitlaufen lassen, der die Anzahl der Subtraktionen (und Addition) mitzählt. Dieser ergibt dann deine Ziffer. Schau mal bei ATMEL in die Appnotes (oder in die Appnotes in deinem Rechner, unter avrtools\avrassembler\appnotes), da war glaube was zur Division dabei. ...
Anbei mal eine Umwandlung 32 Bit in ASCII nach der optimierten Subtraktionsmethode. Einfach mal mit beliebigen Werten durch den Simulator laufen lassen und zuschauen, was passiert. In C geht das natürlich viel einfacher (itoa, ltoa). Peter
Hallo, Peter, kann ich das einfach für 16 Bit umwandeln? Oder gibt es da auch schon was fertiges? Oder 8 Bit...wäre noch einfacher zu verstehen. Gruß Avus
Ok, habe das jetzt mal gemacht, für 8 Bit und es scheint zu funktionieren. Nur wie gehe ich da bei 16 Bit vor? Das von Peter mit subi r28, byte1(100000);-100,000 sbci r29, byte2(100000) sbci r30, byte3(100000) verstehe ich nicht... byte1???? (100000)??? Keine Ahnung, Gruß Avus
Wieso benutzt Du zum Subtrahieren temp1, 2 und 3? Benutze statt SUB SUBI (Subtract immediate). Das heißt nix anderes, das Du zum subrahieren kein 2. Register benötigts und den Subtraktionswert direkt angeben kannst: SUBI temp1,100 Das würde dann so aussehen: ldi zahl1, -1 + '0' Zahl1: inc zahl1 subi temp1, 100 brcc Zahl1 subi temp1, -100 ldi zahl2, -1 + '0' Zahl2: inc zahl2 subi temp1, 10 brcc Zahl2 subi temp1, -10 ldi Zahl3, '0' add zahl3, temp1 Da es beim AVR kein ADDI (add immediate) gibt kann man mit SUBI auch addieren wenn vor der Zahl ein - (minus) steht: SUBI temp1,-100 addiert 100 auf temp3 (A + B = A - -B). @Peter: Beim Abschnit, wo 10.000.000 subtrahiert werden und Du das nur mit Byte1, 2 und 3 machst, geht das denn? ldi r23, -1 + '0' _bcd3: inc r23 subi r28, byte1(10000000) ;-10,000,000 sbci r29, byte2(10000000) sbci r30, byte3(10000000) sbci r31, 0 brcc _bcd3 Beim subtrahieren von 1 Milliarde läßt Du das Byte1 weg, klar, wird da auch nicht benötigt, aber bei 10 Millionen und einer möglichen 27 Bit-Zahl nur 24 Bit subtrahieren? Mit 24 Bit kann man doch nur bis 16777216 darstellen und evtl. ist im Abschnitt _bcd3 noch 99.999.999 übrig. Gruß Andi
Hallo Andi, Danke, so gehts gleich viel leichter... Doch bei 16 Bit, wie gehts dann weiter? .include "m8def.inc" .def temp1 = r16 .def zahl1 = r17 .def zahl2 = r18 .def zahl3 = r19 .def zahl4 = r20 .def zahl5 = r21 ldi temp1, LOW(RAMEND) ; LOW-Byte der obersten RAM-Adresse out SPL, temp1 ldi temp1, HIGH(RAMEND) ; HIGH-Byte der obersten RAM-Adresse out SPH, temp1 ldi temp1, 0xFF ;Port D = Ausgang out DDRD, temp1 ;Da schreib ich den Hexwert rein: ldi zl, 0x03 ; Lowbyte für Z ldi zh, 0xD9 ; Highbyte für Z ldi zahl1, -1 + '0' Zahl1: inc zahl1 ??????????????????????????????????????????? brcc Zahl1 Wie Kann ich vom Registerpaar R31 und R32 1000(dez) subtrahieren? Mit "sbiw zl:zh, 63" kann man ja nur 63 subtrahieren Weiß da jemand weiter? Gruß Avus
Da der AVR ein 8Bit-µC ist muß man eine 16Bit-Subtraktion aus 2 8Bit-Subtraktionen machen was dank des Carry-Bits (Überlauf) einfach möglich ist. subi r28,low(-10000) sbci r29,high(-10000) "SBCI" (sub immediate with carry) heißt, das zur subtraktion das Carry-Bit (0 oder 1) mit herangezogen wird. Falls durch "SUBI" das low-Byte der 16Bit-Zahl in r28 ein Overflow entsteht, z. B. Binär von 0b11100101 auf 0b01000101, wird nach dem SUBI das Carry-Bit gesetzt und SBCI subtrahiert dann auf das high-Byte noch mal 1 dazu. Eigentlich hat Peter bereits in seinem Beispiel 8 bis 32Bit-Subtraktionen/Additionen dargestellt. Mußt Du nur noch übernehmen und für Deinen Zweck anpassen. Gruß Andi
Ok, es klappt jetzt. Wenn ich in R30 0xE8 und R31 0x03 schreibe, dann paßt die 1000er Stelle, die 100er Stelle, die 10er Stelle nur die 1er Stelle geht nicht. Kann doch nicht gehen, oder? Bei den letzten Zeilen von Peter..... subi r16, -10 - '0' mov r16, r30 Zuerst addiert Peter 10 zu der Zahl "0", ist dann 0x3A. Und dann überschreibt er R16 mit r30. Wie kann das gehn? Kapier ich leider nicht.. Gru? Avus
Ich glaube, das es so gehöhrt: ldi r16, 10 + '0' add r16, r30 Stimmt das? Gruß Avus
Schaut doch einfach mal in die appnote http://www.atmel.com/dyn/resources/prod_documents/doc0938.pdf ;*********************************************************************** **** ;* ;* "bin2BCD16" - 16-bit Binary to BCD conversion ;* ;* This subroutine converts a 16-bit number (fbinH:fbinL) to a 5-digit ;* packed BCD number represented by 3 bytes (tBCD2:tBCD1:tBCD0). ;* MSD of the 5-digit number is placed in the lowermost nibble of tBCD2. ;* ;* Number of words :25 ;* Number of cycles :751/768 (Min/Max) ;* Low registers used :3 (tBCD0,tBCD1,tBCD2) ;* High registers used :4(fbinL,fbinH,cnt16a,tmp16a) ;* Pointers used :Z ;* ;*********************************************************************** **** ;***** Subroutine Register Variables .equ AtBCD0 =13 ;address of tBCD0 .equ AtBCD2 =15 ;address of tBCD1 .def tBCD0 =r13 ;BCD value digits 1 and 0 .def tBCD1 =r14 ;BCD value digits 3 and 2 .def tBCD2 =r15 ;BCD value digit 4 .def fbinL =r16 ;binary value Low byte .def fbinH =r17 ;binary value High byte .def cnt16a =r18 ;loop counter .def tmp16a =r19 ;temporary value ;***** Code bin2BCD16: ldi cnt16a,16 ;Init loop counter clr tBCD2 ;clear result (3 bytes) clr tBCD1 clr tBCD0 clr ZH ;clear ZH (not needed for AT90Sxx0x) bBCDx_1:lsl fbinL ;shift input value rol fbinH ;through all bytes rol tBCD0 ; rol tBCD1 rol tBCD2 dec cnt16a ;decrement loop counter brne bBCDx_2 ;if counter not zero ret ; return bBCDx_2:ldi r30,AtBCD2+1 ;Z points to result MSB + 1 bBCDx_3: ld tmp16a,-Z ;get (Z) with pre-decrement ;---------------------------------------------------------------- ;For AT90Sxx0x, substitute the above line with: ; ; dec ZL ; ld tmp16a,Z ; ;---------------------------------------------------------------- subi tmp16a,-$03 ;add 0x03 sbrc tmp16a,3 ;if bit 3 not clear st Z,tmp16a ; store back ld tmp16a,Z ;get (Z) subi tmp16a,-$30 ;add 0x30 sbrc tmp16a,7 ;if bit 7 not clear st Z,tmp16a ; store back cpi ZL,AtBCD0 ;done all three? brne bBCDx_3 ;loop again if not rjmp bBCDx_1
... oder einfach subi r30,-(10 + '0') Dann ist die Einer-Stelle zwar nicht in r16, sondern in r30, aber dafür wieder einen Takt eingespart :-) @Peter: Ist das eigentlich von Deiner Sicht richtig, das auf Byte3 und Byte4 (r30 und r31) 1000 addiert und 100 subtrahiert werden? Liegt das an der Optimierung? @Hauke Sattler: Das mit der AppNote ist mir bekannt aber die Routinen daraus dauern gegenüber der Sub-Methode ewig und benötigen auch noch SRAM. Für bestimmte Anwendungsfälle sind die geeignet aber nicht nötig für ASCII-Ausgabe von Binärzahlen. Da komme ich mit meiner Multifunktionsroutine zur Ausgabe von 8 bis 32 Bit-Zahlen schneller weg inkl. ASCII-Ausgabe in den LCD-Buffer. Gruß Andi
Ich meinte halt nur das man das Rad nicht von neuem Erfinden muß. Und außerdem hat die Appnote ne relativ gute Doku. Und letzteres ist bei "Anfänger" Projekten mehr wert als ein paar eingesparte Taktzyklen. P.S. Ich progge AVR und Z80 und DMG90 ausschließlich in Assembler. Das liegt aber daran, das es damals als ich auf dem Amstrad CPC464 geproggt habe, noch kein C gab (oder ich damals zumindest nicht davon wußte) Als ich dann mal vor ein paar Jahren nen DMG90 unter C proggen wollte, hat mich der Compiler faßt ibn den Wahnsinn getrieben. Nur Fehlermeldungen (mit deren Doku man nix anfangen konnte) Danach hab dann mit ASM probiert und es fubbte faßt auf anhieb. Ich denke dieses Erlebniss hat mit C für den Rest meines Lebens verleidet. Deshalb code ich selbst große Projekte ausschließlich in Assembler. (Das größte hat zu Zeit ca 4,6 kWord)
Ist schon klar, aber das mit dem Sub-Verfahren ist nicht neu erfunden sondern muß immer wieder eingetrichtert werden ;-) Desweiteren spart es wirklich unmengen an Takte gegenüber der Bin2BCD-Funktion. Die Bin2BCD16-Funktion benötigt laut Atmel selbst mindestens 751 Takte, die Sub-Methode für 16 Bit im Schnitt nur ca. 150. Wenn das nicht aussagekräftig genug ist sich die Sub-Methode anzutun dann weis ich auch nicht mehr. Gruß Andi
@avusgalaxy ups, das ist ein Dreckfuhler, richtig muß es heißen: subi r30, -10 - '0' mov r16, r30 subi r28, byte1(10000000) ;-10,000,000 sbci r29, byte2(10000000) sbci r30, byte3(10000000) sbci r31, 0 Das ist doch eine 32 Bit Subtraktion (4 Byte: r31...r28) Kannst natürlich auch schreiben: ... sbci r31, byte4(10000000) denn 10000000 = 0x00989680, d.h. Byte4 ist 0. Anbei das 16-Bit Beispiel, a0..a4 muß man mit Registern definieren. Peter
Danke für die Zahlreichen Antorten. Das mit dem Sub funktioniert wirklich gut und ist auch verständlich. Jetzt läuft auch mein AD-Wandler (zum erstenmal). AVCC auf 5V und den Eingang auch. (1023). Wenn ich den Eingang auf GND lege, dann erscheint 0 am Display. Soweit so gut. Hab jetzt im Forum ein bisschen gestöbert und hab folgendes gefunden. Anzeigewert = Messwert * (5 / 1024) Damit kann ich ja die Spannung auf das LCD ausgeben. Nun bahnt sich aber das nächste Problem auf: Messwert * (5 / 1024)... Da muß ja wieder multiplizieren und dividieren.. Beim 8051'er wäre das nicht schwer.. Also 5/1024 = 0.0048828125. Und das müßte ich dann mit dem Wert ADCL und ADCH multiplizieren. Wie soll das bitte gehn? Wie habt ihr das bloss geschaft? Gruß Avus
Mal 5 dürfte einfach sein. (Notfalls 5 mal addieren) Durch 1024 erreichst du durch Schieben und Wegwerfen des unteren Bytes. ...
Im Ordner Appnotes vom AVR-Studio findest Du in den Dateien avr200.asm und avr201.asm (nur für Megas) Divisions- und Multiplikationsroutinen welche Du verwenden kannst. Aber es geht auch anders. 5 / 1024 = 0,0049 mal 10000 = 49 Damit haben wir schon mal eine feste Konstante als Ganzzahl was das ganze Vereinfacht. Diese Ganzzahl multiplieziert man dann einfach mit dem Messwert: z. B. 374 * 49 = 18326 Das ist dann die gemessene Spannung welche am LCD mit einem "." nach der 1 dargestellt werden kann, also 1.8326. Die nötigen 16x16 Bit-Multiplikationen dafür findest Du in der avr200.asm. Natürlich kannst Du auch in einer Schleife 49 mal den gemessenen Wert auf ein 16 Bit-Register aufaddieren: in r16,ADCL in r17,ADCH clr r18 clr r19 ldi r20,49 loop: add r18,r16 adc r19,r17 dec r20 brne loop Im Registerpaar r17:r16 ist der Messwert welcher auf das Registerpaar r19:r18 49 mal addiert wird. Das wäre dann schon alles. Danach zur Dezimalwandlung, 1. Stelle ausgeben, "." sugeben, 2. Stelle... Ist zwar dann nicht auf die 1/100-Stelle genau aber OK. Gruß Andi
Hm, mach ich da was falsch? Bei mir zeigt das LCD "b.127" anstatt 5.000 an. Aref ist auf 5V in r28,ADCL in r29,ADCH clr zl clr zh ldi temp1,49 rechner: add zl,r28 adc zh,r29 dec temp1 brne rechner rcall lcd_null ldi r19, -1 + '0' _bcd7:inc r19 subi r30, low(1000);-1000 sbci r31, high(1000) brcc _bcd7 ldi r18, 10 + '0' _bcd8:dec r18 subi r30, low(-100);+100 sbci r31, high(-100) brcs _bcd8 ldi r17, -1 + '0' _bcd9:inc r17 subi r30, 10;-10 brcc _bcd9 ldi r16, 10 + '0' add r16, r30 mov temp1, r19 rcall lcd_data ldi temp1, '.' rcall lcd_data mov temp1, r18 rcall lcd_data mov temp1, r17 rcall lcd_data mov temp1, r16 rcall lcd_data
Das liegt daran, das Du Deine Dezimalwandlung nur für Zahlen bis 9999 ausgelegt hast. Aber der maximale Zahlenwert beträgt 1023 * 49 = 50127. Füge am Anfang folgendes hinzu: ldi r20, -1 + '0' _bcd6: inc r20 subi r30, low(10000) ;-10000 sbci r31, high(10000) brcc _bcd6 subi r30, low(-10000) ;+10000 sbci r31, high(-10000) Danach dann das Digit in r20 zuerst ausgeben. Gruß Andi
Habe mir überlegt, wie man die Ungenauigkeit ausbügeln könnte. Probier mal folgendes: in r28,ADCL in r29,ADCH clr zl clr zh ldi temp1,49 rechner: add zl,r28 adc zh,r29 dec temp1 brne rechner mov r16,zh ;Abgleich (ZH:ZL - ZH:ZL/512) lsr r16 ;r16 (ZH:ZL) / 512 mov r17,r16 lsr r17 ;In r17 1/4 von r16 lsr r17 add r16,r17 ;zu r16 addieren ergibt 5/4 von 1/512 sub zl,r16 ;Abgleichwert von ZH:ZL subtrahieren sbci zh,0 rcall lcd_null In r16 kommt 1/512 von ZH:ZL was von ZH:ZL abgezogen wird. Je höher der Messwert desto höher der Abgleichwert. Zumindest ist damit der höchste Wert 5.0006 Wenn Du dann noch die 5 stelle (6) nicht ausgiebst werden genau 5.000 angezeigt. Gruß Andi
Wow, wieso weißt (kannst) du soviel? Hattest du Kurse? Wieviel Jahre machst du das schon?
Mach schon ein bißchen länger mit Proggen rum (seit den ZX81-Zeiten). Funtzt das mit dem Abgleich? Gruß Andi
ja, 5,0006 V... Haargenau. Hab die letzte Stelle weggenommen. So könnte man ja auch einen Drehzahlmesser fürs Auto oder Motorad bauen. Muß ja nicht immer der Timer sein, oder? Mit dem LM2907 könnte man ja die Frequenz in eine Spannung umwandeln und dann in den AD Wandler schicken. Müßte doch gehen, oder? Mit den Timern habe ich leider noch keinen Durchblick, deshalb kommt mir diese Idee. Danke nochmal Gruß Avus
Theoretisch ginge das. Man muß dann eine ADC/RPM-Wandlung machen also das bei ADC=1023 7000RPM und beim ADC=0 0RPM angezeigt werden. Wenn man das in Schritten von 7 macht kommt man bei ADC=1023 auf 7161RPM als max.-Wert. Wenn das nicht reicht dann mit einer Stufung von 8 (max. 8184RPM). Nur wird es dann wohl schwieriger sein den Spannungsteiler für den ADC-Pin anzupassen. Mittels Timer ist RPM-Messung technisch gesehen wesentlich einfacher da man mittels einem sehr genauen Quarz die Zeit zwischen 2 oder mehr Impulsen mist und diese dann einfach als Divisor zur Umrechnung mittels Division hernimmt. Dafür ist die Software wohl etwas komplexer aber das wird doch hoffentlich noch. Gruß Andi
Hallo Andi.. Ich dachte mir, wenn ich 1023x10 nimm, dann hab ich eine max. Drehzahl von 10230, was ich aber nicht brauche. Brauche bis max. 7000 U/min Aber auf was ich noch nicht gekommen bin ist, in 50'er Schritten anzuzeigen. Habe vorher 1023x50 genommen = 51150, aber da wird die Anzeige sehr empfindlich. Mit 1023x10 Hab ich jetzt 10'er Schritte. Gibt es da irgendeinen Trick? Ich dachte mir, daß ich die letzte Stelle Abfrage, bevor ich sie ans LCD Ausgebe. Also 125"2" U/min, da ist 2 kleiner als 5 und ich mache eine 0 draus. Wenn es 125"8" ist, dann ist es größer als 4 und ich mach 1255 U/min drauß. Wäre das so in Ordnung, oder würdest du das anders machen? Gruß Avus
Upsss.. da hab ich was falsch geschrieben. Natürlich will ich wenn 12"7"0 U/min ist das in 1250 U/min umwandeln. Habe das Problem jetzt gelöst.
Also so ne Art beruhigte Anzeige oder zum einfachen ablesen, find ich gut. Einfach das 3. Digit vergleichen auf kleiner 8 und kleiner 3. Ist es kleiner 8 dann das 3. Digit auf 5. Ist es größer, also nicht der Fall, also >= 8, das 3. Digit auf 0 und das 2. Digit um 1 erhöhen. Aber was ist, wenn das 2. Digit bereits 9 hat (Überlauf (von 9 auf 0 springt))? Dann muß das 2. Digit auf 0 gesetzt werden und das 1. Digit um 1 erhöht werden. Brauchst dazu eine verschachtelte Rundungsfunktion für die 3. Stelle die auch die 1. und 2. Stelle beeinflussen kann. Wie hast Du das gelöst? Gruß Andi
Ginge das vielleicht so? Vor/bei der ganzen Rechnerei 3 Subtrahieren und dann statt Runden nur Abschneiden. (unter 5 ist 0, über 5 ist 5) ...
Ach ja, das mit dem subtrahieren braucht man glaube ich nicht. Einfach vor der LCD-Ausgabe prüfen, ob die vorletzte Stelle kleiner '5' ist. Wenn, dann ne '0' draus machen, wenn nicht, dann ne '5': cpi r17,'5' brlo Kleiner5 ldi r17,'5' rjmp WarGrößerGleich5 Kleiner5: ldi r17,'0' WarGrößerGleich5: ldi r16,'0' ;Das letzte Digit dann noch auf '0' .... oder so ähnlich. Gruß Andi
Hi Andi, habe es gestern noch so gelöst: cpi r17, '5'; vergleiche R17 mit Zeichen 5 brlo null ; wenn kleiner, dann springe zu null ldi r17, '5'; wenn größer, dann lade Zeichen 5 rjmp digit null: ldi r17, '0'; wenn kleiner, dann lade Zeichen 0 digit: Eigentlich könnte ich es ja auch in einem Timer laufen lassen. Nur fehlt mir da der Ansatz, wie ich es mache. Muß dabei das ganze Programm in den Timer Interrupt kopiert werden und beim Hauptprogramm: Loop: rjmp Loop stehn? Oder muß ich in dem Timer nur die berechungen für den ADC machen und im Hauptprogramm immer ans LCD schicken? Geht das mit dem Overflow Timer? Was für einen Timer brauche ich (8 Bit glaube ich) und was für einen Teiler sollte man da nehmen? Oder z.B. könnte ich jetzt eine Digitaluhr spielendleicht machen. Wie sieht das dann da aus? Mache ich da im Hauptprogramm immer die Berechnungen, was er anzeigen soll und im Timer nur ein Register inkrementieren, oder wie sieht das da dann aus? Wie kann ich ausrechnen, das alle Sekunden ein Timerinterrupt stattfindet? Wenn das Programm im Timerinterrupt ist, werden da die Programmschritte auch mitgezählt, oder hält da der Timer an, weil ja das sei (Interruptbyte) ausgeschaltet wird, damit kein anderer Interrupt stattfinden kann. Fragen über Fragen. Ich weiß, es gibt ein Datenblatt von Atmel... aber mein Englisch... Kennt jemand einen Link, da wo die verschiedenen Timer erklärt werden und auch Testprogramme dabei sind? Weil bei Testprogrammen lerne ich am leichtesten, weil ich dann den Ablauf verstehe, wenn ich ihn sehe. Danke im voraus Gruß Avus
Das jetzt so im Forum recht schwierig zu erklären. Falls du ne ICQ Nummer hast, dann kann man das Timerhandling mal P2P durchsprechen. cu Hauke
Hallo Hauke. Nehme das Angebot gerne an. Werde mich in nächster Zeit mal melden. ICQ Nummer? Schick ich dir per mail.. Habe jetzt mal ein Programm geschrieben. Sollte eine Digitaluhr werden. Hab mir das mal so zusammengebastelt. Den Timer habe ich irgenswo aus dem Forum, Nur wie muß ich das Programm umschreiben, damit die Zeit am Display im Sekundentakt hochläuft? Habe mir vom Peter die genaue Sekunde angeschaut, doch da blicke ich nicht durch. Peter ladet da 256 in ein Register, warum? Ein Register hat ja nur 8 Bit, oder? Vielleicht kann mir jemand helfen. Gruß Avus
@Avus... Ohne Datenblatt kommst du nicht weiter. Du wirst dein Englisch also aufbessern müssen. Muss(te) ich auch, wir hatten in den 50er und 60er Jahren kein Englisch, nur etwas Russisch. Und ohne ein dickes Englisch-Deutsch-Wörterbuch verstehe ich die Datenblätter auch nicht. Das ist zwar mühsam, aber notwendig. Also tu selbst mal was und fang selbst an, Datenblätter zu lesen, lass nicht immer Andere deine Arbeit erledigen. Gruß... ...HanneS...
ah, hab ich gesagt, das du mir ein Programm schreiben sollst? Ich habe mir selber Gedanken über die Digitaluhr gemacht und das (fast) fertige Programm dazugegeben. Für Dich mag es ein Kinderspiel sein, sowas zu machen, aber andere Leute fällt das schon ein bisschen schwerer. Ich bräuchte ja nur einen Denkanstoss. Dabei glaube ich nicht, das du (andere) mit einem guten Tip dann meine Arbeit erledig(st)(en). Gruß Avus
Hi Avus... Meine Reaktion bezog sich auf diesen Abschnitt von dir: ----- Fragen über Fragen. Ich weiß, es gibt ein Datenblatt von Atmel... aber mein Englisch... ----- Und deine Fragen beweisen, dass du kaum ins Datenblatt rein schaust. Für mich ist es auch kein Kinderspiel, aber ich versuche erstmal mit dem Datenblatt weiter zu kommen, auch wenn es schwer fällt. Ich könnte es mir auch leicht machen und einfach hier fragen. Aber erstens ist das nicht mein Stil und zweitens ist der "Anfänger-Bonus" irgendwann aufgebraucht, dann bekommt man keine Fragen mehr beantwortet, die man selbst mit Hilfe des Datenblatts hätte klären können. Viel Erfolg...
"Und deine Fragen beweisen, dass du kaum ins Datenblatt rein schaust" Im Datenblatt steht meißtens, welche Bytes man wo setzen und löschen kann und was man damit machen kann. Nur wie man es dann im Programm sinvoll anwendet, damit man zu einem Ergebnis kommt, davon kann ich nicht viel finden.(Überhaupt, wenn man nochnie mit Timer gearbeitet hat) "Aber erstens ist das nicht mein Stil und zweitens ist der "Anfänger-Bonus" irgendwann aufgebraucht, dann bekommt man keine Fragen mehr beantwortet, die man selbst mit Hilfe des Datenblatts hätte klären können." Willst du damit sagen, daß wenn ich das Datenblatt auswendig lerne, daß ich einwandfrei Assembler behersche und den µC zu 100 % ausnutzen kann... Das glaub ich nicht.... Ich denke, programmieren hat viel mit Fantasie zu tun, weil was nützt es mir, wenn ich alle Befehle auswendig kenne und dann nicht weiß, wie man was machen kann. Am Anfang ist es wichtig, das man mal die Grundsachen beherscht...(Timer, ADC, PWM, LCD, rechnen) Erst wenn man das ein bisschen beherscht, dann kommt die Fantasie dazu, um tolle Programme zu entwickeln, weil man dann Timer, ADC, PWM, LCD und rechnen miteinander verbinden kann. Nur wenn man noch nie mit einem Timer gearbeitet hat, dann hilft das Datenblatt auch nicht weiter. Wenn man den 8 Bit Timer erstmal versteht, dann ist es sicher kein Problem, die Informationen zum 16 Bit Timer aus dem Datenblatt zu holen, weil ja dann die Grundkenntnisse schon da sind. Verstehst du mich? Gruß Avus
Ich verstehe dich sogar recht gut. Trotzdem fällt mir da erstmal ein Zitat ein (der Zitierte möge mir bitte verzeihen...): ----->8----- Also als wir noch Kinder waren, teilweise sogar noch als Jugendliche, hat uns so mancher (Erwachsener) das Sprichwort: "Betroffene Hunde bellen!" um die Ohren gehauen. Das hat bei uns meisten einen gewissen Gegenprotest hervorgerufen, weil stets ein Quäntchen Wahrheit dran war. -----8<----- Übrigens: Datenblatt auswendig nutzt garnix. Es geht da eher ums Verstehen. Aber tröste dich, ich vertehe es auch nicht beim ersten Lesen. Ich konzentriere mich jeweils auf die Dinge, die ich gerade benötige. Aber ich beschäftige mich damit, auch wenn es schwer fällt. Frohes Schaffen...
@avusgalaxy: Wird mal wirklich Zeit, das Du Dir gedanken über "Algorythmik" mast, also über Vorgehensweisen mit dem was man zur Verfügung hat. Fang doch mal mit einer Routine an mit der Du ganze Ketten von Zeichen im Flash (Zeichenketten oder auch Strings) an das LCD ausgeben kannst aber probiers komplett selber. Z. B. übergibts Du der Routine die Startadresse der Zeichenkette in den Z-Pointer und die Routine (Unterprogramm) gibt die Zeichenkette Zeichen für Zeichen aus. Mach Dir mal Gedanken drüber wie man das macht. Es gibt auch die Methode von Diagrammen in der man erst mal grafisch darstellt, welche Schritte nach welchen Schritten zu erledigen sind. Ohne Algorythmik nutzt Dir die komplette Kenntnis eines µC (Register, Befehle) nicht sehr viel. Gruß Andi
Hi ihr zwei. Dann werde ich mich mal bemühen, euch nicht wegen Sachen, die im Datenblatt stehen zu nerven...Werde so gut es geht, mein Englisch einsetzen. Habe nur noch 2 Probleme, um meine Digitaluhr zu verwirklichen: 1.) der Timer... Wie er jetzt funktioniert, hab ich geschnallt(Timer 0). Habe mir heute den ganzen Tag überlegt, wie ich mit dem Vorteiler und dem Timer auf 1 Sekunde komme.(ohne Rest.) Bin auf folgendes gekommen: 8MhZ 1s = 1000mS ; 8 000 000 Hz....1000ms / 8 000 000 = 0,000125 mS 0,000125 mS ; 1 Takt..........Vorteiler=256 (0,000125*256) 0,032 mS ; 256 Takte.......TCNT0=125 (0,032*125) 4ms ; alle 32000 Takte 4 mS wird der Timer aufgerufen. Stimmt das bis jetzt, oder bin ich auf dem Holzweg? Um aus den 4 mS eine Sekunde zu machen, dekrementiere ich ein Register (250), bis es null ist. Wenn es noch nicht null ist, dann verläßt das Programm den Timer. OK. 4 mS * 250 = 1000 mS also eine Sekunde. Am Anfang des Timers setze ich noch das TCNT0 neu, damit das Timing auch passt. Nur wenn ich das Programm teste, dann braucht die Sekunde am Display 5 Sekunden(@8 MhZ). Wo ist da der Fehler? 2.) @ Andi Eigentlich wollte ich es mit .db " " machen. Nur komm ich nicht drauf, wie man dazwischen Register einfügen kann. Also so: .db " ",h1,h2,":",m1,m2,":",s1,s2,0 Verstehst du, was ich meine? Ist das Überhaupt möglich? Gruß Avus, und sorry wegen gestern...
Läuft dein AVR mit 8Mhz?? - Wirklich??? - Nachgemessen???? (Fuse?) Ansonsten läuft Timer0 nicht runter sondern hoch. Willst du 125, dann musst du 125 vor Überlauf einstellen. Das wäre dann 256-125. Da dies aber nicht allzuweit von 125 entfernt ist, wird es daran alleine nicht liegen. Mit Vorteiler 256 und Zählumfang 125 kommst du schon auf 4ms, liegst also richtig. Ansonsten rate ich dir nicht einfach drauflos zu programmieren, sondern erstmal zu versuchen, anderer Leute ihre Quelltexte zu analysieren und zu verstehen, warum das so und nicht anders gemacht wird. Dann würdest du etwas über das Sichern des SREG erfahren, würdest feststellen, das der Timer etwas "holperig" arbeiten kann weil die Int-Aufrufzeit unterschiedlich sein kann (und was man dagegen tun kann). Und die vielen kleinen Stolpersteine, von denen im Wiki schon ein großer Teil aufgelistet ist. ...
Das mit den Zeichenketten mit einer Routine vom Flash zum LCD war jetzt nicht für die Darstellung der Uhrzeit gedacht. Du willst ja nicht nur die Uhrzeit oder Zahlen am LCD anzeigen sondern auch ganz normale Texte wie "Uhrzeit: " oder vielleicht irgend wann mal verschiedene Menüpunkte. Und dafür jedes Zeichen mit 2 Befehlen auszugeben ist doch doof. Das war als Anregung gedacht damit Du Übung bekommst, um einen Algorythmus selbst zu 'kreieren'. Ein Algorythmus hat nix mit den ASM-Befehlen zu tun. Ein Algorythmus in ASM ist genau der gleiche Algorythmus wie in C, Basic oder 8051-ASM wenn der das gleiche macht, nur mit unterschiedlichen Möglichkeiten oder mehr oder weniger Komfortabel zu erstellen. Du kannst Ja auch die Routine zur Dezimalumwandlung so ändern, das diese die LCD-Ausgabe gleich mit macht, aber versuchs auch mal allein, auch, wenns ne Woche dauert. Gruß Andi
Hi Hannes, Hi Andi.. Die 8 Mhz stimmen... @Andi Mit db hab ich schon gearbeitet bzw, das LCD Programm so umgeschrieben, daß ich es mit zl und zh und R0 dann auslesen konnte. Ah ja, das mit .db "Uhrzeit",hh,0 funktioniert das irgendwie? Brauchst nur ja oder nein hinschreiben. Danke Avus
>"Ah ja, das mit .db "Uhrzeit",hh,0 >funktioniert das irgendwie?" Oh man, Du mußt noch viel lernen. Gruß Andi
Hi Andi, hi Hannes. Wollte mal mein neues Programm hier reinstellen. Die Digitaluhr läuft jetzt. In 24 Stunden geht sie allerdings 4 Minuten nach. Sieht jetzt der Algorythmus besser aus, bzw. das Programm allgemein? Habe jetzt sehr mit den Registern gespart und mehr mit dem S-RAM gearbeitet. LCD Ausgabe mache ich immer gleich nach den Berechnungen. Und .db ist auch Dabei. Passt das jetzt besser? Gruß Avus
Nunja, selber schuld. Du wolltest es ja wissen... Also mein zweites Programm sah auch nicht besser aus. Eher schlechter, denn ich hatte nur "selbstgeschriebenen und verstandenen" Code drin. Ich gehe trotzdem mal davon aus, dass du jede Zeile in deinem Code verstehst, die einfachen selbstgeschriebenen genauso wie die hochoptimierten von Peter... Aber das gesamte Programm konnte ich nicht begutachten, denn es ist ja nicht vollständig. Da fehlen ja noch die LCD-Routinen, die du per Include einbindest und in der ISR aufrufst. Und genau da sehe ich ein Problem. Du hast nämlich gegen die drei goldenen Regeln der Interruptprogrammierung verstoßen: http://www.mikrocontroller.net/forum/read-1-130140.html#130537 Denn du rufst aus der ISR die LCD-Routinen auf, von denen ich annehme, dass sie Warteschleifen enthalten. Das ist tödlich, denn das dauert so lange, dass einige Interrupts "verschlafen" werden. Teste das doch mal im Simulator, besonders die Situation, wenn sich Sekunde, Minute und Stunde ändern, also die meisten LCD-Zugriffe stattfinden. Ich möchte wetten, dass diese ISR länger als 4ms braucht. Das wird die Ursache für das Nachgehen sein. Obwohl du nicht übermäßig viel Genauigkeit erwarten solltest. Abhilfe (Vorschlag): Im SRAM einen Bereich definieren, in dem die gesamte (aufbereitete) Ausgabezeile steht. Erreichst du am einfachsten, wenn du in INIT den Ausgabestring aus dem Flash ins SRAM kopierst. In der ISR werden dann nur die Ziffern aktualisiert, mehr nicht. Zusätzlich wird in einem (oberen) Register ein Flag gesetzt, das dem Hauptprogramm sagt, dass sich die Zahlen geändert haben. Im Hauptprogramm fragst du das Flag ab. Ist es gesetzt, dann gibst du den gesamten String aus dem SRAM an das LCD aus, löscht das Flag wieder und schickst den AVR schlafen (Idle-Mode). Ist das Flag nicht gesetzt, dann schickst du ihn gleich schlafen. Der Sleep-Mode bewirkt, dass der Timeraufruf präziser erfolgt. Das ist zwar bei deinem Programm aufgrund des großen Vorteiles nicht so wichtig, sollte man aber trotzdem tun. Da nun dein Hauptprogramm auch Register nutzt, wird es jetzt wichtig, in der ISR das SREG zu sichern. ...
Halli Hallo. Du hast recht, Hannes, da hab ich ja viele Fehler gemacht. Habe dann zum Probieren die LCD-Ausgabe ins Hauptprogramm geschrieben, doch ohne erfolg, weil der Befehl "rcall lcd_null" mehr als 4ms braucht. Ich glaube, er braucht bei mir ca. 10 ms. Jetzt hab ich mich für den 16 bit Timer (50 000 Zyklen)entschieden, 8 als vorteiler--->400 000 Zyklen bis zum Überlauf, und im Timerinterrupt einen Teiler von 20...400 000 x 20 = 8MhZ.. OK. Habe am Anfang das mit dem S-Ram gemacht. Der µC ließt die db Adresse einzeln aus und speichert sie sofort ins S-Ram. 0x0060 ist der Startwert des LCD's. Den Idle-Modus hab ich auch aktiviert... Wußte garnicht daß das geht. Ich dachte, der µC wacht nur bei ext. Interrupts auf. Danke Als erstes nach dem Init ruf ich den Timer auf, damit die berechneten Werte gleich ins S-Ram gespeichert werden. Im Timer setzte ich dann das T-Flag (zum Schluss), das ich dann im Hauptprogramm auslese. Wenn nichts berechnet wird, wird das T-Flag nicht gesetzt.Wenn es gesetzt ist, wird das LCD aktualisiert, indem ich wieder einzeln das S-Ram auslese und ans LCD schicke. Ist es jetzt besser als vorher? Ich vertrag die Wahrheit. Also raus damit..:-) Ah ja, vielen Dank für die guten Tips mit S-RAM , sleep und so... Gruß Avus
Ich habe das letzte Programm noch nicht angesehen, da das sehr viel Zeit kostet, die ich jetzt nicht habe. Das T-Flag ist dazu eigentlich Blödsinn. Jedes gute Programm, das einige Arbeiten im Timer-Int erledigt, andere im Hauptprogramm, braucht ein Register, mit dem Hauptprogramm und ISR miteinander kommunizieren können. Ich nenne dieses Register (eines der oberen, damit man "billig" Bits setzen und löschen kann, also r16...r23) meist "flags" oder "mode". Die 8 Bits können dann unterschiedliche Aufgaben signalisieren. Sie bekommen dann dementsprechende Namen. In der ISR wird das jeweilige Bit gesetzt, in der Mainloop wird es geprüft, gelöscht und abgearbeitet. Die Abarbeitung darf sogar länger dauern als Zeit zwischen 2 INTs ist, da nur jeder 250. Int das Flag setzt. Dann unterbricht der Int eben die Abarbeitung, sie wird nach Verlassen der ISR dort, wo sie unterbrochen wurde, fortgesetzt. SREG-Sicherung in der ISR vorausgesetzt, aber das ist ja selbstverständlich, oder? Besser ist jedoch, wenn man sich eigene LCD-Routinen schreibt, die keine Warteschleifen enthalten und stattdessen mit dem Timer-Int synchronisiert werden. Mittels einer Zustandsvariable (Register) kann sich diese Routine merken, was sie schon getan hat bzw. was sie in der "nächsten Runde" tun muss. Somit entfallen die Warteschleifen, die ja nur sinnlos Prozessorleistung verheizen. Die LCD-Routinen sollten nicht in der ISR laufen sondern im Hauptprogramm. Ein weiteres Flag in "flags" (oder "mode") kann die Synchronisation übernehmen. Die Timer-ISR setzt es alle x Durchläufe, wobei x der erforderlichen Verzögerung entspricht (lieber etwas größerer Abstand, du kannst eh' nicht so schnell lesen). Die LCD-Routine löscht dann dieses Flag wieder. Mit dem LCD-Zustandsregister wird dann ermittelt, ob und was die Routine tun soll. Das ist aber schon höherer Stuff, habe ich auch noch nicht realisiert, das liegt aber daran, dass ich derzeit nix mit LCD mache. Wenn ich Zeit gefunden habe, deinen neuen Quelltext zu analysieren, dann melde ich mich nochmal. ...HanneS...
Hi Hannes. Danke für die schnelle Antwort. Ich dachte mir, ich nehme das T-Flag her, da es bei meinem Programm sowieso nicht benützt wird. SREG hab ich leider nicht gesichert. Verstehe ich das richtig=? Am Anfang des Programmes sollte der Timer schon laufen. R16 ist dabei 0x00. Im Timer wird R16 immer inkrementiert. Vor dem Hauptprogramm Frage ich dann ab, welchen Wert R16 beinhaltet. Wenn es 0x01 ist, dann soll es das LCD Initialisieren, wenn es 0x02 ist, dann soll es das LCD löschen. Das mach ich dann solange, bis das LCD bereit ist.... Äh, is glaube ich noch zuhoch für mich. Was machst du gerade? Grafik Displays? Gru0 Avus
Wenn du kein SREG sicherst, dann macht dein Hauptprogramm Mist, da die ISR dem Hauptprogramm die Flags unterm Ar..(Hintern) verändert. Grafik-LCDs interessieren mich nur im Textmode. Zur Zeit mach ich aber garnix mit LCDs. Habe noch genügend andere Projekte mit kleinen AVRs. Einen Teil findest du bei www.hannes.de.md. Das heißt aber nicht, dass ich mich noch garnicht mit LCD beschäftigt hätte (Anhang). ...
Hi avusgalaxy Ich hab mir mal dein Prog angesehen aber den Thread nicht sonderlich genau verfolgt. Ich hätte da ein paar Vorschläge. Zum einen wäre da die Überlaufprüfung (ich nehme einfach mal die von der Sekunde aber die anderen gehen equivalent) du hattest in deinem Code: ;Sekundenausgabe sek: lds temp, sekunde cpi temp, 60;Wenn 60 dann brne sek_ausgabe clr temp ;Sekunde 0 ... statt dessen könnte man folgendes schreiben: lds temp, sekunde cpi temp, 60;Wenn >=60 dann brlo sek_ausgabe subi temp, 60;Sekunde -60 das würde die Zähler erheblich unempfindlicher machen. Überleg mal was passiert, wenn durch Zufall deine Sekunden bis 61 erreichen? (ich weiß daß das momentan nicht passieren kann) Weiterhin komme ich mit deinem timer setup nicht ganz klar. Du willst doch deinen M8 mit 8MHz betreiben. Wenn man nun den Teiler in den CS1xs Bit auf clk/256 stellen würde (CS1x=1 0 0) Dann würde der timer nach 1 Sekunde auf 31249 stehen (nicht 31250 denn er fängt ja bei 0 und nicht bei 1 an) Wenn man jetzt den Timer1 im CTC mode betreibt, und das ICR1 bzw. OCR1A register auf diese 31249 einstellt, dann würde tatsächlich nur jede volle sekunde ein Interupt auf "Timer/Counter1 Capture" bzw. auf "Timer/Counter1 Compare Match A" eintreten. Ein zusätzlicher Teiler ist nicht notwendig. Weiterer Vorteil wäre das sich durch den CTC mode der Timer sofort beim ereichen der 31249 wieder auf null stellt. Man braucht also gar nicht mehr direkt auf die TCNT1H und TCNT1L zuzugreifen. Als letztes würde ich die generelle Programmstruktur umändern. Die Hex->BCD Routinen würde in die Darstellungsroutine vebannen. Wieterhin würde ich zulassen, daß der Timer Interrupt aus der LCD herraus aufgerufen werden kann. Dazu müßte man den Inhalt der darzustellenden Daten Doppelpuffern (doublebuffering). Ich versuche m,ich mal durch den code zu wurschteln. Cu Hauke
Ich habe mich bereits durchgewurschtelt. Mir gefallen auch diese und jene Stellen nicht, aber so richtig sachlich falsch sind die meisten Stellen ja nicht. Bei Beibehaltung der Original-LCD-Routinen gehört das Hochzählen der Zeiten und die Ausgabe in die Mainloop, gesteuert von einem Flag, welches in der Timer-ISR alle Sekunde gesetzt wird. Die Timer-ISR würde ich bei 4ms lassen, aber mit Timer0, da Timer1 hierfür zu schade ist (ICP usw.). Die 4ms hätten den Vorteil, dass man in dieser ISR noch nebenbei Tasten entprellen kann, denn irgendwann kommt sicher die Idee, die Uhr auch stellen zu wollen. Mit 1s-INT-Takt entprellt es sich schlecht. Nach dem Stellen der Uhr kommt sicher auch der Wunsch, die Uhr als Wecker oder Schaltuhr nutzen zu können. Alles Dinge, die sich sehr gut in eine 4ms-Timer-ISR einbauen lassen, nicht aber in ein 1s-Intervall. ...
Hallo Hauke. Danke für die vielen Tips. Das mit der Sekundenausgabe ist so viel sicherer, wie du es schreibst. Doch die Zeile mit "subi temp, 60;Sekunde -60" würde ich doch auf "clt temp" lassen, weil wenn es mal 61 ist, dann minus 60 ist eins. Stimmt das? Versuche schon die ganze Zeit, das mit dem Timer zu realisieren... Zuerst: ldi temp, 1<<toie1 ; 00000100 out TIMSK, temp ; Timer 1 interrupt ein Dann: Timer 1 = 32249 ldi temp, timer1high out ocr1ah, temp ldi temp, timer1low out ocr1al, temp Dann den CTC Mode und Teiler 256 einstellen: ldi temp, 0b00000100 out tccr1a, temp ldi temp, 0b00001000 out tccr1b, temp und sei Müßte doch stimmen, oder? Fehlt da nochwas? Ich brings nicht zum laufen...
Hi Hannes... dann vergiss ich das mit dem 16 Bit Timer wieder. Also dann (fast) alles raus aus timer 0. Nur den Teiler erhöhen und wenn der Teiler auf 0 ist, ein Flag(register) setzen. Das mit der Tastenentprellung klingt auch gut. Ach Hannes, du hast geschrieben, das "sleep" bei mir nicht viel bringt, weil ich so einen großen Vorteiler habe. Wäre da gut, wenn ich garkeinen Vorteiler habe? Gruß Avus
@Hannes Ich habe ja auch nicht gesagt das die falsch sind. Mit dem Entprellen hast du schon recht, aber es geht ja hier erstmal darum, daß Timerhandling zu lernen. Der Timer1 ist eigendlich wirlich zu schade, aber wenn man was richtig aud die beine stellen will dann nimmt man eh ne RTC von Dallas oder Phillips oder nimmt nen async RTC-Timer im AVR mit Uhrenquarz. Aber in einer sache muß ich dir wiedersprechen. Das hochzahlen der Stunden, Minuten und Sekunden gehört auf jeden fall in die ISR. Sich bei dieser Sache nämlich auf das Hauptprogramm zu verlassen, provoziert geradezu eine ungenaue Uhr. Nur die Umrechnung Hex auf BCD hat nun wirklich nichts dort verloren, diese macht man am besten direkt vor der Ausgabe zum LCD. cu Hauke
Nööö... Großer Vorteiler bewirkt (nebenbei), dass man viel Zeit hat, den Timer auf Startwert zu setzen, denn er bleibt ja soviele Takte Null, wie der Vorteiler groß ist. Du hast Vorteiler 256, also ist es egal, ob du den Timer-Reload nach 7, 9 oder 15 Takten setzt. Bei Vorteiler 1:1 wäre das schon ein gewaltiger Unterschied, da sollte man schon exakte reproduzierbare Verhältnisse haben oder man muss den Timer einlesen und den Reload dazu addieren. Wenn du alles in die Mainloop legst, dann musst du im Int unbedingt das SREG sichern und wiederherstellen. Du brauchst irgendwann noch mehr (eigene) Flags, also solltest du ein Register (r16...r23) dafür reservieren. Das erste Flag sagt, dass die Sekunde voll ist (Zählerstand 250=0). Das nächste könnte in der ISR bei Zählerstand 125 gesetzt werden, worauf die Mainloop den (oder die) Doppelpunkt(e) auf dem Display durch Leerzeichen überschreibt, was das Blinken des/der Doppelpunkte(s) bewirkt (das wolltest du doch schon immer, oder etwa nicht???). Wenn du die "Variablen" Sekunde, Minute und Stunde in drei unteren Registern führst, sparst du dir SRAM-Zugriffe. Da du dort nicht mit Konstanten vergleichen kannst (cpi), schau dir mal cpse an. Der Vergleichswert wird dann vorher in ein oberes Register (tmp) geschrieben. Dabei würde ich zuerst alle Zählumfangsbegrenzungen (Sek, Min, Stunde) abarbeiten, dann erst die ASCII-Umwandlung und Aktualisierung des im SRAM liegenden Ausgabestrings. Denn die Vergleichsreferenz für Sek und Min ist 60 (braucht nur einmal gesetzt werden), für Std dann 24. ...
@Hauke "Der Timer1 ist eigendlich wirlich zu schade, aber wenn man was richtig aud die beine stellen will dann nimmt man eh ne RTC von Dallas oder Phillips oder nimmt nen async RTC-Timer im AVR mit Uhrenquarz." Geht astrein. Der Timer ist doch zum Benutzen da. Aber unnütz extra Hardware zu verschwenden, das wäre wirklich schade ums Geld und den Platinenplatz. "Das hochzahlen der Stunden, Minuten und Sekunden gehört auf jeden fall in die ISR. Sich bei dieser Sache nämlich auf das Hauptprogramm zu verlassen, provoziert geradezu eine ungenaue Uhr." Na aber Hallo ! Oftmals habe ich auch ein Userinteface, d.h. die Hauptschleife wird immer unter 1s durchlaufen. Da ich keine Sanduhren darstellen kann (und will), muß der Nutzer sofort eine Reaktion auf einen Tastendruck erkennen können. Und damit ist das Sekundenzählen so sicher, wie die Bundesbank. Ich mache es ausschließlich so, seit etwa 15 Jahren und habe nie auch nur eine Sekunde verloren. "Nur die Umrechnung Hex auf BCD hat nun wirklich nichts dort verloren, diese macht man am besten direkt vor der Ausgabe zum LCD." Warum denn nicht ? Ich richte mir oft einen Zeichenspeicher ein, wo alles sofort eingetragen wird. Und dieser Zeichenspeicher wird etwa alle 0,2..0,5s komplett an das LCD übertragen. Damit verhindert man ein Flackern bei schnell wechselnden Werten und der Nutzer ist in der Lage, die Werte auch abzulesen (Ergonomie). Den Trick habe ich mir vom Multimeter abgeguckt, da werden auch nur maximal 2..5 Meßwerte je s angezeigt. Peter
@Hauke: Dein Einwurf betreffs Genauigkeit und RTC ist berechtigt. Wenn du aber mal weiter oben liest, stellst du sicher fest, dass ich meine Aussage zur Genauigkeit bereits gemacht habe. Du rennst also offene Türen ein. Du meinst, dass das Hochzählen der Sekunden, Minuten und Stunden in die ISR gehört. Nunja, wenn man bei den Hundertstel Sekunden beginnt, dann auf alle Fälle. Ich glaube aber nicht, dass die Mainloop das Flag verpennen könnte, das nur alle Sekunde einmal gesetzt wird. Selbst wenn die LCD-Routinen ihre Warteschleifen behalten, dürfte das unmöglich sein. Da muss man schon derben Stuss programmieren... ...
Hm, jetzt weiß ich dann wirklich nicht mehr, was richtig und was falsch ist. Avus
Es gibt kein richtig oder falsch, nur manche Sachen machen mehr Probleme als andere. Deshalb habe ich dir ja angeboten das man sich per icq mal unterhält. P.S. Die Modifikation von deinem Code hab ich fertig. Bin sie nur grade am testen. Dann schick ich sie hier. cu Hauke
Hi Hauke, auf Dich hab ich ja ganz vergessen... Du bist leider Offline im ICQ Gruß Avus
Nicht wirklich offline nur unsichtbar Ich bin es leid Werbung über ICQ zu bekommen cu Hauke
OK Die Uhr läuft =8) Source im Anhang Falls du noch Fragen hast meine ICQ# ist: 1905682 cu Hauke
P.S. Im Anhang hab ich noch nen *.prj File für VMLAB reingetan
Hallo Hauke. Das Programm sieht ja sehr gut aus. Da hab ich wohl ein paar überflüßige Sachen gemacht. Vielen Dank. Was kann ich mit der .prj File machen? Gruß Avus
Ich entnehme aus deiner frage, daß du vmlab nicht kennst. VMLAB ist eine sehr gute Programmierungs und Simulationsumgebung. Es gibt auch eine freie (in der codegröße beschränkte) Testversion. Zu finden ist das prog unter www.amctools.com Das praktische ist, daß das Prog nicht nur den AVR simuliert sondern auch das drumherum um den AVR. z.B. RS232 Geräte, I2C Bausteine, LCD Module, Taster, LEDs, Wiederstände, Kondensatoren usw. Das Projektfile dieht zur Definition dieser simulierten Hardwareumgebung. In dem oben genannten *.prj File habe ich halt ein 20*4 Char LCD Modul eingebaut. Ok so eine Simulation, ist vileicht nicht so schnell wie nen echter M8, aber man kann genau sehen wo das Programm hängengebleiben ist. cu Hauke
Hallo Hauke. Wie kann ich die Simulation im VMLAB ansehen? Bist überhaupt online? (ICQ)? Gruß Avus
Moin erstmal. Ich war gestern nicht mehr lange online. Ich hab noch nen paar Anmerkungen zu deinem Orginalcode, welche ich gestern vergessen habe dabeizuschreiben. Deine hex->BCD routine war zwar gut und richtig, aber du hattest noch vergessen die BCD Zahlen in ASCII Zeichen zu verwandeln. Ich habe dies mit in die Hex->BCD Routine reingepackt. Deine Methode zur Erkennung des String-Endes für die LCD Daten ist zwar recht elegant, aber sie braucht zwingend eine 0x00 am Ende des Strings. Diese hast du auch bei den Uhrdaten angegeben. .db "Zeit: 12:00:00",0,0 Nur die Routine welche die Uhrdaten ins RAM einließt hat einen entscheidenen Mangel anfang: lpm tst r0 breq fertig ... fertig: Du testest direkt am anfang ob das aus dem Flash gelesene Zeichen eine 0 ist, und wenn ja dann bendest du die Schleife. Das bedeutet, daß die für das Beenden der LCD Routine benötigte Null gar nicht erst ins RAM geschrieben wird. Man muß den Test ob es eine Null war ans Ende der Schleife setzen. Ich habe im neuen code mal die ganze Sache von Timer1 auf Timer2 portiert. (Damit Hannes seinen Timer1 frei hat) Weiterhin wird jetzt das SREG und die Register welche in der ISR verwendet werden erst gesichert und am Ende zurück gelesen. Das heißt die ISR kann dein Hauptprogramm nicht durcheinderbringen. cu Hauke
Hi Hauke... > Deine hex->BCD routine war zwar gut und richtig, aber du hattest > noch vergessen die BCD Zahlen in ASCII Zeichen zu verwandeln. > Ich habe dies mit in die Hex->BCD Routine reingepackt. Hat er nicht vergessen, war drin. Schade eigentlich, dass du Peters hochoptimierte Bin->Ascii-Routine nicht verstanden und daher so verunstaltet hast. > Nur die Routine welche die Uhrdaten ins RAM einließt hat einen > entscheidenen Mangel Das stimmt. Diese Routine ist zu umständlich und kopiert die Null nicht mit. Habe ich auch erkannt (auch ohne VMLAB), aber vorerst als Pillepalle abgetan, da es schlimmere Bugs gab und ich schrittweise an die Beseitigung herangehen wollte damit der Lerneffekt (und damit der Weg zur Selbstständigkeit) nicht auf der Strecke bleibt. Das trifft auch auf die anderen Stellen zu, in denen ("sicherheitshalber") unnötige Befehle ausgeführt werden. ...HanneS...
Räusper.... Sorry ich kann in seinem code Uhr2.asm die BCD->Ascii umrechnung partou nicht finden. Und was heißt hier eigendlich verunstaltet. Ich habe Peters Routine sehr wohl verstanden, und hab auch noch einen Fehler darin gefunden. Die 3.letzte zeile muß subi r30, -10 - '0' lauten. subi r16, -10 - '0' mov r16, r30 wäre Unsinn weil R16 noch nicht definiert ist und weil das um 10 erhöhte R16 sofort mit r30 überschrieben würde. Ich bin von der Routene ausgegangen welche in Avus seinem Code war. Es wurde halt lediglich die letzten beiden Digits von Peters Routine verwendet und folgende Zeilen eingefügt: mov temp1,temp um temp zu beizubehalten subi temp1,-58 ; =addi temp,(10+48) (48=ASCII 0) subi temp2,-48 ; =addi temp,48 (48=ASCII 0) bcd->Ascii Umrechnung Jetzt sag mir mal bitte wo da die Verunstalltung sein soll. Und mit Pillepalle bezeichne ich nur sachen die Schönheitsfehler produzieren. Sachen die den Code ins Nirvana laufen lassen nenn ich anders. cu Hauke P.S. Hannes wenn du flamen willst dann bist du hier im falschen Forum.
> P.S. > Hannes wenn du flamen willst dann bist du hier im falschen Forum. Will ich nicht, bin deshalb auch weg hier. Macht weiter. ...HanneS...
Na, ja... Lassen wir das lieber... Vielen Dank für eure große Hilfe. Hab durch eure Beispiele sehr viel dazugelernt. @ Hauke... Wie kann ich dich im ICQ erreichen=? Gruß Avus
@Avus Evt. muß ich dich Einladen. Schick am besten mal deine ICQ# per mail. Mal schauen ob das klappt. cu Hauke
Sorry Aber erhätte seine Zeit ja nicht mit rumstänkern verplempern müssen. cu Hauke
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.