How to arrange code - word clock with simultaneous effects using WS2812b

Hi,

I've been chipping away at this for a while and ended up in various places.
I built a simple word clock a couple of years ago using standard LEDs and multiplexing, but now I'm making a new one using WS2812b (Neopixel) LEDs.

The problem I'm having is how to arrange my code to allow effects to display simultaneously.

I've written it with a series of functions for different words, that then call display / hide functions. I've got states for if the word has already been on (don't do effect) or has just turned on (do effect), which works as planned to only have the effects show for words that change.

I have limited programming experience. I've now reached the point I can write a basic function and tend to get it to do what I want.

From what I understand at present:

void loop() - runs repeatedly as fast as the code will run
functions called from loop() will be run (line by line) until they are finished, then will return to loop.
This means that if I'm using a for loop in my function, to fade LEDs for instance, that nothing else will happen until that is finished and the program can continue back in loop().
This is despite using millis() and not using delay() at all.
However, if I use millis() in the main loop, I can have the program continue and come back when it loops around again.

What I would like input on, is the best way to organise the code to allow me to do this, without being an unwieldy mess. Ideally I would like to be able to do different effects on different words (potentially randomly), happening at the same time (rather than one after another). E.g. all words fade in at the same time.

Coding a simple word clock with constant functions / simple effects is easy enough, but I'm struggling with coding something than can be a bit flexible.

My code (part 1): I have removed some of the word functions due to character limit

#include <Arduino.h>
#include <Adafruit_GFX.h>
#include <Adafruit_NeoMatrix.h>
#include <Adafruit_NeoPixel.h>
#include <Wire.h>         //http://arduino.cc/en/Reference/Wire (included with Arduino IDE)
#include "RTClib.h"

struct RGB {
  byte r;
  byte g;
  byte b;
};

// Define some colors we'll use frequently
RGB white = { 255, 255, 255 };
RGB red = { 255, 0, 0 };
RGB purple = { 241, 57, 106 };
RGB off = { 0, 0, 0 };
RGB teal = { 60, 89, 78 };
RGB yellow = { 34, 50, 69 };
RGB orange = { 156, 55, 39 };

//IF CLOCK TIME IS WRONG: RUN FILE->EXAMPLE->DS1307RTC->SET TIME
RTC_DS3231 rtc;

//constants
const int nCols=16; //number of columns in matrix
const int nRows=6; //number of rows in matrix
const int NEOPIN=6;

const int wpause = 250;

//variables
long lastMillis = 0;
int fadeInterval = 25;
int intBrightness = 200;

int mytimemonth;
int mytimeday;
int mytimehr;
int mytimemin;
int mytimesec;

//states (variables)
int sNEAR = LOW;
int sPAST = LOW;
int sEXACTLY = LOW;
int sA1 = LOW;
int sQUARTER = LOW;
int sTWENTY = LOW;
int sMTEN = LOW;
int sA2 = LOW;
int sMFIVE = LOW;
int sA3 = LOW;
int sHALF = LOW;
int sTO = LOW;
int sPAST2 = LOW;
int sTEN = LOW;
int sFOUR = LOW;
int sTHREE = LOW;
int sE1 = LOW;
int sEIGHT = LOW;
int sSEVEN = LOW;
int sN1 = LOW;
int sNINE = LOW;
int sE2 = LOW;
int sELEVEN = LOW;
int sTWO = LOW;
int sFIVE = LOW;
int sONE = LOW;
int sSIX = LOW;
int sTWELVE = LOW;

// configure for 16x6 neopixel matrix
Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(nCols, nRows, NEOPIN,
      NEO_MATRIX_BOTTOM  + NEO_MATRIX_LEFT +
      NEO_MATRIX_ROWS + NEO_MATRIX_ZIGZAG,
      NEO_GRB         + NEO_KHZ800);

void setup() {
  Serial.begin(57600);  //Begin serial communcation (for photoresistor to display on serial monitor)
  matrix.begin();

}

void loop (){

  DateTime now = rtc.now();
  mytimemonth=now.month();
  mytimeday=now.day();
  mytimehr=now.hour();
  mytimemin=now.minute();
  mytimesec=now.second();

  displayClock(); //function at bottom

}

void near (int state){
  //this words details
  int iRow = 0;
  int arrCols[] = {0,1,2,3,-1};
  int arrSize = (sizeof(arrCols)/sizeof(arrCols[0])); //find array size so we don't read random values
  int& wstate = sNEAR; //int&

  displayWord(iRow, arrCols, arrSize, wstate, state); // not working with state
  }

void past (int state){
  int iRow = 0;
  int arrCols[] = {4,5,6,7,-1};
  int arrSize = (sizeof(arrCols)/sizeof(arrCols[0])); //find array size so we don't read random values
  int& wstate = sPAST; //int&

  displayWord(iRow, arrCols, arrSize, wstate, state); // not working with state
  }

void twelve (int state){
  int iRow = 5;
  int arrCols[] = {10,11,12,13,14,15,-1};
  int arrSize = (sizeof(arrCols)/sizeof(arrCols[0])); //find array size so we don't read random values
  int& wstate = sTWELVE; //int&

  displayWord(iRow, arrCols, arrSize, wstate, state); // not working with state
  }

// Display functions

// Fill the pixels one after the other with a color
void colorWipe(RGB color, uint8_t wait) {
  unsigned long currentMillis = millis();
  if (currentMillis - lastMillis > wait){
    lastMillis = currentMillis; //reset counter
    for(uint16_t row=0; row < nRows; row++) {
      for(uint16_t column=0; column < nCols; column++) {
        matrix.drawPixel(column, row, matrix.Color(100, 100, 100));
        matrix.show();
      }
    }
  }
}

// Crossfade entire screen from startColor to endColor
void crossFade(RGB startColor, RGB endColor, int steps, int wait) { //fade whole matrix from one color to another
  unsigned long currentMillis = millis();
  if (currentMillis - lastMillis > wait){
    lastMillis = currentMillis; //reset counter
    for(int i = 0; i <= steps; i++)
    {
       int newR = startColor.r + (endColor.r - startColor.r) * i / steps;
       int newG = startColor.g + (endColor.g - startColor.g) * i / steps;
       int newB = startColor.b + (endColor.b - startColor.b) * i / steps;

       matrix.fillScreen(matrix.Color(newR, newG, newB));
       matrix.show();
    }
  }
}

void fadeIn(int fadeInterval){ //fade word brightness
  unsigned long currentMillis = millis();
  //Serial.println(fadeInterval);
  if (currentMillis - lastMillis > fadeInterval){
      lastMillis = currentMillis; //reset counter
      for(int i = 1; i < intBrightness; ++i){
        matrix.setBrightness(i);
        matrix.show();
      }
  }
}

// Fade pixel (x, y) from startColor to endColor
void fadePixel(int x, int y, RGB startColor, RGB endColor, int steps, int wait) { //fade individual pixel from one colour to another
  int fade;
  unsigned long currentMillis = millis();
  if (currentMillis - lastMillis > wait){
    lastMillis = currentMillis; //reset counter
    for(int i = 0; i <= steps; i++)
    {
       int newR = startColor.r + (endColor.r - startColor.r) * i / steps;
       int newG = startColor.g + (endColor.g - startColor.g) * i / steps;
       int newB = startColor.b + (endColor.b - startColor.b) * i / steps;

       matrix.drawPixel(x, y, matrix.Color(newR, newG, newB));
       matrix.show();
    }
  }
}

void displayWord(int iRow, int arrCols[], int arrSize, int& shownState, int state) { //function to display word, effect if just turned on, no effect (stay on) otherwise
  if (state == ON) {
    if (shownState == LOW) {
      fadeWord(arrCols, arrSize, iRow, yellow, red, 50, 25);
      //void spellWord(int arrCols[], int arrSize, int iRow, RGB color, int wait){
      //spellWord(arrCols, arrSize, iRow, 2500);
      shownState = HIGH; //we have now shown the word
    } else {
      showWord(arrCols, arrSize, iRow, red);
    }
  } else {
    //void hideWord(int arrCols[], int arrSize, int iRow)
    hideWord(arrCols, arrSize, iRow);
    shownState = LOW;
  }
}

void hideWord(int arrCols[], int arrSize, int iRow) {
  for (int i = 0; i < arrSize; i++) {
      if(arrCols[i] == -1){ //at end of word
          matrix.show(); //show the pixels
          //break; //get out, we are at end of word
      }else{
          //if not yet at end of word, set colour and keep looping through for statement
          matrix.drawPixel(arrCols[i], iRow, matrix.Color(0, 0, 0));
      }
  }
}


void showWord(int arrCols[], int arrSize, int iRow, RGB Color) { //simple show word, no effect
  for (int i = 0; i < arrSize; i++) {
      if(arrCols[i] == -1){ //at end of word
          matrix.show(); //show the pixels
          //break; //get out, we are at end of word
      }else{
          //if not yet at end of word, set colour and keep looping through for statement
          matrix.drawPixel(arrCols[i], iRow, matrix.Color(Color.r, Color.g, Color.b));
      }
  }
}

Any help is much appreciated

Code part 2

void fadeWord(int arrCols[], int arrSize, int iRow, RGB startColor, RGB endColor, int steps, int wait) { //fade word from one colour to another
  //matrix.drawPixel(col, row, Adafruit_NeoMatrix::Color)
    long lastMillis = 0;
    unsigned long currentMillis = millis();
    if (currentMillis - lastMillis > wait){
        lastMillis = currentMillis; //reset counter
        for(int i = 0; i <= steps; i++) {
           int newR = startColor.r + (endColor.r - startColor.r) * i / steps;
           int newG = startColor.g + (endColor.g - startColor.g) * i / steps;
           int newB = startColor.b + (endColor.b - startColor.b) * i / steps;
           for (int j = 0; j < arrSize; j++) {
               if(arrCols[j] == -1){ //at end of word
                   matrix.show(); //show the pixels
                   //break; //get out, we are at end of word
               }else{
                   //if not yet at end of word, set colour and keep looping through for statement
                   matrix.drawPixel(arrCols[j], iRow, matrix.Color(newR, newG, newB));
               }
           }
        }
    }
}

void spellWord(int arrCols[], int arrSize, int iRow, int wait){
  long nextOperation = millis() + wait;
  for(int i = 0; i < arrSize;){
    if(arrCols[i] == -1){
      break; // get out of loop if end of word, indicated by -1
      Serial.println("out");
    }else{
      if (nextOperation <= millis()) {
      nextOperation = millis() + wait;
      Serial.println(arrCols[i]);
      matrix.drawPixel(arrCols[i], iRow, matrix.Color(red.r, red.g, red.b));
      matrix.show(); //display pixels
      i++;
      }
    }
  }
}

void displayClock() {

if ((mytimemin== 0)
      | (mytimemin== 5)
      | (mytimemin== 10)
      | (mytimemin== 15)
      | (mytimemin== 20)
      | (mytimemin== 25)
      | (mytimemin == 30)
      | (mytimemin == 35)
      | (mytimemin == 40)
      | (mytimemin == 45)
      | (mytimemin == 50)
      | (mytimemin == 55)) {
    exactly(ON);
  } else {
    exactly(OFF);
  }
  if ((mytimemin == 1)
      | (mytimemin == 2)
      | (mytimemin == 6)
      | (mytimemin == 7)
      | (mytimemin == 11)
      | (mytimemin == 12)
      | (mytimemin == 16)
      | (mytimemin == 17)
      | (mytimemin == 21)
      | (mytimemin == 22)
      | (mytimemin == 26)
      | (mytimemin == 27)
      | (mytimemin == 31)
      | (mytimemin == 32)
      | (mytimemin == 36)
      | (mytimemin == 37)
      | (mytimemin == 41)
      | (mytimemin == 42)
      | (mytimemin == 46)
      | (mytimemin == 47)
      | (mytimemin == 51)
      | (mytimemin == 52)
      | (mytimemin == 56)
      | (mytimemin == 57)) {
        past(ON);
      } else {
        past(OFF);
  }
  if ((mytimemin == 3)
      | (mytimemin == 4)
      | (mytimemin == 8)
      | (mytimemin == 9)
      | (mytimemin == 13)
      | (mytimemin == 14)
      | (mytimemin == 18)
      | (mytimemin == 19)
      | (mytimemin == 23)
      | (mytimemin == 24)
      | (mytimemin == 28)
      | (mytimemin == 29)
      | (mytimemin == 33)
      | (mytimemin == 34)
      | (mytimemin == 38)
      | (mytimemin == 39)
      | (mytimemin == 43)
      | (mytimemin == 44)
      | (mytimemin == 48)
      | (mytimemin == 49)
      | (mytimemin == 53)
      | (mytimemin == 54)
      | (mytimemin == 58)
      | (mytimemin == 59)) {
        near(ON);
    } else {
        near(OFF);
  }


  //calculate minutes on the hour
    if(mytimemin<3){
      quarter(OFF);
      twenty(OFF);
      mten(OFF);
      mfive(OFF);
      half(OFF);
      to(OFF);
      past2(OFF);
    }
    // do nothing, no minutes it's on the hour

    if(mytimemin>2 && mytimemin<8){
      quarter(OFF);
      twenty(OFF);
      mten(OFF);
      mfive(ON);
      half(OFF);
      to(OFF);
      past2(ON);
    }

    if(mytimemin>7 && mytimemin<13){
      quarter(OFF);
      twenty(OFF);
      mten(ON);
      mfive(OFF);
      half(OFF);
      to(OFF);
      past2(ON);
    }
    if(mytimemin>12 && mytimemin<18){
      quarter(ON);
      twenty(OFF);
      mten(OFF);
      mfive(OFF);
      half(OFF);
      to(OFF);
      past2(ON);
    }
    if(mytimemin>17 && mytimemin<23){
      quarter(OFF);
      twenty(ON);
      mten(OFF);
      mfive(OFF);
      half(OFF);
      to(OFF);
      past2(ON);
    }
    if(mytimemin>22 && mytimemin<28){
      quarter(OFF);
      twenty(ON);
      mten(OFF);
      mfive(ON);
      half(OFF);
      to(OFF);
      past2(ON);
    }
    if(mytimemin>27 && mytimemin<33){
      quarter(OFF);
      twenty(OFF);
      mten(OFF);
      mfive(OFF);
      half(ON);
      to(OFF);
      past2(ON);
    }
    if(mytimemin>32 && mytimemin<38){
      quarter(OFF);
      twenty(ON);
      mten(OFF);
      mfive(ON);
      half(OFF);
      to(ON);
      past2(OFF);
    }
    if(mytimemin>37 && mytimemin<43){
      quarter(OFF);
      twenty(ON);
      mten(OFF);
      mfive(OFF);
      half(OFF);
      to(ON);
      past2(OFF);
    }
    if(mytimemin>42 && mytimemin<48){
      quarter(ON);
      twenty(OFF);
      mten(OFF);
      mfive(OFF);
      half(OFF);
      to(ON);
      past2(OFF);
    }
    if(mytimemin>47 && mytimemin<53){
      quarter(OFF);
      twenty(OFF);
      mten(ON);
      mfive(OFF);
      half(OFF);
      to(ON);
      past2(OFF);
    }
    if(mytimemin>52 && mytimemin<58){
      quarter(OFF);
      twenty(OFF);
      mten(OFF);
      mfive(ON);
      half(OFF);
      to(ON);
      past2(OFF);
    }


  // Calculate hour & oclocks
  if(mytimehr==1||mytimehr==13){
    if(mytimemin>32){
      one(OFF);
      two(ON);
      three(OFF);
    }
    else
    {
      one(ON);
      two(OFF);
      twelve(OFF);
    }
  }
  if(mytimehr==2||mytimehr==14){
    if(mytimemin>32){
      two(OFF);
      three(ON);
      four(OFF);
    }
    else
    {
      one(OFF);
      two(ON);
      three(OFF);
    }
  }
    if(mytimehr==3||mytimehr==15){
    if(mytimemin>32){
      three(OFF);
      four(ON);
      five(OFF);
    }
    else
    {
      two(OFF);
      three(ON);
      four(OFF);
    }
  }
  if(mytimehr==4||mytimehr==16){
    if(mytimemin>32){
      four(OFF);
      five(ON);
      six(OFF);
    }
    else
    {
      three(OFF);
      four(ON);
      five(OFF);
    }
  }
  if(mytimehr==5||mytimehr==17){
    if(mytimemin>32){
      five(OFF);
      six(ON);
      seven(OFF);
    }
    else
    {
      four(OFF);
      five(ON);
      six(OFF);
    }
  }
  if(mytimehr==6||mytimehr==18){
    if(mytimemin>32){
      six(OFF);
      seven(ON);
      eight(OFF);
    }
    else
    {
      five(OFF);
      six(ON);
      seven(OFF);
    }
  }
  if(mytimehr==7||mytimehr==19){
    if(mytimemin>32){
      seven(OFF);
      eight(ON);
      nine(OFF);
    }
    else
    {
      six(OFF);
      seven(ON);
      eight(OFF);
    }
  }
  if(mytimehr==8||mytimehr==20){
    if(mytimemin>32){
      eight(OFF);
      nine(ON);
      ten(OFF);
    }
    else
    {
      seven(OFF);
      eight(ON);
      nine(OFF);
    }
  }
  if(mytimehr==9||mytimehr==21){
    if(mytimemin>32){
      nine(OFF);
      ten(ON);
      eleven(OFF);
    }
    else
    {
      eight(OFF);
      nine(ON);
      ten(OFF);
    }
  }
  if(mytimehr==10||mytimehr==22){
    if(mytimemin>32){
      ten(OFF);
      eleven(ON);
      twelve(OFF);
    }
    else
    {
      nine(OFF);
      ten(ON);
      eleven(OFF);
    }
  }
  if(mytimehr==11||mytimehr==23){
    if(mytimemin>32){
      one(OFF);
      eleven(OFF);
      twelve(ON);
    }
    else
    {
      ten(OFF);
      eleven(ON);
      twelve(OFF);
    }
  }
  if(mytimehr==12||mytimehr==24){
    if(mytimemin>32){
      one(ON);
      two(OFF);
      twelve(OFF);
    }
    else
    {
      one(OFF);
      eleven(OFF);
      twelve(ON);
    }
  }

}

You have the start of a proper State machine here, however your fade functions run the entire color fade in a for(;:wink: loop.
You need to keep a colorCursor that increments one step on each call to the fade() - when the wait time has passed, and after doing all pixels call matrix.show() and return.

note also that color fades in RGB space can be weird.
it works ok if you are fading from or to black, but between two bright colors the linear interpolation can produce some values do not look correct to humans. Much better to interpolate in HSV or HCL space, but that requires converting from/to RGB, which is time consuming. Perhaps a pre-computed array might help, but would add greatly to the memory footprint of an application.
For background on this issue see:

I will also reference (as you have found already) the NeoEffects library

which i really need to sync with my local copy.

pippin88:
The problem I'm having is how to arrange my code to allow effects to display simultaneously.

I did a word clock using the Particle Photon and used the NeoPixel Library.

I added Weather (temperature and conditions) and an email notification system.

you can view it here

the code has a few tricks up its sleeve... I'm sure you can use a few of them!

The Link also includes a link to the artwork I used for the weather icons and temperature...

Have fun making something great!!!