AVR-GCC 7-Segment Multiplex Problem

OP #2664699
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
}
#2664716
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...
#2665078
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
#2673472
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)
OP #2676378
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
}
#2676381
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.
#2676392
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
}
OP #2676420
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren