Generating the CPS signal

To operate an ECM while not installed, besides all simulated sensors and actuators (replaced by resistors and LED) the camshaft position sensor's (CPS) signal is required too. The CPS signal synchronizes everything inside the ECM. Originally this signal is generated by detecting slots in a rotating cup utilizing a Hall sensor. At the start of each slot the sensor voltage will drop from 5 to 0 volts, at the end it will jump up from 0 to 5 volts again.

Depending on the Buell model, two versions of rotor cups are available: for EFI equipped bikes a 6-slot cup is used, for carburettor Buells it's a cup with only 2 slots. Later models (DDF-3 and up) do not have a camshaft sensor at all, but a crankshaft sensor instead. This sensor signal can not be simulated that easily, therefore it is not taken into account here.

To set up a signal generator, a small micro controller can be used, like a AVR or PIC (to name just two of the many available). A very common platform is the Arduino UNO, exemplary shown below. This board will be connected to a PC with a USB cable, which will provide power and also will be used to transfer new programs ("sketches" in Arduino speak) to the board.

Beschaltung

The sketch made available below will provide 5 signals, as listed:

  1. Pin 13 - Activity signal, also shown by the built-in LED, toggles very 2nd revolution of the crankshaft
  2. Pin 12 - CPS signal for DDFI and DDFI-2 (6 slots in CPS cup)
  3. Pin 11 - Crankshaft signal, toggles with each degree of cranshaft rotation
  4. Pin 10 - CPS signal for Buells with carburetors (2 slots in CPS cup)
  5. Pin 8 - O2 sensor trigger signal, toggles each 16th revolution. Before feeding this signal to the ECM, a voltage divider is required, to adjust it's voltage to the usual 0 ... 1 volts range.

All signals combined will look similar to the image below (the crankshaft signal has been suppress for better visibility):

Click image for unscaled view

The source code for this sketch can also be downloaded here.

// Arduino XB/S1 CPS signal generator
//
// To set engine speed: open serial monitor, send "+" to increase and  "-" to decrease engine speed
//
// Pins used:
// 13 = onboard LED (toggles once per ignition cycle)
// 12 = CPS signal XB (6 slots in cup)
// 11 = crank signal  (toggles once per crankd degree)
// 10 = CPS signal S1 (2 slots in cup)
//  8 = O2 signal (toggles once per 8 cycles) - voltage divider required for ECM input!
//

// avr-libc library includes
#include 
#include 

#define O2        PINB0
#define CPSS1     PINB2
#define CRANK     PINB3   
#define CPSXB     PINB4
#define LED       PINB5


static volatile unsigned int degCount;                        // count timeticks
static volatile byte revCount;                                // count revolutions

static volatile byte count1, count2, count3, count4, count5;  // GP counters
static volatile byte blipXBIndex, blipS1Index;                // rotor cup description

// 6 slot rotor cup for fuel injected engines, degrees crankshaft where CPSXB signal toggles
static unsigned int blipsXB[12] = {45, 90, 135, 180, 225, 405, 450, 495, 540, 585, 630, 720};

// 2 slot rotor cup for carbed engines, degrees crankshaft where CPSS1 signal toggles
static unsigned int blipsS1[4] = {270, 315, 675, 720};

// overflow compare register values @ 8 MHz for specific engine speeds

//OCR1A = 168;     // 8k   RPM
//OCR1A = 178;     // 7k5  RPM
//OCR1A = 192;     // 7k   RPM
//OCR1A = 199;     // 6750 RPM
//OCR1A = 207;     // 6k5  RPM
//OCR1A = 225;     // 6k   RPM
//OCR1A = 244;     // 5k5  RPM
//OCR1A = 269;     // 5k   RPM
//OCR1A = 299;     // 4k5  RPM
//OCR1A = 338;     // 4k   RPM
//OCR1A = 384;     // 3k5  RPM
//OCR1A = 448;     // 3k   RPM
//OCR1A = 537;     // 2k5  RPM
//OCR1A = 673;     // 2k   RPM
//OCR1A = 897;     // 1k5  RPM
//OCR1A = 1224;    // 1k1  RPM
//OCR1A = 1346;    // 1k   RPM
//OCR1A = 1682;    // 800  RPM

//static unsigned int compares[18] = {168, 178, 192, 199, 207, 225, 244, 269, 299, 338, 384, 448, 537, 673, 897, 1224, 1346, 1682};     // @ 8 MHz system clock
static unsigned int compares[18] = {332,  355,  380,  394,  409,  443,  484,  532,  592,  665,  761,  888, 1066, 1332, 1778, 2425, 2670, 3340};    // @ 16 MHz system clock
static unsigned int rpms[18] =    {8000, 7500, 7000, 6750, 6500, 6000, 5500, 5000, 4500, 4000, 3500, 3000, 2500, 2000, 1500, 1100, 1000,  800};

// index to compares/rpm arrays
static volatile char speedIndex = 15;  // start @ 1100 rpm

// main loop

void loop()
{
  int input = 0;
  
  if(Serial.available()) {  
    
    input = Serial.read();
    //Serial.println(input, HEX);  // mirror input, for testing only
    
    // parse input
        
    if (input == (int) '+')
      speedIndex --;
    else if (input == (int) '-')
      speedIndex ++;  
      
    // index rotation
    
    if (speedIndex < 0)
      speedIndex = 17;
    else if (speedIndex > 17)
      speedIndex = 0;
    
    // update compare match register
    OCR1A = compares[speedIndex];    
    
    Serial.println(rpms[speedIndex]);
    Serial.print ("? ");
  }  
}

// timer1/counter1 compare_A interrupt signal handler

ISR(TIMER1_COMPA_vect)
{
  // called, when a timer1/counter1 matches compare value

  // toggle crankPin @ every degree of crankshaft
  PORTB ^= (1 << CRANK);

  // increase degress crankshaft
  degCount ++;

  if (degCount >= blipsXB[blipXBIndex]) {

    // toggle CPSXB signal @ every blip of CPSXB
    PORTB ^= (1 << CPSXB);

    // increase blip index
    blipXBIndex ++;

    // reset index
    if (blipXBIndex >= 12)
      blipXBIndex = 0;
  }
  
  if (degCount >= blipsS1[blipS1Index]) {

    // toggle CPSS1 signal @ every blip of CPSS1
    PORTB ^= (1 << CPSS1);

    // increase blip index
    blipS1Index ++;

    // reset index
    if (blipS1Index >= 4)
      blipS1Index = 0;
  }
  
  // reset crankshaft degrees counter after 2 revolutions
  
  if (degCount == 720) {
    // toggle LED @ every ignition cycle
    PORTB ^= (1 << LED);
    degCount = 0;
    revCount ++;
  }
  
  if (revCount == 8) {
    // toggle O2 signal @ 8 ignition cycles
    PORTB ^= (1 << O2);
    revCount = 0;
  }
}

// setup and initialization

void setup()
{
  // set pins of port B to output mode
  DDRB = 0xFF;
  
  // set CPS signal to high on start, 
  // otherwise output will be inverted
  
  PORTB ^= (1 << CPSXB);
  PORTB ^= (1 << CPSS1);
  
  // initialize counter and indexes
  degCount = 0;
  revCount = 0;
  blipXBIndex = 0;
  blipS1Index = 0;
  //speedIndex = 15;  // start @ 1100 rpm
  //speedIndex = 17; 
 
  // initialize serial connection
  Serial.begin(9600);
  Serial.print("? ");
  Serial.println(rpms[speedIndex]);
  Serial.print ("? "); 

  // initialize Timer1
  cli();          // disable global interrupts
  TCCR1A = 0;     // set entire TCCR1A register to 0
  TCCR1B = 0;     // same for TCCR1B

  // turn on CTC mode
  TCCR1B |= (1 << WGM12);

  // Set CS10 and CS12 bits for 1024 prescaler (test only)
  //TCCR1B |= (1 << CS10);
  //TCCR1B |= (1 << CS12);

  // Set CS10 for no prescaler
  TCCR1B |= (1 << CS10);

  // set compare match register to desired timer count
  OCR1A = compares[speedIndex];
  
  // enable timer compare interrupt:
  TIMSK1 |= (1 << OCIE1A);

  // enable global interrupts:
  sei();
}