Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Hand tools and fabrication machines | ||||||
|
NK Brand speedcoaches, while small, light, and effective, are often in excess of $400, and sometimes affordable for individual coxswains or clubs who spend their money on boats/boat repairs, and oars. For my personal project in high school, I decided to solve that problem by making my own speedcoach, potentially as the basis for future rowing tech. Features include - support for biking and walking, as well as other water sports, a timer, and a battery monitor.
Version 2.0.0 Updates -
- Maxing out the 10 Hz GPS for a whopping 10 data points a second.
- Split averaging optimized for 20 spm to stop jumpy split readings. This results in an excellent 30 data points per split reading displayed!
- Datalogging! Simply plug in a microSD card to the board (Feather Adalogger M0 in my case), flip the data write switch, and you'll get split, meters, and time recordings every 3 seconds, each with a handy timestamp.
- General aesthetics/functionality improvements centered around the user interface. Because, who doesn't love a pretty and useful UI?
Version 3.0.0
- UI rework once again, this time removing the screen flicker emblematic of the previous model.
Usage-
To start a piece, simply hit the reset button, and after a 3 second delay, the screen should turn on and the piece will start as soon as the screen is lit. To record data, flip the data write permission switch on.
#include <Adafruit_SSD1306.h>
#include <splash.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <SPI.h>
#include <SandTimer.h>
#include <SD.h>
#include <millisDelay.h>
#include <Adafruit_GPS.h> //Libraries
#define GPSSerial Serial1
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define PAUSE 1000
Adafruit_GPS GPS(&GPSSerial);
File myFile;
SandTimer timer1;
#define GPSECHO false
Adafruit_SSD1306 display(SCREEN_WIDTH,SCREEN_HEIGHT,&Wire,OLED_RESET);
uint32_t timer = millis();
const int chipSelect = 4;
float ms = 0; //Meters/Second variable
float avems = 0; //Average M/S variable
float mscounter = 0; //Speed total (for average)
int integcounter = 0; //Counts half-seconds
float meterstraveled = 0; //Total meters
int splittotal = 0; //Split total (in seconds) (Essential to be an integer)
int splitseconds = 0; //Seconds on split display (Essential to be an integer)
int splitminutes = 0; //Minutes on split display (Essential to be an integer)
int seconds = 0;
int minutes = 0;
int hours = 0; //Self-explanatory (for timers)
int splitaveragetotal = 0; //All split seconds added together (for average split)
int splitaverageseconds = 0; //Split average seconds
int splitaverageminutes = 0; //Split average minutes
int splitaverage = 0; //Split average
int splitaveragesecondsdisp = 0; //Display variables, fixes a bug
int splitaverageminutesdisp = 0;
int splitmdisp = 0;
int splitsdisp = 0; //Display Variables
int tc = 0; //Counter variable
int dp = 0; //Data-point variable
int dpave = 0; //Average of the data-points
int dprec = 0; //Counter variable
float knot = 0;//Speed in knots recorded from GPS
float aveknots = 0; //Speed in knots averaged over 1/2 second (equal to 1 data point and 5 knot readings)
int knotcounter = 0; //Counter variable
int sensorValue = analogRead(A3); //For the data write switch
float msfm = 0; //M/S for the meters calculation
float checkBattery(){
//Function to check battery voltage
float measuredvbat = analogRead(9);
measuredvbat *= 2; // we divided by 2, so multiply back
measuredvbat *= 3.3; // Multiply by 3.3V, our reference voltage
measuredvbat /= 1024; // convert to voltage
return measuredvbat;}
millisDelay secdelay; //Timing delay (for timer)
void setup() {
float voltage = sensorValue * (5.0 / 1023.0); //Converts A3 reading into a 5v equivalent
Wire.begin();
Wire.setClock(400000L);
Serial.begin(115200);
GPS.begin(9600);
GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
GPS.sendCommand(PGCMD_ANTENNA);
GPSSerial.println(PMTK_Q_RELEASE);
int realhours = GPS.hour - 8;
if(realhours < 0) {realhours = realhours+24;} //For SD timestamp (24 hour format)
if (voltage == 5) {if (!SD.begin(chipSelect)) { //Begins SD card startup
Serial.println("Card failed, or not present");
}
}
uint32_t m = micros(); //Main timer initialization
display.begin(SSD1306_SWITCHCAPVCC,0x3C);
display.fillScreen(0);
display.display();
display.fillRect(0,26,26,8,1);
display.fillTriangle(20,32,24,32,24,55,1);
display.fillTriangle(20,26,80,13,80,26,1);
display.fillTriangle(80,13,100,26,80,26,1);
display.fillRect(25,26,76,31,1);
display.fillTriangle(80,64,128,40,128,64,0);
display.setCursor(32,25);
display.setTextColor(0);
display.setTextSize(4);
display.print("RTI");
display.setCursor(0,0);
display.setTextColor(1);
display.setTextSize(1);
display.print("RTI Development");
display.setCursor(0,8);
display.print("v. 3.0.0");
display.display();
delay(2000);
secdelay.start(1000); //Start secondary timer delay (1 Second)
File dataFile = SD.open("storage.txt", FILE_WRITE); //Opens SD card, prints initial piece introduction + timestamp
if (dataFile) {
Serial.println("Writing to SD");
dataFile.println("PIECE BEGIN - - -");
dataFile.print(realhours);
dataFile.print("/");
dataFile.println(GPS.minute);
dataFile.close();}
timer1.start(100);
}
void loop() {
sensorValue = analogRead(A3);
float voltage = sensorValue * (5.0 / 1023.0);
float batVoltage = checkBattery(); //get battery voltage
if (secdelay.justFinished()) {seconds++; secdelay.repeat();} //Checks if 1 second has passed, updates seconds variable
if (seconds == 60) {seconds = 0; minutes++;} //Checks if 60 seconds have passed, resets seconds, increases minutes
if (minutes == 60) {minutes = 0; hours++;} //Checks if 60 minutes have passed, resets minutes, increases hours
char c = GPS.read();
if (GPSECHO)
if (c) Serial.print(c);
if (GPS.newNMEAreceived()) {
Serial.println(GPS.lastNMEA());
if (!GPS.parse(GPS.lastNMEA())); //Serial GPS info for debugging
}
if (timer > millis()) timer = millis(); //Restarts main timer
if (timer1.finished()) {knot = knot+GPS.speed; knotcounter++;
timer1.startOver();}
if (millis() - timer > 500) { //Runs every half-second (responsible for screen-flicker)
timer = millis(); // Resets timer
if (GPS.fix) {
aveknots = knot/knotcounter;
Serial.println(aveknots);
ms = (aveknots*0.5144444); //Converts knots to meters/second
Serial.println(ms);
knot = 0;
tc = 0;
knotcounter = 0;
aveknots = 0;
if (ms <= 0.75) {display.fillScreen(0);
display.setCursor(0,0);
display.setTextColor(1);
display.setTextSize(1);
display.print("Error: IDLE");
display.setCursor(0,15);
display.print("T: ");
display.setCursor(10,15);
display.print(hours);
display.setCursor(16,15);
display.print(":");
display.setCursor(22,15);
display.print(minutes);
display.setCursor(34,15);
display.print(":");
display.setCursor(40,15);
display.print(seconds);
display.setCursor(0,30);
display.print("Battery: ");
display.setCursor(47,30);
display.print(batVoltage);
display.setCursor(0,45);
display.print("Meters: ");
display.setCursor(43,45);
display.print(meterstraveled); }//Idle screen
display.display();
if (ms > 0.75)
{
msfm = ms;
mscounter = (mscounter+msfm); //Continually adds speed to total (for averages)
integcounter = (integcounter+1); //Adds +1 to variable every half second (for averages)
avems = mscounter/integcounter; //Generates average meters/second (for total)
meterstraveled = avems*(integcounter/2); //Calculates total (meters/second divided by seconds)
splittotal = 500/(ms); //Generates seconds per 500m
dp = dp + splittotal; //Adds data-points for dpave variable
dprec++;
if (dprec >= 6) {dpave = dp/6; //Calculates average
dprec = 0;
dp = 0; //Resets data-points and counter variable
if(voltage > 4.5) {File dataFile = SD.open("storage.txt", FILE_WRITE);
//Opens SD card if write switch is on
if (dataFile) {
Serial.println("Writing to SD");
dataFile.println("S: ");
dataFile.print(splitminutes);
dataFile.print(":");
dataFile.println(splitseconds);
dataFile.print("Ave. S ");
dataFile.print(splitaverageminutes);
dataFile.print(":");
dataFile.println(splitaverageseconds);
dataFile.print("M: ");
dataFile.println(meterstraveled);
dataFile.print(hours);
dataFile.print(":");
dataFile.print(minutes);
dataFile.print(":");
dataFile.println(seconds);
dataFile.println();
dataFile.close();}} //Prints data to SD card
}
splitminutes = dpave/60; //Generates split minutes from split seconds
splitseconds = dpave - (splitminutes*60); //Displays remaining seconds
splitaveragetotal = splitaveragetotal+splittotal; //All split times (in seconds) added together
splitaverage = splitaveragetotal/(integcounter); //Averages out split times w/ seconds
splitaverageminutes = splitaverage/60; //Calculates split minutes
splitaverageseconds = splitaverage-splitaverageminutes*60; //Calculates split seconds
splitaverageminutesdisp = splitaverageminutes;
splitaveragesecondsdisp = splitaverageseconds;
splitmdisp = splitminutes;
splitsdisp = splitseconds; //Display variables
display.fillScreen(0);
display.setCursor(0,0);
display.setTextColor(1);
display.setTextSize(2);
display.print("M: ");
display.setCursor(20,0);
display.print(meterstraveled); //Displays total meters
display.setCursor(0,20);
display.print("S: ");
display.setCursor(20,20);
display.print(splitmdisp);
display.setCursor(35,20);
display.print(":");
display.setCursor(45,20);
display.print(splitsdisp);
//oled.print(" AS: "); oled.print(splitaverageminutesdisp); oled.print(":"); oled.println(splitaveragesecondsdisp);
//Displays split and average split
display.setCursor(0,40);
display.print("T: ");
display.setCursor(22,40);
display.print(hours);
display.setCursor(33,40);
display.print(":");
display.setCursor(42,40);
display.print(59);
display.setCursor(63,40);
display.print(":");
display.setCursor(72,40);
display.print(seconds); //Displays timer
display.display();
//if (voltage >= 4.5) {oled.print("SD Write ON");}
//if (voltage < 4.5) {oled.print("SD Write OFF");} //Shows write permission status
}}}}
Comments