millis() slowing things down ?

Status
Not open for further replies.

Headroom

Well-known member
The code below works in conjunction with my High Power RGB LED shield http://ledshield.wordpress.com. The shield is connected to the Teensy3 per I2C bus.
The core routine is an adapted 3D Bresenham algorithm to with the three coordinates being red, green, blue. each time through the main loop the algorithm advances one pixel in 3D RGB space (I guess in that case it's really a voxel )

When out-commenting the blink-without-delay code marked as such in the main loop the fading algorithm runs substantially faster than with it enabled (the value for "interval" is set to 0). The rough measurement for an 8-bit fade from r,g,b 0,0,0 --> 255,255,255 appears to be dependent on which frequency I am running the I2C bus with. Going from 100kHz to 2.4MHz results in a 10x increase. When the blink-without-delay code is enabled there is no increase in execution speed beyond 4ookHz.

Is there an alternative method to mills() or am I using this incorrectly or inefficiently ?

Without blink-without-delay code

100 kHz --> 337 ms
400 kHz --> 97 ms
600 kHz --> 75 ms
800 kHz --> 58 ms
1000 kHz --> 53 ms
1200 kHz --> 47 ms
1500 kHz --> 42 ms
2000 kHz --> 36 ms
2400 kHz --> 33 ms

with blink-without-delay

100kHz --> 337 ms
400kHz --> 256 ms
600kHz --> 256 ms
800kHz --> 256 ms
1000kHz --> 256 ms
1200kHz --> 256 ms
1500kHz --> 256 ms
2000kHz --> 256 ms
2400kHz --> 256 ms

Code:
#include "i2c_t3.h"
#include "HPRGB2.h"

long interval = 0;
long previousMillis = 0;
long previousMillisTimer = 0;
int current;

class RGBFader: 
public HPRGB {
public:
  RGBFader (uint8_t mcp4728ID = 0x00, uint8_t pca9685ID = 0x00);
  void randomLineInit(void);
  void randomLine(void);
  void rgbFade(uint8_t x1, uint8_t y1, uint8_t z1,uint8_t x2,uint8_t y2,uint8_t z2);
  
private:
  int16_t  xd, yd, zd;
  uint8_t  x, y, z;
  uint16_t ax, ay, az;
  int8_t   sx, sy, sz;
  int16_t  dx, dy, dz;
  uint8_t  x1, y1, z1;
  uint8_t  x2, y2, z2;
  uint8_t  r, g, b;
  boolean newLine;
  boolean (RGBFader::*pLine)();
  uint16_t MAX(uint16_t a, uint16_t b);
  uint16_t ABS(int16_t a);
  int8_t ZSGN(int16_t a);
  boolean lineXdominant(void);
  boolean lineYdominant(void);
  boolean lineZdominant(void);
};

RGBFader::RGBFader(uint8_t mcp4728ID, uint8_t pca9685ID)
{
  _mcp4728ID = mcp4728ID;
  _mcp4728_address = (MCP4728_BASE_ADDR | _mcp4728ID);
  _pca9685ID = pca9685ID;
  _pca9685_address = (PCA9685_BASE_ADDR | _pca9685ID);
}

/* find maximum of a and b */
inline uint16_t RGBFader::MAX(uint16_t a, uint16_t b) { 
  return (a > b) ? a : b;
}

/* absolute value of a */
inline uint16_t RGBFader::ABS(int16_t a) { 
  return (a < 0) ? -a : a;
}

/* take sign of a, either -1, 0, or 1 */
inline int8_t RGBFader::ZSGN(int16_t a) { 
  return (a < 0) ? -1 : a > 0 ? 1 : 0;
} 

void RGBFader::randomLineInit(void){

    uint8_t rand;
    rand = random (1, 4);
    
    x1 = 0;
    y1 = 0;
    z1 = 0;
    
    if (rand==1) { x2=255; } else { x2= random(0, 256); };
    if (rand==2) { y2=255; } else { y2= random(0, 256); };
    if (rand==3) { z2=255; } else { z2= random(0, 256); };

    x = x1;
    y = y1;
    z = z1;
    
    dx = x2 - x1;
    dy = y2 - y1;
    dz = z2 - z1;
      
    ax = ABS(dx) << 1;
    ay = ABS(dy) << 1;
    az = ABS(dz) << 1;

    sx = ZSGN(dx);
    sy = ZSGN(dy);
    sz = ZSGN(dz);

    if (ax >= MAX(ay, az)){                 /* x dominant */
      yd = ay - (ax >> 1);
      zd = az - (ax >> 1);
      pLine = &RGBFader::lineXdominant;
    }
    else if (ay >= MAX(ax, az)){            /* y dominant */
      xd = ax - (ay >> 1);
      zd = az - (ay >> 1);
      pLine = &RGBFader::lineYdominant;
    }
    else if (az >= MAX(ax, ay)){            /* z dominant */
      xd = ax - (az >> 1);
      yd = ay - (az >> 1);
      pLine = &RGBFader::lineZdominant;
    }
}

void RGBFader::rgbFade(uint8_t x1, uint8_t y1, uint8_t z1, uint8_t x2, uint8_t y2, uint8_t z2){
    newLine = (this->*pLine) ();
    if (newLine){
      
    unsigned long currentMillis = micros();            
    Serial.println(currentMillis-previousMillisTimer);
    previousMillisTimer = currentMillis;
    
    x = x1;
    y = y1;
    z = z1;

    dx = x2 - x1;
    dy = y2 - y1;
    dz = z2 - z1;

    ax = ABS(dx) << 1;
    ay = ABS(dy) << 1;
    az = ABS(dz) << 1;

    sx = ZSGN(dx);
    sy = ZSGN(dy);
    sz = ZSGN(dz);

    if (ax >= MAX(ay, az)){                 /* x dominant */
      yd = ay - (ax >> 1);
      zd = az - (ax >> 1);
      pLine = &RGBFader::lineXdominant;
    }
    else if (ay >= MAX(ax, az)){            /* y dominant */
      xd = ax - (ay >> 1);
      zd = az - (ay >> 1);
      pLine = &RGBFader::lineYdominant;
    }
    else if (az >= MAX(ax, ay)){            /* z dominant */
      xd = ax - (az >> 1);
      yd = ay - (az >> 1);
      pLine = &RGBFader::lineZdominant;
    }
  }
}

void RGBFader::randomLine(void){
  newLine = (this->*pLine) ();   
  
    if (newLine){
    int rand;
    rand = random (1, 4);
    
    x1 = x2;
    y1 = y2;
    z1 = z2;
    
    if (rand==1) { x2=0; } else { x2= random(0, 256); };
    if (rand==2) { y2=0; } else { y2= random(0, 256); };
    if (rand==3) { z2=0; } else { z2= random(0, 256); };

    x = x1;
    y = y1;
    z = z1;

    dx = x2 - x1;
    dy = y2 - y1;
    dz = z2 - z1;

    ax = ABS(dx) << 1;
    ay = ABS(dy) << 1;
    az = ABS(dz) << 1;

    sx = ZSGN(dx);
    sy = ZSGN(dy);
    sz = ZSGN(dz);

    if (ax >= MAX(ay, az)){                 /* x dominant */
      yd = ay - (ax >> 1);
      zd = az - (ax >> 1);
      pLine = &RGBFader::lineXdominant;
    }
    else if (ay >= MAX(ax, az)){            /* y dominant */
      xd = ax - (ay >> 1);
      zd = az - (ay >> 1);
      pLine = &RGBFader::lineYdominant;
    }
    else if (az >= MAX(ax, ay)){            /* z dominant */
      xd = ax - (az >> 1);
      yd = ay - (az >> 1);
      pLine = &RGBFader::lineZdominant;
    }
  }
}

boolean RGBFader::lineXdominant(void){
 
  goToRGB(x, y, z);
  if (x == x2)
  {
    return 1;
  }
  if (yd >= 0)
  {
    y += sy;
    yd -= ax;
  }
  if (zd >= 0)
  {
    z += sz;
    zd -= ax;
  }
  x += sx;
  yd += ay;
  zd += az;
  return 0;
}


boolean RGBFader::lineYdominant(void){
    
  goToRGB(x, y, z);
  if (y == y2){
    return true;
  }
  if (xd >= 0){
    x += sx;
    xd -= ay;
  }
  if (zd >= 0){
    z += sz;
    zd -= ay;
  }
  y += sy;
  xd += ax;
  zd += az;
  return false;
  
}

boolean RGBFader::lineZdominant(void){
    
  goToRGB(x, y, z);
  if (z == z2)
  {
    return true;
  }
  if (xd >= 0)
  {
    x += sx;
    xd -= az;
  }
  if (yd >= 0)
  {
    y += sy;
    yd -= az;
  }
  z += sz;
  xd += ax;
  yd += ay;
  return false;

}

RGBFader ledOne;// default mcp4728 id(0) and default PCA9685 id(0)

void setup()
{
  Serial.begin(115200);
   
  ledOne.begin();
  ledOne.setCurrent(100,100,100); // set maximum current for channel 1-3 (mA)
  ledOne.setFreq(330);            // operation frequency of the LED driver (KHz)
  ledOne.setPWMFrequency(120);
  ledOne.eepromWrite();           // write current settings to EEPROM
  delay(100);                     // wait for EEPROM writing
  
  
//test current-settings read function  
  Serial.print("Max current - channel one   :");
  Serial.println(ledOne.getCurrent(1));
  Serial.print("Max current - channel two   :");
  Serial.println(ledOne.getCurrent(2));
  Serial.print("Max current - channel three :");
  Serial.println(ledOne.getCurrent(3));
  
//test current-switch-frequency read function
  Serial.print("Operating Frequency :");
  Serial.println(ledOne.getFreq());
  
  ledOne.randomLineInit();
}

void loop()
{
  unsigned long currentMillis = millis();            // if out commented
  if(currentMillis - previousMillis > interval) {    // code runs
    previousMillis = currentMillis;                     // substantially  
    ledOne.rgbFade(0,0,0,255,255,255);
  }                                                             // faster
}
 
A little bit more experimentation shows that eliminating exactly one line of code in the main loop speeds the code up several magnitudes.

So in essence this:
Code:
void loop()
{
  unsigned long currentMillis = millis();
  if(currentMillis - previousMillis > interval) { 

    ledOne.rgbFade(0,0,0,255,255,255);
  } 
}

Runs 10 times faster than this:

Code:
void loop()
{
  unsigned long currentMillis = millis();
  if(currentMillis - previousMillis > interval) { 
    [COLOR="#FF0000"]previousMillis = currentMillis;[/COLOR]
    ledOne.rgbFade(0,0,0,255,255,255);
  } 
}

Pretty puzzling to me!
 
Not sure if this is your problem, but there's no need to re-declare currentMillis every loop. And previousMillis & currentMillis are not of the same type which is probably not intended.

You should declare them at the top
Code:
unsigned long previousMillis = 0, currentMillis = 0;

And in loop:
Code:
void loop()
{
  currentMillis = millis();
  if(currentMillis - previousMillis > interval) { 
    previousMillis = currentMillis;
    ledOne.rgbFade(0,0,0,255,255,255);
  } 
}
 
Thanks for the reply. Nope, those are not the problem. I had tried that out already.
I have to admit that I am pretty stumped right now!

I've briefly tested this on the Teensy++2 BTW and it shows similar behavior with the one line of code being the problem.
 
I'm not sure if it'll help, but have you tried using the elapsedMillis datatype that's part of the Teensy? https://www.pjrc.com/teensy/td_timing.html

at the top
Code:
elapsedMillis fadeMillis;

and in the loop
Code:
void loop()
{
  if(fadeMillis >= interval) { 
    fadeMillis = 0;
    ledOne.rgbFade(0,0,0,255,255,255);
  } 
}
 
@Dawnmist
Thank you very much!!! Australia to the rescue! I tried it out and it works beautifully.
I guess I'll have to be more aware of all the little and not so little software gems that Paul has provided with the Teensy boards!

I still would like to know why the usual blink-without-delay code did not work...but I can live without that for the moment :)
 
First, let's keep in mind both previousMillis and interval are zero.....

So in essence this:
Code:
void loop()
{
  unsigned long currentMillis = millis();
  if(currentMillis - previousMillis > interval) { 

    ledOne.rgbFade(0,0,0,255,255,255);
  } 
}

Runs 10 times faster than this:

Code:
void loop()
{
  unsigned long currentMillis = millis();
  if(currentMillis - previousMillis > interval) { 
    [COLOR="#FF0000"]previousMillis = currentMillis;[/COLOR]
    ledOne.rgbFade(0,0,0,255,255,255);
  } 
}

Of course the first one runs more times per second. It will NEVER wait after millis() becomes more than zero. Remember, those variables are zeros that never change. So the code is essentially this:

Code:
void loop()
{
  if (millis() > 0) {
    ledOne.rgbFade(0,0,0,255,255,255);
  } 
}

The result is no delay at all, after your program has been running for only 1 millisecond.

The second case, where you update previousMillis (so it's not always zero), will delay up to 1 millisecond, minus whatever time was spent in the rgbFade() function. The comparison is greater than zero, so millis() must increase by at least 1 before the condition becomes true.
 
Paul, you are correct, of course. Now I have a hard time grasping how the value of 256 stared me in the face at close range and it still did not click.

Another question though: I find the the elapsedMillis solution suggested by dawnmist very elegant. Is that strictly Teensy specific or can that be adapted for use in generic Arduino code ?
 
Paul, you are correct, of course. Now I have a hard time grasping how the value of 256 stared me in the face at close range and it still did not click.

Another question though: I find the the elapsedMillis solution suggested by dawnmist very elegant. Is that strictly Teensy specific or can that be adapted for use in generic Arduino code ?

I'm pretty sure it's a Teensyduino thing specifically implemented by Paul. I love it though, because it makes it easy to do time-based checking without making mistakes like the one Paul pointed out (I missed noticing that interval = 0), and it's pretty clear what the code is doing when you look back on it later. However, as Paul also pointed out, you probably do want some sort of value set for interval...otherwise why are you bothering to check that a time interval has passed at all? That then of course limits the rate at which it the leds would change, because the led-change code would run only every "interval" ms (every other trip through the loop, the led-change code would be skipped because the elapsedMillis >= interval test would be false).
 
elapsedMillis is unique to Teensyduino. I've intending to publish it as a stand-alone library for official Arduino boards and clones, but that's been a very low priority. The code is actually just a single .h file, so if you want to put it into a non-Teensy project, it's a pretty simply matter to just copy elapsedMillis.h.

I must confess, the idea for elapsedMillis was inspired by my own stumbling with using millis() and subtracting/comparing in the conventional way. Every time, I would go through the same steps and worry if I had handled the 32 bit rollover correctly. Sometimes I would also make mistakes like the one above. It's pretty easy to do when you're focusing on the other stuff going on.

Maybe someday elapsedMillis may become part of the software Arduino publishes? They're certainly welcome to use it. But someone would need to talk them into doing so. They've become much more open to outside contributions over the last several months, but they're still very sensitive to any API changes in the core library. My position is they're welcome to include my code, but I don't have the time and motivation to participate in a lengthy conversation on their developer mail list to advocate they use it.
 
Last edited:
Thanks for the helpful responses!

Yes, of course I wanted to have a delay. The whole algorithm was fast enough in my own project running on a Teensy++2.
My intend, however, is to add the best possible, fastest fading algorithm ( within my limited means of programming skills :) to the library for the LED Shield.
So I proceeded to "measure" the speed with the millis() in it assuming when I set Interval to 0 there would be no measurable delay. Thus I was really surprised when I removed it that the fading speed was that much faster. Also, I wanted to know how dependent the execution speed was on the operation frequency of the I2C bus. The I2Cmaster library from dsscircuits.com I've been using with the Teensy++2 only allowed the standard 100kHz and the 400kHz speed but the new and awesome I2C library for the Teensy3 by user Nox allows speeds up to 2.4 MHz. The hardware on the LED shield is FM+ (1MHz) capable and I wanted to see how much of a difference this would make.
 
Some issues with the code

Thanks for the helpful responses!

Yes, of course I wanted to have a delay. The whole algorithm was fast enough in my own project running on a Teensy++2.
My intend, however, is to add the best possible, fastest fading algorithm ( within my limited means of programming skills :) to the library for the LED Shield.
So I proceeded to "measure" the speed with the millis() in it assuming when I set Interval to 0 there would be no measurable delay. Thus I was really surprised when I removed it that the fading speed was that much faster. Also, I wanted to know how dependent the execution speed was on the operation frequency of the I2C bus. The I2Cmaster library from dsscircuits.com I've been using with the Teensy++2 only allowed the standard 100kHz and the 400kHz speed but the new and awesome I2C library for the Teensy3 by user Nox allows speeds up to 2.4 MHz. The hardware on the LED shield is FM+ (1MHz) capable and I wanted to see how much of a difference this would make.

Hi Headroom, Just one thing I noticed about your code that could be causing a bit of confusion. In this little snippet here:
Code:
void RGBFader::rgbFade(uint8_t x1, uint8_t y1, uint8_t z1, uint8_t x2, uint8_t y2, uint8_t z2){
    newLine = (this->*pLine) ();
    if (newLine){
      
    unsigned long currentMillis = micros();            
    Serial.println(currentMillis-previousMillisTimer);
    previousMillisTimer = currentMillis;

You are calling micros() to get the time tag that it appears you want to be a millisecond value. That would cause confusion.

And it looks like Paul provided a clear explanation of the problem and you understood. Of course the reason that Dawnmist's solution worked is that it changed the check for the elapsed time being "> interval" to ">= interval". Since interval was 0 the elapsedMillis solution also resulted in no delay.

I'm wondering if you don't want to use micros() or elapsedMicros type to get a finer resolution delay?
 
This would appear to be the answer to this question I posted yesterday ( http://forum.pjrc.com/threads/24429-Teensyduino-code-on-non-Teensy-(eg-Arduino-Uno) ).

On the Mac I found elapsedMillis.h in here:
\\Arduino v1.0.5.app\Contents\Resources\Java\hardware\teensy\cores\teensy\elapsedMillis.h

...but I've no idea where to copy it to?

I tried \\Arduino v1.0.5.app\Contents\Resources\Java\hardware\arduino\cores\arduino\elapsedMillis.h, restarted the Arduino IDE, but compiling still barfs at "51: error: 'elapsedMillis' does not name a type"...
 
Just to keep things in perspective, you are asking for technical support for an Arduino Uno board on the forum for Teensy.
 
I appreciate that, but what I'm really responding to is the line "TODO: publish a library for elapsedMillis and elapsedMicros on Arduino boards.", on www.pjrc.com/teensy/td_timing.html. Of course you're under no obligation to do so, but in the spirit of "Teach a man to fish..." all I was asking was 'how'?

For the sake of others who come along later:
Turns out all I needed to do was copy elapsedMillis.h into a folder of the same name in Arduino's user-defined Libraries location (like any other library), and now that sketch compiles & uploads onto a Uno (i.e. any generic Arduino).

As a microcontroller geek & EE who graduated in the early 90s & who's only seriously programmed in assembler until this year, I had no idea if a new C data-type could be added 'just like that', just as tho it were a general code library. Now I do. I don't think I've experienced such a learning curve since those youthful days, as I have in the past several months, but I'm trying my best. Sometimes not every 'stupid question' that appears off-topic is what it seems. Well maybe it does to some, but I can't do anything about such disparate perceptions.

Is having someone politely lobbying on the Arduino Dev forum to have elapsedMillis included something you're still interested in having someone do? I keep half an eye on the dev forums & see the 'not invented here' thing all the time & it shirts me too, & I understand why you don't want to go there yourself any more. But I'd be willing to give it a go...

FWIW, I received my 2nd order (#139457) of several more Teensy boards & other stuff last week, thank you. Teensy has become my favourite platform for some development & tinkering. A client asked me to do their thing using a Teensy recently, I'd vaguely heard of it/you, but believe it or not, it wasn't until I came across the 'elapsedMillis' datatype that I became interested ("wow, that's simplicity and so useful!") and then I discovered your OctoWS2811 library that uses DMA & PWM to generate the bitstream for WS2812 strings on the Teensy 3.0 & thought "Wow! That's sheer brilliance!". & now I'm beginning to discover all the USB peripheral types that are possible... Anyway, enough smoke-up-butt blowing :).

'till next time,
Anthony.
 
Here's the Arduino developers mail list.

https://groups.google.com/a/arduino.cc/forum/?fromgroups#!forum/developers

Before you look, there's a heated discussion going on right now regarding many aspects of 1.5.X's changes for structure and metadata in libraries. It's actually a little depressing to see some of the positions of certain Arduino Team members.

I came here to ask if elapsedMicros handled the 32bit rollover correctly, and in one of Paul's earlier posts it seems it does :) But if I might throw my $0.02 in to this slightly off topic thread, I had given up with the Arduino API, until I discovered the Teensy 3.1 and the IntervalTimer class.

Until the generic Arduno platform grows a periodic timer function as part of the basic API, it will never be more than a toy.
 
I can tell you elaspedMicros uses the micros() function internally. It properly handles the case (every 71.5 minutes) where micros() rolls over.

However, elaspedMicros reports the elasped number of microseconds as a 32 bit number. You can't use it to measure longer than 2^32 microseconds (approx 71.5 minutes). After that length of time, the elaspedMicros variable will roll over to zero. But if you use elaspedMicros to measure shorter time spans, it will always work. You don't need to worry if you're using it close to the moment when micros() would roll over.
 
I can tell you elaspedMicros uses the micros() function internally. It properly handles the case (every 71.5 minutes) where micros() rolls over.

However, elaspedMicros reports the elasped number of microseconds as a 32 bit number. You can't use it to measure longer than 2^32 microseconds (approx 71.5 minutes). After that length of time, the elaspedMicros variable will roll over to zero. But if you use elaspedMicros to measure shorter time spans, it will always work. You don't need to worry if you're using it close to the moment when micros() would roll over.

Brilliant answer, you should add this information to the Teensyduino documentation as it is a reason in itself to avoid using the old millis() and micros() functions. The Arduino folks would be foolish not to include this in their mainline.

Also it would be useful to note that there are no limits to the number of the elapsedtimer objects one may have :)

Perhaps there should be a variant that automatically resets up reading? Or would that be of limited value?
 
Status
Not open for further replies.
Back
Top