How to reset micros() to start/zero ?

Guys, I'm using micros() for my midi clock, but I need to reset it every time Play is pressed, so we don't end up having problems with the overflow after 70 minutes...

Any way I could just reset micros() timer to zero ?

Thanks, Wk

You would be better off writing your code in such a way that micros() overflowing won't cause problems.

What, in a midi clock, requires microsecond precision?

Correctly resetting micros is not trivial. Dealing with the overflow is.

If you provide more detail, I will help you deal with the overflow.

(@PaulS: Jinx! :))

Thanks guys.

Check this following thread:

http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1292022093

There you can take a look of the code.

  currentmicroTime = (float)micros();
  if (currentmicroTime >= ppqmicroTime)

I guess what I could do is also check

  if (currentmicroTime < ppqmicroTime)

Which would tell me it has gone back to zero?

A bit lost, but I'm sure I will figure this out eventually. :wink:

Thanks again, Wk

Both ways are wrong to deal with the overflow. First, don't make floats out of it, you just lose precision. Those numbers are unsigned longs, so leave them that way. Second, never compare two time stamps directly, never add a delay to a time stamp, only compare the difference between now and the starting-time with the expected delay. Thus, the correct way to do it is:

unsigned long currentmicroTime = micros();

if (currentmicroTime - ppqmicrostartTime >= ppqmicroDelay) {
....
}

Exactly the same applies to millis(), only you see the effect every 49 days instead of every 72 minutes, so there's less of a chance that the bug will bite you in your backside.

Korman

Hummm, unless I'm totally off here, that doesn't seem to work for a midi clock, but I will check tomorrow, with a fresh mind. :wink:

In any event, the way I setup the midi clock, it does output correctly, but who knows.

I must use floats, as the PPQ time is not an integer, its a fractional number, so I can't just round down, otherwise the clock will drift a bit after a few minutes, and that's not good. But I will test anyway, with unsigned long, just in case, maybe it takes a bit longer to drift, and it could be that it doesn't drift that much anyway, but I need to check first...

Wk

Ok, I removed the float and indeed, it still works and doesn't drift. (from the small test I did) Now I will make a bigger test.

BPM: 95.00
PPQ: 96.00
Micros PPQ: 6578
All BPM Done!
Processed Time MS: 59991

BPM: 120.00
PPQ: 96.00
Micros PPQ: 5208
All BPM Done!
Processed Time MS: 59996

BPM: 145.00
PPQ: 96.00
Micros PPQ: 4310
All BPM Done!
Processed Time MS: 59995

BPM: 170.00
PPQ: 96.00
Micros PPQ: 3676
All BPM Done!
Processed Time MS: 59994

Also, keep in mind that the other code I posted is just a test, not the actual midi clock I'm using. Of course I compare to another variable. :wink:

MidiClock.h
(removed some other stuff that is not related to this post)

#define PPQ 96.0f

class MidiClock 
{
public:
      MidiClock();

      boolean isTick();
      void midiEcho();
      void nextTick();
      void updateTime();
      void newTime(int _bpm);
      void start();
      void stop();
      void reset();
      void sendCmd(char cmd, char data1, char data2);
      boolean isQuarter();
      boolean isRunning();

      boolean running;
      float bpm;
      unsigned long measurecounter;
      unsigned int beatcounter;
      int ppqcounter;
      boolean didIsQuarter;

private:
      boolean doReset;
      unsigned long prevtime;
      unsigned long currenttime;
      unsigned long ppqtime;
      byte incomingByte;
};

MidiClock.cpp

/*
 
 www.Wusik.com - Created by WilliamK @ Wusik Dot Com (c) 2010
 
*/

#include "WConstants.h"
#include "MidiClock.h"

// ------------------------------------------------------------------------------------------- //
MidiClock::MidiClock()
{
      MSerial.begin(31250); //start MSerial (defined in the .h file) for MIDI Input/Output at baudrate 31250 //
      #if MCPrintDebug
            Serial.begin(38400); // For Debuging //
      #endif
      bpm = 140;

      doReset = true;
      running = false;
      didIsQuarter = false;
      ppqcounter = 0;
      measurecounter = 0;
      beatcounter = 0;

      updateTime();
}

// ======================================================================================= //
void MidiClock::updateTime()
{
      ppqtime = 60000000.0f / (bpm*PPQ);  // (60 x 1,000,000) / (BPM*PPQ)
      doReset = true;

      #if MCPrintDebug
            Serial.print(bpm);      
            Serial.println(" BPM");
            Serial.print(ppqtime);      
            Serial.println(" ppqtime");
      #endif
}

// ======================================================================================= //
boolean MidiClock::isRunning()
{
      return running;
}

// ======================================================================================= //
void MidiClock::start()
{
      running = true;
      doReset = true;
      reset();
}

// ======================================================================================= //
void MidiClock::reset()
{
      ppqcounter = 0;
      measurecounter = 0;
      beatcounter = 0;
      doReset = true;
}

// ======================================================================================= //
void MidiClock::stop()
{
      running = false;
}

// ======================================================================================= //
void MidiClock::midiEcho()
{
      if (MidiEcho)
      {
            while (MSerial.available() > 0) 
            { 
                  incomingByte = MSerial.read();  
                  MSerial.print(incomingByte, BYTE);
            }
      }
}

// ======================================================================================= //
boolean MidiClock::isTick()
{      
      currenttime = micros();

      if (doReset)
      {
            prevtime = currenttime+ppqtime;
            doReset = false;
            didIsQuarter = false;
            ppqcounter = 0;
            measurecounter = 0;
            beatcounter = 0;
      }

      if (running)
      {
            
            //if (otherProcessesTime > 0 && (currenttime+otherProcessesTime) >= prevtime) 
            /*if ((currenttime+4000) >= prevtime)
            {
                  while (currenttime < prevtime) { currenttime = micros(); }
            }*/

            if (currenttime >= prevtime)
            {
                  //if (currenttime >= (prevtime+90)) missedPPQ++;

                  prevtime += ppqtime;
                  return true;
            }
      }
      return false;
}

// ======================================================================================= //
boolean MidiClock::isQuarter()
{
      if (!didIsQuarter)
      {
            didIsQuarter = true;
            return true;
      }

      return false;
}

// ======================================================================================= //
void MidiClock::newTime(int _bpm)
{
      bpm = _bpm;
      updateTime();
}

// ======================================================================================= //
void MidiClock::nextTick()
{
      if (ppqcounter == 0 || ppqcounter == ((PPQ/4)*1) || ppqcounter == ((PPQ/4)*2) || ppqcounter == ((PPQ/4)*3)) 
      {
            didIsQuarter = false;
            beatcounter++;
            if (beatcounter == 16) beatcounter = 0;
      }

    // Next Tick //
      ppqcounter++;
      if (ppqcounter == PPQ)
    {
            ppqcounter = 0;
            measurecounter++;
            Serial.print("Measure: ");
            Serial.println(measurecounter, DEC);
      }

      /*#if MCPrintDebug
            if (missedPPQ > 10)
            {
                  missedPPQ = 0;
                  Serial.println("BADCLK!");
            }
      #endif*/
}

// ======================================================================================= //
void MidiClock::sendCmd(char cmd, char data1, char data2)
{
  MSerial.print(cmd, BYTE);
  MSerial.print(data1, BYTE);
  MSerial.print(data2, BYTE);
}

// ======================================================================================= //
// ======================================================================================= //
// ======================================================================================= //
// ======================================================================================= //

The problem with using floats for clock ticks is, the longer the clock runs the less precise it gets and at some point you can't properly resolve one tick any more. With longs, the precision is always of 1 tick.

Korman

Got it, thanks Korman, I'm no longer using floats. :wink:

Now, how do I deal with the overflow problem? What exactly happens? What is the max number micros() will output? So I could just treat it as a big loop, and when micros() is < than the last prevtime, I can just do the math as a looped sample. (its how I think) :wink:

Wk

Even in cases of overflows, endtime-startime will always be the correct elapsed duration, as long as the types of endtime and starttime match the the type returned by your counter, which means unsigned long for millis() and micros().

If you start adding durations to timestamps or comparing timestamps with each other, your program will fail because of the overflow.

The sane conclusion to this is to only use code such as:if (endtime - starttime >= delay) {

Korman

Thanks Korman, but maybe its too earlier for me, as I don't get what you are talking about. What StartTime and EndTime? Keep in mind that this is a PPQ clock. I will try to think more about this later today and see if I understand what you mean. :-[

Wk

PPQ is, if I remember correctly, a frequency when combined with bpm. If you want to generate things based on the beats, you'll end up measuring the time of one beat, because that's what the Arduino can do. And once you start measuring delays, you'll pretty soon hit functions like millis() and micros() or you start counting your own beats. In all those situations you'll face the overflow. And subtracting the endvalue from the startvalue lets you prevent the overflowing.

Oh, and in case you worry that your bpm and ppq don't line up with microseconds well enough, you just need to make sure you distribute the error evenly to minimise it. That's not such a great matter, one just needs to be aware of the resolution of the timer (usually 4 or 8 µs) and prevent accumulating errors.

Korman

Here's the youtube video of my project:

Wk

Ahhh, nevermind.... I found another solution. I took a look at this page:

http://popdevelop.com/2010/04/mastering-timer-interrupts-on-the-arduino/

And create my own MyTimer library. :smiley:

It works great, and has no overflow problem. I'm still checking how I can improve it, and even how to have a better resolution, but for now its pretty neat. :wink:

Here's the whole library and also an example below.

MyTimer.h

/*

      Original code by Sebastian Wallin
      http://popdevelop.com/2010/04/mastering-timer-interrupts-on-the-arduino/

      Adapted and Optimized by WilliamK @ Wusik Dot Com (c) 2010
      http://arduino.wusik.com

      This timer will clock 125000 times in a second.

      You must add to your Arduino code the following:
            
            ISR(TIMER2_OVF_vect) { timer.tick(); } // "timer" as in your "MyTimer timer = MyTimer();" variable //

*/

#ifndef MYTIMER_h
#define MYTIMER_h

#include <inttypes.h>
#include "HardwareSerial.h"

// ------------------------------------------------------------------------------------------- //
class MyTimer
{
public:
      MyTimer();
      
      inline void tick(void)
      {
            TCNT2 = 255;
            timerCounter++;
            if (timerCounter == timerTimeValue) { eventCounter++; timerCounter = 0; }
      }

      void stop(void);
      void start(void);
      void setTime(unsigned long time);
      boolean hasEvent(void);

private:
      unsigned long timerCounter;
      unsigned long timerTimeValue;
      int8_t eventCounter;
      
};

#endif

MyTimer.cpp

/*

      Original code by Sebastian Wallin
      http://popdevelop.com/2010/04/mastering-timer-interrupts-on-the-arduino/

      Adapted and Optimized by WilliamK @ Wusik Dot Com (c) 2010
      http://arduino.wusik.com

      See MyTimer.h for instructions

*/

#include "WConstants.h"
#include "MyTimer.h"

// ------------------------------------------------------------------------------------------- //
MyTimer::MyTimer() 
{
      eventCounter = 0;
      timerCounter = 0;
      timerTimeValue = 125000;
}

// ------------------------------------------------------------------------------------------- //
void MyTimer::start(void)
{
      TIMSK2 &= ~(1<<TOIE2);
      TCCR2A &= ~((1<<WGM21) | (1<<WGM20));
      TCCR2B &= ~(1<<WGM22);
      ASSR &= ~(1<<AS2);
      TIMSK2 &= ~(1<<OCIE2A);
      TCCR2B |= (1<<CS22)  | (1<<CS20);
      TCCR2B &= ~(1<<CS21);
      eventCounter = 0;
      timerCounter = 0;
      TCNT2 = 255;
      TIMSK2 |= (1<<TOIE2);
}

// ------------------------------------------------------------------------------------------- //
void MyTimer::stop(void)
{
      TIMSK2 &= ~(1<<TOIE2);
      eventCounter = 0;
      timerCounter = 0;
}

// ------------------------------------------------------------------------------------------- //
boolean MyTimer::hasEvent(void)
{
      if (eventCounter > 0)
      {
            eventCounter--;
            return true;
      }
      return false;
}

// ------------------------------------------------------------------------------------------- //
void MyTimer::setTime(unsigned long time)
{
      timerTimeValue = time;
}

MyTimerExample.pde

/*
  
  Original code by Sebastian Wallin
  
  Adapted and Optimized by WilliamK @ Wusik Dot Com
  http://arduino.wusik.com

*/

#include <WProgram.h>
#include <MyTimer.h>

int8_t secondsCounter = 0;
int8_t minutesCounter = 0;
int8_t hoursCounter = 0;
int8_t daysCounter = 0;

MyTimer timer = MyTimer();
ISR(TIMER2_OVF_vect) { timer.tick(); }

void setup() 
{
  Serial.begin(115200);
  timer.setTime(125000); // one second //
  timer.start();
}

void loop() 
{
  while (timer.hasEvent())
  {
    secondsCounter++;
    if (secondsCounter == 60) { secondsCounter = 0; minutesCounter++; }
    if (minutesCounter == 60) { minutesCounter = 0; hoursCounter++; }
    if (hoursCounter == 24) { hoursCounter = 0; daysCounter++; }
    
    Serial.print("Day: ");
    Serial.print(daysCounter, DEC);
    Serial.print(" Time: ");
    Serial.print(hoursCounter, DEC);
    Serial.print(":");
    Serial.print(minutesCounter, DEC);
    Serial.print(":");
    Serial.println(secondsCounter, DEC);
  }
}

I'm moving this to another thread, so we can close this one up.

http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1292438829/0

Wk

After taking a look at the source code of wiring/arduino_ide, I found out this:

timer0_overflow_count

That's the one micros() uses.

So I did a quick test:

#include <WProgram.h>

int8_t secondsCounter = 0;
extern unsigned long timer0_overflow_count;

void setup() 
{
  Serial.begin(115200);
  timer0_overflow_count = 0;
}

void loop() 
{
    delay(1000);
    secondsCounter++;
    if (secondsCounter > 4) 
    {
      timer0_overflow_count = 0;
      secondsCounter = 0;
      Serial.println("!");
    } else Serial.println(micros());

}

And heck, it works. Could it be so simple, or am I going to mess up other stuff up? Just wondering...

Wk

And heck, it works.

You play Russian Roulette. You win (meaning, you didn't die). You conclude that you're good at Russian Roulette and decide to play again. And again. And again. I suspect you know how this story is going to end.

Could it be so simple

No, it isn't that simple.

or am I going to mess up other stuff up?

Yes. You're playing Russian Roulette with your application. Eventually, your application will crash in a way that is almost impossible to debug.

And heck, it works. Could it be so simple, or am I going to mess up other stuff up? Just wondering...

Yes it works somewhat, just like cutting bread with a chainsaw works somewhat. With what you do, you mess up things, but depending on whether you use the messed up things or not, you won't be affected by it. And then, from time to time your code will randomly behave strangely for a moment, but I guess for your kind of application it won't matter much either. All in all, one wonders what benefit you expect from all this messing around and whether the added problems aren't worse than the problems you think you're solving.

In a commercial environment I would reject your solution as bad design. But for a school project or just a hobby, that isn't a major concern.

Korman

Guys, I'm learning, and yes, this is not commercial, just trying to learn something new. So, please, don't be so hard on me. :wink:

Still, I have checked the whole code and I still don't understand why it would crash anything just by messing out with one variable that only micros() uses. But hey, what do I know? :-X

Wk