Wireless Timing Project

Pages
Contributors: Gior Dior
Favorited Favorite 4

Broncos Country, Let's Race!

alt text

The standard “Combine” assessment is the gold standard for evaluating athlete performance throughout the NFL. The 2022 NFL Draft combine had over 55 million viewers around the world and the 40-yard dash times were amongst the top discussions.

There are two techniques for timing the athlete's 40-yard dash; hand timing, and fully automated electronic timing. As you probably know, hand timing consists of manually starting and stopping time as the athlete crosses the start and finish line. Fully automated electronic timing methodologies are used to mitigate human error, whereby the actions of the athlete will start and stop the time. Typically, an athlete will have a “slower” 40-yard dash time if they are timed using a fully automated timing system. These electronic systems are extremely expensive costing the average Joe more than $600 for one setup. Inspired by the significance of the 40-yard dash and its costly market, we set out to replicate this timing system using only SparkFun components.

alt text

The components used in this tutorial were miles less expensive than the average timing system in today's market. This system brought great ideas for mild horseplay in the workplace, but of course, safety first, not third. We decided to host a company 40-yard dash competition, winner took home bragging rights and a visa gift card.

In this tutorial, you will learn how to create a wireless timing system using the ESP32 WIFI system. This system can be used in limitless applications involving timing such as running, driving, dog racing, robot racing, etc. The hardware includes a pair of ESP32-S2 Thing Plus, two SparkFun Distance Sensor Breakouts 4 Meter VL53L1X, two Metal Pushbuttons, a SparkFun Qwiic Cable Kit, a SparkFun qwiic OLED display, and two Lithium Ion Batteries. You can add all components to your cart using the wish list below:

Required Materials

To follow along with this tutorial, you'll need the following materials.

ESP32 MAC Address

alt text

We will be setting up the ESP32 devices as access points. This will give the ESP32’s the ability to create their own WIFI network, which provides communication between the two devices. In order to receive two-way communication, we need to know the Media Access Control (MAC) address of one ESP32 device. There is a great tutorial, Sending Sensor Data Over Wifi, made by our very own Rob Reynolds, which goes into depth on discovering the MAC address of an ESP32. Take a look at this tutorial and follow the instructions for Step 1: Obtaining Mac Address. We are sending a trigger from the start ESP32 to the finish ESP32, you will only need to obtain the mac address for the ESP32 that you plan to use at the finish line. Here is the code Rob has created for us to find the MAC address:

/*
 * MAC Address Finder
 * Run this on each of your WiFi-enabled
 * boards to get their MAC addresses, so
 * you can plug them into your code,
 * allowing your components to connect
 * on power-up or after power-cycling
 * without the need for any intervention!
 * 
 * Once you've uploaded the code, open
 * the Serial Monitor, hit the reset
 * button on your board, and write down
 * the MAC address.
 * (I use a label maker to put the MAC
 * address on the back of each board.)
 */

#include "WiFi.h"

void setup(){
  Serial.begin(115200);

}

void loop(){
  WiFi.mode(WIFI_STA);
  Serial.print("The MAC address for this board is: ");
  Serial.println(WiFi.macAddress());
  while(1){     // This holds the loop, so it doesn't 
    }           // print the info a million times.
}

Connecting the Hardware

The qwiic connect ecosystem has made this project practically solderless, although we are going a little over the top and adding a metal pushbutton that requires soldering. It’s best to split the start and finish into two separate setups, then finish up by adding the casing and pushbuttons.

If you aren't familiar with the Qwiic system or new to SparkFun electronics, we recommend utilizing these tutorials. Our tutorials will go in depth on the basics of setting up your devices. Also, nothing goes as expected when creating a project, so these will be great resources for troubleshooting.

Qwiic Connect System
Qwiic Connect System

How to Solder: Through-Hole Soldering

This tutorial covers everything you need to know about through-hole soldering.

Serial Communication

Asynchronous serial communication concepts: packets, signal levels, baud rates, UARTs and more!

Serial Peripheral Interface (SPI)

SPI is commonly used to connect microcontrollers to peripherals such as sensors, shift registers, and SD cards.

Pulse Width Modulation

An introduction to the concept of Pulse Width Modulation.

Installing Arduino IDE

A step-by-step guide to installing and testing the Arduino software on Windows, Mac, and Linux.

Logic Levels

Learn the difference between 3.3V and 5V devices and logic levels.

I2C

An introduction to I2C, one of the main embedded communications protocols in use today.

Analog vs. Digital

This tutorial covers the concept of analog and digital signals, as they relate to electronics.

How to Work with Jumper Pads and PCB Traces

Handling PCB jumper pads and traces is an essential skill. Learn how to cut a PCB trace, add a solder jumper between pads to reroute connections, and repair a trace with the green wire method if a trace is damaged.

ESP32 Thing Plus Hookup Guide

Hookup guide for the ESP32 Thing Plus (Micro-B) using the ESP32 WROOM's WiFi/Bluetooth system-on-chip in Arduino.

How to Install CH340 Drivers

How to install CH340 drivers (if you need them) on Windows, Mac OS X, and Linux.

Installing Board Definitions in the Arduino IDE

How do I install a custom Arduino board/core? It's easy! This tutorial will go over how to install an Arduino board definition using the Arduino Board Manager. We will also go over manually installing third-party cores, such as the board definitions required for many of the SparkFun development boards.

Start Setup

For the start setup, we will need to connect the distance sensor to the ESP32 Thing Plus. This is the “Start”, so be sure this is the ESP32 that we did NOT obtain the MAC address for. Use one qwiic cable connecting the ESP32 to our distance sensor. Your setup should look like this.

alt text

Once we have connected the distance sensor, we are now ready to flash the ESP32 with our “Start” code.

alt text

This code controls sending a message to our “Finish” ESP32 when the distance sensor has been triggered. The distance sensor will continuously scan at 4m. When an object crosses the sensor, the distance will be less than 1500mm. Why 1500mm? It’s what the sensor was reading when there was no object interfering. If the sensor is triggered or distance is less than 1500mm, we will send an integer “1” to the “Finish” ESP32. The “Finish” ESP32 is always looking out for the “1”. When the “1” integer is received, the “Finish” device will begin the time and wait for another distance sensor trigger. This will be the time the object is between the start and finish lines.

/*
*Wireless timing start sensor example, Transmitter sketch
*Giordan Thompson, SparkFun Electronics, September 2022
*This example transmits data to the finish sensor example
*You can find this example in github here ()
*
*The purpose of this code is to provide functionality to our wireless timing system
*This code will send data to the finish sensor, relaying the start sensor has been triggered 
*and we are ready to begin the timing. 
*
*Complete project details can be found here () where we dive into the creation and 
*desciption of this system.
*Please feel free to customize this code to your needs and raise an issue if necessary. 
*
*Enjoy!!!
*
*/



#include <Wire.h> //used to establish serial communication on the I2C bus
#include "SparkFun_VL53L1X.h" //Distance sensor library
#include <esp_now.h> //ESP wireless communication library
#include <WiFi.h> 

SFEVL53L1X distanceSensor;

// REPLACE WITH YOUR RECEIVER MAC Address
uint8_t broadcastAddress[] = {0x7C, 0xDF, 0xA1, 0x55, 0xAE, 0x50};  

// Structure example to send data
// Make sure to match the receiver structure on finish sensor
typedef struct struct_message {
 int outgoingInt;
} struct_message;

// Create a struct_message called myData
struct_message myData;

// callback when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
 Serial.print("\r\nLast Packet Send Status:\t");
 Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}

void setup() {
//Pin for indecator LED
 pinMode(13, OUTPUT);

 Wire.begin();

 Serial.begin(115200);

  // Device ia set as a Wi-Fi Station
  WiFi.mode(WIFI_STA);

  // Init ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }

  // Once ESPNow is successfully Init, we will register for Send CB to
  // get the status of Trasnmitted packet
  esp_now_register_send_cb(OnDataSent);

  // Register peer
  esp_now_peer_info_t peerInfo;
  memcpy(peerInfo.peer_addr, broadcastAddress, 6);
  peerInfo.channel = 0;  
  peerInfo.encrypt = false;

  // Add peer otherwise indecate failure        
  if (esp_now_add_peer(&peerInfo) != ESP_OK){
    Serial.println("Failed to add peer");
    return;
  }


  //Communicate sensor is online and ready for trigger 
  if (distanceSensor.init() == false)
    Serial.println("Sensor online!");

}

void loop() {
  //Indicator light on, device is ready for trigger
  digitalWrite(13, HIGH);

  distanceSensor.startRanging(); //Write configuration bytes to initiate measurement
  int distance = distanceSensor.getDistance(); //Get the result of the measurement from the sensor
  //Serial.print(distance);
  //After testing, 1500 was a consitent sensor value with NO object interference
  if (distance < 1500){

    //Sensor has been tiggered, send "1" integer to finish sensor
    myData.outgoingInt = 1;

    //Serial communication for troubleshooting
    Serial.print("Outgoing Int Val:");
    Serial.println(myData.outgoingInt);
    esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData.outgoingInt,sizeof(myData.outgoingInt));
    Serial.println("Distance(mm): ");
    Serial.print(distance); 

    //Sensor tiggered and integer has been sent to finish ESP
    //now ready to pause code and wait for reset
    distanceSensor.stopRanging();
    digitalWrite(13, LOW);
    while(1);
  }

  delay(10);

}

Finish Setup

The “Finish” setup is a little bit more involved than the “Start” setup. For the “Finish”, we have an ESP32, distance sensor, Metal Pushbutton, and display. To make all these components work in harmony, we will need considerably more code than the “Start”. We will begin by connecting the sensor and OLED display to the ESP32 via qwiic connect.

alt text

When your hardware is all set, we can move to flashing the device with our “Finish” code. The “Finish” code will run through its setup, getting the distance sensor up and running. When the “Start” ESP32 has been triggered, this code will receive a “1” integer and jump into the VOID loop “if” function. This function will set the distance sensor to begin taking measurements and wait for an object to trigger a measurement less than 1500mm. Meanwhile, the display will begin displaying the elapsed time of the race. When the sensor is triggered, the display will stop and the Void Loop will reach a “while(1)”. The purpose of the “while(1)” is to pause the code from going any further in the void loop. When the code is paused, the display will show the total elapsed time in seconds. This is where the reset pushbutton comes in handy, because the next step is to record your time on a piece of paper and restart the setup for the next race.

/*
*Wireless timing Finish Sensor example, Reciever sketch
*Giordan Thompson, SparkFun Electronics, September 2022
*This example is recieves data from the Start Sensor example 
*You can find the Start Sensor example in github here ()
*
*The purpose of this code is to provide functionality to our wireless timing system 
*This code will recieve data from the Start Sensor, Start and Stop time as the sensor is 
*triggered during our race. 
*
*Complete project details can be found here () where we dive into the creation and discription 
*of this system. 
*Please feel free to customize this code fit your needs and as always please feel free to 
*raise an issue
*
*Enjoy!!
*
*/



#include <esp_now.h> //ESP wireless communication library 
#include <Wire.h> //Used to establish serial communication on the I2C bus
#include <WiFi.h>
#include "SparkFun_VL53L1X.h" //Distance Sensor Library
//#include <Adafruit_GFX.h> //Font Library
#include <Adafruit_SSD1306.h> //OLED Display Library 
#include <Fonts/FreeSans9pt7b.h> //special font Library 

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 //OLED display hieght, in pixels
#define OLED_RESET -1  // GPIO -1
#define SCREEN_ADDRESS 0x3C //OLED diplay address

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

SFEVL53L1X distanceSensor2;

//define variables used in this example
int start=0;
int min1=0;
unsigned long tim=0;
unsigned long msec=0;
unsigned long mili=0;
int sec1=0;

//OLED display logo 
const unsigned char PROGMEM icon [] = {
0x00, 0x1F, 0xF8, 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x1F, 0xF8, 0x00,0x00, 0x1F, 0xF8, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0xE0,0x00, 0x1F, 0xF8, 0xF0, 0x00, 0x7F, 0xFE, 0x70, 0x00, 0xFF, 0xFF, 0x20, 0x01, 0xF0, 0x0F, 0x80,0x03, 0xC1, 0x83, 0xC0, 0x03, 0x81, 0x81, 0xC0, 0x07, 0x01, 0x80, 0xE0, 0x07, 0x01, 0x80, 0xE0,0x0E, 0x01, 0x80, 0x70, 0x0E, 0x01, 0x80, 0x70, 0x0E, 0x03, 0xC0, 0x70, 0x0E, 0x03, 0xC0, 0x70,0x0E, 0x03, 0xC0, 0x70, 0x0E, 0x01, 0x80, 0x70, 0x0E, 0x00, 0x00, 0x70, 0x07, 0x00, 0x00, 0xE0,0x07, 0x00, 0x00, 0xE0, 0x07, 0x80, 0x01, 0xE0, 0x03, 0xC0, 0x03, 0xC0, 0x01, 0xE0, 0x07, 0x80,0x00, 0xF8, 0x1F, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x07, 0xE0, 0x00


};

//Structure example to send data
//Make sure to match the transmitter structure of Start Sensor "a" used for simplicity
typedef struct struct_message {
    int a;

} struct_message;

//Create a struct_message called incomingReading
struct_message incomingReading;

//Callback when data is recieved  
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
  memcpy(&incomingReading, incomingData, sizeof(incomingReading));

  Serial.print("Incoming Reading Val:");
  Serial.print(incomingReading.a);
}


void setup() {
  Serial.begin(115200);

  //Pin for indecator LED
  pinMode(13, OUTPUT);

  Wire.begin();

  // Set device as a Wi-Fi Station
  WiFi.mode(WIFI_STA);

  // Init ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }

  // Once ESPNow is successfully Init, we will register for recv CB to
  // get recv packer info
  esp_now_register_recv_cb(OnDataRecv);

  //Communicate sensor is online and ready for trigger
  if (distanceSensor2.init() == false)
    Serial.println("Sensor2 online!");

  //Display Setup
  display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
  display.display();
  display.clearDisplay();
  display.setCursor(50,0);
  display.print("Start");
  display.drawBitmap(48, 0,  icon, 32, 32, WHITE);
  display.display();
  delay(2000);

}


void loop() {

  //indicator light on, device is ready for trigger
  digitalWrite(13, HIGH);

  //Begin time when integer "1" has been recieved from the Start Sensor
  //Object has crossed Start Sensor when message recieved 
  if (incomingReading.a == 1)
  {
    display.clearDisplay();
   distanceSensor2.startRanging();

    display.setTextSize(2);
    display.setTextColor(SSD1306_WHITE);

    if(start==0)
          {
           start=1;
           tim=millis();  
         }
      msec=(millis()-tim); 



       min1=msec/60000;


        if((msec/1000)>59)
           {
            sec1=(msec/1000)-(min1*60);
           }else{
             sec1=msec/1000;
             }

         mili=(msec%1000)/10;

          display.setCursor(0,30);
          //if(min1<=9)
         //{

         // } 

            if(sec1<=9)
          {
           display.print("0");
           display.print(sec1);
           }else {display.print(sec1);}
           display.print(".");
           display.setFont(&FreeSans9pt7b);

            if(mili<=9)
          {
           display.print("0");
           display.print(mili);
           }else {display.print(mili);
           display.setTextSize(1);
                   display.print("sec");} 




   int distance = distanceSensor2.getDistance();

   //After testing, 1500 was a consistent sensor value with NO object interference 
    //pause code when the sensor has been triggered
    if (distance < 1500){
     Serial.print("Distance: ");
     Serial.print(distance);
     distanceSensor2.stopRanging();
     Serial.print("Sensor 2 Stop Ranging...");
      digitalWrite(13, LOW);
      while(1);
    }
  }

  display.display();

}

Test

Now that we have the hardware and code ready for both setups, we should test that our devices work properly. To do this, find an area to place your sensors, with no objects within 4m. This will help make sure we have no interference with your distance sensors. Set this up similarly to a race, with “Start” and “Finish” in sequential order. Make sure both devices are reset and ready for an object to trigger the sensors. An easy quality control (QC) test, is to wave your hand over the “Start”, wait a moment, then wave your hand over the “Finish” sensor.

alt text

To know your devices are functioning, the elapsed time will be displayed on the OLED display. How did you do? If you are having trouble, please refer to the attached tutorials, where you can get some ideas for finding a solution.

alt text

3D Printing

If your setup passed QC, congratulations, and welcome to the case section. There are lots of free CAD software on the internet like tinker CAD, Onshape, and SketchUp. We challenge you to design a better case! If you're not up for the challenge, no big deal, it can be a ton of work.

alt text

This tutorial uses Onshape to design the case, which is an open-source software that allows you to share designs with the public. Click the link and you will find all the files you need to 3D print the case. It takes a little trial and error to find the optimal printer settings for this print. For those curious, we used a fine layer height, 20% infill, extrusion temperature of 200°C, and a bed temperature of 55°C. The case print for both the “Start Case” and “Finish Case” take 6hrs to print. The “Lid” takes roughly 1.5 hrs, so in total, we are looking at around 15hrs of print time.

Metal Indicator Pushbutton

We saved this step for last because you will need to size your hookup wire to fit within your casing. For this step, we used some hookup wire, lead-free solder, a soldering iron, heat shrink, and a heat gun. For those looking to get their hands on this equipment, check out the list below.

To get started, we recommend soldering your hookup wire to the headers on the pushbuttons. Our very talented BBOYHO has created this incredible table to show us the solder points on the momentary pushbutton.

Component Component Pin ESP32 I/O Pin
Red Momentary Metal Pushbutton + : LED Anode Side 13
NC1: Normally Closed Pin
NO1: Normally Open Pin RESET
C1: Common Pin GND
-: LED Cathode Side GND

It’s best to do all soldering in one sitting to mitigate going back and forth between setups. Please be sure that your wire is long enough to reach the button within your casing. Our setup had roughly 5 inch cuts for each wire. Once you have soldered the button leads, place some heat shrink on the leads and secure them using your heat gun.

alt text

Now, we are ready to place the buttons inside our casing.

Assembly

Now that we have the case and buttons soldered, it is time to assemble the two “Start” and “Finish” devices. For this, you will need a hot glue gun and of course some hot glue. A hot glue gun is your best friend for prototyping any components that need secure positions, so if you don't have one we recommend getting one.

First, let us begin with the “Start” case. Insert and fasten the pushbutton to the wall of your case. Then soldered the metal push button leads to pins 13, GND, and RESET. Once this is complete, grab your battery and plug it into the ESP32. Again, make sure this is the ESP32 that you do not have the MAC address for. Next, we will need to secure all the components in our case to ensure there are no moving parts when the device is in use. The order of gluing these parts to the case is up to you, but make sure you do not cover the sensor lens. Finish up by connecting the distance sensor to the ESP32 via qwiic.

Now let us finish up with assembling the “Finish” setup. Insert and fasten the pushbutton to the wall of your case. Then soldered the metal push button leads to pins 13, GND, and RESET. Grab your battery and plug it into the ESP32. Make sure this is your “Finish” ESP-32 with MAC address. Glue the distance sensor and OLED display to your case. Qwiic tip, be very careful when gluing the OLED display, the device is fragile and took us a couple attempts. Finish up by connection the distance sensor to the ESP32 via qwiic. Your final setup should look similar to the image below.

alt text

Instructions

Now that we have completed our build, its time for action! The devices are simple to use, but can be finicky at times. Place your "Start" sensor at the starting line and you "Finish" sensor at the finish. Press the reset button on the "Start" sensor and make sure the red LED halo is on. This verifies the sensor is ready to be triggered. Now, complete the same for the "Finish" sensor. Once both reset buttons are illuminating, we are ready to begin our race. Ready, set, go!

Resources and Going Further

The purpose of this tutorial was to create a comparatively inexpensive product to the wireless timing systems on the market. If you'd like to go further, there are many components you can swap out for your needs. Maybe a better display or distance sensor... Or maybe you'd like to add a camera for photo finishes. We've added some great resources to help you get started. Spark Fan's, let us know how your next wireless timing project goes. What's different from ours?

Qwiic Distance Sensor (VL53L1X, VL53L4CD) Hookup Guide

The Qwiic Distance Sensor - VL53L1X is a time of flight sensor that is capable of several modes, as well as having a range of 4M. It's cousin VL53L4CD is also a time of flight sensor with similar characteristics but it has a range of about 1.3M. Let's hook it up and find out just how far away that thing over there is.

ESP32-S2 Thing Plus Hookup Guide

Looking to use the more secure ESP32-S2? Follow this hookup guide to get started.

Sending Sensor Data Over WiFi

This tutorial will show you how setup a simple peer-to-peer connection to send and receive sensor data between two ESP32 WiFi boards.