openobject.org

Spoke-o-dometer Beta 8

From Open Source Urbanism

Code for the Arduino spoke-o-dometer project
Status: working
Environment: Arduino 0011 (not working on version 12)

Download Arduino sketch: spoke-o-dometer Beta 8.zip

Or copy the following code into the Arduino programming environment (use the font.h code from Bike_POV_Beta_4).

//============================================================
// spoke_o_dometer
//
// by Scott Mitchell
// www.openobject.org
// Open Source Urbanism
//
// Copyright (C) 2008 Scott Mitchell 12-10-2008
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// A copy of the GNU General Public License
// can be found at <http://www.gnu.org/licenses/>.
//
// B8.0
// Last Modified: November 4, 2008
//============================================================

#undef int() // fixes a bug with the stdio.h
#include <stdio.h> // gives access to function sprintf

#include <EEPROM.h> //Needed to access the eeprom read write functions

// defining the alphabet
#include "font.h"

// get access to better timekeeping. ref: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1193623343
extern volatile unsigned long timer0_overflow_count;

// define the Arduino LED pins in use.
const byte LEDpinsRHS[] = {
  2,3,4,5,6,7,8};
const byte LEDpinsLHS[] = {
  15,14,13,12,11,10,9};

// use #define were posible to reduce memory load
// sensor setup
#define sensorPIN 5  // define the Arduino sensor pin
#define mean 508  // sensor at rest (no magnet)
#define sensitivity 30  // sensor sensitivity

// EEPROM setup
#define saveSwitchPin 16 // digital pin for save button
#define memLoc 168 // EEPROM memory address - any number between 0 and (512 - 4)
unsigned long storedMetres; // the distance in metres as stored in memory

// number of LEDs
#define charHeight 7
#define charWidth 5

// set up display parameters
#define numChars 30  // number of characters in the display (increasing this number will condense char width)
#define letterSpace 4	// number of frames between characters
const int numFrames = (numChars + 5) * (charWidth + letterSpace); // number of frames per rotation with buffer of 5 characters to prevent truncation
#define RtextOffset 200  // right hand text start distance from magnet in frames
#define LtextOffset 120  // left hand text start distance from magnet in frames
int RtextPoss = 0;  // the possition in the text string - right side
int LtextPoss = numChars - 1;   // the possition in the text string - left side
int RcharIndex = 0;  // the font index number for the current caracter - right side
int LcharIndex = 0;  // the font index number for the current caracter - left side
int RbyteIndex = 0;  // the byte index number for the current caracter byte - right side
int LbyteIndex = charWidth - 1;  // the byte index number for the current caracter byte - left side
unsigned long frameDuration = 1000;	// time for each frame in microseconds - updated from rotation speed
unsigned long startTime = microSeconds();	// start time for wheel rotation in microseconds
unsigned long RresetTime = 5000000;	// reset time for text in microseconds (5 sec)
unsigned long LresetTime = 5000000;	// reset time for text in microseconds (5 sec)

// distance and speed variables (floats are avoided because they slow the Arduino down)
unsigned long numWheelRotations;  // the base from which all other data is calculated
#define wheelCircum 1100  // bike wheel circumference in mm (adjust this to match your bike)

// text string
char textString[numChars] = "HELLO"; // declared large enough to hold the full string

void setup()
{
  int i;
  for (i = 0; i<charHeight; i++){
    pinMode(LEDpinsRHS[i], OUTPUT);	// set each pin as an output
    digitalWrite(LEDpinsRHS[i], HIGH);  // turns the LEDs off
    pinMode(LEDpinsLHS[i], OUTPUT);	// set each pin as an output
    digitalWrite(LEDpinsLHS[i], HIGH);  // turns the LEDs off
  }
  pinMode(saveSwitchPin, INPUT);  // set the save button as an input
  digitalWrite(saveSwitchPin, HIGH);  // sets the pull-up resistor

  // should we clear the EEPROM memory?
  i=0;
  while(digitalRead(saveSwitchPin) == LOW){  // is the save switch being pressed?
    cycleLEDs(HIGH, 20); // indicate the button press and take care of debounce
    cycleLEDs(LOW, 20);
    i++;
    if(i>5){ // if the button is held for longer than 4 cycles
      EEPROMWriteInt(memLoc, 0); // write 0 to the memory location
      allLEDs(LOW); // indicate a clear has taken place
      delay(1000);
      allLEDs(HIGH);
      break;  // exit the while loop
    }
  }
  // set the number of wheel rotations from data stored in EEPROM memory
  storedMetres = EEPROMReadInt(memLoc);  // EEPROM value is stored in metres
  numWheelRotations = (storedMetres * 1000)/ wheelCircum;  // metres converted to number of wheel rotations
}

void loop()
{
  byte Rbyte, Lbyte;
  unsigned long currentTime = microSeconds();

  // check the reset time
  if (currentTime > RresetTime){
    RtextPoss = 0;
    RcharIndex = charToFontIndex(textString[RtextPoss]);
    RbyteIndex = 0;
    RresetTime = currentTime + 50000000; // set to reset in 5 seconds.
  }
  if (currentTime > LresetTime){
    LtextPoss = numChars - 1;
    LcharIndex = charToFontIndex(textString[LtextPoss]);
    LbyteIndex = charWidth - 1;
    LresetTime = currentTime + 50000000; // set to reset in 5 seconds.
  }

  // get the RHS character byte for printing
  if (RbyteIndex < charWidth){
    Rbyte = font[RcharIndex][RbyteIndex];
  } 
  else if (RbyteIndex < (charWidth + letterSpace)){
    Rbyte = font[0][0];  // blank space
  } 
  else {
    // move to next character in text string (if there is one)
    if (RtextPoss < numChars -1){
      RtextPoss++;
      RcharIndex = charToFontIndex(textString[RtextPoss]);
      RbyteIndex = 0;  // reset the byte index
      Rbyte = font[RcharIndex][RbyteIndex];
    } 
    else {
      Rbyte = font[0][0];
    }
  }

  // get the LHS character byte for printing
  if (LbyteIndex >= 0){
    Lbyte = font[LcharIndex][LbyteIndex];
  } 
  else if (LbyteIndex >= (0 - letterSpace)){
    Lbyte = font[0][0];  // blank space
  } 
  else {
    // move to previous character in text string (if there is one)
    if (LtextPoss > 0){
      LtextPoss--;
      LcharIndex = charToFontIndex(textString[LtextPoss]);
      LbyteIndex = charWidth - 1; // reset the byte index
      Lbyte = font[LcharIndex][LbyteIndex];
    } 
    else {
      Lbyte = font[0][0];  // blank space
    }
  }

  // print the current byte
  printByte(Rbyte, Lbyte);
  // pause for frame duration
  pauseFrame(1);
  // clear LEDs
  allLEDs(HIGH);

  // increment/decrement the byte count
  RbyteIndex++;
  LbyteIndex--;

  // check for save button press
  int i=0;
  while(digitalRead(saveSwitchPin) == LOW){  // is the save switch being pressed?
    cycleLEDs(HIGH, 20); // indicate the button press and take care of debounce
    i++;
    if(i>3){ // if the button is held for longer than 3 cycles
      unsigned long distancem = numWheelRotations * wheelCircum / 1000;  // calculate the distance in metres
      if(storedMetres != distancem){ // if it hasn't already been stored
        EEPROMWriteInt(memLoc, distancem); // write the metres to memory
        storedMetres = distancem;
        allLEDs(LOW); // indicate a save has taken place
        delay(500);
        allLEDs(HIGH);
      }
      break;  // exit the while loop
    }
  }
}

void printByte(byte Rb, byte Lb)
{
  // bit shift through the byte and output it to the pin
  for (int i=0; i<charHeight; i++) {
    digitalWrite(LEDpinsRHS[i], !(Rb & (1 << i)));
    digitalWrite(LEDpinsLHS[i], !(Lb & (1 << i)));
  } 
}

void pauseFrame(int frames){
  unsigned long currentTime;
  int sensVal;  // variable to store the value coming from the sensor
  unsigned long endTime = microSeconds() + (frames * frameDuration);  // calculate the end time

  while(microSeconds() < endTime){
    // check the sensor
    sensVal = analogRead(sensorPIN);  // read the Hall Effect Sensor
    if (sensVal > (mean + sensitivity) || sensVal < (mean - sensitivity)) {
      currentTime = microSeconds();  // in microseconds
      if(currentTime > (startTime + (20 * frameDuration))){  // de-bounce the sensor reading
        // recalculate frameDuration
        frameDuration = (currentTime - startTime) / numFrames;  // recalculate frameDuration
        if(frameDuration > 10000)
          frameDuration = 10000;  // limit the frame duration to a reasonable time

        // set the reset time
        RresetTime = currentTime + (RtextOffset * frameDuration);
        LresetTime = currentTime + (LtextOffset * frameDuration);

        numWheelRotations++;  // add 1 to the number of wheel rotations
        setTextString(currentTime - startTime); // reset the display text
      }
      startTime = currentTime;  // reset the start time
    }
  }
}

int charToFontIndex(char ch)
{
  // make sure the character is within the alphabet bounds (defined by the font.h file)
  // if it's not, make it a blank character
  if (ch < 32 || ch > 126)
    ch = 32;
  // converts the ASCII number to the font index number
  return (ch - 32);
}

void setTextString(unsigned long duration){
  int n; // number of char written to the string (not used)
  // calculate distance
  unsigned long distancem = numWheelRotations * wheelCircum / 1000; // distance in metres
  unsigned long distancekm = distancem / 1000; // distance in km (this truncates the number)
  unsigned long distanceRem = distancem - (distancekm * 1000); // the bit after the decimal point
  // calculate speed
  // mm / microsecond x 1000 is mm / millisecond
  // mm / millisecond is equivalent to m / sec
  // m / sec x 60 x 60 is m / hr
  // m / hr / 1000 is km / hr
  unsigned long speed100m = wheelCircum * 36000 / duration; // 100m per hour
  unsigned long speedkm = speed100m / 10; // km per hour
  unsigned long speedRem = speed100m - (speedkm * 10); // the bit after the decimal point

  // print a new text string into the variable
  n = sprintf(textString, "%ld.%.3ld km     %ld.%.ld km/hr \n", distancekm, distanceRem, speedkm, speedRem);
}

// get an accurate time reading. ref: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1193623343
unsigned long microSeconds(void)
{
  return ((timer0_overflow_count << 8) + TCNT0)*4;
}

//This function will write a 4 byte long integer to the eeprom from the specified address
void EEPROMWriteInt(int p_address, long p_value)
{
  byte Byte1 = ((p_value >> 0) & 0xFF);
  byte Byte2 = ((p_value >> 8) & 0xFF);
  byte Byte3 = ((p_value >> 16) & 0xFF);
  byte Byte4 = ((p_value >> 24) & 0xFF);

  EEPROM.write(p_address, Byte1);
  EEPROM.write(p_address + 1, Byte2);
  EEPROM.write(p_address + 2, Byte3);
  EEPROM.write(p_address + 3, Byte4);
}

//This function will read a 4 byte long integer from the eeprom from the specified address
unsigned long EEPROMReadInt(int p_address)
{
  byte Byte1 = EEPROM.read(p_address);
  byte Byte2 = EEPROM.read(p_address + 1);
  byte Byte3 = EEPROM.read(p_address + 2);
  byte Byte4 = EEPROM.read(p_address + 3);

  // bit shifting left more than 8 places wont work because the result gets truncated
  // so do this in two stages
  long firstTwoBytes = ((Byte1 << 0) & 0xFF) + ((Byte2 << 8) & 0xFF00);
  long secondTwoBytes = (((Byte3 << 0) & 0xFF) + ((Byte4 << 8) & 0xFF00));
  secondTwoBytes *= 65536; // multiply by 2 to power 16 - bit shift 24 to the left

  return (firstTwoBytes + secondTwoBytes);
}

void allLEDs(int newState)
{
  // turn all the LEDs ON or OFF
  for (int i=0; i<charHeight; i++) {
    digitalWrite(LEDpinsRHS[i], newState);
    digitalWrite(LEDpinsLHS[i], newState);
  }
}

void cycleLEDs(int LEDdirection, int LEDdelay)
{
  int i;
  // turn all the LEDs ON and OFF in a cycle
  if(LEDdirection == HIGH){
    for (i=0; i<charHeight; i++) {
      digitalWrite(LEDpinsRHS[i], LOW);  // turn LED on
      digitalWrite(LEDpinsLHS[i], LOW);  // turn LED on
      delay(LEDdelay);
      digitalWrite(LEDpinsRHS[i], HIGH);  // turn LED off
      digitalWrite(LEDpinsLHS[i], HIGH);  // turn LED off
    } 
  } 
  else {
    for (i=charHeight-1; i>=0; i--) {
      digitalWrite(LEDpinsRHS[i], LOW);  // turn LED on
      digitalWrite(LEDpinsLHS[i], LOW);  // turn LED on
      delay(LEDdelay);
      digitalWrite(LEDpinsRHS[i], HIGH);  // turn LED off
      digitalWrite(LEDpinsLHS[i], HIGH);  // turn LED off
    } 
  }
}