So here's a summary of the project:
The problem as we see it: A significant number of avid cyclists and would be cyclists don't ride as often as they might like, particularly in a 'utility' capacity, running errands, commuting, etc. Contributing factors include age, physical conditioning, persistent injuries, hilly terrain, and the distance or loads involved. This is unfortunate since this is the type of cycling that would have the most direct positive impact on congestion and fossil fuel consumption.
Power assisted bikes and kits have been on the market for a while now, and would appear to be a good solution to these problems except for one thing: they don't hold much appeal to experienced cyclists. Our research indicated several reasons for this, including bike quality, complexity, efficiency, ego, and the type of bike or bike fit.
Our Solution: We wanted to design a power assist that would appeal to a potential utility cyclist, which as we saw it, was the group most likely to adopt the behavior patterns mentioned above. To do this, we recognized that this group has an established mental model of what the cycling experience is, and we needed to develop a solution that functions within that framework.
We quickly realized that the best way to accomplish this was to keep the power assist as unobtrusive as possible so the interaction continued to be a 'cycling experience' and not a 'power assist experience'. This is were the power of the Arduino comes into play, allowing us to use an array of sensors to monitor ride conditions rather than requiring input from the rider via a throttle or switches. Since the rider no longer has to worry about monitoring and integrating the power assist with their ride, they are free to appreciate the cycling experience - without worrying about the big hill ahead!
Implementation: The only direct interface the cyclist needs to worry about is a set and forget dial used to set the point where the rider would like to start getting some help from Tailwind. Once this is set, Tailwind uses an array of sensors and micro-controllers to monitor and adapt to what the cyclist is experiencing. Idealy, the cyclist would not even be aware of the system engaging and would always feel as though they were ridding unencumbered.
Our algorithm begins by ensuring that the brake lever is not engaged using a momentary switch. If the brake is engaged, the boost is immediately turned off. The sketch then checks the rider position with a pressure sensor in the saddle, reducing the crank RPM if the rider is out of the saddle. The force exerted on the pedals is constantly monitored and sent to the central controller by a wireless data link.
Once the pedal forces are read, the main controller has to decide what to do with the data. 1) The pedal force is assessed to determine if it exceeds the value set by the rider. 2) If the force of either pedal exceeds the threshold value, the force on both pedals is then compared. Approximately equal readings would indicate the rider is 'jockeying' (standing on both pedals but coasting or attempting to balance) and the assist would be disengaged. 3) The amount of effort the rider is exerting on the pedals and the riders position on the saddle are then used to determine the duty cycle of the Pulse Width Modulation (PWM) sent to the motor.
Ideally, the assist provided to the crankset would reduce the force the rider exerts on the pedals and a state of equilibrium is reached between the rider and the assist. If the cyclist wishes to increase the cadence (crank rpm) a momentary increase in effort would cause the amount of boost to increase until equilibrium is again established. If the system is well set up, the rider would not be aware of these changes, focusing only on the ride.
Our Code: This is the code for the main controller using the VirtualWire library. While it compiles, we are not yet getting good data from the pedals and are still attempting to isolate the cause. The serial.print statements are included for debugging purposes.
#include <VirtualWire.h>
//Declair I/O Pins
const int assist_pin = A0;//threshhold
const int boost_pin = 9; //pwm out to mofset
const int pedalLeft_pin = 11;//left FSR via virtual_wire
const int pedalRight_pin = 12;//right FSR via virtual_wire
const int saddle_pin = A1;//saddle FSR
const int brake_pin = 8;//NC micro-switch on rear brake lever
const int transmit_en_pin = 3;
//Declair variables
int assist = 0; //amoount of desired assistance
int boost = 0; //PWM duty cycle 0-255
int pedalLeft = 0; //FSR value from left pedal
int pedalRight = 0; //FSR value from right pedal
int saddle = 0; //FSR value from saddle
int saddleOut = 0; //Threshold value for determing out of saddle
int standing = 30; //if the difference in pedal readings is below this value, assume the rider is jockeying
void setup() {
delay(500);
Serial.begin(9600); // Debugging only
Serial.println("setup");
vw_set_tx_pin( pedalRight_pin);
vw_set_rx_pin(pedalLeft_pin);
vw_set_ptt_pin(transmit_en_pin);
vw_set_ptt_inverted(true); // Required for DR3100
vw_setup(2000); // Bits per sec
vw_rx_start(); // Start the receiver PLL running
pinMode(boost_pin, OUTPUT); // set the boost_pin as output:
}
void loop() {
delay(2000);
int saddleFactor = 100; //default percentage for saddle corection
assist = analogRead(assist_pin); //read assist level
map(assist, 0, 1023, 0, 255);
saddle = analogRead(saddle_pin); //read saddle state
map(saddle, 0, 1023, 0, 255);
if (digitalRead(brake_pin) == HIGH) { //if the brake is off
uint8_t buf[VW_MAX_MESSAGE_LEN];
uint8_t buflen = VW_MAX_MESSAGE_LEN;
vw_set_rx_pin(pedalLeft_pin); //set the recieve pin to left side
pedalLeft = vw_get_message(buf, &buflen);
vw_set_rx_pin(pedalRight_pin); //set the recieve pin to right side
pedalRight = vw_get_message(buf, &buflen);
if (pedalLeft > assist || pedalRight > assist) { //if either pedal exceedes the assist threshold
if (abs(pedalLeft - pedalRight) > standing) { //if the difference in pedal forces is greater than the standing test
if (saddle < saddleOut)saddleFactor = 80; //if rider is out of the saddle then seat = 0.80
boost = 180 * (pedalLeft + pedalRight) * (saddleFactor / 100) / assist;
analogWrite (boost_pin, boost);
}
}
}
Serial.print("Assist is set at ");
Serial.println(assist);
Serial.print("Brake is ");
Serial.println(brake_pin);
Serial.print("Left pedal is at ");
Serial.println(pedalLeft);
Serial.print("Right pedal is at ");
Serial.println(pedalRight);
Serial.print("Saddle is ");
Serial.println(saddle);
}
The build: As mentioned, the code is not yet functional and this is perhaps our takeaway for the course. We attempted a very ambitious project and by all accounts, one that would be successful when fully realized. But it also involved a sophisticated mechanical system AND multiple sensors controlled by three Arduinos communicating over two wireless links. In the end, this was too much to resolve in the time available to us. We had hoped to have a ride-able bike for our presentation. Our success with each individual component and small groups of components had encouraged us and led us to believe this was an attainable goal, even as our deadline approached. While we were able to demonstrate individual components, in retrospect, we should have reduced the complexity of our prototype to something less than fully functional at an earlier stage or have settled on an intermediate goal from the outset.
Thanks: We would like to extend our thanks to John Martin and Robert Beyer for their cycling expertise and encouragement and to Maia Gower-Opincarne for the generous use of her bike.
No bicycles were harmed in the making of Tailwind.