Motorcycle Control Panel with Arduino + Bluetooth

My prototype code - evil and crufty as it is, is too big to post here! The most obscure thing about it is probably the interrupt handling. Each of the wheel and the tach interrupt routines adds an entry into a log of times. The main loop routine polls the log every 100 ms and runs the required logic. I've posted the tach and wheel routines below because that's where the bulk of the logic is. The reason for the two stage interrupt process is so I can put debugging into the logic without messing up interrupts. I've also posted the actual tach interrupt routine. the wheel routine is simpler because it posts every event where the tach bulks them up by 10.

void processwheel(unsigned long lmillis){ //input is time of interrupt from log
  unsigned long wheeltime; //time for previous revolution
  if (lastwheelint!=0){ //don't do calculations 1st time in
    wheeltime=lmillis-lastwheelint; //track the exact rotation time
    wheelrpm=60000/(float)wheeltime; 
    //Serial.print(" wheeltime="); Serial.print(wheeltime); Serial.print(" wheel rpm="); Serial.print(wheelrpm);
    roadspeed=.123*wheelrpm; //condensed version of 2.05*60*wheelrpm/1000; where 205 is wheel circ. in metres
    //Serial.print(" road speed kph="); Serial.print(roadspeed);
    setgear(); //figure out what gear we're in
  }
  lastwheelint=lmillis;
}
void processtach(unsigned long lmillis){ // input is interrupt time in millis
  unsigned long tachtime; //elapsed time for 10 tach events
  if (lasttachint!=0) { //skip calculation on first time in
    tachtime=lmillis-lasttachint;  //time for last 10 events
    if (tachtime>0){ //just avoid divide by 0
      insttachrpm=60000/(float)tachtime*10;  //rpm based on time in ms for 10 tach pulses
      avgtachrpm=(avgtachrpm*2+insttachrpm)/3; //rolling average rpm
      //Serial.print(" tachtime="); Serial.print(tachtime); Serial.print(" inst tach rpm="); Serial.print(insttachrpm); Serial.print(" avg tach rpm="); Serial.print(avgtachrpm);
    }
  }
  lasttachint=lmillis;
}
void tachmonitor(){  //this is the actual interrupt routine
  static int tachcount;
  if (tachcount <9) { //bulking up tach interrupts by a factor of 10
    tachcount++;
  }
  else {  //every 10th tach pulse we spawn a log entry
    if (ilog<=maxlog){
      lflag[ilog]='T'; //pedal pass flag
      lmillis[ilog]=millis();
      ilog++;
    } 
    else {
      logoflo=true; //we've overrun the log table
    }
    tachcount=0;
  }
}


I've already said I'm easily amused but this project's 15 minutes of fame has spread over onto Hack-a-Day.

Yeah but we read it here first guys...

Mowcius

June 19
BoB is Alive!
I can't imagine why I thought this was simple. I reproduced the RBBB circuit on a Radio Shack proto-board and added the extra power protection and the voltage dividers and debounce circuits needed for the MC control panel. I'm not completely finished the wiring but it got to the point where I could try a sketch and it worked. The good news is that it does fit in the allowable space on the bike so that's something.

Next steps I have to finish wiring the LCD header and the serial hookup then I should be able to port the software. Way too much trouble though. If I do another prototype I'll try out something like the mpguino or some other proto-board as a base .

What is that square white object in the lower right corner? It has five leads connected to the board. :slight_smile:

Pakrat

a connector with 5 leads

mircho

Junior Member

Online

Arduino rocks

Posts: 66

Re: Motorcycle Control Panel with Arduino + Bluetooth
Reply #34 - Today at 08:47:55
a connector with 5 leads

CUTE

Ok, BoB took his seat yesterday.
I was struggling with how to mount the new prototype in the motorcycle console. The hole where the tach fits is a good size but positioning and holding it seemed tricky. I hit on the idea of using a cupboard door knob as a seat for the circuit board. The door knob mounts to a support made for the tach and positions the display just about perfectly. The first pic below shows BoB and the display beside the tank console. The upper hole in the console is for the tachometer, the lower one is the gas filler. The second pic shows the cupboard door know mounted in the tach hole and the third pic shows BoB mounted on his new seat. The positioning is not perfectly centered but I can figure that out. I remain stonkered stymied though by mounting the thing in a weatherproof way - that panel could be subjected to hours or days of driving rain and high winds.



I remain stonkered though by mounting the thing in a weatherproof way...

http://www.wordwebonline.com/en/STONKERED
Adjective: stonkered stóng-ku(r)d

  1. Very tired
  2. [Austral, NZ] Very drunk
  1. To be severely affected by alcohol. Usually accompanied by loss of co-ordination, slurred speech and dishevelled appearance.
  2. To feel bloatedly full, primarily with food.
  3. To be stoned. Under the influence of Marijuana. THC poisoning, if you will.

You've seen it here first! A new definition for the word "stonkered"! :wink:

P.S. Thanks bill2009 for writing about your adventure. :slight_smile:

  • Brian

BoB is retiring for the moment. I cobbled up an LCD mount which I had hoped would stand up to modest water torture. Sadly, no.

So how has BoB come up after the unexpected showering ?? :frowning:

BoB is fine, the "head" is in disgrace on my coffee table. I bought (another!) bike speedo and I'm going to try using that as a readout - presumably it's waterproof.

It's also kind of a neat idea if you think of it as a bike speedo circuit bending exercise. You're basically breaking into the speedo's sensor wire and installing an arduino that changes the readout depending on the tach input.

Bill I like the idea it is a great ap to the micro. I would be interested in your code as I would like to build a data logger for the rpm on my dirt race car. any help would be apreciated. Hugh

Here is the latest version of the BoB code. I may have to split it in multiple postings. The only tricky part of this in my mind is the way I handle interrupts. I have them split into stubs which catch the interrupts and store them in a table, and routines which run periodically out of "Loop" which scan the table and handle the events. I do it this way so sloppy/slow coding doesn't make me miss interrupts without knowing. In principal I could take that stuff out once it's working but I never seem to.

//july 11 V8 cut out voltage display cuz it doesn't work!
//display speed in MPH for US trip
#include <LiquidCrystal.h>
LiquidCrystal lcd(10,13,9,12,15,11,16); //(rs,rw,enbl,d4,d5,d6,d7) 
unsigned long lasttachint,lastwheelint; //time in millis of most recent tach and wheel interrupts
float insttachrpm=0, avgtachrpm=0, wheelrpm=0;  //tach instantaneous and rolling average rpm,  wheel rpms 
float revratio; int currentgear=0; //updated by setgear routine
float roadspeed=0; //speed in kph
volatile int ilog=0; 
int maxlog=31;  
volatile boolean logoflo; //index to log table, max entry #, log overflow flag
volatile byte lflag[32]; 
volatile unsigned long lmillis[32]; //log of interrupts
int tachpin=2;  //pedal sensor booster, drives interrupt 0;
int wheelpin=3; //wheel sensor, drives interrupt 1;
int neutralpin=5;
void setup() {
  lcdbegin(); //initialize the lcd
  clearlog();  //clear the interrupt table
  attachInterrupt(0, tachmonitor, RISING);  //interrupt from tachometer
  digitalWrite(wheelpin,HIGH); //set pullup resistor for reed switch
  attachInterrupt(1, wheelmonitor, RISING);  //interrupt from reed switch wheel sensor
}

void loop(){
  checklog();  //make sure log is ok before processing
  processlog(); //process and dump the log file to the PC
  chkneutral(); //test for neutral
  chkstale(); //check to see if tach or speed readings are >3 seconds old (bike has stopped!)
  updatedisplay();
  delay(100);  //wait a while
}
void chkstale(){
  if ((millis()> (lastwheelint+3000)) && (wheelrpm>0)){
    wheelrpm=0; roadspeed=0;
  }
  if ((millis()> (lasttachint+3000)) && (insttachrpm>0)){
    insttachrpm=0; avgtachrpm=0;    
  }
}

void updatedisplay(){
  lcd.clear();
  pbc(currentgear); //display what gear we're in
  lcd.setCursor(4,0); lcd.print(int((avgtachrpm+50)/100)*100); //tach
  lcd.setCursor(4,1); lcd.print(int((roadspeed/1.6)+.5)); //speed in Mph
}
void chkneutral(){
  if (inneutral()){
    currentgear=10;
  } //10 is code for neutral
  else{ //need to see if we've come out of neutral with no other info   
    if (currentgear==10) {// if we used to be in neutral
      currentgear=0; //      0 is "don't know"
    }
  }
}

boolean inneutral(){ //see is neutral light is on
  int n=analogRead(neutralpin); //neutral light sensor is on analog pin 5 - usually <100 in neutral, >300 in gear.
  if (n<150){
    return(true);
  }
  else{
    return(false);
  }
}
void checklog(){ //make sure log hasn't overflowed
  if (logoflo==true){
    noInterrupts();
    clearlog();
    interrupts();
    Serial.print("log overflowed at ");
    Serial.println(millis());
  }
}

void processlog(){
  int dlog,i; //how many entries to copy and dump
  unsigned long dmillis[32];
  byte dflag[32];  //space for the log entries
  noInterrupts(); //disable interrupts while we copy the log
  dlog=ilog;  //copy the count
  if (dlog!=0) { //if there's anything to copy
    for(int i=0; i<dlog;i++){
      dmillis[i]=lmillis[i]; 
      dflag[i]=lflag[i];
    }
    ilog=0; //reset the count
  }
  interrupts(); //reenable interrupts
  if (dlog!=0){ //if there's anything to process
    for (i=0;i<dlog;i++){
      //Serial.print(i); Serial.print(dflag[i]); Serial.print(" "); Serial.print(dmillis[i]); Serial.print(" ");
      if (dflag[i]=='W') {
        processwheel(dmillis[i]);
      }
      else {
        if (dflag[i]=='T') {
          processtach(dmillis[i]);
        }
      }
      //Serial.println(" ");
    }
  }
}

void processtach(unsigned long lmillis){ // input is interrupt time in millis
  unsigned long tachtime; //elapsed time for 10 tach events
  if (lasttachint!=0) { //skip calculation on first time in
    tachtime=lmillis-lasttachint;  //time for last 10 events
    if (tachtime>0){ //just avoid divide by 0
      insttachrpm=60000/(float)tachtime*10;  //rpm based on time in ms for 10 tach pulses
      avgtachrpm=(avgtachrpm*2+insttachrpm)/3; //rolling average rpm
      //Serial.print(" tachtime="); Serial.print(tachtime); Serial.print(" inst tach rpm="); Serial.print(insttachrpm); Serial.print(" avg tach rpm="); Serial.print(avgtachrpm);
    }
  }
  lasttachint=lmillis;
}

void tachmonitor(){
  static int tachcount;
  if (tachcount <9) { //bulking up tach interrupts by a factor of 10
    tachcount++;
  }
  else {  //every 10th tach pulse we spawn a log entry
    if (ilog<=maxlog){
      lflag[ilog]='T'; //pedal pass flag
      lmillis[ilog]=millis();
      ilog++;
    } 
    else {
      logoflo=true; //we've overrun the log table
    }
    tachcount=0;
  }
}

void processwheel(unsigned long lmillis){ //input is time of interrupt from log
  unsigned long wheeltime; //time for previous revolution
  if (lastwheelint!=0){ //don't do calculations 1st time in
    wheeltime=lmillis-lastwheelint; //track the exact rotation time
    wheelrpm=60000/(float)wheeltime; 
    //Serial.print(" wheeltime="); Serial.print(wheeltime); Serial.print(" wheel rpm="); Serial.print(wheelrpm);
    roadspeed=.123*wheelrpm; //condensed version of 2.05*60*wheelrpm/1000; where 205 is wheel circ. in metres
    //Serial.print(" road speed kph="); Serial.print(roadspeed);
    setgear(); //figure out what gear we're in
  }
  lastwheelint=lmillis;
}

void wheelmonitor(){
    if (ilog<=maxlog){
      lflag[ilog]='W'; //wheel pass flag
      lmillis[ilog]=millis(); //record time
      ilog++;
    } 
    else {
      logoflo=true;
    }
}

remaining code:

int whichgear(float ratio){
  float validratios[]={
    11.7,8.37,6.65,5.54,4.61  }; 
  int maxratioindex=4; //ratios for motorcycle

  int gearsforratios[]={
    1,2,3,4,5    };
  for (int i=0;i<=maxratioindex;i++){
    if (closeto(ratio,validratios[i],5)) return gearsforratios[i];
  }
  return 0;
}

boolean closeto(float param, float reference, float pcttolerance){ //true if param is within tolerance% of reference
  return (param>=(reference*(100-pcttolerance)/100))&&(param<=(reference*(100+pcttolerance)/100));
}


void setgear(){ //determine which gear the bike is in
  int newgear; 
  static int prevgear, prev2gear;  
  if (wheelrpm!=0 && insttachrpm!=0){
    revratio=insttachrpm/wheelrpm;
    //Serial.print("<"); Serial.print(revratio); Serial.print(">");
    newgear=whichgear(revratio);
    if (newgear!=0){
      if (newgear==prevgear /*&& newgear==prev2gear*/){
        currentgear=newgear;
      //  Serial.print("Sel: "); Serial.print(newgear); Serial.print(" ");
      }
      //prev2gear=prevgear; 
      prevgear=newgear;
      //Serial.print("Candidate: "); Serial.print(newgear); Serial.print(" ");
    }
  }
}

void clearlog(){ //clear the interrupt log
  for(int i=0; i<=maxlog; i++){
    lflag[i]=' '; 
    lmillis[i]=12345678;
  }
  ilog=0; 
  logoflo=false;
}

void pbc(byte x){
  //routine to print digits 0-9 & letter N in double height characters on an LCD
  //strings to index into for displaying the big numbers
  char bignumchars1[]={
    32,32,32,0, 1,4,32,0, 3,3,4,0, 1,3,4,0, 4,2,4,0,   4,3,3,0, 4,3,3,0, 1,1,4,0,   4,3,4,0, 4,3,4,0, 4,6,4,0  }; //0 was 4,4,4
  char bignumchars2[]={
    32,32,32,0, 2,4,2,0,  4,2,2,0, 2,2,4,0, 32,32,4,0, 2,2,4,0, 4,2,4,0, 32,32,4,0, 4,2,4,0, 2,2,4,0, 4,7,4,0  }; //0 was 4,2,4
  lcd.setCursor(0,0);
  lcd.print(bignumchars1+x*4);
  lcd.setCursor(0,1);
  lcd.print(bignumchars2+x*4);
}
void setupbigchars(){
  //routine to load the character generator ram on an lcd controller with block characters
  //credit to dcb on arduino forums http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1213319639
  lcd.command(B01001000);  // set cgram
  byte chars[]={
    B11111,B00000,B11111,B11111,B00000,B00000,B01110,
    B11111,B00000,B11111,B11111,B00000,B11000,B00111,
    B00000,B00000,B00000,B11111,B00000,B11000,B00111,
    B00000,B00000,B00000,B11111,B00000,B11000,B00111,
    B00000,B00000,B00000,B11111,B00000,B11100,B00011,
    B00000,B00000,B00000,B11111,B01110,B11100,B00011,
    B00000,B11111,B11111,B11111,B01110,B11100,B00011,
    B00000,B11111,B11111,B11111,B01110,B01110,B00000  };

  for(byte x=0;x<7;x++)
    for(byte y=0;y<8;y++)
      lcd.write(chars[y*7+x]); //write the character data to the character generator ram    
}

void lcdbegin(){
  delay(25); //make sure there's a clear delay for lcd power-on initialization
  lcd.command(0x28);  // function set: 4 bits, 1 line, 5x8 dots
  lcd.command(0x0C);  // display control: turn display on, cursor off, no blinking
  lcd.command(0x06);  // entry mode set: increment automatically, display shift, right shift
  lcd.clear();delay(5);
  lcd.print("V8 Bill!"); delay(250); lcd.clear(); delay(5);
  setupbigchars();delay(25); //write big fonts to lcd cgram
}

I have them split into stubs which catch the interrupts and store them in a table, and routines which run periodically out of "Loop" which scan the table and handle the events

Nice! The essence of a micro-kernel running on a micro-conroller.

I bought (another!) bike speedo and I'm going to try using that as a readout - presumably it's waterproof.

It's also kind of a neat idea if you think of it as a bike speedo circuit bending exercise. You're basically breaking into the speedo's sensor wire and installing an arduino that changes the readout depending on the tach input.

Ok, so meet FrankenBoB - it's BoB with a head transplant.

This is my attempt to use the original bike speedometer head as a waterproof serial LCD display - initially I was just using it to show what gear the bike is in. The good news is, it's serial, it's waterproof, it's simple to drive. The bad news is it's meant to be a bicycle speedometer! It doesn't want to register very low speeds so I couldn't just run 1mph for first gear etc. I tried a bunch of things- and what I settled on was driving it at gear*10+"a bit" kph - the gear number shows up in the 10's digit of the speed. In principle you could mask off or disable the other digits but then you have a more limited display.

I had it out on the road today for a couple of hours and it worked fine except for being sluggish and the extra digits being distracting. I don't really care for the look of it either so I probably won't pursue the idea further - although knowing me I don't like to let a bad idea go without a fight.

Ok, in the spirit of beating a dead horse, I just had a go at keeping the gear indicator in the ones digit with an offset of 10.5 i.e. 1st gear is 11.5, 2nd is 12.5. This was responsive enough that I might try finding a small speedo who's LCD traces I can hack.

BoB has had another head transplant. This one is named Charlie - it's a set of 5 leds charlieplexed from 3 pins on the arduino. I's a tad fugly but it's something I wanted to try.


What are the 5 LEDs showing exactly?

It looks ok, you just need a better place to mount it I think. With the motorcycle being chrome, maybe you will need to spend some money getting a mount chromed...

Mowcius

there is chrome paint, its not very nice to apply but you get the look on any surface you want :wink: