Raspberry Pi Pico motorcycle quickshifter issue

Hey everyone, this is my first time creating an argument here. I built a quickshifter for my motorcycle (i don't know if you are familiar with it). Its fuction is not hard, it reads the TPS sensor of the throttle, and it reads the number of impulses from the negative wire of the ignition coil (with an appropriate circuit, which uses an optocoupler), which i use to basically read the engine RPMs. Based on these two inputs values, the program changes the duration of the power cutoff. The ignition coil is a CDI. The power is cutted off when i press a switch (which is normally open, and becomes closed when pressed) implemented in the shifter rod, it is a switch specifically designed for quickshifters. The cutoff works in the following way: my circuit enables an IGBT transistor which grounds the negative wire of the coil (so it charges the ignition coil and therefore the spark plug doesn't emit any sparks). My issue is that when the engine is running, raspberry pi pico is not detecting at all the quickshifter switch (which works perfectly fine because i tested it with the multimeter). In the current version, i don't use any interrupts, however i tested it with also interrupts and it SOMETIMES detected the pressing of the switch. The other parts of the circuit work well, except for the RPMs reader that works correctly only above 2400rpms.
Here's the code, i used earlephilhower's library for the pi pico, which enables the usage of both cores:

#define SENSIN 18      // INPUT PEDAL SHIFTER SWITCH SENSOR
#define SENSRPM 22     // INPUT PIN OPTOCOUPLER FOR READING RPMs
#define SPARKCUT 21   // OUTPUT PIN FOR THE TC4427AEPA
#define TPS_IN 26   //INPUT PIN A0 TO READ THE ANALOG TPS SIGNAL, which goes from 0v to 5v, at 5v the TPS equals to 100%
#define LEDPIN LED_BUILTIN   // LED on Pi Pico

unsigned long int ultimo_cambio = 0;
unsigned int rpm = 0, rps = 0, rpm_min, intervallorpm = 0, tps = 0, intervallo_cambio, cutoff_offset;
volatile int statosens = 0;

/* Matrix to host the cutoff duration values ​​of the coil, from bottom to top we have the TPS in increasing percentage
   while from left to right we have the engine revs in increasing order. All numbers are in milliseconds.*/

/* 5 because the TPS resolution is 20% (5 / 100), the resolution can be further improved: (num. of rows / 100) */
/* 7 columns for the RPM resolution. It starts at 3000 RPM, each column is increased by 1000 giri, up to 10000 RPM. The engine rev limiter is set at 10500 RPM. 
   C1 >= 3000 && < 4000, C2 >= 4000 && < 5000, C3 >= 5000 && < 6000, C4 >= 6000 && < 7000, C5 >= 7000 && < 8000, C6 >= 8000 && < 9000, C7 >= 9000 && < 10500 (rev limiter RPMs) */

unsigned int cutoff[5][7] = {
/* R5 */  { 90,  80,  75,  72,  70,  65,  60},   //  TPS > 80% && <= 100% 
/* R4 */  {100,  90,  85,  80,  75,  72,  70},   //  TPS > 60% && <= 80%
/* R3 */  {115, 105,  90,  85,  82,  80,  75},   //  TPS > 40% && <= 60%
/* R2 */  {130, 115, 105, 100,  95,  93,  90},   //  TPS > 20% && <= 40%
/* R1 */  {140, 135, 130, 120, 110, 105, 100}    //  TPS >= 0% && <= 20%
        /* ||   ||   ||   ||   ||   ||   ||
           C1   C2   C3   C4   C5   C6   C7 */
};

void setup(){

    Serial.begin(9600);

    pinMode(SENSRPM, INPUT_PULLUP);
    pinMode(TPS_IN, INPUT);
    pinMode(SENSIN, INPUT_PULLUP);
    pinMode(SPARKCUT, OUTPUT); 
    pinMode(LEDPIN, OUTPUT);

    // Config variables
    
    intervallo_cambio = 250;    // Debouncing value for the pedal switch sensor
    rpm_min = 3500;   // RPM Value used to set a minimum RPM to use the quickshifter. A lower RPM Value could damage the gearbox when using lower gears (first and second gears for instance)

    // Added value to increase or decrease the total amount of the cutoff time  
    cutoff_offset = 5;
    
    // End of config variables
    
    ultimo_cambio = millis();
    tps = 100;
}

void setup1(){

    digitalWrite(SPARKCUT, LOW); 
    digitalWrite(LEDPIN, LOW); 

}

void loop(){
    
    Serial.print("\n Core 0");
    conta_giri();       // Function to read RPMs from the optocoupler signal  
    tps = map(analogRead(TPS_IN), 0, 1023, 0, 100);   // Reads the TPS signal which goes from 0 to 1023 and the converts it to a percentage which goes from 0% up to 100%
    
    // Debug prints

    Serial.print("\n RPM = ");
    Serial.print(rpm);
    Serial.print("\n TPS = ");
    Serial.print(tps);
    Serial.print("\n cut: ");
    Serial.print(cutoff[cutoff_riga(tps)][cutoff_colonna(rpm)] + cutoff_offset);
    Serial.print("\n riga: ");
    Serial.print(cutoff_riga(tps));
    Serial.print("\n colonna: ");
    Serial.print(cutoff_colonna(rpm));
    Serial.print("\n\n"); 

    // End of debug prints       
}

void loop1(){

    if((digitalRead(SENSIN) == LOW) && (statosens == 1)){ 

      digitalWrite(SPARKCUT, HIGH);   // Sets the signal of the TC4427 to HIGH, enabling the IGBT gate and therefore cuts the power of the ignition coil
      digitalWrite(LEDPIN, HIGH);

      delay(cutoff[cutoff_riga(tps)][cutoff_colonna(rpm)] - cutoff_offset);    // Delay which gets the values of the cut-off duration from the matrix
          
      digitalWrite(SPARKCUT, LOW);   // Sets the signal of the TC4427 to LOW, so that the IGBT is LOW and doesn't cut off the power of the ignition coil
      digitalWrite(LEDPIN, LOW);

      Serial.print("\n stato sens");  // debug  
      ultimo_cambio = millis();  // Sets this variable to the last time the power was cutted off so that we can use the debounce to avoid multiple presses of the switch
      statosens = 0;  // this value indicates that the gear shfit sensor has been pressed, and it is set to zero when it is pressed
        
    }

    if (((millis() - ultimo_cambio) > intervallo_cambio) && (digitalRead(SENSIN) == HIGH)){
      statosens = 1;  // if the debounce time was surpassed and the shift sensor is not pressed, the value is set to one so that we can use it again 
    }
    else if(digitalRead(SENSIN) == LOW){
      ultimo_cambio = millis();  // if the previous condition is not met, and the shift sensor is still pressed, we reset the timer 
    }

}

void conta_giri(){  // i found this browsing the arduino forum
    Serial.print("\n cunta giri");
    intervallorpm = pulseIn(SENSRPM, HIGH, 100000) + pulseIn(SENSRPM, LOW, 100000); 
    rps = 1000000UL/intervallorpm;  //rps is roations per second
    rpm = (rps*60)*2;   //rpm is rotations per minute, which is multiplied by two because the engine is a 4 stroke, therefore i have a spark event every 720 crankshaft degrees, if i don't multiply it by 2 the programs gives me the exact half of the actual rpms

    if(rpm>10600)		// Problem to solve: under 2400 rpm the program reads random rpms above 12 thousand rpms. So if the revs are higher than the limiter (10500), I reset them to make the program work properly
      rpm = 0;
              
}

int cutoff_colonna(int rpm){
  
    /* selection construct to choose the column of revolutions (x axis), to be modified in case of change in the number of columns of the matrix */

    if((rpm >= 3000) && (rpm < 4000))
        return 0;
    else if ((rpm >= 4000) && (rpm < 5000))
        return 1;
    else if ((rpm >= 5000) && (rpm < 6000))
        return 2;
    else if ((rpm >= 6000) && (rpm < 7000))
        return 3;
    else if ((rpm >= 7000) && (rpm < 8000))
        return 4;
    else if ((rpm >= 8000) && (rpm < 9000))
        return 5;
    else if ((rpm >= 9000) && (rpm < 105000))
        return 6;
    else
	      return 0;   // In case of any problem, the program returns the lowest column which has the highest cutoff time
        
}

int cutoff_riga(int tps){ 

    /* selection construct to choose the row of the TPS (y axis), to be modified in case of variation of the number of rows of the matrix */

    if((tps >= 0) && (tps <= 20))
        return 4;
    else if ((tps > 20) && (tps <= 40))
        return 3;
    else if ((tps > 40) && (tps <= 60))
        return 2;
    else if ((tps > 60) && (tps <= 80))
        return 1;
    else if ((tps > 80) && (tps <= 100))
        return 0;
    else
        return 4;    // In case of any problem, the programs returns the row (the bottom one), which has the highest cutoff time

}

and here's the electric diagram, which has more electric components that i hanve't described:

Let me know if you have any kind of solution! The power is cutted of perfectly and i have zero issues of shortcircuits or other electrical kind of issues. Thanks a lot in advance!

How do you avoid firing the spark plug on the exhaust cycle of the engine?

Hi Paul, thanks for the answer. I simply don't, because when you ground the ignition coil it actually doesn't generate a voltage for the spark plug, but it loads up. Basically inside the ignition coils there are two inductive coils with different characteristics. The first coil is the one which is connected between 12V and GND, it loads up when the GND is connected, when we remove the GND its magnetic field collapses and the second inductive coil gets a really high voltage which goes directly to the spark plug (i kinda read this online, correct me if I'm wrong). So my circuit actually loads the ignition coil, while the stock ECU keeps working, the ECU basically commands wether or not the GND has to be enabled, in fact the negative wires goes directly to the ECU.(Anyway i haven't said this before, but my english isn't perfect and i'm sorry about that).
I also tried a version which implemented a while construct in the ISR of the code that used interrupts, the while simply had a condition where it monitored if the ground was or wasn't enabled by the ECU. If the ground was enabled, my circuit simply held the ground a little longer for the the cutoff, otherwise it would wait until the ground was enabled by the ECU. However i noticed some sort of retard and so i didn't use this method.
Anyway the motorcycle is a 2010 Yamaha YZF-r125, a single cylinder 4 stroke 125cc

Then the ECU is doing all the critical work. On a regular spark ignition, the spark is created when the ground connection is OPENED by the ignition points.

I also forgot to mention that the buck converter steps down the voltage to 5v for the pi pico.

Yes, that's correct. But if my circuit grounds it, no spark can't be generated for the whole cutoff time

Hi, i thank you again for replying yesterday. Today i did some tests and i managed to fix my problem. Basically there's definitely some sort of noise on the SENSIN input pin, i fixed this by declaring the SENSIN pin only as INPUT instead of INPUT_PULLUP, and then i added a 4.7K pullup resistor between the 3.3V output pin and the SENSIN input, this because i read that the internal pullup resistor was around 50K. I also connected a 22nF capacitor between the SENSIN pin and GND. I tested my quickshifter and it works flawlessy. Now i have to put everything on a through hole board and test it on road in every case scenario (i was using a breadboard so far). If it will work for a month or so i will definitely do a PCB with SMD components. Let me know if you're interested and i will upload the final code and the final electric circuit.

Thanks again, best regards

Your problem may well be electrical noise ( as you just said - we must have typing at the same time)
Also that transistor on the coil neg may see reverse voltages when it switches due to coil inductance.

These can be tricky to fix ! I would start with some filtering on the 12v input

Be aware that faults in this system could make you fall off .

Hi Hammy, thanks for replying. Yes, the noise is definitely the issue here. Apparently in automotive electric circuits this is a common thing, as i just read on the internet. Do you have any kind of advice for lowering the noise? As you can see from the electric diagram, the input noise could come from the battery and from the tc4427aepa which is also connected to the battery through a resistor, i don't think that the IGBT might cause some noise because its gate should be theoretically isolated from the source and drain, right? I forgot to mention that i also added a 100nF capacitor to the input of the optocoupler and it smoothed out the RPM readings. But in any case the optocoupler definitely doesn't give me noise.
Should i add some capacitor to the output of the buck converter and add a flyback 1n4007 between source and drain of the IGBT?
Thanks in advance

I’d probably try a big cap (470uf ?) on the 12v input in parallel with 0.1uf ceramic .
I’d also put 0.1 on the other side of that 47 resistor at vdd.

Think I’d power Q1 thro’ an opto so all the coil side is isolated and run sep ground for it

So i shoul add a 470uF capacitor algoside a 1uF capacitor to the 12V input of the buck converter. Also i should add a 1uF capacitor between 12V and GND of the tc4427 mosfet driver. I was also thinking that i could add a Schottky diode in series with the 15ohm resistor of the SPARKCUT output pin, so that it might block electrical noise when the SPARKCUT pin is LOW and not sending a signal

Yes and yes , not sure about your third option !

Perfect, thanks again. I hope to find the time to buy the 470nF capacitor i need within this week, because i don't have enough to reach such value. I have all other kind of values though

So, between the last evening and this morning i soldered everything on a THT board, i double checked the connections and everything is wired correctly. I went to test the quickshifter on road and after around 15 minutes of testing the IGBT failed and i can't figure out why. The circuit is the photo i sent earlier plus the capacitors we discussed, but without the 470nF (i couldn't resist i was curious to test it out). Basically the engine started to stutter at high RPMs right after a shift (the clutchless shift is cool af tho), at low RPMs everything seemed fine so i proceded to do some other tests and at a certain point the check engine light turned on, with code 33, meaning that the ECU detected a short circuit in the ignition coil wiring and the engine wouldn't turn on until i disconnected the module i soldered. It was the IGBT which is permanently on, i also removed the MOSFET driver and grounded the gate directly to ground with the 20ohm resistor (it was 15 ohm in the diagram, but i changed it because i broke one end by twisting it too many times), it was still short circuited and the MOSFET driver is working correctly. What could cause this? The IGBT is rated for 1200 Volts and around 16 Amps, it has no flyback diode.

Have you seen this thread? Motorcycle Quickshifter it's an old one but the goal is the same. They had the same noise issues and I guess they solved it. There is a final schematic in one of the last posts.

When I made my quickshifter (Thread linked above) I used a P-Channel MOSFET and not an IGBT. This was configured as a high side switch and would interrupt the 12V suply to the coils and not the negative. Mine is a 4 cylinder motorcycle and so I cut the common 12V that supplies all coils.

I was also getting a lot of noise. I eventually solved this by using a 10uF ceramic capacitor directly next to the sensor pin. This will try to keep the voltage on the GPIO pin high when the EMI from the coils is trying to lower it.

I would change your RPM funtion to an interrupt, and count how many pulses you get in a short period. The PulseIn function is blocking the core it is running on.

I would also remove the Serial print statements.

Here is some code that I used for the quickshifter and reading of RPM (From a crankshaft sensor). If I was still using this code I would remove the delays in the quickshifter section and use millis() instead.

#define DEBUG false
#define DEBUG_PORT if (DEBUG)Serial

#include <Bounce2.h>
#include <EEPROM.h>

#define EEPROM_SIZE 14
#define KILLDEF 60
#define DELAYDEF 40
#define RESENSEDEF 30

// State
bool cutTriggered = false;
bool testNext = false;

// Debouncing
Bounce debouncerSensor = Bounce();

// settings
const byte delayAddr = 0;
const byte resenseAddr = 2;
const byte killAddr = 9;

uint8_t killTime[5] = {0, 0, 0, 0, 0};
int16_t delayTime = 0;
int16_t resenseDelay = 0;

// PINS
const byte QSSensorPin  = 22;   
const byte mosfetPin 	= 23;	
const byte rpmInPin 	= 17;	

// RPM
uint16_t rpm = 0;
volatile byte rpmPulses = 0;
const byte rpmPulsesToCount = 30;
unsigned long oldMicros = 0;
const byte pulsesPerRot = 12;
unsigned long lastRPM   = 0;
uint8_t resetRPMInterval = 250;

int8_t gear 								= -1;

void IRAM_ATTR countRPMPulses() {
  rpmPulses++;
}

void setup() {
  Serial.begin(115200);
  EEPROM.begin(EEPROM_SIZE);
  getStoredValues();
 
  pinMode(mosfetPin, OUTPUT);
  
  // Make sure the coils are able to fire on boot
  digitalWrite(mosfetPin, LOW);
  
  debouncerSensor.attach(QSSensorPin, INPUT);
  debouncerSensor.interval(5);
  debouncerSensor.update();

  pinMode(rpmInPin, INPUT);
  attachInterrupt(rpmInPin, countRPMPulses, FALLING);
}

void loop() {
  checkForQSSensor();
  readRPM();
  readGear();
}

void readGear() {
  // read the gear for different kill times
  return;
}

void readRPM() {
  if (rpm > 0 && millis() - lastRPM >= resetRPMInterval) {
    // We haven't seen pulses for a while, reset rpm
    rpm = 0;
    return;  
  }
  if (rpmPulses >= rpmPulsesToCount) {
    byte rpmCopy = rpmPulses;
    rpmPulses = 0;
    unsigned long elapsed = micros() - oldMicros;
    oldMicros = micros();
    lastRPM = millis();
    rpm = ( (rpmCopy * 60000000UL) / (elapsed * pulsesPerRot) );
  }
  return;
}

void checkForQSSensor() {
  debouncerSensor.update();

  if ( debouncerSensor.read() == LOW and not cutTriggered) {
    DEBUG_PORT.println(F("Cut Triggered"));
    if (gear == 6) {
      // Don't cut ignition when in top gear
      return;  
    }
	  // Detected a gear change
    cut_ignition();
    delay(resenseDelay);
  } else if (testNext and not cutTriggered) {
    // Manual cut triggered via Bluetooth
    testNext = false;
    DEBUG_PORT.println(F("Manual Cut Triggered"));
    cut_ignition();
    delay(resenseDelay);
  }

  // Make sure the ignition only cuts once, even if the sensor remains tripped
  if (  debouncerSensor.read() == HIGH and cutTriggered ) {
    cutTriggered = false;
  }
}

void ignition_on() {
  digitalWrite(mosfetPin , LOW);  // Ignition on
}

void ignition_off() {
  digitalWrite(mosfetPin, HIGH); // Ignition off
}

void cut_ignition() {
  cutTriggered = true;
  delay(delayTime);
  ignition_off();
  byte index = gear - 1;
  // Set the killTime to 60, incase we do not know what gear we are in
  byte offTime = 60;
  if (index < 6) {
    // We know what gear we are in, use the saved value
    offTime = killTime[index];
  }
  DEBUG_PORT.printf("%-20s%s\n%-20d%d\n", "Kill Time", "Index", offTime, index);
  delay(offTime);
  ignition_on();
}

void getStoredValues() {
  // Get saved values from EEPROM
  EEPROM.get(delayAddr, delayTime);
  EEPROM.get(resenseAddr, resenseDelay);
	EEPROM.get(killAddr, killTime);
  
	// Set defaults if there is no value stored
	if (delayTime == -1) {
	    delayTime = DELAYDEF;
	}
	for (byte i = 0; i <= 4; i++) {
    if (killTime[i] == 255) {
      killTime[i] = KILLDEF;
    }
  }
	if (resenseDelay == -1) {
		resenseDelay = RESENSEDEF;
	}
  
	return;
}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.