The Smell of Molten Projects in the Morning

Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.

Author: Ed

  • Bathroom Door Retainer

    The weather got warm enough to open the windows before pollen season started, which led to the front bathroom door slamming closed in the middle of the night when a gusty rainstorm blew through town. After far too many years, I decided this was an annoyance up with which I need no longer put.

    A few minutes with OpenSCAD and Slic3r produces the shape:

    Bathroom Door Retainer - Slic3r
    Bathroom Door Retainer – Slic3r

    It’s basically an extrusion of a 2D shape with a rectangular recess for the door chewed out.

    An hour later, it’s in full effect:

    Bathroom Door Retainer - installed
    Bathroom Door Retainer – installed

    The model now sports a little ball to secure the retainer against the towel bar:

    Bathroom Door Retainer - bump
    Bathroom Door Retainer – bump

    Maybe someday I’ll reprint it.

    That was easy …

    The cast-iron pig sometimes standing guard as a doorstop in the relatively narrow doorway poses a bit of a foot hazard, so he moves into a closet during the off season. He can now remain there, snug and comfy, until a need for ballast arises.

    The OpenSCAD source code as a GitHub Gist:

    // Bathroom Door Retainer
    // Ed Nisley KE4ZNU – May 2017
    Layout = "Show"; // Show Build
    //——-
    //- Extrusion parameters must match reality!
    ThreadThick = 0.20;
    ThreadWidth = 0.40;
    HoleWindage = 0.2;
    Protrusion = 0.1; // make holes end cleanly
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    //——-
    // Dimensions
    TowelBarSide = 20.5; // towel bar across flat side
    TowelBarAngle = 45; // rotation of top flat from horizontal
    DoorOffset = 16.0; // from towel bar to door
    DoorThick = 36.5;
    WallThick = 4.0; // minimum wall thickness
    RetainerDepth = 10.0; // thickness of retaining notch
    NumSides = 6*4;
    CornerRad = WallThick;
    BarClipOD = TowelBarSide*sqrt(2) + 2*WallThick;
    BarClipRad = BarClipOD/2;
    OAH = RetainerDepth + WallThick;
    module LatchPlan() {
    union() {
    linear_extrude(height=OAH,convexity=4)
    difference() {
    union() {
    circle(d=BarClipOD,$fn=NumSides);
    hull()
    for (i=[0,1], j=[0,1])
    translate([i*(BarClipRad + DoorOffset + DoorThick + WallThick – CornerRad),j*(BarClipRad – CornerRad)])
    circle(r=CornerRad,$fn=4*4);
    }
    rotate(TowelBarAngle) // towel bar shape
    square(size=TowelBarSide,center=true);
    translate([0,-TowelBarSide/sqrt(2)]) // make access slot
    rotate(-TowelBarAngle)
    square(size=[2*TowelBarSide,TowelBarSide],center=false);
    }
    translate([0,-TowelBarSide/sqrt(2),OAH/2])
    rotate([90,0,45])
    sphere(r=TowelBarSide/25,$fn=4*3);
    }
    }
    module Latch() {
    difference() {
    LatchPlan();
    translate([BarClipRad + DoorOffset,-BarClipRad/2,-Protrusion])
    cube([DoorThick,BarClipOD,RetainerDepth + Protrusion],center=false);
    }
    }
    //——-
    // Build it!
    if (Layout == "Show") {
    Latch();
    }
    if (Layout == "Build") {
    translate([0,0,OAH])
    rotate([180,0,0])
    Latch();
    }
  • Road Conditions: Rt 376 Brush North of Maloney

    NYS DOT ground the asphalt surface and repaved Rt 376, dramatically improving the southern route to the rail trail along Maloney Drive.

    Alas, the Japanese Knotweed continues to flourish:

    This slideshow requires JavaScript.

    I sent a note to their email contact and got the usual autoresponder message, but may have a side channel through the Dutchess County Planning Department to their Bicycle Coordinator. We shall see.

  • Beware the Hissing Goose!

    Rolling into Vassar Farms, we encountered a Canadian Canada Goose (*) family:

    Geese at Vassar Farm Pond 2017-05-21
    Geese at Vassar Farm Pond 2017-05-21

    The gander pulled straight up and hissed as we rolled by at what we thought was a respectful distance:

    Geese at Vassar Farm Pond 2017-05-21 - detail
    Geese at Vassar Farm Pond 2017-05-21 – detail

    Their little fuzzballs retreated in good order under the fence toward the pond; they don’t need much survival training.

    Word has it a goose family (perhaps this one) built their nest near a path around the ponds and defend their turf with sufficient resolve to deter even singletrack bikers.

    I occasionally see snakes along the way, but none that hiss:

    Black Snake on Rail Trail - 2017-04-28
    Black Snake on Rail Trail – 2017-04-28

    We approach rail-trail curves with a bit more caution than some folks; I’m at about the spot where that rider began losing control and didn’t quite wipe us out.

    Update: They’re “Canada Geese“, with (AFAICT) a legal distinction between Canadian tourists and resident Yanks during the hunting season. Thanks to David for the reminder!

  • Arduino vs. Significant Figures: Useful 64-bit Fixed Point

    Devoting eight bytes to every fixed point number may be excessive, but having nine significant figures apiece for the integer and fraction parts pushes the frequency calculations well beyond the limits of the DDS hardware, without involving any floating point library routines. This chunk of code performs a few more calculations using the format laid out earlier and explores a few idioms that may come in handy later.

    Rounding the numbers to a specific number of decimal places gets rid of the repeating-digit problem that turns 0.10 into 0.099999:

    uint64_t RoundFixedPt(union ll_u TheNumber,unsigned Decimals) {
    union ll_u Rnd;
    
      Rnd.fx_64 = (One.fx_64 / 2) / (pow(10LL,Decimals));
      TheNumber.fx_64 = TheNumber.fx_64 + Rnd.fx_64;
      return TheNumber.fx_64;
    }
    

    That pretty well trashes the digits beyond the rounded place, so you shouldn’t display any more of them:

    void PrintFixedPtRounded(char *pBuffer,union ll_u FixedPt,unsigned Decimals) {
    char *pDecPt;
    
      FixedPt.fx_64 = RoundFixedPt(FixedPt,Decimals);
    
      PrintIntegerLL(pBuffer,FixedPt);  // do the integer part
    
      pBuffer += strlen(pBuffer);       // aim pointer beyond integer
    
      pDecPt = pBuffer;                 // save the point location
      *pBuffer++ = '.';                 // drop in the decimal point, tick pointer
    
      PrintFractionLL(pBuffer,FixedPt);
    
      if (Decimals == 0)
        *pDecPt = 0;                    // 0 places means discard the decimal point
      else
        *(pDecPt + Decimals + 1) = 0;   // truncate string to leave . and Decimals chars
    }
    

    Which definitely makes the numbers look prettier:

      Tenth.fx_64 = One.fx_64 / 10;             // Likewise, 0.1
      PrintFixedPt(Buffer,Tenth);
      printf("\n0.1: %s\n",Buffer);
      PrintFixedPtRounded(Buffer,Tenth,9);                    // show rounded value
      printf("0.1 to 9 dec: %s\n",Buffer);
    
      TestFreq.fx_64 = RoundFixedPt(Tenth,3);                 // show full string after rounding
      PrintFixedPt(Buffer,TestFreq);
      printf("0.1 to 3 dec: %s (full string)\n",Buffer);
    
      PrintFixedPtRounded(Buffer,Tenth,3);                    // show truncated string with rounded value
      printf("0.1 to 3 dec: %s (truncated string)\n",Buffer);
    
    0.1: 0.099999999
    0.1 to 9 dec: 0.100000000
    0.1 to 3 dec: 0.100499999 (full string)
    0.1 to 3 dec: 0.100 (truncated string)
    
      CtPerHz.fx_64 = -1;                       // Set up 2^32 - 1, which is close enough
      CtPerHz.fx_64 /= 125 * MEGA;              // divide by nominal oscillator
      PrintFixedPt(Buffer,CtPerHz);
      printf("\nCt/Hz = %s\n",Buffer);
    
      printf("Rounding: \n");
      for (int d = 9; d >= 0; d--) {
        PrintFixedPtRounded(Buffer,CtPerHz,d);
        printf("     %d: %s\n",d,Buffer);
      }
    
    Ct/Hz = 34.359738367
    Rounding:
         9: 34.359738368
         8: 34.35973837
         7: 34.3597384
         6: 34.359738
         5: 34.35974
         4: 34.3597
         3: 34.360
         2: 34.36
         1: 34.4
         0: 34
    

    Multiplying two scaled 64-bit fixed-point numbers should produce a 128-bit result. For all the values we (well, I) care about, the product will fit into a 64-bit result, because the integer parts will always multiply out to less than 232 and we don’t care about more than 32 bits of fraction. This function multiplies two fixed point numbers of the form a.b × c.d by adding up the partial products thusly: ac + bd + ad + bc. The product of the integers ac won’t overflow 32 bits, the cross products ad and bc will always be slightly less than their integer factors, and the fractional product bd will always be less than 1.0.

    Soooo, just multiply ’em out as 64-bit integers, shift the products around to align the appropriate parts, and add up the pieces:

    
    uint64_t MultiplyFixedPt(union ll_u Mcand, union ll_u Mplier) {
    union ll_u Result;
    
      Result.fx_64  = ((uint64_t)Mcand.fx_32.high * (uint64_t)Mplier.fx_32.high) << 32; // integer parts (clear fract) 
      Result.fx_64 += ((uint64_t)Mcand.fx_32.low * (uint64_t)Mplier.fx_32.low) >> 32;   // fraction parts (always < 1)
      Result.fx_64 += (uint64_t)Mcand.fx_32.high * (uint64_t)Mplier.fx_32.low;          // cross products
      Result.fx_64 += (uint64_t)Mcand.fx_32.low * (uint64_t)Mplier.fx_32.high;
    
      return Result.fx_64;
    }
    

    This may be a useful way to set magic numbers with a few decimal places, although it does require keeping the decimal point in mind:

      TestFreq.fx_64 = (599999LL * One.fx_64) / 10;           // set 59999.9 kHz differently
      PrintFixedPt(Buffer,TestFreq);
      printf("\nTest frequency: %s\n",Buffer);
      PrintFixedPtRounded(Buffer,TestFreq,1);
      printf("         round: %s\n",Buffer);
    
    Test frequency: 59999.899999999
             round: 59999.9
    

    Contrary to what I thought, computing the CtPerHz coefficient doesn’t require pre-dividing both 232 and the oscillator by 2, thus preventing the former from overflowing a 32 bit integer. All you do is knock the numerator down by one little itty bitty count you’ll never notice:

      CtPerHz.fx_64 = -1;                       // Set up 2^32 - 1, which is close enough
      CtPerHz.fx_64 /= 125 * MEGA;              // divide by nominal oscillator
      PrintFixedPt(Buffer,CtPerHz);
      printf("\nCt/Hz = %s\n",Buffer);
    
    Ct/Hz = 34.359738367
    

    That’s also the largest possible fixed-point number, because unsigned:

      TempFX.fx_64 = -1;
      PrintFixedPt(Buffer,TempFX);
      printf("Max fixed point: %s\n",Buffer);
    
    Max fixed point: 4294967295.999999999
    

    With nine.nine significant figures in the mix, tweaking the 125 MHz oscillator to within 2 Hz will work:

    Oscillator tune: CtPerHz
     Oscillator: 125000000.00
     -10 -> 34.359741116
      -9 -> 34.359741116
      -8 -> 34.359740566
      -7 -> 34.359740566
      -6 -> 34.359740017
      -5 -> 34.359740017
      -4 -> 34.359739467
      -3 -> 34.359739467
      -2 -> 34.359738917
      -1 -> 34.359738917
      +0 -> 34.359738367
      +1 -> 34.359738367
      +2 -> 34.359737818
      +3 -> 34.359737818
      +4 -> 34.359737268
      +5 -> 34.359737268
      +6 -> 34.359736718
      +7 -> 34.359736718
      +8 -> 34.359736168
      +9 -> 34.359736168
     +10 -> 34.359735619
    

    So, all in all, this looks good. The vast number of strings in the test program bulk it up beyond reason, but in actual practice I think the code will be smaller than the equivalent floating point version, with more significant figures. Speed isn’t an issue either way, because the delays waiting for the crystal tester to settle down at each frequency step should be larger than any possible computation.

    The results were all verified with my trusty HP 50g and HP-15C calculators, both of which wipe the floor with any other way of handling mixed binary / hex / decimal arithmetic. If you do bit-wise calculations, even on an irregular basis, get yourself a SwissMicro DM16L; you can thank me later.

    The Arduino source code as a GitHub Gist:

    // Fixed point exercise for 60 kHz crystal tester
    #include <avr/pgmspace.h>
    char Buffer[10+1+10+1]; // string buffer for long long conversions
    #define GIGA 1000000000LL
    #define MEGA 1000000LL
    #define KILO 1000LL
    struct ll_fx {
    uint32_t low;
    uint32_t high;
    };
    union ll_u {
    uint64_t fx_64;
    struct ll_fx fx_32;
    };
    union ll_u CtPerHz; // will be 2^32 / 125 MHz
    union ll_u HzPerCt; // will be 125 MHz / 2^32
    union ll_u One; // 1.0 as fixed point
    union ll_u Tenth; // 0.1 as fixed point
    union ll_u TenthHzCt; // 0.1 Hz in counts
    union ll_u Oscillator; // nominal oscillator frequency
    union ll_u OscOffset; // oscillator calibration offset
    union ll_u TestFreq,TestCount; // useful variables
    union ll_u TempFX;
    //———–
    // Round scaled fixed point to specific number of decimal places: 0 through 8
    // You should display the value with only Decimals characters beyond the point
    // Must calculate rounding value as separate variable to avoid mystery error
    uint64_t RoundFixedPt(union ll_u TheNumber,unsigned Decimals) {
    union ll_u Rnd;
    // printf(" round before: %08lx %08lx\n",TheNumber.fx_32.high,TheNumber.fx_32.low);
    Rnd.fx_64 = (One.fx_64 / 2) / (pow(10LL,Decimals));
    // printf(" incr: %08lx %08lx\n",Rnd.fx_32.high,Rnd.fx_32.low);
    TheNumber.fx_64 = TheNumber.fx_64 + Rnd.fx_64;
    // printf(" after: %08lx %08lx\n",TheNumber.fx_32.high,TheNumber.fx_32.low);
    return TheNumber.fx_64;
    }
    //———–
    // Multiply two unsigned scaled fixed point numbers without overflowing a 64 bit value
    // The product of the two integer parts mut be < 2^32
    uint64_t MultiplyFixedPt(union ll_u Mcand, union ll_u Mplier) {
    union ll_u Result;
    Result.fx_64 = ((uint64_t)Mcand.fx_32.high * (uint64_t)Mplier.fx_32.high) << 32; // integer parts (clear fract)
    Result.fx_64 += ((uint64_t)Mcand.fx_32.low * (uint64_t)Mplier.fx_32.low) >> 32; // fraction parts (always < 1)
    Result.fx_64 += (uint64_t)Mcand.fx_32.high * (uint64_t)Mplier.fx_32.low; // cross products
    Result.fx_64 += (uint64_t)Mcand.fx_32.low * (uint64_t)Mplier.fx_32.high;
    return Result.fx_64;
    }
    //———–
    // Long long print-to-buffer helpers
    // Assumes little-Endian layout
    void PrintHexLL(char *pBuffer,union ll_u FixedPt) {
    sprintf(pBuffer,"%08lx %08lx",FixedPt.fx_32.high,FixedPt.fx_32.low);
    }
    // converts all 9 decimal digits of fraction, which should suffice
    void PrintFractionLL(char *pBuffer,union ll_u FixedPt) {
    union ll_u Fraction;
    Fraction.fx_64 = FixedPt.fx_32.low; // copy 32 fraction bits, high order = 0
    Fraction.fx_64 *= GIGA; // times 10^9 for conversion
    Fraction.fx_64 >>= 32; // align integer part in low long
    sprintf(pBuffer,"%09lu",Fraction.fx_32.low); // convert low long to decimal
    }
    void PrintIntegerLL(char *pBuffer,union ll_u FixedPt) {
    sprintf(pBuffer,"%lu",FixedPt.fx_32.high);
    }
    void PrintFixedPt(char *pBuffer,union ll_u FixedPt) {
    PrintIntegerLL(pBuffer,FixedPt); // do the integer part
    pBuffer += strlen(pBuffer); // aim pointer beyond integer
    *pBuffer++ = '.'; // drop in the decimal point, tick pointer
    PrintFractionLL(pBuffer,FixedPt);
    }
    void PrintFixedPtRounded(char *pBuffer,union ll_u FixedPt,unsigned Decimals) {
    char *pDecPt;
    //char *pBase;
    // pBase = pBuffer;
    FixedPt.fx_64 = RoundFixedPt(FixedPt,Decimals);
    PrintIntegerLL(pBuffer,FixedPt); // do the integer part
    // printf(" Buffer int: [%s]\n",pBase);
    pBuffer += strlen(pBuffer); // aim pointer beyond integer
    pDecPt = pBuffer; // save the point location
    *pBuffer++ = '.'; // drop in the decimal point, tick pointer
    PrintFractionLL(pBuffer,FixedPt);
    // printf(" Buffer all: [%s]\n",pBase);
    if (Decimals == 0)
    *pDecPt = 0; // 0 places means discard the decimal point
    else
    *(pDecPt + Decimals + 1) = 0; // truncate string to leave . and Decimals chars
    // printf(" Buffer end: [%s]\n",pBase);
    }
    //– Helper routine for printf()
    int s_putc(char c, FILE *t) {
    Serial.write(c);
    }
    //———–
    void setup ()
    {
    Serial.begin (115200);
    fdevopen(&s_putc,0); // set up serial output for printf()
    Serial.println (F("DDS calculation exercise"));
    Serial.println (F("Ed Nisley – KE4ZNU – May 2017\n"));
    // set up useful constants
    TempFX.fx_64 = -1;
    PrintFixedPt(Buffer,TempFX);
    printf("Max fixed point: %s\n",Buffer);
    One.fx_32.high = 1; // Set up 1.0, a very useful constant
    PrintFixedPt(Buffer,One);
    printf("\n1.0: %s\n",Buffer);
    Tenth.fx_64 = One.fx_64 / 10; // Likewise, 0.1
    PrintFixedPt(Buffer,Tenth);
    printf("\n0.1: %s\n",Buffer);
    PrintFixedPtRounded(Buffer,Tenth,9); // show rounded value
    printf("0.1 to 9 dec: %s\n",Buffer);
    TestFreq.fx_64 = RoundFixedPt(Tenth,3); // show full string after rounding
    PrintFixedPt(Buffer,TestFreq);
    printf("0.1 to 3 dec: %s (full string)\n",Buffer);
    PrintFixedPtRounded(Buffer,Tenth,3); // show truncated string with rounded value
    printf("0.1 to 3 dec: %s (truncated string)\n",Buffer);
    CtPerHz.fx_64 = -1; // Set up 2^32 – 1, which is close enough
    CtPerHz.fx_64 /= 125 * MEGA; // divide by nominal oscillator
    PrintFixedPt(Buffer,CtPerHz);
    printf("\nCt/Hz = %s\n",Buffer);
    printf("Rounding: \n");
    for (int d = 9; d >= 0; d–) {
    PrintFixedPtRounded(Buffer,CtPerHz,d);
    printf(" %d: %s\n",d,Buffer);
    }
    HzPerCt.fx_64 = 125 * MEGA; // 125 MHz / 2^32, without actually shifting!
    PrintFixedPt(Buffer,HzPerCt);
    printf("\nHz/Ct: %s\n",Buffer);
    TenthHzCt.fx_64 = MultiplyFixedPt(Tenth,CtPerHz); // 0.1 Hz as delta-phase count
    PrintFixedPt(Buffer,TenthHzCt);
    printf("\n0.1 Hz as ct: %s\n",Buffer);
    printf("Rounding: \n");
    for (int d = 9; d >= 0; d–) {
    PrintFixedPtRounded(Buffer,TenthHzCt,d);
    printf(" %d: %s\n",d,Buffer);
    }
    // Try out various DDS computations
    TestFreq.fx_64 = One.fx_64 * (60 * KILO); // set 60 kHz
    PrintFixedPt(Buffer,TestFreq);
    printf("\nTest frequency: %s\n",Buffer);
    PrintFixedPtRounded(Buffer,TestFreq,1);
    printf(" round: %s\n",Buffer);
    TestCount.fx_64 = MultiplyFixedPt(TestFreq,CtPerHz); // convert to counts
    PrintFixedPt(Buffer,TestCount);
    printf("Delta phase ct: %s\n",Buffer);
    PrintFixedPtRounded(Buffer,TestCount,0);
    printf(" round to int: %s\n",Buffer);
    TestFreq.fx_64 += Tenth.fx_64; // set 60000.1 kHz
    PrintFixedPt(Buffer,TestFreq);
    printf("\nTest frequency: %s\n",Buffer);
    PrintFixedPtRounded(Buffer,TestFreq,1);
    printf(" round: %s\n",Buffer);
    TestCount.fx_64 = MultiplyFixedPt(TestFreq,CtPerHz); // convert to counts
    PrintFixedPt(Buffer,TestCount);
    printf("Delta phase ct: %s\n",Buffer);
    PrintFixedPtRounded(Buffer,TestCount,0);
    printf(" round to int: %s\n",Buffer);
    TestFreq.fx_64 -= Tenth.fx_64 * 2; // set 59999.9 kHz
    PrintFixedPt(Buffer,TestFreq);
    printf("\nTest frequency: %s\n",Buffer);
    PrintFixedPtRounded(Buffer,TestFreq,1);
    printf(" round: %s\n",Buffer);
    TestCount.fx_64 = MultiplyFixedPt(TestFreq,CtPerHz); // convert to counts
    PrintFixedPt(Buffer,TestCount);
    printf("Delta phase ct: %s\n",Buffer);
    PrintFixedPtRounded(Buffer,TestCount,0);
    printf(" round to int: %s\n",Buffer);
    TestFreq.fx_64 = (599999LL * One.fx_64) / 10; // set 59999.9 kHz differently
    PrintFixedPt(Buffer,TestFreq);
    printf("\nTest frequency: %s\n",Buffer);
    PrintFixedPtRounded(Buffer,TestFreq,1);
    printf(" round: %s\n",Buffer);
    TestCount.fx_64 = MultiplyFixedPt(TestFreq,CtPerHz); // convert to counts
    PrintFixedPt(Buffer,TestCount);
    printf("Delta phase ct: %s\n",Buffer);
    PrintFixedPtRounded(Buffer,TestCount,0);
    printf(" round to int: %s\n",Buffer);
    TempFX.fx_64 = RoundFixedPt(TestCount,0); // compute frequency from integer count
    TestFreq.fx_64 = MultiplyFixedPt(TempFX,HzPerCt);
    PrintFixedPt(Buffer,TestFreq);
    printf("Int ct -> freq: %s\n",Buffer);
    PrintFixedPtRounded(Buffer,TestFreq,1);
    printf(" round: %s\n",Buffer);
    TestFreq.fx_64 = One.fx_64 * (10 * MEGA); // set 10 MHz
    PrintFixedPt(Buffer,TestFreq);
    printf("\nTest frequency: %s\n",Buffer);
    PrintFixedPtRounded(Buffer,TestFreq,1);
    printf(" round: %s\n",Buffer);
    TestCount.fx_64 = MultiplyFixedPt(TestFreq,CtPerHz); // convert to counts
    PrintFixedPt(Buffer,TestCount);
    printf("Delta phase ct: %s\n",Buffer);
    PrintFixedPtRounded(Buffer,TestCount,0);
    printf(" round to int: %s\n",Buffer);
    TempFX.fx_64 = RoundFixedPt(TestCount,0); // compute frequency from integer count
    TestFreq.fx_64 = MultiplyFixedPt(TempFX,HzPerCt);
    PrintFixedPt(Buffer,TestFreq);
    printf("Int ct -> freq: %s\n",Buffer);
    PrintFixedPtRounded(Buffer,TestFreq,1);
    printf(" round: %s\n",Buffer);
    TestFreq.fx_64 = One.fx_64 * (10 * MEGA); // set 10 MHz + 0.1 Hz
    TestFreq.fx_64 += Tenth.fx_64;
    PrintFixedPt(Buffer,TestFreq);
    printf("\nTest frequency: %s\n",Buffer);
    PrintFixedPtRounded(Buffer,TestFreq,1);
    printf(" round: %s\n",Buffer);
    TestCount.fx_64 = MultiplyFixedPt(TestFreq,CtPerHz); // convert to counts
    PrintFixedPt(Buffer,TestCount);
    printf("Delta phase ct: %s\n",Buffer);
    PrintFixedPtRounded(Buffer,TestCount,0);
    printf(" round to int: %s\n",Buffer);
    TempFX.fx_64 = RoundFixedPt(TestCount,0); // compute frequency from integer count
    TestFreq.fx_64 = MultiplyFixedPt(TempFX,HzPerCt);
    PrintFixedPt(Buffer,TestFreq);
    printf("Int ct -> freq: %s\n",Buffer);
    PrintFixedPtRounded(Buffer,TestFreq,1);
    printf(" round: %s\n",Buffer);
    TestFreq.fx_64 = One.fx_64 * (10 * MEGA); // set 10 MHz – 0.1 Hz
    TestFreq.fx_64 -= Tenth.fx_64;
    PrintFixedPt(Buffer,TestFreq);
    printf("\nTest frequency: %s\n",Buffer);
    PrintFixedPtRounded(Buffer,TestFreq,1);
    printf(" round: %s\n",Buffer);
    TestCount.fx_64 = MultiplyFixedPt(TestFreq,CtPerHz); // convert to counts
    PrintFixedPt(Buffer,TestCount);
    printf("Delta phase ct: %s\n",Buffer);
    PrintFixedPtRounded(Buffer,TestCount,0);
    printf(" round to int: %s\n",Buffer);
    TempFX.fx_64 = RoundFixedPt(TestCount,0); // compute frequency from integer count
    TestFreq.fx_64 = MultiplyFixedPt(TempFX,HzPerCt);
    PrintFixedPt(Buffer,TestFreq);
    printf("Int ct -> freq: %s\n",Buffer);
    PrintFixedPtRounded(Buffer,TestFreq,1);
    printf(" round: %s\n",Buffer);
    Oscillator.fx_64 = One.fx_64 * (125 * MEGA);
    Serial.println("Oscillator tune: CtPerHz");
    PrintFixedPtRounded(Buffer,Oscillator,2);
    printf(" Oscillator: %s\n",Buffer);
    for (int i=-10; i<=10; i++) {
    OscOffset.fx_64 = i * One.fx_64;
    CtPerHz.fx_64 = 1LL << 63;
    CtPerHz.fx_64 /= (Oscillator.fx_64 + OscOffset.fx_64) >> 33;
    PrintFixedPt(Buffer,CtPerHz);
    printf(" %+3d -> %s\n",i,Buffer);
    }
    }
    //———–
    void loop () {
    }
    view raw DDSCalcTest.ino hosted with ❤ by GitHub
    DDS calculation exercise
    Ed Nisley – KE4ZNU – May 2017
    Max fixed point: 4294967295.999999999
    1.0: 1.000000000
    0.1: 0.099999999
    0.1 to 9 dec: 0.100000000
    0.1 to 3 dec: 0.100499999 (full string)
    0.1 to 3 dec: 0.100 (truncated string)
    Ct/Hz = 34.359738367
    Rounding:
    9: 34.359738368
    8: 34.35973837
    7: 34.3597384
    6: 34.359738
    5: 34.35974
    4: 34.3597
    3: 34.360
    2: 34.36
    1: 34.4
    0: 34
    Hz/Ct: 0.029103830
    0.1 Hz as ct: 3.435973831
    Rounding:
    9: 3.435973832
    8: 3.43597383
    7: 3.4359738
    6: 3.435974
    5: 3.43597
    4: 3.4360
    3: 3.436
    2: 3.44
    1: 3.4
    0: 3
    Test frequency: 60000.000000000
    round: 60000.0
    Delta phase ct: 2061584.302070550
    round to int: 2061584
    Test frequency: 60000.099999999
    round: 60000.1
    Delta phase ct: 2061587.738044382
    round to int: 2061588
    Test frequency: 59999.900000000
    round: 59999.9
    Delta phase ct: 2061580.866096718
    round to int: 2061581
    Test frequency: 59999.899999999
    round: 59999.9
    Delta phase ct: 2061580.866096710
    round to int: 2061581
    Int ct -> freq: 59999.914551639
    round: 59999.9
    Test frequency: 10000000.000000000
    round: 10000000.0
    Delta phase ct: 343597383.678425103
    round to int: 343597384
    Int ct -> freq: 10000000.014506079
    round: 10000000.0
    Test frequency: 10000000.099999999
    round: 10000000.1
    Delta phase ct: 343597387.114398935
    round to int: 343597387
    Int ct -> freq: 10000000.114506079
    round: 10000000.1
    Test frequency: 9999999.900000000
    round: 9999999.9
    Delta phase ct: 343597380.242451271
    round to int: 343597380
    Int ct -> freq: 9999999.914506079
    round: 9999999.9
    Oscillator tune: CtPerHz
    Oscillator: 125000000.00
    -10 -> 34.359741116
    -9 -> 34.359741116
    -8 -> 34.359740566
    -7 -> 34.359740566
    -6 -> 34.359740017
    -5 -> 34.359740017
    -4 -> 34.359739467
    -3 -> 34.359739467
    -2 -> 34.359738917
    -1 -> 34.359738917
    +0 -> 34.359738367
    +1 -> 34.359738367
    +2 -> 34.359737818
    +3 -> 34.359737818
    +4 -> 34.359737268
    +5 -> 34.359737268
    +6 -> 34.359736718
    +7 -> 34.359736718
    +8 -> 34.359736168
    +9 -> 34.359736168
    +10 -> 34.359735619
    view raw DDSCalcTest.txt hosted with ❤ by GitHub
  • Sharing the Lane on Burnett Blvd. at Rt 55

    When we get to the end of Overocker Road, we occupy the entire left-and-straight lane, because we’re turning left onto Burnett Blvd and there’s no room for another vehicle beside us:

    Burnett at Rt 55 - Right pass - 2017-05-23 - 1
    Burnett at Rt 55 – Right pass – 2017-05-23 – 1

    I’m towing a trailer of groceries.

    On Burnett Blvd, we take the left side of the right lane (marked for left-and-right turns), because we’re turning left onto Rt 55, don’t want to get right-hooked by right-on-red traffic, and will be on the right side of the right lane of Rt 55 when we’re through the turn.

    Without turn signals, it’s not clear whether the car following us from Overocker will turn left or right, but the driver is snuggling up next to Mary:

    Burnett at Rt 55 - Right pass - 2017-05-23 - 2
    Burnett at Rt 55 – Right pass – 2017-05-23 – 2

    The driver’s window is sliding downward. Fortunately, we started moving before any comments were made. Perhaps he was going tell us we’re riding cool bikes?

    Ah-ha! The driver is turning left and intending to pass me on the right while we’re in the intersection:

    Burnett at Rt 55 - Right pass - 2017-05-23 - 3
    Burnett at Rt 55 – Right pass – 2017-05-23 – 3

    Helmet mirror FTW!

    I’m moving rightward across the turning lane to end up on the right side of the Rt 55 lane, while not riding across the steel manhole cover at the car’s front wheel:

    Burnett at Rt 55 - Right pass - 2017-05-23 - 4
    Burnett at Rt 55 – Right pass – 2017-05-23 – 4

    Mary doesn’t accelerate nearly as hard as I do; those pictures are one second apart.

    I’m un-leaning from the turn into Rt 55, with the trailer still on my left and the driver accelerating toward me:

    Burnett at Rt 55 - Right pass - 2017-05-23 - 5
    Burnett at Rt 55 – Right pass – 2017-05-23 – 5

    A close pass, but not too bad:

    Burnett at Rt 55 - Right pass - 2017-05-23 - 6
    Burnett at Rt 55 – Right pass – 2017-05-23 – 6

    Most of the time, our rides aren’t this interesting, but I have plenty of examples showing how NYS DOT’s road designs ignore cyclists. The Burnett intersection signals still give us four seconds to clear the intersection.

  • Arduino vs. Significant Figures: Preliminary 64-bit Fixed Point Exercise

    Although it’s not advertised, the Arduino / AVR compiler mostly does the right thing with long long = uint64_t variables: add & subtract work fine, but multiplication & division discard anything that doesn’t fit into 64 bits. Fitting a 32 bit integer and a 32 bit fraction into such a thing should eliminate (most) problems with significant figures.

    The general idea is to set up a struct giving access to the two 32 bit halves for direct manipulation, then overlay / union them with a single 64 bit integer for arithmetic purposes:

    struct ll_s {
      uint32_t low;
      uint32_t high;
    };
    
    union ll_u {
      uint64_t ll_64;
      struct ll_s ll_32;
    };
    

    Of course, the integer part still falls one bit shy of holding 2³². At the cost of one bit’s worth of resolution, you can still compute 2³² / 125×10⁶ by pre-dividing each quantity by 2:

    2^63 = [80000000 00000000]
    2^63 / 125/2 M = [00000022 5c17d04d]
    

    The low-order digit should be 0xe, not 0xd, but I think that’s survivable.

    Unfortunately, printf doesn’t handle 64 bit quantities, necessitating some awkward conversion routines. “Printing” to a string seems the least awful method, as I’ll eventually squirt the strings to a display, not send them to the serial port:

    void PrintFractionLL(char *pBuffer,uint64_t *pLL) {
      uint64_t Fraction;
    
      Fraction = (uint32_t)*pLL;                      // copy 32 fraction bits, high order = 0
      Fraction *= ONEGIG;                             // times 10^9 for conversion
      Fraction >>= 32;                                // align integer part in low long
      sprintf(pBuffer,"%09lu",(uint32_t)Fraction);    // convert low long to decimal
    }
    
    void PrintIntegerLL(char *pBuffer,uint64_t *pLL) {
      sprintf(pBuffer,"%lu",*((uint32_t *)pLL+1));
    }
    
    void PrintDecimalLL(char *pBuffer,uint64_t *pLL) {
      PrintIntegerLL(pBuffer,pLL);
      pBuffer += strlen(pBuffer);       // pointer to end of integer part
      *pBuffer++ = '.';                 // drop in the decimal point, tick pointer
      PrintFractionLL(pBuffer,pLL);
    }
    

    The result seems nearly indistinguishable from the Right Answer:

    Integer:  34
    Fraction: 359738367
    Decimal: 34.359738367
    

    This whole mess has a bunch of rough edges, but it looks promising. The code coalesced while fiddling around, so the union notation didn’t get much love at first.

    The Arduino source code as a GitHub Gist:

    Long long integer exercise
    Ed Nisley – KE4ZNU – May 2017
    Long long size = 8 bytes
    .. value = [12345678 9abcdef0]
    divided result = [01234567 89abcdef]
    2^32 = [00000001 00000000]
    125M = [00000000 07735940]
    2^32 / 125M = [00000000 00000022]
    Scaled fixed point tests
    2^63 = [80000000 00000000]
    2^63 / 125/2 M = [00000022 5c17d04d]
    Integer: 34
    Fraction: 359738367
    Decimal: 34.359738367
    Hz Per Ct: 0.029103830
    60000: 60000.000000000
    60 kHz as count: 2061584.302070550
    0.1 Hz as count: 3.435973836
    60000.1 Hz as count: 2061587.738044387
    60000.1 Hz from count: 60000.078519806
    60000.2 Hz as count: 2061591.174018223
    60000.2 Hz from count: 60000.194935128
    60000.2 Hz rnd count: 60000.224038958
    Union size: 8
    TestFreq: [0000ea60 395a9e00]
    Union ll: [0000ea60 395a9e00]
    From union: 60000.224038958
    Buffer length: 15
    Trunc dec: 60000.224
    view raw LongLong.txt hosted with ❤ by GitHub
    // Long long integer exercise for 60 kHz crystal tester
    //– Helper routine for printf()
    int s_putc(char c, FILE *t) {
    Serial.write(c);
    }
    char Buffer[10+1+10]; // string buffer for long long conversions
    #define ONEGIG 1000000000LL
    uint64_t CtPerHz; // will be 2^32 / 125 MHz
    uint64_t HzPerCt; // will be 125 MHz / 2^32
    uint64_t TenthHzCt; // 0.1 Hz in counts
    struct ll_s {
    uint32_t low;
    uint32_t high;
    };
    union ll_u {
    uint64_t ll_64;
    struct ll_s ll_32;
    };
    //———–
    // Long long print-to-buffer helpers
    // Assumes little-Endian layout
    void PrintHexLL(char *pBuffer,uint64_t *pLL) {
    sprintf(pBuffer,"%08lx %08lx",*((uint32_t *)pLL+1),(uint32_t)*pLL);
    }
    // converts 9 decimal digits
    void PrintFractionLL(char *pBuffer,uint64_t *pLL) {
    uint64_t Fraction;
    Fraction = (uint32_t)*pLL; // copy 32 fraction bits, high order = 0
    Fraction *= ONEGIG; // times 10^9 for conversion
    Fraction >>= 32; // align integer part in low long
    sprintf(pBuffer,"%09lu",(uint32_t)Fraction); // convert low long to decimal
    }
    void PrintIntegerLL(char *pBuffer,uint64_t *pLL) {
    sprintf(pBuffer,"%lu",*((uint32_t *)pLL+1));
    }
    void PrintDecimalLL(char *pBuffer,uint64_t *pLL) {
    PrintIntegerLL(pBuffer,pLL);
    pBuffer += strlen(pBuffer); // pointer to end of integer part
    *pBuffer++ = '.'; // drop in the decimal point, tick pointer
    PrintFractionLL(pBuffer,pLL);
    }
    //———–
    void setup ()
    {
    Serial.begin (115200);
    fdevopen(&s_putc,0); // set up serial output for printf()
    Serial.println ("Long long integer exercise");
    Serial.println ("Ed Nisley – KE4ZNU – May 2017");
    unsigned long long LongLong;
    uint64_t LongLong2;
    LongLong = 0x123456789abcdef0LL;
    printf("Long long size = %d bytes\n",sizeof(LongLong));
    printf(" .. value = [%08lx %08lx]\n",(long)(LongLong >> 32),(long)(LongLong & 0x00000000ffffffffLL));
    LongLong /= 16;
    printf(" divided result = [%08lx %08lx]\n",(long)(LongLong >> 32),(long)LongLong);
    LongLong = 1LL << 32;
    printf(" 2^32 = [%08lx %08lx]\n",(long)(LongLong >> 32),(long)LongLong);
    LongLong2 = 125000000LL;
    printf(" 125M = [%08lx %08lx]\n",(long)(LongLong2 >> 32),(long)LongLong2);
    LongLong /= LongLong2;
    printf("2^32 / 125M = [%08lx %08lx]\n",(long)(LongLong >> 32),(long)LongLong);
    Serial.println("Scaled fixed point tests");
    uint64_t TestFreq,TestCount;
    CtPerHz = 1LL << 63; // start with 2^31 to avoid overflow
    PrintHexLL(Buffer,&CtPerHz);
    printf("2^63 = [%s]\n",Buffer);
    CtPerHz /= 125000000LL / 2; // divided by 2 to to match 2^31
    PrintHexLL(Buffer,&CtPerHz);
    printf("2^63 / 125/2 M = [%s]\n",Buffer);
    PrintIntegerLL(Buffer,&CtPerHz);
    printf("Integer: %s\n",Buffer);
    PrintFractionLL(Buffer,&CtPerHz);
    printf("Fraction: %s\n",Buffer);
    PrintDecimalLL(Buffer,&CtPerHz);
    printf("Decimal: %s\n",Buffer);
    HzPerCt = 125000000LL; // 125 MHz / 2^32, directly to fraction part
    PrintDecimalLL(Buffer,&HzPerCt);
    printf("Hz Per Ct: %s\n",Buffer);
    TestFreq = 60000LL << 32;
    PrintDecimalLL(Buffer,&TestFreq);
    printf("60000: %s\n",Buffer);
    TestCount = 60000LL * CtPerHz;
    PrintDecimalLL(Buffer,&TestCount);
    printf("60 kHz as count: %s\n",Buffer);
    TenthHzCt = CtPerHz / 10; // 0.1 Hz as counts
    PrintDecimalLL(Buffer,&TenthHzCt);
    printf("0.1 Hz as count: %s\n",Buffer);
    TestCount += TenthHzCt;
    PrintDecimalLL(Buffer,&TestCount);
    printf("60000.1 Hz as count: %s\n",Buffer);
    TestFreq = (TestCount >> 32) * HzPerCt;
    PrintDecimalLL(Buffer,&TestFreq);
    printf("60000.1 Hz from count: %s\n",Buffer);
    TestCount = (60000LL * CtPerHz) + (2 * TenthHzCt);
    PrintDecimalLL(Buffer,&TestCount);
    printf("60000.2 Hz as count: %s\n",Buffer);
    TestFreq = (TestCount >> 32) * HzPerCt;
    PrintDecimalLL(Buffer,&TestFreq);
    printf("60000.2 Hz from count: %s\n",Buffer);
    TestFreq = ((TestCount + (TenthHzCt / 2)) >> 32) * HzPerCt;
    PrintDecimalLL(Buffer,&TestFreq);
    printf("60000.2 Hz rnd count: %s\n",Buffer);
    union ll_u LongLongUnion, TempLLU;
    printf("Union size: %d\n",sizeof(LongLongUnion));
    LongLongUnion.ll_64 = TestFreq;
    PrintHexLL(Buffer,&TestFreq);
    printf("TestFreq: [%s]\n",Buffer);
    PrintHexLL(Buffer,&LongLongUnion.ll_64);
    printf("Union ll: [%s]\n",Buffer);
    TempLLU.ll_64 = LongLongUnion.ll_32.low;
    TempLLU.ll_64 *= ONEGIG; // times 10^9 for conversion
    TempLLU.ll_64 >>= 32; // align integer part in low long
    sprintf(Buffer,"%lu.%09lu",LongLongUnion.ll_32.high,TempLLU.ll_32.low);
    printf("From union: %s\n",Buffer);
    sprintf(Buffer,"%lu.%09lu",LongLongUnion.ll_32.high,TempLLU.ll_32.low);
    printf("Buffer length: %d\n",strlen(Buffer));
    Buffer[strlen(Buffer) – 9 + 3] = 0;
    printf(" Trunc dec: %s\n",Buffer);
    }
    //———–
    void loop () {
    }
  • Arduino vs Significant Figures: Floating Point Calculations

    Herewith, to nail down the reasons why you can’t (or, perhaps, shouldn’t) use Arduino float variables, a small collection of DDS-oid calculations.

    Remember that float and double variable are both IEEE 754 single-precision floating point numbers:

    Size of float: 4
           double: 4
    

    The Arduino floating-point formatter gags on some values, although they calculate correctly:

    2^24: 16777216.000
    printf:        ?
    2^32: ovf or ovf
    2^32: ovf
    2^32 / 256: 16777216.000
    

    Don’t add values differing by more than seven orders of magnitude and suspect any results beyond the first half-dozen significant figures:

    Oscillator steps: HzPerCt
     Oscillator: 125000000.00
     -25 -> 0.02910382461
     -24 -> 0.02910382461
     -23 -> 0.02910382461
     -22 -> 0.02910382461
     -21 -> 0.02910382461
     -20 -> 0.02910382747
     -19 -> 0.02910382747
     -18 -> 0.02910382747
     -17 -> 0.02910382747
     -16 -> 0.02910382747
     -15 -> 0.02910382747
     -14 -> 0.02910382747
     -13 -> 0.02910382747
     -12 -> 0.02910382747
     -11 -> 0.02910382747
     -10 -> 0.02910382747
      -9 -> 0.02910382747
      -8 -> 0.02910382747
      -7 -> 0.02910382747
      -6 -> 0.02910382747
      -5 -> 0.02910382747
      -4 -> 0.02910383033
      -3 -> 0.02910383033
      -2 -> 0.02910383033
      -1 -> 0.02910383033
      +0 -> 0.02910383033
    

    The Arduino source code as a GitHub Gist:

    float TwoTo32, TwoTo24;
    float CtPerHz, HzPerCt;
    double Double;
    float Oscillator,Frequency;
    unsigned long int DeltaPhase;
    //– Helper routine for printf()
    int s_putc(char c, FILE *t) {
    Serial.write(c);
    }
    //– Calculate delta phase from output & oscillator
    uint32_t CalculateDP(float Freq, float Osc) {
    uint32_t DP;
    Serial.print("Freq: ");
    Serial.print(Freq,3);
    Serial.print(" Osc: ");
    Serial.print(Osc,3);
    DP = Freq * TwoTo32 / Osc;
    printf(" -> DP: %lu = 0x%08lx\n",DP,DP);
    return DP;
    }
    //– Calculate frequency from delta phase & oscillator
    float CalculateFreq(uint32_t DP, float Osc) {
    float Freq;
    Freq = DP * Osc / TwoTo32;
    printf("DP: %lu = 0x%08lx ",DP,DP);
    Serial.print(" Osc: ");
    Serial.print(Osc,3);
    Serial.print(" -> Freq: ");
    Serial.println(Freq,3);
    return Freq;
    }
    //——————
    void setup() {
    Serial.begin(115200);
    fdevopen(&s_putc,0); // set up serial output for printf()
    Serial.println (F("DDS Numeric Values"));
    Serial.println (F("Ed Nisley – KE4ZNU – May 2017\n"));
    printf("Size of float: %u\n",sizeof(TwoTo32));
    printf(" double: %u\n",sizeof(Double));
    TwoTo24 = pow(2.0,24);
    Serial.print("2^24: ");
    Serial.println(TwoTo24,3);
    printf("printf: %8.8f\n",TwoTo24);
    TwoTo32 = pow(2,32);
    Serial.print("2^32: ");
    Serial.print(TwoTo32,3);
    Serial.print(" or ");
    Serial.println(TwoTo32,3);
    TwoTo32 = 4294967296.0;
    Serial.print("2^32: ");
    Serial.println(TwoTo32,0);
    Serial.print("2^32 / 256: ");
    Serial.println(TwoTo32 / 256.0,3);
    Oscillator = 125e6;
    Serial.print("Oscillator: ");
    Serial.println(Oscillator,3);
    Frequency = 10e6;
    Serial.print("Frequency: ");
    Serial.println(Frequency,3);
    HzPerCt = Oscillator / TwoTo32;
    Serial.print("HzPerCt: ");
    Serial.println(HzPerCt,9);
    CtPerHz = TwoTo32 / Oscillator;
    Serial.print("CtPerHz: ");
    Serial.println(CtPerHz,9);
    Frequency = 10e6 + 0.0;
    DeltaPhase = Frequency * CtPerHz;
    Serial.print(Frequency,3);
    printf(": Delta Phase: %lu = %08lx\n",DeltaPhase,DeltaPhase);
    Frequency = 10e6 + 0.5;
    DeltaPhase = Frequency * CtPerHz;
    Serial.print(Frequency,3);
    printf(": Delta Phase: %lu = %08lx\n",DeltaPhase,DeltaPhase);
    Frequency = 10e6 + 0.8;
    DeltaPhase = Frequency * CtPerHz;
    Serial.print(Frequency,3);
    printf(": Delta Phase: %lu = %08lx\n",DeltaPhase,DeltaPhase);
    Frequency = 10e6 + 1.0;
    DeltaPhase = Frequency * CtPerHz;
    Serial.print(Frequency,3);
    printf(": Delta Phase: %lu = %08lx\n",DeltaPhase,DeltaPhase);
    Serial.println("Oscillator steps: HzPerCt");
    Serial.print(" Oscillator: ");
    Serial.println(Oscillator,2);
    for (int i=-25; i<=25; i++) {
    HzPerCt = (Oscillator + i) / TwoTo32;
    printf(" %+3d -> ",i);
    Serial.println(HzPerCt,11);
    }
    Serial.println("Oscillator tune: CtPerHz ");
    Serial.print(" Oscillator: ");
    Serial.println(Oscillator,2);
    for (int i=-25; i<=25; i++) {
    CtPerHz = TwoTo32 / (Oscillator + i);
    printf(" %+3d -> ",i);
    Serial.println(CtPerHz,11);
    }
    // printf("CtPerHz int: %lu = %08lx\n",long(CtPerHz),long(CtPerHz));
    }
    //——————
    void loop() {}
    view raw FloatTest.ino hosted with ❤ by GitHub
    DDS Numeric Values
    Ed Nisley – KE4ZNU – May 2017
    Size of float: 4
    double: 4
    2^24: 16777216.000
    printf: ?
    2^32: ovf or ovf
    2^32: ovf
    2^32 / 256: 16777216.000
    Oscillator: 125000000.000
    Frequency: 10000000.000
    HzPerCt: 0.029103830
    CtPerHz: 34.359737396
    10000000.000: Delta Phase: 343597376 = 147ae140
    10000000.000: Delta Phase: 343597376 = 147ae140
    10000001.000: Delta Phase: 343597408 = 147ae160
    10000001.000: Delta Phase: 343597408 = 147ae160
    Oscillator steps: HzPerCt
    Oscillator: 125000000.00
    -25 -> 0.02910382461
    -24 -> 0.02910382461
    -23 -> 0.02910382461
    -22 -> 0.02910382461
    -21 -> 0.02910382461
    -20 -> 0.02910382747
    -19 -> 0.02910382747
    -18 -> 0.02910382747
    -17 -> 0.02910382747
    -16 -> 0.02910382747
    -15 -> 0.02910382747
    -14 -> 0.02910382747
    -13 -> 0.02910382747
    -12 -> 0.02910382747
    -11 -> 0.02910382747
    -10 -> 0.02910382747
    -9 -> 0.02910382747
    -8 -> 0.02910382747
    -7 -> 0.02910382747
    -6 -> 0.02910382747
    -5 -> 0.02910382747
    -4 -> 0.02910383033
    -3 -> 0.02910383033
    -2 -> 0.02910383033
    -1 -> 0.02910383033
    +0 -> 0.02910383033
    +1 -> 0.02910383033
    +2 -> 0.02910383033
    +3 -> 0.02910383033
    +4 -> 0.02910383033
    +5 -> 0.02910383224
    +6 -> 0.02910383224
    +7 -> 0.02910383224
    +8 -> 0.02910383224
    +9 -> 0.02910383224
    +10 -> 0.02910383224
    +11 -> 0.02910383224
    +12 -> 0.02910383224
    +13 -> 0.02910383224
    +14 -> 0.02910383224
    +15 -> 0.02910383224
    +16 -> 0.02910383224
    +17 -> 0.02910383224
    +18 -> 0.02910383224
    +19 -> 0.02910383224
    +20 -> 0.02910383224
    +21 -> 0.02910383701
    +22 -> 0.02910383701
    +23 -> 0.02910383701
    +24 -> 0.02910383701
    +25 -> 0.02910383701
    Oscillator tune: CtPerHz
    Oscillator: 125000000.00
    -25 -> 34.35974502563
    -24 -> 34.35974502563
    -23 -> 34.35974502563
    -22 -> 34.35974502563
    -21 -> 34.35974502563
    -20 -> 34.35974121093
    -19 -> 34.35974121093
    -18 -> 34.35974121093
    -17 -> 34.35974121093
    -16 -> 34.35974121093
    -15 -> 34.35974121093
    -14 -> 34.35974121093
    -13 -> 34.35974121093
    -12 -> 34.35974121093
    -11 -> 34.35974121093
    -10 -> 34.35974121093
    -9 -> 34.35974121093
    -8 -> 34.35974121093
    -7 -> 34.35974121093
    -6 -> 34.35974121093
    -5 -> 34.35974121093
    -4 -> 34.35973739624
    -3 -> 34.35973739624
    -2 -> 34.35973739624
    -1 -> 34.35973739624
    +0 -> 34.35973739624
    +1 -> 34.35973739624
    +2 -> 34.35973739624
    +3 -> 34.35973739624
    +4 -> 34.35973739624
    +5 -> 34.35973739624
    +6 -> 34.35973739624
    +7 -> 34.35973739624
    +8 -> 34.35973739624
    +9 -> 34.35973739624
    +10 -> 34.35973739624
    +11 -> 34.35973739624
    +12 -> 34.35973358154
    +13 -> 34.35973358154
    +14 -> 34.35973358154
    +15 -> 34.35973358154
    +16 -> 34.35973358154
    +17 -> 34.35973358154
    +18 -> 34.35973358154
    +19 -> 34.35973358154
    +20 -> 34.35973358154
    +21 -> 34.35973358154
    +22 -> 34.35973358154
    +23 -> 34.35973358154
    +24 -> 34.35973358154
    +25 -> 34.35973358154
    view raw FloatTest.txt hosted with ❤ by GitHub