Strange problem with a servo slowing down my code...

Hi all! I hope you can help, I'm a little confused...

I have been building up some code to run a dashboard I'm designing for my car, and all has been going reasonably well up until I added a servo to test today (I plan to have 4 eventually for the pointers).

I have added some super simple code to just move the pointer back and forth, and when I run it on its own, the needle moves along nicely and all is well. When I added it to my existing code though (which ran fine with no speed issues when updating the screen to show live values or changing menu pages using a pushbutton), the needle started running extremely slowly.

To try to trace the issue, I started switching off bits of code until I found that the part causing this issue is this:

  if (millis() - lastmillis >= 250){
  if (menu == 1) displayodometer();
  if (menu == 2) display_speedmph();
  if (menu == 3) display_RPM();
  if (menu == 4) display_oil_water();
  if (menu == 7) display_OilP_FuelP();
  if (menu == 8) display_FuelR();
  if (menu == 10) display_BattV();
  if (menu == 11) display_Stats();
  if (menu == 12) display_Acceltimer();
  if (menu == 13) display_servicedist();

If any of these display functions is called, the needle runs slow. And putting this little if statement at the top makes the needle run fast, then slow for a quarter second, then fast again. So it seems to be to do with the OLED screen updating, but the OLED runs as normal with or without the servo. I can't quite understand why that is, can anyone help?

This is running on a Teensy 3.2 for information

Thanks!

DashNoCAN.ino (33.4 KB)

Where do you update "lastmillis" ?

How often?

 //-----------------Update Odometer Counter and Display every second ----------------------------------------------------
//    
   if (millis() - lastmillis >= 1000){ //Update every  second.
  
   SPEED_MPS = SPEED_KPH / (3.6); //convert to distance meters per second 
   distm = SPEED_MPS *(1) ;  // spare conversion factor
   meterssubtotal += distm;  // add meters traveled to a total called meters
   lastmillis = millis(); // Update lasmillis
   updateodometer(); //add the meters traveled to the meters counter 
//

That looks to me to be your answer.

The Adafruit library lets you set the output you want to see on the display in a buffer and send it all to the display when you call display(). That's apparently taking a while. It might be worth confirming that by printing the value of micros before and after a call to display.

I see that there is a method in the wire library: Wire.setClock. It may be possible to ask the device to run faster. Equally of course, the Adafruit library may already be doing this: I didn't look.

AWOL:
That looks to me to be your answer.

I feel you may be right, but could you elaborate a little as I'm struggling to see it... If I move lastmillis = millis(); outside of that argument (or remove the "every second" part) then it still runs slow... What am I doing wrong??

For three-quarters of a second, if (millis() - lastmillis >= 250) is true.

wildbill:
The Adafruit library lets you set the output you want to see on the display in a buffer and send it all to the display when you call display(). That's apparently taking a while. It might be worth confirming that by printing the value of micros before and after a call to display.

I see that there is a method in the wire library: Wire.setClock. It may be possible to ask the device to run faster. Equally of course, the Adafruit library may already be doing this: I didn't look.

Thanks very much for your answer! That sounds plausible, would you mind explaining a little more? I'm not sure where to go with this information...

AWOL:
For three-quarters of a second,

if (millis() - lastmillis >= 250)

is true.

I just put that in to test, taken it back out and and the pointer is now slow all of the time rather than just every quarter of a second...

First of all, I'd prove to myself that the display call is actually slow. I suspect it is, especially since your display requires 624 bytes of buffer data to be sent over the wire. Just printing micros before and after you call it for a single one of your display functions would do.

If that is indeed the culprit, you have a number of possible solutions. Dig into the library and find out how fast wire is running - it takes a number of different speeds and if the display will run at a higher speed, that may be enough to make the delay imperceptible.

If that isn't viable, I'd be tempted to overload the display function to take a pointer to a function that calls your stepper code. I believe that wire sends data in 32 byte packets, so you could try calling it every time the library code needs to start another packet.

Most painful of all, you could examine the data sheet for the display and see whether there are ways to control the display without having to take the all or nothing approach that the library does. I would expect that this would be possible, but rather more trouble.

One other possibility would be to have a second Arduino whose job it is to drive the display. You could send the necessary data to it over serial. You might want to jack up the baud rate a bit though :wink:

wildbill:
First of all, I'd prove to myself that the display call is actually slow. I suspect it is, especially since your display requires 624 bytes of buffer data to be sent over the wire. Just printing micros before and after you call it for a single one of your display functions would do.

If that is indeed the culprit, you have a number of possible solutions. Dig into the library and find out how fast wire is running - it takes a number of different speeds and if the display will run at a higher speed, that may be enough to make the delay imperceptible.

If that isn't viable, I'd be tempted to overload the display function to take a pointer to a function that calls your stepper code. I believe that wire sends data in 32 byte packets, so you could try calling it every time the library code needs to start another packet.

Most painful of all, you could examine the data sheet for the display and see whether there are ways to control the display without having to take the all or nothing approach that the library does. I would expect that this would be possible, but rather more trouble.

Sorry I'm don't quite follow this... I feel like even it the code ran 4x faster it wouldn't be fast enough, so probably looking at option 2 or 3. I'd rather use 1 board if possible. Could you please explain a little better about how you would break the i2c protocol each packet?

Thanks! And sorry I'm learning..

Take a look at the source code of the Adafruit library, in particular the display function. You can see there that it's calling wire.endTransmission and wire.beginTransmission again if the byte count exceeds WIRE_MAX. I'd try calling your stepper code between those two.

Hi Bill,

Thanks for the reply - digging through the .h and .cpp files I see neither of those statements? Should I be looking somewhere else?

Ok I found something reasonably similar to what you're saying. But it's in a slave function (.h/.cpp called by the main code). How would you get this to call a function in the main code half way through executing?

I was looking at the cpp code in Adafruit's GitHub repository.

I'm suggesting that you tweak your library and make a copy of the display function that does exactly the same thing, except that you add a parameter that is a pointer to a function. When you call display, pass a pointer to one of your own functions that operates the stepper.

The reason to use the pointer to function approach is that you can re-use the method in another sketch that uses this particular display hardware rather than hardcoding a reference to something in this sketch in your library.

Thanks Bill,

Ok so I've made a copy of the Adafruit library and am calling that from my main sketch. So I need to find the point at which the buffer is full which I think is this statement:

if(bytesOut >= WIRE_MAX) {

Then I need to run a pointer to the display update function back in the main sketch. I guess this will run and then jump back to the next line in the Adafruit code? So my next question is on how to form the syntax of the the pointer function?

I'm googling but can't find much info on how function pointers work in a similar situation to mine... Any help would be much appreciated!

Here's a little example:

void MoveStepper()
{
  Serial.println("Move stepper");
}

void setup()
{
  Serial.begin(115200);
}
void loop()
{
  void (*fncptr)()=MoveStepper;
  display(fncptr);
  delay(1000);
}

void display(void (*fncptr)())
{
  Serial.println("Display");
  (*fncptr)();
}

The display function in your case will be the one in your adapted library, but this shows the syntax you'll need.

I'm really sorry this is so hard to grasp!

So this part:

(*fncptr)();

goes into the modified adafruit library within the transaction part:

void Adafruit_SSD1306::display(void) {

The rest of it then goes in the main sketch?

What you're doing is giving display a function pointer that lets it execute your function periodically as it does its thing.

So the library display definition has to be changed to take a pointer to a function parameter. You can see what it would look like in the sample I gave you.

When display is between packets (for the first attempt anyway) you need to call your function from within display. Again, my example version of display shows you the syntax for that.

Finally, whenever your sketch calls display, it must pass a pointer to the function you want executed. You can see the loop function doing that in my code above.