Schönen guten Tag,
nach und nach möchte ich versuchen, mir eine Uhr auf mein 4-stelliges
7-Segment-Display zu basteln.
Allerings versuche ich erst einmal, um die Grundlagen etwas
reinzubekommen, einen Zähler auf den letzten beiden Stellen
programmieren. Nun eine kurze Erläuterung zum Programm:
Jede Sekunde wird ein Interrupt ausgelöst, bei dem die Variable counter
(bzw. im main dan "sec" für die Sekunden) inkrementiert wird.
Um eine LED am Display zur Erleuchtung zu bringen müssen die Pins der
Anode (PORTA 0-7) und der Kathode (PORTF 0-3) auf 1 gesetzt sein, da
beide durch einen Transistor durchgeschaltet werden (ich weiß ist
unsinnig, aber zum Zeitpunnkt des Lötens hab ich mich noch auf mein Buch
verlassen).
Ist es denn richtig, dass man die Einerstellen mit Hilfe des Modulo und
die Zehnerstellen mit einer Division durch 10 anzeigen kann?
Wie kann ich das Programm kürzen, um nicht 2x die 10 Cases hinschreiben
zu müssen?
Ich bitte um Verständnis für Fehler, da dies mein erstes wirklich
komplexeres Projekt ist.
Ich bedanke mich schon einmal für die Hilfe!
Grüße,
Alex
Alex schrieb:
Und
> Ist es denn richtig, dass man die Einerstellen mit Hilfe des Modulo und> die Zehnerstellen mit einer Division durch 10 anzeigen kann?
ja, das ist richtig.
Hast du in der Grundschule schon gemacht. Modulo ist (bei positiven
Zahlen) nichts anders als der Rest, der bei einer Division bleibt.
eine Mutter hat 36 Torten und 10 Kinder. Wenn jedes Kind gleich viele
Torten kriegt, wieviele Torten kriegt dann jedes Kind und wieveler
bleiben der Mutter übrig.
36 / 10 = 3
36 % 10 = 6
Jedes Kind kriegt 3 Torten und 6 Torten bleiben übrig. Denn 3 * 10 + 6
ergibt wieder die 36
Du solltest auch noch mal in dein C Buch schauen und nachlesen, wie das
mit den Makros des Präprozessors wirklich ist.
1
#define ZERO PORTA|= 0b00111111
2
...
3
4
switch(number)
5
{
6
case0:
7
number=ZERO;
ein #define vereinbart im wesentlichen eine Textersetzung. D.h. wenn ich
die mal durchführe, dann wird aus
1
number=ZERO;
das hier
1
number=PORTA|=0b00111111;
mal abgeshen davon, dass du hier kein |= haben willst, weil du damit die
Ausgangspins des Ports nur auf 1 setzen kannst, aber nicht auf 0 zwingen
kannst, ist das ziemlich sicher nicht das, was du willst. WOzu willst du
das Ergebnis des Veroderns des Musters mit dem PORTA in number
speichern? Macht keinen Sinn.
(Der wirkliche Hinweis ist hier wohl untergegangen. Du willst hier kein
|= haben. Denn mit |= kannst du nur Pins auf 1 setzen. Du kriegst sie
aber nicht wieder zurück auf 0. In deinem Programm bedeutet das, dass
kurz über lang alles BIts des PORTA auf 1 sein werden, weil du sie
nirgendwo zurücksetzt. Wenn am PORTA sowieso nur die Spaltentreiber
hängen, dann kannst du das Muster ganz einfach zuweisen.)
Alex schrieb:> Wie kann ich das Programm kürzen, um nicht 2x die 10 Cases hinschreiben> zu müssen?
Es wurde dir ja schon gezeigt, dass das mit einem Array viel einfacher
zu machen ist.
Aber hier gehts jetzt ums Prinzip.
Wenn du das hier
1
voidout_num(uint8_tnum)
2
{
3
PORTF....
4
5
switch(num)
6
{
7
....
8
}
9
10
PORTF...
11
}
12
13
voidout_num_zehn(uint8_tnum)
14
{
15
PORTF....
16
17
switch(num)
18
{
19
....
20
}
21
22
PORTF...
23
}
vereinfachen willst, dann machst du die Beobachtung, dass der COde im
switch...case in beiden Funtkionen identisch sind. Deine FUnktion
bestehen also aus einem 'einleitenden Teil', dem identischen
switc...case und einem 'ausleitenden Teil'. Das zu verinfachen, ist aber
nicht schwer. Zieh den gemeinsamen Teil in eine eigene Funktion heraus
und ruf sie von den beiden Funktionen auf
1
voidout_digit(uint8_tnum)
2
{
3
switch(num)
4
{
5
....
6
}
7
}
8
9
voidout_num(uint8_tnum)
10
{
11
PORTF....
12
out_digit(num);
13
PORTF...
14
}
15
16
voidout_num_zehn(uint8_tnum)
17
{
18
PORTF....
19
out_digit(num);
20
PORTF...
21
}
und voila. Du hast den beide male identischen Teil nur ein einziges mal
geschrieben.
Hallo nochmal,
ich hatte eure Antworten bzgl. des Arrays erst später gesehen und habe
dann selber noch weiter rumgebastelt. Aufgenommen habe ich jetzt erstmal
nur den Vorschlag für das Zusammenführen der Cases.
Beim folgenden Programm sollen nun Einzer- und Zehnerstelle ausgegeben
werden. Um die Anzeige zweier unterschiedlicher Ziffern zu ermöglichen,
habe ich einfach schnell einen zweiten Timer Interrupt eingefügt.
Allerdings scheint es mir so, dass der Wert PORTA bzw. "number" nur
einen Wert annehmen kann, denn auf beiden Stellen wird die selbe Ziffer
angezeigt; und zwar die, die als erstes im Programm auftritt.
Ich gehe mal ganz stark davon aus, dass es, wie Karl-Heinz schon gesagt
hat daran liegt, dass die Bitmanipulation im Fall von PORTA daran Schuld
ist.
Aber mit &,|,^ komm ich irgendwie nie auf die Lösung, dass die Bits für
die nächste Ziffer richtig gesetzt werden.
Ich möchte erst einmal dieses "Anfangsprogramm" zu Ende führen und widme
mich denn der Sache mit den Arrays!
Gruß,
Alex
Na ja.
Wie sage ich es.
Der ganze Ansatz, wie du das aufziehst ist quatsch. So macht man kein
Multiplexing.
Du musst dich von der Vorstellung lösen, dass du beide Stellen
gleichzeitig aufleuchten lassen willst. Statt dessen, lässt du sie
hintereinander aufleuchten. Eine zeit lang lässt du die Einerstelle
leuchten, dann eine Zeit lang die Zehner Stelle. Danach wieder die
Einerstelle usw. usw.
Ja, tatsächlich blinken solche Anzeigen ständig. Nur Blinken die so
schnell, dass du als Mensch das nicht mehr als Blinken siehst, weil es
viel zu schnell blinkt.
Von daher ist auch dieser ganze Ansatz mit den Funktionen für die Zehner
und die Einer an und für sich unsinng. Wenn du schon bei Interrupts
angelangt bist, dann macht man das so, dass man sich global 2 uint8_t
Variablen anlegt (sinnigerweise als Array) und in einer einzigen ISR
jeweils eine von beiden ausgibt, mit dem jeweils zugehörigen SChalten am
F-Port. Beim nächsten Timer-ISR AUfruf kommt dann die andere Variable
drann.
In diesen beiden Variablen legst du dir bereits das komplette
auszugebende Muster für diese Stelle ab. Das hat 2 Gründe. Zum einen
willst du da in der ISR nicht mehr grossartig 200 mal in der Sekunde aus
der Ziffer das auszugebende Bitmuster bestimmen. Denn das ändert sich
aus Sicht der ISR ja nur alle heiligen Zeiten mal.
Zum anderen soll die ISR auch deswegen einfach nur das fertig
hinterlegte Muster ausgeben, damit du da jedes beliebige Muster auf die
anzeige zaubern kannst und nicht nur ein paar Ziffern. D.h. was die
jeweilige Stelle anzeigen kann, bestimmt nicht die ISR (welche die
eigentliche physikalische Ausgabe macht), sondern derjenige, der das
Musterr bereit stellt.
Gib mir ein paar Minuten, dann such ich mir aus deinem Programm mal die
Details raus und zeig dir, wie man sowas richtig macht.
Alex schrieb:> Um die Anzeige zweier unterschiedlicher Ziffern zu ermöglichen,> habe ich einfach schnell einen zweiten Timer Interrupt eingefügt.
Wie kommst du auf so eine sinnfreie Idee?
Brauchst Du dann für alle Stellen von Stunden bis Sekunden 6 Timer???
In einem Interrupt werden nacheinander die Stellen angezeigt. Das
geht natürlich nicht, wenn der INT nur einmal in der Sekunde auftritt.
Du willst ja keine "blinkende" Uhr.
EDIT: zu spät.
OCR1A=15624;//Zähler um auf 1s Intervall zu gelangen
88
TIMSK1=(1<<OCIE1A);//Compare Modus aktivieren
89
90
sei();
91
92
while(1)
93
{
94
}
95
}
Im Code sind möglicherweise noch Tippfehler.
Aber vom Prinzip her ist der Code ok. So betreibt man eine gemultiplexte
Anzeige mit mehreren Stellen.
Du kannst auch gerne den Timer 0 etwas langsamer laufen lassen. Ein
Vorteiler von 1 ist jetzt übertrieben. So schnell muss das Multiplexen
nicht laufen. Ein Vorteiler von 8 oder 64 würde es auch tun, so dass
noch nichts flackert.
Alex schrieb:> Ich möchte erst einmal dieses "Anfangsprogramm" zu Ende führen
Da kannst du nichts zu Ende führen. Du steckst mit deinem Code in einer
Sackgasse. Es gibt nur einen Ausweg: wegwerfen und richtig neu
schreiben.
Karl Heinz schrieb:> So betreibt man eine gemultiplexte> Anzeige mit mehreren Stellen.
Und für eine Uhr benötigt man nicht einen zweiten Timer. Das kann im
Multiplexinterrupt gleich auch noch erledigt werden. Zumindest der
Sekundenzähler.
Karl Heinz schrieb:> eine Mutter hat 36 Torten und 10 Kinder. Wenn jedes Kind gleich viele> Torten kriegt, wieviele Torten kriegt dann jedes Kind und wieveler> bleiben der Mutter übrig.
Ich fand deine andere Erklärung mit Wirtshaus und 7 Halben viel
verständlicher.
Also:
eine Mutter hat 36 halbe Bier und 10 Kinder. Wenn jedes Kind...
und wievele halbe Bier bleiben der Mutter übrig ?
Und dann kommt schon die erste Frage:
Ich möchte das ganze jetzt auf 3 Stellen erweitern. Alle nötigen
Parameter(sprich NR_digit, DDRF, die if-Schleife für digitToShow etc.)
wurden auf die neue Anzahl eingestellt.
1
ISR(TIMER1_COMPA_vect)
2
{
3
second++;
4
if(second==60)
5
{
6
second=0;minute++;
7
}
8
9
outputNumber(minute*100+second);
10
}
Nach 60 Sekunden werden die Minuten hochgezählt und der Wert für "value"
wurde angepasst.
1
uint8_toutputNumber(uint8_tvalue)
2
{
3
if(value>999)
4
value=999;
5
6
digit[2]=DigitPattern[value/100];
7
digit[1]=DigitPattern[(value/10)%10];
8
digit[0]=DigitPattern[value%10];
9
}
Um nun die Zehnerstelle der Sekunden zu generieren, muss man ja zuerst
durch 10 teilen und dann wieder den Modulo mit 10 nehmen.
Ergbnis ist, dass Zehner- und Einerstelle funktionieren, die
Minutenanzeige zwar die richtige Minute anzeigt, die Einerziffer der
Sekunden allerdings mitläuft und sich darüber schreibt.....
Alex schrieb:> Ergbnis ist, dass Zehner- und Einerstelle funktionieren, die> Minutenanzeige zwar die richtige Minute anzeigt, die Einerziffer der> Sekunden allerdings mitläuft und sich darüber schreibt.....
Tja. Dann hast du wohl in der ISR einen Fehler gemacht.
Übrigens kannst du die Zerlegung auch so machen
1
for(i=0;i<3;i++)
2
{
3
digit[i]=value%10;
4
value/=10;
5
}
Und bei einem uint8_t wirds natürlich etwas Essig mit Zahlen größer als
255.
Ich hätte im Falle einer Uhr das auch nicht so gelöst, dass ich zuerst
dem µC die Arbeit aufbürde, die Minuten mit 100 zu multiplizeren, nur um
dann durch entsprechende Divisionen alles wieder auseinander klamüsern
zu müssen.
Im speziellen Beispiel einer Uhr spricht ja nichts dagegen, sich eine
Ausgabe für Sekunden zu machen, die die digits 0 und 1 beschreibt und
eine Ausgabe für Minuten, die die digits 2 und 3 beschreibt. Dann kann
man zb in der 'Uhr'-ISR dann auch gezielt nur die Stellen updaten, die
es notwendig haben
1
voidoutputSecond(uint8_tsec)
2
{
3
digit[0]=DigitPattern[sec%10];
4
digit[1]=DigitPattern[sec/10];
5
}
6
7
voidoutputMinute(uint8_tmin)
8
{
9
digit[2]=DigitPattern[min%10];
10
digit[3]=DigitPattern[min/10];
11
}
12
13
ISR(TIMER1_COMPA_vect)
14
{
15
second++;
16
if(second==60)
17
{
18
second=0;
19
minute++;
20
21
outputMinute(minute);
22
}
23
24
outputSekunde(second);
25
}
man muss ja nicht mit Gewalt dem µC Mehrarbeit aufzwingen.
Alex schrieb:> Ergbnis ist, dass Zehner- und Einerstelle funktionieren, die> Minutenanzeige zwar die richtige Minute anzeigt, die Einerziffer der> Sekunden allerdings mitläuft und sich darüber schreibt.....
Zeig alles, hier ist dein Fehler bestimmt nicht.
Fehler liegt zu 99% beim multiplexen.
Karl Heinz schrieb:> Im speziellen Beispiel einer Uhr spricht ja nichts dagegen, sich eine> Ausgabe für Sekunden zu machen, die die digits 0 und 1 beschreibt und> eine Ausgabe für Minuten, die die digits 2 und 3 beschreibt.
Wobei auch nichts dagegen spricht, sich für beide Aufgaben eine einzige
Funktion zu schreiben, der man dann ganz einfach mitgibt, an welcher
Digitposition die jeweilige Einerstelle sein soll
1
voidoutputNumber(uint8_tvalue,uint8_tfirstDigit)
2
{
3
digit[firstDigit+0]=DigitPattern[value%10];
4
digit[firstDigit+1]=DigitPattern[value/10];
5
}
6
7
ISR(TIMER1_COMPA_vect)
8
{
9
second++;
10
if(second==60)
11
{
12
second=0;
13
minute++;
14
15
outputNumber(minute,2);
16
}
17
18
outputNumber(second,0);
19
}
Die ursprüngliche outputNumber war mehr eine allgemeine Funktion. In
konkreten speziellen Fällen kann man da durchaus auch davon abweichen
und sich die Funktionen auch so zurecht legen, dass man
a) gut mit ihnen arbeiten kann
b) man dem µC ein wenig Arbeit abnimmt
Spezielle Funktionalität kann oft einfacher gestrickt sein, als wie wenn
man eine allgemein verwendbare Funktion benötigt. Das ist alles nicht in
Stein gemeisselt.
Karl Heinz schrieb:> Jedes Kind kriegt 3 Torten und 6 Torten bleiben übrig. Denn 3 * 10 + 6> ergibt wieder die 36
...und dann wundern sich alle über die übergewichtige Jugend...
;-)
John Wayne sein Garagennachbar schrieb:>> Jedes Kind kriegt 3 Torten und 6 Torten bleiben übrig. Denn 3 * 10 + 6>> ergibt wieder die 36>> ...und dann wundern sich alle über die übergewichtige Jugend...> ;-)
Sei still, sonst kommt demnächst der Beispiel mit Vater, 7 Söhnen und
3 Nutten im Puff.