Forum: Mikrocontroller und Digitale Elektronik Display Sekundenzähler


von Alex (Gast)


Angehängte Dateien:

Lesenswert?

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

von Rebi (Gast)


Lesenswert?

Ich begreife dann sinn deines Programm nicht doch dein void 
number_output_ONES(uint8_t number) könntest du so kürzen:
1
PORTF |= (1<<PF0);  //Kathode für Einerstellen aktivieren -->Transistor schalten
2
3
if(number < 10)
4
{
5
number = array[number];
6
}

In dem Array sind deine ergebnisse gespeichert.

von Eric B. (beric)


Lesenswert?

2 Tipps

1)
1
char digits[] = { ZERO, ONE, TWO ... NINE };

2)
1
int main()
2
{
3
  char sec1  = 0;
4
  char sec10 = 0;
5
  char min1  = 0;
6
  char min10 = 0;
7
8
  for(;;)
9
  {
10
    output(digits[min10], digits[min1], 
11
           digits[sec10], digits[sec1]);
12
13
    sec1++;
14
    if(sec1 == 10)
15
    {
16
      sec1 = 0;
17
      sec10 ++;
18
    }
19
    if(sec10 == 6)
20
    {
21
      sec10 = 0;
22
      min1 ++;
23
    }
24
    if(min1 == 10)
25
    {
26
      min1 = 0;
27
      min10 ++;
28
    }
29
    if(min10 == 6)
30
    {
31
      min10 = 0;
32
    }
33
  }
34
}

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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
  case 0:
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.)

von Karl H. (kbuchegg)


Lesenswert?

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
void out_num( uint8_t num )
2
{
3
  PORTF ....
4
5
  switch( num )
6
  {
7
    ....
8
  }
9
10
  PORTF ...
11
}
12
13
void out_num_zehn( uint8_t num )
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
void out_digit( uint8_t num )
2
{
3
  switch( num )
4
  {
5
    ....
6
  }
7
}
8
9
void out_num( uint8_t num )
10
{
11
  PORTF ....
12
  out_digit( num );
13
  PORTF ...
14
}
15
16
void out_num_zehn( uint8_t num )
17
{
18
  PORTF ....
19
  out_digit( num );
20
  PORTF ...
21
}

und voila. Du hast den beide male identischen Teil nur ein einziges mal 
geschrieben.

von Alex (Gast)


Angehängte Dateien:

Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Route_66 H. (route_66)


Lesenswert?

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.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Hier deer (ungetestete) Code
1
#include <avr/io.h>
2
#include "avr/interrupt.h"
3
4
#define F_CPU 16000000UL
5
#include "util/delay.h"
6
7
uint8_t DigitPattern[] = { 0b00111111,   // 0
8
                           0b00000110,   // 1
9
                           0b01011011,   // 2
10
                           0b01001111,   // 3
11
                           0b01100110,   // 4
12
                           0b01101101,   // 5
13
                           0b01111101,   // 6
14
                           0b00000111,   // 7
15
                           0b01111111,   // 8
16
                           0b01101111,   // 9
17
                         };
18
19
#define NR_DIGITS 2
20
volatile uint8_t digit[NR_DIGITS];
21
uint8_t digitToShow;
22
23
ISR(TIMER0_OVF_vect)
24
{
25
  // egal welche Stelle gerade eingeschaltet war,
26
  // jetzt auf jeden Fall ausschalten
27
  PORTF &= ~(( 1 << PF0 ) | ( 1 << PF2 ) );
28
29
  // welches ist die nächste anzuzeigende Stelle?
30
  digitToShow++;
31
  if( digitToShow == NR_DIGITS )
32
    digitToShow = 0;
33
34
  // das für diese Stelle auszugebende Muster auf die Segmente legen
35
  PORTA = digit[digitToShow];
36
37
  // und die jeweilige Stelle aktivieren
38
  if( digitToShow == 0 )
39
    PORTF |= ( 1 << PF0 );
40
  else
41
    PORTF |= ( 1 << PF2 );
42
43
  // ... diese Stelle leuchtet jetzt so lange, bis die ISR das nächste mal
44
  // vom Timer aufgerufen wird. Dann wird sie wieder abgeschaltet und die
45
  // nächste Stelle eingestellt und aktiviert.
46
}
47
48
// das auszugebende Muster für eine 2 stellige Zahl bestimmen
49
// und in den Variablen für die ISR ablegen
50
uint8_t outputNumber( uint8_t value )
51
{
52
  if( value > 99 )
53
    value = 99;
54
55
  digit[1] = value / 10;
56
  digit[0] = value % 10;
57
}
58
59
// Alles für die 'Uhr'
60
volatile uint8_t second;
61
62
ISR(TIMER1_COMPA_vect)
63
{   
64
  second++;
65
  if( second == 60 )
66
  {
67
    second = 0;
68
  }
69
70
  outputNumber( second );
71
}
72
73
int main()
74
{
75
  uint8_t cnt = 0;
76
77
  DDRA = 0xFF;
78
  DDRF = ( 1 << PF0 ) | ( 1 << PF2 );
79
80
  // Multiplex-Timer
81
  TCCR0A = (1<<CS00);
82
  TIMSK0 = (1<<TOIE0);
83
84
  // Sekunden Timer
85
  TCCR1B = (1<<CS10)|(1<<CS12)| (1<<WGM12);  //16-bit Timer 1 --> Prescaler 1024, CTC-Mode
86
  TCCR1C = 0;
87
  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.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

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.

von Alex (Gast)


Lesenswert?

Ich bedanke mich vielmals! Werde das jetzt mal akribisch durcharbeiten 
und mich ggf. melden.

von Karl H. (kbuchegg)


Lesenswert?

Karl Heinz schrieb:

Und schon der erste Fehler.

hier, bei der Zerlegung der Zahl, muss es natürlich
1
// das auszugebende Muster für eine 2 stellige Zahl bestimmen
2
// und in den Variablen für die ISR ablegen
3
uint8_t outputNumber( uint8_t value )
4
{
5
  if( value > 99 )
6
    value = 99;
7
8
  digit[1] = DigitPattern[ value / 10 ];
9
  digit[0] = DigitPattern[ value % 10 ];
10
}

heissen. Denn irgendwann muss ja auch mal die 'Umsetzung' einer 
numerischen Ziffer in das auszugebende Segment-Muster erfolgen.

von Route_66 H. (route_66)


Lesenswert?

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.

: Bearbeitet durch User
von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

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 ?

von Alex (Gast)


Lesenswert?

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_t outputNumber( uint8_t value )
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.....

von Karl H. (kbuchegg)


Lesenswert?

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
void outputSecond( uint8_t sec )
2
{
3
  digit[0] = DigitPattern[ sec % 10 ];
4
  digit[1] = DigitPattern[ sec / 10 ];
5
}
6
7
void outputMinute( uint8_t min )
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.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

Karl Heinz schrieb:

> Tja. Dann hast du wohl in der ISR einen Fehler gemacht.

Gemeint ist die Multiplex-ISR

von Karl H. (kbuchegg)


Lesenswert?

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
void outputNumber( uint8_t value, uint8_t firstDigit )
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.

: Bearbeitet durch User
von John Wayne sein Garagennachbar (Gast)


Lesenswert?

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

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

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.

: Bearbeitet durch User
von Jörg E. (jackfritt)


Lesenswert?

Wie gross kann ein uint8 werden?
999??

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
Noch kein Account? Hier anmelden.