PulseIn - again

I was thinking about new PulseIn (and PulseOut) functions and calibrating them.
How about creating two functions maybe called FastPulseIn and SlowPulseIn

with the FastPulseIn it could just use a unsigned int as a counter and timeout when the variable rolled over - there's a way to do this with an interrupt I think?
This might keep the loop tighter and faster, for use with faster pulses.
PulseIn now uses some integer math, it's something like (20 / 23) to do the translation to us's. I'm incredulous that this very accurate and it might be a good idea to use a float for the translation/ after all it's outside the timing loop.

SlowPulseIn could have a timeout parameter, which would slow the loop down a bit, perhaps degrading resolution somewhat.
It seems to me that this function would definitely benefit from floating point math - to retain accuracy at higher counts.

As to calibration - what seemed like the obvious approach to me would be to locate a calibrated square-wave frequency source, with as low a frequency as possible, and just take the proposed function by and test it to see what comes out the end of the loop.

I can investigate what might be available at a couple of nearby University EE departments, and will volunteer to do the testing if we can come up with some functions to test.

Paul Badger

Interesting discussion. We do, of course, want as accurate a pulseIn() function as possible - any testing you can do of the current one would be most appreciated. I haven't heard any complaints about the accuracy, but as you say, the 20/23 is a hack, and I wouldn't be surprised to find that it doesn't give the best results.

In general, I'd prefer to have only one pulseIn() function - the difference between the fast and slow versions is not likely to be great enough to be worth the confusion it would cause. As for floating point math - using floats still takes a sizable percentage of the usable flash on the ATmega8, so I'd like to keep them out of the core if possible. At some point, it may be that the cumulative benefit of using floats throughout core makes it worthwhile, but I haven't yet seen the need. If you have some situations in which they provide a substantial benefit, please let me know.

The version of pulseIn I sent up for 0008 loses that funny fudge factor. It is using function that aren't in 0007, but I think you can see the timing factors. It is accurate down to 1 (more or less) microsecond.

unsigned long pulseIn(uint8_t pin, uint8_t state)
{
    // cache the port and bit of the pin in order to speed up the
    // pulse width measuring loop and achieve finer resolution.  calling
    // digitalRead() instead yields much coarser resolution.
    uint8_t bit = digitalPinToBitMask(pin);
    uint8_t port = digitalPinToPort(pin);
    uint8_t stateMask = (state ? bit : 0);
    unsigned long width = 0; // keep initialization out of time critical area */
        
    // wait for the pulse to start
    while ( (*portInputRegister(port) & bit) != stateMask)
        ;
    
    // wait for the pulse to stop
    while ( (*portInputRegister(port) & bit) == stateMask)
        width++;

    // convert the reading to microseconds. The loop has been determined
    // to be 10 clock cycles long and have about 12 clocks between the edge
    // and the start of the loop. There will be some error introduced by
    // the interrupt handlers.
    return clocksToMicroseconds( width * 10 /*clocks/loop*/ +
                                 12 /* approximate clocks from edge to loop*/); 
}

That is calibrated by counting instruction cycles and verified experimentally. As far as a square wave signal generator, you can program the timer 1 and timer 2 channels to put out square waves on digital 9, 10, and 11. Very handy for testing.

OK - I'm getting this. There are 10 commands that each execute on a machine cycle so it's all locked precisely to the system clock. Which is one of several different clocks, from my (cursory) reading of the datasheet. How come "width * 10" instead of "width * 16" to convert to microseconds. (16 Mhz sys clock?)

Also the function appears to be mostly ready - how about adding the timeout and retesting before 008?

I know, it's easy for me to say.

Paul

The "width * 10" converts from "loops" to clock cycles. The clocksToMicroseconds() converts from clock cycles to microseconds and does the division by 16 or whatever clock rate you declared. (It got renamed since I wrote that, it will be clockCyclesToMicroseconds() by the time it comes out.)

Frustratingly, all though there are a number of clock sources available, the chip appear to only be able to see one for any given fuse configuration. I wanted to make a provision to automatically learn the crystal rate for the various timing conversion functions, but I can't figure out a way to watch one of the slower clocks to count my faster clock cycles.

The serial bootloader could be made to do it by watching the RS232 and performing an autobaud operation, but there isn't room for the code and then I'd have to define a spot in flash or eeprom to hold the result until the sketch was running which would be odd.

(Ok, there is a trick I could do with the watchdog timer to figure out the clock rate, but it seems extreme and is incompatible with people's current bootloader and fuse combinations.)