I want to count and add actions to the gear teeth on my bicycle. For reference, the gear has 55 teeth, I paddle max 150 rpm, so each one tooth every 7 milliseconds.
Here's my current idea, using an inductive sensor:
Read toothSensor
If toothState is low and toothSensor reads high, set toothState high and do a lot of stuff.
If toothState is low and toothSensor reads low, don't do anything.
If toothState is high and toothSensor reads high, don't do anything.
If toothState is high and toothSensor reads low, set toothState low.
But I also read about pulsein() function, and there are probably more options. I just haven't found them yet, and don't know which one would suit this situation best. Hence, I ask you to advise from experience.
I am unfamiliar with inductive sensors but what about using an infrared sensor and the Arduino interrupts to count the teeth. Or a rotary encoder can also work.
That function is for reading the length of a pulse. That's useful if you want to measure the RPM or speed of the bike, but, for such long pulses, it will take up a lot of the Arduino's time, which could be used for other tasks. You can measure speed/rpm in more efficient and probably more accurate ways than using pulseIn().
Yes, just log the timestamps for each tooth into a buffer, and you can calculate speed (or even acceleration) from that data. Perhaps use an ISR for updating the buffer so timing is more accurate.
For a fairly smooth estimate of speed you'd look at the first and last timestamps in the buffer and divide by the buffer size-1 to get a smoothed tooth-time, then use that to calculate the speed.
Whilst you could do that, it would be impractical to do if the bicycle has gears and a freewheel, which most bicycles do.
It would only work well for a single gear fixed wheel bicycle.
Hi, thanks for all the input. I'm not really looking for an RPM or speed signal. I'll let you in on the project concept:
I have mounted a load cell in such a way, it measures the tension in the chain resulting from my pedal input. Pres pedal harder, more chain tension. But the input is not continious. When the cranks are horizontal, there is no pedal pressure transferring to the chain, when they are vertical all pressure goes to the chain. So:
I start at 0 torque measured
At each tooth passing the signal, I measure the torque (chain tension * arm) and add that to accumulate.
After 55 teeth, I devide the accumulated torque number by 55 and I know the average torque of that revolution. I also measure the duration of that revolution with a millis part in the code. So now I know power, which is torque * RPS. And do some work to get from the load cell amplifier signal to a real Wattage, by subtracting a base figure and multiplying by a factor to get to real Watts. Last but not least, a TM1637 display is updated to the new number.
After the 55 teeth, I reset the torque to 0, reset the millis-start figure, etc.
So, I have a power gauge on my bike. Which I really like. I've found some for sale for € 500,- or more, mine will be below € 50,- and I like tinkering.
I found some error solutions and therefore changed this post.
Here's the first crack at a code:
/*
02-03-2024 HugoW
This is the sketch for my Arduino based bicyle power gauge.
It uses an Arduino Nano, a TM1637 display, an inductive sensor on the bike's main sprocket
and a load cell with a wheel pressing on the power-part of the chain.
The harder you push on the pedal, the bigger the chain tension.
The sketch records the load cell value at each passing of a tooth and adds those numbers together.
After one full revolution, the accumulated number is devided by the number of teeth, is the average torque (with a bit of
calculating to get from load cell amplifier signal to real torque in Nm). The average torque multiplied by RPS(revolutions per second) is Watts.
*/
// Include libraries:
#include <Arduino.h>
#include <TM1637Display.h>
#include <HX711.h>
// Declare variables and pins:
// For torque measurement and power calculation:
const int LOADCELL_DOUT_PIN = 2;
const int LOADCELL_SCK_PIN = 3;
long Torque;
HX711 scale;
int TorqueSum;
int TorqueTare;
int TorqueFactor;
int Wattage;
// For TM1637 display:
#define CLK 9
#define DIO 10
TM1637Display display = TM1637Display(CLK, DIO);
// For tooth pulse input:
int ToothSensorState;
int ToothSensor = 5;
int ToothState;
int ToothCount;
int NumberOfTeeth;
// For cycle time:
int StartMillis;
int StopMillis;
int CycleDuration;
void setup()
{
// Declare pins and such:
Serial.begin(9600);
scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
pinMode(ToothSensor, INPUT);
// Setup display:
display.clear();
display.setBrightness(7);
// Set changing values to zero
ToothSensorState = 0;
TorqueSum = 0;
StartMillis = 0;
StopMillis = 0;
display.showNumberDec(0, false);
// Set fixed values:
NumberOfTeeth = 5;
TorqueTare = 0;
TorqueFactor = 1;
}
void loop()
{
ToothSensorState = digitalRead(ToothSensor);
if ((ToothSensorState == 1) && (ToothState == 0) && (ToothCount < NumberOfTeeth))
{
Torque = scale.read();
ToothState = 1;
TorqueSum = (TorqueSum + Torque);
ToothCount = (ToothCount + 1);
}
if ((ToothSensorState == 1) && (ToothState == 0) && (ToothCount >= NumberOfTeeth))
{
Torque = scale.read();
ToothState = 1;
TorqueSum = TorqueSum + Torque;
ToothCount = 0;
StopMillis = millis();
CycleDuration = ((StopMillis - StartMillis) / 1000);
StartMillis = millis();
Wattage = (((TorqueSum * -1000 / NumberOfTeeth) - TorqueTare) * TorqueFactor / CycleDuration);
TorqueSum = 0;
display.showNumberDec(Wattage, false);
Serial.println(Wattage);
}
if ((ToothSensorState == 0) && (ToothState == 1))
{
ToothState = 0;
}
}
I do get some signals, but I fear I made a wrong assumption. The HX711 only gives 10 signals per second. I can jack it up to 80 signals a second, but that is still less than I need for 150 teeth signals per second. So, check on every even tooth, not on the uneven. Maybe that will work. Does the 10 Hz signal mean it refreshes ten times a second, or does it spit out 10 short signals and nothing in between (so you would be lucky to catch them)? I hope it's the first.
You are probably better off with a different sensor. Does the sprocket have slits in it? Instead of counting each individual tooth, just count the slits.
@xfpd;
Assumptions are killing. It's a recumbent with 20" wheels (FlevoBike 50/50 is the make and model). I don't do a steady 150, I cruise at about 100 - 110. But I choose 150 as a max, because under accelleration in lower gear I do exceed the 110 often.
@HazardsMind;
The gear is mounted on the axle on five spokes. But only 5 triggers per revolution is way too little, as the input torque makes two half sinus-like curves per revolution. I would miss at least one peak. I could add a toothed ring with, say, 20 teeth, for the signal. But I hate adding stuff...
I'm looking into ditching the HX711 all together and using an analog amplifier for the load cell signal, to an analog port of the Arduino. This gives a signal all the time.
Maybe would be easier to put a small magneto in the sprocket and a sensor to detect a full revolution. My bike has that but in the wheel, for the speed.
For the counting, you can set an interrupt that triggers a function for each high transition in the arduino pin. This is transparent to the rest of your code, that will run normally meanwhile.
With your current setup you need to count 55 hits. So the interrupt function would just increase a counter by one and return. Except when the counter==55, then you would call another function to do your calculations, and reset the counter to zero.
In case that you count full revolutions with the magnet, or you count radial bars (or holes) of the sprocket instead of teeth, would be the same but without counter, or counting number of bars.
I am planning to attach this set-up to the bike frame so it slightly lifts the chain between the main gear and a guide roller. Pressure on the sensor should change with pedal pressure.
The first code sort-of worked, but the sensor was still manual and of course sometimes missed if the Arduino was bussy doing something else while I triggered the button. So, I gave interrupts my first try. And I failed. I don't know why, hence the question here. Two things bother me;
I seem to get 3 steps up on the counter every pulse of RISING of the trigger
I cannot seem to get a stable output number, it keeps going to 0, 1 or -1.
/*
16-03-2024 HugoW
This is the sketch for my Arduino based bicyle power gauge.
It uses an Arduino Nano, a TM1637 display, an inductive sensor on the bike's main sprocket
and a load cell with a wheel pressing on the power-part of the chain.
The harder you push on the pedal, the bigger the chain tension.
The sketch records the load cell value at each passing of a tooth and adds those numbers together.
After one full revolution, the accumulated number is devided by the number of teeth, is the average torque (with a bit of
calculating to get from load cell amplifier signal to real torque in Nm). The average torque multiplied by RPS(revolutions per second) is Watts.
*/
// Include libraries:
#include <Arduino.h>
#include <TM1637Display.h>
#include <HX711.h>
// Declare variables and pins:
// For torque measurement and power calculation:
const int LOADCELL_DOUT_PIN = 5;
const int LOADCELL_SCK_PIN = 6;
long torque;
HX711 scale;
int torqueSum;
int torqueTare;
float torqueFactor;
int wattage;
// For TM1637 display:
#define CLK 9
#define DIO 10
TM1637Display display = TM1637Display(CLK, DIO);
// For tooth pulse input:
volatile int trigger = 2;
volatile int triggerState;
int triggerCount;
int numberOfTriggers;
int mainGearTeeth;
int sprocketTeeth;
int sprocketSpokes;
// For cycle time:
unsigned long startTimer;
unsigned long stopTimer;
unsigned long cycleDuration;
void triggered()
{
triggerState = 1; // Set triggerState high, so the loop knows what to do
}
void setup()
{
// Declare pins and such:
Serial.begin(9600);
scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
pinMode(trigger, INPUT);
// Setup display:
display.clear();
display.setBrightness(7);
// Set changing values to zero
torqueSum = 0;
startTimer = 0;
stopTimer = 0;
triggerState = 0;
triggerCount = 0;
display.showNumberDec(0, false);
// Set fixed values:
mainGearTeeth = 52;
sprocketTeeth = 13;
sprocketSpokes = 6;
numberOfTriggers = (mainGearTeeth / sprocketTeeth * sprocketSpokes);
torqueTare = 0;
torqueFactor = -0.001;
// Tare torque sensor
torqueTare = scale.read();
attachInterrupt (digitalPinToInterrupt(trigger), triggered, RISING);
}
void loop()
{
if ((triggerState == 1) && (triggerCount < numberOfTriggers))
{
torque = scale.read(); // Read torque sensor.
torqueSum = (torqueSum + torque); // Add torque to sum.
triggerCount = (triggerCount + 1); // Raise counter by one.
triggerState = 0; // Reset trigger state.
}
if ((triggerState == 1) && (triggerCount >= numberOfTriggers))
{
torque = scale.read(); // Read torque sensor.
torqueSum = (torqueSum + torque); // Add torque to sum.
triggerCount = (triggerCount + 1); // Raise counter by one.
triggerState = 0; // Reset trigger state.
triggerCount = 0; // Reset trigger count to zero.
stopTimer = millis(); // Read millis at stop of cycle, determine duraton of cycle.
cycleDuration = ((stopTimer - startTimer) / 1000);// Subtract starting value of millis to determine duraton of cycle.
startTimer = millis(); // Reset cycle.
wattage = (((torqueSum / numberOfTriggers) - torqueTare) * torqueFactor / cycleDuration);// calculate wattage by dividing the torquesum by the triggerCount, subtracting the tare value and multiplying a factor to get real Newton meters, divide by cycle duration.
torqueSum = 0; // reset torque sum to zero.
}
display.showNumberDec(wattage, false);
Serial.println(triggerCount);
delay(10);
}
I'll keep trying and digging through examples, advice is appreciated.
Oh, BTW, the main gear on my bike is 52 teeth, the little gear on the torque sensor has 13 teeth and 6 spokes. The sensor is triggered by those spokes. See void setup bit.