How to add a throttle to a Carrera Vengeance E Spec

vfr400

Esteemed Pedelecer
Jun 12, 2011
9,822
3,993
Basildon
Maybe the problem is nothing to do with your code. Have you got a low pass filter on your Arduino's output to the torque sensor input?
 

Woosh

Trade Member
May 19, 2012
20,225
16,819
Southend on Sea
wooshbikes.co.uk
Have you got a low pass filter on your Arduino's output to the torque sensor input?
No, I thought the 1ms time division in the loop is sufficient to filter out transients. The output on A5 (controller's torque) is shaped by a simulated DAC, basically switching the pin to 5V with a pulse width modulator
the torque output seems to work OK when the throttle is in charge.

At the moment, I am trying to solve the mystery why the motor fires up on its own.
I have thought that the motor won't start until the controller receives the sync signal on the PAS wire. Raj said that his motor starts on its own when:
1. he joined the PAS wires outside the arduino
2. he joined the PAS wires on pin A2.

The problem is I don't know precisely how the Suntour system works.
I assume it's similar to standard Chinese and I only have for reference a picture of the torque signal that Evian posted sometime ago.
 
Last edited:

Evian1040

Pedelecer
Jul 7, 2020
75
9
Tried compiling that but it gave me "OUTPUT_PULLUP was not declared in this scope"
It's either the TorqueOutputPin or the CadenceOutputPin that is giving that error
 

Woosh

Trade Member
May 19, 2012
20,225
16,819
Southend on Sea
wooshbikes.co.uk
sorry about that.
Could you change the setup() to;

void setup() {
pinMode(CadenceOutputPin, OUTPUT);
pinMode(TorqueInputPin, INPUT_PULLUP);
pinMode(TorqueOutputPin, OUTPUT); //ControllerTorquePin
pinMode(CadenceInputPin, INPUT_PULLUP); //PASCadencePin

startMillis = millis();
// Serial.begin(9600);
}
 

vfr400

Esteemed Pedelecer
Jun 12, 2011
9,822
3,993
Basildon
No, I thought the 1ms time division in the loop is sufficient to filter out transients. The output on A5 (controller's torque) is shaped by a simulated DAC, basically switching the pin to 5V with a pulse width modulator
the torque output seems to work OK when the throttle is in charge.
It was the same with the Speedict, which was probably just an Arduino in a wrapper. They provided a special cable for the throttle that had a low pass filter in it. The Speedict took your PAS and throttle and reprocessed them to do some clever things. If you used a normal cable, it worked fine for a few minutes, then seemed to get a mind of its own. A normal controller can't cope with a pwm signal on the throttle input. It has to be smooth analogue.

The controller is probably just a standard non-zero start one using the pulsed input as the PAS signal and the analogue as a throttle signal. Both signals from the torque sensor arrangement are in the same range as a throttle and PAS. Why would they waste time developing a special controller?

Remember a couple of years back, when a guy made a video of his Crossfire with a push-button throttle. All he did was short the throttle signal wire to 5v. He got that idea after his cable got damaged and the bike started going at full speed by itself. I can't find the link now.
 

Woosh

Trade Member
May 19, 2012
20,225
16,819
Southend on Sea
wooshbikes.co.uk
They provided a special cable for the throttle that had a low pass filter in it. The Speedict took your PAS and throttle and reprocessed them to do some clever things. If you used a normal cable, it worked fine for a few minutes, then seemed to get a mind of its own. A normal controller can't cope with a pwm signal on the throttle input. It has to be smooth analogue.
There are two errors: 21 and 22.
21 relates to the rest state of the torque signal. When you stop pedalling, the torque voltage is 3.85V. When active, the torque voltage is linear between 1.5V (at 0NM) and 3.85V (80NM).
Both Evian and Raj confirmed that characteristics independently with their measurements.
That suggests that the sensor's torque has an open collector on the output. That's why I changed the pinMode for A2 to input_pullup so to add a load to the open collector to stop any drift if the sensor has a tri-state output.

We found a solution for error 21 last Monday in version 8 of the code.
However, if you stop pedalling or using the throttle, error 22 shows up 3 seconds later. Both Raj and Evian confirmed that.

Evian could not help much in the last couple of days, so I only had Raj's test results and they are strange: the motor starts on its own or shows error 22 with the signal wires joined outside the arduino board.
How can you explain that?
 
Last edited:

Woosh

Trade Member
May 19, 2012
20,225
16,819
Southend on Sea
wooshbikes.co.uk
Remember a couple of years back, when a guy made a video of his Crossfire with a push-button throttle. All he did was short the throttle signal wire to 5v. He got that idea after his cable got damaged and the bike started going at full speed by itself. I can't find the link now.
that may explain what Raj found.
 

Woosh

Trade Member
May 19, 2012
20,225
16,819
Southend on Sea
wooshbikes.co.uk
I have revised the code to addres vfr400's comments.
1. controller's non zero start: cadence input set to input_pulldown, torque input set to input_pullup.
2. low pass filter: previous code had 1ms time division for low pass, new code has half pulse (about 25ms) time division. New code records the current pulse and plays it back 1 pulse later.

Code:
// Version 9
// use internal pullup/pulldown resistors on input pins.
// implement low pass filter using recording and playback

const byte ThrottlePin = A0;
const byte CadenceInputPin = A2;
const byte TorqueInputPin = A3;
const byte TorqueOutputPin = A5;
const byte CadenceOutputPin = 13;
const byte TorqueAtRest = 198;

// I am on throttle
int pulseoff = 25; // milliseconds when D13 is low
int pulseon = 25;   // milliseconds when D13 is high 4Hz

// I am pedalling.
// I record the cadence pulse first.

bool cadencestate = false;
int cadencepulselength = 991;
int cadencepulseonlength = 0;
int cadencepulseofflength = 0;
unsigned long torqueontotal =0;
unsigned long torqueofftotal =0;

// then play it back, average values are used to implement low pass filter
int playbackpulseon = 25;
int playbackpulseoff = 25;
int playbacktorqueon = 0;
int playbacktorqueoff = 0;

bool pulse = false;
unsigned long startMillis;
unsigned long currentMillis;

void setup() {
    pinMode(CadenceOutputPin, OUTPUT);
    pinMode(TorqueInputPin, INPUT_PULLUP); // cadence input default high
    pinMode(TorqueOutputPin, OUTPUT); //ControllerTorquePin
    pinMode(CadenceInputPin, INPUT); //PASCadencePin
    digitalWrite(TorqueOutputPin, LOW);
    startMillis = millis();
    // Serial.begin(9600);
}

void loop() {
    if (millis()>currentMillis) // run once every millisecond
    {
        currentMillis = millis();

        // ****** Start recording current pulse ******
       
        if (cadencepulselength < 1000) cadencepulselength++;

        if (cadencestate)
        {
            // on cadencestate transtion from high to low, reset cadencepulselength and transfer accummulator to playback
            if (analogRead(CadenceInputPin) < 512)
            {
               cadencepulselength = 0;
               cadencestate = false;
               playbackpulseon = pulseonlength;
               playbackpulseoff = pulseofflength;
               if (pulseonlength >0) playbacktorqueon = torqueontotal/pulseonlength;
               if (pulseofflength >0) playbacktorqueoff = torqueofftotal/pulseofflength;
               torqueontotal = 0;
               torqueofftotal = 0;
               pulseonlength = 0;
               pulseofflength = 0;              
            }
            else // continue with pulse high
            {
                pulseonlength++;
                torqueontotal = torqueontotal + analogRead(torqueInputPin)/4;
            }
        }
        else // if cadencestate is low
        {
            if (analogRead(CadenceInputPin) < 512) // continue with pulse low
            {
                pulseofflength++;
                torqueofftotal = torqueofftotal + analogRead(torqueInputPin)/4;          
            }
        }
        if (analogRead(CadenceInputPin) > 512) cadencestate = true;
        if (cadencepulselength > 200) // defaults when stopping
        {
            cadencestate = true;
            torqueofftotal =0 ;
            torqueontotal =0;
           
        }
        // ****** end recording current pulse ******

        // check throttle

        int torqueval = analogRead(ThrottlePin) / 4; // Read the value from the throttle pin A0
        if (torqueval < 75)  // disable throttle if voltage < 1.5V
        {
            // ****** I'm pedalling, play back last pulse *****
           
            pulse = true;
            if (currentMillis > (startMillis + playbackpulseon)) pulse = false;
            if (currentMillis > (startMillis + playbackpulseon + playbackpulseoff)) startMillis = millis();     // reset timer clock
            if (pulse)
            {
                digitalWrite(CadenceOutputPin, HIGH);
                analogWrite(TorqueOutputPin, playbacktorqueon);
            }
            else
            {
                digitalWrite(CadenceOutputPin, LOW);
                analogWrite(TorqueOutputPin, playbacktorqueoff);
            }
            // ****** end play back last pulse *****
        }
        else
        {
            //I'm using the throttle
            analogWrite(TorqueOutputPin, torqueval);
            pulse = true;
            if (currentMillis > (startMillis + pulseon)) pulse = false;
            if (currentMillis > (startMillis + pulseon + pulseoff)) startMillis = millis();     // reset timer clock
            if (pulse)  digitalWrite(CadenceOutputPin, HIGH);
            else digitalWrite(CadenceOutputPin, LOW);
        }
    }
}
 
Last edited:

rajeshtailor

Pedelecer
Jun 5, 2020
170
3
I have revised the code to addres vfr400's comments.
1. controller's non zero start: cadence input set to input_pulldown, torque input set to input_pullup.
2. low pass filter: previous code had 1ms time division for low pass, new code has half pulse (about 25ms) time division. New code records the current pulse and plays it back 1 pulse later.

Code:
// Version 9
// use internal pullup/pulldown resistors on input pins.
// implement low pass filter using recording and playback

const byte ThrottlePin = A0;
const byte CadenceInputPin = A2;
const byte TorqueInputPin = A3;
const byte TorqueOutputPin = A5;
const byte CadenceOutputPin = 13;
const byte TorqueAtRest = 198;

// I am on throttle
int pulseoff = 25; // milliseconds when D13 is low
int pulseon = 25;   // milliseconds when D13 is high 4Hz

// I am pedalling.
// I record the cadence pulse first.

bool cadencestate = false;
int cadencepulselength = 991;
int cadencepulseonlength = 0;
int cadencepulseofflength = 0;
unsigned long torqueontotal =0;
unsigned long torqueofftotal =0;

// then play it back, average values are used to implement low pass filter
int playbackpulseon = 25;
int playbackpulseoff = 25;
int playbacktorqueon = 0;
int playbacktorqueoff = 0;

bool pulse = false;
unsigned long startMillis;
unsigned long currentMillis;

void setup() {
    pinMode(CadenceOutputPin, OUTPUT);
    pinMode(TorqueInputPin, INPUT_PULLUP); // cadence input default high
    pinMode(TorqueOutputPin, OUTPUT); //ControllerTorquePin
    pinMode(CadenceInputPin, INPUT_PULLDOWN); //PASCadencePin default low
    digitalWrite(TorqueOutputPin, LOW);
    startMillis = millis();
    // Serial.begin(9600);
}

void loop() {
    if (millis()>currentMillis) // run once every millisecond
    {
        currentMillis = millis();

        // ****** Start recording current pulse ******
       
        if (cadencepulselength < 1000) cadencepulselength++;

        if (cadencestate)
        {
            // on cadencestate transtion from high to low, reset cadencepulselength and transfer accummulator to playback
            if (analogRead(CadenceInputPin) < 512)
            {
               cadencepulselength = 0;
               cadencestate = false;
               playbackpulseon = pulseonlength;
               playbackpulseoff = pulseofflength;
               if (pulseonlength >0) playbacktorqueon = torqueontotal/pulseonlength;
               if (pulseofflength >0) playbacktorqueoff = torqueofftotal/pulseofflength;
               torqueontotal = 0;
               torqueofftotal = 0;
               pulseonlength = 0;
               pulseofflength = 0;              
            }
            else // continue with pulse high
            {
                pulseonlength++;
                torqueontotal = torqueontotal + analogRead(torqueInputPin)/4;
            }
        }
        else // if cadencestate is low
        {
            if (analogRead(CadenceInputPin) < 512) // continue with pulse low
            {
                pulseofflength++;
                torqueofftotal = torqueofftotal + analogRead(torqueInputPin)/4;          
            }
        }
        if (analogRead(CadenceInputPin) > 512) cadencestate = true;
        if (cadencepulselength > 200) // defaults when stopping
        {
            cadencestate = true;
            torqueofftotal =0 ;
            torqueontotal =0;
           
        }
        // ****** end recording current pulse ******

        // check throttle

        int torqueval = analogRead(ThrottlePin) / 4; // Read the value from the throttle pin A0
        if (torqueval < 75)  // disable throttle if voltage < 1.5V
        {
            // ****** I'm pedalling, play back last pulse *****
           
            pulse = true;
            if (currentMillis > (startMillis + playbackpulseon)) pulse = false;
            if (currentMillis > (startMillis + playbackpulseon + playbackpulseoff)) startMillis = millis();     // reset timer clock
            if (pulse)
            {
                digitalWrite(CadenceOutputPin, HIGH);
                analogWrite(TorqueOutputPin, playbacktorqueon);
            }
            else
            {
                digitalWrite(CadenceOutputPin, LOW);
                analogWrite(TorqueOutputPin, playbacktorqueoff);
            }
            // ****** end play back last pulse *****
        }
        else
        {
            //I'm using the throttle
            analogWrite(TorqueOutputPin, torqueval);
            pulse = true;
            if (currentMillis > (startMillis + pulseon)) pulse = false;
            if (currentMillis > (startMillis + pulseon + pulseoff)) startMillis = millis();     // reset timer clock
            if (pulse)  digitalWrite(CadenceOutputPin, HIGH);
            else digitalWrite(CadenceOutputPin, LOW);
        }
    }
}
Looks like INPUT_PULLDOWN isn't valid on an Arduino Nano. Receiving the "INPUT_PULLDOWN was not declared in this scope" error.
 

Woosh

Trade Member
May 19, 2012
20,225
16,819
Southend on Sea
wooshbikes.co.uk
Hi Raj, change that line to
pinMode(CadenceInputPin, INPUT);
 

rajeshtailor

Pedelecer
Jun 5, 2020
170
3
Hi Raj, change that line to
pinMode(CadenceInputPin, INPUT);
I have tested the v9 release of the code with the following code changes:

1. Declared pulseonlength and pulseofflength as integer and changed torqueinputpin to TorqueInputPin

Results as follows:

1. On Power on received error 21
2. Engage throttle then power on, motor runs for approx 10 secs then error 22 received.
3. Engage pedalling then power on, motor runs for approx 10secs then error 22 received.

So just so we are on the same page is it correct to assume error 21 is Torque related and error 22 is cadence related?
 

Woosh

Trade Member
May 19, 2012
20,225
16,819
Southend on Sea
wooshbikes.co.uk
yes, I agree and thank you for testing it out.
I am out of idea at the moment.
The best version is still version 8.
Do you have a resistor across A2-GND?
That's what INPUT_PULLDOWN does.
 

rajeshtailor

Pedelecer
Jun 5, 2020
170
3
yes, I agree and thank you for testing it out.
I am out of idea at the moment.
The best version is still version 8.
Do you have a resistor across A2-GND?
That's what INPUT_PULLDOWN does.
Yeah I think your right the best version to date is v8.

I don't have a resister between A2-GND let me try it with a resister.
 

Woosh

Trade Member
May 19, 2012
20,225
16,819
Southend on Sea
wooshbikes.co.uk
we don't have a problem with the torque sensor low pass filter.
The problem is with the cadence replication.
see post #269
Raj had the torque wires joined together OUTSIDE the arduino board, the problem remains.
He switched on the LCD and this happened:
"I connected A5 [ controller side wire] to A3 [sensor side wire] and after approx 3 seconds I received error 22. "
A3 and A5 are wires here, not pins.

see post #294

I am stuck. Can't figure this out.
Put your Sherlock hat on and think about that for us.
 

Woosh

Trade Member
May 19, 2012
20,225
16,819
Southend on Sea
wooshbikes.co.uk
this is the wiring that produces error 22 after 3 seconds:
D13 to controller's cadence
A2 to sensor's cadence.
controller's torque to sensor's torque
Can anyone see an explanation?

see post #294

error22.jpg
 

rajeshtailor

Pedelecer
Jun 5, 2020
170
3
this is the wiring that produces error 22 after 3 seconds:
D13 to controller's cadence
A2 to sensor's cadence.
controller's torque to sensor's torque
Can anyone see an explanation?

see post #294

View attachment 41216
I was reading this post:

ArduinoPins

And looks like some pins operate differently to others? I'm not an electrical genius but looks like D13 operates at a higher frequency, could this me an issue?
 

Woosh

Trade Member
May 19, 2012
20,225
16,819
Southend on Sea
wooshbikes.co.uk
You can try moving the controller PAS wire to another pin to see if error 22 pops up again. For example move it to D1 and change the code:

const byte TorqueOutputPin = D1;
 
Last edited:

Advertisers