Bike Servo Controller based on an rpm input

hi everyone.

i have used arduinos for some time for other small projects (car glow plug timers, based on temperature sensor input), but now I am trying do do something different.

I have an old motorcycle with YPVS (exhaust power valve). my original servo, was controlled by the original CDI wich isn't working... I fitted a universal CDI because i couldn't find one that would work with the YPVS controller.

So I came up with this ideia and I built a "bench" valve controller based on RPM input (sent by hall effect sensor 3144).

I wrote this program (quite simple actually...) using the example of freqmeasure library to get frequency.

#include <ServoTimer2.h>
#include <FreqMeasure.h>

ServoTimer2 servo1;

int YPVS=3;
int angle;     // angle in degrees
int anglem;    // angle in servotimer2 value 750 to 2200
float frequency;
int rpm;

void setup() {
  Serial.begin(57600);
  pinMode(YPVS,OUTPUT);
  servo1.write(2200);     // called cleaning process, first closes valve to 0 degrees, and then goes full open
  delay(800);
  servo1.write(750);
  delay(800);
  servo1.attach(YPVS);
  FreqMeasure.begin();


}

double sum=0;
int count=0;


void loop() {

     

  if (FreqMeasure.available()) {
    // average several reading together
    sum = sum + FreqMeasure.read();
    count = count + 1;
    if (count > 5) {
      float frequency = FreqMeasure.countToFrequency(sum / count);
          rpm=frequency*60;
    angle=(18./500.)*(rpm-4500.);    // equation to find valve position in degrees, based in rpm input
    if(rpm<1700)  {
    angle=180;
    }
    if(rpm>1700 && rpm<4500)
  {
    angle=0;
    }
        if(rpm>9500)
  {
    angle=180;
    }
      Serial.print("frequency - ");
      Serial.print(frequency);
      Serial.print("  -       RPM  -");
      Serial.print(rpm);
      Serial.print("    -  angleval - ");
      Serial.println(anglem);

      sum = 0;
      count = 0;
    }
  anglem=2200-angle*(145./18.);  //equation to convert degrees to sertimver2 value
  servo1.write(anglem);
    
  }

} 

works 100% in bench but I'm only having one problem... when the bike is at working temperature, in order to start, the valve must be openned ... wich means i need it to go do 0 degrees when no frequency is detected!.
I tried using "else" in the end of the first condition, but when i add else the program wont work at all and the servo goes mad... any ideias how to overcome this?

another problem is the start, it should close valve, and then open, but the commands in void setup don't seem to be working... other than that everything works fine!

best regards

We don't know how you "added an else"... did you post the sketch with, or without it? If without, then please also post the revised sketch.

this is how:

#include <ServoTimer2.h>
#include <FreqMeasure.h>

ServoTimer2 servo1;

int YPVS=3;
int angle;     // angle in degrees
int anglem;    // angle in servotimer2 value 750 to 2200
float frequency;
int rpm;

void setup() {
  Serial.begin(57600);
  pinMode(YPVS,OUTPUT);
  servo1.write(2200);     // called cleaning process, first closes valve to 0 degrees, and then goes full open
  delay(800);
  servo1.write(750);
  delay(800);
  servo1.attach(YPVS);
  FreqMeasure.begin();


}

double sum=0;
int count=0;


void loop() {

     

  if (FreqMeasure.available()) {
    // average several reading together
    sum = sum + FreqMeasure.read();
    count = count + 1;
    if (count > 5) {
      float frequency = FreqMeasure.countToFrequency(sum / count);
          rpm=frequency*60;
    angle=(18./500.)*(rpm-4500.);    // equation to find valve position in degrees, based in rpm input
    if(rpm<1700)  {
    angle=180;
    }
    if(rpm>1700 && rpm<4500)
  {
    angle=0;
    }
        if(rpm>9500)
  {
    angle=180;
    }
      Serial.print("frequency - ");
      Serial.print(frequency);
      Serial.print("  -       RPM  -");
      Serial.print(rpm);
      Serial.print("    -  angleval - ");
      Serial.println(anglem);

      sum = 0;
      count = 0;
    }
  anglem=2200-angle*(145./18.);  //equation to convert degrees to sertimver2 value
  servo1.write(anglem);
    
  }

  else  {
    angle=180;
  }

}

this way wont work at all, servo goes mad betwheen 0 and 180 a hundred times per second...

Why are you basing the 'else' decision on the 'available()' method? Obviously, most of the time nothing is available.

sorry, like I said i've only used arduinos for simple projects, I only know what i know from what i saw others doing...

from what I understood:
if 'available()' returns true, it will measure frequency and start,

if 'available()' returns false angle should be 180º, wich is why I used else...?

I tried instead of else doing:

if (FreqMeasure.available()==0) {
angle=180;

but wont work either!

Maybe it doesn't like being set to 180 several thousand times a second... just a guess. That, or possibly, it is actually the case that the frequency you are sampling is unstable, flip flopping in and out just as it behaves.

You could put in a serial debug statement to find out more about that. And/or put a 'delay(1000);' in the loop() to gain some visibility to what is going on.

sorry because english is not my native language, but i think i understood what you're saying!

when i put a delay in the loop(), somehow it affects the readings! even with a smaller delay (50,100,500,1000, the values I tried) inside the loop, the measured frequency won't be correct...

freqmeasure measures time betwheen pulses, and somehow messing with delay seems to affect readings...?

That would indicate a very poor program design. By the way, your formatting is awkward to read, use ctrl-T in the IDE to format it in a standard way:

#include <ServoTimer2.h>
#include <FreqMeasure.h>

ServoTimer2 servo1;

int YPVS = 3;
int angle;     // angle in degrees
int anglem;    // angle in servotimer2 value 750 to 2200
float frequency;
int rpm;

void setup() {
  Serial.begin(57600);
  pinMode(YPVS, OUTPUT);
  servo1.write(2200);     // called cleaning process, first closes valve to 0 degrees, and then goes full open
  delay(800);
  servo1.write(750);
  delay(800);
  servo1.attach(YPVS);
  FreqMeasure.begin();


}

double sum = 0;
int count = 0;


void loop() {



  if (FreqMeasure.available()) {
    // average several reading together
    sum = sum + FreqMeasure.read();
    count = count + 1;
    if (count > 5) {
      float frequency = FreqMeasure.countToFrequency(sum / count);
      rpm = frequency * 60;
      angle = (18. / 500.) * (rpm - 4500.); // equation to find valve position in degrees, based in rpm input
      if (rpm < 1700)  {
        angle = 180;
      }
      if (rpm > 1700 && rpm < 4500)
      {
        angle = 0;
      }
      if (rpm > 9500)
      {
        angle = 180;
      }
      Serial.print("frequency - ");
      Serial.print(frequency);
      Serial.print("  -       RPM  -");
      Serial.print(rpm);
      Serial.print("    -  angleval - ");
      Serial.println(anglem);

      sum = 0;
      count = 0;
    }
    anglem = 2200 - angle * (145. / 18.); //equation to convert degrees to sertimver2 value
    servo1.write(anglem);
  }
  else  {
    angle = 180;
  }
}

Are you sure that the servo library is compatible with the frequencyMeasure library? They likely both use hardware timers, you may have a timer conflict.

@vinniept I believe this is a direction that you will like to go with your program
using a blink without delay timer in this manner allows the default 0 RPM mode to be detected after a failure to get a reading within a short time period.

using blink without delay I added a 20ms delay between servo write.

I also moved the serial print to a standalone blink without delay loop to prevent spamming of the serial port
Z

#include <ServoTimer2.h>
#include <FreqMeasure.h>

ServoTimer2 servo1;

int YPVS = 3;
int angle;     // angle in degrees
int anglem;    // angle in servotimer2 value 750 to 2200
float frequency;
int rpm;
unsigned long Timeout;

void setup() {
  Serial.begin(57600);
  pinMode(YPVS, OUTPUT);
  servo1.write(2200);     // called cleaning process, first closes valve to 0 degrees, and then goes full open
  delay(800);
  servo1.write(750);
  delay(800);
  servo1.attach(YPVS);
  FreqMeasure.begin();
}

double sum = 0;
int count = 0;

void loop() {
  if (FreqMeasure.available()) {
    Timeout = millis();
    // average several reading together
    sum = sum + FreqMeasure.read();
    count = count + 1;
    if (count > 5) {
      float frequency = FreqMeasure.countToFrequency(sum / count);
      rpm = frequency * 60;
      angle = (18. / 500.) * (rpm - 4500.); // equation to find valve position in degrees, based in rpm input
      if (rpm < 1700)  {
        angle = 180;
      }
      if (rpm > 1700 && rpm < 4500)
      {
        angle = 0;
      }
      if (rpm > 9500)
      {
        angle = 180;
      }
      sum = 0;
      count = 0;
    }
  }

  if ((millis() - Timeout) >= (200)) {  // After 200 MS has passed without a reading taking place set angle to 180.
    angle = 180;  
  }

//These following sections can stand alone and have different durations as the angle, frequency, rpm, and angle variables are global and will hold their last values between loops.

  static unsigned long ServoWriteDelayTimer;
  if ((millis() - ServoWriteDeaayTimer) >= (20)) { // a 20 ms delay between servo writes should be appropriate
    ServoWriteDeaayTimer = millis();
    anglem = 2200 - angle * (145. / 18.); //equation to convert degrees to sertimver2 value
    servo1.write(anglem);
  }

  // Serial print becomes an issue if you try to spam the serail port.
  static unsigned long SpamTimer;
  if ((millis() - SpamTimer) >= (50)) {
    SpamTimer = millis();
    Serial.print("frequency - ");
    Serial.print(frequency);
    Serial.print("  -       RPM  -");
    Serial.print(rpm);
    Serial.print("    -  angleval - ");
    Serial.println(anglem);
  }
}

that is why I am using servotimer2, because i tried using servo.h in the first place, but wouldn't work at all! wouldn't even get an output at pin3...
thank you for the Ctrl+T tip about format, like I said earlier, I'm newbie :face_holding_back_tears:

thank you a lot for that! got my program better for sure! but still wont show me 0... i have just tested it, and it will keep printing the last value...

@vinniept
thank you a lot for that! got my program better for sure!
Welcome :slight_smile:

but still won't show me 0... I have just tested it, and it will keep printing the last value...

Try Changing this to an "unsigned long"

int count = 0;
to
unsigned long count = 0;

uint32_t is identical to unsigned long
the definition of FreqMeasureClass::read
shows it returns a uint32_t in outer words an unsigned int with 32 bits or an unsigned long integer
uint32_t FreqMeasureClass::read(void);

While this may not solve the problem it is a step in the right direction.
Z

Another Thought

Add:

    Serial.print("Timeout Timer- ");
    Serial.print(millis() - Timeout);

so we can see if the FreqMeasure.available(); returns true even when there is nothing triggering it.

Also you will want to change this line:

// average several reading together
    unsigned long value;
    while((value = FreqMeasure.read()) != 0xFFFFFFFF ){ // clear out all readings in the buffer 
          sum = sum + value ;
          count = count + 1;
    }

The source code shows why this is necessary the frequency counter buffers up to 12 readings

#define FREQMEASURE_BUFFER_LEN 12

// and

uint32_t FreqMeasureClass::read(void)
{
	uint8_t head, tail;  // FIFO Buffer Pointers
	uint32_t value;

	head = buffer_head;
	tail = buffer_tail;
	if (head == tail) return 0xFFFFFFFF;  // <<< when no reading are available: return 0xFFFFFFFF
	tail = tail + 1;
	if (tail >= FREQMEASURE_BUFFER_LEN) tail = 0;
	value = buffer_value[tail];
	buffer_tail = tail;
	return value;
}

again, TOP, thank you for your time!!! its working like a champ now!!! the "while" function seems to solve it!

i only have one problem now, in void setup, when i reset arduino, servo should go from 0 to 180 (called cleaning process) but for some reason, when i reset it, it stays in the last position, having the commands in setup, or not having them, seems to make no difference... is there any way to overcome thjis? or what is wrong?

again, thank you !!!

current code

#include <ServoTimer2.h>
#include <FreqMeasure.h>

ServoTimer2 servo1;

int YPVS = 3;
int angle;     // angle in degrees
int anglem;    // angle in servotimer2 value 750 to 2200
float frequency;
int rpm;
unsigned long Timeout;

void setup() {
  Serial.begin(57600);
  pinMode(YPVS, OUTPUT);
  servo1.write(2200);     // called cleaning process, first closes valve to 0 degrees, and then goes full open
  delay(800);
  servo1.write(750);
  delay(800);
  servo1.attach(YPVS);
  FreqMeasure.begin();


}

double sum = 0;
int count = 0;


void loop() {


  if (FreqMeasure.available()) {
    Timeout = millis();
    // average several reading together
    unsigned long value;
    while ((value = FreqMeasure.read()) != 0xFFFFFFFF ) { // clear out all readings in the buffer
      sum = sum + value ;
      count = count + 1;
    }
    if (count > 5) {
      frequency = FreqMeasure.countToFrequency(sum / count);
      rpm = frequency * 60;
      angle = (18. / 500.) * (rpm - 4500.); // equation to find valve position in degrees, based in rpm input
      if (rpm < 1700)  {
        angle = 180;
      }
      if (rpm > 1700 && rpm < 4500)
      {
        angle = 0;
      }
      if (rpm > 9500)
      {
        angle = 180;
      }
      sum = 0;
      count = 0;
    }

  }
  if ((millis() - Timeout) >= (200)) {  // After 200 MS has passed without a reading taking place set angle to 180.
    angle = 180;
  }


  static unsigned long ServoWriteDelayTimer;
  if ((millis() - ServoWriteDelayTimer) >= (20)) { // a 20 ms delay between servo writes should be appropriate
    ServoWriteDelayTimer = millis();
    anglem = 2200 - angle * (145. / 18.); //equation to convert degrees to sertimver2 value
    servo1.write(anglem);
  }

  // Serial print becomes an issue if you try to spam the serail port.
  static unsigned long SpamTimer;
  if ((millis() - SpamTimer) >= (20)) {
    SpamTimer = millis();
    Serial.print("frequency - ");
    Serial.print(frequency);
    Serial.print("  -       RPM  -");
    Serial.print(rpm);
    Serial.print("    -  angleval - ");
    Serial.print(anglem);
    Serial.print("  - Timeout Timer- ");
    Serial.println(millis() - Timeout);
    Serial.println();

  }
}

1 Like

It is not working in setup() because you are trying to write to the servo before you attach it.

actually i was experimenting before posting, and forgot to put it back, but i tried several ways, and its not working...

seems like i need to delay "FreqMeasure.begin();"?!

Try this:

void setup() {
  Serial.begin(57600);
  servo1.attach(YPVS); // you don't need pinMode the ServoTimer2 library takes care of that
  while(!(servo1.attached())); // Wait here till we know servo1 is attached
  servo1.write(2200);     // called cleaning process, first closes valve to 0 degrees, and then goes full open
  delay(800);
  servo1.write(750);
  delay(800);
  FreqMeasure.begin();
}

EDIT: edited code

Also I would do this:

int convertAngle(int angle) {
  return  2200 - angle * (145. / 18.);
}

void setup() {
  Serial.begin(57600);
  servo1.attach(YPVS); // you don't need pinMode Aervo takes care of that
  while(!(servo1.attached)); // Wait here till we know servo1 is attached
  servo1.write(convertAngle(0));     // called cleaning process, first closes valve to 0 degrees, and then goes full open
  delay(800);
  servo1.write(convertAngle(180));
  delay(800);
  FreqMeasure.begin();
}

TOP again!!! fully working in bench!

now i have more problems , not really related to arduino -.-'''
I was using a hall effect sensor, to measure frequency... mounted it on the bike flywheel near the small outside magnet of the flywheel (the one that triggers the pickup coil).

it works 100% until 5000/6000rpm, but above that it switches on ... the flywheel is full of magnets on the inside, and with rpm I think the magnetic field is affecting the hall effect sensor!

I have an oscilloscope, a DS0120M and measured the signal in the pickup coil. identical to this onehttps://europe1.discourse-cdn.com/arduino/original/4X/5/e/9/5e9a1882c474be3756b1838a9cfe290b01d2de6b.jpeg

i found this topic https://forum.arduino.cc/t/oscilloscope-on-motorcycle/696127/9 and my pickup coil signal is identical to his! the only main difference is max/min voltage wich in my case goes from -22 (min) to +66 depending on the rpm.

i saw his circuit https://europe1.discourse-cdn.com/arduino/original/4X/6/e/d/6edb37e4977afe432f679a596cfde2f0bc782ebe.jpegto convert the signal into a 5v square wave but since I'm dealing with higher voltages, is there any chance that i can use his circuit with different components?

once again thank you, code is working like a champ!

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