projector - delay function screws rpm measurement

Hello

I am building a laser projector

and have a problem with the RPM measurement --

I use an interrupt , and the loop() only toggles the laser on and off with delay() functions.

so if I remove the delay() lines the RPM measurement is good , if delay() is used -- not in the interrupt itself but in loop() , the RPM goes crazy and deviates about 20% up/down.

checked for optical interference , none

checked for voltage interference between optical sensor and laser diode with oscilloscope , none

also disabled serial communication , no help

any ideas?

volatile byte rpmcount;
float rpm;
float interval;
unsigned long timeold;
volatile unsigned long sig;
long pausebegin;
long totalpause;

void rpm_fun()
{
rpmcount++;
}

void setup()
{
Serial.begin(19200);
attachInterrupt(0, rpm_fun, FALLING);
rpmcount = 0;
rpm = 0;
timeold = 0;
interval=46000;
totalpause=0;

}

void loop()
{
if (rpmcount >= 20) {
//Update RPM every 20 counts, increase this for better RPM resolution,
//decrease for faster update
rpm = 60000/(sig-timeold)rpmcount;
interval=1000
(millis()-timeold)/rpmcount;
timeold = millis();
rpmcount = 0;
Serial.print("rpm=");
Serial.println(rpm,DEC);
Serial.print("interval=");
Serial.println(interval,DEC);

}

// digitalWrite(13, HIGH);
// delayMicroseconds(interval/4);
// digitalWrite(13, LOW);
// delayMicroseconds(interval/4);
// digitalWrite(13, LOW);
// delayMicroseconds(interval/4);
// digitalWrite(13, LOW);
// delayMicroseconds(interval/4);

}

Hi,
The cause of the problem is pretty simple. The solution probably isn't.

The delayMicroseconds() function disables interrupts during the delay. If your external interrupt occurs during a delay, which apparently happens a lot, the interrupt will either be missed, or at least blocked until delayMicroseconds returns.

Possible solutions:
1 - if your delays are greater than 1000 microseconds, and the timing is not critical, you can use the delay() function, which does not block interrupts. However, delay() itself is not very accurate and can be off by 1/2 millisecond.
2 - write your own delayMicroseconds function that doesn't block interrupts. Note that because the function uses timed loops, every time you get an interrupt during a delay, the delay will be lengthened. So that too is not very accurate.
3 - instead of using a delay, sample the timer0 clock and base your toggle on elapsed ticks. You'll need code to handle overflows and delay values greater than 255 clock ticks.

Hope this helps,

Thanks Chris,

I've never sampled an internal clock , but I'll look for a reference.

also , I will review some non-arduino code which was already written for this task.

thanks again!

Tom.S.

Tom, I have hacked the following out of some code I use that I think is similar to your application. I hope it helps.

The first function configures timer1 to count in half microsecond steps.

The second waits until the given number of microseconds have elapsed. Interrupts are neither used nor blocked, but that means that if an interrupt occurs just as the timer count threshold is reached there will be a little extra delay for the interrupt service routine to finish.

void configTimer1(){
  // call this from setup to configure timer1 to count in half microseconds ticks
  TCCR1A = 0x00;      // COM1A1=0, COM1A0=0 => Disconnect Pin OC1 from Timer/Counter 1 -- PWM11=0,PWM10=0 => PWM Operation disabled 
  TCCR1B = 0x02;      // 16MHz clock with prescaler means TCNT1 increments every .5 uS (cs11 bit is set)
  TIMSK1 = 0x00;      // Timer1 interrupts off  
}
/*************************************************************************************/
void waitMicroSeconds(unsigned int Microseconds){   // returns after the given number of microseconds have elapsed
// longest delay is just over 30 milliseconds 

     TCNT1 = 0;                          //reset timer
     SET_PULSE_HIGH(OutPort, OutBit); 
     OCR1A = Microseconds  *  2;            // timer is counting in 500ns steps 
     TIFR1 = (1<<OCF1A);                  //Clear the compare A flag 
     uint8_t ocFlag = 1<<OCF1A ;
     while( ! (TIFR1 & ocFlag) )          // Wait for output compare flag 
            ;             
}

Wow , I can't wait to try it out...

By the way I thought I was smart and used delay() for the delay_time/1000 part , and delayMicroseconds() for the delay_time%1000 part.

as you can imagine , it worked better than before , but still very badly.

thanks!

TS

Tried to paste the first 3 lines , and got "timsk1 was not declared in scope..."

also I assume "SET_PULSE_HIGH(OutPort, OutBit); " is a leftover from some other code..?

#include <stdint.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>


 volatile byte rpmcount;


 void rpm_fun()
 {
   rpmcount++;
   //Each rotation, this interrupt function is run 
 }


 void setup()
 {
   Serial.begin(19200);
   attachInterrupt(0, rpm_fun, FALLING);
   
  TCCR1A = 0x00;      // COM1A1=0, COM1A0=0 => Disconnect Pin OC1 from Timer/Counter 1 -- PWM11=0,PWM10=0 => PWM Operation disabled 
  TCCR1B = 0x02;      // 16MHz clock with prescaler means TCNT1 increments every .5 uS (cs11 bit is set)
  TIMSK1 = 0x00;      // Timer1 interrupts off  
   
 }




 void loop()
 {  
 }

It compiles fine in the version 010 that I am running, doesn't even need any of the #includes.
What version of the arduino release do you have?

And quite right, SET_PULSE_HIGH is from my application and should not have been in the code posted.

Which board? The timers have different names between Atmega8 and Atmega168.

Yes , I Browsed some websites and noticed that just after posting...sorry... I have both boards so I'll bring the Diecimilia over and run the code on it.

oops..