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 
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
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:
- Pulse from VR sensor at flywheel
- Interrupt on D2.
- Calculate rpm based on time between pulses on D2.
- Start Timer2. Ticks based on rpm and ignition advance required.
- Timer 2 interrupts and starts Timer1.
- OCR1A interrupts and turns on DC to DC converter.
- 2ms later OCR1B interrupts and turns off the DC to DC converter and turns on a thyristor.
- Capacitor discharges and fires spark plug.
- Timer1 interrupts on Top and switches off thyristor around 10 ticks (haven't decided yet) after OCR1B interrupt.
- 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. 
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.