Attiny 45 - Timer based input capturing vs. pin change interrupts

You will be using timers to count pulses from the crankshaft. The timers are hardware...you can only set one software variable for them, the "overflow"...but you wont be doing that. You will just be incrementing them using an "external clock source"...the crankshaft sensor/fuel injector sensors/ etc.

All your main loop will then do, is every cycle of the loop() you will check the time gone by (millis() on a spare timer) and then get the values held in the timer registers (like TCNT0, TCNT1, TCNT2 etc.). Then reset the timers to 0 again (TCNT0 = 0; etc.) ready for the next loop round.

I think you are adding a lot of complication for the sake of say 0.3% accuracy in revs for a user's graphical user interface...

You'd use an interrupt for what you want, which causes "some" code blocking. You'd also be using an interupt and a timer to time the time between 2 interupts on a pin...even more hardware and software usage with more complication...

Example, while driving at 3000rpm, the crankshaft will turn from its current position 3000/60 times = 50 revolutions of the crankshaft in a second.

Now, there are 32 rises each revolution...

32*50 = 1600 rises from the timing pins.

Now we don't know where the crank ended on a cycle...might be in a "dip" bit where there is a gap or not...but there are only 4 in that last cycle...

So we know esentially, we are 1600 ticks +/- 4 ish (might be +/- 2...someone may clarify).

Being 4 rises/ticks out out of 1600 = 0.25% inaccuracy. Basically itd say 3000 RPM on the screen, but in reality it could be between 2996 and 3003 ish. Is that an issue for a graphical user interface?

No, I completely understand what you're saying. For simplicity's sake, it will definitely be the way forward to just count the rising edges as they happen, and simply assume that one crankshaft turn is 32 teeth/ 32 risings.

Once my CarDuino can reliably calculate the revs, I was going to round off whatever figure it comes up with to the nearest multiple of ten, maybe even fifty, before displaying it on the screen. I.e. 3163 rpm would show up on the TFT screen either as 3160 or 3150. While in traffic, you can only afford split second glances at your CarDuino screen anyway, so it does not add any value to have an rpm reading that is dead precise.

What I meant was, how to mimic the crankshaft signal pattern on the simulator circuit that feeds the simulated waves into the CarDuino. Even if we just assume in the CarDuino's code that 32 risings equals one crankshaft turn, I was thinking maybe generate a simulated wave with the simulator circuit that precisely mimics the crankshaft sensor pattern. Like in the array that I posted.

carguy:
What I meant was, how to mimic the crankshaft signal pattern on the simulator circuit that feeds the simulated waves into the CarDuino. Even if we just assume in the CarDuino's code that 32 risings equals one crankshaft turn, I was thinking maybe generate a simulated wave with the simulator circuit that precisely mimics the crankshaft sensor pattern. Like in the array that I posted.

I think there is no reason for this.

On second thought, that is probably true. If the CarDuino counts 32 pulses from the trigger wheel as one 360° crankshaft turn regardless, then there is probably really no point in trying to emulate the trigger wheel's tooth pattern on the simulator circuit.

I just had another attempt at speeding up the frequency of the crankshaft signal on my simulator circuit, but the shortest I can get it to be is HIGHs of around 300 us, which is barely enough to simulate roughly 1500 rpm. The signal won't budge beyond that. So I am going to have to use Johnny's timer sketch which he graciously wrote for me, at least for the crankshaft.

I am probably going to implement that this weekend. And once that's done and working, I will have a go at simplifying the engine data module, using timers as well.

I think your maximum speed is so low because you are doing too much calculations. You should avoid floating point calculations whenever possible if you are interested in speed. *2/3 is nearly the same as *0.6933 and it is faster. I doubt you need such precision for your signal generator when you control it with pot. Also if you are doing something else not shown (Serial.print) it speed it down also. With properly written code and dedicated CPU (no other work for it) I would expect speed of software square wave generated by code in C at least F_CPU/20 ~ 400 kHz for 8MHz clock. Maybe a bit faster with Assembler.

BTW did you think about using hardware to generate the test signal? 555 or Schmitt trigger NOT (NAND/NOR) gate may be easy way to get pot controllable square wave. But on the other hand you need learn to use timers and this is good exercise :wink:

EDIT: most of the delay in the code you posted some time ago is probably not due to floating point aritmetic but analogRead in each cycle which is blocking. You could tirgger ADC conversion, continue generating PWM and when result from ADC is ready read it. Other possibility is to set ADC free running and just read ADCW register.

Smajdalf:
EDIT: most of the delay in the code you posted some time ago is probably not due to floating point aritmetic but analogRead in each cycle which is blocking. You could tirgger ADC conversion, continue generating PWM and when result from ADC is ready read it. Other possibility is to set ADC free running and just read ADCW register.

Other options for this are...
1 Speed up the ADC clock so the sample time is shorter.

2 Use a Schmitt trigger on the signal to effectively convert the analogue signal to a digital one as digitalRead is a lot faster than an analogRead. Or feed the signal into the T0 pin to count the signal edges in hardware but this may mess with the core times as it may use Timer0 for millis/micros.

3 Finally, if you don't want to use extra hardware to shape the signal you could use the the tiny85's internal analogue comparator that effectively converts the analogue input to a digital signal like the Schmitt trigger (at the expense of an extra pin).

ok again, this is the sketch that I was using so far.

#include <digitalWriteFast.h>

// 5k pot inputs
#define kphIn A5         
#define rpmIn A3        
#define injectorIn A4  

// signal outputs
#define kphOut 13 
#define rpmOut 11
#define injectorOut 7

unsigned long timeMicros;
unsigned long timeMillis;

// injector variables
int injector;

unsigned long injectionFactor;
unsigned long injectionOffFactorBasic;
unsigned long injectionOffFactor;
unsigned long injectorLowTimestamp;
unsigned long injectorHighTimestamp;
unsigned long injectionLowElapse;
unsigned long injectionHighElapse;
boolean injectionIntervalStart = false;

// kph variables
int kph;

unsigned long kphFactor;
unsigned long kphTimestamp = millis();
unsigned long kphElapse;
boolean kphOn = false;
boolean rpmOn = false;

// rpm variables
int rpm;
int rpmFactor;

unsigned long rpmTimestamp = micros();
unsigned long rpmElapse;

void setup() {

  pinModeFast(injectorIn, INPUT);
  pinModeFast(injectorOut, OUTPUT);

  pinModeFast(kphIn, INPUT);
  pinModeFast(kphOut, OUTPUT);

  pinModeFast(rpmIn, INPUT);
  pinModeFast(rpmOut, OUTPUT);

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

void loop() {

  // ------- Generating signals

  // injector signal

  // ----------------- calculating factors --------------------
  // I have observed that injection intervals at low engine load
  // are constantly around 1 ms, and at high engine load they are ca. 2 ms.
  // This should be the engine control unit's response to
  // detecting higher engine load.
  // So the ECU increases fuel injection quantities both by shortening
  // intervals between injection events and by lengthening
  // the injection interval itself.
  // Note: Fuel is injected by switching the fuel injectors to GND.
  // This opens a needle valve on the injector and causes fuel to inject.

  injector = analogRead(injectorIn);

  if (injector >= 800) injectionFactor = 2000;         // injection interval (us) at high engine load
  else if (injector < 800) injectionFactor = 1000;      // injection interval (us) at low engine load

  injectionOffFactorBasic = (1023 - injector);          // Calculating the spacing between injection events

  if (injector >= 800) injectionOffFactor = injectionOffFactorBasic * 50;         // high engine load

  else if (injector < 800) injectionOffFactor = injectionOffFactorBasic * 100;    // low engine load

  if (injectionIntervalStart) {

    timeMicros = micros();
    injectionLowElapse = timeMicros - injectorLowTimestamp;

    if (injectionLowElapse >= injectionFactor) {

      injectorHighTimestamp = micros();
      digitalWriteFast(injectorOut, HIGH);
      injectionIntervalStart = false;
    }
  }

  else if (!injectionIntervalStart) {

    timeMicros = micros();
    injectionHighElapse = timeMicros - injectorHighTimestamp;

    if (injectionHighElapse >= injectionOffFactor) {

      injectorLowTimestamp = micros();
      digitalWriteFast(injectorOut, LOW);
      injectionIntervalStart = true;
    }
  }

  // kph signal

  // The kph signal is a symmetric square wave generated
  // by the car's speed transducer. Its frequency is directly
  // proportional to the vehicle speed.

  kph = analogRead(kphIn);

  if (kph == 0) kph = 1;
  kphFactor = (int) (kph * (-0.085) + 90);  // generates life-like speed signal from 3 mph to about 125 mph

  timeMillis = millis();
  kphElapse = timeMillis - kphTimestamp;

  if (kphElapse >= kphFactor) {

    kphTimestamp = millis();

    if (kphOn) {
      digitalWriteFast(kphOut, LOW);
      kphOn = false;
    }
    else {
      digitalWriteFast(kphOut, HIGH);
      kphOn = true;
    }
  }

  // rpm signal

  rpm = analogRead(rpmIn);

  if (rpm == 0) rpm = 1;

  rpmFactor = (int) (rpm * (-0.551) + 650); // leads to 86 us if pot analogRead is at 1023, which is about 7000 rpm

  timeMicros = micros();
  rpmElapse = timeMicros - rpmTimestamp;

  if (rpmElapse >= rpmFactor) {

    rpmTimestamp = micros();

    if (!rpmOn)  {
      digitalWriteFast(rpmOut, HIGH);
      rpmOn = true;
    }
    else {
      digitalWriteFast(rpmOut, LOW);
      rpmOn = false;
    }
  }
}

I still need to get my head around a few timer related things and read up on them (I'm feeling reluctant to just use code I don't yet fully understand), but once I've managed that, I will probably finally implement Johnny's code from further up in this thread and see how it does.

And then one of the next things on my to-do list will be incorporating the Attiny's functionality so far of measuring just the rpm pulses into the Atmega. So that there is really just one chip at work in the engine data module.

On another note - is there any point in trying to keep I2C transmissions brief, if I've got such time critical code? Or will that not make a big difference?

At the moment, this is what happens when the engine data module "phones home", or rather, when the master requests data:

void requestEvent() {
  bitWrite(PORTB, led, HIGH);   //  Confirmation LED blinks every time data is sent.
                                          //  This should help debug the system when it
                                          //  gets installed in the car.

  // Generating Fuel Consumption and Speed Bytes
  byte  Data[5];
  fuelConsFloatPtr = (byte*) & fuelConsumption;

  Data[0] = fuelConsFloatPtr[0];
  Data[1] = fuelConsFloatPtr[1];
  Data[2] = fuelConsFloatPtr[2];
  Data[3] = fuelConsFloatPtr[3];

  // Maximum speed is 190 kph, so one byte will be enough
  Data[4] = (byte) (kmh_speed);

  
  Wire.write(Data, 5);

  // Generating fuel tank, oil temp and coolant temp bytes
  byte  DataGauges[9];

  // fuel tank
  fuelTankFloatPtr = (byte*) & fuelLevelFinal;

  DataGauges[0] = fuelTankFloatPtr[0];
  DataGauges[1] = fuelTankFloatPtr[1];
  DataGauges[2] = fuelTankFloatPtr[2];
  DataGauges[3] = fuelTankFloatPtr[3];

  // oil and coolant... 16 bit, always low byte first, second byte last
  DataGauges[4] = (byte) (oilTempFinal & 0xff);
  DataGauges[5] = (byte) ((oilTempFinal >> 8) & 0xff);

  DataGauges[6] = (byte) (coolantTempFinal & 0xff);
  DataGauges[7] = (byte) ((coolantTempFinal >> 8) & 0xff);

  DataGauges[8] = lowCoolant;

  Wire.write(DataGauges, 9);

  delay(50);  //    I know that this will need to be addressed; a later version of the code
                  //    will have a non-blocking led blink.

  bitWrite(PORTB, led, LOW);
}

Ok.

Can you provide this information? It may help select a micro controller that will do EVERYTHING on one chip.

As a table:

Column 1: The sensor or modules name / Part Number
Column 2: The Output or Required Pins.
Column 3: Specifics of the dataform (frequency for example).

The whole thing will probably be do-able on 1 MCU and with some code squeezing (like avoiding floats) should probably be fast enough for a good update rate on that i2c screen.

You are investing a lot of time in a "multi-IC-network" like idea, but I just feel it may be added complication and could lead to all sorts of errors...

If you are determined to have more than 1 MCU, then can you draw a clear schematic (hand drawn or whatever) so we can sort of actually "see" than trying to read what you are attempting?

I actually have detailed schematics for everything except the Atmega1284 module (the latter is about 50 percent complete). Including specifications of the sensors that I want to tap into.

It'd probably be best to update them with the latest changes, and complete the Atmega1284/TFT screen schematic. This project was on hold for a few months, and I haven't had the time to apply all the changes that are arising now to the schematics.

I will see if I can do all that this weekend.

I know that I2C is potentially daring when you've got very time-critical code; but as I said, the alternative would be to run a substantial number of wires from the back of the car, where the engine is, to the front of the car and the dashboard where the display will be. The car is a 1998 MG F.

So far, on my test rig, the 1284 requests data only every three seconds. I figured that this would be a compromise between data being as up to date as possible, and disrupting the data gathering itself as little as possible.

Besides displaying data on the screen, and neatly with lots of changing PROGMEM images, the 1284 will also be busy displaying full screen warning icons (stored as .raw images on a microSDHC card) when faults are detected. And it will do calculations regarding fuel consumption, fuel range, average speed, etc. It will also be busy juggling through different menu pages, and access EEPROM occasionally. The code for the 1284 is only about 20 percent complete so far and it's full of redundant bits and needs a major cleanup before it is presentable. But anyway, all things considered, the 1284 will have its work cut out for itself and although timers are still a bit of an "if" for me, I am not sure it would be able to analyze 86-microsecond pulses on top of everything else.

The timers are hardware. They don't care what is going on in software...at all. It is like having a little seperate "counter" MCU like you are already trying to do...but instead of having i2c to get the "count" from it, you literally read from a register like:

int revs = (TCNT1H << 8 + TCNT1L) / 32;
byte injectors = TCNT0;
byte other_timer_thing = TCNT2;

instead of a large number of operations and extra external hardware.

If really required, and the 8 bit timers are not "large" enough, then either you can turn their interrupts on so they flag up when they are "full" and you can in the interrupt ISR assign a variable like T0_fill_time = microseconds();

Which makes it so you are timing the time it takes for 256 rises of the pin rather than counting individual rises...

OR a hardware bit divider...like a frequency dividing IC.

You can just buy some 16bit counters. Texas do some 2 in 1 packages.

ok, here are the overhauled schematics. The new engine data module already omits the Attiny and assumes that its tasks will be handled by the 328. The 1284 schematic is still pending, I am actually having trouble finding it on my computer right now. There might be errors in these schematics, but they will probably mainly be due to the fact that it's 3 am here and I have been up almost 20 hours... :slight_smile:

I probably forgot to mention that there will be other modules as well. One additional module will monitor such things as outside and inside temperature, open doors and lids, windscreen wash level, and headlamp light bulbs. And, if I can be bothered, a module that checks the rear lights, and one that checks if the soft top is properly shut or all the way open.

Well, here goes. Click on the images for larger views.

I know that this project is bonkers with a capital B, but a) I've set myself no time constraints and I don't mind if it takes another year, and b) most of it actually already exists - and works - on my breadboards.

Even if the 1284 might have enough processing power, I don't think it'd have enough pins. Besides, I plan to put both the TFT display and the accompanying 1284 inside the rev counter housing in the instrument cluster. Which means I can't just run a dozen additional wires through that confined space. Linking all the modules up via I2C would also drastically reduce the number of different wires I would have to run across the whole length of the car.

It looks like you use separate switching buck converters to power each module. But my estimation is each module takes ~20mA or less (most of this consumed by LEDs). If you look into datasheet of R78E5 it shows declared efficiency is for ~50mA and more. For small load the efficiency is poor. I think you would get comparable consumption with mere linear regulator which will be smaller, cheaper, with less ripple and will tolerate higher input voltage: someone in another post said his (car) power which is 12V nominal has spikes form -10(!) to +30V - probably during ignition of engine. Your regulator should be prepared for it. I am not sure if Zener and 1.5A (why so much?) fuse is the right way.

Right... well, no problem about the power regulator. These are just schematics so far. And on my breadboards, naturally I don't use them, but everything is powered by two Uno boards and an Adafruit FTDI Friend.

Could you point me to a part number for a linear power regulator that will be a better fit?

But maybe the Recom regulator will be adequate for the Atmega1284 circuit. It will have a 0.8W subminiature speaker on board to deliver warning beeps when needed (an Ekulit LSF-15M/S), as well as this Adafruit display.

Question - I have now, in my schematic, connected the injection signal and the speed transducer to the two external interrupts of the 328. The very fast crankshaft signal will be measured on the input capture pin D8. Is that the right way to go?

ATMEGA 328 has Timer0 used up for millis()...but Timer 1 (the 16 bit jobby) is available for input capture/counting:

void setup(){
Serial.begin(115200);
pinMode(5,INPUT);
  
//Set up Counter 1 - 16 bits!
  TCCR1A = 0; // "Normal Operation".
  TCCR1B =0 |(CS12<<1)|(CS11<<1)|(CS10<<1); //External CLock Source on T1 pin (Uno pin 5) on rising edge...
  
}
void loop(){
Serial.print(millis());
Serial.print("\tT1=");
unsigned int t1 = TCNT1H << 8;     // take the register that holds the 8 highest value bits...shift them 8 to the right
t1+=TCNT1L;               // add on the lower 8 bits to form the 16bit int.
Serial.println(t1);
}

10Khz over 65000 possible ticks gives a good 6.5 seconds before you overflow that counter.

ok I understand...

I am sitting here right now rebuilding the engine data module according to my latest schematic.

So the crankshaft will be measured with the input timer on pin D5... ok I will change that...

But the relatively slow moving injection signal and speed transducer can be handled by the two external interrupts on pin 2 and 3?

As far as I am aware...yes.

The Highest frequency one is at least dealt with by Timer/Counter 1 as hardware so allows your software to deal with the other 2 lower frequency ones.

Someone may be able to clarify, but if possible, I believe on the ATMEGA328 you can also use Timer 2 (only 8 bits mind) to count rise/falls on the Timing pin T0.

Timer0 is used by millis...so probably used by i2c and all sorts and well...if you use millis() at all.

To regulator: basic one may be for example L78L05 with maximum 30V input voltage and "internal current limiting and thermal shutdown, making them essentially indestructible" (Datasheet says). But its quiescent current is 6 mA, you may try to find some better - with lower quiescent current, LDO... If you want use SMD package you will have more options. Such as LP295x with negligible dropout and quiescent current in hundreds of uA. But I am no EE - those are regulators I have and use I have no idea if there are better regulators or if there is some catch in those.

To timers: Timer0 may be clocked from T0, Timer1 from T1 and Timer2 from XTAL1. AFAIK you cannot clock Timer2 externally when using external crystal. Johny is right Arduino uses Timer0 for generating time for millis() and delay(). But you don't have to use it. You may use Timer2 for keeping time (clocked from external 32kHz crystal if you use internal 8MHz RC oscillator as main clock; or by dividing main clock if using crystal) and free Timer0 for counting the other signal. ATMega uses hardware I2C module. It does not depend on any timer or delay/millis usage.

@Smajdalf:

Thanks for the link. I will go get a regulator like that and see how it does.

Quiescent current isn't a big concern, as my CarDuino will only have power when the ignition is on and the car is running.

I started experimenting with timers yesterday; I changed the ADC timer speed on the simulator circuit like this, to make analogRead() from the 5K pots faster:

 ADCSRA &= ~(1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);  // remove bits set by Arduino library

ADCSRA |= (1 << ADPS2);  // prescaler set to 1/16

So far, it had no effect. The simulated rpm signal still won't get any faster than 300 us. But I haven't implemented the timer based pin toggle code, so maybe that will change things.

You should not run ADC much faster - the resolution will be poor. Anyway forget about software pin toggling, use the timers. They look intimidating at first but when you understand them you discover they are very easy to use yet extremely powerful. For things like counting pulses, generating signal and precise timing one timer/counter module does work of one AVR with carefully written code in assembly.