The idea for this bit of wearable tech was born out of a climb-a-thon that I do annually to benefit First Descents, an organization that gets young cancer survivors like myself living a life beyond cancer.
For the climb-a-thon, participants usually set a goal and climb to that goal to help raise donations. Many set that goal as their distance climbed, either setting a goal of say 500 ft or climbing a foot for every $1-5 raised.
I thought for the next climb-a-thon it would be great to have a piece of wearable tech to track the amount I climbed, like a fitness tracker. I would then be able to make another one as an incentive for others on my team to help spur donations.
From that I developed Climbit, an altimeter-enabled fitness tracker that tracks the amount you climb!
Step 1: Materials
For the Climbit build, I utilized the following components:
- Adafruit Pro Trinket - 3V ($10)
- OLED Display ($17.50)
- BMP280 Sensor ($10)
- LiPo Battery ($6)
- LiPo Backpack ($5)
- Tactile Switch ($0.25)
- Slide Switch ($0.60)
- A few 26-30 AWG wires ($0.15)
- Carabiner ($0.50)
- M2 screws ($0.25)
- Pulldown Resistor (51k, not pictured but needed, $0.01)
In total the build came to about $50 before 3D printing. I also looked into LED displays instead, but the form factor was too large and it didn't save that much money. There is also the SparkFun Micro OLED breakout, which looks like a nice little OLED breakout but logistically it was cheaper to stick with the display listed.
Step 2: Tools & Software
Step 3: Component Design
Since I wanted this item to be clipped to a standard climbing harness or belt loop without being obtrusive I tried to get the smallest components that would work together without too many wires requiring a larger enclosure. Initially I was hoping to use the standard Trinket for this, however the buffering of the OLED display required a microcontroller with a bit more RAM.
I2C was a simple choice for reducing the number of wires though. Even though now, with using the Trinket Pro, there were enough pins to use SPI I wanted to minimize the number of wires inside the enclosure. I didn't see much of an issue with this since we don't need a quick refresh rate.
Ultimately, the idea is that the climber would turn the Climbit on or reset the counter (using a 5 second long hard press) before climbing. Then, after that, they would be able to press the button to see how far they have climbed (quick press <5 seconds) and to check the temperature.
When the battery gets low the climber would be able to just plug it into a microUSB plug and charge it without opening the enclosure.
Step 4: 3D Design
Using a combination of PCB layouts and actual measurements from the components using my digital calipers I made a three-part enclosure to house the electronics and the carabiner. I was looking for the smallest form factor possible, so measurements needed to be pretty exact.
Using the free software at Autodesk's 123D Design, I made digital mock-ups of the electronic components then I designed the printed parts. The enclosure has a top and bottom that fits around the carabiner with a central piece to hold the battery and provide a surface to mount the button.
As an aside, the battery holder needs to be modified slightly. I may fix this in a future release, but for now you'll have to cut the holder in half and countersink the screw holes by ⅛" (3mm). This is so that the listed screws fit the entire length and that the holder doesn't trigger the reset button on the Trinket.
Step 5: Wiring
Using I2C really simplified my wiring schema. Like I said in the Component Design section, SPI could be used but I don't see much of a point.
I first prototyped everything out on a breadboard with an Arduino Uno R3 (see Component Design step). It was this way that I was able to use the Serial output to better model the algorithm. In conjunction with that, I just followed the wiring diagrams and pinouts in the Trinket Pro, OLED Display, LiPoly Backpack, and BMP280 Sensor tutorials. To simplify things, the pinouts are as follows:
- Trinket Pro A4 (SDA) to Sensor SDI and Display SDA
- Trinket Pro A5 (SCL) to Sensor SCK and Display SCL
- Trinket Pro A2 to Display RST
- Trinket Pro A1 to Button pin1 (a pulldown resistor will also connect to this pin)
- Trinket Pro 3V to Button pin3, Sensor VIN and Display VIN
- Trinket Pro GND (G, same as BATT G) to Sensor GND, Display GND, Pulldown Resistor on Button pin1/A1,and Backpack G
- Trinket Pro BAT+ to Backpack BAT
- Trinket Pro BUS to Backpack 5V
I also had to take some liberties with the components in Fritzing, since the latest Adafruit library doesn't have Fritzing components for all of the physical components and I didn't want to take the time to make all of them. The Fritzing diagram is just for illustration anyway, so no big deal, but you will notice that the image doesn't align with the actual parts in all regards but it comes very.
Step 6: The Algorithm
I broke the algorithm for this out from the code section because it warranted its own explanation.
In a perfect world with perfect sensors and perfect weather (Ideal Gas Law anyone?) we wouldn't need a special algorithm to handle the data from the sensor. Ideally we would just add up all the positive changes in altitude (distance climbed vertically) and that would be it. Since we're not that lucky, we need an a way to smooth out the data coming from the sensor to get an accurate estimation of the distance climbed.
This is illustrated in the attached graphs of the test data I produced in R v3.1.1 to model the algorithm. This is very simplistic, but it illustrates what we will encounter with real world data without being too simplistic.
- The first graph (black line) is the actual "perfect world" data with the actual total vertical distance climbed.
- The second graph (red line) is the simulated sensor data with the sum of the positive values as the vertical distance climbed.
- The third graph (green line) is the smoothed simulated sensor data with its sum of the positive values as the total vertical distance climbed.
- The fourth graph is a plot of all the lines on the same graph.
The second image is a larger version of the plot of all the lines on the same graph. In this one you can see the noise in the simulated sensor data (red line) and how the algorithm smooths it out (green line, take a look at the corners).
As you can see, just taking the positive changes in altitude from the sensor data as we would do in a perfect world ends up skewing the total distance climbed in the positive direction. This error is iterative with every sensor reading so over time the amount the total distance is skewed by will grow. Smoothing the data by taking an average of the past 10 readings avoids this.
Smoothing the data in this fashion comes with its own limitations, but it's better than not smoothing it. Here we can see it rounds off the corners, subtracting distance at ever acute angle and adding distance at every obtuse angle. Of course better algorithms will model the actual distance climbed better, however I'm not looking to get that complicated.
The third image is an actual plot of the altitude readings coming from the sensor after smoothing! In fact, I even added an extra layer of smoothing (three consecutive reads every 150ms averaged) to help tame the sensor data. You can see there is still a bunch of noise (blue line) however the total distance climbed (red line) is still at zero. This is due to another aspect of the code I inserted to control what I called the standing creep of accumulated noise at the base altitude.
Step 7: Code
Our application of the sensor data in one aspect makes handling the data somewhat easier and in another aspect complicates it. It's easier because we don't need to calibrate the sensor, therefore we can use a standard reference setting and just measure the difference. It's more complicated though because we want total amount climbed, not just a simple altitude reading.
In order to get the total amount climbed we want the program to just add up the positive change in altitude from the algorithm in the previous step. This was accomplished by setting the initial reset point as the lowest altitude, checking any positive change in increments, then adding that change. The timing is arbitrary, faster time increments add noise while slower time increments may not accrue the full distance before descending. If the climber goes below the aforementioned lowest altitude the program resets it.
As an added bonus the climber can also get the temperature. Why? Because the sensor also has the capability of sensing temperature so, why not!
Download the .ino file at Github!
Step 8: Assembly
I tried to include as many pictures as reasonable feasible during the assembly. In reality the process is more intuitive and a bit like Tetris, which doesn't really lend itself to concise step by step instructions. I will say, however, that the process involved repeated steps that I will sum up here:
- Wires were initially cut long, sized up several times, then cut to size right before soldering
- All leads were stripped to within ⅛" (3mm) from the end
- Whenever possible, I soldered with the components laying flat on a piece of aluminum foil so that all solder points were flush with the bottom of the component (see the picture with the sensor)
- Pieces where constantly removed and replaced while assembling, so don't over-tighten the screws
- Actually, never over-tighten the screws
A general overview of the steps are as follows:
- Clip the JST connector from the backpack at each side of the base.
- Solder all wires onto the sensor and display, follow the pin diagrams in the Wiring section of this instructable.
- Clip one leg of your pulldown resistor to about ½" (13mm) and solder it and a wire to Trinket A1.
- Test fit the Trinket and sensor into the bottom of the enclosure, trim the sensor wires to length.
- Test fit the battery holder and LiPoly backpack on top of the Trinket and sensor, trim battery leads to length separately, do not let the leads touch during or after cutting.
- Remove the battery and holder, making sure the leads remain separated (tape them off).
- Test fit the display and button in the front of the enclosure, trim display wires to length.
- Solder the free end of the wire at A1 to pin1 of the button (you will have to flatten the button pins).
- Solder a wire to pin3 of the button.
- Solder the wire at pin3 of the button, display Vin and Sensor Vin to the Trinket 3V (next to BUS).
- Position the LiPoly backpack on the Trinket, flattening out the wires going to 3V, you will probably have to take the Trinket out of the enclosure for the next steps.
- Trim all ground wires and the remaining resistor pole to length then allow ½" (13mm), wrap the exposed portion of resistor in shrink wrap tubing.
- In through the G pins of both the backpack and Trinket put the resistor pole, Display GND, and Sensor GND.
- Solder the G pins to the pole/wires placed through the Trinket and backpack.
- Solder a short length of wire between the BAT+ pin on the Trinket and the BAT pin on the backpack.
- Solder a short length of wire between the BUS pin on the Trinket and the 5V pin on the backpack.
- Solder the wire from the display RST pin to the Trinket A2 pin.
- Trim and solder the wires from the display SDA and the sensor SDI to the Trinket A4 pin.
- Trim and solder the wires from the display SCL and the sensor SCK to the Trinket A5 pin.
- Break the trace on the backpack as outlined in the Adafruit tutorial and solder wires connecting the slide switch as instructed, ensuring the are just long enough to fit the switch in it's socket (see pictures).
- Carefully solder each battery lead to the corresponding pad from the JST connector on the backpack (see the pictures, make sure the switch is off).
- Screw the Trinket into the bottom of the enclosure.
- Screw the sensor and battery holder (with battery inside) into the bottom of the enclosure by placing the screws through the holder and the sensor screw holes.
- Screw the display into the top of the enclosure and place the momentary button into the top of the enclosure.
- Make sure all wires fit within the enclosure and screw the top and bottom together with the carabiner inside the channel.
Step 9: Finished
The Climbit is now ready for prime time!
The build was a relative success and it even saw some time on the wall for it's first round of ascensions. Both me and my belay partner were able to get precise readings with it however it's difficult to determine how accurate they were without plotting the data and matching it up with a video (acquiring our own "beta" in climbing lingo). This is because any vertical ascension is summed up into the total, since we did the same route it's reasonable that we should have similar (hence precise) results, but we can't use the height of the wall to determine how accurate it is. I'm thinking of making a prototyping backpack (the literal backpack, not a microcontroller backpack!) so that I can incorporate a datalogger to visualize the sensor data on an actual climb, but that's in the future.
Step 10: Limitations
The current version has some limitations that I feel I would be remiss for not saying:
- Current sensor/code combinations only allow for vertical calculations, lateral traverses won't be included.
- Inclines will only be totaled for their vertical component.
- The smoothing algorithm still accommodates too much noise (in my opinion) because it is constantly using the previously acquired altitude value. This can be mitigated by instead using the "base" value as the comparator, however additional algorithms would need to be incorporated to account for intermittent vertical ascensions (see image).
- The current enclosure probably needs another screw and perhaps some rubberization to be sturdy for prolonged climbs.
Maybe I'll address some of these limitations in a version 2 if there is enough interest, it would take adding another component however, which would increase the price and complicate the build. Not that adding a magnetometer and a little Cartesian geometry wouldn't be prohibitively expensive or hard, I'm more concerned about fitting everything into the enclosure.
Second Prize in the
Arduino All The Things! Contest
SepeSy made it!