Materials Needed:
3D printed parts (attached files)Unihiker boardHall effect sensorKY-039 heart rate sensorCylindrical portable battery (Power bank)USB type A to USB type C cableM3 x 3 cm screw with nutDupont cablesZip tiesElectrical tape
Once the 3D printed parts are ready, start by assembling the base, which includes the Unihiker board, the portable battery, and the USB cable. This step is crucial for both the pedometer and bike modes. You can follow the attached video for guidance during the assembly. This base securely connects to the bike or can be used directly as a pedometer.
For bike mode, install the sensors (Hall effect sensor and heart rate sensor), the magnet, and the mount for the Unihiker board. The Hall effect sensor is mounted on the bike fork, while the magnet is attached to one of the wheel spokes with zip ties. Make sure to secure the sensors and cables with electrical tape for added safety. Connect the Hall sensor to pin 22 and the heart rate sensor to pin 21 through the Unihiker's external ports.
In this section, Iâll explain how the code works so that you can understand the calculations behind the device. I have commented on each part of the code to make it easier to follow and modify if needed. Hereâs an overview of how sensor readings are handled in both modes.
Bike mode
: The Hall effect sensor detects each time the wheel completes a revolution. With this information, we calculate speed by dividing the distance traveled by the time elapsed between revolutions. Based on the speed and distance, we estimate the calories burned during the activity. Pedometer mode: The accelerometer integrated into the board detects steps while running or walking. Based on an average stride length, we calculate the distance traveled. Calories burned are estimated based on the number of steps and an approximate calorie-per-step value.
Mathematical calculations used in the device:
Speed (km/h)= (Wheel perimeter / time per revolution) * 3.6Calories burned (bike) = Distance traveled (km) * 40Distance traveled (pedometer) = Number of steps * average stride length
The complete code is attached and available in both English and Spanish versions for download from GitHub. To load the code onto your device and add image files, I recommend using Mind+, the editor I used for this project.
#general bike mode code
from unihiker import GUI
from pinpong.board import Board, Pin
import time
# Initialize the board
Board().begin()
# Create an instance of the GUI
u_gui = GUI()
# Define sensor pins
heart_sensor_pin = Pin(Pin.P21, Pin.ANALOG) # Heart rate sensor on Pin 21
bike_sensor_pin = Pin(Pin.P22, Pin.ANALOG) # Bike sensor on Pin 22
# Parameters for the bike
wheel_perimeter = 1000 # in meters (adjust according to your wheel size)
revolutions = 0
distance_traveled = 0.0 # in kilometers
speed = 0.0 # in km/h
max_speed = 0.0 # in km/h
calories_burned = 0.0
start_time = 0
previous_time = 0
previous_bike_sensor_value = 512
active = False
# Parameters for the heart rate
last_beat_time = time.time()
heart_rate_BPM = 0
is_peak = False
min_time_between_beats = 300 # Minimum time between beats in milliseconds
max_time_between_beats = 2000 # Maximum time between beats
change_threshold = 100 # Significant change between readings to detect a beat
previous_heart_sensor_value = 0
# Functions for the bike
def calculate_calories(distance):
return distance * 40 # adjust for moderate activity
def calculate_speed(lap_time):
global wheel_perimeter
if lap_time > 0:
speed_m_s = wheel_perimeter / lap_time
return speed_m_s * 3.6 # Convert from m/s to km/h
else:
return 0.0
# Functions for managing the bike ride
def on_buttona_click_callback():
global active, previous_time, start_time, revolutions, distance_traveled, max_speed, calories_burned
active = True
start_time = time.time()
previous_time = time.time()
revolutions = 0
distance_traveled = 0.0
max_speed = 0.0
calories_burned = 0.0
u_gui.clear()
def on_buttonb_click_callback():
global active
active = False
u_gui.clear()
total_time = time.time() - start_time
total_time_min = total_time / 60 # Convert to minutes
u_gui.draw_text(text=f"Trip Finished!", origin="center", x=120, y=20, font=("Arial", 20, "bold"), color="#FF0000")
u_gui.draw_text(text=f"Distance: {distance_traveled:.2f} km", x=5, y=50, font=("Arial", 16), color="#00FF00")
u_gui.draw_text(text=f"Max Speed: {max_speed:.2f} km/h", x=5, y=70, font=("Arial", 16), color="#0000FF")
u_gui.draw_text(text=f"Calories: {calories_burned:.2f}", x=5, y=90, font=("Arial", 16), color="#FF00FF")
u_gui.draw_text(text=f"Time: {total_time_min:.2f} min", x=5, y=110, font=("Arial", 16), color="#FFAA00")
u_gui.draw_text(text="Press A to restart", origin="center", x=120, y=160, font=("Arial", 12), color="#000000")
u_gui.draw_image(x=-15, y=190, w=350, h=150, image='bici1.png')
u_gui.on_a_click(on_buttona_click_callback)
u_gui.on_b_click(on_buttonb_click_callback)
# Display initial message
u_gui.clear()
u_gui.draw_text(text="Press A to", origin="center", x=120, y=50, font=("Arial", 18, "bold"), color="#7ed957")
u_gui.draw_text(text="start your trip", origin="center", x=120, y=80, font=("Arial", 18, "bold"), color="#7ed957")
u_gui.draw_image(x=-15, y=120, w=350, h=150, image='bici3.png')
# Function to detect heartbeat
def detect_heartbeat(sensor_value):
global is_peak, last_beat_time, heart_rate_BPM, previous_heart_sensor_value
current_time = time.time()
time_diff = (current_time - last_beat_time) * 1000 # Convert to milliseconds
if abs(sensor_value - previous_heart_sensor_value) > change_threshold and not is_peak:
if min_time_between_beats < time_diff < max_time_between_beats: # Consider reasonable times
is_peak = True
heart_rate_BPM = 60000 / time_diff # Calculate BPM
last_beat_time = current_time
print(f"Heartbeat detected. Interval: {time_diff} ms. Calculated BPM: {heart_rate_BPM}")
elif abs(sensor_value - previous_heart_sensor_value) < change_threshold and is_peak:
is_peak = False # Prepare to detect the next heartbeat
previous_heart_sensor_value = sensor_value # Update the last read value
# Main loop
while True:
if active:
# --- Calculations for the bike ---
bike_sensor_value = bike_sensor_pin.read_analog()
if bike_sensor_value < 512 and previous_bike_sensor_value >= 512:
current_time = time.time()
lap_time = current_time - previous_time
previous_time = current_time
speed = calculate_speed(lap_time)
revolutions += 1
distance_traveled = revolutions * wheel_perimeter / 1000 # Convert to km
if speed > max_speed:
max_speed = speed
calories_burned = calculate_calories(distance_traveled)
# Calculate elapsed time
total_time = time.time() - start_time
total_time_min = total_time / 60 # Convert to minutes
# --- Calculations for heart rate ---
heart_sensor_value = heart_sensor_pin.read_analog()
detect_heartbeat(heart_sensor_value)
# Display information on screen
u_gui.clear()
u_gui.draw_text(text=f"Speed: {speed:.2f} km/h", x=5, y=10, font=("Arial", 16), color="#0000FF")
u_gui.draw_text(text=f"Distance: {distance_traveled:.2f} km", x=5, y=40, font=("Arial", 16), color="#00FF00")
u_gui.draw_text(text=f"Calories: {calories_burned:.2f}", x=5, y=70, font=("Arial", 16), color="#FF00FF")
u_gui.draw_text(text=f"H.R.: {heart_rate_BPM:.2f} BPM", x=5, y=100, font=("Arial", 16), color="#FF0000")
u_gui.draw_text(text=f"Time: {total_time_min:.2f} min", x=5, y=130, font=("Arial", 16), color="#FFAA00")
u_gui.draw_image(x=-15, y=170, w=350, h=150, image='bici2.png')
previous_bike_sensor_value = bike_sensor_value
time.sleep(0.5)
Congratulations! You have assembled your DualSport Tracker, and now it's ready to use. You can use it on your bike rides or during your running sessions. The main menu will allow you to easily switch between bike and pedometer modes, depending on the exercise you prefer.
The device will display key real-time information such as speed, distance traveled, calories burned, heart rate, and total exercise time. If you want to switch from bike mode to pedometer mode, simply disconnect the two sensors and detach the device from the bike (this is easy thanks to the simple design of the device). To mount it back on the bike, just reverse the process.
Using the Menu
Start Activity
: Press button 'A' to start recording your activity.End Activity: Press button 'B' to stop recording and display the total data of your exercise, including distance, max speed, calories burned, and time.Switch Modes: Use the menu to toggle between bike and pedometer modes depending on the activity youâre doing.
Conclusion and Potential of the DualSport Tracker
The DualSport Tracker has great potential for those who enjoy mountain biking, running, or simply want to monitor their physical activity without relying on GPS or internet connections. The flexibility of the device, which allows it to be used both while cycling and running, and the ability to customize its parameters according to the userâs needs, make it a practical and versatile tool, ideal for both athletes and makers.
This project combines technology, creativity, and a passion for sports, providing an efficient solution for measuring distances, speeds, calories burned, and heart rate. It's perfect for those who train in areas without GPS coverage or are looking for a portable and easy-to-use tool to improve their performance.
I encourage you to experiment with the DualSport Tracker, adjust it to your needs, and explore new features. If you have any questions or ideas for your own version of the device, I would be happy to help. Without further ado, see you next time!