A few months ago, I decided that I wanted to do some cycle training indoors over the winter. Riding outdoors in Seattle from November through April is pretty lousy; the streets are wet and slick, and it takes a lot of motivation to overcome the desire to stay warm and dry indoors. It has been a few years since I've ridden much, as well, so the winter season is a good time to get into shape before next year. It's also a lot more convenient to ride at home. I have a great gym membership, and they've got nice facilities, but it's at least another hour on top of the workout to get in the car, go there, park, change, get in, shower, get out, go home, etc.
The solution to these motivation and time problems was to pick up a fluid resistance trainer unit. There are several kind of bike trainers on the market: fan based units, magnetic resistance units, rollers, and fluid trainers. Rollers are great for balance (and mine sucks), but for good strength training and a realistic power curve, you have to use a resistance trainer. Only a fluid trainer can accurately simulate the feel and power curve of riding on the road. Sorry, but the cheaper magnetic units just can't compete. Although you can certainly get a good workout on a magnetic unit, you're fooling yourself if you think it's anything to the same “feel” as a fluid drive.
That left two choices in the "not ungodly expensive" category – either CycleOps or Kinetic by Kurt. They're both excellent units, or so I'm told, but the Kurt unit uses a completely sealed fluid unit, meaning that there's no way that it's ever going to leak out. I've got a few pieces of scuba gear with fluid shaft seals, like my Cuda DPV, and I know what the weaknesses of that kind of a system are, and I don't want those weaknesses on my living room carpet.
Off I went to REI and, thanks to last year's dividend check and a super sale, I got a great price on a Kurt Kinetic Road Machine trainer. It's ugly green, but it is smooth, sets up quickly, and runs well. My bike came back from the shop, where it was getting a new dérailleur bracket put on. I hooked the whole thing up and went for a quick ride. It was great...with just one major issue: I had no idea how fast I was riding, how much energy I was expending, or how far I'd ridden.
The trainer manufacturer makes a small computer for the trainer that tells you some of that information, but all the internet reviews for it were basically horror stories of poor quality, inferior engineering, multiple unit replacements, etc. It seems Kurt invested his R&D dollars in making a fantastic trainer, and flubbed up big time on the bike computer.
They make some really kick-butt high end trainer computers, too, but they start near to $1000...that's $1000 I could better spend on diving, vacations, my next Antarctic trip, a CNC machine shop, etc. Plus, that's just a bit outrageous, and it should be less than a tenth of that to put something together myself...or maybe even 5% of that.
No worries...I'm an engineer, and this is a trivial engineering exercise to remedy.
I can build my own bike trainer and have it do anything I want!
In this case, the trainer has three major parts:
The sensors – there's two reed switches mounted on the bike, along with magnets, in order to pick up rear wheel speed and crank cadence. There's also a temperature sensor to measure the surface temperature of the fluid drive unit (there was an extra input on the microcontroller hooked up to the A-D). Finally, I found a source for the Polar heart rate monitor receivers, so the unit also senses wireless heartbeat rates from the coded Polar transmitters.
The interface board – there's two PIC microcontrollers which read all the sensors, along with associated glue logic to tie them together. The sensor block connects up via USB to the PC.
A Java app, using Swing, which reads the sensor block, interprets the data, graphs it, maintains rider profiles, and can save the data off to be used in Excel.
The most important sensors here are the magnetic pickups on the bike itself. I bought a couple of reed switches, which are just glass encased metal strips that close, or complete the switch, when put into the presence of a magnetic field. There are two problems with reed switches: first, they're fragile – they are glass, after all. Second, they “bounce”, but so do all switches. Even those switches that turn on your house light bounce when you flip the switch on. If you're just turning a light on or off, it's not an issue, but if you're reading the switch with a microprocessor, you have to make sure that on is really “on” and not “on-off” four or five times before it really settles to on.
The bounce problem is easily solved in either hardware or software. In software, you simply read the switch multiple times over a period of time, and if it's on for a few consecutive periods, it's settled and can be considered on.
You can also debounce in hardware, by using a resistor and capacitor, and taking advantage of the time it takes to charge up a capacitor through a resistor (remember high school physics?) Buffer that with an inverter gate, and you have a clean switch at the expense of some hardware components. More about that in the hardware section below...
To address the fragility issue, my initial plan was to encase the switches in rubber; Tool-Dip is a great substance, which you can get at Home Depot. I was going to add wire leads, a piece of plastic to give a flat edge, and coat the whole thing in rubber. It'd be waterproof, and relatively impact resistant.
However, I was at Performance Bike to pick up some spoke and crank magnets and they had these: http://www.performancebike.com/shop/profile.cfm?SKU=25933&subcategory_ID=4111
It's an E3 F11C spare mounting kit for their computers. It was $8 on sale, which was about the same price for “replacement magnet” kits, and, best of all, it had two magnets included! I brought it home cut off the dinky mounting piece for their computer to get at the bare wires, and discovered to my delight that the pickups were just basic reed switches in nice looking plastic housings. It looks a lot better.
So, I soldered a 1/8” stereo miniature jack to the end of the cable, attached the sensors to the bike, and the physical interface was good to go. I ran the wire to the front and terminated it on the front, just below the handlebars, in case I ever decided to make a portable computer...there's some really cool OLED displays out there, and it may be fun to do this as a portable project with the same sensor block.
Illustration 1: Bike closeup showing sensor mounting
Illustration 2: Sensor connector
As I was putting this all together, I decided that I would just use an extra analog channel of the microcontroller's input to read the temperature of the resistance unit.
For the temperature pickup, there are a lot of good, inexpensive temperature sensing chips out there. I had an LM235 lying around, and, since I don't need a lot of accuracy, it is a very inexpensive and good choice. It comes packaged in a TO-92 case, which is small, and conveniently, has a flat side. The output is easy to read – feed it five volts in, and you get 10 mV / K, or, at room temperature, 2.98 Volts (25 C, or 298 Kelvin.) Since the microcontroller used here only has an 8 bit data port, we only get an accuracy of around 2 degrees C, or about 4 degrees F.
To connect up the temp sensor without drilling, a trip to the dollar store was in order, and soon enough, I had a very tiny, spring loaded “A” clamp in my hands. Inside the clamp jaws, I placed the sensor so that its flat side faced the inside (and would clamp flat against whatever I attached the A-clamp to). I ran the wire out the handle of the clamp, and wrapped the whole thing in heat shrink tubing. It's not the best thermally conductive material, but we have plenty of time for the sensor to warm up.
The whole assembly just clips on the heat dissipation fins of the fluid unit and plugs into the hardware with another miniature headphone jack. This time, I used 3/32” mono, just to prevent mis-configuring things.
Illustration 3: Temperature sensor clipped onto heat fins
Finally, one important part of this whole project was to read the Polar heart rate band that I own. I picked up the chip here: http://www.sparkfun.com/commerce/product_info.php?products_id=8660 It's cheap.
This is where it gets interesting...now that I had the sensors, I had to actually make use of them.
The hardware consists of two blocks: the computer I/O (which also reads heartrate and temperature) and the bike interface, which reads the two sensors from the wheel and crank, flashes the appropriate LEDs to indicate status, and is effectively slaved to the computer I/O board.
I lied a little when I said I bought just the Polar chip. If I had to do this over, I would've. Instead, I picked up the dev board with it, from Dan Julio Designs, also available from SparkFun, but that's because I wanted it NOW and the bare chip was backordered. I also didn't know that the chip was so trivial to interface with, or that stock was replenished fairly quickly. It's not that the Dan Julio board has anything wrong with it. Rather, it's just limiting in how it interfaces, and my additional hardware that does all the computations is really underutilized. The Dan Julio board is also very expensive for what it actually contains.
Here's the basic block diagram:
The bike interface interface is very straightforward. I used a PIC 16F73 because my junk drawer had a stash of them. It's crazy overkill for this project, but it's what was on hand. Because the chip is so over powered, I wrote the firmware for it in C, using the Hi-Tec free PIC compiler. The free version of the compiler generates hideous assembly code – they pad it out unnecessarily to encourage you to buy the paid version. It's bad. Seriously bad. We're talking "jump two instructions forward, jump one instruction back, jump two forward and continue" type stuff. On the other hand, the stats indicate that I'm only using 34% of the chip's capacity, so who cares? It's a bit faster to develop in C than assembly, but PIC assembly is so very, very simple that it's almost always worth it to go the assembly route. Oh, well...next time.
The PIC runs at 8 MHz. It makes it easy to get millisecond pulses and simplifies the math, and gives us plenty of spare cycles during the timing loops. Most of the time is spent idly waiting for interrupts from the switches, or for the timer to overflow. The basic algorithm is to start a counter that fires an interrupt every millisecond. When it fires, add one ms to the value for each of the cadence and wheel counters. When the sensor is tripped for cadence or for wheel, record off the value and reset the counter. Later on, when you know the time it takes to make a complete revolution, and you couple that with the wheel size, you know how fast you're moving...
Here's where the limitation of the Dan Julio board starts to come into play... for hardware communications, there's five digital pins available for reading and writing, and four analog channels for reading only... and I need to read out 16 bit values from an 8 bit microcontroller.
The obvious solution is read out by nibble. What's a nibble? Half of a byte, or four bits. Got to love compsci humor, eh?
So, the PIC is wired to the Dan Julio board (also controlled by a PIC) with four data bits on D0-D3. The fifth I/O digital bit is used as a clock to sequence my PIC nibble by nibble...there are eight (two 16 bit values = 32 bits / 4 bits per nibble = 8 nibbles.)
How do you know where the sequence is and where it begins? You need a mark bit... you can do this algorithmically in software, but the easiest thing to do was just use a separate I/O pin from the PIC which is pulled high at the very beginning of the sequence for the first nibble. It's hooked to the ANALOG input pin A0 on the Don Julio board.
What? Right...when it's high, it's sitting at five volts, so we read a value of “255” when we do an “A0” of the analog pin. If it's low, we read 0. We don't care about anything in between. To make it a little more realistic, I use a slightly lower/higher value than 255 or 0 to simulate some hysteresis in the circuit.
The PIC drives 3 LEDs – a blue, red, and green one. The blue one blinks once when the cadence sensor is triggered, the red blinks for the wheel, and the green blinks at a 10% duty cycle, once per second, just to let me know that the chip is still alive.
Switch debouncing is done in hardware with a schmitt trigger hex inverter (74HC14). Here's the bike side circuit:
Illustration 4: Bike Board Hardware - power, ground, reset, and xtal oscillator omitted for clarity
The rest of the hardware is simply the Dan Julio board wired as indicated above. The temperature sensor is soldered to analog input A3, although physically, it connects to the bike board (because of packaging concerns.)
Illustration 5: Breadboarded circuit, with Dan Julio board wired in. Note blue reed switches to the left of the Dan Julio board, which made testing easier than spinning the bike wheel.
Here's some pics of the breadboard with the project under development, along with some completed shots. One of these days, I'll sit down on the mill and mill out a plastic case that fits a little better.
Illustration 6: Real hardware populated and waiting to be joined to the Dan Julio board
Illustration 7: Hardware packaged in a small scrap plastic box
As previously mentioned, I did up the 16F73 firmware in C. This was pure laziness on my part, and was just taking complete advantage of the huge chip I used to do all the work... but it works.
The firmware is quite simple. There's an ISR that handles four interrupts:
Wheel sensor going high
Cadence sensor going high
Data clock from the master Dan Julio board
Timer overflow once per millisecond
The ISR figures out which one has fired and does the appropriate action.
Every time the clock overflows on the timer, the value is reset to a new count that will give exactly 1ms before the next overflow. Also, the “tick count” for the wheel sensor is updated. The cadence sensor actually reads in centiseconds, so every tenth pulse updates the cadence sensor tick count, as well. Occasionally, the LED for the heartbeat is blinked, too.
When a wheel or cadence sensor fires, the appropriate reading is entered into an array of the last eight readings for that sensor. The oldest reading is discarded. The appropriate LED is blinked for the right period of time.
When the Dan Julio board data clock fires, the nibble index is updated by one, and the new nibble is loaded onto the output port pins.
It's pretty simple, really. The readings are double buffered internally, since there's sixteen bit math on an eight bit chip with unpredictable interrupts. If you don't take precautions, it's possible you'll have high and low bytes messed up in your numbers, since the moves are not atomic. Careful buffering ensures that you will always get a consistent reading for a value, although it may not be the “latest” reading (but it won't be very old...) The reading value is locked when one of its nibbles is on the output port, and only updates when the other sensor's nibbles are being read.
The array of 8 values is averaged (with zeros discarded) for the actual value of the output. This helps to smooth things a little bit, or, in DSP terms, is a very cheap finite impulse response filter on the real time sensor data!
The Dan Julio board actually has a nice interface to the computer... it's just a USB serial port, and very simple ASCII commands are sent to it. The basics commands are:
I/O mask (whether the digital pins are input or output. D0-3 are “in” and D4 is “out”)
Read input from digital pins
Place output onto digital pins
Read analog value
Read heartbeat
There are some setup, mode, and other fancy commands, which are used for initialization, but they're really not that interesting.
In any case, these are the tasks that the computer executes to get all the values:
Returned values are as follows:
Wheel – time in milliseconds for a revolution
Cadence – time in centiseconds for a revolution
Temperature – 8 bit value indicating voltage from 0-5V; voltage is temperature in Kelvin
Heartrate – Beats Per Minute with a set of status flags indicating the quality of the data stream
I would be remiss not to mention the excellent PIC IDE simulator here: http://www.oshonsoft.com/pic.html This IDE provides a great visual representation of the PIC code, runs pretty fast, and saved me a LOT of firmware debugging time. Being able to set breakpoints and watch registers is much more efficient than probing the breadboard all afternoon long.
The initial test of the hardware was done with a Perl script that did all the flow chart steps above, worked through the math, and spit out the derived values to the console until it was interrupted. Since the device is just a USB serial port, it's very easy to just open /dev/ttyUSB0, read from it like a file, use Perl's excellent string parsing, and spit out the values with a print statement. I wanted more, though. I'm greedy.
Specifically, I wanted to watch TV on the plasma in the living room, with the picture-in-picture displaying my bike ride stats. That called for a GUI, as command line text is just not easily read when compressed that small.
Since most of what I'm doing these days is under Linux, in Java, using Eclipse as an IDE was the logical choice for development. I'm sure that I could've banged this out in C# in half the time, given that I'm much more familiar with it, but this was a good excuse to delve into Swing. Eclipse isn't half bad...I'd like it even better if I didn't still get the MS Alumni discount on Visual Studio...
There's really three main pieces to the software – the app itself, derived from a JFrame, the charting library, which is JChart2D, and javax.comm, which is downloadable from Sun directly, and is buggy as sin (let's just say “timing errors suck.”) Opening the serial port works almost all the time, but subequent calls fail quite often when setting flow control or baud rate, and the error is "port not open," only the open call succeeded and the port looks good. Way to go, Sun...now, about your stock price...
Put together, here's a screenshot. Yeah, it's ugly, but it's functional... why waste time on “skinning” something when you can do real work?
The screen contains four real-time graphs: Speed, cadence, temperature, and heart-rate, along with the real-time numeric data from the sensor array. At the bottom is the profile of the loaded user with a brief history of rides and some overall stats.
The software itself is actually capable of supporting a variety of wheel sizes and trainers – Kurt Kinetic is really good about publishing the equations and constants that convert speed to watts for not only their own trainers, but for other manufacturers' trainers as well. Once you have watts, the rest of it is pretty straightfoward.
One item of note is the yellow heartrate in the real time data...that indicates the heart rate data's not valid. I'm sitting here writing, not cycling, so I don't have the band on. It changes color when the data's good.
The software will do both English and metric measurements, and knows about all the common wheel sizes, both in the US and abroad.
Finally, the software can export your ride as a comma-separated value file, which can be loaded into Excel or other spreadsheet programs so that you can have second-by-second analysis of your ride, if you really care.
It's time to put the computer up and start riding for the winter! Eventually, I'll go back and make some tweaks...for instance, I'd like to do training zone indication for the heart rate, or set up the trainer to understand power sprints. On the other hand, it's good enough, and it's definitely going to give me some feedback this winter during the living room rides.
Copyright 2008 Cameron Etezadi