pasko-zh / brzo_i2c

Brzo I2C is a fast I2C Implementation written in Assembly for the esp8266
GNU General Public License v3.0
244 stars 47 forks source link

A 24-bit "Time Window" to represent "Frequency" and "Clock Stretch" #17

Open MikeFair opened 7 years ago

MikeFair commented 7 years ago

I've been playing with making these "Request Ids" to represent precanned I2C Item fetches.

Two of the items in the description list for buses and devices are the busSpeed and the clockStretch.

Currently a 16-bit and 32-bit value. Together they require 48-bits; busSpeed is represented in kHz, (1KHz to 65.5Mhz) and the other is clock stretch represented in nanoseconds (0 nanoseconds to ~4.3 seconds). I wanted to make them smaller (and increase their value range) if I could.

So I came up with a different way to encode the same ideas. Instead of representing frequency as "the number of pulses in a second" I imagined describing frequency as "the time between pulse changes" (a much smaller number).

To make a long story short, I ended up hacking together the concept of a 24-bit "TimeWindow" structure using a badly hacked version of floating point numbers to "play with it".

A "Time Window" is a "pulse start" and a "stretch time".

As an example, 400kHz is a pulse time of 2.5 ms, e.g. 0.250 x 10^-5 seconds {-, 5, 250} 0.001 x 10^-9 seconds == 1 THz {-, 9, 1} 0.600 x 10^2 seconds == 0.0167 Hz (a clock pulse of ~1 minute) {+, 2, 600}

And that's how this structure encodes a pulse_frequency, a 10-bit "significant digits", a 4-bit exponent, and a 1-bit exponent sign. "Significant digits" are the three digits after the decimal point. The value range then is 0.001 to 1.024; and the exponent is between -15 and +15; using 1 second as a base.

For the clock stretch, I split the byte 50/50; 4-bits for "Significance" and 4-bits for "Exponent". This gives a significance range of 0.1 to 1.6 * 10 +/- 15. Using 0.1 microseconds as a base.

Combined the two values take up 24-bits.

Here's the structure in code:

    /*
     * This time_window32_t represents two 16-bit floats; 
     * one for "time between pulses" (1 == 1 second); the other for "stretch before timeout" (1 == 0.1 microseconds)
     * together they make a repeating clock frequency and a "clock stretch" to deal with device inconsistencies
     */ 
    union time_window32_t {
        uint32_t bytes;
        struct {
            uint16_t pulse_exp_sign:1;   // 0 or 1
            uint16_t pulse_exp:4;        // 0 through 15
            uint16_t pulse_sig:10;       // 0 through 1023 (exp0 == 1s) (0.001x10^-15) (0.001 femtosecond) to [1.023 x 10^15] (32 million years)
            uint16_t stretch_exp_sign:1; // 0 or 1
            uint8_t stretch_exp:4;       // 0 through 15
            uint8_t stretch_sig:4;       // 1 through 16 (exp0 == 0.1us (or 100ns); range is [1 x 10^-23] (time for light to travel 1 femtometer) to [1.6 x 10^8 (~5 years))
            uint8_t reserved;           // reserved; the request length ultimately ends up in this spot in a txn_request_id
        } settings;
    };

This method is an extremely inefficient (aka really horrendous) way to represent floating point numbers. The main point right now was introducing the concept and gauging feedback. I like that both these values are encoded into a single 24-bit value. I could probably learn from the techniques that a "half-precision" (16-bit) IEEE floating point value uses.

Another change would be "clock stretch" being a divisor of "pulse frequency"; so rather than having a prefixed value like it does now, it's always some fraction of a "pulse time"... This should use the available values better to get more reasonable granularity. For instance, 6 hours + 1 femtosecond (i.e. exactly 6 hours) seems like a very unlikely time window to me. There's likely a better way to use the clock stretch bits to either make pulse frequency better or express more reasonable values within a pulse range.

I still expect users to express "frequency" in terms of "kHz" and "clock_stretch" in terms of nanoseconds. I just see the library transforming these two separate values into a single 24-bit "time window" identifier.

I have another use case for this "time window" to provide data synchronization frequencies. For instance, "these values must be refreshed from the device at least every 5 minutes +/- 15 seconds" or "these values must be synchronized every 500 nanoseconds +/- 10 nanoseconds". Once set, the library ensures that those frequencies for data synchronization are maintained.