Go Down

Topic: Motorcycle Control Panel with Arduino + Bluetooth (Read 26277 times) previous topic - next topic

bill2009


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

http://hackaday.com/2009/06/14/bluetooth-motorcycle-control-panel-with-arduino/

mowcius

Yeah but we read it here first guys...

Mowcius

bill2009

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 .


pakrat

[size=12]What is that square white object in the lower right corner?  [/size]It has five leads connected to the board. :)

Pakrat

mircho


pakrat

Quote
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


bill2009

#36
Jun 30, 2009, 02:52 pm Last Edit: Jul 01, 2009, 12:02 am by bill2009 Reason: 1
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.



Coding Badly

#37
Jun 30, 2009, 09:36 pm Last Edit: Jun 30, 2009, 09:38 pm by bcook Reason: 1
Quote
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

http://www.urbandictionary.com/define.php?term=stonkered
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"!   ;)

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

- Brian

bill2009

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.  

Adr1an

So how has BoB come up after the unexpected showering ?? :(
Checkout my projects development blog @ SLiDA

bill2009

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.

hu_man

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

bill2009

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.

Code: [Select]
//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);
 }
}

Code: [Select]
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;
   }
}



bill2009

remaining code:
Code: [Select]
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
}

Coding Badly

Quote
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.

Go Up