Testing Countersteering on a Bicycle

If you ride a motorcycle, you have probably heard of countersteering. If you've only ridden a bicycle, you may not have. Countersteering is the principle that to initiate a turn, you must first steer the handlebars in the opposite direction that you wish to turn. If you wish to turn left, the turn is initiated with a subtle turn to the right.

This principle is well-known to motorcyclists and essentially required to pass the licensing test. Does it also apply to bicycles? Almost no one is taught the technique, and yet even young children learn how to ride. Although basic physics says it should work this way, my goal is to find out if it can actually be observed.

Jump to the results if you don't care about the how.

The basic idea is to mount a rotary encoder ($10 on eBay) to the steering on my bicycle and log the results. Rotary encoders (at least the kind I'm using) output two signals--two square waves 90 degrees apart (also called quadrature encoding):

Due to the offset, it is possible to tell not only how many steps have been taken when turning the shaft, but also the direction. I used an encoder with 600 pulses/revolution, but because there are four edges per pulse, this effectively gives a resolution of 2400 steps/rotation.

The bicycle wasn't designed for an encoder, and I didn't want to cause any permanent damage to the bike, so I used a low-temperature moldable thermoplastic (I used some stuff called Simple-Plastic, but I'm sure there are plenty of other varieties) to create a kind of attachment structure, which I was able to secure with zipties. It conformed nicely to the head tube and the encoder.

Unfortunately the stuff does look like solidified elephant spit, but it does work nicely. You can see that I left a gap with a small flexible connection between the frame part and the encoder part. This is so I could apply tension with rubber bands to give some compression between the wheel and rubber bushing on the steering.

Simple video test (view in HD to see the actual results):

And a closeup of the action:

As said earlier, the encoder gives 2400 steps/rotation. The wheel is 64 mm in diameter, while the grommet on the bike is 49 mm. As such, the overall precision is (49*360)/(64*2400) = 0.195 degrees. This matches closely with the ~0.2 degree precision shown in the logs.

I used a Teensy 3.5 to interpret the encoder results and log the data. Conveniently, it has a MicroSD slot, FAT filesystem support, and is small and pleasant to use. It is massive overkill for this project but I got the whole thing running in under an hour. The code was trivial, but in case you're interested:

#include <SPI.h> #include <SdFat.h> #define SAMPLE_FREQ 20 #define HEAD_DIAM 49.0f #define ENCODER_DIAM 64.0f #define TICKS_PER_ROT 2400.0f #define DEGREES_PER_ROT 360.0f int ledPin = 13; int pinPhaseA = 0; int pinPhaseB = 1; const uint8_t SD_CHIP_SELECT = SS; const int8_t DISABLE_CHIP_SELECT = -1; SdFatSdioEX sd; char filename[13] = {0}; FatFile file; ArduinoOutStream cout(Serial); int currentPhase = 0; int rotaryPos = 0; int missedSamples = 0; int dupeSamples = 0; void phaseChangeISR() { int phaseA = digitalReadFast(pinPhaseA); int phaseB = digitalReadFast(pinPhaseB); int newPhase = (phaseA << 1) | (phaseA ^ phaseB); // gray code: 00, 01, 11, 10 uint32_t diff = (newPhase - currentPhase) & 0x3; if (diff == 1) { rotaryPos++; } else if (diff == 3) { rotaryPos--; } else if (diff == 2) { missedSamples++; } else { dupeSamples++; } currentPhase = newPhase; } void setup() { pinMode(ledPin, OUTPUT); Serial.begin(115200); pinMode(pinPhaseA, INPUT_PULLUP); digitalWrite(pinPhaseA, HIGH); pinMode(pinPhaseB, INPUT_PULLUP); digitalWrite(pinPhaseB, HIGH); attachInterrupt(digitalPinToInterrupt(pinPhaseA), phaseChangeISR, CHANGE); attachInterrupt(digitalPinToInterrupt(pinPhaseB), phaseChangeISR, CHANGE); // wait for USB Serial int waitCycles = 0; while (!Serial && (waitCycles < 20)) { delay(100); waitCycles++; } if (!sd.cardBegin()) { cout << "cardBegin failed\n"; } else { cout << "cardBegin succeeded\n"; } cout << "card size: " << sd.card()->cardSize() << " blocks\n"; if (!sd.fsBegin()) { cout << "\nFile System initialization failed.\n"; } cout << "volume is FAT" << int(sd.vol()->fatType()) << endl; for (int i=0; i<1000; i++) { sprintf(filename, "log%04d.csv", i); cout << "testing for '" << filename << "'\n"; if (!sd.exists(filename)) { cout << " using '" << filename << "'\n"; break; } } file = sd.open(filename, O_CREAT | O_WRITE); file.write("[t], [pos]\n"); } void loop() { static int numSamples = 0; if ((numSamples % SAMPLE_FREQ) == 0) { file.close(); file = sd.open(filename, O_CREAT | O_WRITE | O_APPEND); } float curTime = float(numSamples) / SAMPLE_FREQ; float posInDegrees = DEGREES_PER_ROT / TICKS_PER_ROT * ENCODER_DIAM / HEAD_DIAM * rotaryPos; char str[256]; sprintf(str, "%.2f, %.1f\n", curTime, posInDegrees); file.write(str); cout << str; numSamples++; delay(1000 / SAMPLE_FREQ); digitalWriteFast(ledPin, !digitalReadFast(ledPin)); }

Results

So, how about the data? Well, the first run didn't go so well:

As you can see, the data is very noisy. There is also some drift in there, which I suspect is due to vibration. Unfortuantely, the roads around here are like half cobblestone, which obviously causes problems. There are very few turns which don't involve a change in slope or going over a bump of some kind. And there are a million speedbumps. So for further data I need to find an empty parking lot or something. Check back later for more data.

Update 2 (2017/03/13)

Collected somewhat more data. I rode around on a smooth parking lot this time. No hands-free riding, but I wanted to see if the countersteer signal was visible in the results. It was:

In each of these, you can definitely see a small bump opposite the main direction of the turn. A few extra notes: it's more subtle, but it looks like there's a countersteer coming out of the turn as well. This isn't entirely unexpected: in fact, it seems inevitable if the entrance and exit of the turn is more-or-less symmetrical. One countersteer to initiate the tilt, and another to halt it. The direction of the torque is the same, and hence the direction of the countersteer is the same.

Another apparent artifact--most visible in the first chart, which has the sharpest response--is a kind of ringing response as I hit the maximum angle. It's very reminiscent of the ringing you see on a square wave. And not too surprising, either, given the likelihood of me overshooting the desired steering point slightly, and then overshooting the correction, and so on (but with decreasing amplitude). Not entirely related to countersteering, but interesting I thought.

As before, check back later for more results.

Obligatory cat pic:

Copyright 2017 Scott Cutler. All code may be used freely and is without warranty. Acknowledgement is preferred but not required. Contact scott at scottcutler dot net for questions.