21x11 LED Matrix WS 2812B Arduino Nano

Hallo Zusammen!

Ich baue zur Zeit einen BeerPong Tisch, dessen zentrales Element eine 21x11 große LED Matrix werden soll. Hier soll der aktuelle Spielstand und andere Animationseffekte angezeigt werden.

Zur Zeit hapert es an der Ansteuerung der LED Matrix. Ich verwende derzeit die FastLED lib. Regenbogen- und Demo-Effekte sind mit der Demo kein Problem; Heißt LED sind richtig verdrahtet und alles läuft Hardware-seitig. Bin leider sehr unerfahren mit der Arduino IDE und benötige nun Hilfe beim realisieren der Anzeige des Spielstands.

Im Anhang noch ein Entwurf für Buchstaben in diesem Fall. Wie lege ich die Muster für die Buchstaben am besten an um sie dann mit einfachen Methoden abrufen zu können? Hat da jemand Erfahrung bzw. eine geeignete Hilfestellung?

Für Hinweise bin ich sehr dankbar

cheers

Du möchtest also einen monochromen(?) Zeichengenerator programmieren, noch dazu mit Zeichen variabler Breite?
Mit diesen Stichworten kannst Du schon mal nach Beispielen im Forum oder Web suchen.

Dann reicht ein Byte für die Pixel in einer Zeile, oder auch in einer Spalte. Eine Anordnung nach Zeilen läßt sich einfacher hinschreiben, die Pixel (= Bits) stehen dann auch im Code untereinander. Das A würde dann etwa so aussehen:

const byte A[7] = {
 0b00000,
 0b01110,
 0b01010,
 0b01110,
 0b01010,
 0b01010,
 0b00000
}

Die oberste und unterste Zeile können auch weggelassen werden, wenn sie immer 0 sind.
Diese Zeichen als Array abspeichern, damit man über den Index (ASCII-Wert?) des Zeichens auf die zugehörigen Bytes zugreifen kann. Auch da können die unbenutzten Zeichen weggelassen werden, man muß dann jeden Zeichencode in den zugehörigen Array-Index umrechnen.

Dann brauchst Du eine Funktion, welche die Bits eines Zeichens in die LED-Matrix schreibt, an eine vorgegebene Position und mit frei wählbarer oder fest eingestellter Farbe für Vordergrund (Bit gesetzt) und Hintergrund (Bit gelöscht).

Bei variabler Zeichenbreite mußt Du noch wissen (speichern), wie breit jedes Zeichen ist, damit die richtige Anzahl Bits ausgegeben werden kann, und die Position für das nächste Zeichen entsprechend erhöht werden kann.

Ganz schön viel Arbeit kommt da auf Dich zu :wink:

DrDiettrich:
Ganz schön viel Arbeit kommt da auf Dich zu :wink:

Ja damit habe ich schon gerechnet :slight_smile: Das ist ok.

Ich werde dann im Laufe der nächsten Tage ein wenig coden und das Ergebnis hier vorstellen.

Wenn noch jemand weitere Hilfestellung geben kann (Tutorials, Videos, Snippets, Projekte) gerne immer her damit!

So, ich bin jetzt schonmal einen Schritt weiter. Habe folgende Basis gefunden und möchte darauf aufbauen:
https://forum.arduino.cc/index.php?topic=411117.0
Das Problem an dieser Umsetzung ist, dass eine 20x15 = 300LED Matrix verwendet wird. Mein Gedanke: 300 > 255 also kann ich keine uint8_t Variablen verwenden. Muss also alles ummodeln. Das an sich bekomme ich locker hin, vorausgesetzt ich verstehe das gesamte Script. Das ist leider noch nicht der Fall :smiley:

Für Testzwecke habe ich mir eine 3x3 Matrix aufgebaut. Diese funktioniert Hardware-seitig ohne Probleme. Auch das setzen einzelner Pixel und ganzer Zeilen / Spalten habe ich verstanden. Dies allerdings nur mit einer festen Schleife, nicht via lesen aus der font Datei.
Genau da hapert es zur Zeit.
Ich habe mir mal eine eigene pattern.h zu Testzwecken angelegt.

/* pattern.h
 *
 * uTFT Font library
 * http://www.henningkarlsen.com/electronics/r_fonts.php
 * 
 */

#ifndef  __FONT_H
#define  __FONT_H
const unsigned char pattern[12] =
{
	0x00,0x00,0x00, // <Space>
	0x07,0x05,0x07, // circle
	0x05,0x05,0x05, // outer lines
        0x04,0x02,0x01, // diagonal line
};
#endif

Das Problem ist gerade, dass ich es nicht schaffe, die hex code in bits umzuwandeln um ihn dann bitweise auslesen.

also:

0x05 = 0101

um anschließend in einer Schleife die bits durchzugehen. Wenn bit gesetzt, dann LED AN, wenn nicht gesetzt LED AUS.
Habe mir schon Bitweise Operatoren angeschaut, verstehe aber leider nichts. Brauche also Hilfe bei dieser IF-Abfrage.

patternRowValue = 0x05;

for(uint8_t y = 0; y < 3; y++){
    if( "bit an der Stelle y von patternRowValue == 1"){
        leds[y] = ON;
    }
    else{
        leds[y] = OFF;
    }
}

die if Abfrage bekomm ich einfach nicht auf die Kette.
Zum Abschluss jetzt das vollständige Script inklusive der Stelle, da der ich zur Zeit hänge.

/********** basic includes and defines for LED Strip *********/
#include <FastLED.h>
#define LED_PIN 3
#define COLOR_ORDER GRB
#define CHIPSET WS2812B
#define BRIGHTNESS 64


/********** Defines for the Matrix *************/

const uint8_t xMatrix = 3;
const uint8_t yMatrix = 3;

#define NUM_LEDS (xMatrix * yMatrix)

const bool kMatrixSerpentineLayout = true;

/********** Set 'kMatrixSerpentineLayout' to true if your pixels are ******/
// laid out back-and-forth, like this:
//
//     0 >  1 >  2 >  3 >  4
//                         |
//                         |
//     9 <  8 <  7 <  6 <  5
//     |
//     |
//    10 > 11 > 12 > 13 > 14
//                        |
//                        |
//    19 < 18 < 17 < 16 < 15
//
//
// Helper functions for an two-dimensional XY matrix of pixels.
// Simple 2-D demo code is included as well.
//
//     XY(x,y) takes x and y coordinates and returns an LED index number,
//             for use like this:  leds[ XY(x,y) ] == CRGB::Red;
//             No error checking is performed on the ranges of x and y.
//
//     XYsafe(x,y) takes x and y coordinates and returns an LED index number,
//             for use like this:  leds[ XY(x,y) ] == CRGB::Red;
//             Error checking IS performed on the ranges of x and y, and an
//             index of "-1" is returned.  Special instructions below
//             explain how to use this without having to do your own error
//             checking every time you use this function.  
//             This is a slightly more advanced technique, and 
//             it REQUIRES SPECIAL ADDITIONAL setup, described below.

uint16_t XY( uint8_t x, uint8_t y)
// method to get actual led number out 
{
  uint16_t i;
  
  if( kMatrixSerpentineLayout == false) {
    i = (y * xMatrix) + x;
  }

  if( kMatrixSerpentineLayout == true) {
    if( y & 0x01) {
      // Odd rows run backwards
      uint8_t reverseX = (xMatrix - 1) - x;
      i = (y * xMatrix) + reverseX;
    } else {
      // Even rows run forwards
      i = (y * xMatrix) + x;
    }
  }
  
  return i;
}

CRGB leds_plus_safety_pixel[ NUM_LEDS + 1];
CRGB* leds( leds_plus_safety_pixel + 1);

uint16_t XYsafe( uint8_t x, uint8_t y)
// method to get SAFE LED number; prevents overflow
{
  if( x >= xMatrix) return -1;
  if( y >= yMatrix) return -1;
  return XY(x,y);
}

/********** methods for generell LED operations ************/
// 
// initPixels        sets up the Strip with chipset, output pin, color order and brightness
// 
// clearTablePixels  gives all LED state Black == off
//
// showPixels        same as FastLED.show(); actually send the set states to the strip
//
// setPixelRGB       sets n'th LED to set hex code color
//
// setPixelHSV	     sets n'th LED to set hue degree (0-255)
//
// setMatrixPixel    sets x,y coordinate of matrix to hue color; full saturation brightness


void initPixels()
// initialize Strip; setup brightness
{
  FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalSMD5050);
  FastLED.setBrightness( BRIGHTNESS );
}


void clearTablePixels()
// run through all LED and give them state BLACK  
{
    for(uint8_t i = 0; i < NUM_LEDS; i++){
      leds[i] = CRGB::Black;
    }
}

void showPixels()
{ 
   FastLED.show(); 
} 

void setPixelRGB(int n, int color)
// set n'th LED to hex color
{ 
    leds[n] = CRGB(color); 
} 


void setPixelHSV(int n, uint8_t hue)
// set n'th LED to hue degree; 1byte
{
    leds[n] = CHSV(hue);
}

void setMatrixPixel(uint8_t x, uint8_t y, uint8_t color)
// sets x,y coordinate of matrix to hue color; full brightness/saturation
{
    leds[ XYsafe(x,y) ] = CHSV(Hue, 255, 255);
}


/********** pattern helper methods ***********/
#include "pattern.h"
uint8_t charBuffer[3][3];

void printPattern(uint8_t patternNumber, uint8_t hue)
// llops over x coloums and y rows and sets matrix pixel to states red from pattern.h
{
	clearTablePixels();
	// clear all pixels
	
	uint8_t patternIndex = patternNumber*3;
	// get first row number (hex value) of selected pattern
	// for example: patternNumber = 2 -> actual pattern starts in pattern array (pattern.h)
	// at index 6 -> patternIndex = 6
	
	patternRowValue = pattern[patternIndex];
	// get the hex value of the row of the patternIndex

/********** AB HIER HABE ICH DAS BESCHRIEBENE PROBLEM  *******/
	setMatrixPixel(xPos, yPos, hue);


}



/********** SETUP **************/
void setup(){
	initPixels();
	clearTablePixels();
	showPixels();
	delay(2000);
	// delay for 2 sec for optical reasons, can be removed later on
}



/********** Main loop **********/
void loop(){
	printPattern(2, 96);
	// print pattern with index 2 --> vertical outer lines
	// hue color 96 == green
	delay(100);
	// update every 100 

}

Und nochmal das FastLED wiki falls dazu Fragen sind:

Ich würde ein zweidimensionales Array für die LEDs und den Zeichengenerator nehmen. Dann hat man einen einfachen Zusammenhang zwischen Zeilen und Spalten.

Die Muster sind binär gespeichert, da braucht man keine Umrechnung zwischen hex und binär Darstellung. Zum Abfragen eines Bits gibt es z.B. bitRead(), oder zu Fuß bit = (var & (1<<bitNr)) != 0;

Hallo DrDiettrich,

erstmal vielen Dank für deine schnelle Antwort!

die "zu Fuß" Variante habe ich schon öfter gesehen, ich verstehe die Funktionsweise aber nicht. Da liegt mein Hauptproblem.

Ich fürchte, die BitRead(x, n) Methode sollte gut funktionieren, ich weiß aber nicht wie sich das auf die Performance der Matrix auswirkt... Darum nehme ich lieber die vermeintlich schnellere denke ich.

Außerdem möchte ich wissen, wie du das mit dem zweidimensionalen Array meinst?

Meinst du, ich sollte die pattern.h schon als 2D Array anlegen? Kann dir da nicht so ganz folgen :frowning:

Versuche mal, Dich mit dem Dualsystem und booleschen Operatoren vertraut zu machen. Mit dem Dualsystem kann man rechnen wie im Dezimalsystem, nur daß die Stellen nicht die Werte 10^x haben, sondern 2^x, und die Ziffern nicht von 0-9 reichen, sondern nur von 0-1. Dann bekommt man den Wert einer Zahl, indem man ihre Ziffern mit ihrem Stellenwert multipliziert, und das ganze zusammenzählt. Damit ist 0x05 (hex) gleich 0b0101 (binär) und gleich 5 (dezimal).

Für die Verschiebung um 1 Stelle gibt es in C die Operatoren << und >>, die einen Wert um n Stellen verschieben, also mit 2^n multiplizieren oder dadurch dividieren. Dann wird folgendes Verfahren vielleicht einfacher zu verstehen:

bit = (Wert >> BitNr) & 1;

wobei & die logische UND Verknüpfung ist, hier also das Bit an der niedrigsten Stelle der verschobenen Zahl liefert.

Dein pattern[12] sollte besser als pattern[4][3] aufgebaut sein, mit dem ersten Index für das Muster und dem zweiten als Zeilenindex im Muster. Dann bekommst Du mit pattern[muster] einen Pointer auf das erste Byte des Musters.

Die eigentliche Ausgabe wird wirklich kompliziert, da man nach jeder Zeile des Musters eine neue LED Nummer safeXY(spalte,zeile) berechnen muß. Am verständlichsten wären zwei verschachtelte Schleifen mit Zeilen- und Spaltenindex, ab dem Pixel (x0, y0) in der LED Matrix:

for (int z = 0; z<Zeichenhöhe; z++) {
   for (int s = 0; s<Zeichenbreite; s++) {
     bool bit = ((pattern[muster][z]) >> s) & 1;
     if (bit) setMatrixPixel(x0+s, y0+z, hell);
     //else dunkel
   }
 }

Der "Bildspeicher braucht schon mal 693 Byte von den 2048 zur Verfügung stehenden.
Ich würde Dir raten vom NANO zum Teensy3 zu wechseln.
Grüße Uwe

Monochrom wird nur 1 Bit pro LED benötigt, bei mehr Farben dürften die LED ihre eigenen Farbgeneratoren enthalten, womit auf einen Bildwiederholspeicher komplett verzichtet werden kann.

Zeichen variabler Breite vergiss am besten gleich. Mach alle Buchstaben gleich breit.

Das Problem mit den schmalen Buchstaben oder Zeichen kann man einfach lösen. Du legst einen C String dieser schmalen Buchstaben an. Das sind nur eine handvoll wie das kleine "L", beide "I"s, oder das Ausrufezeichen. Dann fragst du bei jedem Zeichen mit strchr() ab ob das Zeichen in diesem Array ist. Wenn ja, lässt du einfach die überflüssigen leeren Spalten zu beiden Seiten des Zeichens weg.

Das lässt sich auch später implementieren wenn der Rest erst mal geht. Kann man als Optimierung des Schriftbilds sehen.

Super!
Vielen Dank für die tolle Hilfe! Ich stehe ja noch ganz am Anfang meiner Arduino Karriere :slight_smile:

Ich habe Dank der Hilfe die ersten Pattern anzeigen lassen können. Jetzt da ich das mit dem Bitweisen Operatoren zumindest ansatzweise verstanden habe, werde ich die Komplexität ein wenig erhöhen.
Alles auf einen Schwung ist für learning by doing einfach unmöglich.
Als nächstes kommt dann die zweidimensionale Matrix. Das hört sich sehr sinnvoll an. Besser als dieses "Index finden" mit

Index = patternNumber*3;

Zeichen variabler Breite werde ich gar nicht verwenden. Für meine Anwendung ist das gar nicht wichtig.
Eine spätere Implementierung ist dann ja immer noch möglich.

Das mit dem Bildwiederholspeicher und der Auslastung checke ich auch nicht so ganz.

Ich werde den Thread auch weiter verwenden und weitere Änderungen gerne mit euch teilen!
Ich hoffe, dass dann auch weiterhin so gute Hilfestellung geleistet wird.
Bis später,

cheers!

P.S.: Hier noch das Update mit dem 2D-Array

void printPattern(uint8_t patternNumber,uint8_t xOffset, uint8_t yOffset, uint8_t hue)
// loops over x coloums and y rows and sets matrix pixel to states red from pattern.h
{
	clearTablePixels();
	// clear all pixels
	
	
	for(uint8_t y = 0; y < 3; y++)
	// go from x row to row 
	{
		for(uint_8 x = 0; x < 3; x++)
		// run through all coloums and compare the bitvalue of 
		// the patternRowValue bit with true. If true, set LED ON
		{
			bool bit = ((pattern[patternNumber][y]) >> x) & 1;
			if(bit){
				setMatrixPixel(xOffset+x, yOffset+y, hue);
			}
			// else not set
		}
	}
}

und die pattern.h

/* pattern.h
 *
 * uTFT Font library
 * http://www.henningkarlsen.com/electronics/r_fonts.php
 * 
 */

#ifndef  __FONT_H
#define  __FONT_H
const unsigned char pattern[12] =
{
	{0x00,0x00,0x00}, // <Space>
	{0x07,0x05,0x07}, // circle
	{0x05,0x05,0x05}, // outer lines
        {0x04,0x02,0x01}, // diagonal line
};
#endif

Nach einigem Nachdenken über den Bildspeicher sieht es wohl so aus, daß der in der Bibliothek implementiert wird. Notwendig ist er deshalb, weil bei den Strips immer alle Daten rausgeschoben werden müssen, auch wenn nur eine einzige LED geändert wird. Wieviel Speicher übrig bleibt, siehst Du ja bei jeder Compilierung.

Und die Muster müssen natürlich ins Flash (PROGMEM).

Ah ok, verstanden.
Beim Compilieren gestern Abend (kann das ganze gescripte leider immer erst zu Hause testen) wurde mir gesagt, dass beide Speicher nicht besonders belastet sind (~15%).

Habe jetzt noch die Offset funktionalität hinzugefügt:
// Ich bin furchtbar schlecht darin ordentliche Namen für meine Variablen zu finden :confused:

void printPattern(uint8_t patternNumber,uint8_t xOffset, uint8_t yOffset, uint8_t hue)
// loops over coloums and rows and sets matrix pixel to states from pattern.h
{
	clearTablePixels();
	// clear all pixels
	
	
	for(uint8_t hp = 0; hp < 3; hp++)
	// go through rows; number depending on hp 'height of pattern'
	{
		for(uint_8 wp = 0; wp < 3; wp++)
		// run through coloums and compare the bitvalue of 
		// the pattern with Index 'patternNumber' at pos 'wp' bit with true. If true, set LED ON
		// wp stands for 'width of pattern'
		{
			bool bit = ((pattern[patternNumber][hp]) >> wp) & 1;
			if(bit){
				setMatrixPixel(xOffset+wp, yOffset+hp, hue);
			}
			// else not set
		}
	}
}

Hallo Zusammen.

Ich bin wieder einmal an die Grenzen meiner Programmierkünste gestoßen.
Ich möchte in meiner nxm Matrix einen Array mit allen Randpixeln erstellen.
Ziel ist, den Array anschließend durchzulaufen und eine LED nach der anderen anzuschalten.
Mit einem hardgescriptetem Array wie bspw. dieser klappt das:

uint8_t tmpArray[28] = {0,1,2,3,4,5,6,7,8,23,24,39,40,55,56,57,58,59,60,61,62,63,48,47,32,31,16,15};

Grund für diese seltsame Nummerierung ist das Serpentinen Layout des Strips.

Ich habe eine Schleife geschrieben um den Array zu initialisieren, diese hängt sich aber irgendwie immer auf. Ich finde den Fehler einfach nicht:

const uint8_t boarderPixelCount = 28;

void initBoarderArray()
// initialise the boarder LEDS clockwise as own Array for easier acess
{
	uint8_t tmpArray[boarderPixelCount];	
	
	uint8_t xPos = 0;
	uint8_t yPos = 0;
	// Start Position 0,0	

	uint8_t xAction = 1;
	uint8_t yAction = 0;
	// 1 = run right
	// 2 = run left
	// 0 = pause
	
	if(xAction == 1 && yAction == 0){
		for(uint8_t i = 0; i < xMatrix; i++){
			tmpArray[i] = XYsafe(i, yPos);
		}
		xAction = 0;
		yAction = 1;
		xPos = xMatrix-1;
	}

	if(xAction == 0 && yAction == 1){
		for(uint8_t i = 0; i < yMatrix; i++){
			tmpArray[(xMatrix-1+i)] = XYsafe(xPos, i);
		}
		xAction = 2;
		yAction = 0;
		yPos = yMatrix-1;
	}
	if(xAction == 2 && yAction == 0){
		for(uint8_t i = (xMatrix-1); i >= 0; i--){
			tmpArray[(xMatrix-1+yMatrix-1)] = XYsafe(i, yPos);
		}
		xAction = 0;
		yAction = 2;
		xPos = 0;
	}
	if(xAction == 0 && yAction == 2){
		for(uint8_t i = (yMatrix-1); i > 0; i--){
			tmpArray[(xMatrix-1+yMatrix-1+xMatrix-1)] = XYsafe(xPos, i);
		}
		yAction = 0;
		xAction = 0;
		yPos = 0;
	}
	return tmpArray;
}

//     XYsafe(x,y) takes x and y coordinates and returns an LED index number,
//             for use like this:  leds[ XY(x,y) ] == CRGB::Red;
//             Error checking IS performed on the ranges of x and y, and an
//             index of "-1" is returned.  Special instructions below
//             explain how to use this without having to do your own error
//             checking every time you use this function.  
//             This is a slightly more advanced technique, and 
//             it REQUIRES SPECIAL ADDITIONAL setup, described below.

Vielleicht findet ja einer von euch den Grund, warum die Funktion dazu führt, dass der Nano sich aufhängt.

Das tmpArray darf keine lokale Variable sein, die geht nach dem Verlassen der Funktion verloren.

Entweder global machen, oder als Pointer an die Funktion übergeben.

Oh, ja klar!
Ich werde dann das hier heute Abend mal testen :slight_smile:

const uint8_t boarderPixelCount = 28;

uint8_t tmpArray[boarderPixelCount];	

void initBoarderArray()
// initialise the boarder LEDS clockwise as own Array for easier acess
{
	
	uint8_t xPos = 0;
	uint8_t yPos = 0;
	// Start Position 0,0	

	uint8_t xAction = 1;
	uint8_t yAction = 0;
	// 1 = run right
	// 2 = run left
	// 0 = pause
	
	if(xAction == 1 && yAction == 0){
		for(uint8_t i = 0; i < xMatrix; i++){
			tmpArray[i] = XYsafe(i, yPos);
		}
		xAction = 0;
		yAction = 1;
		xPos = xMatrix-1;
	}

	if(xAction == 0 && yAction == 1){
		for(uint8_t i = 0; i < yMatrix; i++){
			tmpArray[(xMatrix-1+i)] = XYsafe(xPos, i);
		}
		xAction = 2;
		yAction = 0;
		yPos = yMatrix-1;
	}
	if(xAction == 2 && yAction == 0){
		for(uint8_t i = (xMatrix-1); i >= 0; i--){
			tmpArray[(xMatrix-1+yMatrix-1)] = XYsafe(i, yPos);
		}
		xAction = 0;
		yAction = 2;
		xPos = 0;
	}
	if(xAction == 0 && yAction == 2){
		for(uint8_t i = (yMatrix-1); i > 0; i--){
			tmpArray[(xMatrix-1+yMatrix-1+xMatrix-1)] = XYsafe(xPos, i);
		}
		yAction = 0;
		xAction = 0;
		yPos = 0;
	}
}

//     XYsafe(x,y) takes x and y coordinates and returns an LED index number,
//             for use like this:  leds[ XY(x,y) ] == CRGB::Red;
//             Error checking IS performed on the ranges of x and y, and an
//             index of "-1" is returned.  Special instructions below
//             explain how to use this without having to do your own error
//             checking every time you use this function.  
//             This is a slightly more advanced technique, and 
//             it REQUIRES SPECIAL ADDITIONAL setup, described below.
 }
  if(xAction == 2 && yAction == 0){
   for(uint8_t i = (xMatrix-1); i >= 0; i--){
    tmpArray[(xMatrix-1+yMatrix-1+i)] = XYsafe(i, yPos);
   }
   xAction = 0;
   yAction = 2;
   xPos = 0;
  }

Guten Morgen zusammen.

Ich hab dank des debugging mit Serial.print(); den Fehler gefunden.
Er versteckt sich in dieser Schleife oben:

uint8_t hat einen Definitionsbereich von 0-255. Die Anweisung läuft solange i >= 0 ist.
Da i aber nur Werte >= 0 annehmen kann habe ich mir eine schöne versteckte Dauerschleife gebastelt.