Nano GPS Speedometer OLED SSD1306 Display Controlling Led Strip - Runs Too Slow

Been working on a project for my son's Crazy Cart but this same project could also be applied to a bicycle.

A GPS OLED Speedometer that also controls an WS2818B Led Strip using the FastLED Library.
LED Pattern changes depending on speed.

I am using a Nano and a .96" I2C OLED display with a NEO M6N GPS.

No matter what I try, when I am running both FastLEd and the display functions everything runs too slow.

GPS with just FASTLed function runs good.

GPS with just Display runs good.

I have GPS set to direct Serial and have tried multiple SSD1306 libraries.

It does run fine on an ESP8266.

Is I2C too slow to update the display quick enough or is this too much for a Nano?

If I move

FastLED.show();

into the main loop it runs even slower. And by slow I mean the display and leds are in "slow motion".

Appreciate any help!

Sketch Attached:

GPS_Speedometer_U8G2.ino (8.09 KB)

I am using a... NEO M6N GPS.

Are you sure? The NAV-PVT message is only available on NEO 7 and 8, according to the specs. Just curious...

If I move

    FastLED.show();

into the main loop it runs even slower. And by slow I mean the display and leds are in "slow motion".

That makes sense, because you would be reloading the WS2812 string on every loop iteration. :stuck_out_tongue:

No matter what I try, when I am running both FastLEd and the display functions everything runs too slow.

Yes, that's (obviously) quite a load for a Nano, but there are a few things to you can do. I think the biggest problems are

1) GPS and OLED updates @ 10Hz

The display is tiny, the speed digits are tiny, the spinner is tiny... Are you sure? I would knock the GPS updates down to 1Hz and OLED updates to 4Hz for the spinner.

2) OLED and FastLED updates aren't sync'ed to the GPS updates

If you look at loop, the GPS updates and the screen updates run independently. The best time to do things is right after a GPS update comes in. Then the serial port is quiet for a while (no interrupts). As it is, the GPS character interrupts could be occurring while you're trying to update the screen and the LEDs. FastLED will interfere with the GPS data, too.

I would suggest something like this:

void loop() {
  if ( processGPS() ) {
    numSV = pvt.numSV;
    gSpeed = pvt.gSpeed;
    hMSL = pvt.hMSL;    
    gpsUpdateSpinnerPos = (gpsUpdateSpinnerPos + 1) % 4;
//  }
  

//  unsigned long now = millis();
//  if ( now - lastScreenUpdate > 100 ) {
    updateScreen();
//    lastScreenUpdate = now;
    screenRefreshSpinnerPos = (screenRefreshSpinnerPos + 1) % 4; // Are you sure you need this?
  }
}

This will still update the screen at 10Hz, but it will do it right after the GPS goes quiet for a while.

Also, it might help to move the FastLEDs.show before the OLED draw:

void updateScreen()
{
  //int kmh = gSpeed * 0.0036;
  int mph = gSpeed * 0.002237;

  if (mph < 2 ) {
      sinelon();
  } else if (mph < 6 && mph >= 2 ) {
      juggle();
  } else {
      bpm();
  } 
    
  FastLED.show(); 
  //EVERY_N_MILLISECONDS( 20 ) { gHue++; } 

  sprintf(speedBuf, "%3d", mph);
  sprintf(satsBuf, "%c %c %d", spinner[screenRefreshSpinnerPos], spinner[gpsUpdateSpinnerPos], numSV);
  int feet = hMSL / 304.8;
  sprintf(heightBuf, "%5d", feet);
  u8g2.firstPage();
  do {
    draw();
  } while( u8g2.nextPage() );  
  
}

The OLED update can be interrupted by GPS characters, if they start coming in again, before updateScreen is completed. The FastLED.show cannot be interrupted, so those GPS characters would be lost.

There are several other things, but these might be enough.

Cheers,
/dev

/dev

Thanks for the help!

it is definitely a NEO-6M I just opened it up to check. And it is working.

I made the changes you suggested and even removed the screen update spinner but LEDs are still slow.

New Loop:

void loop() {
  
    if ( processGPS() ) {
       numSV = pvt.numSV;
       gSpeed = pvt.gSpeed;
       hMSL = pvt.hMSL;    
       gpsUpdateSpinnerPos = (gpsUpdateSpinnerPos + 1) % 4;
       updateScreen();
    } 
}

And new updateScreen:

void updateScreen()
{
  //int kmh = gSpeed * 0.0036;
  int mph = gSpeed * 0.00225; //Speedo calibrated

  if (mph < 2 ) {
      sinelon();
  } else if (mph < 7 && mph >= 2 ) {
      juggle();
  } else {
      bpm();
  }
  FastLED.show();    
  sprintf(speedBuf, "%3d", mph);
  sprintf(satsBuf, "%d %c", numSV, spinner[gpsUpdateSpinnerPos] );
  //sprintf(satsBuf, "%c %c %d", spinner[screenRefreshSpinnerPos], spinner[gpsUpdateSpinnerPos], numSV);
  int feet = hMSL / 304.8;
  sprintf(heightBuf, "%5d", feet); 
  u8g2.firstPage();
  do {
    draw();
  } while( u8g2.nextPage() );   
}

Now here comes the stupid question :wink:

What is the best way to knock down the GPS updates 1Hz and OLED updates to 4Hz?

I'd like to try that before giving up on the Nano.

Thanks agaiN!

There's some bytes at the top that are sent to configure the ublox. Comment out the 10Hz and uncomment the 1Hz:

 // Rate
  //0xB5,0x62,0x06,0x08,0x06,0x00,0x64,0x00,0x01,0x00,0x01,0x00,0x7A,0x12, //(10Hz)
  //0xB5,0x62,0x06,0x08,0x06,0x00,0xC8,0x00,0x01,0x00,0x01,0x00,0xDE,0x6A, //(5Hz)
  0xB5,0x62,0x06,0x08,0x06,0x00,0xE8,0x03,0x01,0x00,0x01,0x00,0x01,0x39 //(1Hz)

That will make the GPS run @ 1Hz.

Next, make the updates run off of a delta from each GPS update:

void loop()
{
  static uint32_t lastScreenUpdate;
  const  uint8_t  SCREEN_UPDATE_FREQ   = 4; // Hz
  const  uint8_t  SCREEN_UPDATE_PERIOD = 1000UL/(uint32_t) SCREEN_UPDATE_FREQ; // ms
  static uint8_t  updateNumber         = SCREEN_UPDATE_FREQ;
  
  if ( processGPS() ) {
    numSV               = pvt.numSV;
    gSpeed              = pvt.gSpeed;
    hMSL                = pvt.hMSL;
    gpsUpdateSpinnerPos = (gpsUpdateSpinnerPos + 1) % 4;
    updateNumber        = 0;
  }

  if ((updateNumber == 0) ||
      ( (updateNumber < SCREEN_UPDATE_FREQ) && 
        (millis() - lastScreenUpdate >= SCREEN_UPDATE_PERIOD) )) {
    lastScreenUpdate = millis();
    updateScreen();
    updateNumber++;
  } 
}

That will update the screen right after a GPS update, and once every 250ms for 3 times after that. I think. :slight_smile:

Cheers,
/dev

P.S. This will make the LEDs update at 4Hz. That's about twice as slow as on other Arduinos. If this works, I would suggest separating the OLED update from the FastLED update. Then you might be able to bump the FastLED rate up and the OLED rate down.

You got me really close with the following code but then I think I accidentally corrupted the bootloader when putting the GPS RX jumper cable on!

I'll order another Arduino and pick this up next week. Should I get another Nano or something else?

Thanks again for all your help, I really appreciate it.

Changed the GPS to 1Hz as you suggested and this still seemed to update fine.

void loop()
{
  static uint32_t lastScreenUpdate;
  const  uint8_t  SCREEN_UPDATE_FREQ   = 4; // Hz
  const  uint8_t  SCREEN_UPDATE_PERIOD = 1000UL/(uint32_t) SCREEN_UPDATE_FREQ; // ms
  static uint8_t  updateNumber         = SCREEN_UPDATE_FREQ;
  
  if ( processGPS() ) {
    numSV               = pvt.numSV;
    gSpeed              = pvt.gSpeed;
    hMSL                = pvt.hMSL;
    gpsUpdateSpinnerPos = (gpsUpdateSpinnerPos + 1) % 4;
    updateNumber        = 0;
  }

  if ((updateNumber == 0) ||
      ( (updateNumber < SCREEN_UPDATE_FREQ) && 
        (millis() - lastScreenUpdate >= SCREEN_UPDATE_PERIOD) )) {
    lastScreenUpdate = millis();
    updateScreen();
    updateNumber++;
  } 

  unsigned long now = millis();
  if ( now - lastLEDUpdate > 50 ) {
    updateLED();
  lastLEDUpdate = now;
  }
  
  
}

New functions

void updateScreen()
{
  int mph = gSpeed * 0.00225; //Speedo calibrated   
  sprintf(speedBuf, "%3d", mph);
  sprintf(satsBuf, "%d %c", numSV, spinner[gpsUpdateSpinnerPos] );
  //sprintf(satsBuf, "%c %c %d", spinner[screenRefreshSpinnerPos], spinner[gpsUpdateSpinnerPos], numSV);
  int feet = hMSL / 304.8;
  sprintf(heightBuf, "%5d", feet); 
  u8g2.firstPage();
  do {
    draw();
  } while( u8g2.nextPage() );   
}


void updateLED()
{
  //int kmh = gSpeed * 0.0036;
  int mph = gSpeed * 0.00225; //Speedo calibrated
  //if (mph != prevSpeed) {
    if (mph < 3 ) {
    sinelon();
    } else if (mph < 7 && mph >= 3 ) {
      juggle();
    } else {
      bpm();
    }
  //prevSpeed = mph;
 //}
  FastLED.show(); 
}

RichAP:
You got me really close with the following code but then I think I accidentally corrupted the bootloader when putting the GPS RX jumper cable on!

You can use another Arduino as an ISP. It will upload a program, including the bootloader, through a few of the Nano pins instead of through the USB port. Here's a really good description from Nick, with lots of links at the end. Here's one from this forum.

I'll order another Arduino and pick this up next week. Should I get another Nano or something else?

From what I can see, a 16MHz Arduino can support up to ~150 LEDs. I'm pretty sure you can get your 90 LEDs updating at 10Hz (the original speed). As you already know, an ESP8266 is up to the task. Other 32-bit MCUs should also be fine: Teensy 3.x, Arduino Due or a Zero. Of those, only the Teensy has the same size as your Nano, if that matters.

Changed the GPS to 1Hz as you suggested and this still seemed to update fine.

What do you mean by "update fine"?

  unsigned long now = millis();

if ( now - lastLEDUpdate > 50 ) {
   updateLED();
 lastLEDUpdate = now;
 }

LOL, that will try to update the LEDs @ 20Hz, and it's not sync'ed with the GPS updates. I would suggest updating the LEDs before the display, and going back to 10Hz:

void formatInt( int i, int factor, char *buf )
{
  uint8_t len        = 0;
  while ((factor > 1) && (i >= factor)) {
    buf[ len++ ] = ' ';
    factor /= 10;
  }
  itoa( i, &buf[ len ], 10 );
}

void loop()
{
  static uint32_t lastLEDUpdate;
  const  uint8_t  LED_UPDATE_FREQ   = 10; // Hz
  const  uint8_t  LED_UPDATE_PERIOD = 1000UL/(uint32_t) LED_UPDATE_FREQ; // ms
  static uint8_t  ledUpdateNumber   = LED_UPDATE_FREQ;

  static uint32_t lastScreenUpdate;
  const  uint8_t  SCREEN_UPDATE_FREQ   = 1; // Hz
  const  uint8_t  SCREEN_UPDATE_PERIOD = 1000UL/(uint32_t) SCREEN_UPDATE_FREQ; // ms
  static uint8_t  screenUpdateNumber   = SCREEN_UPDATE_FREQ;

  if ( processGPS() ) {
    gpsUpdateSpinnerPos = (gpsUpdateSpinnerPos + 1) % 4;

    // Update buffers now.

    formatInt( pvt.numSV, 10, satsBuf );
    uint8_t len = strlen( satsBuf );
    satsBuf[ len++ ] = ' ';
    satsBuf[ len++ ] = spinner[gpsUpdateSpinnerPos];
    satsBuf[ len   ] = '\0'; // NUL-terminate the C string

    //kmh      = (pvt.gSpeed * 36) / 10000L;
    mph        = (pvt.gSpeed * 225) / 100000L; // integer math is faster!
    formatInt( mph, 100, speedBuf );

    int feet   = (pvt.hMSL * 10) / 3048;
    formatInt( feet, 10000, heightBuf );

    //  Restart LED and screen updates

    screenUpdateNumber  = 0;
    ledUpdateNumber     = 0;
  }

  unsigned long now = millis();

  // Time to update the LEDs?

  if ((ledUpdateNumber == 0) ||
      ( (ledUpdateNumber < LED_UPDATE_FREQ) && 
        (now - lastLEDUpdate >= LED_UPDATE_PERIOD) )) {
    lastLEDUpdate = now;
    updateLED();
    ledUpdateNumber++;
  }

  // Time to update the screen?

  if ((screenUpdateNumber == 0) ||
      ( (screenUpdateNumber < SCREEN_UPDATE_FREQ) && 
        (now - lastScreenUpdate >= SCREEN_UPDATE_PERIOD) )) {
    lastScreenUpdate = now;
    updateScreen();
    screenUpdateNumber++;
  }

  
}

Notice that the buffers are only updated after a GPS update. You can remove that code from updateScreen:

void updateScreen()
{
  u8g2.firstPage();
  do {
    draw();
  } while( u8g2.nextPage() );   
}

Actually, there's no reason to call updateScreen @ 4Hz anymore, so I changed that above.

This eliminates sprintf by using formatInt instead, and also eliminates floating-point arithmetic. Your sketch should be smaller by several thousand bytes and a little bit faster. :slight_smile:

If all this works, you can try increasing the LED_UPDATE_FREQ.

Cheers,
/dev

Just got my Nano bootloader reloaded and now working on this again.(thanks for the help with that!)

I also picked up an SPI Oled (SH1106) vs the SSD1306 I2C thinking the SPI might be faster.

Will work on implementing your code updates today and report back!

Great success!

A couple of things:

I am running the SPI Display instead of the I2C display:

U8G2_SH1106_128X64_NONAME_2_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 10, /* data=*/ 9, /* cs=*/ 12, /* dc=*/ 11, /* reset=*/ 13);

GPS is at 5Hz

 // Rate
  //0xB5,0x62,0x06,0x08,0x06,0x00,0x64,0x00,0x01,0x00,0x01,0x00,0x7A,0x12, //(10Hz)
  0xB5,0x62,0x06,0x08,0x06,0x00,0xC8,0x00,0x01,0x00,0x01,0x00,0xDE,0x6A, //(5Hz)
  //0xB5,0x62,0x06,0x08,0x06,0x00,0xE8,0x03,0x01,0x00,0x01,0x00,0x01,0x39 //(1Hz)

I've been playing with different LED Update Frequencies but have it at 60Hz right now.

static uint32_t lastLEDUpdate;
  const  uint8_t  LED_UPDATE_FREQ   = 60; // Hz
  const  uint8_t  LED_UPDATE_PERIOD = 1000UL/(uint32_t) LED_UPDATE_FREQ; // ms
  static uint8_t  ledUpdateNumber   = LED_UPDATE_FREQ;

The more LEDs I had defined in FastLed, the more issues I ran into with is displaying smoothly. I now have it setup for 3 simultaneous strips of 30 leds each(1.5 meters) which is perfect for my project anyhow.

Thanks again to /dev for all your help with this.

I will continue to update as the project progresses. My 8 year old is excited to get this all mounted on his Crazy Cart!

Demo video here: Arduino Nano NEO M6N GPS OLED Speedometer Controlling WS2812B LED Strips - YouTube