IR based tachometer with SD logger

Hi, I am a student during my Internship and I am trying to create a tachometer using an IR sensor. I will describe my program the problem.

Materials : arduino mega2560 ,IR sensor TCRT5000 Infrared Sensor Module - Line Track, LCD screen 16x2 , SD module MicroSD Module with SPI interface (for Arduino) and a couple of buttons.

The way my program works. As the program begins I initialize the sd module , the screen , the serial and my interrupts. When someone presses the first button an ISR routine sets a flag that enables the timer1 and the external interrupt for the IR sensor. Every time the IR sensor sends a pulse another ISR increments a counter. When timer 1 reaches a value (0.5seconds in my case) from another ISR sets another flag which allows a funtion on loop to stop the timer and the external interrupts from the IR sensor in order to calculate the RPM. I am using a homemade encoder disc with 4 pulses per revolution. The way I calculate the rpm=Tcounter60 /4where T is the period of the timer1 count and counter is the number of pulses i got in that time. Besides that within loop every second I call uppon another function that displays the rpm value on the LCD screen.

The problem is that i get almost 3 times less RPMs that what it should report.

I am hosting all of my code down below. Any ideas on what I am doing wrong ? Any advice is welcome around all of my code but my main concern at the moment is to get the correct value out of my tachomereter.

Thanks in advance :slight_smile:

#include <SPI.h>
#include <SD.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <LiquidCrystal.h>
#include <RTClib.h>


#define interrupts() sei()
#define noInterrupts() cli()

//debug
volatile int debugCounter=0;


//sd module
int CS_PIN=53;

File file;

//buttons
const byte toggleButton=3;
const byte dataSaveButton=18;

//sensor
const int pinIRd = 2;
const int pinIRa = A0;

//screen
const int rs=40, en=41, d4=42, d5=43, d6=44, d7=45; //lcd-arduino pins
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

//used for calculations
unsigned int rpm=0;
unsigned long lastMillis=0;
unsigned long lastMillisData=0;
unsigned long timeToDisplay=1000;
bool meterSet=false;


//volatile variables to be used inside int routines
volatile int counter=0;
volatile bool readyToCalculate=0;
volatile unsigned long lastInt_time=0;
volatile unsigned long currentInt_time=0;
volatile bool meterActive=0;
volatile bool writeToSD=0;
volatile unsigned int counterTable[10];
volatile byte counterTableIndex=0;

//measurement values
double firq=2;
unsigned int prescaler=1024;
unsigned long compVal=0;


void setup()
{
  displaySetUp();
  sdSetUp();
  pinSetUp();
  extIntSetUp();
}

void extIntSetUp(){
    //setting external interrupts
  EIMSK=0;//avoid unwanted interrupts
  EICRB=0x0F;//trigger INT4 INT5 rising edge
  EICRA=0xC0;//trigger INT3 on rising edge
  EIMSK=0x28;//only INT5 and INT3
}

void sdSetUp()
{
 //file test
initializeSD();
//createFile("test.txt");
//writeToFile("This is a sample text!");
//closeFile();
lcd.clear();
lcd.print("Sd initialized");
}

void displaySetUp()
{
    //serial and screen init
  Serial.begin(57600);
  lcd.begin(16,2);
  lcd.print("RPM counter v3.2");
}

void pinSetUp()
{
   //setup for the pins
  pinMode(pinIRd,INPUT);//attached to external interrupt device
  pinMode(pinIRa,INPUT);
  pinMode(toggleButton,INPUT_PULLUP);
  pinMode(dataSaveButton,INPUT_PULLUP);
  lcd.clear();
  lcd.print("Sensor Init/zed");
}



void rpmMeterSetUP()
{
//initializing the timers
  compVal=(16000000*((1/firq)/1024));
  TCCR1A |=(1<<COM1A1);//reset OC1A on parity
  TCCR1B = 0;
  TCNT1=0;
  TCCR1B |=(1<<WGM12); //turn on ctc mode

  //set prescaler
  TCCR1B |=(1<<CS12) | (0<<CS11) | (1<<CS10); //set prescaler 1024
    
  OCR1A=compVal; //value at which the timer interrupts
  TIMSK1 |= (1<<OCIE1A); //output compare interrupt enable 1
  EIMSK=0x38;
  lcd.clear();
  lcd.print("setting up meter");
}




void loop()
{
  rpmCalc();

  if(millis()-lastMillis-timeToDisplay<=0)
  {
     lastMillis=millis();
     storeData();
     messagePrint();
  }
  
}

void storeData()
{
   if(writeToSD==1)
  {
    cli();
    char text [16];
    itoa(rpm,text,10);
    createFile("TestLog.txt");  
    writeToFile(text);
    closeFile();
    Serial.println("writeToSD run");
    sei();
  }
}

void rpmCalc()
{
   if (readyToCalculate==1)
    {
      readyToCalculate=0;
      byte tempReg=TCCR1B;
      EIMSK=0x28;//stops the external interrupts of int4
      TCCR1B=0; //stops the timer
      rpm=0; 
      rpm=((1/firq)*counter*60)/4;   //rpm=T * pulses *60 sec /4 pulses per revolution
      counter=0;
      TCCR1B=tempReg; //starts the timer 
      EIMSK=0x38;//starts the external interrupts int  
    }
    
}

void messagePrint()
{
   if(!meterActive)
   {
    stopDevice();
    lcd.clear();
    lcd.print("Ready to measure");
    lcd.setCursor(0,1);
    lcd.print("Press Start btn");
    
   }
   else if(meterActive && !meterSet)
   {
    lcd.clear();
    lcd.print("Measureing");
    startDevice();
   }
   else if (meterActive && meterSet)
   {
    // DateTime now = rtc.now();
  Serial.print("RPM : ");   
  Serial.println(rpm); 
  Serial.println(".....................");
  Serial.print("writeToSD : ");
  Serial.println(writeToSD);
  
  //Serial.println(now.hour(),DEC);
  
  //lcd
  lcd.clear();
  if(rpm>0)
  {
  lcd.setCursor(4,0);
  lcd.print("RPM  : ");
  lcd.print(rpm);
  lcd.setCursor(1,1);
  lcd.print("Logging : ");
  if(writeToSD==1)
  {
  lcd.print("YES");
  }
  else if(writeToSD==0)
  {
  lcd.print("NO");
  }
  }
  else if (rpm==0)
  {
    lcd.print("RPM : SLOW!!");
  }
 }
}

void startDevice(){
  rpmMeterSetUP(); //sets up timer and ir interrupts
  meterSet=1;
}

void stopDevice()
{
      EIMSK=0x28;//stops the external interrupts of int4
      TCCR1B=0; //stops the timer 
      counter=0;
      meterSet=0;
}

void initializeSD()
{
  Serial.println("init SD card...");
  pinMode(CS_PIN,OUTPUT);
  if (SD.begin())
  {
    Serial.println("SD card ready");
  }
  else
  {
    Serial.println("SD card failed");
    lcd.clear();
    lcd.print("Enter SD Card");
    return;
  }
}

int createFile (char filename[])
{
  file=SD.open(filename,FILE_WRITE);

  if(file)
  {
    Serial.println("File created");
    return 1;
   }
   else
   {
    Serial.println("File Error");
    return 0;
   }
}

int writeToFile(char text[])
{
  if (file)
  {
    file.println(text);
    Serial.println("Writing");
    Serial.println(text);
    return 1;
  }
  else
  {
    Serial.println("Write Failed!");
    return 0;
  }
}

void closeFile()
{
  if(file)
  {
    file.close();
    Serial.println("File closed");
  }
}




  ISR (INT4_vect){
    currentInt_time=micros();
    if(currentInt_time-lastInt_time>1) //skips the counter++ if the interrupt occurs in less than 10milliseconds
    {
    counter++;
    lastInt_time=currentInt_time;
    }
  }

  ISR(TIMER1_COMPA_vect)
  {
      readyToCalculate=1;
  }

  ISR(INT5_vect)
  {
    meterActive=!meterActive;
  }

  ISR (INT3_vect)
  {
     writeToSD=!writeToSD;
  }


 

I should also mention that the second button through an ISR routine every second disables all interrupts, saves the rpm value on the sd card and the enables the interrupts again.

Are you sure you are getting the proper value here? Doing integer math, 1/2 = 0 and 0 / 1024 = 0 and 16000000 * 0 = 0.

I did a Serial.print(compVal) to confirm 7812 as the result. Which is the same us I calculated by hand. V=(16x10^6 P * 0.5)/1024 for the number of pulses i wanna get for 0.5sec of time keeping using prescaler of 1024.Plus I am getting values from the rpm variable. Bu they are 1/3 of what they should be. Am I missing something ?

My bad, your firq variable is double so it is not integer math.

How do you have your sensor wired up? It looks like is is an open collector output which will require pullup resistors.

Good programming practice would make clear that this is a floating point calculation, e.g.

compVal=(16000000*((1.0/firq)/1024));

Likewise:
rpm=((1.0/firq)*counter*60)/4; //rpm=T * pulses *60 sec /4 pulses per revolution

The sensor has two outputs. I am using the digital for the interrupts direclty connected to digital pin 2 which I have set with pinMode as an input with pullup. I have tested it on low "revolutions" with my finger swingin at 60-120-240 bpm using a metronome and it usually works. But when I use something faster like a cordless drill with some white tape on it its reports about a third of the rpm. The analog output is connected to an analog input but i am not using it at the moment.

But at both occasions both the compVal and the rpm are not zero. I have checked with both the lcd screen and the Serial.print(); .

you need to make a copy of your counter variable inside your rpmCalc() function since you can not access a 16 bit variable atomically.

int counter_copy;
noInterrupts();
counter_copy = counter;
counter = 0;
interrupts();
...

You code also seems overly complicated. It would be much easier to just have your pulse counter running all the time. When you want to take a measurement, you grab the current value, wait 0.5 seconds and grab the final value and compute the difference. With unsigned variables, rollover is not a problem.

This is also odd

if (millis() - lastMillis - timeToDisplay <= 0)

since unisgned math can never be less than 0. This expression will only be true when it evaluates to exactly 0. The more common idiom is

if (millis() - lastMillis >= timeToDisplay)

which will evaluate to true if 1000 msec or more has elapsed (other code may cause longer delays)

Finally, your buttons do not need to be interrupts, just poll them every time through loop() and you do need to add some debouncing code.

Blockquotesince unisgned math can never be less than 0. This expression will only be true when it evaluates to exactly 0.

Woa ! Thanks ! Never though about it allthough it makes perfect sence. I will try tommorow the solution you suggested and come back with the results.

I am using an RC filter for debouncing the buttons.

Thanks for all the tips :slight_smile:

Yeap it worked allright :slight_smile: thanks for the help !

Hey sorry for opening the chat after so long, I was just hoping that you could share the code if you don't mind it?

No problem at all. You can find the final code in my github page GitHub - KostasChili/Arduino-Tacho-Meter-Logger: A project based on arduino for measurign rpm values and logging them

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.