(Nested) SWITCH...CASE and millis()

Hey there!

So basically my code has to read sensors and also update displays and so on. I decided to "rotate" through all the different sensors at a given interval since I do not need to update every sensor that often and it gives me some room for time-critical things (like the countdown timer I have in this). I am using nested SWITCH...CASE statements to do my rotation. This for example works perfectly fine:

const uint8_t PHASES = 4;
const uint8_t SUBPHASES = 3;

uint8_t currentPhase = 0;
uint8_t currentSubphase = 0;

void setup()
{
  Serial.begin(115200);
}

void loop(void)
{
  switch (currentPhase)
  {
    case 0:
      Serial.println("Temperature/Climate Sensors:");
      delay(1000);
      switch (currentSubphase)
      {
        case 0:
          Serial.println("DS18B20 #1");
          delay(1000);
          break;
        case 1:
          Serial.println("DS18B20 #2");
          delay(1000);
          break;
        case 2:
          Serial.println("BME680");
          delay(1000);
          break;
      }
      currentSubphase = (currentSubphase + 1) % SUBPHASES;
      break;
    case 1:
      Serial.println("ADCs");
      delay(1000);
      break;
    case 2:
      Serial.println("RFID");
      delay(1000);
      break;
    case 3:
      Serial.println("Timer");
      delay(1000);
      break;
  }
  currentPhase = (currentPhase + 1) % PHASES;
}

Now I need to add the actual timing to this so every (sub)phase will be executed whenever necessary. Therefore I defined some intervals and used the millis() function. Basically it should work like the "blink without delay". Here is my code:

#define SERIAL_BAUDRATE 115200

const uint8_t PHASES = 4;
const uint8_t SUBPHASES = 3;

uint8_t currentPhase = 0;
uint8_t currentSubphase = 0;

uint32_t currentMillis = 0;

uint32_t previousTempClimateMillis = 0;
const int16_t TEMP_CLIMATE_INTERVAL = 1000;

uint32_t previousVoltageMillis = 0;
const int16_t VOLTAGE_INTERVAL = 500;

uint32_t previousRFIDMillis = 0;
const int16_t RFID_INTERVAL = 100;

uint32_t previousTimerMillis = 0;
const int16_t TIMER_INTERVAL = 100;


void setup()
{
  Serial.begin(SERIAL_BAUDRATE);
}


void loop(void)
{
  switch (currentPhase)
  {
    case 0:
      switch (currentSubphase)
      {
        case 0:
          currentMillis = millis();
          if (currentMillis - previousTempClimateMillis >= TEMP_CLIMATE_INTERVAL)
          {
            previousTempClimateMillis = currentMillis;
            Serial.println("DS18B20 #1");
          }
          break;

        case 1:
          currentMillis = millis();
          if (currentMillis - previousTempClimateMillis >= TEMP_CLIMATE_INTERVAL)
          {
            previousTempClimateMillis = currentMillis;
            Serial.println("DS18B20 #2");
          }
          break;

        case 2:
          currentMillis = millis();
          if (currentMillis - previousTempClimateMillis >= TEMP_CLIMATE_INTERVAL)
          {
            previousTempClimateMillis = currentMillis;
            Serial.println("BME680");
          }
          break;
      }
      currentSubphase = (currentSubphase + 1) % SUBPHASES;
      break;

    case 1:
      currentMillis = millis();
      if (currentMillis - previousVoltageMillis >= VOLTAGE_INTERVAL)
      {
        previousVoltageMillis = currentMillis;
        Serial.println("ADCs");
      }
      break;

    case 2:
      currentMillis = millis();
      if (currentMillis - previousRFIDMillis >= RFID_INTERVAL)
      {
        previousRFIDMillis = currentMillis;
        Serial.println("RFID");
      }
      break;

    case 3:
      currentMillis = millis();
      if (currentMillis - previousTimerMillis >= TIMER_INTERVAL)
      {
        previousTimerMillis = currentMillis;
        Serial.println("Timer");
      }
      break;
  }
  currentPhase = (currentPhase + 1) % PHASES;
}

This does not work properly. Actually I does go through the pases correctly but the subphases (in phase 0) are messed up somehow. When I add some serial output to print the current phase and subphase, everything looks normal, but the three subphases are neither executed in the right order nor at the right time.
For instance: DS18B20 #2 is called twice in a row. Or I see DS18B20 #1 and DS18B20 #2 just 20ms apart from each other. Things like this.

If anyone has a clue what might be going on here I'd appreciate any kind of help.

Thanks,
Vulpecula

Your challenge is that you execute

      currentSubphase = (currentSubphase + 1) % SUBPHASES;

regardless of the validation of your wait period. Same for phases.

You need to define how you know you want to go to next subphase or phase - probably when your delay has expired, so embed the change into each 'if'. And as you know what's the value you don't need the math anymore, when subphase 0 is done set it to 1, when subphase 1 is done, set it to 2, then back to 0

    case 0:
      switch (currentSubphase)
      {
        case 0:
          currentMillis = millis();
          if (currentMillis - previousTempClimateMillis >= TEMP_CLIMATE_INTERVAL)
          {
            previousTempClimateMillis = currentMillis;
            Serial.println("DS18B20 #1");
            currentSubphase = 1;
          }
          break;

        case 1:
          currentMillis = millis();
          if (currentMillis - previousTempClimateMillis >= TEMP_CLIMATE_INTERVAL)
          {
            previousTempClimateMillis = currentMillis;
            Serial.println("DS18B20 #2");
            currentSubphase = 2;
          }
          break;

        case 2:
          currentMillis = millis();
          if (currentMillis - previousTempClimateMillis >= TEMP_CLIMATE_INTERVAL)
          {
            previousTempClimateMillis = currentMillis;
            Serial.println("BME680");
            currentSubphase = 0;
          }
          break;
      }
      break;

Note that you could set up your currentMillis at the start of the loop to avoid repeating in every case statements

Hey, thanks for your reply. :slight_smile:

Yes, I have a lot of repetitive code and it did bother me so I have done some fiddeling and came up with this so far:

#define SERIAL_BAUDRATE 115200

const uint8_t PHASES = 4;
const uint8_t SUBPHASES = 3;

uint8_t currentPhase = 0;
uint8_t currentSubphase = 0;

uint32_t currentMillis = 0;

uint32_t previousTempClimateMillis = 0;
const int16_t TEMP_CLIMATE_INTERVAL = 1000;

uint32_t previousVoltageMillis = 0;
const int16_t VOLTAGE_INTERVAL = 500;

uint32_t previousRFIDMillis = 0;
const int16_t RFID_INTERVAL = 100;

uint32_t previousTimerMillis = 0;
const int16_t TIMER_INTERVAL = 100;


void setup()
{
  Serial.begin(SERIAL_BAUDRATE);
}


void loop(void)
{
  currentMillis = millis();
  switch (currentPhase)
  {
    case 0:    
      if (currentMillis - previousTempClimateMillis >= TEMP_CLIMATE_INTERVAL)
      {
        previousTempClimateMillis = currentMillis;
        switch (currentSubphase)
        {
          case 0:
            Serial.println(".: SENSOR 1 :.");
            break;
          case 1:
            Serial.println(".: SENSOR 2 :.");
            break;
          case 2:
            Serial.println(".: SENSOR 3 :.");
            break;
        }
        currentSubphase = (currentSubphase + 1) % SUBPHASES;
      }
      break;

    case 1:
      if (currentMillis - previousVoltageMillis >= VOLTAGE_INTERVAL)
      {
        previousVoltageMillis = currentMillis;
        Serial.println("ADCs");
      }
      break;

    case 2:
      if (currentMillis - previousRFIDMillis >= RFID_INTERVAL)
      {
        previousRFIDMillis = currentMillis;
        Serial.println("RFID");
      }
      break;

    case 3:
      if (currentMillis - previousTimerMillis >= TIMER_INTERVAL)
      {
        previousTimerMillis = currentMillis;
        Serial.println("Timer");
      }
      break;
  }
  currentPhase = (currentPhase + 1) % PHASES;
}

Actually that works fine for the subphases but what happens now is, that sometimes the phase does not advance.

Regardless of that you are right. Setting the phases without doing any math is totally fine. I don't even know why I went for modulo - I guess just habit. ::slight_smile: Does modulo vs. setting a variable actually have any impact on performace? (Not that I have any problems with performance, but I was just wondering.)

Anyways...I'll revamp the code and report back.

You get what you have written in the code.

I still don't understand what you want.
Suppose you have an Arduino with a webserver and I2C and 1-Wire sensors and also analog inputs should be read and a display should be updated and a led should be blinked.

Then the first thing to test is if there is an incoming request for the webserver. That should be tested everytime the loop() runs.

Every sensor and every analog input and every led and every display update can have its own millis() timer. That means that sometimes they might all be active during one loop(). Is that a problem ?
All millis() timers will be in the main level of the loop(), not behind 'if' or 'switch-case'.

When the I2C and the 1-Wire take too long for a fast count-down timer, then you might want to avoid that they both run during one loop().
However, it is not possible to have different intervals for them. That makes no sense.
Have, for example, a single software timer with one second interval, and rotate through the sensors within the software timer. With three sensors, each will be updated once per three seconds. That is straightforward and it works.

A 'if-else' will avoid that multiple software timers will run during one loop(). If two software timers become active, then the first is executed and the second one is delayed for the next loop().

With the 'if-else' the software timers have priority. The first one is the higest priority. I have not yet made an example with priority, but it is on my list as you can see in the very last line of this page.

void loop()
{
  if (currentMillis - previousVoltageMillis >= VOLTAGE_INTERVAL)
  {
    previousVoltageMillis = currentMillis;
    Serial.println("ADCs");
  }
  else if (currentMillis - previousRFIDMillis >= RFID_INTERVAL)
  {
    previousRFIDMillis = currentMillis;
    Serial.println("RFID");
  }
  else if (currentMillis - previousTimerMillis >= TIMER_INTERVAL)
  {
    previousTimerMillis = currentMillis;
    Serial.println("Timer");
  }
}

I don't like a software millis timer within another millis timer. By adding a 'bool' variable it is in my opinion easier to maintain: millis_within_millis.ino

Vulpecula:
So basically my code has to read sensors and also update displays and so on. I decided to "rotate" through all the different sensors at a given interval since I do not need to update every sensor that often and it gives me some room for time-critical things (like the countdown timer I have in this).

Vulpecula:
This does not work properly. Actually I does go through the pases correctly but the subphases (in phase 0) are messed up somehow. When I add some serial output to print the current phase and subphase, everything looks normal, but the three subphases are neither executed in the right order nor at the right time.
For instance: DS18B20 #2 is called twice in a row. Or I see DS18B20 #1 and DS18B20 #2 just 20ms apart from each other. Things like this.

The blink without delay paradigm would be a way of thinking like rotating through all your sensors, but you would use it INSTEAD of rotating through each sensor.

When changing over to the blink without delay paradigm you want to do away with this other phase switch construct. The timers you setup will rotate through your sensors because "it is time" to read this sensor.