Forum: Mikrocontroller und Digitale Elektronik 3x3 LED-Matrix-Multiplexing - Software tuts nicht


von Gast (Gast)


Angehängte Dateien:

Lesenswert?

Hallo,

ich will, hauptsächlich ums Multiplexen zu kapieren, eine Ansteuerung 
für eine kleine 3x3 LED-Matrix bauen. Im Anhang der "Schaltplan" - ich 
betreibe dass Ding an einem Arduiono Duemilanove, programmiert über ISP 
mit AVRISP mkII, also am Bootloader vorbei. Deswegen so spartanisch.
Nachfolgend die Software:
1
#define         F_CPU 8000000  
2
3
//#define MEGA16  // Target
4
#define MEGA168   // Arduino-Testboard
5
6
#include <inttypes.h>
7
#include <avr/io.h>
8
#include <avr/interrupt.h>
9
#include <compat/deprecated.h>
10
#include <util/delay.h>
11
                                      //   PB0 LOW     PB1 LOW    PB2 LOW
12
volatile const uint8_t rowSelect[3] = { 0b11111110, 0b11111101, 0b11111011}; 
13
/*über iRowCnt als Index werden im Interrupt die Bitmuster für eine jeweils  aktive Zeile auf PORTB geschrieben */
14
uint8_t pattern[3][3] = { { 0, 1, 1 }, \
15
                          { 1, 0, 1 }, \
16
                          { 1, 1, 0 } };
17
int iRowCnt = 0;
18
int cnt001 = 0;
19
ISR(TIMER0_OVF_vect) { 
20
  PORTD = 0x00;
21
  PORTB = rowSelect[iRowCnt % 3]; //s.o.
22
23
  iRowCnt++;
24
  int cnt002 = 0;
25
  for(cnt002; cnt002 < 3; cnt002++) {
26
    if(pattern[cnt001 % 3][cnt002] == 1) { 
27
      
28
      PORTD |= (1 << (cnt002)); //wenn '1' im pattern-Array, Bit-# setzen
29
        }
30
        
31
      else 
32
        ;
33
      }
34
35
      
36
  cnt001++;
37
38
  
39
    
40
}
41
42
int main (void) {
43
44
    
45
  DDRB = 0x07; // ZEILEN  - 0b00000111
46
  DDRD = 0x3F; // SPALTEN - 0b00111111
47
  
48
  PORTB = 0xFF; 
49
  PORTD = 0x00;
50
  
51
  #ifdef MEGA16 
52
      TCCR0 = _BV(CS02); //clk/256 == 122 interrupts/sek
53
      timer_enable_int (_BV(TOIE0));
54
  #else
55
      TCCR0B = _BV(CS02); //clk/256 == 122 interrupts/sek
56
      //TCCR0B = _BV(CS00);
57
      TIMSK0 = _BV(TOIE0);
58
  #endif
59
    
60
  sei();
61
62
  while(1) {
63
    asm volatile ("nop");
64
        }
65
66
  return (0);
67
}

Leider tut das Ganze nicht so wirklich. Es leuchten konstant die linke 
und mittlere Spalte. Wenn ich mir die ersten paar Schritte beim Debuggen 
im Simulator anschaue, scheint er die Ports aber richtig zu schalten.

Könnte mir jmd. einen Tipp geben? Über einen JTAG-Debugger verfüge ich 
leider nicht, und im Simulator macht dass nicht wirklich Spass, man 
sieht halt nicht, was dabei rumkommt.

Danke!

von Andreas K. (ergoproxy)


Lesenswert?

Du weißt schon das du die Matrix beim multiplexen dauerhaft neu 
ansteuern musst? Warum ist der kram nicht in einer Schleife?

€: Arbeite doch mal mit leerzeilen ^^ das steckt im Timerint, ok vergiss 
was da oben steht.

Außerdem muss ich sagen, dass ich den Code besonders oben nicht ganz 
kapiere da brauch ich noch etwas Zeit für.

Gruß ErgoProxy

von Gast (Gast)


Lesenswert?

> Du weißt schon das du die Matrix beim multiplexen dauerhaft neu
> ansteuern musst? Warum ist der kram nicht in einer Schleife?

Gegen Ansteuerung mit 2 for-Schleifen in main hab ich mich entschieden, 
weil es hier in diversen Threads hies, man bräuchte fürs Multiplexen 
eine halbwegs konstante Zeitbasis. Und bei Schleifen wäre eine 
Abarbeitung in einem konstanten Zeitraum X eben nicht garantiert.

> Außerdem muss ich sagen, dass ich den Code besonders oben nicht ganz
> kapiere da brauch ich noch etwas Zeit für.

Wo hakts denn? Ich kann gern nochmal nachkommentieren.
Im Prinzip läufts im Interrupt wie folgt:

1. Ausgänge PortD (Spalten) alle Low
2. Neues Bitmuster für PortB laden (iRowCnt % 3 ergibt einen Wert 
kleiner 3, wird am Ende des Interrupts inkrementiert), dadurch wird das 
passende Muster für die jeweils aktive Zeile aus dem Array gewählt
3. cnt002 iteriert in der Schleife dann nur noch die jeweils benötigten 
Spaltenzustände aus dem Array heraus; wenn Zustand 1 ist, wird das 
entsprechende Register im PortD auf High gesetzt, sonst halt nicht.

von Andreas K. (ergoproxy)


Lesenswert?

Also wenn ich das jetzt richtig geschrieben hab (hab leider die Hardware 
zum testen ned da) sollte das hier gehen:

Ich hab die Zeiten gelassen usw nur die Ansteuerung ist komplett neu.
Jetzt wird es Zeilenweise angesteuert. D.h. erste Spalte leuchtet, wie 
in der Variablen (von hinten nach vorne) ->

erste Spalte zur Zeit:
LED1 aus LED2 an LED3 an
zweite Spalte:
LED1 an LED2 aus LED3 an
3te:
LED1 an LED2 an LED3 aus
1
#define         F_CPU 8000000  
2
3
//#define MEGA16  // Target
4
#define MEGA168   // Arduino-Testboard
5
6
#include <inttypes.h>
7
#include <avr/io.h>
8
#include <avr/interrupt.h>
9
#include <compat/deprecated.h>
10
#include <util/delay.h>
11
12
volatile uint8_t zeile[3] = {0b00000110,0b00000101,0b00000011}; //Jede LED 1BIT, ist effizienter als 8Bit für 1 LED
13
volatile uint8_t zeilennummer = 0;
14
15
16
17
ISR(TIMER0_OVF_vect) {
18
19
  //setzt alles auf Ursprung
20
  PORTD = 0x00;
21
  DDRB = 0x00;
22
  PORTB = 0x00;
23
  
24
  PORTD |= (1<<zeilennummer); //Spalte auf high
25
  
26
  for(uint8_t i=0;i<3;i++) //Geht jede LED durch
27
  {
28
  if(zeile[zeilennummer] & (1<<i)) //ist das Bit in der Variable gesetzt, wird die LED eingeschaltet
29
        {
30
     DDRB |= (1<<i); //Als output (low), Kathode LED auf - LED leuchtet
31
  }
32
  }
33
  zeilennummer++; //nächste Zeile
34
35
  if(zeilennummer = 4) zeilennummer = 0; //ist die Var. größer als die Zeilenanzahl wird sie zurückgesetzt
36
}
37
38
int main (void) {
39
  
40
  DDRD = 0b00000111;
41
  PORTD = 0x00;
42
43
44
  #ifdef MEGA16 
45
      TCCR0 = _BV(CS02); //clk/256 == 122 interrupts/sek
46
      timer_enable_int (_BV(TOIE0));
47
  #else
48
      TCCR0B = _BV(CS02); //clk/256 == 122 interrupts/sek
49
      //TCCR0B = _BV(CS00);
50
      TIMSK0 = _BV(TOIE0);
51
  #endif
52
53
54
  sei();
55
  
56
  while(1) {   asm volatile ("nop");   }
57
}

€: Haken tats an der Art des Schreibens. Ich bin nunmal meine Art des 
c-Schreibens gewöhnt, da dauert es was anderes zu verstehn.
So viel ich weiß Steht % doch für den Rest einer Division oder? Wenn ja 
würde ich das vermeiden, da divisionen im µC nur umständlich gerechnet 
werden können. Das geht auch mit andern Schleifen.

Gruß ErgoProxy

HAB WAS ÜBERSEHN DIE LEDs SIND BEI DIR UMGEKEHRT IN DER SCHALTUNG MUSS 
DAS NOCH UMÄNDERN - So fertig spaltennummer könntest du noch durch 
zeilennummer ersetzen wär glaube ich übersichtlicher.

von ich (Gast)


Lesenswert?

Ne so wird das nix.

Die DDRx-Register müssen an der jeweiligen Position eine 1 haben, da 
alle Pins als Ausgänge benutzt werden. Einmal für 5V - PORTx =1 und 0V - 
PORTx =0.

Damit kann DDRB  nicht mit 0x00 initialisiert werden. Und die 
Grundeinstellung (alles aus) ist dann PORTD=0xff also auf high und 
PORTD=0x00 also auf low. Wenn jetzt die Spalte ausgewählt wird muss der 
PIN in PORTD auf 1 gesetzt werden und für die Zeile muss der Pin in 
PORTB auf 0 gesetzt werden.
An den DDRx Registern muss also nach der Initialisierung nicht mehr 
rumgefummelt werden.

von Gast (Gast)


Lesenswert?

Danke für deine Mühen schonmal!

> HAB WAS ÜBERSEHN DIE LEDs SIND BEI DIR UMGEKEHRT IN DER SCHALTUNG MUSS
> DAS NOCH UMÄNDERN

Hab ich schon gemerkt, ist bereits geändert.

> So viel ich weiß Steht % doch für den Rest einer Division oder?

Modulo-Operator halt, jo.

@ich:

Damit wir über den selben Code sprechen:
1
#define         F_CPU 8000000  
2
3
//#define MEGA16  // Target
4
#define MEGA168   // Arduino-Testboard
5
6
#include <inttypes.h>
7
#include <avr/io.h>
8
#include <avr/interrupt.h>
9
#include <compat/deprecated.h>
10
#include <util/delay.h>
11
12
volatile uint8_t zeile[3] = {0b00000110,0b00000101,0b00000011}; //Jede LED 1BIT, ist effizienter als 8Bit für 1 LED
13
volatile uint8_t zeilennummer = 0;
14
15
16
17
ISR(TIMER0_OVF_vect) {
18
19
  //setzt alles auf Ursprung
20
  PORTD = 0x00;
21
  PORTB = 0xFF; //Zeilen standardmäßig auf HIGH, damit nix anderes blinkt
22
  
23
  PORTB |= (1<<zeilennummer); //nur aktive Zeile auf LOW setzen
24
  uint8_t i=0; // Definition direkt in der for-Schleife will mein Compiler nicht
25
  for(i;i<3;i++) //Geht jede LED durch
26
  {
27
  if(zeile[zeilennummer] & (1<<i)) //ist das Bit in der Variable gesetzt, wird die LED eingeschaltet
28
        {
29
     PORTD |= (1<<i); 
30
  }
31
  }
32
  zeilennummer++; //nächste Zeile
33
34
  if(zeilennummer = 4) zeilennummer = 0; //ist die Var. größer als die Zeilenanzahl wird sie zurückgesetzt
35
}
36
37
int main (void) {
38
  
39
  DDRB = 0b00000111;
40
  DDRD = 0b00000111;
41
42
43
  #ifdef MEGA16 
44
      TCCR0 = _BV(CS02); //clk/256 == 122 interrupts/sek
45
      timer_enable_int (_BV(TOIE0));
46
  #else
47
      TCCR0B = _BV(CS02); //clk/256 == 122 interrupts/sek
48
      //TCCR0B = _BV(CS00);
49
      TIMSK0 = _BV(TOIE0);
50
  #endif
51
52
53
  sei();
54
  
55
  while(1) {   asm volatile ("nop");   }
56
}

Passt das so? DDRx-Register sind angepasst und werden nur einmal 
gesetzt, Rest Andreas' Lösung nachempfunden.

von Andreas K. (ergoproxy)


Lesenswert?

Ich setz die auf Input, dann ist die LED auch aus, da der 
Innenwiderstand eines PINs eigendlich hoch genug sein müsste, dass da 
nix leuchtet. Außerdem setze ich doch jede die an sein muss auf output. 
Glaub ich zumindest ich hab das recht schnell geschrieben und da es für 
die falsche LED Richtung war auch nochmal schnell umgeschrieben ^.^
Aber eigendlich müsste es so gehen.

von ich (Gast)


Lesenswert?

PORTB |= (1<<zeilennummer); //nur aktive Zeile auf LOW setzen

Muss so aussehen:

PORTB &= ~(1<<zeilennummer); //nur aktive Zeile auf LOW setzen

von Gast (Gast)


Lesenswert?

Hallo,

ich nochmal. Die Ansteuerung funktioniert nach ein wenig Bastelei nun, 
ich kann beliebige Patterns auf der Matrix ausgeben.
1
#define         F_CPU 16000000  
2
3
//#define MEGA16  // Target
4
#define MEGA168   // Arduino-Testboard
5
6
#include <inttypes.h>
7
#include <avr/io.h>
8
#include <avr/interrupt.h>
9
#include <compat/deprecated.h>
10
#include <util/delay.h>
11
12
//volatile uint8_t zeile[3] = {0b00011000,0b00100000,0b00111000}; 
13
//Jede LED 1BIT, ist effizienter als 8Bit für 1 LED
14
volatile uint8_t zeile[3][3] = { { 0, 1, 0, }, \
15
                                 { 1, 0, 1, }, \
16
                           { 0, 1, 0 } };
17
volatile uint8_t zeilennummer = 0;
18
19
20
21
ISR(TIMER0_OVF_vect) {
22
23
  //setzt alles auf Ursprung
24
  PORTD = 0x00;
25
  PORTB = 0xFF; //Zeilen standardmäßig auf HIGH, damit nix anderes blinkt
26
  
27
  PORTB &= ~(1<<zeilennummer); //nur aktive Zeile auf LOW setzen
28
  uint8_t i=0; // Definition direkt in der for-Schleife will mein Compiler nicht
29
  for(i;i<3;i++) //Geht jede LED durch
30
  {
31
  //if(zeile[zeilennummer] | (PORTD |=(1<<i))) 
32
//ist das Bit in der Variable gesetzt, 
33
//wird die LED eingeschaltet
34
  if(zeile[zeilennummer][i] == 1) 
35
        {
36
     PORTD |= (1<<i); 
37
  }
38
  }
39
  zeilennummer++; //nächste Zeile
40
  if(zeilennummer == 3) zeilennummer = 0; 
41
/ist die Var. größer als die Zeilenanzahl wird sie zurückgesetzt
42
}
43
44
int main (void) {
45
  
46
  DDRD = 0b00000111;
47
  DDRB = 0b00000111;
48
49
50
  #ifdef MEGA16 
51
      TCCR0 = _BV(CS02); //clk/256 == 122 interrupts/sek
52
      timer_enable_int (_BV(TOIE0));
53
  #else
54
       //clk/256 == 122 interrupts/sek
55
      TCCR0B = 0b00000100;
56
      TIMSK0 = _BV(TOIE0);
57
58
  #endif
59
60
  sei();
61
  
62
  while(1) {   asm volatile ("nop");   }
63
64
  return 0;
65
 }

Folgende Zeile im Code von Andreas war problematisch:
1
if(zeile[zeilennummer] & (1<<i))

Erstmal von mir wie folgt erweitert:
1
  
2
if(zeile[zeilennummer] & (PORTD |=(1<<i)))

(Oder funktioniert auch die erste Version? Imo eher nicht, im Debugger 
siehts auch eher nicht danach aus.)
Nun wurde hier ja einfach der Inhalt des pattern-Arrays mit den Bits, 
die ich über die Schleife gesetzt hab, UND-Verknüpft.
1
Beispiel:
2
pattern Zeile 1:                                00000101
3
PORTD (nach letztem/drittem Schleifendurchlauf: 00000111
4
                                 UND-Verknüpft: 11111101

Wegen der nicht genutzten fünf hohen Bits wird also zwangsläufig etwas 
!= 0 rauskommen, sodass die relevanten Bits von PORTD alle HIGH gesetzt 
werden (was dann eh nicht mehr notwendig wäre, weil das ja schon im 
Schleifenkopf passiert ist - aber das könnte man ja fixen).

Hab ich da was falsch, oder kann das so einfach nicht funktionieren?
Im selben Zug hab ich mich auch von dem eindimensionalen Array getrennt. 
Den mehrdimensionalen kann ich einfach durchiterieren, und schauen, ob 
ein bit gesetzt ist, oder nicht. Oder gibts auch eine elegante 
Möglichkeit, innerhalb weniger Takt den Status einzelner Bits im eindim. 
Array zu ermitteln?

Letzte Frage: Ists überhaupt ok, dass alles im Interrups abzuarbeiten? 
Bei 16MHz und einem Prescaler von 256 (1024 fängt dann schon arg an zu 
flimmern) hab ich ja nur 244 Takte, bis der nächste Interrups 
aufschlägt, und ich fertig sein sollte. Aktuell verbrate ich im 
Interrups schon 100 davon.
Könnte problematisch werden, wenn die Matrix größer wird, oder?
Oder sollte ich die for-Schleife einfach nach main packen? dann bräuchte 
ich ja aber auch wieder eine Art Synchronisierung mit der ISR, damit mir 
die Vorberechnung in main nicht davon läuft? Oder gleich einen zweiten 
Timer hernehmen?

Schönen Abend noch.

von Andreas K. (ergoproxy)


Lesenswert?

Ich verstehe leider nicht, was an dieser Zeile für den Compiler ein 
Problem sein soll:
1
if(zeile[zeilennummer] & (1<<i))

Die Funktion der Zeile ist ja ganz einfach: Es wird überprüft ob an 
einer bestimmten Stelle (i) der Variable (zeile[zeilennummer]) eine 1 
steht oder nicht.

 (0b00001000 & (1<<3)) = false
 (0b00001000 & (1<<4)) = true

Mit dem Compiler den ich hab geht das eigendlich. Ich benutz das auch 
sehr oft ^.^ habe es sogar mal vor etwa 2 Jahren hier im Forum erfragt 
wie man einzelne Bits maskiert und abfragt, um halt nicht für An und 
Auszustände jeweils 8Bit zu opfern.

Das was du dann genommen hast mit PORTD usw gehört da nicht hin.
Wenn du halt genügend Ram hast kannst du auch pro LED ein eigenen Char 
nehmen. Ich find halt nur es ist Recourcenverschwendung.

Ab einer gewissen Größe wird es nicht mehr möglich sein die Matrix ohne 
flackern anzusteuern.

Gruß ErgoProxy

von Gast (Gast)


Lesenswert?

> Mit dem Compiler den ich hab geht das eigendlich.

Knirsch. Mit meinem nun auch, keine Ahnung, was ich vorher verbockt 
hatte.
Muss irgendwo beim Debuggen der Kleinigkeiten (in deinem Code oben waren 
1-2 kleinere Fehler("zeilennummer = 4") untergegangen sein. Trotzdem 
danke.

> (0b00001000 & (1<<3)) = false
> (0b00001000 & (1<<4)) = true

Danke, da hatte ich was missverstanden.

> if(zeile[zeilennummer] & (1<<i))

Dazu hätte ich aber noch eine Frage:
Wo landen die Bits, die ich da setze?

von Andreas K. (ergoproxy)


Lesenswert?

Da werden keine Bits gesetzt, da wird nur geschaut ob das Bit an dieser 
Stelle gesetzt ist. Sry wegen der Fehler ich hab das in nem normalen 
Texteditor abgetippt ^^ da schleichen sich leider manchmal Fehler ein.

Gruß ErgoProxy

von Noknowhow (Gast)


Lesenswert?

@ergoproxy: was muss man den anders machen bzw. ändern um auch größere 
Matrixen flackerfrei softwaremäßig anzusteuern? Bin für Tips dankbar:)

Kennt jemand gute Codebeispiele?

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.