Pages: 1 ... 5 6 [7] 8   Go Down
Author Topic: realtime clock, microseconds, etc.  (Read 8755 times)
0 Members and 1 Guest are viewing this topic.
0
Offline Offline
Newbie
*
Karma: 0
Posts: 38
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

OK so I just changed my code to use micros() instead of some of Don's code that I'd butchered for 0012 and it solved another problem I was having of spurious values being randomly returned from the Arduino (http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1222726966/30)!!!!  I've had a 555 timer running at 35Hz for about 30 minutes now and haven't had one mad value from the Arduino.  I'm not too sure what was wrong with my original code, but here it is just for interest sake more then anything -

Original (returning spurious values every now and then)
Code:
/*
/  Times TTL pulses (rising-rising edge, interrupt driven) on pin 2 and stores them to a buffer,
/   then sends them over the serial port as microsecond figures in an interruptable loop.
/
/  Philip Harrison December 2008 (All hpticks code by Don Kinzer [http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1226257074/5])
*/

#define mTimeBufferSize 64 // MUST only be 2^x value, such as 2, 4, 8, 16, 32, 64, 128
#define timer0TicksToMicroseconds(t)  ((t) * 64L / (F_CPU / 1000000L))  // Function to convert the timer ticks counted by the hpticks() function to microseconds.  Only works with CPU frequencies of 2^x numbers, such as 8MHz or 16MHz

volatile unsigned long mTimesBuffer[mTimeBufferSize]; // Buffer to store values before they are sent over serial
volatile unsigned long mTimesBufferStorePtr=0; // Pointer into the buffer showing where we are currently storing values to.  Note this pointer does not loop over, it counts up continually
volatile unsigned long mTimesBufferSendPtr=0; // Pointer into the buffer showing where we are currently sending values over serial from.  Note this pointer does not loop over, it counts up continually

unsigned long lastSent = 0; // Used to hold the previous hpticks() value sent to the PC so the difference from current hpticks can be calculated


extern "C"
{
  // Defined in wiring.h
  extern unsigned long timer0_clock_cycles;
  extern unsigned long timer0_millis;
};

// Define the timer interrupt flag register if necessary
#if !defined(TIFR0)
  #if defined(TIFR)
    #define TIFR0 TIFR
  #else
    #error AVR device not supported
  #endif
#endif


unsigned long hpticks(void)
{
  uint8_t sreg = SREG;
  cli();
  
  // Get the current value of TCNT and check for a rollover/pending interrupt
  uint16_t t0 = (uint16_t)TCNT0;
  if ((TIFR0 & _BV(TOV0)) && (t0 == 0)) t0 = 256;

  // Get a snapshot of the other timer values
  unsigned long clock_cycles = timer0_clock_cycles;
  unsigned long millis = timer0_millis;
  SREG = sreg;

  // Compute the number of Timer0 ticks represented by the data
  unsigned long timer0_ticks = (((millis * (F_CPU / 1000L)) + clock_cycles) / 64) + t0;

  return timer0_ticks;
}


void setup()
{
  Serial.begin(115200); // Enable serial port communication at 115200 baud
  pinMode(2, INPUT); // Set digital pin 2 to an input pin
  attachInterrupt(0, onChangeX, RISING); // Setup interrupt 0 (which is on digital pin 2), triggered on a rising edge input, calling the onChangeX function
}


void loop()
{
  if(mTimesBufferSendPtr < mTimesBufferStorePtr)
  {
    // The send pointer is behind the store pointer so there must have been a value written that we can send
    
    //Serial.print(mTimesBufferStorePtr - mTimesBufferSendPtr); // Send how far the send pointer is behind the store pointer, if this gets higher then mTimeBufferSize a buffer overflow has occured and the data will not be accurate
    //Serial.print(",");
    //Serial.print(mTimesBuffer[mTimesBufferSendPtr & (unsigned long)(mTimeBufferSize-1)]); // Send the next hpticks count in the buffer, buffer is incremented later.  Note hpticks() is not time, it needs to be converted
    //Serial.print(",");
    Serial.println(timer0TicksToMicroseconds(mTimesBuffer[mTimesBufferSendPtr & (unsigned long)(mTimeBufferSize-1)] - lastSent)); // Subtract the previous hpticks from this hpticks, then convert this to microseconds to get the pulse time for this pulse

    lastSent = mTimesBuffer[mTimesBufferSendPtr++ & (unsigned long)(mTimeBufferSize-1)];
  }
}


// Function called on every interrupt
void onChangeX()
{
  mTimesBuffer[mTimesBufferStorePtr & (unsigned long)(mTimeBufferSize-1)] = hpticks(); // Put the current ticks count into the next space in the buffer
  mTimesBufferStorePtr++; // Increment the buffer store pointer to the next location
}

New
Code:
/*
/  Times TTL pulses (rising-rising edge, interrupt driven) on pin 2 and stores them to a buffer,
/   then sends them over the serial port as microsecond figures in an interruptable loop.
/
/  Philip Harrison March 2010
*/

#define mTimeBufferSize 64 // MUST only be 2^x value, such as 2, 4, 8, 16, 32, 64, 128

volatile unsigned long mTimesBuffer[mTimeBufferSize]; // Buffer to store values before they are sent over serial
volatile unsigned long mTimesBufferStorePtr=0; // Pointer into the buffer showing where we are currently storing values to.  Note this pointer does not loop over, it counts up continually
volatile unsigned long mTimesBufferSendPtr=0; // Pointer into the buffer showing where we are currently sending values over serial from.  Note this pointer does not loop over, it counts up continually

unsigned long lastSent = 0; // Used to hold the previous micros() value sent to the PC so the difference from current micros can be calculated

void setup()
{
  Serial.begin(115200); // Enable serial port communication at 115200 baud
  pinMode(2, INPUT); // Set digital pin 2 to an input pin
  attachInterrupt(0, onChangeX, RISING); // Setup interrupt 0 (which is on digital pin 2), triggered on a rising edge input, calling the onChangeX function
}


void loop()
{
  if(mTimesBufferSendPtr < mTimesBufferStorePtr)
  {
    // The send pointer is behind the store pointer so there must have been a value written that we can send
    
    //Serial.print(mTimesBufferStorePtr - mTimesBufferSendPtr); // Send how far the send pointer is behind the store pointer, if this gets higher then mTimeBufferSize a buffer overflow has occured and the data will not be accurate
    //Serial.print(",");
    //Serial.print(mTimesBuffer[mTimesBufferSendPtr & (unsigned long)(mTimeBufferSize-1)]); // Send the next micros value in the buffer, buffer is incremented later.
    //Serial.print(",");
    Serial.println(mTimesBuffer[mTimesBufferSendPtr & (unsigned long)(mTimeBufferSize-1)] - lastSent); // Subtract the previous micros from this micros to get the pulse time and send across serial port

    lastSent = mTimesBuffer[mTimesBufferSendPtr++ & (unsigned long)(mTimeBufferSize-1)];
  }
}


// Function called on every interrupt
void onChangeX()
{
  mTimesBuffer[mTimesBufferStorePtr & (unsigned long)(mTimeBufferSize-1)] = micros(); // Put the current micros() value into the next space in the buffer
  mTimesBufferStorePtr++; // Increment the buffer store pointer to the next location
}
Logged

Brunsbüttel, SH, F.Rep.GERM
Offline Offline
God Member
*****
Karma: 4
Posts: 596
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

here u can c how they do it...

the conversion to microsecs is done once per micros()-call...

the interrupt handler just shapes the overflow-counter if necessary...

IIRC a function call on a 16MHz arduino takes about 1.125usec...
u can c it in the delayMicroseconds()-code in the above mentioned URL...
the delay due to the computations can be estimated by looking at the datasheet of the atmega-cpu...

can u measure the real time between the flanks with an oscilloscope?
then u could compare it to the results of ur arduino...
or u create a certain frequency with a tlc555, but i dont know how exactly one can compute the frequency from the resistors and the capacitors...

-arne
Logged

-Arne

Brunsbüttel, SH, F.Rep.GERM
Offline Offline
God Member
*****
Karma: 4
Posts: 596
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

in ur original (and current) ring buffer implementation a buffer overflow can cause funny output (when u get more pulses than u can send via the serial output)...

maybe the new Serial implementation causes the better behaviour?

another reason might be a problem with integer arithmetrics: t0 is a "unsigned long (uint32_t, 32 bits)" in 0018, while in ur "original" version u use a uint16_t for t0...

"* (F_CPU / 1000L)" this might be a bad idea, too, because it might cause a overflow of the uint32_t and then u divide by 64 which might result in a wrong number...

-arne
Logged

-Arne

0
Offline Offline
Newbie
*
Karma: 0
Posts: 38
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Thanks for the replies!

There's a formula for working out the expected frequency of a 555 based on the RC circuit, but tbh I don't think the stability of the wave is good enough from a 555 to use it for proof of timing like this.  As you say an oscilloscope would be the way to go, but I don't have access to one sadly.

As I say I'm only measuring the differences in time, so once the conversion to microseconds happens every call and is going to take a fixed amount of time every call (presumably it will?) then it makes no difference to me.

Initially I was outputting the level of the buffer so I could watch for an overflow, but at the speed the pulses were coming in, compared to the speed of the Arduino and serial port there was no risk of overflow.  Having said that in the final implementation I intend to set a flag if the buffer overflows and indicate with a red LED or signal on the serial line.  I did check for overflow during the problem time and am confident it wasn't the cause.

Glad to hear there is a new implementation of Serial as I was a bit suspicious of the error checking before, every now and then I'd see the end of strings missing in transmission.

I'll take you're word on the last two parts, that's all a bit of a black art to me, I just copied and pasted Don's code smiley.

FWIW when I first cranked up the 555 the other day and started timing I was getting maybe 2-3 crazy outputs per minute.  After updating to 0018 and changing to using the micros() function I ran it over night (1000000 results from the Arduino to my Processing code) and didn't see one crazy output!  So feeling very confident about the whole thing smiley.

Now for another quick question.  I read the other day that the ticks counter (which presumably the micros() function relies on) doesn't get incremented during interrupt function calls?  This isn't a problem in the code above, but the next part of this project will be to time pulses from the bike engine itself (probably an inductive pickup on the sparkplug lead), at the same time as timing the drum so tyre/clutch slippage (common problem as tyres heat and start to melt, or with worn clutches) can be checked for.  In threory I'm going to need two interrupt handlers and two timer buffers to achieve this.  Am I going to run into timing issues here because of the ticks counter not being incremented during interrupts?  Is a second Arduino running in parallel a better idea (kinda looking for an excuse to get a Mega anyway lol)?

Thanks!
Logged

Brunsbüttel, SH, F.Rep.GERM
Offline Offline
God Member
*****
Karma: 4
Posts: 596
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

the interrupt handler must not need more than
1. the register needs for a second overflow
plus
2. the timer-interrupt needs for processing that overflow flag...

the register overflows every 64*256 clock ticks... with a 16MHz clock that is 1024usec...

that mega thing is surely cool...

-arne
Logged

-Arne

0
Offline Offline
Newbie
*
Karma: 0
Posts: 38
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Ah OK, so are you saying the processor tick count register IS still incremented during an interrupt handler, but overflows wouldn't be noticed/registered if the handler is too slow?  All I'll be doing in the two interrupt handlers is popping the current micros() value into my buffer, which will take considerably less then 1ms I'm sure.  So I'm good to go with two high speed pulse feeds coming to the one Arduino?
Logged

Brunsbüttel, SH, F.Rep.GERM
Offline Offline
God Member
*****
Karma: 4
Posts: 596
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

yup... mem copy just takes a few cycles... less than 1usec/byte... -arne
Logged

-Arne

0
Offline Offline
Newbie
*
Karma: 0
Posts: 38
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Excellent, thanks!

It would be great if threads like this could be linked to the FAQ/Reference part of the Arduino site as there is some great and pretty important info here to anybody actually using the micros() function for high speed timing.  Just thought this as I was filling in the Arduino survey that came around by email last week.
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 6
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

This thread may not be the right place for this, but it's the closest thing I could find after much Googling.

Quote
David, to my thinking, date/time functions like hour(), day(), etc. don't belong in the wiring.c "kernel".  Tracking "clock-based" time seems to be a different problem than tracking "elapsed" time.  While nearly every Arduino project depends on the latter -- millis() and delay(), etc. -- the need for "clock" time seems more limited.  In my opinion, this should be placed in a library -- like mem's DateTime!

Yes, exactly.

Here's my problem: I need to be able to get the current "clock time", e.g. "time since the epoch" or "time with date/hour/minute/second", but with millisecond accuracy. From what I've been able to find, the current libraries don't have that functionality. Or do they?

JavaScript returns epoch time with millisecond accuracy, natively (if you want regular Unix time, in seconds, you have to /= 1000). That's the functionality I'm looking for.

Is there any hope of that? Or is that beyond most people's basic needs?

(Right now I'm having to combine millis() with now(), which is clearly not optimal since they have nothing to do with one another.)

Thanks...
« Last Edit: April 10, 2010, 05:47:52 pm by bctiemann » Logged

Brunsbüttel, SH, F.Rep.GERM
Offline Offline
God Member
*****
Karma: 4
Posts: 596
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

hm - u would need to tell the arduino the time at reset or so, because: millis() counts the milli seconds since last reset...

and after 49.7 days u need to cope with the 32-bit overflow...

and the arduino clock is not very accurate... mine has an error of about +3000ppm (3msec/sec)... for comparison: my ACPI clock in my big box (ASROCK mainboard: ALiveDual-eSATA2) has +39ppm...

-arne
« Last Edit: April 11, 2010, 03:19:25 am by RIDDICK » Logged

-Arne

0
Offline Offline
Newbie
*
Karma: 0
Posts: 6
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Right. That's the trouble.

I'm using the NTP functionality of Time.h to keep the clock in sync, but even though NTP ostensibly has millisecond resolution, that doesn't really do me any good because I can't report the time in anything more accurate than seconds, so I might as well be off +/- 0.5 sec.

Right now what I'm doing is manually measuring the drift of each of the three Arduino boards I have, then hard-coding the ppm into the sketch to compensate the number of millis() it reports since startup. But even that's only going to keep me inaccurate by a constant amount throughout the device's uptime.

Is the Arduino just not suited for this kind of application?
Logged

Brunsbüttel, SH, F.Rep.GERM
Offline Offline
God Member
*****
Karma: 4
Posts: 596
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

judging from this
Code:
unsigned long getNtpTime()
{
  sendNTPpacket(SNTP_server_IP);
  delay(1000);
arduino is unable to get an accurate NTP signal...  smiley-wink


have u seen this thread?

-arne
« Last Edit: April 11, 2010, 08:07:39 am by RIDDICK » Logged

-Arne

London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6250
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

The Ardudino Time library in the playground is not designed for accuracy greater than a second, it was build assuming that the primary use would be for time of day display to the nearest second.

The code in the NTP example sketch can be improved by replacing the delay with wait on the next available message.
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 6
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Fooey. Well, thanks anyway for the reply. It's good to get confirmation, if nothing else; that way I don't spend time banging my head against the wall. smiley

Guess I'll have to look into real-time clock daughterboards or something of that nature.
Logged

Brunsbüttel, SH, F.Rep.GERM
Offline Offline
God Member
*****
Karma: 4
Posts: 596
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

hm - if u already have internet access, u could try to implement mem's approach:
1. remembering the time when u sent the NTP request
2. polling for the NTP response + remembering the time of arrival
3. repeating that each hour and compute an average clock error

NTP should deliver even the micro seconds somewhere...
i think the 4 bytes after the seconds (xmit_time) r the micro seconds.... :-)

-arne
Logged

-Arne

Pages: 1 ... 5 6 [7] 8   Go Up
Jump to: