Forum: Mikrocontroller und Digitale Elektronik AVR-GCC 7-Segment Multiplex Problem


von Lothar G. (low-d)


Lesenswert?

Hallo!

Bin ziemlich neu, sowohl was Programmierung anbetrifft als auch µC. Im 
Moment experimentiere ich mit einem AtMega8 und möchte einen einfachen 
Zähler von 00 bis FF in Endlosschleife auf zwei 7-Segment-Anzeigen per 
Multiplexing darstellen. Der Controller läuft auf 16 MHz. Mit der 
Einstellung des 16-Bit Timers flackert das zwar ziemlich, aber das ist 
zunächst nicht mein Problem. Das Multiplexing funktioniert ansonsten 
schon ganz ordentlich.
Was nicht richtig funktioniert, ist das Zählen. Er geht mal links 3 
weiter, dann mal rechts 5, oder auch mal eine Zeit lang richtig. Deshalb 
vermute ich, dass die Interrupts irgendwie die Zählroutine durcheinander 
bringen. Was kann ich da machen? Oder ist da noch irgendein grausamer 
Fehler drin?
1
#include <avr/io.h>
2
#include <util/delay.h>
3
#include <avr/interrupt.h>
4
5
// Bits für 7-Segment-Matrix mit gemeinsamer KATHODE
6
7
#define ZERO  0b00111111    // ; 0: a, b, c, d, e, f
8
#define ONE   0b00000110    // ; 1: b, c
9
#define TWO   0b01011011    // ; 2: a, b, d, e, g
10
#define THREE 0b01001111    // ; 3: a, b, c, d, g
11
#define FOUR  0b01100110    // ; 4: b, c, f, g
12
#define FIVE  0b01101101    // ; 5: a, c, d, f, g
13
#define SIX   0b01111101    // ; 6: a, c, d, e, f, g
14
#define SEVEN 0b00000111    // ; 7: a, b, c
15
#define EIGHT 0b01111111    //; 8: a, b, c, d, e, f, g
16
#define NINE  0b01101111    // ; 9: a, b, c, d, f, g
17
#define A    0b01110111
18
#define B    0b01111100
19
#define C    0b00111001
20
#define D    0b01011110
21
#define E    0b01111001
22
#define F    0b01110001
23
24
char data[17] = {ZERO,ONE,TWO,THREE,FOUR,FIVE,SIX,SEVEN,EIGHT,NINE,A,B,C,D,E,F,'\0'};
25
volatile char *c0 = data;
26
volatile char *c1 = data;
27
28
void init(void){
29
  DDRD = 0xFF;
30
  DDRC = 0xFF;
31
  TCCR1B |= (1 << CS11);
32
  TIMSK |= (1 << TOIE1);
33
  PORTC = 0b00000010;
34
  PORTD = *c1;
35
  sei();
36
}
37
38
int main(void){
39
  init();
40
  while(1){
41
      while(*c0){
42
        _delay_ms(500);
43
        c0++;
44
      }
45
      c0 = data;
46
      c1++;
47
      if(!*c1)
48
        c1 = data;
49
  }
50
  return 0;
51
}
52
53
ISR(TIMER1_OVF_vect){
54
  if(PORTD == *c1)
55
    PORTD = *c0;
56
  else
57
    PORTD = *c1;
58
  PORTC ^= (1 << PC0);
59
  PORTC ^= (1 << PC1);
60
61
}

von MaWin (Gast)


Lesenswert?

> if(PORTD == *c1)


Funktioniert nicht.
Der Inhalt von c1 ändert sich doch.
Nimm eine zusätzliche Variable digit mit den Werten 0 und 1.

von Lothar G. (low-d)


Lesenswert?

Funktioniert, danke!

von Achim M. (minifloat)


Lesenswert?

Lothar Glorius schrieb:
> Oder ist da noch irgendein grausamer
> Fehler drin?

Ja. Die Zeitmessung wird in der main() erledigt, das 
Multiplexen/Anzeigen im Timerinterrupt. Eine präzise Zeitmessung sollte 
innerhalb des Timerinterrupts stattfinden. Der Timer sollte natürlich 
entsprechend eingestellt werden.

Lothar Glorius schrieb:
> Was nicht richtig funktioniert, ist das Zählen.
1
#define SEVENSEG_DEACTIVATE()     PORTC|=((1 << PC0)|(1<<PC1))
2
#define SEVENSEG_ACTIVATE_ONES()  PORTC&=~(1 << PC0)
3
#define SEVENSEG_ACTIVATE_TENS()  PORTC&=~(1 << PC1)
4
5
//eine Funktion, die eine uint8_t zahl nimmt und
6
//diese auf dem 7seg darstellt
7
void sevenseg_out(uint8_t number)
8
{
9
   number &= 0x0f;
10
   switch(number)
11
   {
12
      case 0x00:
13
         number = ZERO;
14
         break;
15
      case 0x01:
16
         //usw...
17
      default: 
18
         break;
19
   }
20
   
21
   PORTD = number;
22
}
23
24
volatile uint8_t time, mscount;
25
...
26
//beim initialisieren vor SEI()
27
time = 0;
28
mscount = 0;
29
...
30
31
//Timerinterrupt alle 10ms
32
ISR(TIMER1_OVF_vect)
33
{
34
   mscount++;
35
36
   if(mscount >= 50)
37
   {
38
      mscount = 0;
39
40
      time++;
41
      if(time >= 100)
42
         time = 0;
43
   }
44
   
45
   if(mscount & 1)//mscount ungerade?
46
   {
47
      SEVENSEG_DEACTIVATE();
48
      sevenseg_out(time / 10);
49
      SEVENSEG_ACTIVATE_TENS();
50
   }
51
   else
52
   {
53
      SEVENSEG_DEACTIVATE();
54
      sevenseg_out(time % 10);
55
      SEVENSEG_ACTIVATE_ONES();
56
   }   
57
}

Oh, ja äh, die Mainloop bleibt jetzt "leer" und der Timer macht alles 
alleine... Platz für mehr Kreativität!

mfg mf

PS: dir ist klar, dass dein "data" immerzu im RAM liegt?
Ich hab mal versucht zu skizzieren, wie man das umgeht.
Aber gut, wenns jetzt läuft...

von Lothar G. (low-d)


Lesenswert?

Vielen Dank, an eine präzise Zeitmessung hatte ich noch gar nicht 
gedacht, aber jetzt kann ich es ja auch ordentlich machen :)

von Peter D. (peda)


Lesenswert?

50Hz ist schon recht langsam für Multiplex, wirkt noch etwas unruhig.

Man muß ja auch nicht jedesmal das Division-Unterprogramm aufrufen, 
zumal es 98 mal unnütz erfolgt (es wird der gleiche Wert berechnet).

Der Timer gibt besser nur die Digits aus. Das Berechnen und nach 
7-Segment wandeln macht in aller Ruhe das Main. Dann hat man Luft für 
Erweiterungen.


Peter

von C_anfänger (Gast)


Lesenswert?

hallo :)


eine frage...wie schalte ich den port c. sprich die massen von der 
anzeige.?

mit transistor.? wenn ja welchen.?

danke schonmal. :)
mfg. johannes

von Ingo (Gast)


Lesenswert?

Wie stellst du Zahlen > 99 da?

von C_anfänger (Gast)


Lesenswert?

noch garnicht...bis jetzt hab ich auch noch kein bild...brauche ja erst 
mal die funktionsfähige harware...

von C_anfänger (Gast)


Lesenswert?

^^ hardware ^^

von Karl H. (kbuchegg)


Lesenswert?

C_anfänger schrieb:
> hallo :)
>
>
> eine frage...wie schalte ich den port c. sprich die massen von der
> anzeige.?

Du hast Anzeigen mit gemeinsamer Kathode (Masse)?

> mit transistor.? wenn ja welchen.?

Wie immer:

              +------------+
                           |
                         Gerät
                           |
            µC >------- Transistor
                           |
              -------------+-----   Masse

    in diesem Fall ist der Transistor ein NPN



              +------------+
                           |
           µC >-------- Transistor
                           |
                         Gerät
              -------------+----- Masse

     in diesem Fall ist der Transistor ein PNP Typ
     (wobei es je nach Spannung notwendig sein kann,
      einen Hochsetzsteller für den PNP zu verwenden)


Also: Auf welcher Seite von der Last sitzt der Transistor
      schaltet er nach Masse durch?   Ja -> NPN
      schaltet er die Spannung?       Ja -> PNP (event. Hochsetzsteller)

von Thilo (Gast)


Lesenswert?

Bedenke, dass du beim Multiplexen mit größeren Strömen arbeiten kannst 
(um die gleiche Helligkeit wie beim normalen betrieb zu bekommen)

von C_anfänger (Gast)


Lesenswert?

danke für die ausführliche antwort...:)
funktioniert schonmal...

doch leider flakert es noch zu sehr...was kann ich machen.?

von Peter D. (peda)


Lesenswert?

C_anfänger schrieb:
> doch leider flakert es noch zu sehr...was kann ich machen.?

Beitrag "Re: AVR-GCC 7-Segment Multiplex Problem"


Peter

von C_anfänger (Gast)


Lesenswert?

wie gesagt, ich bin anfänger :D

wie kann ich es da jetzt ändern.?

von C_anfänger (Gast)


Lesenswert?

MaWin schrieb:
> Der Inhalt von c1 ändert sich doch.
> Nimm eine zusätzliche Variable digit mit den Werten 0 und 1.

wie soll man das verstehen.? eine neue variable wofür.?

von MaWin (Gast)


Lesenswert?

> C_anfänger (Gast)
> wie soll man das verstehen.? eine neue variable wofür.?

Lothar Glorius (low-d) hat's verstanden:

> Funktioniert, danke!

von C_anfänger (Gast)


Lesenswert?

weiß ich...kann ja sein, dass es mir einer erklären kann...

von Lothar G. (low-d)


Lesenswert?

Das liegt daran, dass beim Weiterzählen in der main z.B. c1++ gemacht 
wird. Wenn dann die Interruptroutine kommt, kann er mit
1
if(PORTD == *c1)

nicht mehr so prüfen, wie ich mir das vorgestellt hatte.

Die kleine Änderung mit der Variablen sieht dann so aus:
1
uint8_t digit = 1; // oder 0, je nachdem wie man PORTC und PORTD initialisiert
1
ISR(TIMER0_OVF_vect){
2
  if(digit == 0){
3
    PORTD = *c0;
4
    digit = 1;
5
  }
6
  else{
7
    PORTD = *c1;
8
    digit = 0;
9
  }
10
  PORTC ^= (1 << PC0);
11
  PORTC ^= (1 << PC1);
12
13
}

von Karl H. (kbuchegg)


Lesenswert?

Und das allgemeine Schema lautet:
Halte den Multiplex Mechanismus unabhängig von allem anderen. Wenn 
dessen Steuerung in sich geschlossen ist und niemand anderer extern 
drann rumpfuscht, kann auch nix passieren.

Und gegen das Flackern. Nun ja, die Timerfrequenz mit einem kleineren 
Vorteiler hochdrehen, soll da schon gewirkt haben.

von Karl H. (kbuchegg)


Lesenswert?

Lothar Glorius schrieb:


> ISR(TIMER0_OVF_vect){
>   if(digit == 0){
>     PORTD = *c0;
>     digit = 1;
>   }
>   else{
>     PORTD = *c1;
>     digit = 0;
>   }
>   PORTC ^= (1 << PC0);
>   PORTC ^= (1 << PC1);
>
> }


Gibts eigentlich einen Grund, warum du hier mit einem Pointer hantierst? 
Lass doch den unsigned char c0 gleich direkt das auszugebende Muster 
beinhalten. Was immer das auch sei.
Dann kannst du im Grundzustand zb auch mal in deiner Anzeige 2 '-' 
anzeigen lassen, oder die Anzeige blinken lassen, oder nach dem 
Einschalten eine kleine coole Animation, in der nacheinander alle 
Segmente kurz ein und wieder ausgeschaltet werden, oder ....
Je weniger die Multiplex-Routine von irgendwas anderem abhängt, umso 
besser. Und noch weniger abhängig als "Da gibt es 2 Variablen, die das 
auszugebende Muster beinhalten" geht nicht mehr. Ob du dann bei deiner 
Zahlenzerlegung gleich das entsprechende Muster ablegst oder einen 
Pointer auf das Musterbyte im Array, ist auch schon egal.
1
void OutNumer( uint8_t nr )
2
{
3
  c0 = data[ nr / 10 ];
4
  c1 = data[ nr % 10 ];
5
}

Die Digit-Steuerung hätt ich explizit gemacht. Wenn dir da was 
durcheinander kommt, tauschen die Digits wieder ihre Plätze.
1
ISR(TIMER0_OVF_vect)
2
{
3
  static int outDigit = 0;
4
5
  PORTC = ~(( 1 << PC0 ) | ( 1 <<PC1 ));   // alles aus
6
7
  outdigit = 1 - outDigit;
8
  if( outDigit == 0 )
9
  {
10
    PORTD = c0;
11
    PORTC |= ( 1 << PC0 );
12
  }
13
  else
14
  {
15
    PORTD = c1;
16
    PORTC |= ( 1 << PC1 );
17
  }
18
}

von Lothar G. (low-d)


Lesenswert?

Jau, das hört sich gut an. War halt nur ein erster Test, da ich auch 
noch ziemlich neu in der Materie bin und obendrein froh, dass ich was 
mit Pointern hinbekomme. Die nächste Vorteilerstufe war viel zu schnell 
(von Takt/8 auf Takt), habe als schnelle Hilfe dann doch den 8-bit Timer 
mit Takt/1024 genommen, das sieht schon etwas besser aus. CTC wäre 
optimal einzustellen, da hatte ich mich aber noch nicht eingelesen.

Für die nächste Anwendung würde ich so einen Zähler auch schon ganz 
anders machen, dank der Anregungen hier im Forum. Ich wollte nur dem 
Gast noch eben etwas mit dem Problem im Code meines ersten Postings 
helfen.

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.