Counting timer overflow before interrupt

Hello chaps.

I've been playing for a couple of hours, and this is the most stable the code has been.

unsigned long lastTimePrinted;

const int potIn = A0;
unsigned int ticks;
int ADCvalue = 10;

//overflow count variables
volatile byte overflowCount;//incremented in overflow isr
volatile byte lastOverflowCount;//variable to confirm overflows
volatile byte targetOverflowCount;
volatile byte residualCompareCounts;
volatile byte oldTCCR2A;
volatile byte oldTCCR2B;
volatile unsigned long interval;

volatile bool isrRunning = false;

bool working;

////////////////////////////////////////////////////////////////////////////////////////////////

void setup() {

  //  Serial.begin(115200);

  cli();

  ADCSRA =  bit (ADEN);   // turn ADC on
  ADMUX =   bit (REFS0);  // AVcc
  ADCSRA &= ~(bit (ADPS0) | bit (ADPS1) | bit (ADPS2)); // clear prescaler bits
  ADCSRA |= bit (ADPS2);                               //  16
  //  ADCSRA |= bit (ADPS0) | bit (ADPS2);                 //  32
  //  ADCSRA |= bit (ADPS1) | bit (ADPS2);                 //  64
  //  ADCSRA |= bit (ADPS0) | bit (ADPS1) | bit (ADPS2);   // 128

  TCCR2A = 0;                              // Clear TCCR2A Register. Set all to zero
  TCCR2B = 0;                              // Clear TCCR2B Register. Set all to zero
  TIMSK2 = 0;                              // Clear TIMSK2 Register. Set all to zero
  TIMSK2 |= (1 << TOIE2);                  // Enable Interrupt on over Flow
  TIMSK2 |= (1 << OCIE2A);                 // Enable Interrupt on A
  TIFR2 |= bit(OCF2A) | bit(OCF2B) | bit(TOV2);
  //    TCCR2B |= bit(CS22) | bit(CS20);     // Prescale 128. Start Timer2
  TCCR2B |= bit(CS22);                   // Prescale 64. Start Timer2
  //  TCCR2B |= bit(CS21) | bit(CS20);     // Prescale 32. Start Timer2

  oldTCCR2B = TCCR2B;
  TCCR2B = 0;                              // Clear TCCR2B Register. Set all to zero. Stop Timer2
  TCNT2 = 0;                               // Zero Timer2

  sei();                                   // Enable interrupts

  digitalWrite(13, LOW);
  pinMode(13, OUTPUT);

}

////////////////////////////////////////////////////////////////////////////////////////////////

void loop() {

  if (!working)
  {
    bitSet (ADCSRA, ADSC);  // start a conversion
    working = true;
  }

  // the ADC clears the bit when done
  if (bit_is_clear(ADCSRA, ADSC))
  {
    ADCvalue = ADC;  // read result
    ticks = map(ADCvalue, 0, 1023, 2, 1000);
    working = false;
  }

  if (!isrRunning) {

    targetOverflowCount = ticks / 256;         //  truncate with integer math. Example ticks = 300. targetOverflowCount = 1.171875 which then = 1, so 1 overflow
    residualCompareCounts = ticks % 255;       //  Remaining from ticks after removing overflow value. Example 300 ticks. residualCompareCounts = 44

    if (targetOverflowCount != 0) {
      TIFR2 |= bit(OCF2A) | bit(OCF2B) | bit(TOV2);
      TCCR2B = oldTCCR2B;                      // Start Timer2
    }
    else {
      TCCR2B = oldTCCR2B;                      // Start Timer2
      OCR2A = residualCompareCounts;
      TIMSK2 |= bit(OCIE2A);                 //  enable compare match interrupt
      TIFR2 |= bit(OCF2A) | bit(OCF2B) | bit(TOV2);
    }
    isrRunning = true;
  }

  //  if (millis() - lastTimePrinted >= 1000) {
  //    Serial.println("--------------------");
  //    Serial.println(ticks);
  //    Serial.println(targetOverflowCount);
  //    Serial.println(residualCompareCounts);
  //    Serial.println(OCR2A);
  //    Serial.println(ADCvalue);
  //    Serial.println(lastOverflowCount);//add this line
  //    lastTimePrinted = millis();
  //  }
}

////////////////////////////////////////////////////////////////////////////////////////////////

ISR(TIMER2_OVF_vect) {              //

  if (++overflowCount == targetOverflowCount )
  {
    OCR2A = residualCompareCounts;
    TIMSK2 |= bit(OCIE2A);        //enable compare match interrupt for next cycle
    TIFR2 |= bit(OCF2A) | bit(OCF2B) | bit(TOV2);
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////

ISR(TIMER2_COMPA_vect) {  // Save Timer1 values and stop timer

  oldTCCR2B = TCCR2B;
  TCCR2B = 0;                              // Clear TCCR2B Register. Set all to zero. Stop Timer2
  TCNT2 = 0;
  overflowCount = 0;                       //reset overflowCount
  TIMSK2 &= ~bit(OCIE2A);                //  disable compare match interrupt
  TIFR2 |= bit(OCF2A) | bit(OCF2B) | bit(TOV2);

  PINB = bit(5);                           // Toggles builtin LED

  isrRunning = false;
}

There's the odd glitch now and again. I have a sneaky suspicion that it's down to the time take for the analogRead. Since stealing code from Nick Gammon's site things took a good step forward. That won't make any difference in my CDI code, but it's certainly good to have that knowledge.

The clock prescaler is now at 64. Going to 128 adds stability.

The ADC prescaler is now set to 16. Going to 32 or 64 seems to add accuracy, but then may crash at a very low OCR2A value as I think it's not doing all the calculations fast enough.

Commenting out the Serial.println() made a big difference to stability.

Anyway, Happy with how things have gone today. Definitely light at the end of the tunnel, and I've gained a lot of knowledge, which is always a good thing.

It's now half past midnight, so time to clear off.

Next plan will be to try to slot this code into my CDI code and see how we get on.

Cheers for now.

Matt

When the recommended ADC clock is 125 kHz, why are you selecting 1 MHz?

First time playing with the ADC registers by the way, so that was pretty exciting :slight_smile:

Just out of interest, how do the following differ:

bit(5);
_bv(5);
(1 << 5)

Can't say that I saw any difference.

Cheers

Matt

These are bureaucratic styles doing the same thing; rather, I prefer --
bitSet()

@GolamMostafa

It was giving stable results at 16 so I stuck with it.

Reading Nick Gammon's page he too was getting stable reading so I thought might as well give it a go.

Cheers

Matt

Thanks for the info

Look at the following ADC's features summary and see the conversion time which sets the conversion frequency of the ADC,

The range is: 13 us, ..., 260 us ===> 1 MHz, ..., 50 kHz
You are operating the ADC at the edge -- is it stable operation?

Isn't the 4 microsecond granular counter that micros() returns FAST ENOUGH?

Why count to 1000 3 times with the overhead required when 2 bytes can count to 3000?

How long does your fastest engine cycle take in microseconds?

How are you sensing RPM? Once per cycle?

@GoForSmoke

At 13,000rpm it's around 4600us/rev.

This timer will set the ignition advance timing and I want +/- 0.5° of accuracy, so at 13,000rpm that's 6.4us give or take.

Having used CDIs that are adjustable within 0.1° and enjoyed the benefits, might as well get it as good as I can.

Cheers

Matt

Oh and yes, sensing once a cycle. Using a variable reluctance sensor.

AVR interrupts have 85 cycles just overhead.

You might switch to a Teensy LC with 48MHz ARM M0. The base cost is $12.

@GoForSmoke

Thanks for your suggestion. However, I have the Arduino so I'll stick with that for now.

More than anything it's a learning exercise. I've spent hours reading the datasheet on the 328P and other AVR chips and enjoy playing with them.

This is just a fun project. No time limits or deadlines. And just want to see if I can get it to work. Which it will. At some point.

Then I'll have another go with an ESP32 which has more than enough capability to do the job.

I have another long running data logger project with the ESP32 which is pretty good as far as I'm concerned. Monitors many things from ambient air temp through to engine knock and exhaust gas temp. The only limit really is that I've run out of pins LOL!

Cheers

Matt

Sorry chaps. Not time to play today. Update soon

Matt

Can you hang some 'pin-multiplier' shift registers or port expanders on your SPI bus?

@GoForSmoke

Yes! That is a plan and I have some modules to do this.

1 Like

There's quite a lot on the subject between the Main site and the Forum.

Running void loop() at about 20 us on average, I could be pretty sure of catching a wider tolerance on time.. perhaps 50 or 60 us window but you need 6. The commands that have to run inside of the tolerance can't be guaranteed ro do so. Even with a 12 us loop() it's not good enough.

@GoForSmoke @cattledog

To try and break it down. The process will be as follows:

  1. Pulse from VR sensor at flywheel
  2. Interrupt on D2.
  3. Calculate rpm based on time between pulses on D2.
  4. Start Timer2. Ticks based on rpm and ignition advance required.
  5. Timer 2 interrupts and starts Timer1.
  6. OCR1A interrupts and turns on DC to DC converter.
  7. 2ms later OCR1B interrupts and turns off the DC to DC converter and turns on a thyristor.
  8. Capacitor discharges and fires spark plug.
  9. Timer1 interrupts on Top and switches off thyristor around 10 ticks (haven't decided yet) after OCR1B interrupt.
  10. Wait for next interrupt on D2.

The D2 interrupt may come before Timer1 has reached TOP, hence why I'm starting Timer2. Then Timer1 should have reached TOP before Timer2 interrupts.

The process for a CDI is quite simple, but choosing the right components and getting the timing right has been a little bit of a challenge.

With simple switching code and no input pulse I've ha it running for hours at a simulated 5,000rpm. Nothing got hot or exploded.

Using a pulse signal proved a little trickier due to electrical noise and interference, but solved this with some bypass caps and a resistor here and there.

And I'm sure that if/when I've actually got it in a bike then there will be all other ort of noise issues which I'll have to deal with.

Initially I'll just use a fixed ignition advance to test it. And try different advance values to check that the firing of the thyristor changes as it should. Then I'll try to implement variable timing based on rpm. I've built a simple excel table which uses interpolation between two points and this works fine in excel, so I'll try to include that somewhere.

This may work OK at relatively low rpm where there's plenty of time to do the calculations but I fear that the Arduino may struggle at high rpm. But we shall see.

As I said, also plan to try this with an ESP32, which with it's 64bit timers should be a little easier to do (maybe)... The idea is the same pretty much with an external interrupt stating the process and then using some or all of the available 64 bit timers to start each section of the process. Possible in theory. The great thing with the ESP32 is that I can split calculations between cores, with semaphore protection of the variables, to keep things running at light speed. I doubt that the ESP32 will break a sweat running the code. My data logger does way more work, including updating the TFT display and is pretty stable thanks to using both cores.

Anyway, let's see if I can sneak away from the wife today and slip into the factory to test some code.

Cheers

Matt

@cattledog @GoForSmoke

Just a quickie.

Managed to gel the previous CDI code with the overflow code and it works with a couple of glitches now and again:

// OK over 55Hz (3300rpm) due to OCR2A > 255


///////////////////////
//  Assigned Pins    //
///////////////////////

const int pulsePin = 2;             //  Interrupt pin to read the pulse from the VR sensor (INT0)
const int transistorPin = 10;       //  PB2. Turns PWM output on and off on PIN 1 of UC3843 (COMP pin)
const int SCRPin = 9;               //  PB1. Turns SCR on and off
const int LEDpin = 13;              //  PB5. Turns on board LED on and off

///////////////////////
//  Variables of CDI //
///////////////////////

float baseAdv = 15.;                // Base advance. Measured at TDC. End of lobe to centre of VR sensor in degrees
float ignAdv1 = 25.;               // Ignition Advance #1.
unsigned int ignErr = 2;                     // Ignition advance error correction. Increases or decreases timing. Not yet used
unsigned int ticks;
float ticksPerRev = 3000.;          // Number of ticks per rpm @ 500rpm
float ticksToSCR = 2958.;           // 5° BTDC @ 500rpm. (ticksPerRev/360*355)
float ticksToTransistor = 500.;     // 2ms at prescale 64
float ticksPerDeg;                  //
volatile byte oldTCCR1A;
volatile byte oldTCCR1B;
volatile byte oldTCCR2A;
volatile byte oldTCCR2B;
volatile bool pulseFlag = false;    // triggered on pulse interrupt to disable interrupts while timer is running

/////////////////////////////////
//  Calibration of rpm counter //
/////////////////////////////////

volatile unsigned long timeOfInterrupt;
unsigned long oldTimeOfInterrupt;
unsigned long timeBetweenPulses;
float rpm = 500.;

/////////////////////////////////
//  overflow count variables   //
/////////////////////////////////

volatile byte overflowCount;//incremented in overflow isr
volatile byte lastOverflowCount;//variable to confirm overflows
volatile byte targetOverflowCount;
volatile byte residualCompareCounts;
volatile unsigned long interval;

unsigned long lastTimePrinted;

volatile bool isrRunning = false;

void setup() {

  Serial.begin(250000);

  cli();

  TCCR1A = 0;                              // Clear TCCR1A Register. Set all to zero
  TCCR1B = 0;                              // Clear TCCR1B Register. Set all to zero
  TIMSK1 = 0;                              // Clear TIMSK1 Register. Set all to zero
  TIFR1 |= (1 << OCF1B) | (1 << OCF1A);    // Clear Flags on OCR1B and OCR1A
  EIFR |= (1 << INTF0);                    // Clear Flags on INT0

  TCCR1A |= (1 << WGM11);                  // Mode 14. Fast PWM. TOP ICR1
  TCCR1B |= (1 << WGM12) | (1 << WGM13);   // Mode 14. Fast PWM. TOP ICR1
  TIMSK1 |= (1 << OCIE1A);                 // Enable Interrupt on A
  TIMSK1 |= (1 << OCIE1B);                 // Enable Interrupt on B
  TIMSK1 |= (1 << TOIE1);                  // Enable Interrupt on over Flow
  TIFR1 |= (1 << OCF1B) | (1 << OCF1A) | (1 << TOV1); // Clear Flags on OCR1B and OCR1A
  TCCR1B |= (1 << CS11) | (1 << CS10);     // Prescale 64. Start Timer1

  EICRA |= (1 << ISC01) | (1 << ISC00);    // External interrupt on rising edge for INT0
  EIMSK |= (1 << INT0);                    // Enable external interrupts on INT0

  TCCR2A = 0;                              // Clear TCCR2A Register. Set all to zero
  TCCR2B = 0;                              // Clear TCCR2B Register. Set all to zero
  TIMSK2 = 0;                              // Clear TIMSK2 Register. Set all to zero
  TIMSK2 |= (1 << TOIE2);                  // Enable Interrupt on over Flow
  TIMSK2 |= (1 << OCIE2A);                 // Enable Interrupt on A
  TIFR2 |= (1 << OCF2B) | (1 << OCF2A) | (1 << TOV2);  // Clear Flags on OCR2B and OCR2A
  //  TCCR2B |= (1 << CS22) | (1 << CS20);     // Normal Mode. Prescale 128. Start Timer2
  //  TCCR2B |= (1 << CS22);                   // Normal Mode. Prescale 64. Start Timer2
  //  TCCR2B |= (1 << CS21) | (1 << CS20);     // Normal Mode. Prescale 32. Start Timer2
  TCCR2B |= (1 << CS21);                       // Normal Mode. Prescale 8. Start Timer2

  oldTCCR1A = TCCR1A;
  oldTCCR1B = TCCR1B;
  TCCR1A = 0;                              // Clear TCCR1A Register. Set all to zero. Stop Timer1
  TCCR1B = 0;                              // Clear TCCR1B Register. Set all to zero. Stop Timer1
  TCNT1 = 0;                               // Zero Timer1

  oldTCCR2A = TCCR2A;
  oldTCCR2B = TCCR2B;
  TCCR2A = 0;                              // Clear TCCR2A Register. Set all to zero. Stop Timer2
  TCCR2B = 0;                              // Clear TCCR2B Register. Set all to zero. Stop Timer2
  TCNT2 = 0;                               // Zero Timer2

  sei();                                   // Enable interrupts

  digitalWrite(pulsePin, LOW);             // Interrupt pin to read the pulse from the VR sensor
  digitalWrite(transistorPin, HIGH);
  digitalWrite(SCRPin, LOW);
  digitalWrite(LEDpin, LOW);
  pinMode(pulsePin, INPUT);
  pinMode(transistorPin, OUTPUT);
  pinMode(SCRPin, OUTPUT);
  pinMode(LEDpin, OUTPUT);

  timeBetweenPulses = micros();
  oldTimeOfInterrupt = 0;

}

void loop() {

  if (pulseFlag) {
    if (timeOfInterrupt - oldTimeOfInterrupt >= 4000) {         // Code stops if one revolution is faster than 4500ms. Rev limiter.
      EIMSK &= ~(1 << INT0);                                    // Disable external interrupts on INT0
      timeBetweenPulses = timeOfInterrupt - oldTimeOfInterrupt;
      oldTimeOfInterrupt = timeOfInterrupt;
      rpm = 60. / timeBetweenPulses * 1000000.;
      ticksPerRev = 250000. / (rpm / 60.);                      // 1 second = 250000 ticks. Ticks per 360° of rotation. @1,000rpm, ticksPerRev = 15,000
      ticksPerDeg = ticksPerRev / 360.;
      ticksToSCR = ticksPerRev - (ignAdv1 * ticksPerDeg) - ignErr;
      OCR1A = ticksToSCR - ticksToTransistor - 1;               // Switch on transistor to pull pin 1 of UC3843 (COMP) to ground to stop charging the transistor. Switched in COMPA Interrupt
      OCR1B = ticksToSCR - 1;                                   // Turn SCR ON and OFF to discharge capacitor into coil
      ICR1 = ticksToSCR + 15;                                    // Set ICR1 to 5 ticks above ticks to SCR. Then turn off TIMER1 in the OVF interrupt
      //      OCR2A = ticksPerDeg * baseAdv - 1;
      pulseFlag = false;
      EIMSK |= (1 << INT0);                                     // Enable external interrupts on INT0
    }
  }

  if (!isrRunning) {

    ticks = ticksPerDeg * baseAdv * 8 - 1;
    targetOverflowCount = ticks / 256;         //  truncate with integer math. Example ticks = 300. targetOverflowCount = 1.171875 which then = 1, so 1 overflow
    residualCompareCounts = ticks % 255;       //  Remaining from ticks after removing overflow value. Example 300 ticks. residualCompareCounts = 44

    if (targetOverflowCount != 0) {
      TIFR2 |= bit(OCF2A) | bit(OCF2B) | bit(TOV2);
    }
    else {
      OCR2A = residualCompareCounts;
      TIMSK2 |= bit(OCIE2A);                 //  enable compare match interrupt
      TIFR2 |= bit(OCF2A) | bit(OCF2B) | bit(TOV2);
    }
    //    isrRunning = true;
  }

  //  if (millis() - lastTimePrinted >= 250) {
  //    Serial.println("--------------------");
  //    Serial.println(rpm);
  //    Serial.println(ticksPerRev);
  //    Serial.println(ticksPerDeg);
  //    Serial.println(ticksToSCR);
  //    Serial.println(OCR1A);
  //    Serial.println(OCR1B);//add this line
  //    Serial.println(targetOverflowCount);
  //    Serial.println(OCR2A);//add this line
  //    lastTimePrinted = millis();
  //  }

}

////////////////////////////////////////////////////////////////////////////////////////////////

ISR(INT0_vect) {  //  Grab timestamp, start Timer2 and set flag to run calculation in loop

  //  PORTB &= ~bit(5);                           // Toggles builtin LED
  timeOfInterrupt = micros();
  TCCR2B = oldTCCR2B;                      // Start Timer2
  pulseFlag = true;
  isrRunning = true;

}

////////////////////////////////////////////////////////////////////////////////////////////////

ISR(TIMER2_OVF_vect) {              //

  if (++overflowCount == targetOverflowCount )
  {
    OCR2A = residualCompareCounts;
    TIMSK2 |= bit(OCIE2A);        //enable compare match interrupt for next cycle
    TIFR2 |= bit(OCF2A) | bit(OCF2B) | bit(TOV2);
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////

ISR(TIMER2_COMPA_vect) {  // Save Timer1 values and stop timer


  TCCR1A = oldTCCR1A;                      // Start Timer1
  TCCR1B = oldTCCR1B;                      // Start Timer1
  //  TIFR1 |= bit(OCF1A) | bit(OCF1B) | bit(TOV1);

  oldTCCR2B = TCCR2B;
  TCCR2B = 0;                              // Clear TCCR2B Register. Set all to zero. Stop Timer2
  TCNT2 = 0;
  overflowCount = 0;                       //reset overflowCount
  TIMSK2 &= ~bit(OCIE2A);                //  disable compare match interrupt
  TIFR2 |= bit(OCF2A) | bit(OCF2B) | bit(TOV2);
  //  TCCR1A = oldTCCR1A;                      // Start Timer1
  //  TCCR1B = oldTCCR1B;                      // Start Timer1

  isrRunning = false;
}


////////////////////////////////////////////////////////////////////////////////////////////////

ISR(TIMER1_COMPA_vect) {

  PORTB &= ~bit(2);                        // Switch Transistor LOW. PWM starts. Charges capacitor

}
////////////////////////////////////////////////////////////////////////////////////////////////

ISR(TIMER1_COMPB_vect) {

  //  PORTB |= bit(5);                           // Toggles builtin LED
  PINB = bit(2);                           // Switch transistor HIGH. PWM stops. Ready to discharge capacitor
  PORTB |= bit(1);                         // Switch on SCR

}

////////////////////////////////////////////////////////////////////////////////////////////////

ISR(TIMER1_OVF_vect) {                     // Save Timer1 values and stop timer

  PINB = bit(1);                           // Switch off SCR

  oldTCCR1A = TCCR1A;
  oldTCCR1B = TCCR1B;
  TCCR1A = 0;                              // Clear TCCR1A Register. Set all to zero. Stop Timer1
  TCCR1B = 0;                              // Clear TCCR1B Register. Set all to zero. Stop Timer1
  TCNT1 = 0;

}

And a couple of scope traces for yor enjoyment:

1000rpm:

13000rpm

Ch1 (yellow) is the input pulse to interrupt on pin 2
Ch2 (blue) is to switch the transistor from pin 10
Ch3 (orange) is to switch the SCR on and off.

I've been on it all afternoon and I'm quite happy with that.

Matt

Not sure that I'm doing it the best way in thee loop() but it seems to work.

Accuracy is a little off but I suspect that down to the time taken in the interrupts and probably got some errors in my maths. :slight_smile:

Comments?

Matt​

With binary integers you multiply before you divide to keep higher precision.
Type unsigned long is good for 9 places.

Wait... stop... you have FLOATS in there? 8 bit cpu with no fpu, so long fast calc.