How to use millis to time specific events

Hello,

I have an OLED display connected to a TCA9548A multiplexer and I'm trying to achieve the following:

  1. Display a white line on the OLED in a blinking fashion by drawing a black and white line repeatedly with an interval of 200 milliseconds.

The codes works fine if I use a delay but I want to use millis as I have multiple displays connected to the multiplexer. I have the basic understanding of millis but just need some guidance incorporating this in my project. Here's the code which is a basic function:

void DisplayEngine(){
uint8_t TimeInterval = 200;
DisplayEngineCurrent = millis();
if (DisplayEngineCurrent - DisplayEngineStart >= TimeInterval){
    tcaselect(0);                               //Select port 0 on the TCA9548A multiplexer
    display.clearDisplay();                     //Clear the display
    display.drawLine(94, 32, 33, 32, 0);        //Display a black line in the middle of the screen
    display.display();                          //Activate the display
    DisplayEngineStart = DisplayEngineCurrent;  //Delay 200 milliseconds
    
    display.clearDisplay();                     //Clear the display
    display.drawLine(94, 32, 33, 32, 1);        //Display a white line in the middle of the screen
    display.display();                          //Activate the display
    DisplayEngineStart = DisplayEngineCurrent;  //Delay 200 milliseconds
    }
  }

The DisplayEngineStart is an unsigned long variable, has a value of "0" and is defined at the top of the sketch.

Any ideas on how to achieve my goal?

Thanks in advance and have a great day!

Your use of millis() looks correct in the function that you posted

Your function needs a new variable that holds the current state of the line (black or white). Make that variable a boolean

When the time period ends, if the boolean is true draw a white line, save the time and flip the state of the boolean.

When the time period ends, if the boolean is false draw a black line, save the time and flip the state of the boolean.

you don't want to clear the screen every time otherwise everything else goes away and you don't really need to repaint the white line in black

something like this may be:

void blinkLineWhenNeeded(bool reset = false) {
  const uint32_t halfPeriod = 200;
  static bool lineIsVisible = false;
  static uint32_t startTime = 0;
  uint32_t currentTime = millis();

  if (reset) {
    startTime = currentTime - halfPeriod;                         // so that it gets triggered right away
    display.clearDisplay();                                       //Clear the display
    lineIsVisible = false;
  }

  if (currentTime - startTime >= halfPeriod) {
    tcaselect(0);                                                 // Select port 0 on the TCA9548A multiplexer
    if (lineIsVisible) display.drawLine(94, 32, 33, 32, 0);       // Display a black line in the middle of the screen
    else display.drawLine(94, 32, 33, 32, 1);                     // Display a white line in the middle of the screen
    display.display();                                            // Transfer to the display
    lineIsVisible = !lineIsVisible;                               // Inverse line state
    startTime = currentTime;
  }
}

you can call the function directly with blinkLineWhenNeeded(); but if you want to reset the state of the blinking you can call blinkLineWhenNeeded(true);. The latter will take into account the current time for the start of the blinking.

(are you sharing multiple physical displays but using only one display instance ?)

1 Like

Omw, thank you so much! I implemented your suggestion in a test sketch where I put the code into loop() just for testing. This is what I came up with:

void loop() {
uint8_t TimeInterval = 200;
DisplayEngineCurrent = millis();  //Save current time in variable
if ((DisplayEngineCurrent - DisplayEngineStart >= TimeInterval) && (LineWhite == false)){ //Time between current time and start of code. If larger than 200ms, proceed
    tcaselect(0);                               //Select port 0 on the TCA9548A multiplexer
    display.clearDisplay();                     //Clear the display
    display.drawLine(94, 32, 33, 32, 1);        //Display a white line in the middle of the screen
    display.display();                          //Clear the display
    DisplayEngineStart = DisplayEngineCurrent;  //Delay 200 milliseconds
    LineWhite = true;                           //Change state of variable
    }

  if ((DisplayEngineCurrent - DisplayEngineStart >= TimeInterval) && (LineWhite == true)){ //Time between current time and start of code. If larger than 200ms, proceed  
    display.clearDisplay();                     //Clear the display
    display.drawLine(94, 32, 33, 32, 0);        //Display a black line in the middle of the screen
    display.display();                          //Clear the display
    DisplayEngineStart = DisplayEngineCurrent;  //Delay 200 milliseconds
    LineWhite = false;                          //Change state of variable
    }
}

I can now successfully select how fast the line should blink by altering the TimeInterval variable.

A bonus question. I'm planning to display a lot more on this display than just this blinking line and looking at the code, this is going to require a lot of if statements. What would be the best practice for this? I'm planning to integrate at least 4 additional displays with similar functionality.

Have a great day!

Edit: The above is not entirely correct. I can somewhat control the speed of the blinking but any value over 800 in TimeInterval makes the line blink faster and not slower. It does what I need it to do but it's not working entirely like expected. Like if you wanted to blink at 1 second intervals.

Thank you for your example above. I'm going to display a lot more than just a blinking line on this particular display but I needed to start somewhere :slight_smile:

My code was just for one display and I'm going to investigate how to replace the if statements with something else.

Have a great day!

in the code you posted above you can see that there is a lot of repeated code (like testing if it's time to update for example) and you missed the tcaselect() call.

You should factor things a bit together to avoid repeating code

void loop() {
  const uint8_t TimeInterval = 200;
  DisplayEngineCurrent = millis();                                      // Save current time in variable
  if (DisplayEngineCurrent - DisplayEngineStart >= TimeInterval) {      // Is it time to update ?
    tcaselect(0);                                                       // Select port 0 on the TCA9548A multiplexer
    display.clearDisplay();                                             // Clear the display
    if (LineWhite) {
      display.drawLine(94, 32, 33, 32, 0);                              // Display a black line in the middle of the screen
    } else {
      display.drawLine(94, 32, 33, 32, 1);                              // Display a white line in the middle of the screen
    }
    display.display();                              
    DisplayEngineStart = DisplayEngineCurrent;                          // update the time of actoin 
    LineWhite = ! LineWhite;                                             // Invert state of variable
  }
}

Do you plan having multiple instances of the display or just share one and rely on tcaselect() to select the right destination ? (assuming you have a demultiplexer)

This is due to the 8-bit size of the storage for TimeInterval:

If you want values larger than 255, use a larger data type. Using uint16_t would enable intervals up to 2^16-1=65535ms.

Thank very much for the code provided! Thanks works too :slight_smile:

Here's the entire code with your changes:

#include <Wire.h>             //Requried to run I2C SH1106
#include <SPI.h>              //Requried to run I2C SH1106
#include <Adafruit_GFX.h>     //https://github.com/adafruit/Adafruit-GFX-Library
#include <Adafruit_SH110X.h>  //https://github.com/adafruit/Adafruit_SH110x

#define SCREEN_WIDTH 128      //OLED display width, in pixels
#define SCREEN_HEIGHT 64      //OLED display height, in pixels
#define OLED_RESET     -1     //Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C   //See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
#define TCAADDR 0x70          //Hardware address for the TCA9548A Multiplexer

unsigned long DisplayEngineStart = 0;    //Type & value of variable
unsigned long DisplayEngineCurrent;      //Type of variable
boolean LineWhite = false;

Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);  //Type of OLED display used

void tcaselect(uint8_t channel) {
  if (channel > 7) return;
  
  Wire.beginTransmission(TCAADDR);
  Wire.write(1 << channel);
  Wire.endTransmission();
}
void setup() {
  
  Wire.begin();
  Serial.begin(9600);
  tcaselect(0);                                       //Select port 0 on the TCA9548A multiplexer
  if(!display.begin(0x3C, true)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;);
  }
  display.clearDisplay();
  display.display();
}

void loop() {
  const uint8_t TimeInterval = 200;
  DisplayEngineCurrent = millis();                                      // Save current time in variable
  if (DisplayEngineCurrent - DisplayEngineStart >= TimeInterval) {      // Is it time to update ?
    tcaselect(0);                                                       // Select port 0 on the TCA9548A multiplexer
    display.clearDisplay();                                             // Clear the display
    if (LineWhite) {
      display.drawLine(94, 32, 33, 32, 0);                              // Display a black line in the middle of the screen
    } else {
      display.drawLine(94, 32, 33, 32, 1);                              // Display a white line in the middle of the screen
    }
    display.display();                              
    DisplayEngineStart = DisplayEngineCurrent;                          // update the time of actoin 
    LineWhite = ! LineWhite;                                             // Invert state of variable
  }
}

I'm using the TCA9548A Multiplexer and I can use the tcaselect() to select which display I want to update. Right now I only have one connected but I'm planning on 5 in total for this project. Plan is to show some very basic animations and then some text at the end like "Engine On", "Engine Off" etc.

Have a great day!

Thanks a lot for this! Looks like I'm learning some new stuff today :grinning:

Have a great day!

@J-M-L has given you the answer to displaying one set of data in a better way. Follow his advice regarding reducing code

As to displaying more than one set of data in the best way, are the displays going to be of the same kind, ie either on or off ?

at the moment you have only one display instance.

you are using a TCA9548A Multiplexer to access the right display you might want to experiment keeping onl y one display and erasing/repainting a new screen after selecting a target display with your TCA9548A Multiplexer or having multiple display instances but this would have an impact on memory ➜ what's your arduino?

Yes, I just tested with one display for starters. I have three connected to my Arduino Leonardo and all three are working as intended at the moment. I need the USB functionality of the Leonardo, so I can send keyboard commands to my PC using the keyboard.h library.

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