How to make 4 pin OLED display run faster

I made a ballistic chronograph (measures speed of projectile between two IR beams) based off of this project and everything seems to be working except the loop isn’t running fast enough to catch the projectile. For example I can drop something through it and it will display the velocity, but it just isn’t fast enough to detect an airsoft round or even a nerf dart fired through it. I’m well aware that OLED displays are slow and I should have taken that into consideration before purchasing an OLED for this project, but is there any way to make it run fast enough for this application? I got this OLED display from amazon and my code is below.

#include <U8glib.h>

// Pins
extern const int PHOTOGATE_1_PIN = 4;  //D4
extern const int PHOTOGATE_2_PIN = 7; //D7

// Distance between photogates in meters and feet
const float DIST_METERS = 0.055;
const float DIST_FEET = 0.1804;

// Phase of detection
int phase = 0;
/*
 * Phase 0 = Idle
 * Phase 1 = First IR Beam Broken
 * Phase 2 = Second IR Beam Broken
 */

// Time photogates are broken in milliseconds
long unsigned int timeFirstBroken = 0;
long unsigned int timeSecondBroken = 0;

// Speed in Meters per Second and Feet per Second respectivly
float speedMS;
float speedFPS;

// State of each photogate (true = broken, false = not broken)
bool photogate1 = false;
bool photogate2 = false;

// Previous values of photogates
bool photogate1Prev = false;
bool photogate2Prev = false;

/* SLboat Add Device */
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_FAST); // I2C 128x64(col2-col129) SH1106,Like HeiTec 1.3' I2C OLED

void u8g_prepare(void) {
  u8g.setFont(u8g_font_6x10);
  u8g.setFontRefHeightExtendedText();
  u8g.setDefaultForegroundColor();
  u8g.setFontPosTop();
}

// Draws two strings displaying the status of each of the two photogates, used for debug purposes
void drawPhotogateStatus()
{
  // Prepare the strings to be drawn
  String val;
  String val1;
  if(digitalRead(PHOTOGATE_1_PIN) == HIGH)
    val = "true";
  else
    val = "false";

  if(digitalRead(PHOTOGATE_2_PIN) == HIGH)
    val1 = "true";
  else
    val1 = "false";
  String str1 = "Photogate 1: " + val;
  String str2 = "Photogate 2: " + val1;

  // Set font
  u8g.setFont(u8g_font_helvB10);
  
  // Draw the strings
  u8g.drawStr(0, 30, str1.c_str());
  u8g.drawStr(0, 60, str2.c_str());
}

// Draws the chronograph
void drawChronograph()
{
  // Set up each string
  String strMS = String(speedMS) + "m/s";
  String strFPS = String(speedFPS) + "f/s";

  // Set the font
  u8g.setFont(u8g_font_helvB12);

  // Draw strings
  if(speedMS != 0)
  {
    u8g.drawStr(0, 30, strMS.c_str());
    u8g.drawStr(0, 60, strFPS.c_str());
  }
  else
    u8g.drawStr(0, 45, "Ready");
}

// Calculates the velocity
void getVelocityBetweenGates(long unsigned int t1, long unsigned int t2)
{
  //Get time between beam breaks
  float t = float(t2 - t1);

  //Convert to seconds
  t = t * 0.001;

  //Find speed with v=d/t
  speedMS = DIST_METERS/t;
  speedFPS = DIST_FEET/t;
}

// sets previous values
void setPrevTrackers()
{
  photogate1Prev = digitalRead(PHOTOGATE_1_PIN) == HIGH;
  photogate2Prev = digitalRead(PHOTOGATE_2_PIN) == HIGH;
}


void setup(void)
{
  // Display Setup
  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);

  // Photogate 1 Setup
  pinMode(PHOTOGATE_1_PIN, INPUT);
  digitalWrite(PHOTOGATE_1_PIN, HIGH);

  // Photogate 2 Setup
  pinMode(PHOTOGATE_2_PIN, INPUT);
  digitalWrite(PHOTOGATE_2_PIN, HIGH);

  setPrevTrackers();
}

void loop(void) {

  // picture loop
  u8g.firstPage();
  do
  {
    u8g_prepare();
    drawChronograph();
  }
  while ( u8g.nextPage() );

  // Store current photogate values
  photogate1 = digitalRead(PHOTOGATE_1_PIN);
  photogate2 = digitalRead(PHOTOGATE_2_PIN);

  // Wait for first photogate to be broken, when it it, proceed to next statement
  if(phase == 0 && photogate1 && photogate1 != photogate1Prev)
  {
    phase = 1;

    // Store current time value
    timeFirstBroken = millis();
  }

  // Wait for the second photogate to be broken, when it is, proceed to next statement
  if(phase == 1 && photogate2 && photogate2 != photogate2Prev)
  {
    phase = 2;

    // Store current time value
    timeSecondBroken = millis();
  }

  // Calculate the velocity, then set phase to 0 to start over again
  if(phase == 2)
  {
    getVelocityBetweenGates(timeFirstBroken, timeSecondBroken);
    phase = 0;
  }

  //Set the previous values
  setPrevTrackers();
}

Update: I didn’t need the picture to be updated every loop, and I know the picture loop is what’s slowing it down, so I tried moving the picture loop into an if statement like this:

#include <U8glib.h>

// Pins
extern const int PHOTOGATE_1_PIN = 4;  //D4
extern const int PHOTOGATE_2_PIN = 7; //D7

// Distance between photogates in meters and feet
const float DIST_METERS = 0.055;
const float DIST_FEET = 0.1804;

// Phase of detection
int phase = 0;
/*
 * Phase 0 = Idle
 * Phase 1 = First IR Beam Broken
 * Phase 2 = Second IR Beam Broken
 */

// Time photogates are broken in milliseconds
long unsigned int timeFirstBroken = 0;
long unsigned int timeSecondBroken = 0;

// Speed in Meters per Second and Feet per Second respectivly
float speedMS;
float speedFPS;

// State of each photogate (true = broken, false = not broken)
bool photogate1 = false;
bool photogate2 = false;

// Previous values of photogates
bool photogate1Prev = false;
bool photogate2Prev = false;

/* SLboat Add Device */
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_FAST); // I2C 128x64(col2-col129) SH1106,Like HeiTec 1.3' I2C OLED

void u8g_prepare(void) {
  u8g.setFont(u8g_font_6x10);
  u8g.setFontRefHeightExtendedText();
  u8g.setDefaultForegroundColor();
  u8g.setFontPosTop();
}

// Draws two strings displaying the status of each of the two photogates, used for debug purposes
void drawPhotogateStatus()
{
  // Prepare the strings to be drawn
  String val;
  String val1;
  if(digitalRead(PHOTOGATE_1_PIN) == HIGH)
    val = "true";
  else
    val = "false";

  if(digitalRead(PHOTOGATE_2_PIN) == HIGH)
    val1 = "true";
  else
    val1 = "false";
  String str1 = "Photogate 1: " + val;
  String str2 = "Photogate 2: " + val1;

  // Set font
  u8g.setFont(u8g_font_helvB10);
  
  // Draw the strings
  u8g.drawStr(0, 30, str1.c_str());
  u8g.drawStr(0, 60, str2.c_str());
}

// Draws the chronograph
void drawChronograph()
{
  // Set up each string
  String strMS = String(speedMS) + "m/s";
  String strFPS = String(speedFPS) + "f/s";

  // Set the font
  u8g.setFont(u8g_font_helvB12);

  // Draw strings
  if(speedMS != 0)
  {
    u8g.drawStr(0, 30, strMS.c_str());
    u8g.drawStr(0, 60, strFPS.c_str());
  }
  else
    u8g.drawStr(0, 45, "Ready");
}

// Calculates the velocity
void getVelocityBetweenGates(long unsigned int t1, long unsigned int t2)
{
  //Get time between beam breaks
  float t = float(t2 - t1);

  //Convert to seconds
  t = t * 0.001;

  //Find speed with v=d/t
  speedMS = DIST_METERS/t;
  speedFPS = DIST_FEET/t;
}

// sets previous values
void setPrevTrackers()
{
  photogate1Prev = digitalRead(PHOTOGATE_1_PIN) == HIGH;
  photogate2Prev = digitalRead(PHOTOGATE_2_PIN) == HIGH;
}


void setup(void)
{
  // Display Setup
  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);

  // Photogate 1 Setup
  pinMode(PHOTOGATE_1_PIN, INPUT);
  digitalWrite(PHOTOGATE_1_PIN, HIGH);

  // Photogate 2 Setup
  pinMode(PHOTOGATE_2_PIN, INPUT);
  digitalWrite(PHOTOGATE_2_PIN, HIGH);

  setPrevTrackers();
}

void loop(void) {

  // Store current photogate values
  photogate1 = digitalRead(PHOTOGATE_1_PIN);
  photogate2 = digitalRead(PHOTOGATE_2_PIN);

  // Wait for first photogate to be broken, when it it, proceed to next statement
  if(phase == 0 && photogate1 && photogate1 != photogate1Prev)
  {
    phase = 1;

    // Store current time value
    timeFirstBroken = millis();
  }

  // Wait for the second photogate to be broken, when it is, proceed to next statement
  if(phase == 1 && photogate2 && photogate2 != photogate2Prev)
  {
    phase = 2;

    // Store current time value
    timeSecondBroken = millis();
  }

  // Calculate the velocity, then set phase to 0 to start over again
  if(phase == 2)
  {
    getVelocityBetweenGates(timeFirstBroken, timeSecondBroken);
    phase = 0;

      // picture loop
    u8g.firstPage();
    do
    {
      u8g_prepare();
      drawChronograph();
    }
    while ( u8g.nextPage() );
  }

  //Set the previous values
  setPrevTrackers();
}

This produced extremely inconsistent results, but I actually did get the correct velocity displaying when it worked. I know the picture loop isn’t supposed to be used that way, but is there a way to not have it execute unless it needs to?

Can you trigger an interrupt with the IR sensor instead of polling it in the loop?
The detection would then be independent of the loop speed.
In the interrupt service routine you would capture the detection time in milli or microseconds.
On say a uno, this would mean using pins 2 or 3.

There are actually many things, which could be improved in the code.

  • In u8g_prepare, a font is set, which is replaced late by a different font. This will waste flash ROM space
  • Try to reduce the number of commands inside the picture loop. For example, you could easily call u8g_prepare() from outside the loop
  • Within drawPhotogateStatus() you call digitalRead and you calculate several strings. For speedup, this also should happen outside the picture loop. Of course you need to place the strings into global variables
  • You could also move to u8g2, which offers a full buffer mode. If you have sufficient RAM, full buffer mode will be much faster. With full buffer mode you also do not need to implement suggestions 2 and 3.
  • If your application allows this, you could replace the picture loop with a state machine. This is a little bit advanced and also may lead to visible artefacts

Oliver

I have created an example for my suggestion no. 5:

It is actually for U8g2, but sould be work in the same way for U8g.

Oliver

Thanks for all the replies :). I used olikraus’ solution, and it now works, but inconsistently. It works sometimes, but other times it misses the beam break or only one of them triggers. Is there a way to make it even faster? My modified code is below:

#include <U8glib.h>

// Pins
extern const int PHOTOGATE_1_PIN = 7;  //D7
extern const int PHOTOGATE_2_PIN = 4; //D4

// Distance between photogates in meters and feet
const float DIST_METERS = 0.055;
const float DIST_FEET = 0.1804;

// Phase of detection
int phase = 0;
/*
 * Phase 0 = Idle
 * Phase 1 = First IR Beam Broken
 * Phase 2 = Second IR Beam Broken
 */

// Time photogates are broken in milliseconds
long unsigned int timeFirstBroken = 0;
long unsigned int timeSecondBroken = 0;

// Speed in Meters per Second and Feet per Second respectivly
float speedMS;
float speedFPS;

// State of each photogate (true = broken, false = not broken)
bool photogate1 = false;
bool photogate2 = false;

// Previous values of photogates
bool photogate1Prev = false;
bool photogate2Prev = false;

/* SLboat Add Device */
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_FAST); // I2C 128x64(col2-col129) SH1106,Like HeiTec 1.3' I2C OLED

void u8g_prepare(void) {
  // Set font
  u8g.setFont(u8g_font_helvB10);
}

// Draws two strings displaying the status of each of the two photogates, used for debug purposes
void drawPhotogateStatus()
{
  // Prepare the strings to be drawn
  String val;
  String val1;
  if(digitalRead(PHOTOGATE_1_PIN) == HIGH)
    val = "true";
  else
    val = "false";

  if(digitalRead(PHOTOGATE_2_PIN) == HIGH)
    val1 = "true";
  else
    val1 = "false";
  String str1 = "Photogate 1: " + val;
  String str2 = "Photogate 2: " + val1;
  
  // Draw the strings
  u8g.drawStr(0, 30, str1.c_str());
  u8g.drawStr(0, 60, str2.c_str());
}

// Draws the chronograph
void drawChronograph()
{
  // Set up each string
  String strMS = String(speedMS) + "m/s";
  String strFPS = String(speedFPS) + "f/s";

  // Set the font
  u8g.setFont(u8g_font_helvB12);

  // Draw strings
  if(speedMS != 0)
  {
    u8g.drawStr(0, 30, strMS.c_str());
    u8g.drawStr(0, 60, strFPS.c_str());
  }
  else
    u8g.drawStr(0, 45, "Ready");
}

// Calculates the velocity
void getVelocityBetweenGates(long unsigned int t1, long unsigned int t2)
{
  //Get time between beam breaks
  float t = float(t2 - t1);

  //Convert to seconds
  t = t * 0.001;

  //Find speed with v=d/t
  speedMS = DIST_METERS/t;
  speedFPS = DIST_FEET/t;
}

//Fast Loop
void fastLoop()
{
  // Store current photogate values
  photogate1 = digitalRead(PHOTOGATE_1_PIN);
  photogate2 = digitalRead(PHOTOGATE_2_PIN);

  // Wait for first photogate to be broken, when it it, proceed to next statement
  if(phase == 0 && photogate1 && photogate1 != photogate1Prev)
  {
    phase = 1;

    // Store current time value
    timeFirstBroken = millis();
  }

  // Wait for the second photogate to be broken, when it is, proceed to next statement
  if(phase == 1 && photogate2 && photogate2 != photogate2Prev)
  {
    phase = 2;

    // Store current time value
    timeSecondBroken = millis();
  }

  // Calculate the velocity, then set phase to 0 to start over again
  if(phase == 2)
  {
    getVelocityBetweenGates(timeFirstBroken, timeSecondBroken);
    phase = 0;
  }

  //Set the previous values
  setPrevTrackers();
}

// sets previous values
void setPrevTrackers()
{
  photogate1Prev = digitalRead(PHOTOGATE_1_PIN) == HIGH;
  photogate2Prev = digitalRead(PHOTOGATE_2_PIN) == HIGH;
}

void draw_page(void) 
{
  static uint8_t is_next_page = 0;
  
  // call to first page, if required
  if ( is_next_page == 0 )
  {
    u8g.firstPage();
    is_next_page = 1;
  }
  
  // draw our screen
  drawChronograph();
  
  // call to next page
  if ( u8g.nextPage() == 0 ) {
    is_next_page = 0;      // ensure, that first page is called
  }  
}


void setup(void)
{
  // Display Setup
  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);

  // Photogate 1 Setup
  pinMode(PHOTOGATE_1_PIN, INPUT);
  digitalWrite(PHOTOGATE_1_PIN, HIGH);

  // Photogate 2 Setup
  pinMode(PHOTOGATE_2_PIN, INPUT);
  digitalWrite(PHOTOGATE_2_PIN, HIGH);

  setPrevTrackers();

  u8g.begin();
}

void loop(void) 
{
  fastLoop();
  draw_page();
}

To shave a few microseconds of the execution time, you could use direct port manipulation to read the pins instead of digitalRead() described, for example, here: Arduino - PortManipulation.

But, if you are still struggling because of the duration of the activities in the loop, then looks at an example of using interrupts to read an IR sensor. Here is one of many: https://core-electronics.com.au/tutorials/infrared-sensors-arduino.html

KonoDioDa:
Thanks for all the replies :). I used olikraus' solution, and it now works, but inconsistently. It works sometimes, but other times it misses the beam break or only one of them triggers. Is there a way to make it even faster? ....

To make it faster - maybe don't use Arduino framework but native code support for this particular project.
What board are you using?

The obvious solution is not to do anything display related until you have a reading.
After that the speed of the display is not important.
Move everything display related out of loop() and call it only when you're ready to show something.

Use this instead, does the same but it makes more sense.

  pinMode(PHOTOGATE_1_PIN, INPUT_PULLUP);
  pinMode(PHOTOGATE_2_PIN, INPUT_PULLUP)

ocsav:
. . .

Use this instead, does the same but it makes more sense.

  pinMode(PHOTOGATE_1_PIN, INPUT_PULLUP);

pinMode(PHOTOGATE_2_PIN, INPUT_PULLUP)

This would depend on the characteristics of the specific optical sensor the OP is using. There is a large variety. Some are open collector types and require a pullup resistor. Other have push/pull output or come with a built-in pullup. The project the OP followed specified an OPL55C. I can't find a data sheet for this specific one, but here is one for related products http://www.farnell.com/datasheets/2331797.pdf