SO I was given a 1977 Schwinn Super Le tour 12.2 bike for Christmas, it needs work, but not a lot, I have new tires and brakes on the way, the chain is good just needs lube, and the axels just need greased.
on the front wheel it has an ancient bike computer, literally a rubber belt driven mileage counter, I cannot even see the numbers on it because the plastic is aged. On my last bike I had a little bike computer, that kept track of current speed, average speed per trip, mileage and time ridden for each trip, it just used a little reed switch on the fork and a tiny magnet on the wheel, I was looking into making one with arduino found a good schematic, and code I can modify to my limited modification ability (namely removing the safety messages that it displays on startup or changing them to something else,) but I donāt like the motor he set it up to use, a 16x2 backlit LCD, I found one on wish for $1.80 and ordered it today as my fall back because I can easily assemble it as per the original plan, but (long setup over) I have no idea how to make the arduino store the trips, not any idea how to make the kind of modification to the code to allow it, However, I was thinking, I have a little orange and white buddy who has a pinheader, and an SD card port, I bet there is a way to make it where he could be the display, and store each trip for me.
so here is my question, does anyone know if Pokitto can talk to the arduino, receive its info and display and store it?
This is the code, if anyone wants to see it, sorry I donāt know how to format it so it is in a window.
// ---------------------------------------------------------------------------------------------------------------------
// Bicycle Odometer & Speedometer
// Written by Alan De Windt for the Arduino Uno
// alan_dewindt@yahoo.com
// July 2018
//
// Hardware requirements:
// * 16 x 2 character LCD screen found in Arduino Starter Kit
// * Push button for pause/resume (on digital pin 2)
// * Push button for cycling (no pun intended!) through display modes (on digital pin 3)
// * Hall sensor which should be attached to bicycle wheel to sense when wheel has made a revolution (on digital pin 4)
//
// Noteworthy features:
// * Computes time traveled (in hours, minutes and seconds), distance in kilometers, average kilometers per hour for
// entire lap/period, average kilometers per hour during last minute, maximum kilometers per hour cycled during
// lap/period
// * Stores up to 99 laps/periods which can be viewed when in pause mode by pressing the Display Mode button
// NOTE: 100th lap gets recorded in position for lap 99 thus overriding data for 99th lap
// * Computes total time, kilometers traveled, average kilometers per hour and maximum kilometers per hour cycled for
// all laps/periods recorded (shown in "T" data when looking at lap data in pause mode)
// * No data is being recorded while in pause mode
// * Safety features:
// - "CYCLE SAFELY!" message appearing at start of every lap
// - Not possible to cycle through different display modes while lap is ongoing to minimize risk
// of cyclist "toying around/being distracted". Safety on the roads is paramount, so cycle safely!!!
//
// See the following YouTube video for demonstration and additional explanations:
// https://youtu.be/31X-BA0ff4o
//
// NOTE: You should calculate exact circumference of bicycle wheel (in meters) and update value initialized below
// in bicycleWheelCircumference
// ---------------------------------------------------------------------------------------------------------------------
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 8, 7, 6, 5);
// Circumference of bicycle wheel expressed in meters
float bicycleWheelCircumference = 2.1206;
const int pauseButton = 2;
boolean lastPauseButton = LOW;
boolean currentPauseButton = LOW;
const int displayModeButton = 3;
boolean lastDisplayModeButton = LOW;
boolean currentDisplayModeButton = LOW;
const int revolutionButton = 4;
boolean lastRevolutionButton = LOW;
boolean currentRevolutionButton = LOW;
boolean startShown = HIGH;
boolean paused = LOW;
boolean pausedShown = LOW;
unsigned long pausedStartTime = 0;
boolean wheelTurningShown = LOW;
unsigned long wheelTurningStartTime = 0;
boolean cycleSafelyShown = LOW;
unsigned long cycleSafelyStartTime = 0;
unsigned long lastRevolutionStartTime = 0;
unsigned long revolutionTime = 0;
int currentDisplayMode = 0;
int showLap = 0;
int lapCurrentlyShown = 100;
int currentLap = 0;
float currentDistance;
unsigned long currentDuration;
int currentMaximumKPH;
int currentAverageKPH;
int currentKPH;
float arrayDistance[100];
unsigned long arrayDuration[100];
int arrayMaximumKPH[100];
int arrayAverageKPH[100];
unsigned long revolutionCount = 0;
unsigned long currentTime = 0;
unsigned long lapStartTime = 0;
float km = 0.00;
float kph = 0.00;
int intHours;
int intMinutes;
int intSeconds;
unsigned long milliSecondsInSecond = 1000;
unsigned long milliSecondsInMinute = 60000;
unsigned long milliSecondsInHour = 3600000;
void setup()
{
// Configure digital input pins for push buttons and Hall sensor
pinMode (revolutionButton, INPUT);
pinMode (pauseButton, INPUT);
pinMode (displayModeButton, INPUT);
// Initialize maximum KPH in totals as this may not be calculated if no maximum was computed for laps
// and there may be random data in memory location
arrayMaximumKPH[0] = 0;
// Initialize LCD screen & show "PRESS BUTTON TO START"
lcd.begin(16, 2);
lcd.clear();
lcd.setCursor(2, 0);
lcd.print("PRESS BUTTON");
lcd.setCursor(4, 1);
lcd.print("TO START");
}
void loop() {
// Get current millis
currentTime = millis();
// Read revolution Hall sensor
currentRevolutionButton = debounce(lastRevolutionButton, revolutionButton);
if (lastRevolutionButton == HIGH && currentRevolutionButton == LOW) {
// If initial "PRESS BUTTON TO START" is not displayed and not currently paused...
if (!startShown && !paused) {
// Increase wheel revolution count
revolutionCount++;
// Display "+" to show that one revolution was recorded
lcd.setCursor(0, 0);
lcd.print("+");
wheelTurningShown = HIGH;
wheelTurningStartTime = currentTime;
// Compute millis it took for this latest revolution
if (lastRevolutionStartTime > 0) {
revolutionTime = currentTime - lastRevolutionStartTime;
// Compute current speed in kilometers per hour based on time it took to complete last wheel revolution
kph = (3600000 / revolutionTime) * bicycleWheelCircumference / 1000;
currentKPH = kph;
// If current speed is new maximum speed for this lap then store it
if (currentMaximumKPH < currentKPH) {
currentMaximumKPH = currentKPH;
}
}
lastRevolutionStartTime = currentTime;
}
}
lastRevolutionButton = currentRevolutionButton;
// Read PAUSE/RESUME push button
currentPauseButton = debounce(lastPauseButton, pauseButton);
if (lastPauseButton == LOW && currentPauseButton == HIGH) {
// If "PRESS BUTTON TO START" message has been showing then we now need to start 1st lap/period
if (startShown) {
startShown = LOW;
// Show "CYCLE SAFELY!" message
showCycleSafely();
cycleSafelyShown = HIGH;
cycleSafelyStartTime = currentTime;
currentLap = 1;
resetLapVariables();
currentDisplayMode = 1;
}
else {
// Otherwise if pause is active then we need to take it out of pause and start new lap/period
if (paused) {
paused = LOW;
// Show "CYCLE SAFELY!" message
showCycleSafely();
cycleSafelyShown = HIGH;
cycleSafelyStartTime = currentTime;
// Increment lap counter
currentLap++;
// If we are starting a 100th lap/period then we should write data into 99th array position (overwriting this lap)
// as we can only keep track of 99 laps/periods in total
if (currentLap > 99) {
currentLap = 99;
// Pretend lap 100 (out-of-bounds value) is currently shown (even though 99 is currently shown)
// to force display of new data for lap 99
lapCurrentlyShown = 100;
}
resetLapVariables();
currentDisplayMode = 1;
}
// Otherwise pause is not currently active so we need to save lap/period data and activate pause
else {
paused = HIGH;
// Calculate duration
currentDuration = currentTime - lapStartTime;
// If lap duration is less than 2 seconds (which means user pressed the pause button while "CYCLE SAFELY!" message
// was shown) then do not store the lap/ignore it
if (currentDuration < 2000) {
currentLap--;
}
// Otherwise store the lap
else {
// Compute distance and average kilometers per hour if bicycle moved
if (revolutionCount > 0) {
currentDistance = revolutionCount * bicycleWheelCircumference / 1000;
currentAverageKPH = currentDistance * 3600000 / currentDuration;
}
// Store data for lap/period into array
arrayDistance[currentLap] = currentDistance;
arrayDuration[currentLap] = currentDuration;
arrayAverageKPH[currentLap] = currentAverageKPH;
arrayMaximumKPH[currentLap] = currentMaximumKPH;
// Update totals for all laps/periods
arrayDistance[0] = arrayDistance[0] + currentDistance;
arrayDuration[0] = arrayDuration[0] + currentDuration;
arrayAverageKPH[0] = arrayDistance[0] * 3600000 / arrayDuration[0];
if (currentMaximumKPH > arrayMaximumKPH[0]) {
arrayMaximumKPH[0] = currentMaximumKPH;
}
}
// In case "CYCLE SAFELY!" has been showing, turn it off now since we want to show "PAUSED!" message
// and we don't want it to be removed when "CYCLE SAFELY!" times out
cycleSafelyShown = LOW;
// Show "PAUSED!" message
showPaused();
pausedShown = HIGH;
pausedStartTime = currentTime;
// We will need to show data for lap which was just finished
showLap = currentLap;
currentDisplayMode = 3;
// Set out-of-bounds value to lapCurrentlyShown to force lap data to be shown
lapCurrentlyShown = 100;
}
}
}
lastPauseButton = currentPauseButton;
// Read DISPLAY MODE push button
currentDisplayModeButton = debounce(lastDisplayModeButton, displayModeButton);
if (lastDisplayModeButton == LOW && currentDisplayModeButton == HIGH) {
// If "PRESS BUTTON TO START" message has been showing then we now need to start 1st lap/period
if (startShown) {
startShown = LOW;
// Show "CYCLE SAFELY!" message
showCycleSafely();
cycleSafelyShown = HIGH;
cycleSafelyStartTime = currentTime;
currentLap = 1;
resetLapVariables();
currentDisplayMode = 1;
}
else {
// Otherwise if "CYCLE SAFELY!" message is not shown nor is "PAUSED!" message shown...
if (!cycleSafelyShown && !pausedShown) {
// If not currently paused (so lap is ongoing)...
if (!paused) {
// Flip between the two different display modes available
if (currentDisplayMode == 1) {
currentDisplayMode = 2;
}
else {
currentDisplayMode = 1;
}
// Clear display and show appropriate labels
showLabels(currentDisplayMode);
}
// Otherwise we are in paused mode so cycle through lap data available, including totals page
else {
currentDisplayMode = 3;
showLap++;
if (showLap > currentLap) {
showLap = 0; // Show totals
}
}
}
}
}
lastDisplayModeButton = currentDisplayModeButton;
// If wheel revolution indicator has been showing, take if off if it has been 250 millis or more
if (wheelTurningShown && !startShown && !paused && (currentTime >= (wheelTurningStartTime + 250))) {
wheelTurningShown = LOW;
lcd.setCursor(0, 0);
lcd.print(" ");
}
// If wheel revolution indicator has been showing, take if off if it has been 250 millis or more
if (!startShown && !paused && (currentTime >= (lastRevolutionStartTime + 10000)) && currentKPH > 0) {
currentKPH = 0;
}
// If "Cycle Safely!" has been showing, take it off if it has been 2 seconds or more
if (cycleSafelyShown && (currentTime >= (cycleSafelyStartTime + 2000))) {
cycleSafelyShown = LOW;
showLabels(currentDisplayMode);
}
// If "Paused!" has been showing, take it off if it has been 2 seconds or more
if (pausedShown && (currentTime >= (pausedStartTime + 2000))) {
pausedShown = LOW;
showLabels(currentDisplayMode);
}
// If "PUSH BUTTON TO START" is not showing and not currently paused...
if (!startShown && !paused) {
// Compute milliseconds since start of lap
currentDuration = currentTime - lapStartTime;
// Compute distance and average kilometers per hour if bicycle has moved
if (revolutionCount > 0) {
// Compute kilometers traveled
// Circumference of wheel is in meters
currentDistance = revolutionCount * bicycleWheelCircumference / 1000;
// Compute average kilometers per hour since start of lap
currentAverageKPH = currentDistance * 3600000 / currentDuration;
}
}
// If no messages are currently showing then update data on display
if (!startShown && !cycleSafelyShown && !pausedShown) {
if (currentDisplayMode < 3) {
lcd.setCursor(1, 0);
lcd.print(currentDistance);
lcd.print(" km");
lcd.setCursor(14, 0);
if (currentKPH < 10) {
lcd.print(" ");
}
lcd.print(currentKPH);
computeHMS(currentDuration);
lcd.setCursor(1, 1);
if (intHours < 10) {
lcd.print("0");
}
lcd.print(intHours);
lcd.print(":");
if (intMinutes < 10) {
lcd.print("0");
}
lcd.print(intMinutes);
lcd.print(":");
if (intSeconds < 10) {
lcd.print("0");
}
lcd.print(intSeconds);
lcd.setCursor(12, 1);
lcd.print("A");
if (currentDisplayMode == 1) {
lcd.setCursor(12, 1);
lcd.print("A");
lcd.setCursor(14, 1);
if (currentAverageKPH < 10) {
lcd.print(" ");
}
lcd.print(currentAverageKPH);
}
else {
lcd.setCursor(12, 1);
lcd.print("M");
lcd.setCursor(14, 1);
if (currentMaximumKPH < 10) {
lcd.print(" ");
}
lcd.print(currentMaximumKPH);
}
}
// Otherwise device is paused so show historical lap information
else {
// Update display only if we need to show data for different lap to that currently shown
// this way display is not constantly cleared and refreshed with same data which would
// cause display to flicker and is not needed anyway as data is not changing
if (lapCurrentlyShown != showLap) {
lapCurrentlyShown = showLap;
lcd.clear();
lcd.setCursor(0, 0);
if (showLap == 0) {
lcd.print("T ");
}
else {
lcd.print(showLap);
}
lcd.setCursor(3, 0);
lcd.print("Avg");
lcd.setCursor(7, 0);
lcd.print(arrayAverageKPH[showLap]);
if (arrayAverageKPH[showLap] < 10) {
lcd.print(" ");
}
lcd.setCursor(10, 0);
lcd.print("Max");
lcd.setCursor(14, 0);
lcd.print(arrayMaximumKPH[showLap]);
if (arrayMaximumKPH[showLap] < 10) {
lcd.print(" ");
}
lcd.setCursor(0, 1);
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print(arrayDistance[showLap]);
computeHMS(arrayDuration[showLap]);
lcd.setCursor(8, 1);
if (intHours < 10) {
lcd.print("0");
}
lcd.print(intHours);
lcd.print(":");
if (intMinutes < 10) {
lcd.print("0");
}
lcd.print(intMinutes);
lcd.print(":");
if (intSeconds < 10) {
lcd.print("0");
}
lcd.print(intSeconds);
}
}
}
}
// Compute hours, minutes and seconds for given duration expressed in milliseconds
void computeHMS(unsigned long duration) {
float floatHours;
float floatMinutes;
float floatSeconds;
intHours = 0;
intMinutes = 0;
intSeconds = 0;
if (duration >= 1000) {
floatSeconds = duration / milliSecondsInSecond % 60;
intSeconds = floatSeconds;
floatMinutes = duration / milliSecondsInMinute % 60;
intMinutes = floatMinutes;
floatHours = duration / milliSecondsInHour % 24;
intHours = floatHours;
}
}
// Reset all variables used for calculating current/ongoing lap
void resetLapVariables() {
revolutionCount = 0;
lapStartTime = currentTime;
currentDistance = 0;
currentDuration = 0;
currentMaximumKPH = 0;
currentAverageKPH = 0;
}
// Show "CYCLE SAFELY!"
void showCycleSafely() {
lcd.clear();
lcd.setCursor(5, 0);
lcd.print("CYCLE");
lcd.setCursor(4, 1);
lcd.print("SAFELY!");
}
// Show "PAUSED!"
void showPaused() {
lcd.clear();
lcd.setCursor(4, 0);
lcd.print("PAUSED!");
}
// Show appropriate labels for current mode
void showLabels(int currentDisplayMode) {
lcd.clear();
switch (currentDisplayMode) {
case 1:
lcd.setCursor(12, 0);
lcd.print("S");
lcd.setCursor(12, 1);
lcd.print("A");
break;
case 2:
lcd.setCursor(12, 0);
lcd.print("S");
lcd.setCursor(12, 1);
lcd.print("M");
break;
}
}
//A debouncing function that can be used for any button
boolean debounce(boolean last, int pin)
{
boolean current = digitalRead(pin);
if (last != current) {
delay(5);
current = digitalRead(pin);
}
return current;
}