Smart Golf Swing Analyzer

by leoce394 in Circuits > Arduino

13 Views, 0 Favorites, 0 Comments

Smart Golf Swing Analyzer

IMG_20260525_140116.jpg
IMG_20260525_140108.jpg
IMG_20260525_140222.jpg
IMG_20260525_140405.jpg
IMG_20260525_140613.jpg

Considering the exorbitant price of modern golf-swing analyzers, I set out to build a low-cost embedded swing analyzer that uses an IMU to detect swing motions, a piezoelectric disk sensor for impact shock, an RGB LED for system state and a local webpage hosted over a mobile hotspot to display swing analytics.


This tutorial is a part of an assignment Submitted to Deakin University, School of IT, Unit SIT210 - Embedded Systems Development.

Supplies

-Arduino Nano 33 IoT

-LSM6DSOX 6-Axis IMU

-Piezoelectric Disk Vibration Sensor (analog breakout)

-RGB LED

-Pushbutton

-220 ohm resistors

-Two 100uF Capacitors

-Mini Breadboard

-4 x AA USB Battery Pack

-USB A to Micro-USB Cable

-Light Switch Plate

-Double Sided Tape

-Bicycle Light Clips (or related clips)

-Golf Club

System Design

System Inputs:

  1. Push-Button arms the system in preparation for a swing.
  2. IMU detects swing motion and stages through its Gyroscope.
  3. Piezoelectric sensor detects ball strike.

Processing and Calculations:

  1. Arduino loop() logic uses a state machine.
  2. Discerns values for total, backswing and downswing time to calculate swing tempo.
  3. Uses maximum gyroscope magnitude to calculate swing speed.
  4. Uses change in clubface angle to estimate angle at impact.
  5. Detects Real vs Practice swing, calculates different set of metrics for each.
  6. Takes Piezo value at impact for strike magnitude.

System Outputs:

  1. RGB LED displays system state.
  2. WiFi webpage displays latest swing analytics.

Circuitry Connections

ARDUINO 3.3V - Breadboard (+) Rail

ARDUINO GND - Breadboard (-) Rail

IMU V IN - (+) Rail

IMU GND - (-) Rail

IMU SDA (I2C) - Arduino A4

IMU SDA (I2C) - Arduino A5

PIEZO V IN - (+) Rail

PIEZO GND - (-) Rail

PIEZO A0 - Arduino A7

RGB RED - Arduino D5 (220 ohm)

RGB GREEN - Arduino D6 (220 ohm)

RGB BLUE - Arduino D9 (220 ohm)

RGB GND - (-) Rail

PUSH-BUTTON - Arduino D3 -> (-) Rail through INPUT_PULLUP

100uF CAPACITORS - Sit over the (+) and (-) Rails.

Putting It Together

IMG_20260525_140613.jpg
IMG_20260525_140239.jpg

As this is a proof-of-concept prototype, I used a rudimentary solution for holding it all together.

I used double sided tape to adhere the battery box to the switch plate, and another layer to hold the breadboard assembly to the battery box, leaving access to the plate and box screws.

In my case, I found the AA batteries were prone to shifting in their slots during impact. This would cut out the power and restart the microcontroller. This is why I've used two capacitors to supply power during micro-outages. I'd recommend doing this in situations involving alkaline batteries, but if you choose to use a Li-Po or Li-On pack it may not be needed.

I used another small square of tape to stick the Piezo Disc to the side of the battery box, as well as a small patch to hold the capacitors down to the board, since they were prone to fly off during impact.

I found some bicycle light clips about my house, which work well for gripping the club's shaft tightly, and can be tightened by screwing into the switch plate.



Program Flow

image_2026-05-25_145854030.png

I used a state machine dictated by an enumerable, as the system requires distinct states with their own instructions. It works like this:

1, SETUP_STATE

  1. Initialize Pins, Devices, and WiFi Server.

2, STANDBY

  1. Wait for D3 interrupt (Button Press)

3, ARMED

  1. Wait for start of swing, gyroscope magnitude must exceed a small threshold.

4, BACKSWING

  1. Detect swing transition

5, DOWNSWING

  1. Wait for Piezo Impact polling OR practice swing motion timeout.

6, PROCESSING

  1. Calculate, store and output results.
  2. Return to STANDBY

Recognizing the Swing

image_2026-05-25_191407382.png
image_2026-05-25_191614797.png
image_2026-05-25_191852196.png
image_2026-05-25_192056621.png
image_2026-05-25_193733160.png
image_2026-05-25_194142304.png
image_2026-05-25_194153999.png

If we're going to compute swing tempo, we need to know when the golf swing begins, when it enters different stages, and when it stops.

We know that a typical swing begins at rest, nears rest again at the backswing, strikes the ground and rests again in the follow-through.

To detect these steps and allow our state machine to progress, we can input all 3 axes of gyroscope values in degrees/second into the equation sqrt(x^2 +y^2+z^2) to return the net motion of the gyroscope.

We'll use these values to first determine when the swing begins by comparing the net gyro readings to a threshold once the button has been pressed and the system is ARMED.

I used a larger threshold to allow for small adjustive movements a golfer may take in the final moments before the swing, but once its surpassed, we start taking timestamps and move into BACKSWING.

In BACKSWING we once again use Gyroscope magnitude to figure out when the brief pause at the end occurs, using a smaller than threshold before (35.0 worked for me). Once this happens, we can finalize our backswing time by finding the difference between millis() timestamps. Now we progress to the DOWNSWING.

Now the fastest moving motion of the swing, the downswing. To detect a ball strike, the D0 pin on the piezo sensor would usually work best as an interrupt, however this didn't end up working on mine. So I try detect a strike twice, first at the start of the downswing logic, then just before the next set of data is calculated. If the piezo analog reading exceeds a normal threshold (200 in my case), we mark the end of the swing with a Boolean value impactDetected. We also take our timestamps for the end of the swing, as well as the downswing duration, before moving to PROCESSING. But if its a practice swing, during the entirety of the backswing and downswing we've timestamped every motion with lastMotionTime, so if the club ever comes to rest for a set period in the downswing, we can still mark the swing finished and classify it as a practice swing.


Translating Our Data

image_2026-05-25_200303687.png
image_2026-05-25_200409608.png

So we've got all the swing data we need to create some nice metrics to display on our webpage, but how do we turn it into units we know?

During the DOWNSWING, we constantly took measurements of the total Gyroscope magnitude, saving it to a variable peakGyro if it were the largest recorded yet. The following equation:

kmh = (peakGyro*3.14/180)*1.33*3.6;

takes the peak Gyroscope value in degrees/second, multiplies by pi/180 to get radians/second, then uses the formula (linear speed (m/s) = angular velocity (radians/second) * radius (m)), to determine velocity in m/s, multiplying by 3.6 for km/h. In this case, I measured my swing radius (1.33m) from my sternum to the center of the clubface while addressing the golf ball.

Considering the Gyroscope does not measure exact orientation at a specific time, I used the net change in the x-axis to estimate clubface angle at impact. faceAngleUpdate() accumulates the total net change in the x-axis during both the backswing and the downswing. This is achieved by solving for degrees through multiplying the latest gyro reading by the difference of two timestamps, currentTime and previousAngleTime, which are measured in microseconds. This works since raw gyroscope readings are measured in degrees/second. At the end of the swing the current faceAngle is saved to a field finalAngle, that is used in PROCESSING.

For real swings, swing tempo is calculated through backswingTime/downswingTime. Since we can't achieve a timestamp for the end of the downswing for practice swings, we calculate totalSwingTime instead.

We then save our cleaned-up metrics to corresponding Strings for tempo, swing type, face angle, shock magnitude, swing speed, backswing time and total swing time. These are what we will use for display on our webpage.

Display on the Web!

image_2026-05-25_203708965.png
image_2026-05-25_203643039.png
strike ss.png
practice ss.png

It's time to display the metrics from the last swing! Since this is a wireless prototype, we need to output our values over the web, and a simple web server works nicely for this part.

I used the WiFiNINA library, particularly its WiFiServer functionality to host a small server on the Arduino, which listens for HTTP requests from a browser connected to the same WiFi network. (I used my phone hotspot) This allows us to print HTML back to the browser to update the interface when a new swing occurs.

At the beginning of each loop() iteration, if the system state is either in STANDBY or PROCESSING, my method hadleWebpage() calls, which prints the new swing in HTML to the browser on my laptop.

Attatched is the result of two basic swings, one real and one practice. See the difference in output!

With the web dashboard complete, the system is able to collect data during the swing, process it locally on the microcontroller and output it to a browser interface. This completes our low-cost smart golf swing analyzer!