Project in progress
Enviolo (NuVinci) Road Bike Fully Automatic Shifter

Enviolo (NuVinci) Road Bike Fully Automatic Shifter © GPL3+

Developed over 4 years and 14,000 miles for enviolo (NuVinci) CVT hubs using reed switches and excepts power data for fully auto shifting.

  • 250 views
  • 0 comments
  • 1 respect

Components and supplies

Apps and online services

About this project

I’m sharing this not as a detailed how to make but just so as to give anyone interested in trying this a head start on the code. I wouldn’t recommend unless you have some knowledge of bicycle mechanics and practical workshop skills. The mechanical parts design, circuit layout and pin choice are best designed to suit your needs and bicycle. Neither of mine are a truly finished design but can be used as a starting point.

I’ve included every feature in the code so edit out what is not needed. Spreadsheet is provided for calculating values to set up the code for your hub.

How the project began, version 1 and workings of the CVT

Version 2 beginning of build video series

Code

enviolo_AutoShifter_v2.3.0.inoC#
/*
  Bicycle Automatic Shifter Version 2 by Edward Rose 2020

  Uses arduino bike speedometer w serial.print()code by Amanda Ghassaei 2012
  http://www.instructables.com/id/Arduino-Bike-Speedometer/
*/
#include <SPI.h>
#include <NRFLite.h>
#include <avdweb_AnalogReadFast.h>
#include <PWMServo.h>
#include <EEPROM.h>

PWMServo myservo;  // create servo object to control a servo
//define pins
//V1
//#define potpin A0                                   // analog pin used to detect handlebar button press
//#define derailSwitch A2                             // pin connected to deraileur position reed switch
//#define reed A3                                      // pin connected to wheel reed switch
//#define reed2 6                                      // pin connected to cadence reed switch
//V2
#define derailSwitch 6                                // pin connected to deraileur position reed switch
#define reed A2                                      // pin connected to wheel reed switch
#define reed2 A3                                     // pin connected to cadence reed switch
#define buttonPinL 3                                  // pin connected to left shifter button
#define buttonPinR 2                                  // pin connected to right shifter button
#define lightSwitch 4                                 // pin to operate rear light
//BOTH
int btLED = 5;                                       // pin connected to BT LED
//#define servopot A6                                   // pin reading voltage of servos internal pot
#define batVolt A7                                    // Li-ion voltage via potential divider OLD 10k/ 3.9k NEW 3.3k/ 2.7k

// changable values stored in EEPROM some calculated on spreadsheet
// use mode 5 to change via Serial monitor or Bluetooth
//Ratio set up
byte DScalarB = 0;                                   // big chainring
byte DScalarS = 0;                                   // small chainring
//Shifting
int ratioOffset = 0;                                // compensates for differances in ratio profile due to position of hub interface
int shiftSlope = 0;                                // shift slope depends on hub under/overdrive ratio and servo position
//Servo position ajustment values
int servoLowlimit = 0;                             // lower value used in servopos constrain function
int servoUpLimit = 0;                              // upper value used in servopos constrain function
//PID for cadence correction
float scalarP = 0;
float scalarI = 0;
//float scalarD = 500;                             // never found D to be necessary
//Power equation used to get multipler which = ((sin(powerAv / powerDiv)) * powerMult) + powerAdd;
int powerDiv = 0;
byte powerMult = 0;
byte powerAdd = 0;
//Distance measurment
int cirWheel = 0;                                   // wheel circumference in cm for odometer
int firstMult = 200;                                // increase to 2000 if using servopos 540 - 2450

int reedVal;                                         // status of rear wheel reed switch
int reedVal2;                                        // status of crank reed switch
long timer;                                          // time for one full rotation (in ms)
long timer2;                                         // time for one full rotation (in ms)
float Wheel;                                         // outputs of reed switch timer
int Cadence;
int Speed;
int Power;                                            // power recieved from power meter
float multipler;                                      // used in main servopos equation to get correct cadence
int errorC;                                           // cadence error
float prevErrorTotal;                                 // store offSetI
//float prevError;                                    // store prev errorC for offSetD
float offSetC;                                        // total constrained cadence offset
float offSetP;                                        // proportional offset
float offSetI;                                        // integral offset
//float offSetD;                                        // derivative offset
int maxReedCounter = 10;                              // min time (in ms) of one rotation (for debouncing)
int reedCounter;
int reedCounter2;
int buttonCounter = 0;                                // push button press counter
volatile int Counter = 0;
//int buttons;
bool buttonL;                                         // left shifter button status
bool buttonR;                                         // right shifter button status
int servopos;                                         // value written to servo
//int preServopos;
int switchState2 = 0;                                 // state of mode switch2 derailleur position
int DScalar;                                          // sets the correct scalar depending on derailleur position
int BTmode = 0;                                       // read from bluetooth
int mode = 3;                                         // mode set by Bluetooth
int cadenceSet;                                       // cadence setting
int setting = 0;                                      // gear/ cadence/ torque setting
int prevCadenceSet = 60;                              // delay gives time for the servo to move before being checked for error
int lipoPercent;                                      // auto shifter battery percent approx
int lipoPercentP;                                     // power meter battery percent approx
int servovolt;                                        // voltage read from servo pot by pin A6
int servoposError;
int zeroError = 0;                                    // From power meter diffence between min value and sensorValue
int torqueSet = 0;                                    // torque setting using shifter
struct dataStruct {                                   // data recived from power meter via nRF24L01
  int value1;                                         // power from power meter
  int value2;                                         // zeroError from power meter
  int value3;                                         // lipoPercent from power meter
} myData;
const int numReadings = 5;                            // power average divide by 2 for time in secs
int readings[numReadings];                            // for power average code
int index = 0;                                        // for power average code
int total = 0;                                        // for power average code
float powerAv = 0;                                    // averaged power to avoid oscillations
int codeTimer = 0;                                    // triggerd when cadence reed switch closes
int delayTime = 0;                                    // time of a crank rotation
int inputTorque = 0;
int outputTorque = 0;
int offTimer = 0;                                     // save time since bike last moved
int lightMode = 0;                                    // save rear light setting
int inPut = 0;                                        // light setting input from serial
int BTinPut = 0;                                      // program mode serial input
bool btStatus = 0;                                    // status of BT LED
unsigned long distance = 0;                           // add up cm travelled
unsigned long odometer;                               // total distance (100th of a km) travelled, stored in EEPROM
NRFLite _radio;


void setup() {
  Serial.begin(9600);
  _radio.init(0, 7, 10);          // radio id, CE pin, CSN pin

  myservo.attach(SERVO_PIN_A);   // attaches the servo on pin 9 to the servo object
  pinMode(reed, INPUT);
  pinMode(reed2, INPUT_PULLUP);  // POWER LATCH
  pinMode(derailSwitch, INPUT);
  pinMode(batVolt, INPUT);
  pinMode(btLED, INPUT);
  //pinMode(servopot, INPUT);
  //V2 only
  pinMode(buttonPinL, INPUT_PULLUP);
  pinMode(buttonPinR, INPUT_PULLUP);
  pinMode(lightSwitch, INPUT);

  EEPROM.get(20, DScalarB);
  EEPROM.get(21, DScalarS);
  EEPROM.get(22, ratioOffset);
  EEPROM.get(24, shiftSlope);
  EEPROM.get(26, servoLowlimit);
  EEPROM.get(28, servoUpLimit);
  EEPROM.get(30, scalarP);
  EEPROM.get(34, scalarI);
  EEPROM.get(38, powerDiv);
  EEPROM.get(40, powerMult);
  EEPROM.get(41, powerAdd);
  EEPROM.get(42, cirWheel);

  reedCounter = maxReedCounter;
  reedCounter2 = maxReedCounter;
  myservo.write(servoLowlimit);   // give servo a position while setup code finishes

  inPut = EEPROM.read(15);       // get light setting from before last switched off
  while (inPut != lightMode) {
    buttonPress();
    lightMode ++;
    if (lightMode > 7) {
      lightMode = 0;
    }
  }
  // TIMER SETUP- the timer interrupt allows precise timed measurements of the reed switch
  // for more info about configuration of arduino timers see http://arduino.cc/playground/Code/Timer1

  cli();//stop interrupts

  //set timer2 interrupt at 1kHz
  TCCR2A = 0;// set entire TCCR1A register to 0
  TCCR2B = 0;// same for TCCR1B
  TCNT2  = 0;
  // set timer count for 1kHz increments
  OCR2A = 249;// = (1/1000) / (16*10^6)/(1000*64) - 1
  // turn on CTC mode
  TCCR2A |= (1 << WGM21);
  // Set CS11 bit for 64 prescaler
  TCCR2B |= (1 << CS22);
  // enable timer compare interrupt
  TIMSK2 |= (1 << OCIE2A);

  sei();//allow interrupts
  //END TIMER SETUP
}


ISR(TIMER2_COMPA_vect) {                               // Interrupt at freq of 1kHz to measure reed switch
  reedVal = digitalRead(reed);                         // get status of wheel reed switch
  if (reedVal) {                                       // if reed switch is closed
    if (reedCounter == 0) {                            // min time between pulses has passed
      Wheel = ((DScalar * firstMult) / float(timer));  // calculate rotation rate of wheel, includes multipler for big/small chainrings
      Speed = ((4817) / float(timer));
      timer = 0;                                       // reset timer
      reedCounter = maxReedCounter;                    // reset reedCounter
      distance = distance + cirWheel;                  // add another wheel circumference to distance travelled
    }
  }
  else {                                               // if reed switch is open
    if (reedCounter > 0) {                             // don't let reedCounter go negative
      reedCounter -= 1;                                // decrement reedCounter
    }
  }
  if (timer > 2000) {
    Wheel = 0;
    Speed = 0;                                         // if no new pulses from reed switch tire is still, set mph to 0
  }
  else {
    timer += 1;                                        // increment timer
  }

  //TIMER1 CADENCE
  reedVal2 = digitalRead(reed2);           // get status of crank reed switch
  if (!reedVal2) {
    if (reedCounter2 == 0) {
      Cadence = (60000 / float(timer2));
      codeTimer = 1;                    // used to time shift at dead spot of pedal stroke
      delayTime = timer2;
      timer2 = 0;
      reedCounter2 = maxReedCounter;
    }
  }
  else {
    if (reedCounter2 > 0) {
      reedCounter2 -= 1;
    }
  }
  if (timer2 > 2000) {
    Cadence = 0;
  }
  else {
    timer2 += 1;
  }
  //V1 two buttons on one pin with resistors, analog read
  /* buttons = analogReadFast(potpin);          // check if a handlebar button has been pressed

    if (buttons > 600)                          // time button press
    {
     buttonCounter ++;
    }

    else if (buttons < 400)
    {
     buttonCounter --;
    }

    else
    {
     Counter = buttonCounter;
    }*/
  // V2 one button per pin
  buttonL = digitalRead(buttonPinL);
  buttonR = digitalRead(buttonPinR);

  if (buttonL == LOW)
  {
    buttonCounter ++;
  }
  else {
    if (buttonR == LOW)
    {
      buttonCounter --;
    }
    else
    {
      Counter = buttonCounter;
    }
  }
}
void buttonPress() {                // press button on rear light
  pinMode(lightSwitch, OUTPUT);
  digitalWrite(lightSwitch, LOW);
  delay(30);
  pinMode(lightSwitch, INPUT);
  delay(30);
}

void loop() {
  // time shift to happen at dead spots of pedal stroke
  if (Cadence > 38) {
    if (codeTimer) {
      delay (delayTime * 0.2); //0.25
      codeTimer = 0;
      mainCode();
      delay (delayTime * 0.7);  //0.5
      mainCode();
    }
  }
  else {
    mainCode();
    delay (500);
  }
}
void mainCode() {
  // power latch circiut switch off and save data
  /*if (Speed == 0) {
    offTimer++;
    }
    else {
    offTimer = 0;
    }*/
  // if BT LED connected to D5
  if (Speed > 0) {
    offTimer = 0;
  }
  else {
    btStatus = digitalRead(btLED);
    if (btStatus == LOW) {
      offTimer++;
    }
    else {
      offTimer = offTimer;
    }
  }

  if (offTimer > 30) {                        // switch off after approx secs
    EEPROM.get(10, odometer);                 // read last saved 100th of kms from EEPROM
    odometer = odometer + (distance / 1000);  // divide by 1000 to convert cm to 100th of a km
    EEPROM.put(10, odometer);                 // save distance to odometer
    pinMode(reed2, OUTPUT);                   // unlatch power latch circuit
    digitalWrite(reed2, LOW);
    distance = 0;                             // reset distance if Arduino doesn't switch off
  }
  // check for input from bluetooth to change mode
  if (Serial.available() > 0) {
    BTmode = Serial.read();

    if (BTmode == '1') {
      mode = 1;
      Serial.println("Manual");
    }
    else if (BTmode == '2') {
      mode = 2;
      Serial.println("CC on");
    }
    else if (BTmode == '3') {
      mode = 3;
      Serial.println("PowerOffset");
    }
    else if (BTmode == '4') {
      mode = 4;
      Serial.println("PowerSlope");
    }
    else if (BTmode == '5') {
      mode = 5;
      Serial.println("Program Mode");
    }
    else if (BTmode == '6') {
      mode = 6;
      Serial.println("Light Control");
    }
    else if (BTmode == '7') {
      mode = 7;
      Serial.println("EEPROM Read");
    }
    else if (BTmode == '8') {
      mode = 8;
      Serial.println("Servo manaul control");
    }
  }
  // change gear, cadence or torque setting
  if (Counter > 50 && Counter <= 500 && setting  < 8) {
    buttonCounter = 0;
    setting ++;
  }

  else if (Counter < -50 && Counter >= -500 && setting > -4) {
    buttonCounter = 0;
    setting  --;
  }

  else if (Counter > 500 || Counter < -500) {
    buttonCounter = 0;
    setting  = 0;
  }

  // measure battery %
  lipoPercent = ((analogReadFast(batVolt) * 0.55) - 385);    // give approx battery percentage but must be calibrated to the voltage divider used

  //data recived from power meter
  while (_radio.hasData()) {
    _radio.readData(&myData);

    Power = myData.value1;
    zeroError = myData.value2;
    lipoPercentP = myData.value3;
  }

  // get derailluer position and calculate torque if required and power data available
  switchState2 = digitalRead(derailSwitch);                 // check status of derailleur position reed switch

  if (switchState2 == HIGH)                                 // derailleur position check, DScaler setting and torque calcs
  {
    (DScalar = DScalarS);                                   // small chainring
    //inputTorque = ((Power * 9.5488)/(Cadence*1.89));        // calculate ratio chainring/sprocket, example 34/18
  }

  else
  {
    (DScalar = DScalarB);                                   // big chainring
    //inputTorque = ((Power * 9.5488)/(Cadence*2.78));        // calculate ratio chainring/sprocket, example 50/18
  }
  //outputTorque = ((Power * 9.5488)/(Speed * 12.46));

  if (mode == 7) // EEPROM Read
  {
    EEPROM.get(10, odometer);
    odometer = odometer + (distance / 1000);             // divide by 1000 to convert cm to 100th of a km
    Serial.print("Odometer ");
    Serial.print(odometer / 160);                        // divide by 100 for km, 160 for approx miles
    Serial.println(" miles");
    /*Serial.println("Press any key to continue");
      while (!Serial.available()) delay(10);
      inPut = Serial.read();*/
    delay(5000);
    mode = 3;
  }

  if (mode == 6) //Light control
  {
    Serial.println("Enter mode 1 - 7 or 0 for off");
    while (Serial.available() == 0) {}
    inPut = (Serial.parseFloat());
    if (inPut > 7) {
      Serial.println("Error");
    }
    else {
      EEPROM.write(15, inPut);
      while (inPut != lightMode) {
        buttonPress();
        Serial.println(lightMode);
        lightMode ++;
        if (lightMode > 7) {
          lightMode = 0;
        }
      }
      mode = 3;
    }
  }
  if (mode == 5) //Program mode
  {
    Serial.println();
    Serial.println("Select value to change or 13 to exit");
    Serial.print("1.  DScalarB       ");
    Serial.println(DScalarB);
    Serial.print("2.  DScalarS       ");
    Serial.println(DScalarS);
    Serial.print("3.  ratioOffset    ");
    Serial.println(ratioOffset);
    Serial.print("4.  shiftSlope     ");
    Serial.println(shiftSlope);
    Serial.print("5.  servoLowlimit  ");
    Serial.println(servoLowlimit);
    Serial.print("6.  servoUpLimit   ");
    Serial.println(servoUpLimit);
    Serial.print("7.  scalarP        ");
    Serial.println(scalarP);
    Serial.print("8.  scalarI        ");
    Serial.println(scalarI);
    Serial.print("9.  powerDiv       ");
    Serial.println(powerDiv);
    Serial.print("10. powerMult      ");
    Serial.println(powerMult);
    Serial.print("11. powerAdd       ");
    Serial.println(powerAdd);
    Serial.print("12. cirWheel       ");
    Serial.println(cirWheel);
    while (Serial.available() == 0) {}
    BTinPut = Serial.parseInt();


    if (BTinPut == 1) {
      Serial.println();
      Serial.print("DScalarB Enter value ");
      while (Serial.available() == 0) {}
      DScalarB = (Serial.parseInt());
      EEPROM.put(20, DScalarB);
      Serial.println();
      Serial.println(" Saved");
    }
    else if (BTinPut == 2) {
      Serial.println();
      Serial.print("DScalarS Enter value ");
      while (Serial.available() == 0) {}
      DScalarS = (Serial.parseInt());
      EEPROM.put(21, DScalarS);
      Serial.println();
      Serial.println(" Saved");
    }
    else if (BTinPut == 3) {
      Serial.println();
      Serial.print("ratioOffset Enter value ");
      while (Serial.available() == 0) {}
      ratioOffset = (Serial.parseInt());
      EEPROM.put(22, ratioOffset);
      Serial.println();
      Serial.println(" Saved");
    }
    else if (BTinPut == 4) {
      Serial.println();
      Serial.print("shiftSlope Enter value ");
      while (Serial.available() == 0) {}
      shiftSlope = (Serial.parseInt());
      EEPROM.put(24, shiftSlope);
      Serial.println();
      Serial.println(" Saved");
    }
    else if (BTinPut == 5) {
      Serial.println();
      Serial.print("servoLowlimit Enter value ");
      while (Serial.available() == 0) {}
      servoLowlimit = (Serial.parseInt());
      EEPROM.put(26, servoLowlimit);
      Serial.println();
      Serial.println(" Saved");
    }
    else if (BTinPut == 6) {
      Serial.println();
      Serial.print("servoUpLimit Enter value ");
      while (Serial.available() == 0) {}
      servoUpLimit = (Serial.parseInt());
      EEPROM.put(28, servoUpLimit);
      Serial.println();
      Serial.println(" Saved");
    }
    else if (BTinPut == 7) {
      Serial.println();
      Serial.print("scalarP Enter value ");
      while (Serial.available() == 0) {}
      scalarP = (Serial.parseFloat());
      EEPROM.put(30, scalarP);
      Serial.println();
      Serial.println(" Saved");
    }
    else if (BTinPut == 8) {
      Serial.println();
      Serial.print("scalarI Enter value ");
      while (Serial.available() == 0) {}
      scalarI = (Serial.parseFloat());
      EEPROM.put(34, scalarI);
      Serial.println();
      Serial.println(" Saved");
    }
    else if (BTinPut == 9) {
      Serial.println();
      Serial.print("powerDiv Enter value ");
      while (Serial.available() == 0) {}
      powerDiv = (Serial.parseInt());
      EEPROM.put(38, powerDiv);
      Serial.println();
      Serial.println(" Saved");
    }
    else if (BTinPut == 10) {
      Serial.println();
      Serial.print("powerMult Enter value ");
      while (Serial.available() == 0) {}
      powerMult = (Serial.parseInt());
      EEPROM.put(40, powerMult);
      Serial.println();
      Serial.println(" Saved");
    }
    else if (BTinPut == 11) {
      Serial.println();
      Serial.print("powerAdd Enter value ");
      while (Serial.available() == 0) {}
      powerAdd = (Serial.parseInt());
      EEPROM.put(41, powerAdd);
      Serial.println();
      Serial.println(" Saved");
    }
    else if (BTinPut == 12) {
      Serial.println();
      Serial.print("cirWheel Enter value ");
      while (Serial.available() == 0) {}
      cirWheel = (Serial.parseInt());
      EEPROM.put(42, cirWheel);
      Serial.println();
      Serial.println(" Saved");
    }
    else if (BTinPut == 13) {
      mode = 3;
      Serial.println("Return to run");
    }
  }
  else if (mode == 8) //servo manual control
  {
    Serial.println("Use buttons to move servo or enter a value");
    Serial.println("Enter negative number to exit");

    while (Serial.available() == 0) {
      if (buttonCounter > 50) {
        servopos ++;
        buttonCounter = 0;
        myservo.write(servopos);
        Serial.println(servopos);
      }

      else if (buttonCounter < -50) {
        servopos  --;
        buttonCounter = 0;
        myservo.write(servopos);
        Serial.println(servopos);
      }
    }
    servopos = (Serial.parseInt());
    if (servopos >= 0) {
      myservo.write(servopos);
      Serial.println(servopos);
    }
    else {
      mode = 3;
      Serial.println("Return to run");
    }
  }
  else if (mode == 1) //manual
  { /*// alternative manaul

      if (buttonCounter > 80 && servopos  < servoUpLimit){
         servopos ++;
         buttonCounter = 0;  }

      else if (buttonCounter < -80 && servopos > servoLowlimit){
         servopos  --;
         buttonCounter = 0;  }

      myservo.write(servopos);
    */
    if (setting == 8)
    {
      servopos = (servoUpLimit);
    }

    else if (setting <= 7 && setting >= -3)
    {
      servopos = ((((servoUpLimit - servoLowlimit) / 12) * (setting + 4)) + servoLowlimit);
    }

    else if (setting == -4)
    {
      servopos = (servoLowlimit);
    }
    myservo.write(servopos);

    //servovolt = analogRead(servopot);                     // read servos internal pot to get real position
    //servoposError = servovolt - (((servopos - 2) * 2.84) + 83); // calculate error in servos position

    Serial.print("E");
    Serial.print(Power);
    Serial.print(", ");
    Serial.print(Cadence);
    Serial.print(", ");
    Serial.print(Speed);
    Serial.print(", ");
    Serial.print(setting);
    Serial.print(", ");
    Serial.print(servopos);    // (servopos);
    Serial.print(", ");
    Serial.print(lipoPercentP);    //(servoposError);
    Serial.print(", ");
    Serial.println(zeroError);

  }

  else if (Power == 0 || mode == 2) // Constant cadence
  {
    if (setting == 4)
    {
      cadenceSet = 50;
    }

    else if (setting == 3)
    {
      cadenceSet = 60;
    }

    else if (setting == 2)
    {
      cadenceSet = 65;
    }

    else if (setting == 1)
    {
      cadenceSet = 68;
    }

    else if (setting == 0)
    {
      cadenceSet = 72;
    }

    else if (setting == -1)
    {
      cadenceSet = 75;
    }

    else if (setting == -2)
    {
      cadenceSet = 80;
    }

    else if (setting == -3)
    {
      cadenceSet = 90;
    }

    else if (setting == -4)
    {
      cadenceSet = 100;
    }
    multipler = shiftSlope / cadenceSet;

    errorC = (Cadence - prevCadenceSet);

    // calculate offsetP
    if ((Cadence > (cadenceSet + 10)) && (Cadence < 200))
    {
      offSetP = (multipler / scalarP);
    }
    else if (Cadence < 38)
    {
      offSetP = 0;
    }
    else {
      offSetP = (sin((errorC) * 0.15)) * (multipler / scalarP);
    }

    // calculate offsetI
    if (Cadence < 38)
    {
      offSetI = 0;
    }
    else {
      offSetI = constrain ((prevErrorTotal + (errorC * scalarI)), -1.5, 3); //-1.5, 1.5
    }
    prevErrorTotal = offSetI;
    /*
      // calculate offsetD
      if (Cadence < 38)
         {offSetI = 0;}
      else {
      offSetD = constrain(((errorC - prevError) * scalarD),-3,15);
                   }
      prevError = errorC;
    */
    offSetC = offSetP + offSetI; // + offSetD;

    servopos = constrain((ratioOffset + (Wheel * (multipler + offSetC))), servoLowlimit, servoUpLimit); // calculates servopos, sets the limits on under/overdrive.
    //multiplier sets the cadence, fixed offset compensates for difference in ratio profile.
    myservo.write(servopos);

    //CALIBRATE
    /*servovolt = analogRead(servopot);
      servoposError = servovolt-(((servoposD-2)*2.84)+83);

      Serial.print("E");                // send values via bluetooth or serial monitor for graphing or logging.
      Serial.print(Power);            // "E" enables an app called Bluetooth Graphics to recognise it as data.
      Serial.print(", ");
      Serial.print(Cadence);
      Serial.print(", ");
      Serial.print(Speed);
      Serial.print(", ");
      Serial.print(switchState2);
      Serial.print(", ");
      Serial.print(servoposD);     //cadenceSet
      Serial.print(", ");
      Serial.print(servoposError);   //lipoPercentP
      Serial.print(", ");
      Serial.println(zeroError);
    */

    Serial.print("E");                // send values via bluetooth or serial monitor for graphing or logging.
    Serial.print(Power);            // "E" enables an app called Bluetooth Graphics to recognise it as data.
    Serial.print(", ");
    Serial.print(Cadence);
    Serial.print(", ");
    Serial.print(Speed);
    Serial.print(", ");
    Serial.print(setting);  // lipoPercent
    Serial.print(", ");
    Serial.print(lipoPercent);    //cadenceSet, servopos, outputTorque
    Serial.print(", ");
    Serial.print(lipoPercentP);
    Serial.print(", ");
    Serial.println(zeroError);
    //APP
    /*
      Serial.print(" "); //Prevents The operation * cannot accept the arguments: , [*empty-string*], [-0.24]
      Serial.print(Power);
      Serial.print(",");
      Serial.print(Cadence);
      Serial.print(",");
      Serial.print(Speed);
      Serial.print(",");
      Serial.print(cadenceSet);
      Serial.print(",");
      Serial.print(offSetC);
      Serial.print(",");
      Serial.print(setting);
      Serial.print(",");
      Serial.print(lipoPercent);
      Serial.print(",");
      Serial.print(lipoPercentP);
      Serial.print(",");
      Serial.print(zeroError);
      Serial.print(",");
      Serial.print(inputTorque);
      Serial.print(",");
      Serial.println(outputTorque); */

    prevCadenceSet = cadenceSet;     //store prev cadence setpoint
    // preServoposD = servoposD;
  }

  else if (Power > 0 && mode == 3) // Power1 buttons change powerAdd
  {
    // average power 2.5 secs worked best less results in oscillations, more to much lag
    total = total - readings[index];
    readings[index] = Power;
    total = total + readings[index];
    index = index + 1;
    if (index >= numReadings)
      index = 0;
    powerAv = total / numReadings;

    multipler = ((sin(powerAv / powerDiv)) * powerMult) + (setting + powerAdd);
    cadenceSet = shiftSlope / multipler;

    errorC = (Cadence - cadenceSet);
    // calculate offsetP
    if ((Cadence > (cadenceSet + 10)) && (Cadence < 200))
    {
      offSetP = (multipler / 11);
    }
    else if (Cadence < 38)
    {
      offSetP = 0;
    }
    else {
      offSetP = (sin((errorC) * 0.15)) * (multipler / 12);
    }

    // calculate offsetI
    if (Cadence < 38)
    {
      offSetI = 0;
    }
    else {
      offSetI = constrain ((prevErrorTotal + (errorC * scalarI)), -1.5, 1.5);
    }
    prevErrorTotal = offSetI;

    offSetC = offSetP + offSetI; // + offSetD;

    servopos = constrain((ratioOffset + (Wheel * (multipler + offSetC))), servoLowlimit, servoUpLimit);

    myservo.write(servopos);

    //CALIBRATE
    /*servovolt = analogRead(servopot);
      servoposError = servovolt-(((servoposD-2)*2.84)+83);

      Serial.print("E");                // send values via bluetooth or serial monitor for graphing or logging.
      Serial.print(Power);            // "E" enables an app called Bluetooth Graphics to recognise it as data.
      Serial.print(", ");
      Serial.print(Cadence);
      Serial.print(", ");
      Serial.print(Speed);
      Serial.print(", ");
      Serial.print(switchState2);
      Serial.print(", ");
      Serial.print(servoposD);     //cadenceSet
      Serial.print(", ");
      Serial.print(servoposError);   //lipoPercentP
      Serial.print(", ");
      Serial.println(zeroError);
    */
    Serial.print("E");
    Serial.print(Power);
    Serial.print(", ");
    Serial.print(Cadence);
    Serial.print(", ");
    Serial.print(Speed);
    Serial.print(", ");
    Serial.print(setting);     // lipoPercent,inputTorque
    Serial.print(", ");
    Serial.print(lipoPercent);    //cadenceSet, servopos, outputTorque
    Serial.print(", ");
    Serial.print(lipoPercentP);
    Serial.print(", ");
    Serial.println(zeroError);

    /* //APP
      Serial.print(" "); //Prevents The operation * cannot accept the arguments: , [*empty-string*], [-0.24]
      Serial.print(Power);
      Serial.print(",");
      Serial.print(Cadence);
      Serial.print(",");
      Serial.print(Speed);
      Serial.print(",");
      Serial.print(cadenceSet);
      Serial.print(",");
      Serial.print(offSetC);
      Serial.print(",");
      Serial.print(setting);
      Serial.print(",");
      Serial.print(lipoPercent);
      Serial.print(",");
      Serial.print(lipoPercentP);
      Serial.print(",");
      Serial.print(zeroError);
      Serial.print(",");
      Serial.print(inputTorque);
      Serial.print(",");
      Serial.println(outputTorque);
    */
  }

  else if (Power > 0 && mode == 4) // Power2 buttons change powerMult
  {
    // average power
    total = total - readings[index];
    readings[index] = Power;
    total = total + readings[index];
    index = index + 1;
    if (index >= numReadings)
      index = 0;
    powerAv = total / numReadings;

    multipler = ((sin(powerAv / powerDiv)) * (setting + powerMult)) + powerAdd;
    cadenceSet = shiftSlope / multipler;

    errorC = (Cadence - cadenceSet);
    // calculate offsetP
    if ((Cadence > (cadenceSet + 10)) && (Cadence < 200))
    {
      offSetP = (multipler / 11);
    }
    else if (Cadence < 38)
    {
      offSetP = 0;
    }
    else {
      offSetP = (sin((errorC) * 0.15)) * (multipler / 12);
    }

    // calculate offsetI
    if (Cadence < 38)
    {
      offSetI = 0;
    }
    else {
      offSetI = constrain ((prevErrorTotal + (errorC * scalarI)), -1.5, 1.5);
    }
    prevErrorTotal = offSetI;

    offSetC = offSetP + offSetI; // + offSetD;

    servopos = constrain((ratioOffset + (Wheel * (multipler + offSetC))), servoLowlimit, servoUpLimit);

    myservo.write(servopos);

    Serial.print("E");
    Serial.print(Power);
    Serial.print(", ");
    Serial.print(Cadence);
    Serial.print(", ");
    Serial.print(Speed);
    Serial.print(", ");
    Serial.print(setting);     // lipoPercent,inputTorque
    Serial.print(", ");
    Serial.print(lipoPercent);    //cadenceSet, servopos, outputTorque
    Serial.print(", ");
    Serial.print(lipoPercentP);
    Serial.print(", ");
    Serial.println(zeroError);
    /*
      Serial.print("E");
      Serial.print(Power);
      Serial.print(", ");
      Serial.print(Cadence);
      Serial.print(", ");
      Serial.print(Speed);
      Serial.print(", ");
      Serial.print(setting);     //lipoPercent
      Serial.print(", ");
      Serial.print(cadenceSet);     //servopos
      Serial.print(", ");
      Serial.print(offSetC);   //lipoPercentP
      Serial.print(", ");
      Serial.println(zeroError);

      //APP
      Serial.print(" "); //Prevents The operation * cannot accept the arguments: , [*empty-string*], [-0.24]
      Serial.print(Power);
      Serial.print(",");
      Serial.print(Cadence);
      Serial.print(",");
      Serial.print(Speed);
      Serial.print(",");
      Serial.print(cadenceSet);
      Serial.print(",");
      Serial.print(offSetC);
      Serial.print(",");
      Serial.print(setting);
      Serial.print(",");
      Serial.print(lipoPercent);
      Serial.print(",");
      Serial.print(lipoPercentP);
      Serial.print(",");
      Serial.print(zeroError);
      Serial.print(",");
      Serial.print(inputTorque);
      Serial.print(",");
      Serial.println(outputTorque);
    */
  }
}

Schematics

Auto Shifter Spreadsheet
Calculate values for code using spreadsheet
auto_shifter_calcs_FkPqRmHimN.xlsx

Comments

Author

Default
erose
  • 1 project
  • 0 followers

Additional contributors

Published on

February 2, 2020

Members who respect this project

DefaultDefault

and 3 others

See similar projects
you might like

Similar projects you might like

Automatic Servo Tester with Arduino Nano

Project tutorial by Bekir Bilge

  • 2,166 views
  • 0 comments
  • 11 respects

Automatic Sliding Door for the Garage

Project tutorial by DVDMDN

  • 26,357 views
  • 49 comments
  • 101 respects

Automatic Coffee Machine

Project tutorial by Thomas sxt

  • 25,056 views
  • 7 comments
  • 21 respects

Animated RGB Wall Clock

Project tutorial by TheTNR

  • 14,899 views
  • 11 comments
  • 41 respects

Automatic LED Stairs Lighting Arduino Shield

Project tutorial by Aivaredze

  • 11,488 views
  • 3 comments
  • 23 respects

Automatic Device Tester with Arduino

Project tutorial by Brian Lough

  • 6,945 views
  • 13 comments
  • 37 respects
Add projectSign up / Login