I2C and pulse timing

For a datalogger system: I have a master Arduino which uses I2C to poll a number of slave Arduinos five times per second.

One of the slaves monitors a recurring pulse at one of its digital input pins. The frequency and mark/space ratio of the pulse varies. This slave Arduino is required to report the total "up" time in a specific interval, plus the number of "up" pulses in that interval (see postscript). For instance, if there is a 4ms pulse and a 1ms pulse, it will report two pulses, with a total up time of 5ms. Realistically I need to resolve the pulse durations to 0.5ms at most, preferably 0.1ms.

All this is simple enough to do, except for the I2C requests from the master.

The I2C requests from the master are asynchronous from the pulses monitored by the slave. In other words, they could arrive at any point in the timing/counting process being performed by the slave.

My concern is that the I2C requests will mess up the accurate timing of the monitored pulses.

As a relative newbie, I only know of two ways of achieving what I want. I could use pulseIn() to measure the injector pulse durations, or I could loop continuously and use micros() to timestamp each change of state on the input pin, doing the counting and summing after the sampling interval.

I assume both of these may be interrupted by an I2C request, which could then result in an inaccurate pulseIn() reading, or a delay in picking up the state change, which would give me an inaccurate measurement.

The Arduino documentation seems very light on things like this: for instance, which functions use interrupts, which will over-ride the other, and so on. Will pulseIn block I2C requests? Or will I2C requests interrupt pulseIn? I wish this were explained more explicitly.

Can anyone suggest how to time and count my pulses accurately, whilst still being able to respond to requests from the I2C master?

Thanks!


POSTSCRIPT - MORE DETAIL

I am monitoring a single cylinder four stroke engine. I want to measure the total injection time, and the total number of injection pulses, per cycle of the engine.

So, I aim to use one ignition pulse to start the measuring interval, and the next ignition pulse to stop it. During that period I want to see how many times the fuel injector is pulsed, and the total duration of those pulses.

(The fuel injection regime may vary at different engine speeds. For instance, it may use single-pulse-per-cycle at one speed, and multiple-pulse-per-cycle at another speed.)

Obviously the engine speed varies, so the injection pulse rate also varies.

Meanwhile, the master Arduino is polling several of its own I/O ports, plus some other I2C slaves, five times per second. After each round of polling, it builds a data string and saves it to an SD drive.

Thus we end up with five records per second, each record comprising the readings from twelve sensors.

The Arduino monitoring the fuel injection pulses is dedicated entirely to that - it does no other monitoring (apart from using the ignition pulses to start and stop the measurement interval). All it must do is respond to the I2C requests when they arrive.

POSSIBLE COMPROMISES:

The master Arduino requests a reading five times per second. Except when idling, the engine cycles (for a single cylinder four stroke) are much faster than this. For instance, at 3000rpm the engine is rotating 50 times per second, and being a four stroke there are 25 cycles per second.

That is, at 3000rpm there are five engine cycles for each I2C request. But the master Arduino only wants one reading out of the five. Therefore it is OK to miss some of the engine cycles, so long as one of them gets recorded and reported.

Furthermore, if necessary it would be acceptable to occasionally miss all</> the cycles between Arduino requests, such that the previous reading is reported for a second time.

Obviously I don’t want to lose too much data like that.

I assume both of these may be interrupted by an I2C request

Correct.

The Arduino documentation seems very light on things like this: for instance, which functions use interrupts, which will over-ride the other, and so on. Will pulseIn block I2C requests? Or will I2C requests interrupt pulseIn? I wish this were explained more explicitly.

You have all of the source code, so you can see which functions use interrupts.

No, pulseIn() will not block I2C interrupts.

I suspect that you need to have the "recurring pulse" connected to an external interrupt pin, with the interrupt mode being CHANGE, so that you get accurate answers as to how long and often the pin is HIGH.

You will lose data when the I2C interrupt happens, but that's life.

Another possible solution. Use a third line as a “busy” line. Have the master check that line. When your slave is busy doing a reading, have the slave set that line. When you are done, the slave clears that line. Just a thought…

Thank you, PaulS. That clarifies things considerably.

Just as a throwaway thought: what happens to the I2C master if I simply invoke noInterrupts() before I do my measurements, and interrupts() afterwards, with a view to blocking I2C requests?

Will the master hang until the slave responds? Or is there some kind of timeout such that the master will continue?

what happens to the I2C master if I simply invoke noInterrupts() before I do my measurements, and interrupts() afterwards, with a view to blocking I2C requests?

Whether you can do that depends on what you are doing while interrupts are disabled. Things like pulseIn() won't work, because that relies on knowing how much time has passed, which relies on interrupts happening.

Hey, SurferTim, that's great. I was wondering about something like that myself. I didn't mention it in case it seems like a bodge, but I could definitely implement it.

When the "busy" line is set, the master could just skip that slave and use the previous reading.

Thanks, PaulS, point well taken. But will the master stall until the slave agrees to talk? Or will it time out?

But will the master stall until the slave agrees to talk? Or will it time out?

I don't know. But, certainly the time it takes for the slave to be ready to talk, because a timing pulse has been noted and measured, will be very short, won't it?

Oh, and if you use external interrupts for the pulse, you could use direct port manipulation, and simply wait in the interrupt handler until the pulse had ended. Then, you'd only need to use RISING as the type, but still know how long the pulse lasted.

I only mention this because you said the pulses are very short. Hanging around in an ISR is generally not a good idea.

The pulses are expected to be in the range 2ms to 15ms, and I’d quite like to resolve to 0.1ms, but 0.5ms would be acceptable.

PaulS: your suggestion to do the pulse timing in an ISR is something I hadn’t considered and (as a newbie) seems slightly scary! However, I can overcome that with a bit of reading and experimentation.

Maybe I could use the “busy line” solution suggested by SurferTim as an interim, whilst I work on an interrupt-based solution.

Thanks again to you both for taking the time and trouble.