Need help with rpm counter for Photon

Hello to everyone.

I am having some trouble implementing this arduino code to particle photon.
I have replaced interrupt 0 = D2(arduino) for interrupt 4 = D4(photon)

// read RPM
//interrupt 0 is pin D2 on arduino for the Nano, connected to hall effect sensor S

 int revolutions = 0;
 int rpm = 0;
 unsigned long lastmillis = 0;
 
 
 void setup(){
 Serial.begin(9600); 
 attachInterrupt(0, rpm_shaft, FALLING);
}
  
 
 void loop(){
 if (millis() - lastmillis == 1000){        // Uptade every one second, this will be equal to reading frecuency (Hz).
 detachInterrupt(0);                        // Disable interrupt when calculating
 rpm = revolutions * 60;                    // Convert frecuency to RPM, note: this works for one interruption per full rotation. For two interrups per full rotation use half_revolutions * 30.
 Serial.print("RPM =\t");                   // print the word "RPM" and tab.
 Serial.print(rpm);                         // print the rpm value.
 Serial.print("\t Hz=\t");                  // print the word "Hz".
 Serial.println(revolutions);               // print revolutions per second or Hz. And print new line or enter.
 revolutions = 0;                           // Restart the RPM counter
 lastmillis = millis();                     // Uptade lasmillis
 attachInterrupt(0, rpm_shaft, FALLING);    // enable interrupt
  }
 } 

 void rpm_shaft(){                          // this code will be executed every time the interrupt 0 (pin2) gets low.
  revolutions++;
  
 }

Can someone please assist me with the correct code?
I need to send the rpm to Blynk app.
I tried it on arduino uno and it works, but have trouble getting it to work on the photon.

Thanks in advance

What problem are you having with that code (what result does it give you)? What pin is your hall effect sensor attached to?

1 Like

Hi Ric,
No result. Signal pin attached to D4

The first argument is the pin, and that 0 is the same as D0. You need to change that to D4.

2 Likes

I have Ric,

Here is the complete code I have.

//This is eLumaRon Photon in white box connected to mobile LCD Display in Blynk
#include "SparkCorePolledTimer/SparkCorePolledTimer.h"

#include "blynk/blynk.h"

SparkCorePolledTimer updateTimer(500);

#define vin V2                 // Blynk Value vin1 display pin V2
#define vinn V4                // Blynk Value vin2 display pin v4
#define RES V0                 // Blynk Button pin V0
#define rpms V6                // Blynk Value rpms display pin V6
#define lamp D7

#define BLYNK_PRINT Serial

WidgetLED led(V1);             // Blynk LED Widget pin V1
WidgetLED eled(V3);            // Blynk LED Widget pin V3

WidgetLCD lcd(V5);

char auth[] = "222222222222222222222222222";    



// read RPM
//interrupt 4 is pin D4 on particle, connected to hall effect sensor S

 int revolutions = 0;
 int rpm = 0;
 unsigned long lastmillis = 0;
 
 
 

const int relayPin1 = 1;        
const int relayPin2 = 2;        
//const int errorPin = 3;         

int OldState;
int OldState1;
int OldState2;
int OldState4;

byte digitalcontrol = 0;
byte hasdigitalcontrolchanged = 0;

float setting1 = 2;                                       //battery running low - Generator needs to start
float setting2 = 2;                                       //battery has been charged - Generator needs to shut down
float setting3 = 2;                                       //generator is running only when Vin2 > 12

int analogInput1 = A0;          
int analogInput2 = A1;         

float vout1 = 0.0;             
float vin1 = 0.0;               
float R1 = 30000.0;             
float R2 = 7500.0;              
int value1 = 0;

float vout2 = 0.0;
float vin2 = 0.0;              
int value2 = 0;

int state = 0;
int retry = 0;

unsigned long currentMillis = 0;
unsigned long previousMillis = 0;
unsigned long periodMillis = 0;

void setup()
{
  Serial.begin(115200);
  Serial.println("Intelligent Start Controller");
  
  updateTimer.SetCallback(OnTimer);
  delay(3000);
  Blynk.begin(auth);
  
  Serial.print("Connecting");
  while (Blynk.connect() == false) {
    // Wait until connected
    Serial.print(".");
    delay(100);
    attachInterrupt(4, rpm_shaft, FALLING);
    delay(100);
  }
 
 
  pinMode(relayPin1, OUTPUT);
  pinMode(relayPin2, OUTPUT);
  //pinMode(errorPin, OUTPUT);
  pinMode(3, OUTPUT);                                                //Blynk Reset Button Widget on pin D4
  pinMode(4, INPUT_PULLUP);
  pinMode(7, OUTPUT);

  
  digitalWrite(relayPin1, LOW);
  digitalWrite(relayPin2, LOW);
  //digitalWrite(errorPin, LOW);
  digitalWrite(3, LOW);
  digitalWrite(7, LOW);
  
  lcd.clear();
  led.off();
  eled.off();
  OldState=false;
  OldState1=false;
  OldState2=false;
  OldState4=false;
   
}

BLYNK_READ(vin)                               // display vin1 value on Blynk Value Widget
{
Blynk.virtualWrite(vin, vin1);
}

BLYNK_READ(vinn)                               // display vin2 value on Blynk Value Widget
{
Blynk.virtualWrite(vinn, vin2);
}

BLYNK_READ(rpms)                               // display rpm value on Blynk Value Widget V6
{
Blynk.virtualWrite(rpms, rpm);
}



BLYNK_WRITE(0)
{
if (param.asInt()) {
digitalWrite(3, HIGH);
} else {
digitalWrite(3, LOW);
}
}


void loop()
{
  Blynk.run();
  updateTimer.Update();
  readanalog_task();
  rpm_shaft();
  killStatus();
  startStatus();
  runStatus();
  errorStatus();
  
  
  
  if (millis() - lastmillis == 1000){       // Uptade every one second, this will be equal to reading frecuency (Hz).
 detachInterrupt(4);                        // Disable interrupt when calculating
 rpm = revolutions * 60;                    // Convert frecuency to RPM, note: this works for one interruption per full rotation. For two interrups per full rotation use half_revolutions * 30.
 revolutions = 0;                           // Restart the RPM counter
 lastmillis = millis();                     // Uptade lasmillis
 attachInterrupt(4, rpm_shaft, FALLING);    // enable interrupt
  }




  if (state == 0) //Stop
  {
    Serial.print(" State 0 : (Battery charged - Generator OFF)");
    
    digitalWrite(relayPin1, HIGH);                          //Kill On
    digitalWrite(relayPin2, LOW);                           //Start Off     
    if( vin1 < setting1 )                                   //default = 11    if Battery voltage drops, start the generator
    {
      
      Serial.print(" => Starting Generator");

      
      
      state = 1;
      previousMillis = millis();
      retry = 0;   
    }
    else if((vin1 >= setting1) && (vin1 < setting2))
    {
      Serial.print(" => Standby");
    }
    else if (vin1 >= setting2)
    {
      //Serial.print(" => Kill Generator");
      lcd.print(0, 0, "UPS Battery FULL");
      lcd.print(0, 1, "Generator:  OFF");

      
    }
  }
  else if (state == 1) //Initial start
  {
    //Serial.print(" State 1 : (Initial Start)");

    digitalWrite(relayPin1, LOW);                             //Kill Off
    digitalWrite(relayPin2, HIGH);                            //Start On
    currentMillis = millis();

    if (vin2 >= setting3)
    {
      
      //Serial.print(" => Generator Running");
    state = 2;
    }
    else if (currentMillis - previousMillis  >= 5000)
    {
      //Serial.print(" => Generator NOT Running");
      retry++;
      if (retry <= 2)
      {
        previousMillis  = currentMillis;
        digitalWrite(relayPin1, HIGH);                          //Kill On
        digitalWrite(relayPin2, LOW);                           //Start Off           
        //Serial.print(" => Restart");
        //Serial.print(retry);
      }
      else
      {
        state = 3;
      }
    }
  }    
  else if (state == 2) //Running
  {
    Serial.print(" State 2 : (Generator Running)");
    digitalWrite(relayPin1, LOW);                            //Kill Off
    digitalWrite(relayPin2, LOW);
    
   //Start Off
    Serial.print("Gen Running/Starter OFF");
    if (vin1 >= setting2)
    {
      state = 0;
      
      //Serial.print(" => Battery Full State");
    }
  }
  else if (state == 3) //Error
  {
    Serial.print(" State 3 : (Startup Error)");

    digitalWrite(relayPin1, HIGH);                 //Kill On
    digitalWrite(relayPin2, LOW);                  //Start Off 
    Blynk.email("info@elumalite.com", "Generator V2 Startup Error", "Please inspect generators fuel level and diagnose any startup issues.");
    delay(1000);
    state = 4;
  }
  else if (state == 4)                             //Send Message and User has the option to RESET Arduino through Particle Photon and DO Button.
  {

   //Serial.print(" State 4 : (Message sent)");

  }
  //Serial.println();
  delay(500);
}

void readanalog_task()
{
   // read the value at analog input A1
   value1 = analogRead(analogInput1);
   vout1 = (value1 * 3.3) / 4096;
   vin1 = vout1 / (R2/(R1+R2)); 
   
   // read the value at analog input A2
   value2 = analogRead(analogInput2);
   vout2 = (value2 * 3.3) / 4096;
   vin2 = vout2 / (R2/(R1+R2)); 

   //Serial.print("INPUT V1= ");
   //Serial.print(vin1,1);
   //Serial.print(" V2= ");
   //Serial.print(vin2,1);
   
}

void OnTimer(void) 
{ 
    //Handler for the timer, will be called automatically
}

void killStatus()
{
if((state == 0) != OldState )   // Stop hammering the server every loop
  {
    OldState=(state == 0);
    
    if(state == 0)
    {
      led.off();
      eled.off();
    }
    else
    {
     
    }
  }
}

void startStatus()
{
if((state == 1) != OldState4 )   // Stop hammering the server every loop
  {
    OldState4=(state == 1);
    
    if(state == 1)
    {
      lcd.clear();
      delay(100);
      lcd.print(2, 0, "Running Auto");
      lcd.print(1, 1, "Start Sequence");
    }
    else
    {
      
      lcd.clear();
    }
  }
}

void runStatus()
{
if((state == 2) != OldState1 )   // Stop hammering the server every loop
  {
    OldState1=(state == 2);
    
    if( state == 2)
    {
      led.on();
     
      lcd.print(1, 0, "Generator is");
      lcd.print(0, 1, "----Running----");
    }
    else
    {
     lcd.clear();
     led.off();
    }
  }
}

void errorStatus()
{
if((state == 4) != OldState2 )   // Stop hammering the server every loop
  {
    OldState2=(state == 4);
    
    if(state == 4)
    {
     
      lcd.print(0, 0, "Error Status");
      lcd.print(6, 1, "Detected");
      eled.on();
    }
    else
    {
      lcd.clear();
    }
  }
}


void rpm_shaft(){                          // this code will be executed every time the interrupt 4 (pin D4) gets low.
  revolutions++;
  
  if (rpm > 400){               
    
    digitalWrite(lamp, HIGH);
  }
  else
  {
    
    digitalWrite(lamp, LOW);
  }
  
 }

That’s a lot of code, which I don’t have time to go through now. You should do some troubleshooting first to try to pinpoint the problem. For instance, do you know that the interrupt service routine is being called? Also, although 4 is equivalent to D4, you really should write it as D4 for clarity.

You should also reformat the code in your post to make it more readable.

2 Likes

// This #include statement was automatically added by the Particle IDE.
#include "blynk/blynk.h"

// This #include statement was automatically added by the Particle IDE.
#include "SparkCorePolledTimer/SparkCorePolledTimer.h"



SparkCorePolledTimer updateTimer(500);


#define rpms V6                // Blynk Value rpms display pin V6
#define lamp D7

#define BLYNK_PRINT Serial



char auth[] = "11111111111111111111111111111111111111";    



// read RPM
//interrupt 4 is pin D4 on particle, connected to hall effect sensor S

 int revolutions = 0;
 int rpm = 0;
 unsigned long lastmillis = 0;

void setup()
{
  Serial.begin(115200);
  
  
  updateTimer.SetCallback(OnTimer);
  delay(3000);
  Blynk.begin(auth);
  
  Serial.print("Connecting");
  while (Blynk.connect() == false) {
    // Wait until connected
    Serial.print(".");
    delay(100);
    attachInterrupt(4, rpm_shaft, FALLING);
    delay(100);
  }
 
 
 
  pinMode(4, INPUT_PULLUP);
  pinMode(7, OUTPUT);

  
  
  digitalWrite(7, LOW);
 
}

BLYNK_READ(rpms)                               // display rpm value on Blynk Value Widget V6
{
Blynk.virtualWrite(rpms, rpm);
}


void loop()
{
  Blynk.run();
  updateTimer.Update();
  
  rpm_shaft();
  
  
  
  if (millis() - lastmillis == 1000){       // Uptade every one second, this will be equal to reading frecuency (Hz).
 detachInterrupt(4);                        // Disable interrupt when calculating
 rpm = revolutions * 60;                    // Convert frecuency to RPM, note: this works for one interruption per full rotation. For two interrups per full rotation use half_revolutions * 30.
 revolutions = 0;                           // Restart the RPM counter
 lastmillis = millis();                     // Update lasmillis
 attachInterrupt(4, rpm_shaft, FALLING);    // enable interrupt
  }

}


void OnTimer(void) 
{ 
    //Handler for the timer, will be called automatically
}

void rpm_shaft(){                          // this code will be executed every time the interrupt 4 (pin D4) gets low.
  revolutions++;
  
  if (rpm > 40){               
    
    digitalWrite(lamp, HIGH);
  }
  else
  {
    
    digitalWrite(lamp, LOW);
  }
  
 }

sorry Ric.

First time here in the forum.

Greatly appreciate your assistance!

That didn’t do anything. It looks like you’re using the wrong symbol. It’s not the single quote mark, but the grave accent mark (it’s under the tilde on the key above the tab key). Don’t post the code again, edit your last post.

Thanks Ric :wink:

Back to my question. Do you know that rpm_shaft() is being called?

No Ric, how can this be checked?

Put a Serial print statement in there. Normally, you wouldn’t want to do this in an interrupt service routine (ISR), but it should be ok for a test.

I am using the Build web IDE. Never used the serial monitor with that.

Are you using a Mac or a Windows PC?

Windows 7

Sorry, I can’t help you with the serial monitor then, I think I’ve seen people mention a terminal program called PuTTY, but I don’t know anything about it. Are you seeing the D7 LED lighting up, which you should if rpm>40. You could take the if-else logic out of there and just set lamp HIGH in rpm_shaft instead of using a print statement.

Also, I see that you are calling rpm_shaft in loop. Why are you doing that? It’s you ISR, it should only be being called through the interrupt.

1 Like

Why would you keep attaching the interrupt in a while() loop?

And you want to have the interrupt attached after you've set the pinMode() for it to prevent unexpected triggers of the floating pin.

You want to check for >= instead of == in case you missed the exact millisecond for some reason.

All your variables that are manipulated inside an ISR need to be declared volatile so that out-of-order updates will be "noticed" by the linear running code.

This can be abreviated to

  digitalWrite(lamp, rpm > 40);

And since the rpm value is only recalculated once per second and not on each run of rpm_shaft(), I'd only do that digitalWrite(lamp) there too - no need to do that inside the ISR.

Instead, for testing the ISR you could use this

#define DEBUGGING_ISR
#define REV_MASK 0x01  // if rpm is to high to tell shift left a few bits to slow down the blink

void rpm_shaft() {                          // this code will be executed every time the interrupt 4 (pin D4) gets low.
  revolutions++;
#if defined(DEBUGGING_ISR)  
  digitalWrite(lamp, revolutions & REV_MASK);
#endif
 }

No need for a serial terminal then either.

But you should actually get a serial terminal and familiarize yourself with that as it's a valuable debugging tool for other more complicated problems.

On the other hand, if you want to have "permanent" control of the RPMs, you can invert your calculation. Instead of counting the revs over one second, just measure the time between two ISR triggers and calculate the RPMs that way. For maximum accuracy at higher speeds, you could use micros() instead of millis()

volatile uint32_t usLastTime;
volatile uint32_t rpm;

void rpm_shaft()
{
  uint32_t usDelta = micros() - usLastTime;
  usLastTime = micros();
  rpm = 60000000 / usDelta;
}

void loop()
{
  ...
  digitalWrite(lamp, rpm > 40);
}

No detaching of the interrupt required this way.

You can even have fun with the onboard RGB LED :wink:

void setup()
{
  ...
  RGB.control(true); // take over control
  RGB.color(0,0,0);  // switch it off
}

void loop()
{
  ...
  // for RPMs <= 255, otherwise range mapping is required
  // green will directly correspond to the RPMs, while red will go on once > 40
  RGB.color(rpm <= 40 ?  0 : rpm, rpm, 0);
}
2 Likes

Thanks for that info ScruffR…
Will need to check that all out when I get back home!
Greatly aporeciated :slight_smile:

1 Like

I wrote exactly the same at the weekend.

It is waaaay easier to have a counter and simply increment in the isr… then setup a 1s timer and take the number of increments and x60 for RPM.

This way No complex floating point stuff either.

1 Like