Pokitto Biketto

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;
}

also I remembered this post after I made mine,

but there is no code there for me to look at.

If you have to figure out how to make the Pokitto display and store data, you may as well figure out how to make the Arduino do it instead. Otherwise make the Pokitto do everything. Thereā€™s no use having two devices that need to talk to each other when either one could easily do the job alone.

2 Likes

Fair enough, from someone who knows how different is pokitto ide from the arduino ide. I. An sort of understand arduino but only very rudimentarily

1 Like

Technically yes, you can just hook the Pokittoā€™s PEX to the Unoā€™s pins*.
But the problem then changes from ā€œHow do I hook up a Pokitto to an Uno?ā€ to ā€œHow do I communicate my information over a series of digital pins?ā€.
Itā€™s not just a matter of sending the data over the wire,
you have to have a way to identify what data is being sent, how much data is being sent, when the data is being sent, and so on.

(Theoretically you could use an existing data transfer protocol like SPI to handle the transfer part, but that still leaves the part where you have to identify the data being sent and specify how itā€™s structured. Again, there are standards that could help with that, but even soā€¦)

As @MLXXXp says, itā€™s a lot easier to just write for a single device.

If you want help with your task youā€™ll need to tell us more about your intended set up and your objectives.
Specifically what you want your device to be able to do and what hardware you already have (or have ordered, or intend to use).

So far all we know for definite is that you want to be able to calculate how many miles you cover in a single trip and store that information ā€˜somewhereā€™ for multiple trips.
The code you included also computes the time spent travelling,
but how important is that feature to you?
Are you aiming to effectively translate this code to Pokitto,
or do you just want to modify it to use different slightly hardware?

You mention an LCD but donā€™t specify anything about it (e.g. model number, interface).
The code mentions that it was intended to be used with the LCD found in the Arduino Starter Kit (which I just happen to have), but will you be using the same model?


* I attempted something like this a while back, but I was worried about the Unoā€™s 5v causing damage to the Pokittoā€™s pins because I wasnā€™t sure if the Pokitto could handle 5v inputs given that it runs on 3.3v, but @jonne has since assured me the Pokitto should be able to handle 5v without any issue.


Warning: water is wet. :P

(A better feature would be to display a warning when the wheels are rotating too fast.)

Most likely itā€™s one of the popular HD44780 ones.

The listing calls the display (white on blue) IIC I2C TWI 1602.
I wanted white on black or an equivalent OLED but itā€™s no big deal.

For sensors I have a small glass reed switch that will be in a basswood ā€œblockā€ and epoxy coated for durability and neodymium magnet on the spoke to trigger it.

Honestly if pokitto can do it all Iā€™d rather use it, because it has a sad card that can store the data.

I want to keep track of
Current speed (based on rotations of wheel)
Current distance ridden
Current time ridden

And store total distance and time and possibly average speed over all trips.

The buttons would be to start the trip and end it
Perhaps a to start and remove previous trip b to end and save current trip c to access menu where you can either go for a ride or view past ride information or settings for wheel diameter and units.

Nothing super fancy just numbers on the screen for speed time and distance (perhaps trip and total).

1 Like

1 Like

Thatā€™s the same model that comes with the Arduino starter kit.
The layoutā€™s slightly different, but it seems to have all the same things.

1 Like

Cool, I honestly wanted yellow on black but couldnā€™t find an affordable one I just hope that if I make the one from the tutorial it will be visible in daylight though Iā€™d rather use pokitto. That way others could benefit from it easier.

1 Like

The best colour for daylight is probably black pixels on yellow background. Price is about $2 US on eBay

https://www.ebay.com/sch/i.html?_nkw=1602+hd44780+yellow

Thereā€™s 1K of EEPROM on an Arduino ATmega328p based UNO and ATmega32U4 based Leonardo and any other board based on those microcontrollers. If you used 4 bytes (able to hold a float) for each of distance and time you could save 128 trips in EEPROM. You donā€™t need average speed because it can be calculated from the time and distance data.

1 Like

Cool, Im just going to ask if anyone wants to assist / Guide me in this endeavor? Yes Ive had Ideas before about things for pokitto but I admit then I had little understanding of how ti really works ,I have been messing with arduino for a couple of months (thanks to gus at Next thing for sending me that one so long ago lol) I can usually understand what is going on from the code especially if it is annotated though I am curious if there is a book on it.

but as I have thought about it more today, I think Id prefer to use pokitto for this project

1 Like

A Schwinn Super Le Tour - Sky Blue, Flamboyant Red or Silver Mist ?

If you ever have any questions, donā€™t hesitate to ask,
either by making a topic on the forum or sending me a personal PM.

Do not fall into the trap of ā€œthis question is too simple to askā€ or ā€œitā€™ll probably make me look stupidā€ or ā€œtheyā€™re probably fed up with people asking simple questionsā€.

Especially where Iā€™m concerned.
I always enjoy answering questions about C++, even the supposedly simple ones.

Less than a decade ago I had never written a single line of code and I didnā€™t even know the difference between a kilobyte and a megabyte, so I remember exactly what itā€™s like to be clueless.

(Also I canā€™t actually ride a bicycle so you can already do something I canā€™t. :P)

Thereā€™s probably lots of books about Arduino.
Thereā€™s certainly loads about C++.

I have a couple of old copies of Make magazine in .pdf form that I could give you a copy of if you want.
(Iā€™ve never actually read them though, so I donā€™t know how good they are.)

For C++ thereā€™s plenty of online material,
though my favourite C++ tutorial to recommend (though Iā€™ve only skimmed the odd chapter) is https://www.learncpp.com/

I know Iā€™m forever peddling it, but my Resource Collection is full of links to useful websites:

I do update it every now and again.
Just the other day I added two more C++ resources for advanced C++ programming.

You need to get out and do that. You might even find that you love it!

1 Like

Silver mist. And in surprisingly good condition for 20+ years in a garage/barn. Though those pedals will be getting changed out in the refit, theyā€™re nice but too small for my small feetā€™s. And Iā€™m not fond of the idea of my shoes locked in.

Nice bike ā€¦ you need to restore it to its former glory!

Here is a site for you with catalogues and so forth > http://bloggingadeadhorse.com/schwinn2.php Its my site.

A What do you know about cabling for it? Thatā€™s where Iā€™m having trouble, they all work, but I want to put fresh line.

And Iā€™m having issues finding out If it uses off the shelf cables or has special stuff Iā€™ll have to have cut.

Also Iā€™ll add some pics tomorrow when I get home. I need before and after restoration pics.

Iā€™ve had two people tell me to change it over to the wider 700 series wheels but honestly I prefer the old style 27ā€ ones besides I already have a set of classic tan wall tires for it.

Swapping to 700 wheels will also mean you might have to ditch the existing brakes - 27" and 700 rims are 15mm different so unless your brakes have an additional 7.5mm play in the brake pad adjustment, you might be in trouble.

I am sure the cable is just standard - take the bike down to a bike shop and they will sell you everything.