Motorcycle Control Panel with Arduino + Bluetooth

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:

I hate to be a bother Bill, but I am currently attempting to build a digital tach for a car my buddies and I will be entering in the "24 hours of lemons" race this fall.

Can you please give me a run down on how you built the circuit to trap the tachometer signal from the bike?

I am a definite novice when it comes to electronics, I was trying to read the pulses earlier today and am afraid I've killed my first arduino board.

Please help me if you can, Thanks.

Say hello to my little friend. A while ago I decided that there would be two versions of the computer control panel. One, code-named Brawny, would be a full function arduino clone with heavy duty power and connections, and would have all kinds of output capabilities - LCD, bluetooth etc. Brawny became BoB who has been successful but won't fit on my bike without taking off the tachometer.

Pikachu, I figured, would be based on an attiny, have simpler connections, and be small enough to fit alongside my tachometer. Sadly, I haven't succeeded with the attiny and I couldn't put aside my connection lust. The results is sort of a Pikachu-heavy. I'm still using a 168/328 with a resonator but it MIGHT be able to fit.

I'm pretty pleased with how the parts layout turned out. I look at it now and I think, yeah, that's pretty obvious, but this took me hours and hours of staring and poking. On the bottom right are 4 voltage dividers, one each for reading battery voltage, signal lights, neutral light, and tachometer. The bottom left is a 10K resistor on the reset line and above that the debounce circuit for the reed switch wheel sensor. There's female headers for the tx&rx pins but no other outputs yet.

I've decided the most practical waterproof output is the charlieplexed led strip. That needs 3 pins, probably the 28-27-26 on the top left.

I really wish I could have used an attiny, I look at the bottom of the board and half of the atmega's pins aren't connected.

Anyway, I've tested it with a few sketches and it worked fine, I'm hoping to try it out with serial output tonight. Once I have that I'll start on a new LED strip hoping to get done in the next week before I leave on a 5,000 km motorcycle trip.

Pikachu hits the road.

Tomorrow I'm leaving on a three week motorcycle trip starting in Ottawa and ending in Seattle with lots of stops in between. I got pikachu buttoned down under the tank cowl and it's on board for the ride.

The only functions implemented are the basic gear indicator and a voltmeter during startup.

Wish me luck.