0

I'm afraid I have to post a lengthy bit of code, because I can't pinpoint what's causing my issues – in short, it appears repeatedly adding float position + float speed in a struct ends up borking the whole thing.

The purpose of the code is to animate at most MAX_PARTICLES particles ("pixels") traveling at random speeds across the LED stripe.

The array named particles stores the current list of particles being managed. Each particle in the array is stored as a struct, as defined at the beginning of the code. Particles are added to the array using function addParticle() at the very end of the code. Particles which reach the end of the stripe are killed in the second part of the loop() function (after FastLED.show()).

Here's the code:

#define FASTLED_INTERNAL // Eliminates a compiler warning
#include <FastLED.h>
#include <colorutils.h>
#define NUM_LEDS 149
#define DATA_PIN 6
#define MAX_PARTICLES 5 // This uses up the memory, mostly
CRGB leds[NUM_LEDS];

struct particle {
  float position;
  CRGB color;
  float speed;
  int id;
};
typedef struct particle Particle;
Particle particles[MAX_PARTICLES];
int numParticles = 0;
int maxId = 0;

// Gramma correction tables (Defalt Gamma = 2.8)
const uint8_t PROGMEM gammaR[] = {
    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,  1,  1,
    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,
    2,  2,  2,  3,  3,  3,  3,  3,  3,  3,  4,  4,  4,  4,  4,  5,
    5,  5,  5,  5,  6,  6,  6,  6,  7,  7,  7,  7,  8,  8,  8,  9,
    9,  9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 14, 14, 14,
   15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22,
   23, 24, 24, 25, 25, 26, 27, 27, 28, 29, 29, 30, 31, 31, 32, 33,
   33, 34, 35, 36, 36, 37, 38, 39, 40, 40, 41, 42, 43, 44, 45, 46,
   46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
   62, 63, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 78, 79, 80,
   81, 83, 84, 85, 87, 88, 89, 91, 92, 94, 95, 97, 98, 99,101,102,
  104,105,107,109,110,112,113,115,116,118,120,121,123,125,127,128,
  130,132,134,135,137,139,141,143,145,146,148,150,152,154,156,158,
  160,162,164,166,168,170,172,174,177,179,181,183,185,187,190,192,
  194,196,199,201,203,206,208,210,213,215,218,220,223,225,227,230 };

const uint8_t PROGMEM gammaG[] = {
    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,  1,  1,  1,
    1,  1,  1,  1,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  2,  2,
    2,  3,  3,  3,  3,  3,  3,  3,  4,  4,  4,  4,  4,  5,  5,  5,
    5,  6,  6,  6,  6,  7,  7,  7,  7,  8,  8,  8,  9,  9,  9, 10,
   10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16,
   17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
   25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36,
   37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50,
   51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68,
   69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89,
   90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114,
  115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142,
  144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175,
  177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213,
  215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 }; 

const uint8_t PROGMEM gammaB[] = {
    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,
    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  2,  2,  2,
    2,  2,  2,  2,  2,  2,  3,  3,  3,  3,  3,  3,  3,  4,  4,  4,
    4,  4,  5,  5,  5,  5,  5,  6,  6,  6,  6,  6,  7,  7,  7,  8,
    8,  8,  8,  9,  9,  9, 10, 10, 10, 10, 11, 11, 12, 12, 12, 13,
   13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 19,
   20, 20, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 27, 27, 28, 28,
   29, 30, 30, 31, 32, 32, 33, 34, 34, 35, 36, 37, 37, 38, 39, 40,
   40, 41, 42, 43, 44, 44, 45, 46, 47, 48, 49, 50, 51, 51, 52, 53,
   54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 69, 70,
   71, 72, 73, 74, 75, 77, 78, 79, 80, 81, 83, 84, 85, 86, 88, 89,
   90, 92, 93, 94, 96, 97, 98,100,101,103,104,106,107,109,110,112,
  113,115,116,118,119,121,122,124,126,127,129,131,132,134,136,137,
  139,141,143,144,146,148,150,152,153,155,157,159,161,163,165,167,
  169,171,173,175,177,179,181,183,185,187,189,191,193,196,198,200 };

void setup() {
  FastLED.addLeds<WS2812B, DATA_PIN, BGR>(leds, NUM_LEDS);
  randomSeed(analogRead(0));
  Serial.begin(115200);
}

void loop() {
  if (numParticles < MAX_PARTICLES && random(10)>8) {
    addParticle();
  }

  for (int i=0; i<numParticles; i++) {
    // Antialiasing
    int basePosition = floor(particles[i].position);
    byte percentOnePos = round(255 * (particles[i].position-basePosition));
    byte percentBasePos = 255 - percentOnePos;
    CRGB color0 = particles[i].color % percentOnePos;
    CRGB color1 = particles[i].color % percentBasePos;

    leds[basePosition] += color0;
    leds[basePosition+1] += color1;
  }

  // Apply gamma correction
  for (int i=0; i<NUM_LEDS; i++) {
    //leds[i] = applyGamma_video(leds[i], 2.2);
    // *** applyGamma_video(2.2) pros and cons:
    // PROs: smoother color transitions
    // CONs: much slower; much worse color calibration
    // TODO: pre-render custom, color calibrated gamma tables

    leds[i].red = pgm_read_byte(&gammaR[leds[i].red]);
    leds[i].green = pgm_read_byte(&gammaG[leds[i].green]);
    leds[i].blue = pgm_read_byte(&gammaB[leds[i].blue]);
  }

  FastLED.show();

  for (int i=0; i<numParticles; i++) {
    int basePosition = floor(particles[i].position);
    leds[basePosition] = CRGB::Black;
    leds[basePosition+1] = CRGB::Black;
    particles[i].position += particles[i].speed;
    if (particles[i].position > (NUM_LEDS+1)) { // We want to render it fading out
      for (int j = i; j<numParticles-1; j++) {
        particles[j] = particles[j+1];
      }
      numParticles--;
      i--; // We want the for() cycle to process the particle we shifted down
    }
  }
  debugParticles();
}

void debugParticles()
{
  for (int i = 0; i<MAX_PARTICLES; i++) {
    if (i>0) {
      Serial.print(", ");
    }

    Serial.print(i);
    Serial.print(",");
    Serial.print(particles[i].id);
    Serial.print(",");
    Serial.print(particles[i].position);
  }
  Serial.println("");
}

void addParticle()
{
  particles[numParticles].color = CHSV(random8(),255,150);
  particles[numParticles].position = 0;
  particles[numParticles].speed = (0.0+random(10))/100+0.01;
  particles[numParticles].id = maxId++;
  numParticles++;
}

For debugging purposes, each unique particle ever created has a unique ID. The debugging output contains the following fields:

  • [0] the array index for the first entry in the array (always 0)
  • [1] the unique ID of the particle stored in the first record of the array
  • [2] the position of the particle stored in the first record of the array
  • [3] the array index for the second entry in the array (always 1)
  • [4] the unique ID of the particle stored in the second record of the array
  • [5] the position of the particle stored in the second record of the array
  • (and so on – three fields for each record in the particles array)

Finally, here's an interesting excerpt from the output, after ~1,500 records:

0   0   30.36   1   1   30.28   2   2   147.70  3   3   59.04   4   4   73.15
0   0   30.38   1   1   30.30   2   2   147.80  3   3   59.08   4   4   73.20
0   0   30.40   1   1   30.32   2   2   147.90  3   3   59.12   4   4   73.25
0   0   30.42   1   1   30.34   2   2   148.00  3   3   59.16   4   4   73.30
0   0   30.44   1   1   30.36   2   2   148.10  3   3   59.20   4   4   73.35
0   0   8.00    1   1   30.38   2   2   148.20  3   3   59.24   4   4   73.40
0   0   8.00    1   1   30.40   2   2   148.30  3   3   59.28   4   4   73.45
0   0   8.00    1   1   30.42   2   2   148.40  3   3   59.32   4   4   73.50
0   0   8.00    1   1   30.44   2   2   148.50  3   3   59.36   4   4   73.55
0   0   8.00    1   1   30.46   2   2   148.60  3   3   59.40   4   4   73.60
0   0   8.00    1   1   30.48   2   2   148.70  3   3   59.44   4   4   73.65
0   0   8.00    1   1   30.50   2   2   148.80  3   3   59.48   4   4   73.70
0   0   8.00    1   1   30.52   2   2   148.90  3   3   59.52   4   4   73.75
0   0   8.00    1   1   30.54   2   2   149.00  3   3   59.56   4   4   73.80
0   0   8.00    1   1   30.56   2   2   149.10  3   3   59.60   4   4   73.85
0   1   0.00    1   2   149.20  2   3   59.64   3   4   73.90   4   4   73.85
0   1   0.00    1   2   149.30  2   3   59.68   3   4   73.95   4   4   73.85
0   1   0.00    1   2   149.40  2   3   59.72   3   4   74.00   4   4   73.85

Notice how in all but the last three lines, the array index and the particle ID are still in sync (0=>0, 1=>1, 2=>2, 3=>3, 4=>4), which means no particles have died yet (new particles would have larger IDs). I'm trying to say that no particle re-assignment magic has happened yet – for now, we're just running the boring code which moves particles along the strip.

And yet, on the sixth line in the output, the first particle's position becomes mangled – notice how on the first five lines the first particle's position has steadily increased by 0.02 (presumably that particle's speed) up to 30.44, whereas on the sixth line it inexplicably becomes 8.00 (other times I experimented, it becomes 32.00). Not only that, but that 8.00 (the particle's position) never increases again, and three lines from the bottom the code also decides to kill the first particle altogether (notice that the second column becomes 1, which means the first record in the particles array now stores the particle with unique ID 1, instead of unique ID 0).

I have no idea what I'm doing wrong – I've been experimenting with this over and over, killing off all the bugs and warnings I could find, and still no luck.

  • My guess: not checking position leads into the overwritting memory after the leds (which is variable particles). – KIIV Jan 12 '18 at 8:49
  • I'm afraid I don't follow. All of these are automatic variables, I'm not doing any DIY memory management. – Bogdan Stăncescu Jan 12 '18 at 8:52
  • You are using leds[basePosition+1] = ... and if the base position is 148, it means you are writing at the position 149. That means AFTER the space allocated for the CRGB leds[149] as you have 149 elements starting with index 0 and ending by index 148 – KIIV Jan 12 '18 at 8:57
  • By George, I think you've got it! Haven't tested yet (and can't until tonight), but I think you've spotted it! Please jot down an answer proper, so we can do this by the numbers. – Bogdan Stăncescu Jan 12 '18 at 8:59
1

As you are keeping the particles over the position 149 (because of fading), I'd expect overwritting the memory after the space allocated for the leds variable.

According to the log it started somewhere around position 148, which is earlier, but in your code is this:

int basePosition = floor(particles[i].position);
leds[basePosition] += color0;
leds[basePosition+1] += color1;

So if you have position 148, you are already changing something after the array. It might add zero values in the color1 (for 148.0 and 148.1) so it didn't changed anything but later the value gets nonzero and changes a wrong memory.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.