cimanes
Published © MIT

Bike trainer logger

Retrieve data from speed and cadence sensor in a bike trainer. Display info in LCD screen and log in a SD Card

IntermediateProtip2,700
Bike trainer logger

Things used in this project

Hardware components

SPI micro SD card- mini TF reader module
×1
Alphanumeric LCD, 16 x 2
Alphanumeric LCD, 16 x 2
×1
Trimmer Potentiometer, 10 kohm
Trimmer Potentiometer, 10 kohm
×1
Arduino Nano R3
Arduino Nano R3
×1
magnetic N.O. switch
×1
Wemos D1 Mini
Espressif Wemos D1 Mini
×1

Story

Read more

Custom parts and enclosures

Trainer V 5.0

Latest revision (including optional Arduino / Wemos D1 mini and additional options)

Schematics

Fritzing project with schematics

Trainer_schematic

Connections between arduino, sensors, LCD display and SD card

Code

Trainer.ino

Arduino
Initial version of the software
/*   Cimanes   30/04/2019
 *    
 *   Receive speed and cadence signals from a bike trainer 
 *   Use one "attachinterrupt" for speed singal.
 *   The "attachinterrupt" will trigger the speed calculations. 
 *   Cadence is not triggered with an attach interrupt, as it was causing unstable behavior. 
 *   Process the signal as a cycling computer (speed, cadence, pedals, distance, time, average speed/cadence)
 *   Serial print/plot the results
 *   Display results in LCD display
 *   Save results in a file on a SD card (Trainer.log).
 *   
 *   Original software has been designed to work with a "Tacx Fortius" bike trainer. 
 *   Digital inputs are configured as Input Pullup. Cadence signal is sampled in parallel to the Arduino and to the Fortius.
 *   Note: the Fortius cadence sensor uses voltage (0 VDC = constant) and (-5 VDC = pulses). 
 *   
 *   SD card attached to SPI bus as follows:
 *   MOSI     - pin 11 (UNO & NANO) / pin 51 (MEGA)
 *   MISO     - pin 12 (UNO & NANO) / pin 50 (MEGA)
 *   CLK      - pin 13 (UNO & NANO) / pin 52 (MEGA)
 *   CS or SS - pin 10 (UNO & NANO) / pin 53 (MEGA) / pin 4 (Ethernet Shield)
 *   
 *   MISO (Master In Slave Out) - The Slave line for sending data to the master,
 *   MOSI (Master Out Slave In) - The Master line for sending data to the peripherals,
 *   SCK / CLK (Serial Clock) - The clock pulses which synchronize data transmission generated by the master and one line specific for every device:
 *   SS (Slave Select) - the pin on each device that the master can use to enable and disable specific devices.
 *   
 *   On the Ethernet Shield, CS is pin 4. It's set as an output by default.
 *   Note that even if it's not used as the CS pin, the hardware SS pin (10 on most Arduino boards, 53 on the Mega) 
 *   must be left as an output or the SD library functions will not work. 
 *   
 *   LCD connection (LCD 1602 used):   
 *   {VSS VDD  V0 RS  RW  E D0 D1 D2 D3 D4 D5 D6 D7  A   K }
 *   {GND +5V POT 23 GND 25  -  -  -  - 27 22 24 26 +5V GND} --> MEGA board
 *   {GND +5V POT  4 GND  5  -  -  -  -  6  7  8  9 +5V GND} --> NANO board
 */

#include <LiquidCrystal.h>  // LCD library
// LCD connection   RS E  D4  D5  D6  D7
  LiquidCrystal lcd(4, 5, 6,  7,  8,  9);    // --> for NANO
//  LiquidCrystal lcd(23, 25, 27, 22, 24, 26);    // --> (optional) for MEGA. Other options valid too

#include <SPI.h>            // Library for SPI communication with SD card
#include <SD.h>             // SD card library
  File myFile;

const byte SSpin    =   10 ; // (MEGA = 53; NANO = 10). Pin for "Save Select"; digital input
const byte logpin   =    0 ; // (MEGA = 4 ; NANO =  0). Pin for "log save"; digital input 
const byte cadpin   =    3 ; // Pin for cadence pulse; digital input 

const byte Dspd     =    2 ; // Max speed difference between consecutive readings (km/h)
const float wheel   = 2.09 ; // wheel length in cms
const int DTspd     = 1500 ; // Max allowed time delay between speed pulses 
const int DTspdmin  =  100 ; // Min time delay between speed pulses
const int DTcad     = 3000 ; // Max allowed time delay between cadence pulses 
const int DTcadmin  =  350 ; // Min time delay between cadence pulses
const int DTprint   = 2000 ; // Time delay for serial print refresh 
const int DTlog     = 2000 ; // Time delay for data-logger in SD card file; recommended DTlog >= DTprint for accuracy on average readings.
const int DTdisp1   = 1800 ; // Time delay for LCD main screen refresh
const int DTdisp2  = 10000 ; // Time delay for LCD second screen refresh

unsigned long t      = 0 ;   // Current time in miliseconds
unsigned long t_c  = 500 ;   // Current "pedalling" time (Cadence > 0).
unsigned long t_s  = 100 ;   // Current "cycling" time (Speed > 0). 
unsigned long tspd   = 0 ;   // Time reference in miliseconds for cadence calculation
unsigned long tcad   = 0 ;   // Time reference in miliseconds for cadence calculation
unsigned long tprint = 0 ;   // Time reference in miliseconds for signal print
unsigned long tdisp1 = 0 ;   // Time reference in miliseconds for LCD refresh
unsigned long tdisp2 = 0 ;   // Time reference in miliseconds for LCD screen shift
unsigned long tlog   = 0 ;   // Time reference in miliseconds for data-logger

unsigned int Turn   = 0 ;    // Number of pulses from the speed sensor
unsigned int Pedal  = 0 ;    // Number of pedals
unsigned int Pedal0 = 0 ;    // Number of pedals (reference)
float Speed     = 0 ;        // Speed (km/h)
float Speed0    = 0 ;        // Speed reference for filter
byte  Cadence   = 0 ;        // Cadence (rpm)
byte  Cadence0  = 0 ;        // Cadence reference for filter
byte  Dcad      = 4 ;        // Max cadence difference

float flMin = 0 ;            // Time (floating minutes) in movement (speed >0)
byte  Min   = 0 ;            // Time (minutes) in movement (speed > 0)
byte  Sec   = 0 ;            // Time (seconds) in movement (speed > 0)
float Dist  = 0 ;            // Distance (km)
float Avspd = 0 ;            // Average speed (km/h)
byte  Avcad = 0 ;            // Average cadence (pedals / min)

void setup() {
  pinMode(2,        INPUT_PULLUP) ;   // Attach interrupt for speed detection
  pinMode(cadpin,   INPUT_PULLUP) ;   // Pin: input pulse for cadence detection
  pinMode(logpin,   INPUT_PULLUP) ;   // Digital input to save data-log file
  pinMode(SSpin,          OUTPUT) ;   // Slave Select pin for SPI communication
  pinMode(LED_BUILTIN,    OUTPUT) ;    
  digitalWrite(LED_BUILTIN,  LOW) ;   // turn the LED off by making the voltage LOW (I don't like it blinking... :-) )
  attachInterrupt(digitalPinToInterrupt(2),Spd_calc,FALLING);

// Initiate LCD display, Serial comm. and SD Card comm.:
  lcd.begin(16, 2);                   // 16 characters; 2 lines
  Serial.begin (9600);

  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("SD INITIALIZE...");
  delay(2000);

  if (!SD.begin(SSpin)) {
    lcd.setCursor(0,0);
    lcd.print("SD INIT. FAILED ");
    delay(5000);
    return;
  }
  lcd.setCursor(0,0);
  lcd.print(" SD INIT. DONE  ");
  delay(2000);
    
// Open file "Trainer.log" to save data:  
  myFile = SD.open("Trainer.log", FILE_WRITE);    // Open the file

  if(myFile) {                                    // if the file oens, display an "OK" message:
    lcd.setCursor(0,0);
    lcd.print("   FILE OPEN:   ");
    lcd.setCursor(0,1);
    lcd.print("  TRAINER.LOG   ");
    delay(2000);
    lcd.clear();
  }
  else {                                          // if the file didn't open, display an error message:
    lcd.setCursor(0,0);
    lcd.print("  FILE ERROR:   ");
    lcd.setCursor(0,1);
    lcd.print("  TRAINER.LOG   ");
    delay(5000);
    lcd.clear();
  }

// Print the header in the data-log file:
  myFile.println("Min" + String("\t") + "Speed" + String("\t") + "Cadence" + String("\t") + "Av_Speed" 
                 + String("\t") + "Av_Cadence" + String("\t") + "Distance" + String("\t") + "Pedals");
  
  for (int i=0; i<3; i++)  {        // Initial display on LCD
    lcd.setCursor(4,0);
    lcd.print("CIMANES");                                   
    lcd.setCursor(1,1);
    lcd.print("START TRAINING");
    delay(800);
    lcd.clear();
    delay(200);
  } 
}

void loop() {
// Enable line #153 [t = millis();] to maintain LCD and print alive while Speed = 0;
// Disable line #153 [t = millis();] to freeze print and LCD while Speed = 0;
  t = millis();                                           // Time ellapsed since start
  if (!digitalRead(cadpin) and t - tcad > DTcadmin)   Cad_calc();   // Cadence calculation upon cadence pulse
  if (t - tprint > DTprint)           Print()   ;   // Check timer for "print" refresh
  if (t - tdisp1 > DTdisp1)           Disp()    ;   // Check timer for LD refresh
  if (t - tlog   > DTlog  )           Log()     ;   // Check timer for data-logger.
  if (!digitalRead(logpin)) {
    myFile.close();
    lcd.clear();
    lcd.setCursor(2,0);
    lcd.print("FILE SAVED");
    delay(2000);
    lcd.clear();
  }  
}

void Spd_calc() {                                 
  t = millis();                                                 // Refresh time
//  if ((t - tspd) > DTspdmin) {      // Filter needed????   ****************
    Speed = (wheel) * 3600 / (t -  tspd);                       // Calculate the raw speed (wheel(209 cm)  * 3.6 = 752.4)
    Speed = constrain(Speed, Speed0 - Dspd, Speed0 + Dspd);     // Constrain speed incremental

    if (t - tspd < DTspd)   t_s = t_s + (t - tspd);             // Increase time counter for average speed
    Speed0 = Speed;                                             // Refresh Speed reference for filter
    tspd = t;                                                   // Refresh time reference for Speed calculation
    Turn++;                                                     // Increase number of wheel turns
}

void Cad_calc() {
    t = millis();                                                      // Refresh time
    if (Cadence0 < 65) Dcad = 15;
    else Dcad = 4;
    Pedal++;                                                           // Increment pedals upon cadence pulse
    Cadence = byte((Pedal - Pedal0) * 60000 / (t - tcad));             // Calculate the raw cadence (include the "lost" pedals)
    Cadence = constrain(Cadence, max(0, Cadence0 - Dcad), Cadence0 + Dcad);
    
    if(t - tcad < DTcad) t_c = t_c + (t - tcad);                       // Increase time counter for average cadence  
    tcad = t;                                                          // Refresh cadence signal timer
    Pedal0 = Pedal;                                                    // Refresh Pedal reference
    Cadence0 = Cadence;                                                // Refresh Cadence reference for filter       
}

void Print() {
// Force Speed and cadence to "0" when no pulses are detected for the pre-selected time delay:
  if (t - tspd > DTspd) Speed   = 0;
  if (t - tcad > DTcad) Cadence = 0;
  
// Calculate Timers and average values
   flMin = float(t_s) / 60000;                  // Time (floating minutes) in movement (speed >0)
   Min = flMin       ;                          // Time (minutes) in movement (speed > 0)
   Sec = 60 * (flMin - Min);                    // Time (seconds) in movement (speed > 0)
   Dist  = Turn * wheel   / 1000 ;              // Distance (km)
  if (Dist < 0,1) Avspd = 0;
  else Avspd = Dist * 3600000 / t_s  ;              // Average speed (km/h)
   if (t_c < 5000) Avcad = Cadence; 
   else Avcad = byte(Pedal * 60000 / t_c)  ;    // Average cadence (pedals / min)

   Serial.print("S:");
   Serial.print(Speed,1);
   Serial.print(" C:");
   Serial.println(Cadence);
//   Additional available plots (can be added to the previous command:
//   Serial.print(" AS:");
//   Serial.print(Avspd);
//   Serial.print(" D:");
//   Serial.print(Dist);
//   Serial.print(" T:");
//   Serial.print(Turn);
//   Serial.print(" T:");
//   Serial.print(flMin,1);
//   Serial.print(" P:");
//   Serial.print(Pedal/100);
//   Serial.print(" AC:"); 
//   Serial.print(Avcad);
//   Serial.print(" t:");
//   Serial.println(t);   

tprint = t;
   if (!digitalRead(cadpin) and (t - tcad > DTcadmin))  Pedal++;   
}

void Disp() {
// Check if the "shift screen" pushbutton is pressed (digital input pint 7)

   if (t - tdisp2 > DTdisp2) {
     lcd.setCursor(0, 0);
     lcd.print("AS       T   :  ");
     lcd.setCursor(3, 0);
     lcd.print(Avspd, 1);
     lcd.setCursor(11, 0);
     lcd.print(Min);
     lcd.setCursor(14, 0);
     lcd.print(Sec);
     lcd.setCursor(0, 1);
     lcd.print("AC   P     D    ");
     lcd.setCursor(2, 1);
     lcd.print(Avcad);
     lcd.setCursor(6, 1);
     lcd.print(Pedal);
     lcd.setCursor(12, 1);
     lcd.print(Dist,1);
     tdisp1 = t;
     tdisp2 = t;
   }
      
   else    {
     lcd.setCursor(0, 0);
     lcd.print("                ");
     lcd.setCursor(0, 0);
     lcd.print("S       T   :   ");
     lcd.setCursor(0, 1);
     lcd.print("                ");
     lcd.setCursor(0, 1);
     lcd.print("D       C       ");
     lcd.setCursor(2, 0);
     lcd.print(Speed, 1);
     lcd.setCursor(10, 0);
     lcd.print(Min);
     lcd.setCursor(13,0);
     lcd.print(Sec);
     lcd.setCursor(2, 1);
     lcd.print(Dist,1);
     lcd.setCursor(10, 1);
     lcd.print(Cadence);
     tdisp1 = t;
   }
   if (!digitalRead(cadpin) and (t - tcad > DTcadmin))  Pedal++;   
}

void Log() {
  myFile.print(flMin);
  myFile.print("\t");
  myFile.print(Speed,1);
  myFile.print("\t");
  myFile.print(Cadence);
  myFile.print("\t");
  myFile.print(Avspd);
  myFile.print("\t");
  myFile.print(Avcad);
  myFile.print("\t");
  myFile.print(Dist);
  myFile.print("\t");
  myFile.println(Pedal);
  tlog = t;
  if (!digitalRead(cadpin) and (t - tcad > DTcadmin))  Pedal++;   
}

Credits

cimanes
2 projects • 1 follower
Contact

Comments

Please log in or sign up to comment.