mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Propeller Clock


Autor: Mario Mauerer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Marcel (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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 ;(

Autor: Mario Mauerer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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ää?

Autor: Sssssss (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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 ;)

Autor: Uwe Große-Wortmann (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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).

Autor: Hannes Lux (hannes)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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...

...

Autor: Simon Küppers (ohne Login) (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: raoul (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
ich denke auch, dass das mit bascom nicht funktionieren wird, weil das
vielleicht zu langsam ist :-( naja musste halt mal kurz asm lernen :-)

Autor: Sssssss (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Mario Mauerer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

Autor: Mario Mauerer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

Autor: Hannes Lux (hannes)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

...

Autor: Mario Mauerer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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??

Autor: Hannes Lux (hannes)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

...

Autor: Mario Mauerer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Mario Mauerer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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..

Autor: Sssssss (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hier mal mein Code um 16 leds zu setzen:
void set_led(unsigned int status){
        unsigned char led_lo;
        unsigned char led_hi;

        led_hi = 0xFF - (status>>8);
        led_lo = 0xFF - (status & 0x00FF);

        //set led 0-7
        PORTC  = led_lo & 0x3F;
        PORTD  = (PORTD&0xCF) | ((led_lo & 0xC0)>>2);

        //set led 8-15
        PORTB  = led_hi & 0x3F;
        PORTD  = (PORTD&0xFC) | ((led_hi & 0xC0)>>6);
}
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 ;)

Autor: Mario Mauerer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Mario Mauerer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Sssssss (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Mario Mauerer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Sssssss (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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 ..

Autor: Mario Mauerer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Simon K. (simon) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>> 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.

Autor: Mario Mauerer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Martin (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Hannes Lux (hannes)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

...

Autor: Mario Mauerer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Jo, so gehts auch.. :)

Autor: Mario Mauerer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

Autor: Hannes Lux (hannes)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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).

...

Autor: Mario Mauerer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Hannes Lux (hannes)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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...

...

Autor: Mario Mauerer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Hannes Lux (hannes)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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...


...

Autor: Mario Mauerer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: TobyTetzi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Hannes Lux (hannes)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

...

Autor: TobyTetzi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Hannes Lux (hannes)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

...

Autor: TobyTetzi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Hannes Lux (hannes)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

...

Autor: TobyTetzi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Hannes Lux (hannes)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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!!!)

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.