Code submit for big vfd display

here is a video on my experiments with the VFD display in Fran labs recent videos,

I have made an interface and I’m planning on putting it in the display however I haven’t got a bit of code for it and mine is boring so this is an opportunity to submit a bit of code!

ill do a video on the cut off day of the code submitted and then there will be a vote of which code stays in the display when it goes in the museum display

here is the pinouts.
Screenshot 2021-06-24 at 11.49.23 copy

it acts exactly like any other seven segment display,

There will also be a wire hanging of A7 suggested by Felix in a livestream, so if you want some analog input for your code something will be attached to A7.

the code must be able to be run on an Arduino Nano with no special libraries. and either free running with no inputs, or utilising the A7 for some sort of input, most likely a metal pad as a bit of capacitive input, nothing fancy. so your code should keep in mind that anything may be plugged into it for the vid :D.

random is always good!±!! as the obvious choice is to go for numbers! but I dunno I sorta would love to see some creative solutions, maybe doing the most random thing possible haha, but if you want to do numbers then of course go for it! I mention some ideas in the video.

the display is quick enough for fading etc.

CUT OFF DATE FOR CODE IS THE MORNING GMT FRIDAY 9th July

post as a text document in a comment below. and be sure to include your name!!! so we can mention it and make a vote afterwards. The reason I suggest to post as text here is so. people who aint got a fudging clue can learn by seeing em! :smiley:

5 Likes

I’ll be eager to see the code running on the display. Something hacked together in a few hours. Hope it’s not too long for the forum!

//Written by Ennar 24.Jun.2021

#define CHANGEOVER_SECONDS 7 /*after this many seconds, the animation is changed out to another*/
//uncomment either one of these to activate the mode
#define CHANGEOVER_MODE_CONSECUTIVE
//#define CHANGEOVER_MODE_RANDOM

#define INPUT_PIN A7 /* universal input pin, currently not used*/
#define MAX_BRIGHTNESS 48 /*upper limit for Software PWM. Higher means finer control, but runs slower*/
#define DELAY_US 50 /*global delay inside software PWM loop*/

const unsigned char pins[7] = {14, 13, 18, 17, 16, 15, 19}; //abcdefg (display like in the video!)
unsigned char brightness[7] = {}; //max is MAX_BRIGHTNESS

const unsigned char flashAllSegments[] = {4, 0xFF, 48, 0xFF, 150, 0xFF, 48, 0xFF, 150}; //length(once), frame data, dwell steps (interleaved)
const unsigned char individualSegments[] =
{ 7,
  0b00000001, 15,
  0b00000010, 15,
  0b00000100, 15,
  0b00001000, 15,
  0b00010000, 15,
  0b00100000, 15,
  0b01000000, 120
};

const unsigned char insideOutDown[] =
{ 5,
  0b01000000, 15,
  0b00000001, 15,
  0b00100010, 15,
  0b00010100, 15,
  0b00001000, 120
};

const unsigned char sparklyBits[] =
{ 6,
  0b01100000, 16,
  0b00000100, 16,
  0b00010000, 16,
  0b00000010, 16,
  0b01001000, 16,
  0b00000001, 16
};

//draw out numbers
//these two are constants that would be very awful to replace in every line
#define NUMBERS_SPEED1 8
#define NUMBERS_SPEED2 60
const unsigned char animatedNumbers[] =
{ 43,
  0b00000001, NUMBERS_SPEED1,
  0b00000010, NUMBERS_SPEED1,
  0b00000100, NUMBERS_SPEED1,
  0b00001000, NUMBERS_SPEED1,
  0b00010000, NUMBERS_SPEED1,
  0b00100000, NUMBERS_SPEED2,

  0b00000010, NUMBERS_SPEED1,
  0b00000100, NUMBERS_SPEED2,

  0b00000001, NUMBERS_SPEED1,
  0b00000010, NUMBERS_SPEED1,
  0b01000000, NUMBERS_SPEED1,
  0b00010000, NUMBERS_SPEED1,
  0b00001000, NUMBERS_SPEED2,

  0b01000000, NUMBERS_SPEED1,
  0b00000110, NUMBERS_SPEED1,
  0b00001001, NUMBERS_SPEED2,

  0b00100000, NUMBERS_SPEED1,
  0b01000000, NUMBERS_SPEED1,
  0b00000010, NUMBERS_SPEED1,
  0b00000100, NUMBERS_SPEED2,

  0b00000001, NUMBERS_SPEED1,
  0b00100000, NUMBERS_SPEED1,
  0b01000000, NUMBERS_SPEED1,
  0b00000100, NUMBERS_SPEED1,
  0b00001000, NUMBERS_SPEED2,

  0b00000001, NUMBERS_SPEED1,
  0b00100000, NUMBERS_SPEED1,
  0b00010000, NUMBERS_SPEED1,
  0b00001000, NUMBERS_SPEED1,
  0b00000100, NUMBERS_SPEED1,
  0b01000000, NUMBERS_SPEED2,

  0b00000001, NUMBERS_SPEED1,
  0b00000010, NUMBERS_SPEED1,
  0b00000100, NUMBERS_SPEED2,

  0b01000000, NUMBERS_SPEED1,
  0b00110110, NUMBERS_SPEED1,
  0b00001001, NUMBERS_SPEED2,

  0b01000000, NUMBERS_SPEED1,
  0b00100000, NUMBERS_SPEED1,
  0b00000001, NUMBERS_SPEED1,
  0b00000010, NUMBERS_SPEED1,
  0b00000100, NUMBERS_SPEED1,
  0b00001000, NUMBERS_SPEED2 * 2,
};

const unsigned char snakeEight[] =
{ 8,
  0b00000001, 15,
  0b00000010, 15,
  0b01000000, 15,
  0b00010000, 15,
  0b00001000, 15,
  0b00000100, 15,
  0b01000000, 15,
  0b00100000, 15
};

const unsigned char* animations[] = {flashAllSegments, individualSegments, snakeEight, animatedNumbers, sparklyBits, insideOutDown}; //pointers to animations in memory
unsigned char* animation = animations[0]; //holds pointer to currently selected animation
unsigned char animFrame = 0; //holds Currently active frame
unsigned char currentAnimNumber = 0; //Helper variable to prevent double consecutive showings
unsigned char lastAnimNumber = 0; //Helper variable to prevent double consecutive showings
unsigned char dwellSteps = 0; //holds currently active frame's remaining dwell steps. DwellSteps results in a delay of DELAY_US*MAX_BRIGHTNESS µs

void setup() {

  //Set all the pins required for the segments to OUT
  for (int i = 0; i < 7; i++) {
    pinMode(pins[i], OUTPUT);
  }

  pinMode(INPUT_PIN, INPUT); //reserved input pin
  getAnimationFrame(0, 0); //load first animation frame
  //Serial.begin(9600);
  //debugMsg();
}

void loop() {
  //This consists of two nested loops, forming a software PWM and animation player
  // Animation specific code
  //switch animations automatically every CHANGEOVER_SECONDS seconds.
  //Keeping this active for one second jumbles the display up a bit to make it more interesting
  if ((millis() / 1000) % CHANGEOVER_SECONDS == 0) {
#ifdef CHANGEOVER_MODE_RANDOM
    while (currentAnimNumber == lastAnimNumber) {
      currentAnimNumber = random(0, 5);
    }
#endif
#ifdef CHANGEOVER_MODE_CONSECUTIVE
    currentAnimNumber = lastAnimNumber + 1;
#endif
    animFrame = 0;
    randomBrightness();
  } else {
    animation = animations[currentAnimNumber];
    if (currentAnimNumber > 5) currentAnimNumber = 0;
    lastAnimNumber = currentAnimNumber;
    //if (animFrame == 0) Serial.println(nextAnim); //Sometimes there was a glitch happening, trying to find out which animation number causes that
    //Simple fade-out type animations
    if (dwellSteps > 0) {
      dwellSteps--;
    } else {
      animFrame++;
      if (animFrame >= animation[0]) animFrame = 0;
      getAnimationFrame(animFrame, 1); //mode 1 writes HIGH bits from animation byte only
      //debugMsg();
    }
    fadeOutAll();
  }


  // Software PWM
  for (unsigned char softPWM = 0; softPWM < MAX_BRIGHTNESS; softPWM++) {
    for (int segment = 0; segment < 7; segment++) {
      if (brightness[segment] > softPWM) {
        digitalWrite(pins[segment], HIGH);
      } else {
        digitalWrite(pins[segment], LOW);
      }
    }
    delayMicroseconds(DELAY_US);
  }
}


//Loads animation frames from array to segment brightness buffer, and gets new value for dwellSteps
//mode 0: overwrite every bit
//mode 1: overwrite only ones (fade-out animation types)
//mode 2: overwrite only zeroes (fade-in animation types)
void getAnimationFrame(unsigned char frameNumber, unsigned char mode) {
  switch (mode) {
    case 0:
      for (int i = 0; i < 7; i++) {
        brightness[i] = ((animation[(frameNumber * 2) + 1] & (1 << i)) >> i) * MAX_BRIGHTNESS; //uses bits from animation array
      }
      break;
    case 1:
      for (int i = 0; i < 7; i++) {
        if (animation[(frameNumber * 2) + 1] & (1 << i)) brightness[i] = MAX_BRIGHTNESS; //uses bits from animation array
      }
      break;
    case 2:
      for (int i = 0; i < 7; i++) {
        if (!(animation[(frameNumber * 2) + 1] & (1 << i))) brightness[i] = 0; //uses bits from animation array
      }
  }
  dwellSteps = animation[(frameNumber * 2) + 2];
}

//Fades every segment out by 1 if it is still lit (brightness >0)
void fadeOutAll() {
  for (int i = 0; i < 7; i++) {
    if (brightness[i] > 0) brightness[i]--;
  }
}

//Fades every segment in by 1 if it is still below MAX_BRIGHTNESS
void fadeInAll() {
  for (int i = 0; i < 7; i++) {
    if (brightness[i] < MAX_BRIGHTNESS) brightness[i]++;
  }
}

//Write random brightness vvalues to all segments
void randomBrightness() {
  for (int i = 0; i < 7; i++) {
    brightness[i] = random(0, MAX_BRIGHTNESS);
  }
}

//A leftover function to print out all brightness values
void debugMsg() {
  for (int i = 0; i < 7; i++) {
    Serial.print(brightness[i]); Serial.print(' ');
  } Serial.println();
}

4 Likes

perfect!!! yeah thats good I think its good sharing it this way for the project! thanks for the eager submission haha. see how the next 2 weeks go and ill likely do a livestream trying them all

3 Likes

also to add! if anyone chooses to change from now till the 9th July just edit the post instead of reposting. as it would be great to keep this thread organised :slight_smile:

2 Likes

Thanks! Feel free to play around with the #define’s and other numbers that are somewhat labeled, might give interesting results. There is a number animation in there, but if you don’t like seeing the numbers, just jumble around the pins[] array to randomize them. I did plan to implement something for a potentiometer or a switch on A7, but ran out of time. Looking forward to the stream.

2 Likes

Here is my submission, a 10-sided die rolling simulator. Since the A7 analog pin doesn’t have an ADC and I’m using a switch, I just kinda hacked a solution that worked.

Oh, @lookmumnocomputer, should the input be an INPUT or INPUT_PULLUP? You planning on hooking it up to some pullup resistors, or did you want to make use of the ones on the nano? Currently @Ennar set up their input as an INPUT, but they just did so as a dummy. Might be good for anyone else building things to know to account for it.

Right now, I just matched what they were doing so all projects here match.

//Written by Caustic 06-30-2021

const uint8_t pins[7]   = { 14,13,18,17,16,15,19 };
int randNumber;
int buttonPin           = A7;
int buttonState         = LOW;
int lastButtonState     = LOW;
int buttonPushCounter   = 0;
int circle_anim_counter = 0;
bool gotResult          = false;
int minValADCHack       = 10;
int minSpeedAnimation   = 60;

int num_matrix[10][7]   = {
                            { 1,1,1,1,1,1,0 }, // 0
                            { 0,1,1,0,0,0,0 }, // 1
                            { 1,1,0,1,1,0,1 }, // 2
                            { 1,1,1,1,0,0,1 }, // 3
                            { 0,1,1,0,0,1,1 }, // 4
                            { 1,0,1,1,0,1,1 }, // 5
                            { 1,0,1,1,1,1,1 }, // 6
                            { 1,1,1,0,0,0,0 }, // 7
                            { 1,1,1,1,1,1,1 }, // 8
                            { 1,1,1,1,0,1,1 }  // 9
                          };

int circle_matrix[6][7] = {
                            { 1,0,0,0,0,0,0 }, // A
                            { 0,1,0,0,0,0,0 }, // B
                            { 0,0,1,0,0,0,0 }, // C
                            { 0,0,0,1,0,0,0 }, // D
                            { 0,0,0,0,1,0,0 }, // E
                            { 0,0,0,0,0,1,0 }  // F
                          };

int clear_array[7]      = { 0,0,0,0,0,0,0 };   // This clears all segments
int pinCount_num_matrix=sizeof(num_matrix);

void setup()
{
  Serial.begin(9600);

  // Init the modes for each output pin
  for (int thisPin = 0; thisPin < pinCount_num_matrix; thisPin++) {
    pinMode(pins[thisPin], OUTPUT);
  }

  // init mode for input pin
  pinMode(buttonPin, INPUT);
}

void loop() {
  // Since A7 does not have an ADC, we hack a solution here
  if (analogRead(buttonPin)<=minValADCHack) {
    buttonState = LOW;
  }
  else {
    buttonState = HIGH;
  }

  /* Flips the button state so you hold the button 
     to charge up the die roll.
  */
  if(buttonState == HIGH) {
    buttonState = LOW;
  }
  else {
   buttonState = HIGH;
  }

  if(buttonState == HIGH && gotResult == false) {
    // Persist what this state is for the next run
    lastButtonState = HIGH;
    
    /* Iterate the counter, modulo 6 because we have 
       6 possible frames.
    */
    buttonPushCounter++;
    circle_anim_counter++;
    buttonPushCounter %= 6;

    // Clamps the circle animation to a minimum speed
    if (circle_anim_counter >= minSpeedAnimation) {
      circle_anim_counter = minSpeedAnimation;
    }

    /* Lights up each LED in the 7 segment display.
       According to the circle matrix defined in the beginning.
    */ 
    for (int j=0; j < 6; j++) {
      digitalWrite(pins[j], circle_matrix[buttonPushCounter-1][j]);
    }

    // Sets the lag that will decrease the longer you hold the button.
    delay(minSpeedAnimation - circle_anim_counter);
  }

  else if (buttonState == HIGH && gotResult == true) {
    // Clear the results so its clean if we already got a result
    for (int j=0; j < 7; j++) {
      digitalWrite(pins[j], clear_array[j]);
    }

    gotResult = false;
  }
  
  else {
      // Sets a lag so that the display will be ready.
      delay(10);

      /* Uses persisted state of button press to see if it is
         Continually being pressed.
      */
      if(lastButtonState == HIGH) {
        buttonPushCounter++;
        buttonPushCounter %= 7;

        if(buttonPushCounter == 0) {
          buttonPushCounter = 1;
        }
        circle_anim_counter--;

        for (int j=0; j < 7; j++) {
          digitalWrite(pins[j], circle_matrix[buttonPushCounter-1][j]);
        }

        // Sets the lag will be increased as long as you do not hold the button. 
        delay(minSpeedAnimation - circle_anim_counter);

        /* Once the circle animation completes, reset the 
           last button state and get our result.
        */
        if(circle_anim_counter == 0) {
          lastButtonState = LOW;

          // Gets our randomized result.
          randNumber = random(0, 10);
          gotResult = true;

          // Display the randomly selected number
          for (int j=0; j < 7; j++) {
            digitalWrite(pins[j], num_matrix[randNumber][j]);
          }
       }
     }
   }
}
4 Likes

That is a nice effect! Maybe I could combine our two ideas, so the display would play random animations when idle, and rolling dice with the trailing PWM effects when someone pushes a button?

I would have to repost the code as I can’t edit my original post anymore.

1 Like

Try editing again. You had the basic trust level.

1 Like

whey!! awesome :smiley: looking good! I best sort out some sort of button

1 Like

good idea! yeah that could work!! no idea anyone else will submit or what not but that would be a cool idea!

1 Like

Make sure the ground is reliable, since I’m having to use the analog pin and hack it into a sort of digital logic in software, it can be a bit fiddly if the voltage doesn’t go all the way down.

Ideally, a different pin should be used since a7 is one of the analog pins that doesn’t have an adc. It -should- work, but if it’s behaving funky, just let us know and we can adjust it accordingly.

I have some spare time this evening. Can I mod in your dice idea? You would of course be credited in the software. I know how to deal with noisy input on the A7 pin by software-filtering it over a few cycles.

Oh and about editing the first post, sadly I can’t edit it anymore, it doesnt show me the edit icon.

Cheers

1 Like

Yeah go for it. Looks like we are the only ones to submit so far anyways!

I though about implementing a kind of Lorenz Attractor, but I have no good idea how to map the 3d result to the 7 segment display…

I was a bit naughty and got to work right after posting :smiley:

Here’s the updated code. I’ve fiddled and played with it, tweaking the looks and trying to break it on purpose. So far it has withstood some “abuse” on the input pin and has run stable.

The input pin A7 has no digital pin IO hardware on it, but can be MUXed to the ADC. It can only be an INPUT, but not an INPUT_PULLUP. @lookmumnocomputer make sure to connect a button pulling the pin HIGH (to VCC) with a Pull-Down resistor (to GND).

The new finite state machine may look somewhat complicated at first, but ensures smooth and maintainable transitions between modes.

//Big VFD 7 segment display animation for Museum Of Everything Else
//Written by Ennar 24.Jun.2021
//V1.2
//Additional d10 die feature idea contributed by Caustic

//Plays back animations on the seven segment VFD featured in the Museum Of Everything Else display case.
//If INPUT_PIN is held high, it will roll a d10 die. Release to display the number. After some time it
//will resume playing animations


//********** ANIMATION **********
#define CHANGEOVER_SECONDS 8 /*after this many seconds, the animation is changed out to another*/
//uncomment either one of these to activate the mode
#define CHANGEOVER_MODE_CONSECUTIVE /* makes sure every animation has an appearance on stream */
//#define CHANGEOVER_MODE_RANDOM /* selects animation randomly, better for displaying in the museum */

//********** D10 DIE **********
#define DIE_MIN_SPEED 32/* minimum speed for dice spinner animation (dwellSteps value!)*/
#define DIE_MAX_SPEED 7/* maximum speed for dice spinner animation (dwellSteps value!) */
#define DIE_DWELLSTEPS_SHOW 255 /*how long the resulting number will be shown*/
#define DIE_DWELLSTEPS_FADE 120 /*how long the resulting number will be faded before resuming animation player*/
unsigned char dieDwellSteps = DIE_MIN_SPEED;
unsigned char dieRolledNumber = 0;

//********** MISC **********
#define INPUT_PIN A7 /* universal (analog!) input pin, currently used for dice feature*/
#define MAX_BRIGHTNESS 52 /*upper limit for Software PWM. Higher means finer control, but runs slower*/
#define DELAY_US 46 /*global delay inside software PWM loop*/
const unsigned char pins[7] = {14, 13, 18, 17, 16, 15, 19}; //abcdefg (display like in the video!)
//const unsigned char pins[7] = {17, 16, 15, 14, 13, 18, 19}; //abcdefg (in case the numbers display upside down)

//const unsigned char pins[7] = {13, 14, 17, 18, 15, 16, 19}; //randomized segments
unsigned char brightness[7] = {}; //max is MAX_BRIGHTNESS

//********** MODE LOGIC **********
//since the animation is no longer the sole mode, this sketch also needs a state machine to determine its mode
enum stateMachineStates {startUp, playAnimation, dieSpinUp, dieSpinDown, dieShow, dieFade};
stateMachineStates currentState, nextState = startUp;

//input pin is assumed to be an analog pin, give it some helper variables to deal with noisy input
//A function that reads the button will act as its own two-state state machine
#define BUTTON_HYSTERESIS 100 /*hysteresis for button around ADC_MAX/2 (usually 511) */
unsigned char buttonState, buttonLastState = 0; //used as binary state for logic
double buttonVal; //Holds last saved value from button
double buttonInfluence = 0.2; //determines how much influence each ADC reading has on buttonVal


//********** ANIMATIONS **********
const unsigned char heartbeatAllSegments[] = {3, 0xFF, 10, 0xFF, 40, 0xFF, 150}; //length(once), frame data, dwell steps (interleaved)
const unsigned char individualSegments[] =
{ 7,
  0b00000001, 15,
  0b00000010, 15,
  0b00000100, 15,
  0b00001000, 15,
  0b00010000, 15,
  0b00100000, 15,
  0b01000000, 120
};

const unsigned char insideOutDown[] =
{ 5,
  0b01000000, 15,
  0b00000001, 15,
  0b00100010, 15,
  0b00010100, 15,
  0b00001000, 15
};

const unsigned char sparklyBits[] =
{ 6,
  0b01100000, 16,
  0b00000100, 16,
  0b00010000, 16,
  0b00000010, 16,
  0b01001000, 16,
  0b00000001, 16
};

//draw out numbers
//these two are constants that would be very awful to replace in every line
#define NUMBERS_SPEED1 8
#define NUMBERS_SPEED2 60
const unsigned char animatedNumbers[] =
{ 44,
  0b00000001, NUMBERS_SPEED1,
  0b00000010, NUMBERS_SPEED1,
  0b00000100, NUMBERS_SPEED1,
  0b00001000, NUMBERS_SPEED1,
  0b00010000, NUMBERS_SPEED1,
  0b00100000, NUMBERS_SPEED1,
  0b00000001, NUMBERS_SPEED2,

  0b00000010, NUMBERS_SPEED1,
  0b00000100, NUMBERS_SPEED2,

  0b00000001, NUMBERS_SPEED1,
  0b00000010, NUMBERS_SPEED1,
  0b01000000, NUMBERS_SPEED1,
  0b00010000, NUMBERS_SPEED1,
  0b00001000, NUMBERS_SPEED2,

  0b01000000, NUMBERS_SPEED1,
  0b00000110, NUMBERS_SPEED1,
  0b00001001, NUMBERS_SPEED2,

  0b00100000, NUMBERS_SPEED1,
  0b01000000, NUMBERS_SPEED1,
  0b00000010, NUMBERS_SPEED1,
  0b00000100, NUMBERS_SPEED2,

  0b00000001, NUMBERS_SPEED1,
  0b00100000, NUMBERS_SPEED1,
  0b01000000, NUMBERS_SPEED1,
  0b00000100, NUMBERS_SPEED1,
  0b00001000, NUMBERS_SPEED2,

  0b00000001, NUMBERS_SPEED1,
  0b00100000, NUMBERS_SPEED1,
  0b00010000, NUMBERS_SPEED1,
  0b00001000, NUMBERS_SPEED1,
  0b00000100, NUMBERS_SPEED1,
  0b01000000, NUMBERS_SPEED2,

  0b00000001, NUMBERS_SPEED1,
  0b00000010, NUMBERS_SPEED1,
  0b00000100, NUMBERS_SPEED2,

  0b01000000, NUMBERS_SPEED1,
  0b00110110, NUMBERS_SPEED1,
  0b00001001, NUMBERS_SPEED2,

  0b01000000, NUMBERS_SPEED1,
  0b00100000, NUMBERS_SPEED1,
  0b00000001, NUMBERS_SPEED1,
  0b00000010, NUMBERS_SPEED1,
  0b00000100, NUMBERS_SPEED1,
  0b00001000, NUMBERS_SPEED2 * 2,
};

const unsigned char snakeEight[] =
{ 8,
  0b00000001, 15,
  0b00000010, 15,
  0b01000000, 15,
  0b00010000, 15,
  0b00001000, 15,
  0b00000100, 15,
  0b01000000, 15,
  0b00100000, 15
};

const unsigned char scannerEight[] =
{ 10,
  0b01000000, 15,
  0b00000110, 15,
  0b00001001, 15,
  0b00110000, 15,
  0b01000000, 85,
  0b01000000, 15,
  0b00110000, 15,
  0b00001001, 15,
  0b00000110, 15,
  0b01000000, 85
};

//spinner animation for die, dwellsteps are ignored and overwritten by #define'd values
const unsigned char spinner[] =
{ 6,
  0b00000001, 0,
  0b00000010, 0,
  0b00000100, 0,
  0b00001000, 0,
  0b00010000, 0,
  0b00100000, 0,
};

//not an animation, just numbers for the die roll
const unsigned char numbers[] {
  10,
  0b00111111, 0,
  0b00000110, 0,
  0b01011011, 0,
  0b01001111, 0,
  0b01100110, 0,
  0b01101101, 0,
  0b01111101, 0,
  0b00000111, 0,
  0b01111111, 0,
  0b01101111, 0
};


const unsigned char* animations[] = {insideOutDown, heartbeatAllSegments, individualSegments, snakeEight, animatedNumbers, sparklyBits, scannerEight, numbers}; //pointers to animations in memory
unsigned char* animation = animations[0]; //holds pointer to currently selected animation
unsigned char animFrame = 0; //holds Currently active frame
unsigned char currentAnimNumber = 0; //Helper variable to prevent double consecutive showings
unsigned char lastAnimNumber = 0; //Helper variable to prevent double consecutive showings
unsigned char dwellSteps = 0; //holds currently active frame's remaining dwell steps. DwellSteps results in a delay of DELAY_US*MAX_BRIGHTNESS µs

void setup() {
  //Set all the pins required for the segments to OUT
  for (int i = 0; i < 7; i++) {
    pinMode(pins[i], OUTPUT);
  }

  pinMode(INPUT_PIN, INPUT); //reserved input pin
  getAnimationFrame(0, 0); //load first animation frame
  //Serial.begin(9600);
  //debugMsg();
}

void loop() {
  //read button and decide its state
  buttonVal = (1 - buttonInfluence) * buttonVal + (buttonInfluence * analogRead(INPUT_PIN));
  if (buttonVal > (511 + BUTTON_HYSTERESIS) && buttonLastState == 0) {
    buttonState = 1;
  } else if (buttonVal < (511 - BUTTON_HYSTERESIS) && buttonLastState == 1) {
    buttonState = 0;
  }
  buttonLastState = buttonState;

  //Main finite state machine for mode selection
  //enum stateMachineStates {startUp, playAnimation, dieSpinUp, dieSpinDown, dieShow, dieFade};

  //1/2: State jumping and condition-bound actions like resetting something to default values
  //Imagine all states as a tree of connected bubbles. Every bubble must lead to another, each path
  //"blocked" by a condition that must be met (in this case pressed/released button, waiting times)
  switch (currentState) {
    case startUp:
      if (buttonState == 1) nextState = dieSpinUp;
      else nextState = playAnimation;
      break;
    case playAnimation:
      if (buttonState == 1) {
        dieDwellSteps = DIE_MIN_SPEED * 0.7; //Reset to starting speed
        dwellSteps = dieDwellSteps;
        nextState = dieSpinUp;
      }
      else nextState = playAnimation;
      break;
    case dieSpinUp:
      if (buttonState == 1) nextState = dieSpinUp; //resume spinning WITHOUT resetting the speed
      else nextState = dieSpinDown;
      break;
    case dieSpinDown:
      if (buttonState == 1) nextState = dieSpinUp; //resume spinning WITHOUT resetting the speed
      else if (buttonState == 0 && dieDwellSteps < DIE_MIN_SPEED) nextState = dieSpinDown;
      else {
        dieRolledNumber = random(0, 9);
        nextState = dieShow;
      }
      break;
    case dieShow:
      if (buttonState == 1) {
        dieDwellSteps = DIE_MIN_SPEED * 0.7; //Reset to starting speed
        dwellSteps = dieDwellSteps;
        nextState = dieSpinUp;
      }
      else if (buttonState == 0 && dieDwellSteps < DIE_DWELLSTEPS_SHOW) nextState = dieShow;
      else {
        dieDwellSteps = 0; //dieDwellsteps will be used for a timeout in the following state, so reset it!
        nextState = dieFade;
      }
      break;
    case dieFade:
      if (buttonState == 1) {
        dieDwellSteps = DIE_MIN_SPEED * 0.7; //Reset to starting speed
        nextState = dieSpinUp;
      }
      else if (buttonState == 0 && dieDwellSteps > DIE_DWELLSTEPS_FADE) {
        dieDwellSteps = 0; //dieDwellsteps will be used for a timeout in the following state, so reset it!
        dwellSteps = 0;
        nextState = playAnimation;
      }
      break;
    default: //state machine should never reach this under normal conditions, but will be provided anyway
      nextState = startUp;
      break;
  };

  //2/2 state-bound actions
  //These actions are performed as long as its corresponding state is active
  //enum stateMachineStates {startUp, playAnimation, dieSpinUp, dieSpinDown, dieShow, dieFade};
  switch (currentState) {
    case startUp:
      break;
    case playAnimation:
      //This consists of two nested loops, forming a software PWM and animation player
      // Animation specific code
      //switch animations automatically every CHANGEOVER_SECONDS seconds.
      //Keeping this active for one second jumbles the display up a bit to make it more interesting
      if ((millis() / 1000) % CHANGEOVER_SECONDS == 0) {
#ifdef CHANGEOVER_MODE_RANDOM
        while (currentAnimNumber == lastAnimNumber) {
          currentAnimNumber = random(0, 6);
        }
#endif
#ifdef CHANGEOVER_MODE_CONSECUTIVE
        currentAnimNumber = lastAnimNumber + 1;
#endif
        animFrame = 0;
        dwellSteps = 0;
        randomBrightness();
      } else {
        animation = animations[currentAnimNumber];
        if (currentAnimNumber > 6) currentAnimNumber = 0;
        lastAnimNumber = currentAnimNumber;
        //if (animFrame == 0) Serial.println(nextAnim); //Sometimes there was a glitch happening, trying to find out which animation number causes that
        //Simple fade-out type animations
        fadeOutAll();
        if (dwellSteps > 0) {
          dwellSteps--;
        } else {
          animFrame++;
          if (animFrame >= animation[0]) animFrame = 0;
          getAnimationFrame(animFrame, 1); //mode 1 writes HIGH bits from animation byte only
          //debugMsg();
        }

      }
      break;

    case dieSpinUp:
      animation = spinner;
      if (dwellSteps > 0) {
        dwellSteps--;
      } else {
        if (dieDwellSteps > DIE_MAX_SPEED)dieDwellSteps--;
        animFrame++;
        if (animFrame >= animation[0]) animFrame = 0;
        getAnimationFrame(animFrame, 1); //mode 1 writes HIGH bits from animation byte only
        dwellSteps = dieDwellSteps; //Overwrite dwellSteps from animation
        //debugMsg();
      }
      fadeOutAll();
      break;
    case dieSpinDown:
      animation = spinner;
      if (dwellSteps > 0) {
        dwellSteps--;
      } else {
        if (dieDwellSteps < DIE_MIN_SPEED)dieDwellSteps++;
        animFrame++;
        if (animFrame >= animation[0]) animFrame = 0;
        getAnimationFrame(animFrame, 1); //mode 1 writes HIGH bits from animation byte only
        dwellSteps = dieDwellSteps; //Overwrite dwellSteps from animation
        //debugMsg();
      }
      fadeOutAll();
      break;
    case dieShow:
      animation = numbers;
      fadeInAll();
      getAnimationFrame(dieRolledNumber, 2); //mode 2 writes LOW bits from animation byte only, overwriting the fade-ins
      dieDwellSteps++;
      break;
    case dieFade:
      fadeOutAll();
      dieDwellSteps++;
      break;

    default:
      break;
  };
  currentState = nextState;


  // Software PWM
  for (unsigned char softPWM = 0; softPWM < MAX_BRIGHTNESS; softPWM++) {
    for (int segment = 0; segment < 7; segment++) {
      if (brightness[segment] > softPWM) {
        digitalWrite(pins[segment], HIGH);
      } else {
        digitalWrite(pins[segment], LOW);
      }
    }
    delayMicroseconds(DELAY_US);
  }
}

//Loads animation frames from array to segment brightness buffer, and gets new value for dwellSteps
//mode 0: overwrite every bit
//mode 1: overwrite only ones (fade-out animation types)
//mode 2: overwrite only zeroes (fade-in animation types)
void getAnimationFrame(unsigned char frameNumber, unsigned char mode) {
  switch (mode) {
    case 0:
      for (int i = 0; i < 7; i++) {
        brightness[i] = ((animation[(frameNumber * 2) + 1] & (1 << i)) >> i) * MAX_BRIGHTNESS; //uses bits from animation array
      }
      break;
    case 1:
      for (int i = 0; i < 7; i++) {
        if (animation[(frameNumber * 2) + 1] & (1 << i)) brightness[i] = MAX_BRIGHTNESS; //uses bits from animation array
      }
      break;
    case 2:
      for (int i = 0; i < 7; i++) {
        if (!(animation[(frameNumber * 2) + 1] & (1 << i))) brightness[i] = 0; //uses bits from animation array
      }
  }
  dwellSteps = animation[(frameNumber * 2) + 2];
}

//Fades every segment out by 1 if it is still lit (brightness >0)
void fadeOutAll() {
  for (int i = 0; i < 7; i++) {
    if (brightness[i] > 0) brightness[i]--;
  }
}

//Fades every segment in by 1 if it is still below MAX_BRIGHTNESS
void fadeInAll() {
  for (int i = 0; i < 7; i++) {
    if (brightness[i] < MAX_BRIGHTNESS) brightness[i]++;
  }
}

//Write random brightness values to all segments
void randomBrightness() {
  for (int i = 0; i < 7; i++) {
    brightness[i] = (random(0, MAX_BRIGHTNESS) / 16) * 16; //more contrast
    //brightness[i] = random(0, MAX_BRIGHTNESS);
  }
}

//A leftover function to print out all brightness values
void debugMsg() {
  for (int i = 0; i < 7; i++) {
    Serial.print(brightness[i]); Serial.print(' ');
  } Serial.println();
}

I hope this will perform as intended :smiley:
Ennar

Lol I was too lazy to implement a finite state machine, though it likely would have saved me time in the long run. Def see why you would do so with your additional states that you bring to the table.

@lookmumnocomputer is the livestream today? I wouldnt want to miss it :smiley:

1 Like

awesome!!! right im a little bit behind I must admit! a machine im building for this weekends video ending being a lot more tech than I had first hoped! I think this is best going to be a vid! as I said livestream incase it meant a lot of uploading code etc but this is a lot more manageable :smiley: so I will shoot it today! at some point and ill try and get it up by end ut day!!! if not it will be up tomorrow will let you know! thanks a lot @Ennar and @Caustic !

1 Like

Let us know if you have any issues with it not working or whatever.

here we go @Ennar @Caustic !!!

5 Likes