CycleOps Pro300 PT to ANT+, Part 7, V1 is back with better software

I’ve received several requests for updates on the V2 hardware. I’ve had a breadboard version of the V2 sitting on my desk for months. I haven’t yet found the advantages of the ESP8266 processor enticing enough to make new PCBs and deal with the new headaches that will come with trying to get everything working as well as it was on the ATmega32U4.

Back to the development on the ATmega32U4, I went off to find a solution to the memory issues. The power meter conversion code is relatively small, the majority of the space was being consummed by the library for the OLED display, while very nice to have it isn’t really needed for the device to function. At the time I was using Adafruit_SSD1306 an excellent library that includes test and image support. For what I was going, text only was just fine. Hunting around GitHub and Google I found the very well designed SSD1306Ascii. While not a drop in replacement for the previous library do the conversion over took me a few hours and memory usage went from close to 90% where I was dealing with random reboot and odd issues to a much more comfortable 44% of program memory being used. Solution found, time to move on to the next issues.

I prefer to bike outside whenever the weather allows but with December here on the East Coast of the US mother nature doesn’t always allow for that. I’ve been using the CycleOps bike with Zwift just about every day and have started to see random spikes of both power and cadence on my plots. This was unacceptable.

I wish I could put out 1000+ watts

Near the beginning of this project I purchased a Saleae Logic 8 logic analyzer. This has turned out to be one of the most valuable tools in my collection of electronic debugging. For a data packet with no noise we can see that the shortest time for any single bit of data is 1.57ms. Any time a bit is less that this we know that must be an error bit

Data Packet with no errors

Looking for when a high signal is <1ms I was able to find a packet with a glitch.

Data Packet with glitch that was causing spikes

Now that the glitch had been identified I was able to write code to check if a data bit was being read while a glitch occurred. If this happened the code will not update power or torque and will wait for the next data packet. The disadvange of this is that power and torque will be updated at a 2 second interval when a glitch is detected rather than the standard 1 second of each packet. This was a minor price to pay for power and cadence data that is spike free. All of these changes can be found in the GIT Repository CycleOpsPro300PTtoANTPlus

CycleOps Pro300 PT to ANT+, Part 6, Update on V2

I’ve received a few emails asking about buying or for help making the Pro300 PT to ANT+ adapter. I wanted to be as candid as possible and say that I would like to complete the development of version 2 before producing any more PRO300 PT to ANT+ adapters.  The primary reason for the V2 is to address the limited memory that is available in with the ATmega32U4 processor.

As I wrote the current code, The Adafruit_GFX and Adafruit_SSD1306 libraries used to drive the OLED display are rather memory intensive.  I found myself resorting to various tricks to get the memory usage of the ANT+ code as small as possible to get the system to run without issues. This did lead me to the neat F() syntax for storing strings in flash memory rather than RAM.

With a basic working system behind me it’s time to switch processors and compile all of my learning into the V2 board. I chose to stay within the Arduino ecosystem as the abundant well tested libraries makes development far easier than when I use to work in CCS PIC.

Processor comparison

Pro Micro
Program Memory Size (KB)324194 (external flash)
CPU Speed (MIPS/DMIPS)8 (3.3V)80
USB OutputYesNo
Estimated Cost$4.50$2.00

The built in WiFi is a nice addition i’m sure I will find uses for in time. I have plans of turning the Pro300 PT into a smart bike and controlling it via FE-C (Torque control method TBD). I have working software on the V1 prototype board without the OLED display but ran into memory limitation as I tried to pull it all together. Leading down the current path of moving to a new processor.


CycleOps Pro300 PT to ANT+, Part 5, Step by Step

This post will be a tutorial to building your own CycleOps Pro300PT to ANT+ Adapter. For anyone who may have purchased one of these bikes many years ago and finds it sitting unused in a corner this is a great way to bring new life to your bike for use with Zwift, Golden Cheetah or any other ANT+ compatiable application. If you bike looks like the photo below and came with the computer shown below, this tutorial is for you.


The version I have has the power meter welded to the fly wheel so there is no chance to have Cycleops upgrade the hub. If you happen to have the style that is bolted to the flywheel Cycleops will upgrade the Power Cartridge (part 18849) for $339. This wasn’t an option for me as I have the welded style hub shown on the right



This post assumes you have basic electronics knowledge including

  • Soldering of wires, though hole and 0603 SMT components to a PCB,
    • Don’t worry if you don’t, this is a great project to learn on as the PCB has large spacing and is very forgiving in part misplacement.
  • Programming an Arduino, specificly the Pro Micro, see Pro Micro & Fio V3 Hookup Guide
  • Downloading code from GIT hub. All the code, CAD models and PCB files are kept at GitHub Repository
  • Buying parts from electronic suppliers such as Mouser or Digikey

Some great tutorials can be found on Sparkfun and Adafruit

Parts Required:

  1. The complete parts list in excel format can be downloaded from the GIT here
  2. Molex Micro-Fit, 4 Circuit Plug Housing and Pins
  3. A few of the parts are non-standard mouser parts and needed to be ordered from ebay or AliExpress these included
    1. PCB – I am happy to sell some of my extra, just put a comment below otherwise the gerbers are in the GIT if you want to make your own boards
    2. NRF24AP2 Networking Module
    3. Arduino Pro Micro, Ebay or Sparkfun. Just make sure to get the 3.3V version
    4. Project Box, Ebay
    5. Optional OLED Display, Ebay
    6. Battery 14500 3.7V Li-Ion, DX or Amazon or Ebay

Full Parts List for those who just want to skim without going to GIT

PartValuePackageDescriptionMouser Part #LinkDevice
D2BAT20JSOD-323Schottky diodes in SFE's production catalog511-BAT20JFILMDIODE-SCHOTTKY-BAT20J
U$1NRF24AP2NRF24AP2 Networking ModuleAliexpressF10644_NRF24AP2
U$253375-0253375-022.50MM 2P VERT HDR FRCTN POS LOCK538-53375-021053375-02
U1MCP73831SOT23-5Charge management controller579-MCP73831T-2ACIOTMCP73831
U2AP2112K-3.3VSOT23-5Voltage Regulator LDO621-AP2112K-3.3TRG1V_REG_LDOSMD
U4TPS78223DDCRSOT95P280X110-5NLow-Dropout Linear Regulator595-TPS78223DDCRTPS78223DDCR
Power SwitchMountain SwitchToggle Switches SPST OFF-ON108-0041-EVX108-0041-EVX
Reset SwitchMountain SwitchPushbutton Switches METAL BODY BLK103-1013-EVX103-1013-EVX
Project CaseEbay
OLED DisplayEbay

Build Instructions

Prepare the Cable

I’m going to take a guess and say for most people this is going to be the most challenging part. I happen to have access to the proper crimping tools for working with Molex Micro-Fit connectors at work. For most, you have 2 options. Cut the existing cable off of your wired head unit or using some needle noise pliers and solder do your best to crimp the pin onto the wire. I have used the pliers and solder method for years for a non-mission critical application like this you should have no problem. I’m following the pin out numbering as used on the Molex Data Sheet for the 43020-0400 4 pin housing, The image below shows a 6 pin, we will be using a 4 pin version. Don’t forget the heat shrink to keep things nice and clean. The completed end can be see below.

Going off of the colors of the wire I happened to have on hand, wire the connector as follows, substitute the color of wires you use as needed. I didn’t find any function for Pin 3 but I happened to be using 4 wire cable so just to keep things clean I connected all of the pins. You will see later that this pin goes to nothing at the PCB side. I am also using shielded cable. This is not necessary at all but at the PCB side I was able to solder the shield directly to the PCB to help strain relief the data, ground and signal solder joints

PinFunctionWire Color

Solder the Components

Time to take out the solder iron and the 0.015″ solder or some solder paste and hot air and get soldering. The layout of the board puts almost all of the SMT components on the back with lots of room to place them with tweezers and even enough room to hand solder them if you prefer. I suggest doing all of the SMT parts first then the through hole components after the SMT. Last solder all of the wired switches and data line. I put a small rubber grommet on the line that runs to the bike to give it a clean look when going though the project box. You will notice a large pad just above the connection for the bike data line. I used a cable with a shield. While this pad is grounded and in theory will help protect the wires from radiated noise my primary reason was to use the shield as strain relief for the wire. I didn’t want to solder directly to the OLED display to make it easy to remove the cover and work on the PCB if needed at a later date. I found a Molex 0022162040 to be the perfect fit to the pins and provide a nice right angle pin to solder the wire to. You can see this is the right most picture, its the white connector.

Prepare the Case

The case I used for this project was brought on Ebay Electronic Project Box/ Enclosure Instrument Case. A few modification are required for mounting of the power switch, 0 force calibration button, OLED display and access to the USB port. The location and size of the holes can be taken from the CAD model. The OLED Display was attached to the cover with hot glue at the 4 corners. I then put a sheet of transparency plastic on the outside to protect the OLED from the inevitable sweat that will fall on on it. I held the plastic on with some scotch magic tape but really need to find a cleaner solution.



Program Arduino

Before programming there is 1 small modification that must be done to the code. I am not able to provide the ANT+ network key with the code. Getting one is free and quick

  1. Go to //
  2. Create a free account
  3. After you are logged in go to //
  4. Find the line “The ANT+ network key is 8 hex values and is shown below”
  5. You will find 8 hexadecimal number, put each of the 8 numbers in order into this section of the code, replacing the XX with the numbers you are provided

Follow the instruction on Sparkfun for setting up the Arudino Pro Micro Pro Micro & Fio V3 Hookup Guide

Upload the code and apply power, on power up, even if not connected to the bike the OLED display should look like this.


By default I have the following items displayed on the screen

  • O: Torque (counts)
  • R: Torque (counts)
  • T: Torque (in-lbs)
  • P: Power (Watts)
  • C: Cadence (RPM)

Selling extra parts

If anyone is interested I have 4 extra PCBs that I would like to sell for $5 each, PayPal would be ideal method of payment. If interested let me know in the comments below and I’ll get in touch.

CycleOps Pro300 PT to ANT+, Part 4, Hand Wired Prototype

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.

  1. Li-Ion Battery Powered
  2. Built in USB Charger for the battery
  3. Built in display to show, Torque, Power, Cadence and some parameters for debugging such as the current torque zero offset
  4. 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 $5 a board.

For anything following along at home the ANT+ module I had previously used doesn’t look to be available anymore. I found one that looks to be exactly the same NRF24AP2 Networking Module / 8-channel / Serial Interface / ANT Networking. This will be the unit I test with the PCB version of the design.

Photos of of the hand wired box and the PCB layout

CycleOps Pro300 PT to ANT+, Part 3, Calibrating

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 diagtorque-test-menunostics 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.

  1. Read the zero value
  2. Hang 15lbs, record the value
  3. Hang another 15lbs, record the value
  4. 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

Applied Weight
Raw CountsDelta CountsTorque on Rear Wheel (in*lb)Cal Ratio

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;

Wheel Speed

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

WHEEL_SLOPE = 527.2213800;
WHEEL_OFFSET = -3181.8199961;
wheel_period = wheel_speed*WHEEL_SLOPE+WHEEL_OFFSET;

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.

CycleOps Pro300 PT to ANT+, Part 2, Details

Part 1, Identify the signals

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

PinFunctionWire Color

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.

Logic for 0 Speed.jpg
A single packet of data

A packet would be sent every 1.043secPacket Repeat.jpg

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.

  1. 4 bits of data in 7.249ms = 1.812ms per bit (The code uses 1.770)
  2. 6 bits of 0s in 9.152ms = 1.525ms per bit (The code uses 1.545)
  3. Repeat

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.

Copyright blinkyme 2023
Tech Nerd theme designed by Siteturner