I’ve received a few requests for a step by step tutorial on how to build the circuit that I had talked about below. Sorry, this post won’t be that step by step guide but it will provide many of the tools. As a mid stop on my way to making a robust design others can copy I built a battery powered, hand wired, point to point monster that I could keep connected to the bike for my own personal winter training. At the same time I drew up the electrical schematic and laid out a PCB that would make it easy for others to build at home without doing painful point to point wiring. I had a few requirements for the finial design.
Li-Ion Battery Powered
Built in USB Charger for the battery
Built in display to show, Torque, Power, Cadence and some parameters for debugging such as the current torque zero offset
USB programmable for firmware updates and general messing with it at a later time without needing to use an In-Circuit Serial Programming (ICSP).
Battery: I’ve have a pile of 14500, 900mAh 3.7V Li-ion batteries laying around that fit perfectly in the corner of the project box. I’ve been using the bike about an hour a day for the last month and have yet to need to charge it. One of these days I’ll measure the power draw to get a rough idea of how long it should last. This first prototype has no ability to charge the battery. I’ll have to use one of my external chargers to do it for now
Display: I chose to use a small 128×64 OLED display that I had been playing around with. It has incredible view-ability, draws very little power and will run off 3.3V without any external components. It also has really easy to use libraries that made getting the data on the screen very quick to add to the code.
The hand wired unit I built does not have a built in charger but that is addressed in the final PCB design.
Just as before, the PCB design files and source code can be found in Github CycleOpsPro300PTtoANTPlus. SeeedStudio the PCB vendor I used has a minimum 5 board order so if there is any interest leave a comment below and I’m happy to send the extra to a good home $10 a board.
As a quick refresher Power = Torque * Speed or in terms of units Watts = Newton*meter * 1/sec. Where Speed is in units of Radians/Sec
If we can get the torque and get the wheel speed out of the bike we have everything we need. In the very beginning of this project, in a worst case if I could only get the torque, I could always put a magnet on the rear wheel and measure the wheel speed directly to get that part.
I was very thankful the engineers at CycleOps who were nice enough to provide a diagnostics menu with the stock computer. This menu showed the raw output from the torque hub in counts. This was a life savor as it let me know when my decoding algorithm for the torque was correct. I spent many hours pushing on the crank arms with the torque adjuster as tight as it would go to test various decode algorithms. When I finally got it right see CycleOps Pro300 PT to ANTPlus, Details it turned out that 1 bit = 1 in*lb. So as you can see in the code
torque_Nm = (torque_in_lbs)*0.112984829;
I do the conversion in real time just to make the units easier to work with later (I really prefer to work in the metric system)
Being that this bike is close to 10 years old I didn’t want to just trust that it was still calibrated. The best place to hang weights was off the pedals. The crank arms are nicely labeled as being 170mm (I verified they are). Now I needed the tooth count of the crank arm chainring and the rear wheel chainring. I can’t really call it a cassette as this version of the bike behaves exactly like a fixed wheel, no coast at all. At first I thought this would be a problem but the spin bikes at my gym that I have been using for years are just like this and it’s not a problem at all.
Off comes the safety covers and with a little tape to mark my starting point. Several recounts later
Gear Ratio = 3.714. Or to put it another way, torque applied to the crank arms will be reduced by 1/Gear Ratio = 0.26923
Grabbing some free weights and attaching some string made the rest of the process easy.
Read the zero value
Hang 15lbs, record the value
Hang another 15lbs, record the value
Balance 10lbs (because this is all I had to work with), record the value
I did this with the flywheel cold and after a hard 1 hour workout. I’ve read on other forums that this bike has a tendency to drift a small amount as the rear hub heats up. This is the oldest version of the bike with the torque tube welded into the flywheel. Newer version of this bike had the torque tube as a bolted in assembly. I can only guess that CycleOps recognized this issue and corrected it between these versions. For my purpose this is still fine as the slope of the output didn’t change at all, just the offset. I’ll deal with this by putting a zeroing button on my controller and after my warm-up, will zero the system. Some data for those interested
Torque on Rear Wheel (in*lb)
You can see from the last column that the ratio of known applied torque to measured counts output by the hub is very close to 1. This are far from calibrated weights and I would be very happy with an accuracy of ±5%. Given these 3 data points (not really enough but I’ll take it for now). I’m around ±3.5%. One of these days I’ll use a more accurate scale to weight each of these weights to further improve the calibration as well as find some more weight to calibrate at greater applied torques.
Knowing that counts = torque – zero offset in units of in*lb we can find the torque in N*m through
torque_Nm = (torque_in_lbs)*0.112984829;
Once I had a stable output of the wheel speed part of the data steam I knew I needed a way to relate it back to “real” flywheel speed. The simplest approach I came up with was to build a quick hall effect sensor circuit to measure as a small neodymium magnet stuck onto the flywheel passed by. The flywheel is a solid chuck of steel so attaching the magnet was super easy, just let it hold itself on. I still haven’t gotten it off, but it’s not hurting anything. I wrote some code to output the raw data steam (Torque and Wheel Speed) from the CycleOps bike along with the time between each wheel rotation. Plotting these against each other provided a vary satisfying straight line
For speeds of zero the hub will output 4095 counts. Otherwise the wheel_period in µsec can be calculated by
This would give a wheel speed of 27.8 RPM or a cadence of 7.49 RPM. Since no biker is riding along at 7.49 RPM it seems perfectly fair to call any speed slower 0.
From this we can convert to rad/sec by
omega = 6283185.30718/wheel_period;
Back to the cadence number, since this bike is essentially a fixed gear bike if we know the rear hub (flywheel) speed we can also know the pedal cadence by dividing by the gear ratio. Hence from above 27.8 RPM (flywheel) * (14/52) = 7.48 RPM cadence. This feature of the bike was very nice as it allowed me to easily output cadence over ANT+. One of the metrics I really like to log along with power.
After getting the bike and cleaning it up I started to follow each wire though the bike to identify what plugs into where. Overall the wiring is really simple. The hub of the freewheel in the back has all of the processing electronics inside of it and 2 AA batteries.
I do want to say how useful and downright interesting FCC documents are when first looking at unknown commercial hardware. FCC ID T8P-SL2P402 . They also have some great photos of the internals of the torque tube. From the FCC report we can find out that the hub is using 2.4GHz (same as ANT+ but this isn’t ANT+) but most likely back when this was build in 2007 they hadn’t yet figured out how to get the transmission power needed to get the range while keeping power consumption to reasonable levels (totally my guess). So they went with the approach of putting a receive very close to the hub to relay the data back to the head unit. My guess is this exact same protocol was used on the early wired PowerTap hubs.
Thankfully the connector used between the computer and the cable that runs to the rear pickup is a very common Molex Micro-Fit. I made a pass though cable that allowed for easy access to all the pins while keeping the rear pickup connected to the head unit
Some poking around with the Oscilloscope and I had the pinout figured out
Let the fun begin, out comes the Logic Analyzer
I’ve done enough work with digital circuits and your run of the mill digital communication protocols to have some idea what to look for but this was my first time going in with no idea and no documentation to turn to for reference. The first 2 days was spent just watching as I applied force to the crank without turning, then letting the cranks spin with no force applied just to see how the signal changed.
A packet would be sent every 1.043sec
I started by thinking it was just your run of the mill asynchronous serial. I found the shortest pulse in any given chain 1.535ms and assumed (1/0.001535) = 651.4, call it 650baud rate. After trying this and other with every combination of data bit, stop bit and start bit I quickly came to the conclusion this was not right. There really were no start or stop bits.
I know that the torque and speed data must be sent with each packet as both would be displayed on the stock head unit and I could see how the bits shifted depending on if only torque or only speed was applied
The great discovery was that the starting pattern told me everything.
4 bits of data in 7.249ms = 1.812ms per bit (The code uses 1.770)
6 bits of 0s in 9.152ms = 1.525ms per bit (The code uses 1.545)
Once I had this variable bit rate figured out the rest just fell into place
I doubt the method I came up with is the most elegant way to work with this data but it worked for me.
I’ve been telling myself I’m going to document this project for others. So here is the start. Source code is posted to GIT hub and when it’s not almost midnight and I have some time I’ll post how this mess of code (but working) came to be. For now some random photos of the early setup that got me a working prototype.