Hallo zusammen, angeregt durch die geile Heili-"Clock" vom anderen Thread, möcht ich sowas im kleinen Massstab mal bauen. Die Hardware hab ich schon so halb zusammen, ich verwende 8 Leds, die senkrecht stehen und sich um einen vertikal stehenden Motor rumdrehen. Die Hardware war ja nicht so schwer, ich häng an der Software... Über eine Gabellichtschranke wird die Beziehung Software und Umdrehung hergestellt. Sie löst einen Interrupt aus, der das Hauptprogramm (Erzeugung der Zahlen und Darstellung dieser) unterbricht und von vorne wieder beginnt. War das Hauptprogramm noch nicht durch, bevor der Interrupt kam, dauert es zu lange (zuviel Text) Zuerst möchte ich nur mal eine dreistellige Zahl angezeigt bekommen. Sagen wir mal 135. Durch Rechenschritte kann ich diese Zahl in ihre 3 Teilzahlen zerlegen. also: X=1 Y=3 Z=5 Ich muss dem AVR (bei mir: Mega8 mit Bascom programmiert) ja 10 Zahlen "beibringen". Wann er jede Led in welchen Abständen blitzen lassen soll, damit die jeweilige Zahl erscheint. Ich dachte mir, ich schreibe das in 10 Unterprogramme, jedes Unterproggi für eine Zahl. Dann dachte ich mir, ich könnte die Subs durchnummerieren, also Sub 0 für die 0, Sub 1 für die 1 etc. Dann, wenn ich die Zahl zerlegt habe: Gosub X, dann würde er zum Sub 1 gehen und die 1 anzeigen. Danach käme Gosub Y, dann würde er zum Sub 3 gehen etc. Doch: Das Gosub X funktioniert nicht... Da muss der genaue Name der Sub stehen... Gibts da einen Trick, oder soll ich das Ganze anders lösen? Herzliche Grüsse Mario
Ich würde die Zeichen direkt in einem Array ablegen. Jeweils ein Bit pro LED, d.h. 8 x 200 (wenn du die umdrehung in 200 Schritte auflösen willst). In dieses Array schreibst du dann Schritt für schritt deine Zeichen (1 für LED an, 0 Led aus z.B.). Dieses Array gehst du dann mit einem Timer schritt für schritt durch so das die umdrehung mit 200 Schritten erledigt ist. Vielleicht komisch erklärt ;(
Hmm, ich versteh das nicht. Wie meisnt du das? Wo lege ich fest, wann und wo der jeweilige Led-Port auf High gesetzt wird? Hää?
du machst dir nen globalen array: volatile unsigned char data[360]; per timer gibst du nun immer data[0] bis data[359] aus. Dann bau dir eine setPixel(x,y) methode die dir pixe x,y setzt. Danach kannst du ganz normal auf dem Display malen und es wird automatisch ausgegeben ;)
du speicherst in dem array nicht die einzelnen zahle, sondern das komplette "bild", das die LEDs anzeigen sollten. beim obrigen rechenbeispiel legst du also für alle 200 sektoren des bildes fest, ob dort eine led lecuten soll oder nicht. das bild müsstest du dann am PC vorrausberechnen (bei festen bildern und texten) bzw in einer separaten routine erstellen (für dynamische inhalte wie messwerte).
Ich denke mal, du musst deine "Aufgaben" in kleinere Jobs aufteilen. Du hast 8 LEDs, also 8 "Zeilen". Du kannst damit zu jeder Zeit eine senkrechte Pixelspalte anzeigen, also eine Grafik von 1 Pixel Breite und 8 Pixel Höhe. Um nun eine Ziffer oder einen Buchstaben darstellen zu können, musst du mehrere dieser "Grafiken" nacheinander (nebeneinander) anzeigen. Nun kommt die Frage, ob du nur eine feste Pixelgrafik anzeigen möchtest, oder ob das Teil variablen Text darstellen soll. Für eine einfache Grafik (das kann ein fester Text sein) musst du nun die einzelnen Pixelspalten in einer Tabelle im Flash anlegen. In einem Timer-Int wird dann das jeweils nächste Byte aus der Tabelle geholt und an die LEDs ausgegeben. Dabei lässt man einen Zähler mitlaufen, um das Ende der Tabelle nicht zu verpassen. Willst du variablen Text anzeigen, so musst du eine Tabelle mit dem ASCII-Zeichensatz einrichten. Es genügt der "druckbare Bereich" von 32 bis 127. Wenn wir von einer Schrift fester Breite ausgehen, dann benötigt jedes Zeichen 5 oder 6 (wie Text-LCD) oder 8 (wie C64) Pixelspalten je 1 Byte. Um nun ein Zeichen auszugeben, setzt du erstmal den Z-Pointer auf den Tabellenanfang (mal 2). Dann subtrahierst du 32 vom ASCII-Wert des Zeichens, das du anzeigen willst und multiplizierst den Wert mit der Breite der Zeichen. Dabei entsteht ein 16-Bit-Wert, den du auf den Z-Pointer drauf addierst. Nun zeigt der Z-Pointer auf die erste Pixelspalte des Zeichens. In der Timer-ISR liest du ein Byte aus dem Flash (LPM) und gibst dieses an die LEDs aus (Z-Pointer erhöhen nicht vergessen). Dann zählst du mit einem Zähler ab, ob schon alle Pixelspalten des Zeichens ausgegeben wurden. Ist das Zeichen fertig, so wird ein Flag für das Hauptprogramm gesetzt, damit dieses das nächste Zeichen holt und den Z-Pointer neu positioniert. Es setzt dann auch den "Zeichenbreitenzähler" auf Startwert. Die darzustellenden Texte können z.B. von einer internen Uhr (Software) oder über die UART (Infrarot) vom PC kommen. Hier gibt es noch einige andere Threads, in denen diese Themen intensiv diskutiert werden, schau dich mal um... ...
Jungs, Wenn ich das Richtig verstanden habe, verwendet der Meister oben BASCOM. Ich glaube das kannst du vergessen (glaube ich allerdings nur). Um mal zu sagen wie ich das machen würde ( Habe mir die anderen Versionen nicht durchgelesen) hier : Sagen wir du hast pro Zeichen 8 Zeilen und 8 Spalten. Du Speicherst dann im RAM oder sonstwo an Adresse 0 folgendes: Adresse | Wert 0 | Spalte 1 1 | Spalte 2 n | Spalte n 7 | Spalte 8 Das heißt ein Zeichen benötigt 8 Bytes. Das nächste Zeichen käme jetzt an Adresse 8 bis 15. Jetz lässt du einen Timer zyklisch interrupten der nun folgendes macht. (Sagen wir Z ist eine Zählervariable oder ZählerRegister): --- timer interrupt: PORTX = Daten aus Adresse Z Z = Z+1 --- Der Timer gibt jetzt bei jedem Overflow die Daten die an Adresse Z liegen aus, und zählt Z um 1 hoch. Das ganze stelle ich mir sehr einfach zu machen in Assembler, oder mit Arrays in C vor. in BASCOM habe ich aber keine Ahnung. Die Frequenz des Timers bestimmt, wie dicht die einzelnen Spalten aneinander sitzen.
ich denke auch, dass das mit bascom nicht funktionieren wird, weil das vielleicht zu langsam ist :-( naja musste halt mal kurz asm lernen :-)
Zu langsam is das sicher nicht ;) Ne Propeller Clock stellt kaum Anforderungen an den Code, lediglich der ausgabetimer muss vernünftig laufen. Ich code meine in C, die Grundideen hab ich mir bei der ispf.de Uhr abgeguckt. Wenn du 8leds hast nimm einfach 1 Byte als Spalte.
Puh... Ich möchte keine Variablen Texte oder so darstellen, nur fest programmiere Zahlen. Ich guck mal, wie ich das mit dem Array unter Bascom lösen kann, oder weiss jemand, wie ich die Tabelle über Bascom da reinkriege?
Hmm, zum Array: Sagen wir, ich möchte 200 einzelne "Schritte" pro Umdrehung. Ich kann also 200 Spalten mit 8 Zeilen darstellen. Sagen wir, ich möchte ganz am Anfang eine 1 angezeigt haben. Die eins ist 3 Spalten breit. Also muss ich in die ersten drei Spalten des Arrays an den richtigen Stellen die 1 und 0 setzen. Das könnte man ja mit einer Variable machen, oder? Also z.B. für die erste Spalte: x=00010000 Jetzt muss ich aber noch eine Beziehung zu den Ports herstellen... Wie krieg ich das hin? Also z.B. Portd.1 soll den Zustand des 4. Bits der Variable x annehmen, er wäre dann also auf High gesetzt... Gibts da eine Methode?
Das mit dem BASCOM ist mir auch aufgefallen. Ich hatte aber bewusst nicht darauf reagiert weil man mich eh schon für einen BASCOM-Hasser hält. Dabei habe ich nix gegen BASCOM (und andere Hochsprachen), sondern nur etwas dagegen, dass man meint, das Benutzen einer (einfach erscheinenden) Hochsprache erpart einem, sich mit der Architektur (und dem Datenblatt) vertraut zu machen. Um das in ASM zu realisieren, musst du nur das Datenblatt des Controllers verstanden haben. Um das in BASCOM zu realisieren, musst du BASCOM-Meister sein, mit Anfängerwissen schaffst du das nie. Um das in C zu realisieren, solltest du schon recht fit sein in C und der AVR-Lib für C. Für den Anfänger gibt es da reichlich Stolperfallen und Fallstricke. Wenn man C und die Lib halbwegs beherrscht, dann ist es in C sicherlich einfacher bzw. schneller zu realisieren als in ASM. Nimm also ASM, das ist der leichtere Weg. ...
Mal gucken, wie weit ich komme. Das Problem ist: Mit Bascom kann man Arrays machen. Es werden dann einfach soundsoviele Variablen definiert, unter denen man eine Zahl speichern kann. Nur: Wenn ich z.B. unter der Array-Variable X(1) (1. Spalte) die Zahl &B00000001 speichere, dann speichere ich da ja eine 1. Wie kann ich die einzelnen Bits, die ich in einem Array-Byte habe, den Ports zuordnen??
In ASM: (wenn Z-Pointer auf Tabelle (Array mit Konstanten) im Flash zeigt) lpm ;liest von Z-Pointer adressietes Byte in r0 ein adiw zh:zl,1 ;erhöht Z-Pointer für nächsten Zugriff out portb,r0 ;gibt Byte an PortB aus In BASCOM kann ich das auch nicht, könnte aber so ähnlich wie portb=x(n) gehen. ...
Hm, ein Byte kann ich ja nicht ausgeben, den Port kann ich ja nur auf High oder Low setzten... Ich habe ja 8 Ports anzusteuern, in jedem Byte steckt die Info, was die 8 Ports bei einem Schritt tun müssen.. Ich muss also noch die einzelnen 8 Bits aus dem Byte auslesen und dann auf die einzelnen Ports führen... Oh, oder meinst du mit Portb den Gesamten Port... Hmm, das könnte ja geil gehen.. Hey, der PortB besteht aus 8 Pins... Frage an die Bascom-Kenner: Funktioniert das, wenn ich sage: portb= X(1), wobei X(1) das erste Byte im Array ist. Setzt er dann den Portb.1 auf den Wert des ersten Bits? den Portb.2 auf den Wert des zweiten etc. ? Geht das?? Herzlichen Gruss Mario
Hmm, compilieren kann ich so einen Code... Ich werde das jetzt gleich mit Leds testen, mal sehen, ob sie leuchten... Was wäre, wenn ich den PortC nehmen würde? der hat ja nur 6 Pins..
Hier mal mein Code um 16 leds zu setzen:
1 | void set_led(unsigned int status){ |
2 | unsigned char led_lo; |
3 | unsigned char led_hi; |
4 | |
5 | led_hi = 0xFF - (status>>8); |
6 | led_lo = 0xFF - (status & 0x00FF); |
7 | |
8 | //set led 0-7
|
9 | PORTC = led_lo & 0x3F; |
10 | PORTD = (PORTD&0xCF) | ((led_lo & 0xC0)>>2); |
11 | |
12 | //set led 8-15
|
13 | PORTB = led_hi & 0x3F; |
14 | PORTD = (PORTD&0xFC) | ((led_hi & 0xC0)>>6); |
15 | }
|
led0-led5 haengen an PORTC 0..5 led6,led7 haengen an PORTD 4,5 led8-led13 haengen an PORTB 0..5 led14,led15 haengen an PORTD 0,1 uebergeben wird ein 16bit wert also zb 0x1111 = 0b0001000100010001 So ähnlich sollte es auch in bascom gehen ;)
Es geht! An Portb hängen die 8 Leds, dann fütter ich den Port mit dem Byte und er lässt die jeweiligen Leds leuchten!! Dann ist ja die Ausführung kein Problem mehr, wenn man nur fest programmierte Zahlen dargestellt haben möchte. Vielen Dank für die Hilfe!! Gruss Mario
Hmm, weiter gehts.. Über einen voreingestellten Timer füttere ich im Timer-Überlauf-Interrupt die nächste Variable vom Array dem Port zu. Dies mach ich direkt im Timer-Interrupt, ist ja keine grosse Sache. Doch: Damit ich die Anzeige an einem Punkt stehenlassen kann, muss ich das ja mit der Synchronisation hinkriegen. Dafür verwende ich eine Gabellichtschranke. Einmal pro Durchlauf wird ein Interrupt ausgelöst. Dieser Interrupt soll jetzt aber das Hauptprogramm stoppen und von vorne wieder beginnen lassen! Er soll danach nicht wieder an den Ort zurückkehren, von wo aus er ausgelöst wurde... Zudem wird im Interrupt auch die Timervoreinstellung geändert, je nach Drehzahl. Meine Frage jetzt: Wie geht das, in der Interrupt-Routine dem Hauptprogramm sagen, es soll wieder von ganz vorne anfangen? Sprich: Wieder vorne beim Array anfangen anzuzeigen. Oh, ich wüsste, wie ich es mache... Im Timer-Überlauf Interrupt erhöhe ich eine Variable um eins. Diese Variable steht für die Spalte im Array. Wenn der Lichtschranken-Interrupt kommt, setze ich diese Zahl einfach wieder auf 0... Lol, so einfach gehts.. Herzlichen Gruss Mario
Wozu das ? Timer isr: geb aktuelle array[pos] aus if (pos<arrlen) pos++ positions interrupt: pos = 0 main loop: nix tun oder ggf den array neu füllen. den posinterrupt kannst du nachher noch verbessern: du zaehlst wielange eine rotation gebraucht hast und passt den timer neu an -> gleich langes bild bei variierender drehzahl geht aber erstmal auch ohne das
Jo, ist doch fast genau das, was ich machen würde.. Du packst einfach die Ausgabe in den Timer-Interrupt, ich hab sie im Hauptprogramm, kommt aber aufs selbe draufan. Die Drehzahlanpassung hab ich oben ja auch erwähnt.
upps... sorry hab das nicht ganz gelesen g Du schreibst ja weiter unten fast genau dasselbe ::) Die Ausgabe im Interrupt hat den Vorteil dass du in der Main Berechnungen durchführen kannst. zb Linien zeichnen, schrift rüberkopieren & laufen lassen etc ..
Also, ich bin unterdessen weitergekommen!! Hab die Hardware aufgebaut, sie funktioniert. Momentan zeige ich fest programmiert alle 10 Ziffern nacheinander an, das klappt gut. Doch: Nun möchte ich die Zeit "berechnen" und anzeigen... Und da happerts Softwaremässig... Momentan habe ich den Timer0 dazu, in die nächste Array-Spalte zu gehen (eine Led-Reihe weitergehen) Dies macht er mit ca. 1800 Hz. (er zählt mit Prescaler 8 66 Schritte weit, dann kommt der Interrupt, in der die nächste Spalte angezeigt wird. Nachdem alle Spalten durch sind, hält der Timer0 an. Danach kommt der Interrupt von der Lichtschranke. Er stellt die Zählvariable für die Arrays wieder zurück, stellt Timer0 vor und wirft ihn an. Dann gehts wieder von vorne los. Soweit so gut, nur: Wo berechne ich jetzt die Zeit? Ein weiterer Timer mit Interrupt ist sehr problematisch, da es sonst ein Chaos der Interrupts gibt und die Ziffern andere Abstände bekommen.... Wie kann ich das machen? Ich möchte separat die Zeit zählen und so Variablen ändern... ?? Herzlichen Gruss Mario
>> Ein weiterer Timer
mit Interrupt ist sehr problematisch, da es sonst ein Chaos der
Interrupts gibt und die Ziffern andere Abstände bekommen....
Also deine GrundIdee entspricht genau der Meiner ! Wollte es genauso
machen wie Du.
Da der erste Timer mit 1800Hz läuft, ist das nur ein Bruchteil der
Quarzfrequenz von deinem Microcontroller.
Wenn da jetzt abundzu mal ein anderer Timer zwischendurch ein zwei,
oder meinetwegen auch 3 ;) Befehle ausführen will, siehst du vermutlich
garnix verändertes, da sich, im Worst case, dein 1800Hz Interrupt
maximal um ein paar Befehle verzögert (Je nachdem wieviele du im 2.
Timerinterrupt hast).
Wenn der 1. Timer um ein paar µS verzögert wird, ist das alles völlig
egal.
Oh, du meinst also, noch einen Timer einzubinden? Ich habs jetzt anders gelöst, geht aber net so gut, ich werde den zweiten Timer auch noch probieren! Im Timer0 Interrupt zähle ich eine Variable hoch. In der Hauptschleife wird geguckt, ob sie 1800 ist. Falls ja, wird die nächste Zahl angezeigt. Wenn wir bei 9 sind, kommt wieder die eins, zweistelliges schaff ich noch net. So gehts solala, die Zahlen ruckeln etwas, aber es geht. Aber ich probier jetzt noch den zweiten Timer aus! Herzlichen Gruss und vielen Dank! Mario
hmm, Ich hätte da die Idee das Ding so ähnlich wie eine PLL zu programmieren. Du nimmst an das eine Umdrehung x Zeittakte beansprucht und überprüfst das immer dann wenn die Lichtschranke ein Signal gibt. Kommt die Lichtschranke früher wird die Anzahl der Takt erhöht, kommt sie später muss der Timer langamer werden. Das wäre so meine erste Idee. Gruß Martin
Man kann Timer auch so programmieren, dass kein Jitter entsteht. Dazu nutzt man die Output-Compare-Register des Timer1 zum Auslösen von Interrupts.. Dabei lässt man den Timer selbst frei furchlaufen. Output-Compare 1A kann die Uhrzeit generieren (10ms), Output-Compare 1B sorgt für den (verstellbaren) Ausgabetakt. In der jeweiligen ISR wird der Wert des Output-Compare-Registers eingelesen, das erforderliche Intervall dazu addiert und der Wert in das OCR1x-Doppelregister zurückgeschrieben. Da sich der Wert im OCR1x-Register durch den Lauf des Timers nicht ändert, funktioniert das auch, wenn die ISR mit etwas Verspätung aufgerufen werden sollte weil gerade ein anderer Int abgearbeitet wird. Da Timer1 über zwei unabhängige Output-Compare-Einheiten verfügt, lassen sich zwei unabhängige INT-Takte erzeugen, die sich nicht gegenseitig beeinflussen. Und weil dabei der Timer frei durchläuft, kann man auch noch den Input-Capture-Interrupt nutzen, um z.B. nebenbei noch Impulsbreiten zu messen, Dies könnten per IR übertragene Signale von einem anderen MC sein. ...
Ok, sie läuft und zählt bis 99! Dafür waren aber grosse Rechenschritte, auch in den Interrupts nötig... Wie habt ihr das gelöst? Eine zweistellige Zahl anzeigen? Ich zerlege sie in die Ziffern und ordne diese dem Array zu. Macht aber schon 4 VAriablen pro Zahl.... Wie soll ich das lösen? Ohne allzu viel Rechenaufwand?
Liest du eigentlich die Antworten auf deine Fragen? Um variablen Text anzuzeigen, kann man sich den Zeichensatz in eine Tabelle ins Flash legen. Um die Rechnerei einfach zu halten, nimmt man jeweils 8 Bytes pro Zeichen. In diesen sind dann die 8 aufeinander folgenden Bitmuster (incl. Lücke zum nächsten Zeichen) enthalten. In deinem Ausgabe-Interrupt läuft ein Zähler, der die Anzahl der ausgegebenen "Pixelspalten" überwacht. Sagt dieser, dass das Zeichen fertig ist, dann wird der ASCII-Wert des nächsten Zeichen geholt (z.B. aus einem definierten Bereich im SRAM), der Zeichenbreiten-Zähler auf Startwert gesetzt und der Z-Pointer auf das Bitmuster dieses Zeichens gesetzt. Im Hauptprogramm brauchst du dann nur noch die auszugebenden Zeichen in den dafür reservierten SRAM-Bereich abzulegen. Solange nur Ziffern einer Uhr dargestellt werden müssen, reicht ein abgespeckter Zeichensatz, der nur die Ziffern enthält. Um für spätere Ideen offen zu sein, empfehle ich aber den vollständigen "darstellbaren" ASCII-Zeichensatz (32...127). Das sind 96 Zeichen a 8 Bytes, also 768 Bytes Flash (als Tabelle). ...
Ok, ich muss das nun auf einen Timer beschränken, dazu möchte ich den Timer1 mit dem beiden Compare-Registern verwenden. Nur: Wie geht das jetzt? Alle 10ms soll der Compare1A-Interrupt kommen. Wenn die Routine kommt, lese ich den wert ein, der momentan im Compare1a-Register steht und addiere die Anzahl Takte drauf, sodass nach 10ms der nächste Interrupt kommt. Nur: Was ist aber, wenn ich z.B. kurz vor dem Timer-Überlauf bin? Dann beginnt er ja neu zu zählen, mein Compare1A-Register liegt aber über den 16Bit, also würde dieser Interrupt gar nicht mehr kommen... Oder wie soll ich das lösen? Beim Compare1b müsste man es dann genau gleich machen Herzlichen Gruss Mario
Was passiert an einem 2-stelligen Dezimalzähler wenn du zu 98 weitere 5 dazu addierst? 98 + 5 = 103 Du hast aber nur 2 Stellen, also gehen die Hunderter ins Leere und du hast 3 als Ergebnis. Genauso ist das mit dem 16-Bit-Register. Der Übertrag geht ins Carry und wird dann weggeworfen... Mach dir keine Sorgen, das funktioniert wunderbar, solange Niemand den Timer verstellt oder löscht. Also kein CTC-Mode, kein Setzen auf "Startwert" nach Überlauf, einfach frei durchlaufen lassen... ...
Also, ich starte den Timer 1 und lasse ihn immer laufen, stelle ihn auch nie neu ein. Ich habe zwei Compare Register. Diese rufen mir den Interrupt auf. Ich möchte alle 20 und alle 80 Timer-Takte einen Interrupt. Wenn ein Compare-Interrupt kommt, guck ich nach, bei welchem Timer-Wert er kam (bei welchem Compare-Wert). Dann addiere ich jeweils 20 oder 80 drauf und setze den neuen Compare-Wert. Danach kommt das Programm im Interrupt. Den Timer lass ich rennen, keine Voreinstellungen. Falls der Timer nur auf 100 zählen würde, und der neue Wert im Compare-Register wäre 112, dann würde er bei 12 den Interrrupt auslösen. Korrekt? Vielen Dank für die Hilfe! Herzlichen Gruss
Im Prinzip richtig... Nur 20 Takte sind zu knapp. Das wird nichtmal in ASM was. Etwas mehr Zeit musst du dem AVR schon lassen. Der Wertevorrat der 16-Bit-Register ist ja von $0000 bis $ffff. Wenn bei einer Addition ein Wert über $ffff herauskommt, dann landen die unteren 16 Bits in den Registern, das obere Bit (mit dem Übertrag) aber im Carry-Flag, damit du es zum höherwertigen Register dazu addieren kannst. Da du kein höherwertiges Register benutzt, ignorierst du das Carry und wirfst somit den Übertrag weg. Du hast damit einen 16-Bit Ringzähler, genau wie der Timer/Counter selbst. Ein Missverständnis sehe ich noch: Um das Intervall zu setzen liest du nicht den Timer ein (der klappert ja munter weiter und ändert ständig seinen Wert!), sondern das OCR1x-Register. Also den "Zeitstempel" des letzten Interrupts, der (mit etwas Verzögerung) momentan abgearbeitet wird. Dies ist ein fester Wert, der sich durch den Lauf des Timers nicht verändert. Diesen Wert liest du also ein, addierst dein Intervall (die Anzahl der Zimer-Takte zwischen zwei Interrupts) dazu und schreibst den Wert wieder in OCR1x zurück. Und dann machst du in der ISR das, weshalb du den Interrupt überhaupt nutzen willst. Hier zum Veranschaulichen als Beispiel einer Timer-Output-Compare-ISR in ASM: TIM1_COMPA: ;ISR Timer1-Interrupt (alle 10ms) in srsk,sreg ;SREG sichern (Exklusivregister) push xh ;benutzte Register push xl ;sichern in xl,ocr1al ;Weckzeit in xh,ocr1ah ;holen, subi xl,low(-tim1zu) ;Intervall sbci xh,high(-tim1zu) ;dazu, out ocr1ah,xh ;und wieder out ocr1al,xl ;in den Timer ;xl und xh können jetzt innerhalb dieser ISR frei benutzt werden Tastenabfrage: ;Entprellroutine, geklaut bei Peter Dannegger... in xl,tap ;Tastenport einlesen (gedrückt=L) com xl ;invertieren (gedrückt=H) eor xl,tas ;nur Änderungen werden H and tz0,xl ;Prellzähler unveränderter Tasten löschen (Bit0) and tz1,xl ;Prellzähler unveränderter Tasten löschen (Bit1) com tz0 ;L-Bit zählen 0,2,->1, 1,3,->0 eor tz1,tz0 ;H-Bit zählen 0,2,->tz1 toggeln and xl,tz0 ;Änderungen nur dann erhalten, wenn im Prellzähler and xl,tz1 ;beide Bits gesetzt sind (Zählerstand 3) eor tas,xl ;erhaltene Änderungen toggeln alten (gültigen) Tastenstatus and xl,tas ;nur (neu) gedrückte Tastenbits bleiben erhalten or tfl,xl ;und zugehörige Bits setzen (gelöscht wird nach ;Abarbeitung) ;in "tas" steht jetzt der gültige Tastenzustand, ;in "tfl" die Flags der neu gedrückten, noch nicht abgearbeiteten ;Tasten... ;xl ist jetzt wieder frei für weitere temporäre Zwecke in der ISR inc teiler ;Sekundenvorteiler hoch brne nixvollsek ;nein... sbr flags,1<<neusek ;ja, Flag setzen, sbrs flags,aktiv ;ist Ablauf eingeschaltet? ja... rjmp nixvollsek ;nein... inc ztl ;Zeit erhöhen... brne nixvollsek ;Übertrag? nein... inc zth ;ja, erhöhen nixvollsek: tim1_ovf_ende: pop xl ;benutzte Register pop xh ;wiederherstellen out sreg,srsk ;SREG wiederherstellen reti ;fertig... ...
Von Assembler versteh ich nix, hab aber oben geschrieben, dass ich den Compare1a-Wert auslese.... Und die 20 Takte waren natürlich nur ein Beispiel... Vielen Dank für die Unterstützung! Dann werde ich den Code so mal ins Bascom prügeln, mal sehen, wieviel Ersparnis ich herauskriege. Mein momentaner Code hat 2 Timer und 3 Interrupts, ist ca. 350 Zeilen lang und 2kb gross... Funktioniert trotz allem... Mich erstaunt immer wieder, wie verflucht schnell so ein Chip ist, und ich takte intern... (1MHz) Herzlichen Gruss Mario
Hallo, ich wollte mal das Thema PropUhr etwas zusammenfassen, bzw. zusammengefasst haben. 1. Möglichkeit der Regelung: - ein 360 Positionen Array unsigned int display_mem[360]={ 0xFF, 0x0F, ..360 Positionen....}; - Im Ext.Int. if(pos < 360){ OCR1A --; } else { OCR1A ++; } pos = 0; TCNT1=0; - Im Timer1 Compare Int. PORTB = display_mem[pos]; pos++; Fertig. Bei diser Sache ist mir aufgefallen, das zwischen der ertsten und der letzten Spalte (Position) ab und an etwas hin und her wackelt. Muß ich überhaup T1 zurücksetzen? Ja, setze ich ihn nicht zurück, wackelt die erste Position hin und her! Was gibt es noch für Varianten? HanneS, Wie geht es mit dem Timer nicht zurücksetzen? Ich habe da doch auch einen Ext.Int. Was macht der? Könnte man nicht auch einen Compare in eimen bestimten Takt setzen, beim Ext.Int. schauen, wie oft er da war, .... nee, das ist schon wieder das selbe, wie von mir beschrieben. Das große Problem, was ich sehe, ist doch.. Ich versuche es mal Bildlich zu beschreiben: Nehmen wir mal 360 Pflastersteine in einer Reihe. ich messe die Länge des ersten Steins, multiplizire das mal 360. Was ist aber, wenn eine Umdrehung 360,5 oder 359,5 Steine lang ist? Versteht eigentlich jemand meine Komentare, ich selbst versteh es ja kaum noch! ;-) Ich müsste doch die Zeit einer Umdrehung messen, diese durch 360 Positionen teilen. Darauf meinen Spalten Interrupt setzen. Gruß Toby
Hi Tobi... Ob ich deine Kommentare verstehe, weiß ich nicht, denn ich verstehe (d)einen C-Code nicht... ;-( Ich hantiere bei AVRs nur in Assembler. Zum durchlaufenden Timer: Nutzt man den OVF, dann lädt man den Zähler des Timers in jeder ISR erneut auf den Startwert, damit die Zeit (Anzahl der Takte) bis zum Überlauf-Int wieder stimmt. Dies kann aufgrund der unterschiedlichen Interrupt-Responsetime etwas "ruckelig" werden. Abhilfe schafft das Einlesen, Addieren (oder besser subtrahieren?) des Sollwertes und Zurückschreiben des tcntxx-Registers. Variante 2: Der Wert des tcntxx-Registers wird vom Programm nicht verändert. Der Timer zählt stur die Prozessortakte (mit oder ohne Vorteiler). In der OCR-ISR wird das OCR-Register eingelesen, das Intervall (also die Anzahl der Timertakte bis zum nächsten gewünschten Interrupt) dazu addiert und wieder ins OCR-Register zurück geschrieben. Der Timer klappert zwar indessen munter weiter, aber wir manipulieren ja nicht den (ständig klappernden) Timer, sondern das OCR-Register, also die Referenz, die sich nicht durch Hardware ändert, sondern nur durch Zugriffe des Programms. In dieser ISR wird dann natürlich auch noch die eigentliche Arbeit gemacht, wie z.B. Holen und Ausgeben des nächsten Bitmusters an die LEDs. Manchmal kann eine ISR nicht sofort ausgeführt werden, weil das I-Flag gerade nicht gesetzt ist (andere ISR aktiv, andere wichtige Gründe wie EEP-Write). Eine Manipulation des tcntxx-Registers würde aufgrund der Verzögerung und des währenddessen weiterlaufenden Timers einen Fehler verursachen. Da sich die OCRxx-Register aber nicht von allein (Hardware) ändern, kann beim Manipulieren kein Fehler entstehen, solange die Manipulation vor dem nächsten gewünschten Int gemacht wird. Die einzelne ISR kommt dann zwar etwas verspätet, die Verzögerungsfehler addieren sich aber nicht, die nächste ISR kommt wieder pünktlich. Da Timer1 bei den meisten AVRs zwei unabhängige OCRs hat, lassen sich damit zwei unabhängige Zeittakte erzeugen, während der eigentliche Timerstand nicht manipuliert wird. Somit steht der Timer auch noch dem ICP-Ereignis zur Verfügung und sogar weiteren Software-ICP-Kanälen mittels externen Interrupts, die dann jeweils den Timerstand auslesen und mit dem vorherigen Wert verrechnen. Ein Beispiel in ASM ist hier irgendwo, das hast du aber sicherlich schon gesehen, sonst hättest du das Thema ja nicht angesprochen. ...
Hallo HanneS. Ich lasse also den Timer1 (16Bit) mit 8Mhz laufen. Der Prozessor läuft auch mit 8Mhz. Den OCR Interrupt lasse ich, sagen wir mal, alle 100ms kommen. Im OCR Interrupt lese ich das OCR Register. Var = OCR Addiere zum ausgelesenen Wert wieder 100ms dazu, und schreibe diesen wieder ins OCR Register, für den nächsten Vergleichsinterrupt. Dann kommen noch die Aufgaben, wie z.B. LEDs im entsprechendem Bitmuster leuchten lassen. So weit, so gut. Dann muß ich aber doch beim Ext.Int. mein OCR Register wieder auf die ersten 100ms einstellen, und meine Position auf null stellen. Wenn ich irgendwo falsch liege, lass es mich wissen. Was ist aber, wenn meine 100ms zu viel sind, da die Drehzahl höher ist, bzw. zu wenig, da die Drehzahl weniger ist? Demnach müsste ich doch die OCR Interrupts mitzählen. Sind es zu wenig, den einzelnen OCR Wert erhöhen und umgekehrt. Das muß man im Ext Int erledigen. ich werde es mal eben ausprobieren. Gruß Toby
Hi Toby... Mal abgesehen davon, dass du mit 8MHz am Timer1 keine 100ms realisieren kannst (8000000/65536=122Hz), sondern nur 8ms, sind deine Gedanken soweit richtig. Nun habe ich aber keinen Heli. Ich wäre sicherlich auch unfähig, ihn zerstörungsfrei zu fliegen. An einer mechanischen (sich drehenden) Propelleruhr habe ich auch kein Interesse. Bliebe noch, LEDs an meine Trabbi-Räder zu montieren und während der Fahrt Muster oder das Tempo anzeigen zu lassen, aber da hätte sicherlich die Polizei was dagegen. Kurz und gut, ich habe das Problem mit der Synchronisation nicht und habe mir daher noch keine Gedanken darüber gemacht. Daher kann ich dir auch keine probate Lösung anbieten. - Sorry. Ich kann also allenfalls versuchen, einige Gedanken zu sortieren und einige (zu lösende) Probleme in kleinere Einheiten zu zerlegen. Herauskommen wird nix, oder maximal Denkanstöße, die du dann selbst umsetzen musst. - Mitzählen der Interrupts... Ja sicher muss man die "Ausgabe-Ints" mitzählen, schon alleine wegen der Verwaltung der Bitmuster im Flash. Wenn variabler Text angezeigt werden soll, dann hast du den Pointer auf den Text im SRAM und den Pointer auf die Pixelspalte innerhalb des eichens im Flash. Sollen "nur" feste Bitmuster ausgegeben werden, so zählst du ja bereits mittels (Z-) Pointer auf Flash mit. Eine einfache (16-Bit-) Subtraktion von Z-Pointer und Basisadresse der Bitmustertabelle ergibt die Anzahl der bisherigen Ausgaben. Die Anzahl der Ausgaben muss nicht zwingend 360 sein, niemand hindert dich daran, den Kreis (für diesen Zweck) in 256 oder 512 gleiche Teile zu teilen. - "Reset" im externen Int (Lichtschranke, Nullposition)... Richtig, hier muss synchronisiert werden. * Zuerst wird die Pointerposition (Anzahl der Ausgaben während der letzten Umdrehung) ermittelt und gesichert. * Dann wird "auf Null gesetzt", also der Pointer auf Tabellenbeginn gesetzt und der Timer auf "genaues Raster". Dazu ist nicht der Timerstand zu manipulieren, sondern der Stand des OCR-Registers. Man lese also (nur hier) den Timerstand ein, addiere das Intervall hinzu und schreibe das Ergebnis in das OCR-Register. Nun liegt der Timer wieder "im Raster" * Es wird für das Hauptprogramm ein Flag gesetzt, das dem Hauptprogramm mitteilt, dass es das Intervall neu berechnen soll. * Die ISR ist möglichst kurz zu halten, um Kollisionen mit der OCR- ISR zu vermeiden. Hier würde ASM sicher nicht schaden. - Hauptprogramm... Eigentlich kann der AVR in den Sleepmode. Durch jeden Int wird er geweckt und durchläuft einmal die Mainloop, um dann wieder in den Sleepmode versetzt zu werden. In der Mainloop wird nun (unter anderem?) das Flag abgefragt, das mitteilt, dass "gerechnet werden muss". Hier ermittelt man dann anhand der Anzahl der Ausgaben der letzten Umdrehung (vom ext-Int gesicherte Werte) das neue Intervall, welches dann dem OCR-Int zur Verfügung steht. Da diese Berechnung (über einen konkreten Algo habe ich noch nicht nachgedacht, das ist auch nicht "mein Bier") länger dauern kann, als Zeit bis zur nächsten Ausgabe zur Verfügung steht, darf diese Berechnung nicht im ext-Int ausgeführt werden. Denn dann würden Ausgaben ausfallen, da kein OCR-Int aufgerufen werden könnte. Deshalb wird das über ein Flag synchronisiert. Dauert nun die Berechnung mehrere Ausgabeintervalle, dann muss man sich zwischen zwei Varianten entscheiden: * Übernahme des neuen Intervalls erst eine "Runde" später. * Übernahme des neuen Intervalls, sobald es zur Verfügung steht. Die erste Variante könnte zu langsam werden (muss man probieren). Die zweite Variante könnte etwas "unrund" laufen, da die ersten paar Ausgaben mit dem alten Intervall erfolgen, dann das neuberechnete Intervall genutzt wird. Da fällt mir noch eine dritte Variante ein: * Gleitende Anpassung an das neue Intervall in kleinen Schritten. Damit meine ich, dass die OCR-ISR mit einer eigenen Kopie des Intervallwertes arbeitet, die nur die OCR-ISR selbst verändert (entspricht "static" in BASIC). Im OCR-Int wird also der tatsächliche (der benutzte) Intervallwert mit dem jeweils zuletzt errechneten Intervallwert verglichen und bei Unterschied um einen kleinen Schritt an den (errechneten) Vorgabewert angenähert. Diese Variante dürfte am schnellsten auf Drehzahländerungen reagieren und dabei das geringste Zittern erzeugen. Neben der Intervallberechnung kann noch ein Zähler die Umdrehungen zählen und bei Überlauf die Basisadresse der Bitmuster ändern (anderes Muster) oder bei variabler Textausgabe einen neuen Text ins SRAM schreiben oder auch einen Basiszeiger auf SRAM verschieben um Scrolltext anzuzeigen. Da gibt es viele Möglichkeiten, da ist Kreativität gefragt. Wie gesagt, das sind nur Denkanstöße. Du kannst sie mit deinen bisherigen Gedanken / Lösungen vergleichen und zu neuen Ergebnissen kommen. Programmieren werde ich das nicht, da ich keinen Bedarf habe. ...
Hallo HanneS, ich habe deine Version ausprobiert. Nachdem ich bein Ext.Int. den Timerwert auslese, und diesen ins OCR Register schreibe, steht die Anzeige wie "angenagelt". Voher wackelte alles ein wenig. Danke soweit! Nun nochwas. Du schreibst, ich solle im Ext.Int. ein Flag zum berechnen setzen, und im Main dieses Flag nutzen. Das haut noch nicht so ganz hin. Könntest Du mir da noch etwas weiter helfen? Im Moment rechne ich noch im Ext.Int. Habe ich zu wenige OCR Ints gehabt, erhöhe ich den aufzuaddirenden OCR Wert pro Umdrehung plus eins. Waren es zu viele, verringere ich den aufzuaddierenden OCR Wert um eins. Beim Anlaufen des Motors, erhöht, bzw. verringert sich der aufzuaddierende Wert, bis 360 Spalten, nicht mehr, nicht weniger, angezeigt werden. Lade ich den Aufzuaddierenden wert nicht mit einem bestimmten Wert vor, kann es ganz schön lange dauern! Geht das nicht noch Eleganter? Kann man nicht mit Hilfe des Timerwertes, bzw. des OCR Wertes sofort nach einer Umdrehung errechnen, wie lang eine Spalte, also der aufzuaddierende Wert sein muß? Wenn das gelänge, hätte ich alles, was ich brauche! Ähm, etwas wäre da noch! Ich habe an PORTB eine RGB LED. PORTB 0 rot PORTB 1 grün PORTB 2 blau nun möchte ich in einer Spalte, sagen wir mal , ach, ist eigentlich egal, welche, einen Punkt anzeigen. Dieser soll aber seine Farbe ändern. Wie müsste das Spaltenarray aussehen, wenn ich statt 360 Spalten auch noch 7 Farben plus aus habe? Bestimmt hast Du da auch noch eine Idee. Danke allen, bis dahin... ... Gruß Toby
Hi Tobi... Eine einfache Addition oder Subtraktion kann man schon in der ISR durchführen. Allerdings ist damit die Drehzahlanpassung recht ungünstig gelöst. Das geht sicher noch eleganter... Man kann aus den Daten einer Umdrehung mit einer Berechnung das korrekte Intervall für die nächste Umdrehung berechnen. Du brauchst dazu die Anzahl der gewünschten Spalten, die Anzahl der tatsächlich aufgetretenen Spalten und den Dreisatz. Da dies neben der Multiplikation auch eine Division erfordert, dauert die Berechnung etwas länger. Sie muss daher außerhalb der ISR stattfinden. Auch hier würde ich ASM vorziehen, damit du dir nicht den Flash mit der zum Rechnen erforderlichen Bibliothek zumüllst, denn den Flash brauchst du ja für (möglichst viele) Bitmuster. Das Programm muss also sehr platzsparend programmiert werden. Das mit dem Flag: In meinen Programmen (in ASM) gibt es grundsätzlich ein (oberes) Register namens "flags", in dem ich einzelne Bits für spezielle Zwecke reserviert habe. Von C habe ich Null Ahnung, aber das könnten Variablen vom Typ Boolean sein. In der ISR setze ich das betreffende Bit: sbr flags,1<<start In der Mainloop überprüfe ich die einzelnen Bits in "flags" und verzweige dementsprechend. Da ich meist Prioritäten setze, rufe ich die Routinen nicht als Unterprogramm, sondern direkt auf. mainloop: sbrc flags,start ;überspringt, wenn "start" nicht gesetzt ist rjmp neuerunde ;springt nach Label "neuerunde" sbrc flags,irgendwas ;überspringt, wenn "irgendwas" = L rjmp tuirgendwas ;springt zur Routine... ... sleep ;schlafen, falls alles erledigt... rjmp mainloop ;nach dem Wecken durch Int von vorn... neuerunde: ...Berechnungen ausführen cbr flags,1<<start ;Flag zurücksetzen, da Job erledigt ist rjmp mainloop ;prüfen, ob weitere Jobs anstehen tuirgendwas: ...irgendwas tun cbr flags,1<<irgendwas ;Flag zurücksetzen, da Job erledigt ist rjmp mainloop ;weitere Jobflags prüfen... Auf diese Art wird die Mainloop solange durchlaufen, bis alle anstehenden Jobs abgearbeitet sind. Dabei haben die oben stehenden Jobs höhere Priorität. Etwas komplexere Jobroutinen können mehrmals von Interrupts unterbrochen werden. Zum Array... Ich kann kein C und weiß daher nicht, wie Array-Zugriffe von C in Maschinensprache umgesetzt werden. In ASM lege ich ein (Flash-) Array an, indem ich ein Label für die Basisadresse setze und danach mit den Direktiven ".db" oder ".dw" die Werte für die Flash-Zellen definiere. Wie groß die Tabelle (das Array?) wird, wird von der Anzahl der genutzten Zellen bestimmt. Um nun darauf zuzugreifen, setze ich den Z-Pointer auf den Tabellenanfang. Da der Adressraum wortorientiert ist, ich aber Bytes adressieren will, muss ich die (Basis-) Adresse verdoppeln. Das sieht dann so aus, wenn das Label "bitmuster:" heißt: ldi zl,low(bitmuster*2) ;Low-Byte Pointer ldi zh,high(bitmuster*2) ;High-Byte Pointer Dann muss ich den Offset dazu addieren, also den Index auf das Byte, welches ich auslesen will. Beträgt die "Datensatzgröße" mehr als 1 Byte, dann ist der Offset damit vor der Addition zu multiplizieren. add zl,i ;addiert Register "i" zum Pointer adc zh,null ;addiert Übertrag (null ist r2 mit Inh. 0) Nun zeigt der Z-Pointer auf das zu lesende Byte. Dieses kann jetzt gelesen werden: lpm ;liest adressierte Flash-Zelle in r0 ein adiw zl:zh,1 ;Z-Pointer für nächsten Zugriff um 1 erhöhen Nun (innerhalb von 9 Takten) steht der Wert in R0 und kann weiterverarbeitet werden. Neuere AVRs können LPM auch eleganter einsetzen (Auto-Increment, andere Register), aber dieses Beispiel funktioniert auch bei älteren AVRs. Wenn du nur 3 Bit brauchst (RGB-LED), dann hast du eine Menge Bitschieberei (die den Zugriff verlangsamt), wenn du den Speicher optimal nutzen willst. Ich würde deshalb ein ganzes Byte dafür ver(sch)wenden und dafür am Programmcode sparen. Wenn kein variabler Text gezeigt werden soll, sondern nur einfache Bitmuster aus dem Flash, dann würde ich beide Bytes nebeneinander plazieren (also wie 16-Bit-Bitmuster) und gemeinsam adressieren. Das spart Code und Rechenzeit. ...
Hallo, kann es sein, das der Controller die Berechnung nicht zuende ausführt, da er einen Interrupt bekommt? Es scheint mir so, da mit der Berechnung : Spaltenzeit = Spaltenzeit * ( Spalten_ist / Spalten_soll ) komme ich nicht weiter. Momentan liegt die Spaltenzeit bei etwa 1200 - 1500 Takten, bei wenig Drehzahl (geschätzt 100 - 200 U/min) und 8 Mhz. Egal, ob die Berechnung in der ISR oder im Main abläuft. Mit den Flags hats hingehauen, zumindest kann ich im Main die "alte" Berechnung ( addition bzw. subtraktion ) ausführen. Muß ich evtl die Interrupts während der Berechnung deaktivieren. Ich habe diese bis lang Global freigegeben. Gruß Toby
Hi... Es ist ja gerade Sinn des Interrupts, dass er das Hauptprogramm jederzeit unterbrechen darf. Das Hauptprogramm wird nach der ISR dort fortgesetzt, wo es unterbrochen wurde. Wenn das bei dir nicht so ist, dann wird die Ursache woanders liegen. In ASM muss ich dafür sorgen, dass der Stackpointer eingerichtet ist, das SREG während jeder ISR gesichert wird und die ISR entweder die verwendeten Register sichert oder Exklusivregister nutzt. Was in C zu beachten ist, musst du mit dir und deinem C-Compiler ausmachen. Da halte ich mich raus. Da gibt es allerhand Missverständnisse und Fallstricke, das ist auch ein Grund, warum ich bei ASM bleibe. Wenn ich in ASM eine "Variable deklariere", dann weise ich einem Register oder einer SRAM-Zelle einen Namen zu. Somit weiß ich auch jederzeit, wo (physikalisch) die Daten gespeichert sind und was ich tun muss, um Kollisionen zu vermeiden. Eine Hochsprache möchte mir diese Verwaltung abnehmen. Das ist ansich ein "netter Zug", doch es erfordert auch eine Menge weiteres Wissen, wenn man die Übersicht behalten will. Ansonsten gibt es eine Menge Missverständnisse. Siehe auch diverse C-Fragen hier im Forum. ... (Nein, ich will keinen Glaubenskrieg in Richtung Assembler führen!!!)
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.