Open (Bicycle) Grade Simulator - OpenGradeSIM

18,399

68

38

Introduction: Open (Bicycle) Grade Simulator - OpenGradeSIM

Introduction

A certain well known US based fitness company (Wahoo) recently brought out a great indoor training aid that raises and lowers the front of the bike on the turbo trainer according to the simulated grade of hill that the user is riding (the Kickr Climb).

Looks amazing but sadly this is not available to us all as you will need 1) a top of the range Wahoo trainer and 2) £500 cash to make this yours.

I broke a clavicle (never put a road cyclist on a mountain bike) so I had more miles on the trainer and more time to tinker and thought this could be a fun project.

The commercial unit simulates -5% to +20% so I wanted to come close to that but on 10% of the budget!

This is designed around my Tacx Neo but any trainer that broadcasts its power and speed data via ANT+ or BLE could be made to work (I reckon!).

Since the wheel base on my road bike measures exactly 1000mm I'd need to lift the forks by 200mm to simulate 20% (see pic) so a 200mm linear actuator would do. The bike + rider weight is unlikely to exceed 100kg and since this is distributed between the axles and most is on the back 750N will lift 75kg and should be ok. Faster actuators are available for more money but this one cost me around £20 and manages 10mm/sec. Actuators with potentiometers that can be used as simple servos are also 2 to 3 times more expensive.

Supplies:

3D print (PLA or ABSetc) of the through axle adapter part: https://www.thingiverse.com/thing:3963542

100mm of 3/4 inch 10 swg aluminium tube stock (for a through axle frame)

80mm of 6mm stainless steel bar stock

3D print (PLA or ABSetc) of the shoe for the linear actuator part: https://www.thingiverse.com/thing:3963536

3D print of the Case for the H-bridge https://www.thingiverse.com/thing:3963573

3D print of the Case for Arduino (Version 1 with keypad) https://www.thingiverse.com/thing:3984911 (Version 2 as shown (https://www.thingiverse.com/thing:3995976)

Laser cut piece of 3mm clear acrylic 32 x 38mm to keep you from sweating all over the electronics (did that, not ideal).

Some bleeding blocks (adapted to leave the pads in) to prevent you accidentally pushing the calliper pistons out of your Shimano disc brakes in your enthusiasm https://www.thingiverse.com/thing:3989504

Linear Actuator 750N 200mm travel eg Al03 Mini Linear Actuators from https://www.linear-actuator.net.cn/product/linear...

L298N H bridge (like: https://ebay.us/mCWnAl)

Arduino Nano IoT 33 www.rapidonline.com order 73-4863

2 key membrane keyboard eg https://www.aliexpress.com/item/33030999674.html

IIC I2C Logic Level Converter Bi-Directional Module 5V to 3.3V For Arduino eg https://ebay.us/eusJZy

12V 3A DC power supply - the ones for LED lighting work great!

NPE CABLE Ant+ to BLE bridge https://npe-inc.com/cableinfo/

3D printable clip for the CABLE bridge https://www.thingiverse.com/thing:3989476

1.3" OLED LCD Display Module with IIC I2C Interface 128x32 3.3V

Step 1: Some Mathematics

We need to calculate the incline being simulated. I had hoped that the trainer would advertise this data along with speed, power, cadence etc. however the trainer simply sets resistance to maintain power output according to the software on the tablet, computer etc being used to control it. I had no way to easily capture the 'simulated grade' from the software so I would have to work backwards...

The forces acting on the bike and rider are a combination of resistive losses and the power needed to climb the hill. The trainer reports speed and power. If we can find the resistive losses at a given speed then the remaining power is being used to climb the hill. The power to climb depends on the weight of the bike and rider and the rate of ascent and so we can work back to the incline.

First I used the amazing http://bikecalculator.com to find some data points for resistive power loss at typical speeds. Then I transformed the speed domain to produce a linear relationship and found the best fit line. Taking the equation of the line we can now calculate power (W) from resistance = (0.0102*(Speedkmh^2.8))+9.428.

Take the power from resistance from the measured power to give power of 'climbing'.

We know the speed of ascent in km/hr and convert this to SI units of m/s (divide by 3.6).

Incline is found from: Incline (%) =((PowerClimbing/(WeightKg*g))/Speed)*100

where acceleration of free fall g = 9.8m/s/s or 9.8 N/kg

Step 2: Get Some Data

The incline calculation requires speed and power. I used an Arduino Nano 33 IoT to connect to the trainer via BLE to receive this. I got very stuck initially as the current v.1.1.2 version of the native ArduinoBLE library for this module does not handle authentication in any form which means most(?) commercial BLE sensors will not pair with it.

The solution was to use an NPE Cable ANT+ to BLE bridge (https://npe-inc.com/cableinfo/) which keeps the built in BLE of the trainer free for the training app to communicate over and requires no authentication on the BLE side.

The BLE power characteristic is pretty straightforward as power in watts is contained in the second and third bytes of the transmitted data as a 16 bit integer (little endian ie least significant octet first) . I applied a moving average filter to give 3s average power - just like my bike computer shows - as this is less erratic.

if (powerCharacteristic.valueUpdated()) {

// Define an array for the value

uint8_t holdpowervalues[6] = {0,0,0,0,0,0} ;

// Read value into array

powerCharacteristic.readValue(holdpowervalues, 6);

// Power is returned as watts in location 2 and 3 (loc 0 and 1 is 8 bit flags)

byte rawpowerValue2 = holdpowervalues[2];       // power least sig byte in HEX

byte rawpowerValue3 = holdpowervalues[3];       // power most sig byte in HEX

long rawpowerTotal = (rawpowerValue2 + (rawpowerValue3 * 256));

 // Use moving average filter to give '3s power'

powerTrainer = movingAverageFilter_power.process(rawpowerTotal);

The BLE speed characteristic (Cycling Speed and Cadence) is one of those things that makes you wonder what on earth the SIG was smoking when they wrote the specification.

The Characteristic returns a 16 byte array that contains neither speed nor cadence. Instead you get wheel revolutions and crank revolutions (totals) and time since last event data in 1024ths of a second. So more maths then. Oh, and the bytes are not always present so there is a flag byte at the start. Oh, and the bytes are little endian HEX so you need to read backwards multiplying the second byte by 256, third by 65536 etc. then adding them together. To find speed you need to assume a standard bike wheel circumference to know distance....

if (speedCharacteristic.valueUpdated()) {

//  This value needs a 16 byte array

      uint8_t holdvalues[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} ;

//  But I'm only going to read the first 7

      speedCharacteristic.readValue(holdvalues, 7);

byte rawValue0 = holdvalues[0];      // binary flags 8 bit int
byte rawValue1 = holdvalues[1];      // revolutions least significant byte in HEX   
byte rawValue2 = holdvalues[2];      // revolutions next most significant byte in HEX
byte rawValue3 = holdvalues[3];      // revolutions next most significant byte in HEX
byte rawValue4 = holdvalues[4];      // revolutions most significant byte in HEX
byte rawValue5 = holdvalues[5];      // time since last wheel event least sig byte 
byte rawValue6 = holdvalues[6];      // time since last wheel event most sig byte

      if (firstData) {

// Get cumulative wheel revolutions as little endian hex in loc 2,3 and 4 (least significant octet first)

WheelRevs1 = (rawValue1 + (rawValue2 * 256) + (rawValue3 * 65536) + (rawValue4 * 16777216));

// Get time since last wheel event in 1024ths of a second

Time_1 = (rawValue5 + (rawValue6 * 256));

        firstData = false;

      } else {
// Get second set of data         

long WheelRevsTemp = (rawValue1 + (rawValue2 * 256) + (rawValue3 * 65536) + (rawValue4 * 16777216));

long TimeTemp = (rawValue5 + (rawValue6 * 256));

if (WheelRevsTemp > WheelRevs1) {           // make sure the bicycle is moving

WheelRevs2 = WheelRevsTemp;

Time_2 = TimeTemp;

firstData = true;}
// Find distance difference in cm and convert to km          <br><br>float distanceTravelled = ((WheelRevs2 - WheelRevs1) * wheelCircCM);
          <br>float kmTravelled = distanceTravelled / 1000000;
 // Find time in 1024ths of a second and convert to hours
          <br>float timeDifference = (Time_2 - Time_1);
          <br>float timeSecs = timeDifference / 1024;
          <br>float timeHrs = timeSecs / 3600;
// Find speed kmh
          <br>speedKMH = (kmTravelled / timeHrs);

The Arduino sketch is hosted at GitHub (https://github.com/mockendon/opengradesim).

Step 3: Hardware 1 the Linear Actuator

The through axle on my disc brake road bike specifies a 19.2mm axel to clear the 12mm through axle with 100mm between the forks.

Stock 3/4 inch 10swg aluminium tube is a perfect fit and a nice chap called Dave on ebay (https://www.ebay.co.uk/str/aluminiumonline) supplied and cut it to length for me for a few pounds.

The actuator has a 20mm bar with a 6mm hole so the 3D printed part links the aluminium tube to a 6mm steel bar and since the forces are 90% compression some PLA / ABS is up to the challenge.

If you run a standard quick release setup then something like this (https://www.amazon.co.uk/Sharplace-Quick-Release-Conversion-Adapter/dp/B079DCY344) would avoid having to redesign this component.

The boot is designed to fit into the raiser block supplied with my Tacx trainer but would probably fit into many similar raisers or you can just edit the TinkerCad file to suit your requirement.

Step 4: Hardware 2 - the H-Bridge

These L298N H bridge board that are very common online have a built in 5V regulator which is great for powering the Arduino from the 12V power supply required for the linear actuator. Unfortunately the Arduino Nano IoT board is 3.3V signalling hence the need for a logical level convertor (or an optoisolator since the signals are unidirectional only).

The case is designed to accept the power connectors commonly used in LED lighting applications. I butchered a USB extension lead to make it possible to connect / disconnect the Arduino head unit easily and whilst I was sure to use the power lines for power and the data lines for the 3.3V signalling I would honestly advise AGAINST this as I'd hate someone to fry their USB ports or peripherals by plugging them in by mistake!

Step 5: Hardware 3 the Control Electronics (Arduino)

The case for the Arduino OLED and logic level converter has a standard 1/2 turn Garmin style mount on the back to allow it to be mounted securely to the bike. An 'out front' mount will allow the unit to be tilted up or down to 'zero' the accelerometer position or a line of code just to auto zero at the start would be easy to add.

The case has a spot for a membrane keypad - this is used to set the combined rider and bike weight. You can just set this programmatically especially if you don't share a trainer with anyone.

It might be nice to implement a 'manual' mode. Perhaps pressing both buttons could initiate a manual mode and then the buttons could increase / decrease incline. I'll add this to the to-do list!

The STL file of the case is, again, available on Thingiverse (see the supplies section for link).

The Arduino sketch is hosted at GitHub (https://github.com/mockendon/opengradesim).

You can print a neat little clip for your CABLE bridge from here https://www.thingiverse.com/thing:3989476

Step 6: 'The Rear Drop Outs'

Many people have raised the issue of the rear drop out rubbing as the bike moves. Some trainers have an axle that moves (like the Kickr) but many do not.

Currently my best solution for me is to mount some standard 61800-2RS deep groove bearings (about £2 each) on the quick release adapters and then mount the through axel drop outs on these (see pics) with an over size QR skewer

The bearings need a thin shim washer eg M12 16mm 0.3mm between the adapter and the bearing.

They fit perfectly and rotate with the bike and the skewer independently of the trainer.

At the moment this changes the offset on the drive side by a couple of mm so you'll need to re-index

I am designing custom parts (see pdf plan) to machine (on my future brother-in-law's lathe when he has an hour to help!). These are not tested yet!!! But grinding 1mm off the inner surface of the stock drive side QR adapter is a quick fix with no special tools ;)

Made with Math Contest

Participated in the
Made with Math Contest

Be the First to Share

    Recommendations

    • Microcontroller Contest

      Microcontroller Contest
    • Automation Contest

      Automation Contest
    • Make it Glow Contest

      Make it Glow Contest

    38 Comments

    0
    tobias.burger1
    tobias.burger1

    Question 4 weeks ago

    hi, great project and nice inspiration for me. The GradeSim Shoe doesn't seem to be on thingiverse no longer. Can you please send me this file?

    0
    johnny.vandeputte
    johnny.vandeputte

    6 weeks ago

    I can connect with my Kickr core, but for some reason it only shows the cycling power service 0x1818, can't seem to finde the CSC service. or does wahoo uses not the standard service?

    0
    johnny.vandeputte
    johnny.vandeputte

    Reply 5 weeks ago

    resolution: The wahoo trainer supports the speed and cadence trough the cycling power service.
    you need to check the flags (first 2 bytes) of the subcribed data to check at which position you can find them

    0
    chrubblesum
    chrubblesum

    2 months ago

    Sorry - Another question. In the Arduino Sketch files on GitHub there are two different versions:
    OpenGradeSIM_CombinedCode_027.ino
    OpenGradeSIM_CombinedCode_100.ino
    What are the two different versions for? I see that the Actuator Out Pins are swapped between the files, the Moving Average Filter power changes, the Float Version number, etc. Which should I be looking to load to my Arduino and do I need to adjust any setting for a slightly more powerful actuator?

    0
    chrubblesum
    chrubblesum

    3 months ago

    I'm not clear from the guidance how the wiring for the membrane keypad works? Also, is there guidance on how to program the Arduino with the Sketch code? (I've not programmed an Arduino before). Final question: Once powered up, will the Cable automatically find the Tacx Neo Ant+ data (I have a Tacx Neo, also) and the Arduino automatically pick up on the Cable BLE broadcast? Sorry if these are dumb questions - It's new territory for me.

    0
    davidgoddard9
    davidgoddard9

    3 months ago

    Hi,
    A few years ago a colleague explored the ANT+ data packets but could not see the grade information and unlike you, we did not think to get it another way using the other data. Brilliant!
    In the end I went for a web page which you set to 'watch' the zwift screen - i.e. use zwift as a video source. Using just JavaScript and Canvas in a static HTML page (no server, no app, just drop the file onto the browser), I used the % character as a locator and once found the software grabs a part of the screen where the numbers are, cleans up the image to pure black/white of the numbers and then does a simple pattern match. The web page samples once per second (although it only takes about 50-60ms to perform the match) and then sends this number via a web-socket to an ESP8266 board set up on your home wifi to operate a motor and screw thread.
    Different approach to yours but I do get exactly the number the user sees on the display screen but with 1 second lag though - I'm tempted to try your approach now! But I'm not a cyclist and don't have a trainer. Not sure if I would be able to try your approach out. Mine works with just the computer running zwift in the 'just watch' mode.
    After all my work, yours seems neat - it made me smile!
    Thanks.

    0
    Ray_k1984
    Ray_k1984

    Question 4 months ago

    Hi,
    This looks to be a great job.... This my be a stupid question as I have not fully researched yet but will this operate using the Wahoo trainer or does each trainer have specific needs?
    Thanks!

    0
    luigicantadori
    luigicantadori

    4 months ago

    I congratulate you, you did a great job ..
    I tried to do something similar too, starting from the work you developed.
    As a mechanical part I used a worm screw controlled by a motor for wipers, the motor reverses the direction of travel via a shield with two relays controlled by an arduino.
    At first I had created this simulator using the arduino uno and varied the inclination using a trimmer, but after reading what YOU did, I did it all again with the arduino nano 33 iot.
    I didn't need the Cable, I managed to get my trainer (elite suito) to connect arduino without external interfaces.
    Luigi

    image.jpg
    0
    pniewiadomy
    pniewiadomy

    Reply 4 months ago

    Hi Luigi, did you follow the exact steps as in tutorial, apart form the actuator? Do you use it with Zwift? How does it operate in real world. Thank you in advance. I'm in a process of sourcing parts. Hepefully will get this working over the next couple of weeks.

    0
    luigicantadori
    luigicantadori

    Reply 4 months ago

    hi, i don't use zwitf, but there should be no problem using it, because arduino is connected via bluetooth with the trainer and not with the computer.
    The slope is calculated by arduino.
    The code that MATT has made available works great. obviously unless you have the same roller as MATT, the program is not plug and play, there are some things to adapt like the wheel circumference used for the calculations, the weight, etc.
    I used a card with two relays to move the motor.
    Arduino feed alone through the notebook's USB, while the relay and the motor are powered by a separate transformer.
    i left the power supplies separate because arduino is delicate.

    0
    Matt Ockendon
    Matt Ockendon

    Reply 4 months ago

    That looks like some very tidy engineering. Fantastic!

    0
    pniewiadomy
    pniewiadomy

    4 months ago on Step 6

    Hi, this is amazing and really inspiring!!! I'm planing to do it myself.I'm very new to this type of work, but where is a will there is a way. I did some digging and found easy and cost effective way to make any Turbo trainer compatible. You can use needle thrust bearings (usually 2mm thick) to allow for bike movement. https://www.amazon.co.uk/SKF-Needle-Thrust-Roller-Bearing/dp/B00YS8FLFE/ref=sr_1_19?dchild=1&keywords=thrust+bearing&qid=1602013658&sr=8-19. I'm getting bits on line and will post here how my project went. Thank you again.

    0
    PascalK13
    PascalK13

    Question 1 year ago on Step 5

    Can you update the instructable with more detailed PICTURES (than just two small pictures) about of wich Ports numbers on the [arduino nano33 iot] <-> sides you used to connect with wich other Hardware connections exactly
    like the
    <-> IIC I2C Logic Level Converter Bi-Directional Module 5V to 3.3V For Arduino
    <-> L298n H-bridge motor controller,
    <-> 2 key membrane keyboard
    <-> the display>
    <-> the Lineair Actuator.
    <-> and the 12v Power supply ?
    Please?
    Because Without more detailed Pictures of how you have connnected all these different hardware parts together It is very difficult / always impossible are qould take a lot of trial and error for others to make all the connections right and in the same way as you programmed them in the arduino sketch on github.
    Wich is a pity as some followers like me have allready purchased some of the required hardware you mentioned as required in the open grade sim project. ANd most after the preperations likely will get stuck as a faillure occurs halfway without more detail about how to make all the hardware connections right in the instructable .

    0
    m.ockendon
    m.ockendon

    Reply 8 months ago

    Hi, the pin numbers are in the sketch. You can change them to which ever pins you choose to connect. It is unlikely that you will build this exactly the same way since some parts bought from eg AliExpress will change specification over time. Good luck!

    0
    RobertB887
    RobertB887

    Question 1 year ago

    Nearly up and running! But, I'm getting crazy kpm readings on the top right hand corner. Any help and suggestions would be greatly appreciated.

    Update 1; Reset Cable device now it says I'm travelling at 29kpm even though I'm not turning the pedals. % in bottom right stays a 0.

    Update 2: I made this for an Elite Direto OTS which it doesn't appear to like. Erratic, no logic to when it raises and falls, including doing a 'wheelie' on a 0% gradient! Given up and created a manual mode device using a Uno I had lying around with an L298N H-bridge and joystick.

    Readout.jpgReading 2.jpg
    0
    darrenwalker
    darrenwalker

    1 year ago

    One further question ... for the lower shoe, do you grind off the lower mounting tab on the actuator, or remove the cover? Struggling to see how it fits in place. Thanks.

    0
    m.ockendon
    m.ockendon

    Reply 1 year ago

    Hi, the shoe has a rebate to accommodate the mounting tab.

    0
    RobertB887
    RobertB887

    Reply 1 year ago

    The shoe I had printed out didn't have a hole, so ended up grinding it off.

    0
    RobertB887
    RobertB887

    Question 1 year ago

    Cannot source a 1.3" OLED LCD Display Module with IIC I2C Interface 128x32 3.3V anywhere! Where can you buy them?