Premessa
Finalmente è arrivata l’estate e con essa il caldo; la voglia di uscire e stare all’aria aperta si fa sentire ogni giorno con maggiore forza e, talvolta, nei pigri pomeriggi cittadini, nelle assolate domeniche fuori porta o sulla passeggiata del lungo mare decidiamo di abbandonare la tanto amata automobile per tornare a mezzi di locomozione più tradizionali: le nostre gambe ma, soprattutto, la bicicletta.
I più curiosi, in queste occasioni, vorrebbero sapere quanti chilometri riescono a percorrere, quale sia la velocità di punta toccata e quante pedalate riescono a fare in un minuto; magari per vantarsi con gli amici :-). E’ per far fronte a queste esigenze che io ed Enkel abbiamo pensato di progettare il bikeComputer con Arduino. In questo articolo vi racconterò come abbiamo fatto.
L'immagine che vedete qui sopra è il prototipo funzionante che abbiamo avvolto nello scotch sul manubrio della bici di Enkel per fare il test "su strada" :-)
Potete trovare tutte le immagini del progetto in questa pagina.
L'idea
Per contare i giri della ruota abbiamo utilizzato un reed switch. Questo sensore si comporta come un interruttore: esso rimane normalmente aperto (e quindi non passa corrente); arduino lo vede come un segnale digitale LOW.
Quando passa un magnete vicino (posizionato sui raggi della ruota), il sensore si chiude facendo passare corrente; in questo modo restituisce un HIGH digitale ad arduino riconosce il passaggio del magnete; il tempo trascorso tra un passaggio e l’altro ci permette di calcolare la velocità istantanea (e successivamente quella media) e, conoscendo il raggio della ruota, si può calcolare il numero di metri percorsi.
La memoria ha un duplice scopo: l'utente dovrà inserire un file da denominare config.txt (qualsiasi operativo può creare un file di testo semplice) dove inserisce un numero di 4 cifre che esprime il diametro della ruota della propria bicicletta in mm (serve per il calcolo della distanza percorsa); dal canto suo arduino aggiorna il file log.txt.
Tale file sarà creato ed alimentato da arduino con tante righe quanti sono i passaggi del magnete; ogni riga contiene il numero di millisecondi passati dall’accensione di arduino (tramite la funzione millis()) nel momento in cui si rileva il passaggio del magnete vicino al reed (da questi è poi semplice ricavarsi tutto il resto magari effettuando un grafico con Processing).
L’alimentazione del sistema è stata affidata ad un pacco batterie composto da 4 pile in serie. Conteggiando circa 1.2-1.5V per ciascuna si arriva ad una tensione di alimentazione di 4.8-6V che abbiamo inserito direttamente nel pin Vin per evitare inutili dispersioni di energia.
Per quanto riguarda il “case” ci siamo appoggiati al FabLab di Torino (nelle immagini non è ancora presente ma è "coming soon") grazie all’aiuto di Davide G. ed Enrico C. che ci hanno aiutato con il laser-cutter a progettare un adeguato alloggiamento per il sistema.
Una esigenza con la quale ci siamo scontrati, inoltre, era il desiderio di rendere indipendente il reed sensor e il “case” con arduino dentro: l’idea è quella di arrivare a destinazione (magari davanti un supermarket per fare la spesa) e poter staccare tutto il sistema (per non lasciarlo in balia di occhi indiscreti e mani disoneste); a questo proposito è stata ingegnosa l’intuizione di Enkel che ha pensato di riutilizzare delle vecchie cuffie stereo per walkman: abbiamo tagliato gli auricolari dai quali prelevare due fili (massa e segnale); dall’altra estremità il jack da 3.5mm era già pronto per essere inserito durante l’utilizzo ed estratto a destinazione.
Schema

Codice
Di seguito il codice utilizzato/*
////////////////////////////////////////////////
[ Arduino Cycle Computer ] Version A0.2
>> Original written by Adam O'Hern
>> Modified by Alexdlp for Instructables 2011
>> Modified by Enkel Bici
>> and Vittorio Zuccala' for Wired.it 2011
////////////////////////////////////////////////
*/
#include <LiquidCrystal.h>
//#include <LcdBarGraph.h>
// initialize the library with the numbers of the interface pins
LiquidCrystal lcd( 7,6, 5, 4, 3, 2);
#include <SD.h>
File myFile;
//byte lcdNumCols = 15; // -- number of columns in the LCD
//LcdBarGraph lbg(&lcd, lcdNumCols); // -- creating
int LED = 13; //pin for the LED
int SensorPin = 15; //input for REED SensorPin (analog 0 as digital)
int rim = 2035; //circumference of wheel in mm (28" rim)
int val = 0; // used to store input value
int previousVal = 0; // lets not be too repetitious
int debounce = 10; // the debounce time, increase if the output flickers
int cycles = 1; // total number of revolutions
float currentSpeed = 0; // current speed in MPH
float averageSpeed = 0; // average speed since "newRide" was true
unsigned long revTimer; // create a timer that tells us how long we go between pulses,
unsigned long serialTimer; // and one for how long it's been since we sent anything over serial
unsigned long rideTimer; // total time since "newRide" was true
boolean activeRiding = false; // is the bike moving?
boolean activityChange = true; // just a way of keeping track of how long we're inactive
long inactivityTimer; // millis() on which we began inactivity
long activeRidingOffset = 0; // time subtracted from total riding time when calculating average speed
boolean newRide = true; // true if we haven't moved in half an hour or more
float rideDistance = 0; // total distance traveled since "newRide" was true
void setup() {
lcd.begin(16, 2);
pinMode(LED, OUTPUT); // tell arduino LED port is an output,
pinMode(SensorPin, INPUT); // and SensorPin port is input
lcd.setCursor(0,0);
lcd.print("Init SD...");
lcd.setCursor(0,1);
if (!SD.begin(8)) {
lcd.print("Init failed!");
delay(20000);
return;
}
else{
delay(2000);
lcd.print("Init OK!!");
delay(10000);
lcd.clear();
myFile = SD.open("setup.txt");
if (myFile) {
lcd.setCursor(0,0);
lcd.print("wheel:");
lcd.setCursor(0,1);
char c[5];
int count=0;
// read from the file until there's nothing else in it:
while (myFile.available()) {
c[count]=myFile.read();
count++;
}
// close the file:
myFile.close();
delay(2000);
rim=0; //reset the wheel diameter
for(count=0;count<4;count++){ //it supposes the diameter is expressed in mm and composed of 4 characters written in a text file on the sd card
rim=rim+(c[count]-48)*pow(10,(3-count)); //each character reappresents a number. 0 is equal 48 in ascii code. From then on you add 1 to get the next numbers.
}
rim=rim+1; //magic number
lcd.print(rim);
delay(2000);
}
else {
// if the file didn't open, print an error:
lcd.print("error reading...");
delay(20000);
}
}
Serial.begin(9600); // start a serial session
revTimer = millis(); // start pulse timer
serialTimer = millis(); // start serial timer
rideTimer = millis(); // start ride timer
}
void loop(){
if(!activeRiding) {
if(activityChange) {
inactivityTimer = millis();
activityChange = false;
}
}
else {
if(activityChange) {
activeRidingOffset += millis() - inactivityTimer;
activityChange = false;
}
}
val = digitalRead(SensorPin);
if (val==LOW) { // check whether input is LOW (magnet is NOT in range of reed SensorPin)
digitalWrite(LED, LOW); // turn LED off
previousVal = LOW; // allow the next "pulse" to be counted
}
else{
digitalWrite(LED, HIGH); // turn LED off
lcd.setCursor(1, 0); // Set cursor to the top right of LCD
lcd.print("*"); // Print a blank character or 'space'
//delay(100); // debounce timer
lcd.setCursor(1, 0); // Set cursor to the top right of LCD
lcd.print(" "); // Print a blank character or 'space'
if (previousVal != HIGH && millis() - revTimer > debounce) { // we've got a pulse!
pulse();
}
previousVal = HIGH; // (in case the magnet is in range of the SensorPin while sitting still)
}
// if it's been too long since the last pulse, assume we're not moving.
if(millis()-revTimer > 2000) {
currentSpeed = 0;
sendStats();
if(activeRiding) {
activityChange = true;
activeRiding = false;
}
}
// if it's been more than fifteen minutes...
if (millis() - revTimer > 15*60*1000) {
// we'll assume it's a new riding session & zero everything out at next pulse.
newRide = true;
}
}
void pulse() {
if(newRide) {
lcd.clear();
cycles = 0;
averageSpeed = 0;
rideTimer = millis();
rideDistance = 0;
//log data to file
// open the file. note that only one file can be open at a time,
// so you have to close this one before opening another.
File dataFile = SD.open("log.txt", FILE_WRITE);
// if the file is available, write to it:
if (dataFile) {
dataFile.print("newride");
dataFile.println(",");
dataFile.close();
// print to the serial port too:
//Serial.println("newride");
}
// if the file isn't open, pop up an error:
else {
lcd.setCursor(1, 0);
lcd.println("can't write");
}
}
cycles++; // The wheel has obviously turned another revolution
rideDistance = (float) rim*cycles/1000; // distance in meters,
//rideDistance = rideDistance * 0.000621371192; // converted to miles
currentSpeed = (float) (millis() - revTimer)*0.001; // Convert time elapsed to milliseconds to seconds
currentSpeed = rim/currentSpeed; // S = d/t: Rim circumference divided by time elapsed
//currentSpeed = currentSpeed*0.002237; // MPH Conversion: 1 mm/s = 0.001 m/s * 3600 s/hr * 1 mile / 1609 m = 0.002237
i/hr
currentSpeed = currentSpeed*0.0036; // 1Km/hr Conversion: 1 mm/s = 0.001 m/s * 3600 s/hr * 1Km/1000m = 0,0036 Km/hr
// time ridden since "newRide", in hours, not including inactive time
unsigned long activeRidingMillis = millis() - rideTimer - activeRidingOffset;
//float activeRidingHours = (float) activeRidingMillis/1000/60/60; // convert to hours
float activeRidingSeconds = (float) activeRidingMillis*0.001; // convert to hours
averageSpeed = rideDistance / activeRidingSeconds;
averageSpeed = averageSpeed*3,6;
revTimer = millis(); // remember the current moment for speed calculations next time around
sendStats(); // Tell Processing what's going on
newRide = false;
if(!activeRiding) { //if you start from standin still
activityChange = true;
activeRiding = true;
}
//log data to file
// open the file. note that only one file can be open at a time,
// so you have to close this one before opening another.
File dataFile = SD.open("log.txt", FILE_WRITE);
// if the file is available, write to it:
if (dataFile) {
dataFile.print(revTimer);
dataFile.println(",");
dataFile.close();
// print to the serial port too:
Serial.println(revTimer);
}
// if the file isn't open, pop up an error:
else {
lcd.setCursor(1, 0);
lcd.println("can't write");
}
}
void sendStats() {
/*
Serial.print("rideDistance=");
Serial.print(rideDistance,2);
Serial.print("¤tSpeed=");
Serial.print(currentSpeed,1);
*/
lcd.setCursor(0, 1);
lcd.print("Speed = ");
lcd.setCursor(9, 1);
lcd.print(currentSpeed,1);
lcd.setCursor(13, 1);
lcd.print("kmh");
//lbg.drawValue(currentSpeed, 35);
/*
Serial.print("&averageSpeed=");
Serial.print(averageSpeed,2);
// send a linefeed character, telling Processing that we're done transmitting.
Serial.print(10,BYTE);
*/
serialTimer = millis();
}



