/* 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++;
}
Comments
Please log in or sign up to comment.