CDI tester project

Hi all, I have a project I would like some help with. The code I have so far is attached and works great. It is on a pro mini 16Mhz clone. I repair CDI modules for mowers and other small engines and for a couple years I have had a 555 timer that I have used for testing. It is lacking in several ways. Now I have started this project that will have:

  1. a LCD to display rpm from 500 to 3000
  2. generate a pulse at intervals from 120Ms to 20Ms to trigger the CDI (one pulse / revolution)
  3. generate pulses at 50Hz to 300Hz to simulate the charging coils. Here may be the tricky
    part, as there are 6 pulses per revolution, but skip every 6th pulse and have that time interval
    align with the above triggering pulse. (you don't want a triggering pulse at the same time the
    charging coil has output because the part that the charging coil charges is shorted at that
    moment)
  4. input the trigger pulse and also input a signal from the coil and display the time interval as ° timing, as at low rpm there is a delay varying from 100us to 0us approx. The delay at low rpm is taken away at higher rpm to produce advance.

What I have so far is the LCD which works well and displays the rpm. Also for now I had it display the Ms between pulses just to be sure it was changing correctly. The pulse out I made 1Ms and used 2 transistors to switch 12 vdc to the input of the CDI, works well. I added the pin 7 output as the future high pulse rate simulating charging coils. I will feed this also with 2 transistors through a transformer to obtain approx 200 vac.
Thanks, hope it is not to ambitious!
(edited errors)

//Pulse Generator Arduino Code  
#include "U8glib.h"
U8GLIB_ST7920_128X64_1X u8g(13, 11, 10);  // SPI Com: SCK = en = 15, MOSI = rw = 16, CS = di = 17
int pot1 = A5;          // select the input pin for the pot
int potValue = 0;       // variable to store the value coming from the sensor
int pulseValue = 0;    // 2 variables from one pot
int delayValue = 0;
int outputPin = 8;      // select the pin for the output for trigger pulse
int chargePin = 7;      // select the pin for the output for charge pulses

void draw() {
  // graphic commands to redraw the complete screen should be placed here  
  
  u8g.setPrintPos(0, 15);        // call procedure from base class, http://arduino.cc/en/Serial/Print
  u8g.print("RPM =");
  u8g.setPrintPos(50, 15);
  u8g.print(potValue);
  u8g.setPrintPos(8, 30);
  u8g.print("Ms =");
  u8g.setPrintPos(50, 30);
  u8g.print(pulseValue);
  u8g.setPrintPos(0, 45);
  u8g.print("Advance =");
  u8g.setPrintPos(80, 45);
  u8g.print("10");                               // for use in future code

}

void setup() {
  Serial.begin(115200);                       // serial com speed for display
  // flip screen, if required
  // u8g.setRot180();  
 pinMode(outputPin, OUTPUT);            // declare the outputPin as an OUTPUT 
 pinMode(chargePin, OUTPUT);            // declare the chargePin as an OUTPUT
 u8g.firstPage();
 u8g.setFont(u8g_font_unifont);
 delay (2000);

 //Timer1 set up 2ms pulse every 50 ms
  TCCR1A = 0;
  TCCR1B = (1 << WGM12);//CTC mode to OCR1A
  OCR1A = 12500; //50 ms to top value
  TIMSK1 |= (1 << OCIE1A);//interrupt enable compareA
  TIMSK1 |= (1 << OCIE1B);//interrupt enable compareB
  OCR1B = 250; //1 ms pulse width
  TCCR1B |= (1 << CS11) |(1<<CS10); //prescaler 64  4us/tick
 
}
  
void loop() {
  // picture loop
  u8g.firstPage();
  do {
    draw();
} while( u8g.nextPage() );

  potValue = analogRead(pot1);                                          // read the value from the pot for rpm
  pulseValue = analogRead(pot1);                                        // read the value from the pot for pulse
  delayValue = analogRead(pot1);                                        // read the value from the pot for pulse delay
  potValue = map(potValue, 0, 1023, 500, 3000);                         // map pot for 500 - 3000 for rpm display
  pulseValue = map(pulseValue, 0, 1023, 120, 20);                       // map pot for pulse delay 20 to 120 Ms
  delayValue = map(delayValue, 0, 1023, 30000, 5000);                   // map pot for actual pulse delay
  delay (10);
}

ISR(TIMER1_COMPA_vect) {
  OCR1A = (delayValue);                                                 // value to set delay between pulses
  digitalWrite(outputPin,HIGH); //turn on pin 8
  digitalWrite(chargePin,HIGH); //turn on pin 7
}

ISR(TIMER1_COMPB_vect) {
 digitalWrite(outputPin,LOW); //turn off pin 8
 digitalWrite(chargePin,LOW); //turn off pin 7
 
}

Tom--

Let me see if I have this correct. I understand 1,2, and 3 as follows.

You have a 1 ms CDI trigger pulse at 0 degrees. The period between trigger pulses is 20 to 120 ms. In the CDI system is this the trigger for the discharge of the capacitor to the coil?

Then, within that period you then want to output a ? ms pulse at 60°, another at 120°, another at 180°, another at 240° and one at 300°. That's five pulses spaced 360/6 with a gap at 0. This part seems straight forward. In the CDI system, are these pulses charging the capacitor?

I'm not certain about point 4

  1. input the trigger pulse and also input a signal from the coil and display the time interval as ° timing, as at low rpm there is a delay varying from 100us to 0us approx. The delay at low rpm is taken away at higher rpm to produce advance.

The trigger fires. The capacitor discharges into the coil. Please explain more about the "signal from the coil". If you can get this into a 5v signal for input into the Arduino it should be possible to calculate the time from the trigger pulse. I'm not sure if this is better done with a pin change (or external interrupt) or a timer input capture.

For development, you can certainly simulate this pulse with one generated by the Arduino or from possibly a second one. Do you have a second pro mini to possibly use as a coil output signal generator?

I'm not sure if your project will require the millis() and micros() timer. You may need three timers --one to generate the trigger, one to generate the 5 pulses, and one to time the ignition delay. It's possible that this project could benefit from additional hardware timers. Are you bound to the pro mini, or is there the possibility of using a Mega with the additional timers.

I would recommend developing this system without the TFT display. Use a 4x20 lcd or serial to a laptop. Your program will be complicated enough without having to work around the blocking characteristics of the display draw while you are working through other issues.

I tried get the OP's description onto a time line to make it easier to visualise. I hope I've understood it correctly.

I take it you mean 18,000rpm, not 18,000 Hz - that would be a pretty quick engine!

regards

Allan.

ps I've looked at these gadgets - the ones I've seen are potted in epoxy or similar, and I've always thought them unrepairable. How do you get them apart?

cattledog:
Tom--

Let me see if I have this correct. I understand 1,2, and 3 as follows.

You have a 1 ms CDI trigger pulse at 0 degrees. The period between trigger pulses is 20 to 120 ms. In the CDI system is this the trigger for the discharge of the capacitor to the coil?

Yes this simulates the pusle coil and tells the CDI to fire.

Then, within that period you then want to output a ? ms pulse at 60°, another at 120°, another at 180°, another at 240° and one at 300°. That's five pulses spaced 360/6 with a gap at 0. This part seems straight forward. In the CDI system, are these pulses charging the capacitor?

Yes that is the a/c from the charging coil under flywheel that outputs around 200vac

I'm not certain about point 4

The trigger fires. The capacitor discharges into the coil. Please explain more about the "signal from the coil". If you can get this into a 5v signal for input into the Arduino it should be possible to calculate the time from the trigger pulse. I'm not sure if this is better done with a pin change (or external interrupt) or a timer input capture.

I can use a induction coil or a voltage divider to pick up a signal from the coil.

For development, you can certainly simulate this pulse with one generated by the Arduino or from possibly a second one. Do you have a second pro mini to possibly use as a coil output signal generator?

I'm not sure if your project will require the millis() and micros() timer. You may need three timers --one to generate the trigger, one to generate the 5 pulses, and one to time the ignition delay. It's possible that this project could benefit from additional hardware timers. Are you bound to the pro mini, or is there the possibility of using a Mega with the additional timers.

No I can use any other board as needed, the generation of the 5 pulses for charging is just a luxury,
I can use a high impedance transformer like I have now.

I would recommend developing this system without the TFT display. Use a 4x20 lcd or serial to a laptop. Your program will be complicated enough without having to work around the blocking characteristics of the display draw while you are working through other issues.

allanhurst:
I take it you mean 18,000rpm, not 18,000 Hz - that would be a pretty quick engine!

regards

Allan.

ps I've looked at these gadgets - the ones I've seen are potted in epoxy or similar, and I've always thought them unrepairable. How do you get them apart?

Ok, 6 charging coils with magnet turning at 3000 rpm would be 300Hz signal so output needs
to be 50 - 300Hz.

I find the cheaper ones are epoxy but MANY others are in something softer.

tombauer:
6 charging coils with magnet turning at 3000 rpm would be 18000Hz signal would it not? . . .

Exactly, if your minute is only only one second in duration.

the generation of the 5 pulses for charging is just a luxury,

If you use the five pulses, what duration should they be?

If you use only one pulse, what duration should it be, and how should it be timed to the trigger?

Top line would only have 1 pulse at the time where the second line does
not.

The top line represents a system clock and the overall timing. There are no output pulses, but the other pulses are timed in relationship to the clock.

cattledog:
If you use the five pulses, what duration should they be?

These pulses would be fed to a small transformer so they can just be square wave.

If you use only one pulse, what duration should it be, and how should it be timed to the trigger?

.If I don't even have these pulses it is not that bad for a tester, I can just use a transformer off the line with isolation. Right now I have 2 12v to 120v transformers back to back so I also get my 12 vdc from them.

The top line represents a system clock and the overall timing. There are no output pulses, but the other pulses are timed in relationship to the clock.

Ok, I did not understand, so it is correct.

Here is a some test code with the advance (coil pulse delay) measurement in microseconds patched into a serial version of your code.

I have simulated a delay pulse with a signal on pin 4 generated by a software timer which sends a pulse 5 milliseconds after the main trigger. From my testing, most of the timing error is due to the analogRead() going on. Putting the advance measurement on a timer input capture interrupt, instead of an external interrupt might be more accurate.

How accurate does the advance measurement need to be? What is the range of advance measurements?

//Pulse Generator Serial Output Arduino Code

int pot1 = A5;          // select the input pin for the pot
int potValue = 0;       // variable to store the value coming from the sensor
int pulseValue = 0;    // 2 variables from one pot
int delayValue = 0;
int outputPin = 8;      // select the pin for the output for trigger pulse
int chargePin = 7;      // select the pin for the output for charge pulses

volatile boolean trigger = false;
volatile unsigned long delayPeriod;
unsigned long copy_delayPeriod;
volatile unsigned long delayPeriodStart;


void setup() {
  Serial.begin(115200);                       // serial com speed for display
  pinMode(outputPin, OUTPUT);            // declare the outputPin as an OUTPUT
  pinMode(chargePin, OUTPUT);            // declare the chargePin as an OUTPUT
  delay (2000);

  pinMode(4, OUTPUT); //signal out for delay test

  //Timer1 set up 1ms pulse every 50 ms
  TCCR1A = 0;
  TCCR1B = (1 << WGM12);//CTC mode to OCR1A
  OCR1A = 12500; //50 ms to top value
  TIMSK1 |= (1 << OCIE1A);//interrupt enable compareA
  TIMSK1 |= (1 << OCIE1B);//interrupt enable compareB
  OCR1B = 250; //1 ms pulse width
  TCCR1B |= (1 << CS11) | (1 << CS10); //prescaler 64  4us/tick

  attachInterrupt(digitalPinToInterrupt(3), delayPeriodTiming, RISING);
}

void loop() {

  potValue = analogRead(pot1);                                          // read the value from the pot for rpm
  pulseValue = analogRead(pot1);                                        // read the value from the pot for pulse
  delayValue = analogRead(pot1);                                        // read the value from the pot for pulse delay
  potValue = map(potValue, 0, 1023, 500, 3000);                         // map pot for 500 - 3000 for rpm display
  pulseValue = map(pulseValue, 0, 1023, 120, 20);                       // map pot for pulse delay 20 to 120 Ms
  delayValue = map(delayValue, 0, 1023, 30000, 5000);

  //my test for trigger pulse delay response)
  if (trigger == true)
  {
    if (micros() - delayPeriodStart >= 5000) //5 ms delay
    {
      trigger = false;
      digitalWrite(4, HIGH);//simulate pulse
      digitalWrite(4, LOW);
      noInterrupts();
      copy_delayPeriod = delayPeriod;
      interrupts();

      Serial.print("RPM =");
      Serial.println(potValue);
      Serial.print("Ms =");
      Serial.println(pulseValue);
      Serial.print("AdvanceMicroseconds =");
      Serial.println(copy_delayPeriod);                               // for use in future code

    }
  }
}

ISR(TIMER1_COMPA_vect) {
  OCR1A = (delayValue);                                                 // value to set delay between pulses
  digitalWrite(outputPin, HIGH); //turn on pin 8
  digitalWrite(chargePin, HIGH); //turn on pin 7
}

ISR(TIMER1_COMPB_vect)
{
  digitalWrite(outputPin, LOW); //turn off pin 8
  digitalWrite(chargePin, LOW); //turn off pin 7
  delayPeriodStart = micros();//start looking for response
  trigger = true;
}

void delayPeriodTiming()
{
 delayPeriod = micros() - delayPeriodStart;
}

I am repairing just one such CDI right now and will get (hopefully) some measurements from it regarding pulse delay. I anticipate that 5 Ms delay will give 18° advance when it is taken away. I would think a range of 20° would be more than adequate. Also +/- .5° if obtainable would be fine, these are not race car engines!

So far unable to measure and delay! I have one OEM CDI and a cheap Chinese one that should have delay, I am looking at the pulse and the primary of the ign coil with dual trace scope (Tektronix 585A) at any speed pulses are at same time!

I loaded the above code and it compiled and loaded but apparently locks up, the small led flashes once and goes out, no response after that??

I loaded the above code and it compiled and loaded but apparently locks up, the small led flashes once and goes out, no response after that??

I'm running it now on a Uno. Did you jumper pin 4 to pin 3? Did you set the Serial monitor to 115200 baud? I am also running a jumper from 3.3v to A5 for the analog read. Ms =56 RPM = 2110 delay = 5400 us.

RPM =2137
Ms =56
AdvanceMicroseconds =5052
RPM =2137
Ms =56
AdvanceMicroseconds =5052
RPM =2137
Ms =56
AdvanceMicroseconds =5060
RPM =2127
Ms =56
AdvanceMicroseconds =5232
RPM =2117
Ms =56
AdvanceMicroseconds =5224
RPM =2130
Ms =56
AdvanceMicroseconds =5504

I still have the 12864B display and it is a serial setup. I would like to understand "I would recommend developing this system without the TFT display. Use a 4x20 lcd or serial to a laptop. Your program will be complicated enough without having to work around the blocking characteristics of the display draw while you are working through other issues."
Yes 3 and 4 are jumped,
Thanks, Tom

In a previous thread it was apparent that there are blocking characteristics with how you are writing to that display. simple project but need code help - #14 by cattledog - Programming Questions - Arduino Forum

The CDI aspects of your program require critical timing. We already placed one aspect of your program on a hardware timer (the trigger pulse) to work around the display. In my opinion it is more important to get the core functionality of your program working first. If you need to use millis() timers (i.e. software timers instead of hardware) don't tie your hands. It may be possible to work around the display as now written, but I don't have the hardware to help you do that.

There are other possible displays to use, or the entire 12864B display program can be re-crafted to draw a tiny bit of the display image and then return to the program.

OK, I understand that now. Would a hd44780 lcd with i2c adapter be suitable? If so it displays enough to just be permanent. Or a 2004 with i2c?

I now have a serial monitor, silly me, it is built in. Will get a more suitable one soon. The pot I have for rpm, potValue, now does nothing and there is no output from pin 7 or 8. Values on monitor do not change, but ms jumps around a lot from low values to really high.

cattledog,
Success! I had a power supply problem and the board was not getting enough voltage. Now it all works, display on pc shows values and pot1 is consistent and linear. (before it was really flaky) Now to turn pulse delay into ° advance? I have 2 cdi that should have advance built in, they use ic MB4213 for delay but neither of them have any delay. Maybe they are bad and this is exactly why I need this tester!
Best, Tom

I'm glad that you are finding success with your project. In my testing I found that much of the variation in delay timing was coming from the continual analorRead dof the pots in loop. Because the changing of this pot is manual input I thought it would be OK to put the readings at one second intervals. There is far greater stability of the delay values. If the precision of the tester is not adequate with this modification, we can use a running average of several delay values to smooth it more.

I think that 360 * delay/puseValue *1000 should give you degrees of delay. I've calculated as an int, but you may want to use a float for fractional degrees.

//Pulse Generator Serial Output Arduino Code

int pot1 = A5;          // select the input pin for the pot
int potValue = 0;       // variable to store the value coming from the sensor
int pulseValue = 0;    // 2 variables from one pot
int delayValue = 0;
int outputPin = 8;      // select the pin for the output for trigger pulse
int chargePin = 7;      // select the pin for the output for charge pulses

volatile boolean trigger = false;
volatile unsigned long delayPeriod;
unsigned long copy_delayPeriod;
volatile unsigned long delayPeriodStart;
int delayDegrees;

unsigned long analogReadInterval = 1000;//read pots and map
unsigned long lastAnalogRead;

void setup() {
  Serial.begin(115200);                       // serial com speed for display
  pinMode(outputPin, OUTPUT);            // declare the outputPin as an OUTPUT
  pinMode(chargePin, OUTPUT);            // declare the chargePin as an OUTPUT
  delay (2000);

  pinMode(4, OUTPUT); //signal out for delay test

  //Timer1 set up 1ms pulse every 50 ms
  TCCR1A = 0;;
  TCCR1B = (1 << WGM12);//CTC mode to OCR1A
  OCR1A = 12500; //50 ms to top value
  TIMSK1 |= (1 << OCIE1A);//interrupt enable compareA
  TIMSK1 |= (1 << OCIE1B);//interrupt enable compareB
  OCR1B = 250; //1 ms pulse width
  TCCR1B |= (1 << CS11) | (1 << CS10); //prescaler 64  4us/tick

  attachInterrupt(digitalPinToInterrupt(3), delayPeriodTiming, RISING);
}

void loop() {

  if (millis() - lastAnalogRead >= analogReadInterval)
  {
    lastAnalogRead += analogReadInterval;
    potValue = analogRead(pot1);                                          
    pulseValue = analogRead(pot1);                                     
    delayValue = analogRead(pot1);
    potValue = map(potValue, 0, 1023, 500, 3000);                         
    pulseValue = map(pulseValue, 0, 1023, 120, 20);                       
    delayValue = map(delayValue, 0, 1023, 30000, 5000);
  }
  //my test for trigger pulse delay response)
  if (trigger == true)
  {
    if (micros() - delayPeriodStart >= 5000) //5 ms delay
    {
      trigger = false;
      digitalWrite(4, HIGH);//simulate pulse
      digitalWrite(4, LOW);
      noInterrupts();
      copy_delayPeriod = delayPeriod;
      interrupts();

      delayDegrees = 360*(copy_delayPeriod)/(pulseValue*1000UL);
      
      Serial.print("RPM =");
      Serial.println(potValue);
      Serial.print("Ms =");
      Serial.println(pulseValue);
      Serial.print("Delay Microseconds =");
      Serial.println(copy_delayPeriod);                               // for use in future code
      Serial.print("Delay Degrees = ");
      Serial.println(delayDegrees);

    }
  }
}

ISR(TIMER1_COMPA_vect) {
  OCR1A = (delayValue);                                                 // value to set delay between pulses
  digitalWrite(outputPin, HIGH); //turn on pin 8
  digitalWrite(chargePin, HIGH); //turn on pin 7
}

ISR(TIMER1_COMPB_vect)
{
  digitalWrite(outputPin, LOW); //turn off pin 8
  digitalWrite(chargePin, LOW); //turn off pin 7
  delayPeriodStart = micros();//start looking for response
  trigger = true;
}

void delayPeriodTiming()
{
  delayPeriod = micros() - delayPeriodStart;
}