suche ringförmiges Array...

Hi,

ich spiele gerade mit 2801 Stripes am Arduino. Dabei verwende ich die Lib FastSPI_LED und der Bild- bzw. Linienspeicher ist ein 2dimensionales Array.
Ich habe verschiedene "analoge" Zellautomaten programmiert, was zu Plasmaähnlichen Effekten führt - es wird also, ausgehend von einem "Samenmuster" in Abhängigkeit von Nachbarpixeln das nächse "Bild" berechnet.
Dabei habe ich das Problem, dass die Effekte zum Anfang und Ende des Stripes hin auslaufen, was nicht schön ist und ständig neue Samen erfordert.
Gibt es einen Datentyp oder eine Technik, welches meinen 1dimensionalen Bildspeicher als Ring behandelt? Wo also das, was am Ende rausfällt wieder am Anfang ankommt? If Abfragen und im Kreis kopieren ist unpraktisch, weil ich teilweise auf weit entfernte Pixel Bezug nehme, damit das - wie eine Lavalampe - ewig mit sich selbst spielt.
Habe mal gegoogelt und bin bei std::list hängengeblieben, kennt der Compiler das? Habe es in Zusammenhang mit der Arduino IDE noch nie gesehen...

Irgendwelche Vorschläge?

MfG Helmuth

Ich habe verschiedene "analoge" Zellautomaten programmiert, was zu Plasmaähnlichen Effekten führt - es wird also, ausgehend von einem "Samenmuster" in Abhängigkeit von Nachbarpixeln das nächse "Bild" berechnet.

So wie "Conway's Game of Life"?

Dabei habe ich das Problem, dass die Effekte zum Anfang und Ende des Stripes hin auslaufen, was nicht schön ist und ständig neue Samen erfordert.
Gibt es einen Datentyp oder eine Technik, welches meinen 1dimensionalen Bildspeicher als Ring behandelt? Wo also das, was am Ende rausfällt wieder am Anfang ankommt?

Meinst du also, dass der Pixel am rechten Ende des Stripes, Nachbar des Pixels am linken Ende ist?

Wenn das so geimeint ist, brauchst du beim berechnen des neuen Bildes, nur eine if-Abfrage machen ob der Pixel am Rand ist oder nicht.
Falls er am Rand ist, sagst du, dass sein rechter/linker Nachbar der erste/letzte vom Stripe ist.
Oder stell ich mir das alles zu einfach vor?

Grüße,
J3RE

hi,

wenn Du kein speicherplatzproblem hast: ich hab' sowas vor jahrzehnten in turbopascal für eine dimension realisiert, indem ich pointer auf objekte verwendet habe. jedes objekt bestand aus dem wert selbst (können ja auch mehrere sein), und jeweils einem pointer auf das objekt davor und dahinter. in deinem fall könntest Du auch die pointer für darüber und darunter dazunehmen. dann ist es auch leicht, als "rechten nachbarn" das erste pixel in der reihe zu definieren, genauso für oben und unten.

gruß stefan

Ich sehe auch kein Problem bei den Enden den anderen Anfang zu nehmen. Es sind 2 if Abfragen.
Grüße Uwe

Hallo,

@J3RE: "So wie "Conway's Game of Life"?"
So ähnlich. Nicht nur Zelle tot oder lebendig, sondern 24 Bit...und komplexere Regeln, für rot, grün und blau verschieden. Und eindimensional, also alles in einer Zeile.

"Wenn das so geimeint ist, brauchst du beim berechnen des neuen Bildes, nur eine if-Abfrage machen ob der Pixel am Rand ist oder nicht."
Manchmal brauche ich auch die Info, was z.B. mit dem 10. Pixel vom Rand los ist, die Regeln beziehen sich nicht nur auf den direkten Nachbarn.

@Eisbaer:
Das klingt vielversprechend. Ich lese mich mal in die Materie ein, danke.
Momentan habe ich nur 25 LEDs, da geht der Speicher noch klar.

Grüße

Hi uwefed,

meinst Du so:

if (index<0) {index=index+25;}
if (index>24) {index=index-25;}

Das probier´ ich aus. Klingt einfach.

MfG

Helmuth

ja so oder ähnlich.
Ohne den gesamten Sketch kann ich dir aber nicht 100% bestätigen. Die Probleme liegen im Detail.
Grüße Uwe

Helmuth:
if (index<0) {index=index+25;}
if (index>24) {index=index-25;}

Ja, genau so.
Immer wenn Du "rechts neben das Ende" oder "links neben den Anfang" des Arrays greifen würdest, greifst Du stattdessen auf die entsprechende Stelle am anderen Ende des Arrays so, so dass sich beim Zugriff quasi ein geschlossener Ring ergibt.

Ich habe gerade auch mal einen sehr einfachen zellulären Automaten aufgesetzt, der berechnet zwar keine "plasmaähnlichen Effekte", sondern nur simple ein/aus Zustände, aber im Endeffekt dürfte es ziemlich dasselbe sein.

Ich habe zwei Zustandspuffer definiert, die immer im Wechsel mal als Quelle und mal als Ziel für die Berechnung des nächsten Schritts dienen.

Und eine Zugriffsfunktion, die bei Anforderung von Werten mit einem Index unter 0 oder oberhalb der Arraygrenze dann entsprechend Werte "vom anderen Ende des Arrays" zurückliefert.

// Zellulärer Automat by 'jurs' for German Arduino Forum

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
}


#define BUFLEN 61
byte celldata[2][BUFLEN]={
       {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
       {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};


byte cellAccess(int index, int bufindex)
{
  if (bufindex<0) bufindex+=BUFLEN; 
  if (bufindex>BUFLEN-1) bufindex-=BUFLEN; 
  return(celldata[index][bufindex]);
}


void Neuberechnen(byte source, byte dest)
// recalc data of cellular automat
{
  for (int i=0;i<BUFLEN;i++)
  {
    if (cellAccess(source,i-1)==1 && cellAccess(source,i)==1 && cellAccess(source,i+1)==1) celldata[dest][i]=0;
    else
    if (cellAccess(source,i-1)==1 && cellAccess(source,i)==1 && cellAccess(source,i+1)==0) celldata[dest][i]=0;
    else
    if (cellAccess(source,i-1)==1 && cellAccess(source,i)==0 && cellAccess(source,i+1)==1) celldata[dest][i]=0;
    else
    if (cellAccess(source,i-1)==1 && cellAccess(source,i)==0 && cellAccess(source,i+1)==0) celldata[dest][i]=1;
    else
    if (cellAccess(source,i-1)==0 && cellAccess(source,i)==1 && cellAccess(source,i+1)==1) celldata[dest][i]=1;
    else
    if (cellAccess(source,i-1)==0 && cellAccess(source,i)==1 && cellAccess(source,i+1)==0) celldata[dest][i]=1;
    else
    if (cellAccess(source,i-1)==0 && cellAccess(source,i)==0 && cellAccess(source,i+1)==1) celldata[dest][i]=1;
    else
    if (cellAccess(source,i-1)==0 && cellAccess(source,i)==0 && cellAccess(source,i+1)==0) celldata[dest][i]=0;
    else {Serial.print("Error ");Serial.println(i);} // this should never happen
  }  
}


void Zeigen(byte index)
// show data of cellular automat
{
  char output[BUFLEN+1];
  memset(output,0,sizeof(output));
  for (int i=0;i<BUFLEN;i++) 
    if (celldata[index][i]==1) output[i]='X'; else output[i]=' ';
  Serial.println(output);
}


void loop() {
  // put your main code here, to run repeatedly: 
  static boolean even;
  if (even)
  {
    Neuberechnen(0,1);
    Zeigen(1);
  }  
  else  
  {
    Neuberechnen(1,0);
    Zeigen(0);
  }  
  even=!even;
}

Sehr schön sieht man an der Berechnung: Ein einzelner gesetzter Startwert führt sehr schnell zu einem Gewimmel von Werten im Automaten.

Du berechnest wahrscheinlich irgendwie RGB-Werte in Deinem Automaten statt nur einfacher ein/aus Werte?

Was man bei solchen Automaten herausbekommt, ob die aktiven Zellen langfristig "Aussterben" oder einem anderen Endzustand zustreben, bei dem es keine oder nur noch minimale Variation im Ablauf gibt, das hängt natürlich vom Algorithmus und den Parametern ab.

uwefed:
Die Probleme liegen im Detail.
Grüße Uwe

Hi Uwe, ja - so ist es...

Ich habe den Code mal vereinfacht: simplere Regeln, welche sich nur auf die Nachbarpixel beziehen, die Interationsfunktion lässt den ersten und letzten Pixel weg.

#include <FastSPI_LED.h>

#define NUM_LEDS 25

struct CRGB { unsigned char g; unsigned char r; unsigned char b; };
struct CRGB *leds;
int ledsX[NUM_LEDS][3];

//-----Utilitys and functions--------------------------------------------------------------

// Set color of one LED, but don´t show yet
void set_color_led(int adex, int cred, int cgrn, int cblu) {  
  leds[adex].r = cred;
  leds[adex].g = cgrn;
  leds[adex].b = cblu;  
  }

// Fill and show all LEDs with one color
void one_color_all(int cred, int cgrn, int cblu) { 
    for(int i = 0 ; i < NUM_LEDS; i++ ) {
      set_color_led(i, cred, cgrn, cblu);
      FastSPI_LED.show(); 
    }  
}

// Switch all LEDs off
void clear_all(){
  for (int a=0; a<NUM_LEDS; a++) {set_color_led(a, 0,0,0);}
  FastSPI_LED.show();
}

// Buffer current content
void copy_led_array(){
  for(int i = 0; i < NUM_LEDS; i++ ) {
    ledsX[i][0] = leds[i].r;
    ledsX[i][1] = leds[i].g;
    ledsX[i][2] = leds[i].b;
  }  
}

// Random pixels to start with
void random_pixels(){
  for (int a=0; a<1; a++) {set_color_led(random(NUM_LEDS-2)+1, 0,0,random(255));};
  for (int a=0; a<1; a++) {set_color_led(random(NUM_LEDS-2)+1, 0,random(255),0);};
  for (int a=0; a<1; a++) {set_color_led(random(NUM_LEDS-2)+1, random(255),0,0);};
  FastSPI_LED.show();
}

// Here the magic happens ;-)
void play_with_them(){
 for (int b=0; b<300; b++) {  // 
   copy_led_array();
   for (int a=1; a<NUM_LEDS-1; a++) {
     set_color_led(a,
       constrain(int(ledsX[a][0]*0.9+ledsX[a+1][0]*0.2),0,255),
       constrain(int(ledsX[a][1]*0.9+ledsX[a-1][1]*0.2),0,255),
       constrain(int(ledsX[a][2]*0.9+ledsX[a+1][2]*0.2),0,255));
     }
     FastSPI_LED.show();
     delay(20);
   }
}

//-----Setup--------------------------------------------------------------------------
void setup()  
{
  FastSPI_LED.setLeds(NUM_LEDS);
  FastSPI_LED.setChipset(CFastSPI_LED::SPI_WS2801);  
  FastSPI_LED.setDataRate(7);      
  FastSPI_LED.init();
  FastSPI_LED.start();
  leds = (struct CRGB*)FastSPI_LED.getRGBData(); 

}

//-----Main loop-----------------------------------------------------------------------
void loop() {
  random_pixels();
  delay(1000);
  play_with_them();
  clear_all();
  delay(1000);
}

Das Ergebnis sieht soweit fein aus, die Pixel faden zur Seite, ziehen einen schönen Schweif hinter sich her und wenn die Schweife sich begegnen gibt es immer neue Farben...

Nun der Versuch, es im Kreis laufen zu lassen (Regel leicht geändert, damit nicht sofort alles weiss wird):

// Here the magic happens ;-)
void play_with_them(){
 for (int b=0; b<300; b++) {  // 
   copy_led_array();
   for (int a=0; a<NUM_LEDS; a++) {
     int before = a-1;
     int after = a+1;
     if (before<0) {before=NUM_LEDS-1;}
     if (after==NUM_LEDS) {after=0;}
     set_color_led(a,
       constrain(int(ledsX[a][0]*0.85+ledsX[after][0]*0.2),0,255),
       constrain(int(ledsX[a][1]*0.82+ledsX[before][1]*0.2),0,255),
       constrain(int(ledsX[a][2]*0.82+ledsX[before][2]*0.2),0,255));
     }
     FastSPI_LED.show();
     delay(20);
   }
}

Funktioniert soweit, sieht aber irgendwie unelegant aus (der Code)?!

Meintest Du in dieser Art?

Was ist aber, wenn ich von der aktuellen Position +/- 10 Stellen brauche? Alles zwischenspeichern? Und bei einen langen Stripe (150 Pixel)? Wie handelt das ein Profi?

Beste Grüße

Hi jurs,

jurs:
Du berechnest wahrscheinlich irgendwie RGB-Werte in Deinem Automaten statt nur einfacher ein/aus Werte?

Was man bei solchen Automaten herausbekommt, ob die aktiven Zellen langfristig "Aussterben" oder einem anderen Endzustand zustreben, bei dem es keine oder nur noch minimale Variation im Ablauf gibt, das hängt natürlich vom Algorithmus und den Parametern ab.

Ja, das ist die Herausforderung, Formeln zu finden, welche weder aussterben noch überbevölkern zulassen. Ich dachte da an eine Kontrollfunktion, welche bei bestimmten Schwellwerten die Parameter der Regeln modifiziert, was ein chaotisch (?) schwingendes System ergeben sollte.

Beste Grüße

hi,

mir hat das keine ruhe gelassen. mit pointern in c war ich aber zu schwach bzw. hab' ich schnell die lust verloren.
dann ist mir eingefallen, wie ich das vor ein paar monaten für eine uhr gelöst habe.
da hab' ich mit arrays gearbeitet, die über "die grenzen" hinausgehen.
schau' Dir den sketch an. wenn Du nicht nur nachbarspixel brauchst, mußt Du halt das array vergrößern.
den sketch laden, den serial monitor aufmachen und bei conways spiel des lebens zusehen.

gruß stefan

sdl.ino (1.27 KB)