3x3x3 LED Cube and millis()

Hello Everyone!
I am writing here to ask for your help. I have been struggling with one of my assigments and honestly I have run out of ideas. I looked for a solution on the internet, but couldn't find a suitable help.
Here's some backround: I need to write a code which would light up each layer of 3x3x3 LED Cube (like this one for example: Arduino Playground - LEDCube3x3) for 1 sec., turn it off and move to the next one. All in loop. The catch is that the code needs to be written using millis() function. This is the biggest issue I have a problem with. There was absolutely no issue when I used delay() function. Could you please help here?

const unsigned long executiontime = 1000;
unsigned long pasttime = 0;
unsigned long presenttime = 0;

void setup(){
		for (int i=2; i<=13; i++) {
		pinMode(i, OUTPUT);
		}
	}
void fill() {
	for (int i=2; i<=10; i++) {
		digitalWrite(i, HIGH);
		}
	}
	
void empty() {
for (int i=2; i<=10; i++) {
		digitalWrite(i, LOW);
		}
	}

void loop() {
presenttime = millis();
		if (presenttime - pasttime <= executiontime) {
		digitalWrite(11, HIGH);
		fill();
		//delay(250);
 		empty();
		digitalWrite(11, LOW);
		pasttime = presenttime;
		}
		
		if (presenttime - pasttime <= executiontime) {
  		digitalWrite(12, HIGH);
		fill();
		//delay(250);
		digitalWrite(12, LOW);
		empty();
		pasttime = presenttime;
		}
		
		if (presenttime - pasttime <= executiontime) {
		digitalWrite(13, HIGH);
		fill();
		//delay(250);
		digitalWrite(13, LOW);	
		empty();
		pasttime = presenttime;
		}
}

Welcome to the forum

It looks like a state machine would be ideal for what you want to do. It sounds scary but isn't

Your cube has 3 layers which you want to light in turn so it can be in one of 3 states, one for each layer on. So, if you had a variable named state with possible values 0, 1 or 2 then you would know which layer to light and which ones to turn off

You could do this a number of ways but using switch/state makes it easy to write and understand. Use the state number as the switch variable and its value to determine which layer to light and which ones to turn off

Use millis() timing in each state to control the amount of time that a layer is on

Psuedo code

state = 0
start time = millis()
period = whatever suits you

start of loop()
  current time = millis()
  switch(state)
  {
    case 0
    //code here to turn layers on/off for state 0
    if current time - start time >= period
      set state to next value
      start time = current time
    end if
    case 1
    //code here to turn layers on/off for state 1
    if current time - start time >= period
      set state to next value
      start time = current time
    end if
    case 2
    //code here to turn layers on/off for state 2
    if current time - start time >= period
      set state to next value
      start time = current time
    end if
  }
end of loop()

Note that I have deliberately not written real code so you have work to do to work out the real code and get the syntax right but I hope that you can see the basic idea

Thanks for your help. I will try to get it from here.

Hello again,

I have adjusted the code according to the remarks above, but it still does not work. I am clearly doing something wrong. Where I might have done a mistake?

const unsigned long period = 1000;
unsigned long starttime = 0;
unsigned long currenttime;
int state=0;

void setup(){
		for (int i=2; i<=13; i++) {
		pinMode(i, OUTPUT);
		}
	}
void fill() {
	for (int i=2; i<=10; i++) {
		digitalWrite(i, HIGH);
		}
	}
	
void empty() {
for (int i=2; i<=10; i++) {
		digitalWrite(i, LOW);
		}
	}

void loop() {
currenttime = millis();
starttime = millis();

	switch(state)
  		{
			case 0:
				digitalWrite(11, HIGH);
				fill();
				if (currenttime - starttime <= period) {
				digitalWrite(11, LOW);
				empty();
				starttime = currenttime;
				}
				break;
				
			case 1:
		  		digitalWrite(12, HIGH);
				fill();
				if (currenttime - starttime <= period) {
				digitalWrite(12, LOW);
				empty();
				starttime = currenttime;
				}
				break;
			case 2:
				digitalWrite(13, HIGH);
				fill();
				if (currenttime - starttime <= period) {
				digitalWrite(13, LOW);	
				empty();
				starttime = currenttime;
				}
				break;
		}
}

One obvious mistake is that you are setting starttime to millis() every time through loop() when you should be setting it only when a new state starts, ie once at the start of the sketch and subsequently whenever a new state starts

A second problem is that you don't increment the state variable when the timing period in the current state ends so the code never leaves state 0

Hm... Still does not work.

const unsigned long period = 1000;
unsigned long starttime = millis();
unsigned long currenttime;
int state=0;

void setup(){
		for (int i=2; i<=13; i++) {
		pinMode(i, OUTPUT);
		}
	}
void fill() {
	for (int i=2; i<=10; i++) {
		digitalWrite(i, HIGH);
		}
	}
	
void empty() {
for (int i=2; i<=10; i++) {
		digitalWrite(i, LOW);
		}
	}

void loop() {
currenttime = millis();
	for (state=0; state<=2; state++) {
		switch(state)
	  		{
				case 0:
					digitalWrite(11, HIGH);
					fill();
					if (currenttime - starttime <= period) {
					digitalWrite(11, LOW);
					empty();
					starttime = currenttime;
					}
					break;
					
				case 1:
			  		digitalWrite(12, HIGH);
					fill();
					if (currenttime - starttime <= period) {
					digitalWrite(12, LOW);
					empty();
					starttime = currenttime;
					}
					break;
				case 2:
					digitalWrite(13, HIGH);
					fill();
					if (currenttime - starttime <= period) {
					digitalWrite(13, LOW);	
					empty();
					starttime = currenttime;
					}
					break;
		}
	}
}

Why are you using a for loop ?
Let loop() do what it does best ?

if (currenttime - starttime <= period)

You have the period comparisons the wrong way round

When the timing period for a state ends, set the state variable to the next state to be moved to

Try this

const unsigned long period = 1000;
unsigned long starttime = millis();
unsigned long currenttime;
int state = 0;

void setup()
{
  Serial.begin(115200);
  for (int i = 2; i <= 13; i++)
  {
    pinMode(i, OUTPUT);
  }
  Serial.println("starting in state 0");
}

void fill()
{
  for (int i = 2; i <= 10; i++)
  {
    digitalWrite(i, HIGH);
  }
}

void empty()
{
  for (int i = 2; i <= 10; i++)
  {
    digitalWrite(i, LOW);
  }
}

void loop()
{
  currenttime = millis();
  switch (state)
  {
    case 0:
      digitalWrite(11, HIGH);
      fill();
      if (currenttime - starttime >= period)
      {
        digitalWrite(11, LOW);
        empty();
        starttime = currenttime;
        Serial.println("moving to state 1");
        state = 1;  //switch to next state
      }
      break;
    case 1:
      digitalWrite(12, HIGH);
      fill();
      if (currenttime - starttime >= period)
      {
        digitalWrite(12, LOW);
        empty();
        starttime = currenttime;
        Serial.println("moving to state 2");
        state = 2;
      }
      break;
    case 2:
      digitalWrite(13, HIGH);
      fill();
      if (currenttime - starttime >= period)
      {
        digitalWrite(13, LOW);
        empty();
        starttime = currenttime;
        Serial.println("moving to state 0");
        state = 0;
      }
      break;
  }
}

I'm a bit confused by the time calculations in the switch-cases.

Maybe write to the cube only when its needed. Something like this:

lastUpdate  = millis() - timeToWait;
void loop(){
  now = millis();
  if (now - lastUpdate > timeToWait){
    updateCube();
    lastUpdate = now;
  }
}

currentLevel = 0;
void updateCube(){
  nextLevel = (currentLevel + 1) % 3;
  
  turnOff(currentLevel);	// Probably something like digitalWrite(pin_level_0 + currentLevel, LOW);
  setLeds(nextLevel);		// fill()?
  turnOn(nextLevel);		// Probably something like digitalWrite(pin_level_0 + nextLevel, HIGH);
  
  currentLevel = nextLevel;
}

The code could be written to only once when the state variable changes but the cube won't care how many times you write to it.

The time calculations in the cases are easy. Basically, subtract the start time of the state from the current time and test whether the required period has passed.

Note that the start time for the current state is set when the period for the previous state ends

@UKHeliBob Thanks a lot for your tips and help! The code works.

I am glad to hear it

Do you understand how and why it works and what you had wrong previously ?

Yes, definitely.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.