mikrocontroller.net

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


Autor: Gast (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht 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:
#define         F_CPU 8000000  

//#define MEGA16  // Target
#define MEGA168   // Arduino-Testboard

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <compat/deprecated.h>
#include <util/delay.h>
                                      //   PB0 LOW     PB1 LOW    PB2 LOW
volatile const uint8_t rowSelect[3] = { 0b11111110, 0b11111101, 0b11111011}; 
/*über iRowCnt als Index werden im Interrupt die Bitmuster für eine jeweils  aktive Zeile auf PORTB geschrieben */
uint8_t pattern[3][3] = { { 0, 1, 1 }, \
                          { 1, 0, 1 }, \
                          { 1, 1, 0 } };
int iRowCnt = 0;
int cnt001 = 0;
ISR(TIMER0_OVF_vect) { 
  PORTD = 0x00;
  PORTB = rowSelect[iRowCnt % 3]; //s.o.

  iRowCnt++;
  int cnt002 = 0;
  for(cnt002; cnt002 < 3; cnt002++) {
    if(pattern[cnt001 % 3][cnt002] == 1) { 
      
      PORTD |= (1 << (cnt002)); //wenn '1' im pattern-Array, Bit-# setzen
        }
        
      else 
        ;
      }

      
  cnt001++;

  
    
}

int main (void) {

    
  DDRB = 0x07; // ZEILEN  - 0b00000111
  DDRD = 0x3F; // SPALTEN - 0b00111111
  
  PORTB = 0xFF; 
  PORTD = 0x00;
  
  #ifdef MEGA16 
      TCCR0 = _BV(CS02); //clk/256 == 122 interrupts/sek
      timer_enable_int (_BV(TOIE0));
  #else
      TCCR0B = _BV(CS02); //clk/256 == 122 interrupts/sek
      //TCCR0B = _BV(CS00);
      TIMSK0 = _BV(TOIE0);
  #endif
    
  sei();

  while(1) {
    asm volatile ("nop");
        }

  return (0);
}

                

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!

Autor: Andreas K. (ergoproxy)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Gast (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Andreas K. (ergoproxy)
Datum:

Bewertung
0 lesenswert
nicht 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
#define         F_CPU 8000000  

//#define MEGA16  // Target
#define MEGA168   // Arduino-Testboard

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <compat/deprecated.h>
#include <util/delay.h>

volatile uint8_t zeile[3] = {0b00000110,0b00000101,0b00000011}; //Jede LED 1BIT, ist effizienter als 8Bit für 1 LED
volatile uint8_t zeilennummer = 0;



ISR(TIMER0_OVF_vect) {

  //setzt alles auf Ursprung
  PORTD = 0x00;
  DDRB = 0x00;
  PORTB = 0x00;
  
  PORTD |= (1<<zeilennummer); //Spalte auf high
  
  for(uint8_t i=0;i<3;i++) //Geht jede LED durch
  {
  if(zeile[zeilennummer] & (1<<i)) //ist das Bit in der Variable gesetzt, wird die LED eingeschaltet
        {
     DDRB |= (1<<i); //Als output (low), Kathode LED auf - LED leuchtet
  }
  }
  zeilennummer++; //nächste Zeile

  if(zeilennummer = 4) zeilennummer = 0; //ist die Var. größer als die Zeilenanzahl wird sie zurückgesetzt
}

int main (void) {
  
  DDRD = 0b00000111;
  PORTD = 0x00;


  #ifdef MEGA16 
      TCCR0 = _BV(CS02); //clk/256 == 122 interrupts/sek
      timer_enable_int (_BV(TOIE0));
  #else
      TCCR0B = _BV(CS02); //clk/256 == 122 interrupts/sek
      //TCCR0B = _BV(CS00);
      TIMSK0 = _BV(TOIE0);
  #endif


  sei();
  
  while(1) {   asm volatile ("nop");   }
}

€: 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.

Autor: ich (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Gast (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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:
#define         F_CPU 8000000  

//#define MEGA16  // Target
#define MEGA168   // Arduino-Testboard

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <compat/deprecated.h>
#include <util/delay.h>

volatile uint8_t zeile[3] = {0b00000110,0b00000101,0b00000011}; //Jede LED 1BIT, ist effizienter als 8Bit für 1 LED
volatile uint8_t zeilennummer = 0;



ISR(TIMER0_OVF_vect) {

  //setzt alles auf Ursprung
  PORTD = 0x00;
  PORTB = 0xFF; //Zeilen standardmäßig auf HIGH, damit nix anderes blinkt
  
  PORTB |= (1<<zeilennummer); //nur aktive Zeile auf LOW setzen
  uint8_t i=0; // Definition direkt in der for-Schleife will mein Compiler nicht
  for(i;i<3;i++) //Geht jede LED durch
  {
  if(zeile[zeilennummer] & (1<<i)) //ist das Bit in der Variable gesetzt, wird die LED eingeschaltet
        {
     PORTD |= (1<<i); 
  }
  }
  zeilennummer++; //nächste Zeile

  if(zeilennummer = 4) zeilennummer = 0; //ist die Var. größer als die Zeilenanzahl wird sie zurückgesetzt
}

int main (void) {
  
  DDRB = 0b00000111;
  DDRD = 0b00000111;


  #ifdef MEGA16 
      TCCR0 = _BV(CS02); //clk/256 == 122 interrupts/sek
      timer_enable_int (_BV(TOIE0));
  #else
      TCCR0B = _BV(CS02); //clk/256 == 122 interrupts/sek
      //TCCR0B = _BV(CS00);
      TIMSK0 = _BV(TOIE0);
  #endif


  sei();
  
  while(1) {   asm volatile ("nop");   }
}

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

Autor: Andreas K. (ergoproxy)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: ich (Gast)
Datum:

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

Muss so aussehen:

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

Autor: Gast (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

ich nochmal. Die Ansteuerung funktioniert nach ein wenig Bastelei nun, 
ich kann beliebige Patterns auf der Matrix ausgeben.
#define         F_CPU 16000000  

//#define MEGA16  // Target
#define MEGA168   // Arduino-Testboard

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <compat/deprecated.h>
#include <util/delay.h>

//volatile uint8_t zeile[3] = {0b00011000,0b00100000,0b00111000}; 
//Jede LED 1BIT, ist effizienter als 8Bit für 1 LED
volatile uint8_t zeile[3][3] = { { 0, 1, 0, }, \
                                 { 1, 0, 1, }, \
                           { 0, 1, 0 } };
volatile uint8_t zeilennummer = 0;



ISR(TIMER0_OVF_vect) {

  //setzt alles auf Ursprung
  PORTD = 0x00;
  PORTB = 0xFF; //Zeilen standardmäßig auf HIGH, damit nix anderes blinkt
  
  PORTB &= ~(1<<zeilennummer); //nur aktive Zeile auf LOW setzen
  uint8_t i=0; // Definition direkt in der for-Schleife will mein Compiler nicht
  for(i;i<3;i++) //Geht jede LED durch
  {
  //if(zeile[zeilennummer] | (PORTD |=(1<<i))) 
//ist das Bit in der Variable gesetzt, 
//wird die LED eingeschaltet
  if(zeile[zeilennummer][i] == 1) 
        {
     PORTD |= (1<<i); 
  }
  }
  zeilennummer++; //nächste Zeile
  if(zeilennummer == 3) zeilennummer = 0; 
/ist die Var. größer als die Zeilenanzahl wird sie zurückgesetzt
}

int main (void) {
  
  DDRD = 0b00000111;
  DDRB = 0b00000111;


  #ifdef MEGA16 
      TCCR0 = _BV(CS02); //clk/256 == 122 interrupts/sek
      timer_enable_int (_BV(TOIE0));
  #else
       //clk/256 == 122 interrupts/sek
      TCCR0B = 0b00000100;
      TIMSK0 = _BV(TOIE0);

  #endif

  sei();
  
  while(1) {   asm volatile ("nop");   }

  return 0;
 }

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

Erstmal von mir wie folgt erweitert:
  
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.
Beispiel:
pattern Zeile 1:                                00000101
PORTD (nach letztem/drittem Schleifendurchlauf: 00000111
                                 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.

Autor: Andreas K. (ergoproxy)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich verstehe leider nicht, was an dieser Zeile für den Compiler ein 
Problem sein soll:
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

Autor: Gast (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Andreas K. (ergoproxy)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Noknowhow (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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?

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.