Motorcycle gear position indicator

Hi there.

I currently have a project in mind (motorcycle gear position indicator). The motorcycle does not have a gear position sensor, so I have used two interrupts to count pulses for the speedo sensor and crank sensor...then I am dividing speedo pulses by rev pulses to give me a (roughly) static ratio for each gear.

What I am struggling with a bit, is coming up with a way to "learn" each ratio and input that into a variable.
The following code is what I have got so far. What I'm trying to acheive is to sample the ratio several times(10), then calculate if each sample is within 10% to ensure I am reading the same ratio each time(clutch isn't pulled in or bike's not in neutral - hence the delays). If all samples are within tolerance, then set the read ratio to currentRatio variable.
I know, this is NOT pretty code! I'm trying to get away from any user interaction when learning gears, as it is obviously a bit of a safety risk.
Any help appreciated.

float sampleRatios()
{
  float currentRatio;
  boolean breakLoop = false;
  for(; !breakLoop;)
  {
    float testRatio1 = speedoPulse / tachoPulse; //Calculates the current gear ratio
    tachoPulse = 0;
    speedoPulse = 0;
    delay(500);
    float testRatio2 = speedoPulse / tachoPulse; //Calculates the current gear ratio
    tachoPulse = 0;
    speedoPulse = 0;
    delay(500);
    float testRatio3 = speedoPulse / tachoPulse; //Calculates the current gear ratio
    tachoPulse = 0;
    speedoPulse = 0;
    delay(500);
    float testRatio4 = speedoPulse / tachoPulse; //Calculates the current gear ratio
    tachoPulse = 0;
    speedoPulse = 0;
    delay(500);
    float testRatio5 = speedoPulse / tachoPulse; //Calculates the current gear ratio
    tachoPulse = 0;
    speedoPulse = 0;
    delay(500);
    float testRatio6 = speedoPulse / tachoPulse; //Calculates the current gear ratio
    tachoPulse = 0;
    speedoPulse = 0;
    delay(500);
    float testRatio7 = speedoPulse / tachoPulse; //Calculates the current gear ratio
    tachoPulse = 0;
    speedoPulse = 0;
    delay(500);
    float testRatio8 = speedoPulse / tachoPulse; //Calculates the current gear ratio
    tachoPulse = 0;
    speedoPulse = 0;
    delay(500);
    float testRatio9 = speedoPulse / tachoPulse; //Calculates the current gear ratio
    tachoPulse = 0;
    speedoPulse = 0;
    delay(500);
    float testRatio10 = speedoPulse / tachoPulse; //Calculates the current gear ratio
    tachoPulse = 0;
    speedoPulse = 0;
    delay(500);
    noInterrupts();
    if (((testRatio1 <= (testRatio2 * 1.1)) && (testRatio1 >= (testRatio2 * 0.9)))) {            //If all values are within ~10%, average the values 
      if (((testRatio2 <= (testRatio3 * 1.1)) && (testRatio2 >= (testRatio3 * 0.9)))) {          //and place into variable currentRatio, then return  
        if (((testRatio3 <= (testRatio4 * 1.1)) && (testRatio3 >= (testRatio4 * 0.9)))) {        //that value to program
          if (((testRatio4 <= (testRatio5 * 1.1)) && (testRatio4 >= (testRatio5 * 0.9)))) {
            if (((testRatio5 <= (testRatio6 * 1.1)) && (testRatio5 >= (testRatio6 * 0.9)))) {
              if (((testRatio6 <= (testRatio7 * 1.1)) && (testRatio6 >= (testRatio7 * 0.9)))) {
                if (((testRatio7 <= (testRatio8 * 1.1)) && (testRatio7 >= (testRatio8 * 0.9)))) {
                  if (((testRatio8 <= (testRatio9 * 1.1)) && (testRatio8 >= (testRatio9 * 0.9)))) {
                    if (((testRatio9 <= (testRatio10 * 1.1)) && (testRatio9 >= (testRatio10 * 0.9)))) { 
currentRatio = (testRatio1 + testRatio2 + testRatio3 + testRatio4 + testRatio5 + testRatio6 + testRatio7 + testRatio8 + testRatio9 + testRatio10) / 10;
                          }}}}}}}}}
    
  breakLoop = true;
  }
 
return(currentRatio);
}

Any help appreciated.

http://arduino.cc/en/Reference/Array

    noInterrupts();

If you turn interrupts off (not a good idea, generally), you should really turn them back on as soon as possible.

Presumably you can put the bike on a stand and run in each gear long enough to measure the rpm and road speed, how about just printing them out and writing them down? Then you can put those constants in your code and use those to decide which gear the inputs are 'closest' to while it's running.

If that isn't feasible for some reason, I'd have your sketch calculate the current gear ratio at regular intervals, have an array of a couple of hundred counters covering the range of ratios you expect to cover (e.g. 10 .. 30 mph-per-thousand-rpm in steps of 0.1) and just store 'hit counts' for each bucket. You would only need to run for a couple of minutes and go up and down the gears to make it immediately obvious which bucket each gear was in, and print out the peak values via USB when you get back to base.

Then use those values to code your gear detection logic.

If you really want you could write a sketch which learned the ratios on the fly and did the display too, but it would be more complex - simpler just to work the ratios out up front and then your main sketch is very simple.

Hi,
Its not rocket science to learn the ratios as the bike is ridden, in fact it strikes me as a fun project, certainly a lot more fun than doing it manually.

My approach would not be wildly different than the one suggested by PeterH, I say go for it, and keep us posted with how you get on.

Duane B

Thanks for the replies.

Thanks PaulS - I have modified the code using your suggestion re: using arrays, which does clean things up quite nicely.

The end result of this project (this is but a small part of a large project) is to be plug and play onto any motorcycle, without requiring any re-compiling or manual input by the end user, other than to tell it "Learn gear ratios" via the yet-to-be-decided-upon user interface.

PeterH - That's certainly one way I had not considered! The only drawback I see is that the ratios could be wildly different bike-to-bike depending on the sensor arrangement; ie, whether using a crank sensor that may have many pulses per rev as opposed to an inductive-pickup that may only have 1 pulse per rev, and/or using output shaft for revs vs front wheel speed sensor...so the amount of "buckets" would be massive.

I think I have come up with a solution, although not finished coding yet. The ammended "Sample ratios" code below, as well as adding a break line should neutral or clutch be engaged:

float sampleRatios()
  {    
    float testRatio[10];
    int i;
    for(i = 0; i < 10; i++)
    {
      if ((digitalRead(CLUTCHPIN) == HIGH) || (digitalRead(NEUTRALPIN) == HIGH))
      {
        break;
      }
      testRatio[i] = speedoPulse / tachoPulse; //Calculates the current gear ratio
      tachoPulse = 0;
      speedoPulse = 0;
      delay(500);
    }    
    noInterrupts();
    for (i = 0; i < 10; i++)
    {      
      if (((testRatio[i] <= (testRatio[i+1] * 1.1)) && (testRatio[i] >= (testRatio[i+1] * 0.9)))) //Checks If all values are within ~10%
      {
        if (i >= 9)
        {
          float currentRatio = 0;
          for (i = 0; 1 < 10; i++)
          {
          currentRatio = currentRatio + testRatio[i]; //Loops to add each tested ratio for averaging value below
          }
          currentRatio = currentRatio / i;    //averages the test ratios
          interrupts();
          return(currentRatio);
        }
      }     
      else {Setup_TX_Data.failLearn = true;}
  }
}

My idea is to run this function for the first gear and return the "currentRatio" variable, then when this function is run again, compare with the previously written variables. If the value is the same (within ~10% tolerance), then discard the result and sample again. If it is a different value, write to the next gear ratio variable. Rinse and repeat...

I think this should work. I am not really interested in the ratio being used for anything other than for calculating which gear is selected.

However, I am a newbie, that's for sure, so again any advice appreciated.

    noInterrupts();
    for (i = 0; i < 10; i++)
    {      
      if (((testRatio[i] <= (testRatio[i+1] * 1.1)) && (testRatio[i] >= (testRatio[i+1] * 0.9)))) //Checks If all values are within ~10%
      {
         // Snip some stuff that may never happen
      }     
      else {Setup_TX_Data.failLearn = true;}
  }

So, it is possible that you will exit the function with interrupts disabled. Really, that is not a good idea. Trust me on that.

Woops, wrong version of code posted...I had already made some changes in a separate file where I had removed the noInterrupts() as you suggested.

Here is another version that will hopefully be a bit better.

float sampleRatios()
  {    
    delay(500);
    float currentRatio = 0;
    float testRatio[10];
    int i;
    for(i = 0; i < 10; i++)
    {
      testRatio[i] = speedoPulse / tachoPulse; //Calculates the current gear ratio
      if ((digitalRead(CLUTCHPIN) == HIGH) || (digitalRead(NEUTRALPIN) == HIGH)) //If the clutch or Neutral is engaged, the loop starts again
      {
        i = 0;
      }
      else if ((testRatio[i] >= gear1Min) && (testRatio[i] <= gear1Max)
            || (testRatio[i] >= gear2Min) && (testRatio[i] <= gear2Max)
            || (testRatio[i] >= gear3Min) && (testRatio[i] <= gear3Max)  //Checks if the ratio just calculated is equal to any existing gear.
            || (testRatio[i] >= gear4Min) && (testRatio[i] <= gear4Max)  //If it is, the loop is started again
            || (testRatio[i] >= gear5Min) && (testRatio[i] <= gear5Max)
            || (testRatio[i] >= gear6Min) && (testRatio[i] <= gear6Max)
            || (testRatio[i] >= gear7Min) && (testRatio[i] <= gear7Max)
            || (testRatio[i] >= gear8Min) && (testRatio[i] <= gear8Max))
      {
        i = 0;
      }
      tachoPulse = 0;
      speedoPulse = 0;
      if (i >= 4)      //If the loop has run 5 or more times, check if a consistent ratio is found
      {
        currentRatio = testRatio[i];
        int j;
        for (j = 1; j < 6; j++)
        {
          if (((testRatio[i] <= (testRatio[i - j] * 1.1)) && (testRatio[i] >= (testRatio[i - j] * 0.9)))) //Checks If the latest 5 samples are within ~10% of the last sample 
          {
            currentRatio = currentRatio + testRatio[i - j];
            if (j == 5)
            {
              currentRatio = currentRatio / j;        //sets the current ratio to the average tested ratio (+ 1 because array starts at 0)
              return(currentRatio);
            }
          }
        }
      }
    }
  }

Where you are comparing the current ratio with the established limits of the known gears, you should store and handle the established gears using arrays and not using numbered variables. Any time you find yourself assigning sequence numbers to variable names, you should ask yourself whether they ought to be held as an array.

I'm not entirely sold on the algorithm you're using to decide what gear you're in. It seems to rely on you know the approximate ratios but does not seem to give you any way to establish these.

If I was tackling that problem I'd store the 'hit counts' for an array of different gear ratios and use a threshold to identify the most 'popular' ratios, which presumably correspond to your gears. (The others corresponding to running between gears, slipping the clutch, missed sensor readings etc.)

If this is to be a general self-learning device that adapts to whatever bike it is fitted to, it probably shouldn't make any assumptions about how the bike is being ridden or whether the gears are in fact consistent from day to day. You don't need to know which number gear you're in (1st, 2nd etc), only where you are within the set of gears that have been found. So when you start off, there's only one known gear and you're in it. (Might be 1st, 2nd, 3rd, ... we have no way to tell). After you change gear you have two known gears. We don't know what they correspond to on the bike, might be 1st/3rd etc, but from the point of view of our sketch all we know is there are two gears. Similarly as you discover new gears, they may be above/below/between existing gears. You could reflect this in your gear selection display, using bicolour LEDs to show how many gears are known and which (if any) of those you are currently in.

(Just as an aside, have you considered putting a position sensor on the gear selector cam instead?)

Thanks for the reply PeterH.

I'm not entirely sold on the algorithm you're using to decide what gear you're in. It seems to rely on you know the approximate ratios but does not seem to give you any way to establish these.

I should note, that this function is purely a run-once function when telling the device to enter learning mode. Once all ratios are known, the ratios are written to EEPROM. Ratios are retrieved from EEPROM during the setup() function on startup and min/max values calculated for the "threshold" of each gear ratio. The actual "normal mode" is just comparing currentRatio with each stored ratio to find which is correct. If none, it just displays 'N'. Well, this is what I hoped would work for me...I'll take any advice I can get from someone more seasoned than I in Arduino/C!

I'm just trying to picture in my head how using the "hit count" method would work...mainly, how I would populate the array?
Considering that calculated values would likely need to be float variables, I may well run into SRAM limits if I have a very large float array...There is a lot of other things I want the uC to be working on also (namely GPS datalogging with a large buffer)

I'll get out with the DSO on the weekend and take some measurements if I have time and see what the pulse counts are for different speeds/revs and get an idea from there.

Ideally, yes I would love to use a gear position sensor...but I'm trying to cheat and make a universal device that doesn't require engineering on each different motorcycle.

Really appreciate the input,
Cheers.

Once you've determined the ratios, I suggest you show whichever gear is nearest to the current ratio - you can do that by putting a threshold at the mid point between each adjacent pair of gears. In other words, at runtime rather than having an array of expected gear ratios or min/max ratios etc, just have an array of ratios that represent the thresholds for deciding which gear you're in. Then given an arbitrary ratio you can determine which gear it's nearest to by walking through the array and stopping when you reach a value which exceeds the ratio; the array index is then your gear.

(You do number your gears from zero, don't you?)

Ahh, yes...that does sound quite a bit easier, and probably less prone to error also!

I have used your excellent suggestion. The three modified parts of the code below.

The Learning routine:

float sampleRatios()
  {    
    delay(500);
    float currentRatio = 0;
    float testRatio[10];
    int i;
    for(i = 0; i < 10; i++)
    {
      testRatio[i] = speedoPulse / tachoPulse; //Calculates the current gear ratio
      if ((digitalRead(CLUTCHPIN) == HIGH) || (digitalRead(NEUTRALPIN) == HIGH)) //If the clutch or Neutral is engaged, the loop starts again
      {
        i = 0;
      }
      else 
      {
        int j;
        for (j = 0; j <8; j++)                                                                    
        {                                                                                          //Checks if the ratio just calculated is equal to any gear already learned.
          if (testRatio[j] >= (gearRatio[j] * 0.9) && (testRatio[j] <= (gearRatio[j] * 1.1)))      //If it is, the loop is started again
          {
            i = 0;
          }
        }
      }
      tachoPulse = 0;
      speedoPulse = 0;
      if (i >= 4)      //If the loop has run 5 or more times, check if a consistent ratio is found
      {
        currentRatio = testRatio[i];
        int j;
        for (j = 1; j < 6; j++)
        {
          if (((testRatio[i] <= (testRatio[i - j] * 1.1)) && (testRatio[i] >= (testRatio[i - j] * 0.9)))) //Checks If the latest 5 samples are within ~10% of the last sample 
          {
            currentRatio = currentRatio + testRatio[i - j];
            if (j == 5)
            {
              currentRatio = currentRatio / j;        //sets the current ratio to the average tested ratio.
              return(currentRatio);
            }
          }
        }
      }
    }
  }

The "Calculate thresholds" function run straight after finding the absolute ratio in the learning mode:

void calcGearThresholds()
{
  int i;
  for (i = 0; i < 7; i++)
  {
    gearRatio[i] = gearRatio[i] + ((gearRatio[i+1] - gearRatio[i]) / 2);       //Finds the mid-point between two adjacent gears and sets it as the threshold
  }
}

And the normal running code:

void calcGear()
{
  float currentRatio = speedoPulse / tachoPulse; //Calculates the current gear ratio
  if (digitalRead(CLUTCHPIN) == HIGH)
  {
    TX_Data.currentGear = 'C';      //Places the value calculated into currentGear within TX_Data structure
  }
  else if (digitalRead(NEUTRALPIN) == HIGH)
  {
    TX_Data.currentGear = 'N';
  }
  else
  {
    int i;
    for (i = 0; i < 8; i++)
    {
      if (currentRatio < gearRatio[i])    //Checks if current ratio is less than the threshold of gear "i" (loop number). 
      {                                    //If it is, then that is the gear currently selected
        TX_Data.currentGear = (i+49);      // (i + 49) because 49 is the ASCII code for "1". 
      }
    }
  } 
}

Is this what you had in mind with your previous post?
Thanks again!

Hi Jason :slight_smile:
Do You have working example of this code?