Atmega328 input capture

Hello,

I have resumed work on my long-term project, a CarDuino which, among other things, will also monitor the engine's rpm.

To do this, I will tap into the car's crankshaft sensor. It's a hall effect sensor that is stimulated by a flywheel with notches on it. 33 notches and therefore 33 rising edges from the hall sensor equal one revolution of the crankshaft.

Intervals between rising edges can be as short as 250 microseconds at 7000 rpm, so I can't just do it with external interrupts or what-have-you.

My idea was to use the Atmega328's input capture functionality to do the following (faux code):

if(rising edge) ticks++; // every tick is one notch on the crankshaft sensor flywheel


if(2 seconds have passed){

rpm = (int) (ticks/33)*30 ;            // divide the total number of ticks every two seconds by 33 to get the 
                                       // number of revolutions of the crankshaft in those two seconds
                                       // then multiply by 30 to get rounds per minute

ticks = 0;

}

So far, so good. Unfortunately, my grasp of timers and counters is still very tentative at this point. I have tried to make the following code do the above, but so far, it does nothing:

volatile byte ticks = 0;
unsigned long microsStart = 0;
unsigned long microsNow = 0;
unsigned long microsDifference = 0;
int rpm;

void setup() {
  //Set Initial Timer value
  TCNT1 = 0;
  //First capture on rising edge
  TCCR1B |= (1 << ICES1);
  //Enable input capture and overflow interrupts
  TIMSK1 |= (1 << ICIE1) | (1 << TOIE1);
  //Start timer without prescaler
  TCCR1B |= (1 << CS10);
  //Enable global interrutps
  sei();

  Serial.begin(115200);
  Serial.println("Serial OK");

}

ISR(TIMER1_CAPT_vect) {

  ticks++;
}

void loop() {

  microsNow = micros();

 
  if ((microsDifference = microsNow - microsStart) >= 2000) {

    rpm = (int) (ticks / 33) * 30;

    ticks = 0;
    microsStart = micros();

    Serial.println(rpm);
  }

}

I am beginning to get an understanding of timers and registers, but at this point, I am still out of my depth trying to do this bit of the project on my own, so any help will be appreciated.

I don't understand why you can't use an external interrupt with code something like this

void myISR() {
   pulseCount++;
   if (pulseCount == 33) {
      latestRevMicros = micros();
      newRev = true;
      pulseCount = 0;
   }
}

Then your other code can check for newRev becoming true and the read the value of latestRevMicros.

The other option is to find something that just triggers one or two pulses per revolution.

...R

As far as I know, external interrupts are too slow. For my purposes anyway.

There will be plenty of other code on that Atmega too, which will monitor other things such as the injection signal and the vehicle speed signal. All of which "time critical" and where I need to work on a basis of single milliseconds.

So my idea was to measure injection and speed either with the two external interrupts or pin change interrupts, and then clock the crankshaft using input capture, as this seems more practical to measure intervals of as little as 250 us with accuracy.

There are two ways you measure the frequency of a digital signal: measure its frequency or measure its period. In either case, the timers of the chip will help greatly.

The timers in the ATmega328P are in fact just counters. They count pulses. Feeding them with a clock signal allows them to "count time", but you can also change the input to be from an external digital signal like the crankshaft Hall sensor.

To measure the signal's frequency, count the number of pulses that happen in a fixed period of time.

To measure the signal's period, you count the time it takes for a fixed number of pulses to occur.

You might have more luck with the period measurement. Configure the counter in CTC mode, set the TOP value to some reasonable number of pulses, then every time the timer overflows you have an ISR that takes the micros() value and calculates the time difference from the previous time.

Yeah, the obvious number to compare and match would be 33, as the hall sensor gives out 33 rising flanks for one complete crankshaft revolution. And then count the flanks again from zero on the next crankshaft revolution.

I toyed with that at first, but wasn't really sure how to implement it.

At the moment though, I am still trying to get my head around how to tell the Atmega to count anything at all when the input pin (D8 on the Atmega328, right?) receives a rising edge.

Could you help me with bits of "timer code" that set the proper registers?

As I said, I am really still a novice with timers.

carguy:
As far as I know, external interrupts are too slow.

What makes you say that? Have you carried out some tests?

Maybe there is a case for dedicating an Attiny to counting the pulses and raising an interrupt in another Arduino once per revolution. It's not as if an Attiny is very expensive.

...R

I was considering letting an additional Attiny do the job of counting the revs earlier on in this project, but was then convinced by other board members on here that the job could be done all by one Atmega328.

I'm not entirely sure a pin change interrupt will be fast enough. I think I've already tried that and it had trouble keeping up at very high revs.

Again, this chip will be loaded with things to do. It will monitor three square waves (injection, vehicle speed, crankshaft), as well as the coolant level, fuel tank level, coolant temperature and oil temperature, AND it will forward that data to another Atmega via I²C, which will power a TFT display.

(if you ask why I²C and separate modules, the car has a mid-engine that is located towards the back end of the car, with the engine control unit near the trunk/boot. I²C will be a "wire saving" way of transferring all that data across the whole length of the car to the dashboard in the front)

carguy:
At the moment though, I am still trying to get my head around how to tell the Atmega to count anything at all when the input pin (D8 on the Atmega328, right?) receives a rising edge.

Could you help me with bits of "timer code" that set the proper registers?

As I said, I am really still a novice with timers.

You have a datasheet handy, right?

The Clock Select (CSx) bits in TCCR1B have options for an external clock source on the T1 pin, counting on either the rising or falling edge. T1 is pin 11 on the chip, which corresponds to Arduino Pin 5.

The Waveform Generation Mode (WGM) bits has a CTC option (Clear Timer on Compare Match), which can use OCR1A as the TOP value, automatically resetting the count value when it matches the value of the register. You can use the timer compare match interrupt to call an ISR when the timer hits the OCR1A value.

Jiggy-Ninja:
You have a datasheet handy, right?

The Clock Select (CSx) bits in TCCR1B have options for an external clock source on the T1 pin, counting on either the rising or falling edge. T1 is pin 11 on the chip, which corresponds to Arduino Pin 5.

The Waveform Generation Mode (WGM) bits has a CTC option (Clear Timer on Compare Match), which can use OCR1A as the TOP value, automatically resetting the count value when it matches the value of the register. You can use the timer compare match interrupt to call an ISR when the timer hits the OCR1A value.

I am really struggling with all that. It makes sense to me just reading it, but I have no idea how to put that in code.

carguy:
Again, this chip will be loaded with things to do. It will monitor three square waves (injection, vehicle speed, crankshaft), as well as the coolant level, fuel tank level, coolant temperature and oil temperature, AND it will forward that data to another Atmega via I²C, which will power a TFT display.

Obviously I don't know what proportion of the CPU' s time all that stuff will take and whether there might still be enough time to deal with a lot of interrupts.

However I would be wary of any solution that was close to the limit of the Arduino's capability as any little thing could cause it to fail. I would like to feel my project has a good margin of error and is not living on the edge. Using the interrupts as a "clock" for a Timer may free up some clock cycles - but will it give enough of a margin?

There are lots of ill-considered plans here for using multiple "Arduinos" for projects when they are clearly unnecessary. That does not mean that multiple devices should never be used.

...R

Here's an example sketch for a Timer1 external clock source rising edge. Input is on pin D5. Counts for two seconds and calculates rpm based on 33 pulses/rev.

unsigned long dwell = 2000;
unsigned long final_counts;
unsigned long start_time = millis();
unsigned long measured_time;
unsigned long current_time = millis();

void setup()
{
  Serial.begin(115200);
  TCCR1A = 0; //initialize Timer1
  TCCR1B = 0;
  TCNT1 = 0;
  pinMode( 5, INPUT_PULLUP); //external source pin for timer1
  //set external clock source pin D5 rising edge
  TCCR1B =  bit (CS10) | bit (CS11) | bit (CS12);

  // pinMode(11, OUTPUT);
 // tone(11, 4000);//test pulse jumper pin 11 to pin 5
}

void loop()
{
  current_time = millis();
  if (current_time - start_time >= dwell)
  {
    TCCR1B = 0; //stop counter
    final_counts = TCNT1; //frequency limited by unsigned int TCNT1 without rollover counts
    TCNT1 = 0;
    measured_time = current_time - start_time;
    start_time = current_time;
    int rpm = (final_counts * 30) / 33;
    TCCR1B =  bit (CS10) | bit (CS11) | bit (CS12); //restart external clock source

    Serial.print(measured_time); 
    Serial.print("\t\t");
    Serial.println(rpm); 
  }
}

Thanks a million, cattledog, I am in the process of implementing your code and it's looking very good... :slight_smile:

I think I will still need to figure out what to do when the rpms change quickly, because at the moment, that is producing erratic rpm values.

Changing the intervals from two seconds to one second has helped, but maybe the best way forward will be some sort of averaging algorithm.

@Robin2: I have attached the near-complete sketch for the Atmega that shows what I want it to do. It is so far still missing the rpm input capture.

engine-stats-module.ino (12.3 KB)

This is the first thing that caught my eye (line 223)

while (microsDifference <= 3000000) {

If that is intended to wander around in the wilderness for 3 millisecs it has no place whatever in any responsive program.

...R

Right... well, the project was on hold for several months and I am just now getting back to having a look at all the code again that I have written for it.

That while loop really looks out of place there. I am going to have to change that as the project progresses.

Its intended purpose is/was to add up the total amount of fuel that is injected every three seconds (read: the number of milliseconds that the injection valves were open). Further down in the code, that number gets multiplied by the amount of fuel that the injectors are capable of releasing in a given time interval as per their manufacturer datasheet.

If you want a responsive program you should not have any blocking code such as a delay() or a FOR or a WHILE that takes more than a few microseconds to complete.

...R

I know... the whole sketch is due for a major overhaul. It was one of the last things I had been working on before the project was put on hold, and it has its flaws, to say the least.