Electronic gear shift by Arduino servo for Rohloff Speedhub and others

Joined
Apr 27, 2013
Messages
82
With this thread its my intent to trigger a DIY solution for electronic gear shifting of hub gears like the Rohloff, Alfine, Nexus or others.

To my surprise such a thing is still not available as a DIY kit / solution, though technically a rather simple thing methinks :).
So I'd like to share here my slow steps of developing hardware and software in the frame of a new e-bike build of mine, that will be composed of a stripped down gear hub motor driving a Rohloff gear hub. Pedals and motor will have their own dedicated chains, directly driving the Rohloff for both to take advantage of the gear hub. I will cover my build in a separate thread. My mechanically solutions for the electronic gear shifter will therefore concentrate on interfacing with the Rohloff Speedgear.
That's just to give some background here.

Code:
/*

Electronic gear shift for Rohloff Speedhub and others gear hubs
Arduino Nano V3 ATmega328 (old bootloader)

Testversion for 270° Miuzei 20kg Servo
500-2500usec => 270° 

Rohloff Speedhub axle on external gear box needs a full turn of 360° 
to shift from gear 1 to gear 14

360° => 27,6923° eg 27,4074usec>/° each gear => 2000usec => 500 - 2500usec

//

to shift 10 gears on Rohloff Speedhub 
=> 249,231° => 27,6923° eg 27,4074usec>/° each gear => 1384,62usec => 807,69 - 2192,31usec

real calculated usec range for 10 gears on Rohloff: 
808 - 2191usec due to finite INT calculation accuracy

Rohloff speedhub electronic gear shift by Arduino servo
2019 written by "endlessly_ending" at endless-sphere 
*/


#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

#include <Servo.h>
Servo motor;

//--------------------------------------------

  int ButtonUp = 8;               // button for up-shift connected to Arduino port 8
  int ButtonDown = 7;             // button for down-shift connected to Arduino port 7
  int count = 1;                  // gear index
  int LastCount;                  // last gear index
//  int MaxGear = 14;                 // gear count for Rohloff Speedhub
  int MaxGear = 10;               // TEST  setup for <270° servo => 10 gears at maximum possible
  int MinGear = 1;                // first gear
  int GearStepUsec10;             // 10x gear step usec to improve calculation accuracy
  int StartGear = (MaxGear - 2);  // start out at two gears down

  int Motor_Stop_LED = 13;        // LED indicator when motor is on halt, LED and relays connected to Arduino port 13

  int AngleUsec;                  // index angle in usec
  int AngleUsecMax;               // max index angle in usec
  int AngleUsecMin;               // min index angle in usec

  int FirstGearIndexTrim = A3;    // trimmer for adjusting first gear index connected to Arduino port A3
  int PlayComp = A2;              // trimmer for compensating shift gear play connected to Arduino port A2
  int FGIT;                       // variable to store the value read of FirstGearIndexTrim
  int PC;                         // variable to store the value read of PlayComp


//--------------------------------------------

void setup() 
  {
  Serial.begin(9600);       // TEST use the serial port to print the number      
  
  digitalWrite(Motor_Stop_LED,HIGH);
  lcd.begin(16, 2);
  lcd.print("  initialize    ");
  lcd.setCursor(0, 1);
  lcd.print("motor on halt");
  
  pinMode(ButtonUp,INPUT);
  pinMode(ButtonDown,INPUT);
  digitalWrite(ButtonUp,HIGH);
  digitalWrite(ButtonDown,HIGH);
  pinMode(Motor_Stop_LED,OUTPUT);
  count = StartGear;                                                                                                                                                                                                                                                                                                          

  motor.attach(9);
  pinMode(A2, INPUT_PULLUP);
  pinMode(A3, INPUT_PULLUP);
  delay(1000);
  
  digitalWrite(Motor_Stop_LED,LOW);
  }

//--------------------------------------------

void loop() 
  {
//  set start conditions and read status of up shift and down shift button


190831_electronic_shift_Rohloff_V15_TEST270_V5.jpg


As can be seen, the Arduino code will allow for easy editing of
- gear count of what ever gear hub one wants to use
- adapting for different servo motors
- electronically fine adjusting gear one end position
- electronically fine adjusting to compensate play in the shift gear mechanics
- make use of standard RC or robot servos of any kind that can be controlled by PWM signals

As for now, my testing is limited by the restrictions of a 270° angel range of the otherwise nice Miuzei DS3218 servo
https://www.amazon.com/Digital-Waterproof-Mechanical-Fittings-Control/dp/B07NJ6SQKB/ref=sr_1_1_sspa?__mk_de_DE=%C3%85M%C3%85%C5%BD%C3%95%C3%91&keywords=miuzei+DS3218&qid=1567237901&s=gateway&sr=8-1-spons&psc=1&spLa=ZW5jcnlwdGVkUXVhbGlmaWVyPUEzMFJPRkVDV0EyOEtKJmVuY3J5cHRlZElkPUEwNDEyNTgzMkNYWVUwSzdVSVNFQiZlbmNyeXB0ZWRBZElkPUEwMjAxMDU3MUo0ODg2UDM4N0MyJndpZGdldE5hbWU9c3BfYXRmJmFjdGlvbj1jbGlja1JlZGlyZWN0JmRvTm90TG9nQ2xpY2s9dHJ1ZQ==

Its not easy to find a widely available servo of moderate price, that combines a angle range of +360°, quick response with strong force and is more or less waterproof.
Nevertheless I have a Kingmax sail winch servo in the pipe, that possibly could do the trick.
 
csm_BaukastenSystem_01.en_948763bd27.png

https://www.rohloff.de/en/experience/technology-in-detail/modular-parts-system/


01_Wisent_pipe_wrench.JPG

02_fit_Speedhub_rohloff EX transfer box .JPG

03_fit_pipe_wrench.JPG

04_cut_down_pipe_wrench.JPG

05_assembly01.JPG

06_assembly02.JPG

07_assembly_complete.JPG

08_assembly_complete.JPG

09_assembly_complete.JPG


https://www.bike24.com/p235419.html?q=EX+transfer+box++
https://www.robotshop.com/uk/actobotics-90-quad-hub-mount-e.html
https://www.robotshop.com/uk/load-bearing-servo-block-hitec.html
https://www.robotshop.com/uk/actobotics-077-inch-servo-hub-horn-25t.html


As for mechanics so far
:) :) :)
 
Nice work!
But why the piece of pipe wrench and not machine the aluminum part smaller/direct fitting the hexagon nut?
 
reality check


11_reality_check.JPG

http://www.74hc.co.uk/the-all-new-servo-tester/


First tests passed positive with an breadboarded Arduino.
:)

The shown servo tester from 74hc is of great help.
Nick Ward even did a custom made version with extended PWM range for me. Thanks!!!
 
SlowCo said:
Nice work!
But why the piece of pipe wrench and not machine the aluminum part smaller/direct fitting the hexagon nut?

No such machine park accessible for me, sadly
Exorbitant costś and delivery time of pro-shops for custom machined parts are the second reason.

I hope though, others will chime in and possibly provide their expertise and skills to improve where I fall short for one reason or another, so everybody will profit from what I'm just about to start from scratch
 
Now that the first test went well, there is time to ponder the other alternatives I was tinkering.
Though the solution shown above is very simple an can be realized by almost anyone, the added bulk at an exposed place may be a less elegant solution.


One way to overcome this may be to stay with the usage of bowden cables.
This would allow to place the servo unit wherever it fits well at any frame.
The second benefit would be that a 270° servo most likely would do the job, though more torque would be needed of course.
The third benefit would be that adjustment can elegantly be done mechanically.

01_bowden_cable.JPG

https://www.bike24.com/p235486.html?q=rohloff
https://www.bike24.com/p235390.html
 
Another approach might be to directly drive the gears inside the Rohloff ex transfer box



01_ex_transfer_box.JPG

02_ex_transfer_box.JPG

03_ex_transfer_box.JPG

04_ex_transfer_box.JPG


The brass holder for the needle bearing can be screwed off and the driven gear wheel is press fit on its shaft.
With minor machinery the gear wheel and the servo hub horn may be brought together precisely enough, so that the servo ball bearings can do their job well

Downside is that there is a urgent need to have the Rohloff ex transfer box sealed properly.
Second downside is, that if that approach fails another 100 bucks are gone.
Third downside is, that a +360° servo is mandatory to shift through all 14 gears of the Rohloff Speehub
 
An even more complicated approach to the one above would be to replace the driving gear by a bigger one to allow to use 270° servos

06_ex_transfer_box.JPG


Those gears from Robotshop do not exactly match modulus but metric / imperial matching in this case may be close enough to do the job.

Downside here also is that there is a urgent need to have the Rohloff ex transfer box sealed properly.
Second downside is, that if that approach fails another 100 bucks are gone.
 
endlessly_ending said:
SlowCo said:
Nice work!
But why the piece of pipe wrench and not machine the aluminum part smaller/direct fitting the hexagon nut?

No such machine park accessible for me, sadly
Exorbitant costś and delivery time of pro-shops for custom machined parts are the second reason.

I hope though, others will chime in and possibly provide their expertise and skills to improve where I fall short for one reason or another, so everybody will profit from what I'm just about to start from scratch

Rohloff.png

Again: why not have the machined part with the hexagon female hole fit directly to the male hexagon part? Why the piece of pipe wrench as adapter? Is the shiny (aluminum/SS?) part an "as is" bought part?
 
Ah, now I understand. Sorry

You may have missed the link given above:
https://www.robotshop.com/uk/actobotics-90-quad-hub-mount-e.html

So yes, the rectangular off the shelf aluminium part was turned down to roughly 25mm outer diameter to fit.
As the outer diameter is not any critical, one could even simply sand it down.

No big deal and easy to do especially if you also order :
https://www.robotshop.com/uk/actobotics-futaba-servo-shaft-hub-1-2.html
and a set of those imperial screws that drive you crazy when used to metric system.
https://www.robotshop.com/uk/12-6-32-socket-head-machine-screw-25pk.html
https://www.robotshop.com/uk/38-6-32-socket-head-machine-screw-25pk.html


That I found that the imperial hexagon female part exactly fits the outer shape of the metric 8mm tube wrench was pure luck.
All this parts from the Robotshop follow the same system and easily fit together, which on the other hand is nice and handy.
 
Disassembled the ex transfer box to have closer look, if a possibly even easier approach could lead to a good solution.
The brass thread was tightened strongly, but is not glued.


ex_transfer_box_disassembly.JPG


As I happen to have a 32T gear for servos and the driving gear of the Rohloff ex transfer box is 32T as well, this could have been a smart and very elegant solution


01_ex_transfer_box_regearing.JPG

02_ex_transfer_box_regearing.JPG

03_ex_transfer_box_regearing.JPG


Sadly this 32T gear has a 24 tooth inner spline but the Miuzei servo needs to fit for 25T tooth spline
https://www.robotshop.com/uk/lynxmotion-ses-32t-spur-gear-spline.html

So, have to check now for a 32T gear that directly fits my servo
:)
 
This one seems to be the right gear for 25T Hitec / Futaba spline:

Hitec ROB-12231
https://www.servocity.com/32p-25t-3f-spline-servo-mount-gears
http://vionics.co.uk/index.php?route=product/search&search=ROB-12231

lets see what comes out
:)
 
Ready to share a beta version of the fully working Arduino code
It is tested to work both in TinkerCad and on the bench
Have fun :)


Code:
/*

Electronic gear shift for Rohloff Speedhub and other gear hubs
Arduino Nano V3 ATmega328 (old bootloader)
ver: 270_V14
status: BETA TESTING
platform: Arduino IDE 1.8.9



Test version for 270° Miuzei 20kg Servo at Rohloff Speedhub
500-2500usec -> 270° 

Rohloff Speedhub axle on external gear box needs a full turn of 360° 
to shift from gear 1 to gear 14

360° -> 27,6923° eg 27,4074usec>/° -> 2000usec -> 500 - 2500usec

to shift 10 gears on Rohloff Speedhub 
-> 249,231° -> 27,6923° eg 27,4074usec>/° -> 1384,62usec for all 10 gear steps -> 807,69 - 2192,31usec
153,84usec for each gear step

Rohloff Speedhub electronic gear shift by Arduino servo
2019 written by "endlessly_ending" at endless-sphere 

https://endless-sphere.com/forums/viewtopic.php?f=2&t=102122&p=1493203#p1493203
*/


#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

#include <Servo.h>
//  #include <Servo_330Hz_digital.h>

/* ----------------------- USER SETUP ----------------------------------
modern digital servos may perform better with PWM refresh rates of 330Hz

In such a case use "Servo_330Hz_digital.h", where you replaced the line found in "Servo.h" library
"#define REFRESH_INTERVAL    20000     // minumim time to refresh servos in microseconds"
by
"#define REFRESH_INTERVAL    3030     // minumim time to refresh DIGITAL servos in microseconds -> 330Hz"
store your freshly created "Servo_330Hz_digital.h" at the same place where you found the original "Servo.h" library

dobble check not to operate standard 50Hz servos at 330Hz 

----------------------- USER SETUP -------------------------------------
*/

Servo motor;

//--------------------------------------------

  int ButtonUp = 8;               // button for up-shift connected to Arduino port 8
  int ButtonDown = 7;             // button for down-shift connected to Arduino port 7
  int GearIndexTrim = A3;         // trimmer for adjusting gear index connected to Arduino port A3
  int PlayCompTrim = A2;          // trimmer for compensating shift gear play connected to Arduino port A2

  long IndexAdjustUsec;           // usec value for gear index correction by trimmer "GearIndexTrim"
  int count = 1;                  // gear index
  int LastCount;                  // last gear index
  int MinGear = 1;                // first gear
//  int MaxGear = 14;                 // gear count for Rohloff Speedhub
  int MaxGear = 10;               // TEST  setup for 270° servo -> to switch 10 gears of the Rohloff are possible at maximum 
/* ----------------------- USER SETUP ----------------------------------
set the value of "MaxGear" according to the number of gears of your gear hub  
----------------------- USER SETUP -------------------------------------
*/
  
  int StateButtonUp;              // staus of button to shift up
  int StateButtonDown;            // staus of button to shift down 

  
  int GearStepUsec10;             // gear step usec (10x to improve calculation accuracy)
  int StartGear = (MaxGear - 2);  // start out at two gears down

  int Motor_Stop_LED = 13;        // LED indicator when motor is on halt, LED and relays connected to Arduino port 13

  int AngleUsec;                  // index angle in usec that is sent to the sevo
  int MinAngleUsec = 808;         // min index angle in usec - this value may be shifted during startup by applying corrections with trimmer "GearIndexTrim"
  int MaxAngleUsec = 2192;        // max index angle in usec - this value may be shifted during startup by applying corrections with trimmer "GearIndexTrim"
/* ----------------------- USER SETUP ----------------------------------
set the value of "MinAngleUsec" for defining the index position of the first gear 
as found by 

- calculation
- with a servotester that displays usec values.

Either way make sure, that the servo current is not increased in that position

set the value of "MaxAngleUsec" for defining the index position of the last gear 
as found by 

- calculation
- with a servotester that displays usec values.

Either way make sure, that the servo current is not increased in that position

Also keep in mind, that the servo usec range given in manufacurer spec sheet,
must not to be violated, not even with the additional margin of half a gear step either side, 
that can be set for index adjustment by turning the potentiometer 
"GearIndexTrim" off centre

If no electronically index adjustment is necessary, leave the  
"GearIndexTrim" potentiometer at centre position
----------------------- USER SETUP ------------------------------------
*/

  long PlayComp;                          // value for gear shift play compensation 
  long GearStepCompUsec10 = 0;            // usec of corrected gear shift step (10x to improve calculation accuracy)
  long LostCompUsec10;                    // usec loss from play compensation (10x to improve calculation accuracy)
/* -------------------- INFO ----------------------------------
Gear shifting play compensation does not shift the index point of the first or the last gear (as those are alerady found and set above)
"GearStepCompUsec10" shifts all other index points accordingly to up-shift respectively down-shift action

full clockwise trim of potentiometer "PlayCompTrim" results in a play compensation the length of 50% a single gear step 
----------------------- INFO -------------------------------------
*/


void setup() 
  {
//  Serial.begin(9600);                                                         // activate the serial port for debugging only - not needed for normal operation
  
  digitalWrite(Motor_Stop_LED,HIGH);
  lcd.begin(16, 2);
  lcd.print("  initialize    ");
  lcd.setCursor(0, 1);
  lcd.print("motor on halt");

  pinMode(ButtonUp, INPUT_PULLUP);
  pinMode(ButtonDown, INPUT_PULLUP);
  pinMode(GearIndexTrim, INPUT);
  pinMode(PlayCompTrim, INPUT);
  pinMode(Motor_Stop_LED,OUTPUT);
  
  digitalWrite(ButtonUp,HIGH);
  digitalWrite(ButtonDown,HIGH);
  count = StartGear;                                                                                                                                                                                                                                                                                                          

  motor.attach(9);

  delay(1000);
  
//  --------set total index angle and single gear step in usec (10x), according to trimmer settings and number of gears to cover--------
  GearStepUsec10 = (((MaxAngleUsec - MinAngleUsec)*10) / (MaxGear - MinGear));

  IndexAdjustUsec = analogRead(GearIndexTrim);
  IndexAdjustUsec = (IndexAdjustUsec-1023/2);                                             // middle position of trim potentiometer results in zero index shift
  IndexAdjustUsec = (IndexAdjustUsec*(1023*10/GearStepUsec10)/10/4);                      // mapping value of "IndexAdjustUsec" to half a gear step up or down

  MinAngleUsec = MinAngleUsec + IndexAdjustUsec;                                          // shift index of MinAngleUsec according to trimmer "GearIndexTrim"
  MaxAngleUsec = MaxAngleUsec + IndexAdjustUsec;                                          // shift index of MaxAngleUsec according to trimmer "GearIndexTrim"  

  PlayComp = analogRead(PlayCompTrim); 
  PlayComp = (PlayComp/20);
  if (PlayComp > 50)
    PlayComp = 50;
  LostCompUsec10 = (GearStepUsec10/10*PlayComp/10);                                       // mapping
  GearStepCompUsec10=(10*(MaxAngleUsec-MinAngleUsec)-LostCompUsec10)/(MaxGear-MinGear);   // usec value of one gear step, correted by gear shift play compensation
 
  digitalWrite(Motor_Stop_LED,LOW);
//  --------
}


void loop() 
  {
/*  
  Serial.println(MinAngleUsec);               // for debugging only
  Serial.println(MaxAngleUsec);               // for debugging only
 
  Serial.println(PlayComp);                   // for debugging only
  Serial.println(GearStepUsec10/10);          // for debugging only
  Serial.println(GearStepCompUsec10);         // for debugging only
  Serial.println();
*/    
    
//  --------set start conditions and read status of up shift and down shift button--------
  LastCount = count;
  StateButtonUp = digitalRead(ButtonUp);
  StateButtonDown = digitalRead(ButtonDown);
//  --------

  motor.writeMicroseconds(AngleUsec);

//    Serial.println(AngleUsec);              // for debugging only
            
// --------display gear status-------- 
  lcd.begin(16, 2);
  lcd.print("  current GEAR ");
  lcd.setCursor(6, 1);
  lcd.print(count);
  delay(50);
//  --------
  
//  --------check "ButtonUp" to perform gear change accordingly--------
  if(digitalRead(ButtonUp) == LOW && count < MaxGear)
  {
  count++;                                    // Increment Count by 1
  if(count > MaxGear)
    count = MaxGear;
  
  AngleUsec = (MinAngleUsec + ((GearStepCompUsec10*(count - 1))/10));
  AngleUsec = AngleUsec + IndexAdjustUsec;
  AngleUsec = MaxAngleUsec-(((MaxGear-count)*GearStepCompUsec10)/10);  
 
  // --------display gear status change--------
  lcd.setCursor(0, 0);
  lcd.print("up to Usec      ");
  //    lcd.print(GearStepUsec);
  lcd.setCursor(5, 1);
  lcd.print(AngleUsec); 
  //  --------

/*
  Serial.println(count);                    // for debugging only
  Serial.println(AngleUsec);                // for debugging only
  Serial.println();                         // for debugging only
  
  Serial.println(IndexAdjustUsec);          // for debugging only
  Serial.println(GearStepUsec10/10);        // for debugging only
  Serial.println(PlayComp);                 // for debugging only
  Serial.println();                         // for debugging only
*/
  
  delay(50);
        
  if(count != LastCount)
    digitalWrite(Motor_Stop_LED,HIGH);         // disengage motor during gear change   
  delay(50);
  }
                                         
//  --------check "ButtonDown" to perform gear change accordingly--------
  if(digitalRead(ButtonDown) == LOW && count > MinGear) 
  {
  count--;                            // Decrement Count by 1
  if(count < MinGear)
    count = MinGear;

  AngleUsec = (MinAngleUsec + ((GearStepCompUsec10*(count - 1))/10));
  AngleUsec = AngleUsec + IndexAdjustUsec;
  AngleUsec = MinAngleUsec+(((count-MinGear)*GearStepCompUsec10)/10);  
   
  
    
  // --------display gear status change--------
  lcd.setCursor(0, 0);
  lcd.print("down to Usec    ");
  //    lcd.print(GearStepUsec); 
  lcd.setCursor(5, 1);
  lcd.print(AngleUsec); 
  //  --------
/*  
  Serial.println(count);                      // for debugging only
  Serial.println(AngleUsec);                  // for debugging only
  Serial.println();                           // for debugging only

  Serial.println(IndexAdjustUsec);            // for debugging only
  Serial.println(GearStepUsec10/10);          // for debugging only
  Serial.println(PlayComp);                   // for debugging only
  Serial.println();                           // for debugging only
  */        
  delay(50);
  
  if(count != LastCount)
    digitalWrite(Motor_Stop_LED,HIGH);         // disengage motor during gear change
  delay(50);
  }
    
  digitalWrite(Motor_Stop_LED,LOW);  

  }


Code was written with those in mind, that are no full time coders and also to provide the basic comments about what is what and how to make use of it.
Sadly some formatting that make such programs way better readable, got lost at trasferr.

This program refers to the schematic published in my first posting and is meant to aid finding some ones own starting point.



The basic idea was a minimalistic approach
- one up-shift button
- one down-shift button
- one trimmer potentiometer (read at start up only) to shift gear index
- one trimmer potentiometer (read at start up only) to compensate gear shift play
- one display to be kept informed



electronic_shifting_index_and_play_adjustment2.jpg
 
Hello!

I make this super stuff, but i have some question for code.
How can make the servo start the shifting/working from the(14th) endpoint?
14th endpoint, modify to 1th endpoint, and this is the start ponint.
Ad if i start shifting, the servo spin the clockwise from (270deg, 14th endpoint to 0deg, 1th endpoint)
Im from Hungary, sorry my not best english lang.
Please anyone can help for me?

Greetings: Carl
 
Thanks for your servo solution. I integrated the control electronics in advance in my ebike cable killer in 2015 (without the software).
https://www.avdweb.nl/solar-bike/electronics/robust-two-wire-gnd-5v-current-controlled-bus-for-ebikes
 
john61ct said:
He used to sell it, but removed this kit from his website.

Currently there is no real solution, beside the E-14: https://www.rohloff.de/de/produkte/speedhub/e-14

A while ago I tried my own solution, but I was busy with other projects and had to pause..

 
Back
Top