Advertisements

Search Results for: "arduino source code"

Arduino Pseudo-Random White Noise Source

A reader (you know who you are!) proposed an interesting project that will involve measuring audio passbands and suggested using white noise to show the entire shape on a spectrum analyzer. He pointed me at the NOISE 1B Noise Generator based on a PIC microcontroller, which led to trying out the same idea on an Arduino.

The first pass used the low bit from the Arduino runtime’s built-in random() function:

Arduino random function bit timing

Arduino random function bit timing

Well, that’s a tad pokey for audio: 54 μs/bit = 18.5 kHz. Turns out they use an algorithm based on multiplication and division to produce nice-looking numbers, but doing that to 32 bit quantities takes quite a while on an 8 bit microcontroller teleported from the mid 1990s.

The general idea is to send a bit from the end of a linear feedback shift register to an output to produce a randomly switching binary signal. Because successive values involve only shifts and XORs, it should trundle along at a pretty good clip and, indeed, it does:

Arduino Galois shift reg bit timing

Arduino Galois shift reg bit timing

I used the Galois optimization, rather than a traditional LFSR, because I only need one random bit and don’t care about the actual sequence of values. In round numbers, it spits out bits an order of magnitude faster at 6 μs/bit = 160 kHz.

For lack of anything smarter, I picked the first set of coefficients from the list of 32 bit maximal-length values at https://users.ece.cmu.edu/~koopman/lfsr/index.html:
0x80000057.

The spectrum looks pretty good, particularly if you’re only interested in the audio range way over on the left side:

Arduino Galois bit spectrum

Arduino Galois bit spectrum

It’s down 3 dB at 76 kHz, about half the 160 kHz bit flipping pace.

If you were fussy, you’d turn off the 1 ms timer interrupt to remove a slight jitter in the output.

It’s built with an old Arduino Pro Mini wired up to a counterfeit FTDI USB converter. Maybe this is the best thing I can do with it: put it in a box with a few audio filters for various noise colors and be done with it.

It occurs to me I could fire it into the 60 kHz preamp’s snout to measure the response over a fairly broad range while I’m waiting for better RF reception across the continent.

The Arduino source code as a GitHub Gist:

Advertisements

,

Leave a comment

LF DDS Sine Generator With 0.1 Hz Steps

Gutting the crystal tester program and grafting a simple joystick interface onto the corpse produces an LF sine wave generator with 0.10 Hz steps:

FG085 vs AD9850 DDS frequencies

FG085 vs AD9850 DDS frequencies

The FG085 function generator shows 60000 Hz and the AD9850 shows 60001.58 Hz, but they’re running at exactly the same frequency:

DDS 1.58 FG085 0.0

DDS 1.58 FG085 0.0

I trust the AD9850 readout, because I just finished zero-beating it against the GPS-locked 10 MHz frequency reference: it’s dead on. The scope’s frequency measurement is clearly out of its depth at this resolution.

The “user interface” doesn’t amount to much. The DDS starts at 60.000 kHz, as defined by a program constant. Push the joystick left-right to step by 0.1 Hz (actually, multiples of 0.0291 Hz, so either 0.087 or 0.116 Hz, whichever makes the answer come out closer to the next multiple of 0.1 Hz). Push it up-down to step by 1.0 Hz (insert similar handwaving here). Push the button inward to reset to 60.000 kHz.

The OLED displays the frequency (in big digits), the output of the log amplifier (which isn’t hooked up here) in dB (over 4 μV), the DDS clock oscillator temperature, and a few lines of static prompting. The camera shutter blanked the last line, which should read “Button = reset”.

There’s no amplitude adjustment, other than the DDS current-control twiddlepot and the buffer amp’s gain-setting jumpers, but I (think I can) gimmick up an adequate inductive kicker for the fake preamp antenna circuit.

Not much to look at, but now I can (manually) probe the frequency response of the 60 kHz preamp with sufficient resolution to figure out if / how the tuning fork resonator filter behaves.

The Arduino source code as a GitHub Gist:

2 Comments

LF Crystal Tester: Joystick for Oscillator Offset Adjustment

With the joystick button and LM75 temperature sensor running, this chunk of code lets you nudge the nominal DDS oscillator frequency by 1 Hz every 100 ms:

While that’s happening, you compare the DDS output to a reference frequency on an oscilloscope:

Zero-beat oscillator

Zero-beat oscillator

The top trace (and scope trigger) is the GPS-locked 10 MHz reference, the lower trace is the AD9850 DDS output (not through the MAX4165 buffer amp, because bandwidth). If the frequencies aren’t identical, the DDS trace will crawl left or right with respect to the reference: leftward if the DDS frequency is too high, rightward if it’s too low. If the DDS frequency is way off, then the waveform may scamper or run, with the distinct possibility of aliasing on digital scopes; you have been warned.

The joystick acts as a bidirectional switch, rather than an analog input, with the loop determining the step increment and timing. The ad-hoc axis orientation lets you (well, me) push the joystick against the waveform crawl, which gradually slows down and stops when the offset value makes the DDS output match the reference.

The OLED displays the current status:

DDS Offset zero-beat display

DDS Offset zero-beat display

The lurid red glow along the bottom is lens flare from the amber LED showing the relay is turned on. The slightly dimmer characters across the middle of the display show how the refresh interacts with the camera shutter at 1/30 s exposure.

N.B.: Normally, you know the DDS clock oscillator frequency with some accuracy. Dividing that value into 232 (for the AD9850) gives you the delta-phase count / frequency ratio that converts a desired DDS output frequency into the delta-phase value telling the DDS to make it happen.

In this case, I want the output frequency to be exactly 10.000000 MHz, so I’m adjusting the oscillator frequency (nominal 125 MHz + offset), calculating the corresponding count-to-Hz ratio, multiplying the ratio by 10.000000 MHz, stuffing the ensuing count into the DDS, and eyeballing what happens. When the oscillator frequency variable matches the actual oscillator frequency, then the actual output will 10.000000 MHz and the ratio will be correct.

Got it? Took me a while.

Although the intent is to tune for best frequency match and move on, you (well, I) can use this to accumulate a table of frequency offset vs. temperature pairs, from which a (presumably simple) formula can be conjured to render this step unnecessary.

The Arduino source code as a GitHub Gist:

 

,

1 Comment

AD9850 DDS Module: OLED Display

Those little OLED displays might just work:

Arduino with OLED - simulated DDS

Arduino with OLED – simulated DDS

The U8X8 driver produces those double-size bitmap characters; the default 8×8 matrix seem pretty much unreadable on a 0.96 inch OLED at any practical distance from a benchtop instrument. They might be workable on a 1.3 inch white OLED, minus the attractive yellow highlight for the frequency in the top line.

The OLED uses an SPI interface, although the U8X8 library clobbers my (simpleminded) SPI configuration for the AD9850 DDS and I’ve dummied out the DDS outputs. A soon-to-arrive I²C OLED should resolve that problem; changing the interface from SPI to I²C involves changing the single line of code constructing the driver object, so It Should Just Work.

The U8X8 driver writes directly to the display, thus eliminating the need for a backing buffer in the Arduino’s painfully limited RAM. I think the library hauls in all possible fonts to support font selection at runtime, even though I need at most two fonts, so it may be worthwhile to hack the unneeded ones from the library (or figure out if I misunderstand the situation and the Flash image includes only the fonts actually used). Each font occupies anywhere from 200 to 2000 bytes, which I’d rather have available for program code. Chopping out unused functions would certainly be less useful.

The display formatting is a crude hack just to see what the numbers look like:

    int ln = 0;
    u8x8.draw2x2String(0,ln,Buffer);
    ln += 2;

    TestFreq.fx_64 = ScanTo.fx_64 - ScanFrom.fx_64;
    PrintFixedPtRounded(Buffer,TestFreq,1);
    u8x8.draw2x2String(0,ln,"W       ");
    u8x8.draw2x2String(2*(8-strlen(Buffer)),ln,Buffer);
    ln += 2;

    PrintFixedPtRounded(Buffer,ScanStep,3);
    u8x8.draw2x2String(0,ln,"S       ");
    u8x8.draw2x2String(2*(8-strlen(Buffer)),ln,Buffer);
    ln += 2;

    TestFreq.fx_32.high = SCAN_SETTLE;                    // milliseconds
    TestFreq.fx_32.low = 0;
    TestFreq.fx_64 /= KILO;                               // to seconds
    PrintFixedPtRounded(Buffer,TestFreq,3);
    u8x8.draw2x2String(0,ln,"T       ");
    u8x8.draw2x2String(2*(8-strlen(Buffer)),ln,Buffer);
    ln += 2;

Updating the display produces a noticeable and annoying flicker, which isn’t too surprising, so each value should have an “update me” flag to avoid gratuitous writes. Abstracting the display formatting into a table-driven routine might be appropriate, when I need more than one layout, but sheesh.

I calculate the actual frequency from the 32 bit integer delta phase word written to the DDS, rather than use the achingly precise fixed point value, so a tidy 0.100 Hz frequency step doesn’t produce neat results. Instead, the displayed value will be within ±0.0291 Hz (the frequency resolution) of the desired frequency, which probably makes more sense for the very narrow bandwidths involved in a quartz crystal test gadget.

Computing the frequency step size makes heavy use of 64 bit integers:

//  ScanStep.fx_64 = One.fx_64 / 4;                       // 0.25 Hz = 8 or 9 tuning register steps
  ScanStep.fx_64 = One.fx_64 / 10;                    // 0.1 Hz = 3 or 4 tuning register steps
//  ScanStep.fx_64 = One.fx_64 / 20;                    // 0.05 Hz = 2 or 3 tuning register steps
//  ScanStep = HzPerCt;                                   // smallest possible frequency step

The fixed point numbers resulting from those divisions will be accurate to nine decimal places; good enough for what I need.

The sensible way of handling discrete scan width / step size / settling time options is through menus showing the allowed choices, with joystick / joyswitch navigation & selection, rather than keyboard entry. An analog joystick has the distinct advantage of using two analog inputs, not four digital pins, although the U8X8 driver includes a switch-driven menu handler.

There’s a definite need to log all the values through the serial output for data collection without hand transcription.

The Arduino source code as a GitHub Gist:

, ,

2 Comments

AD9850 DDS Module: Hardware Assisted SPI and Fixed-point Frequency Stepping

Having conjured fixed-point arithmetic into working, the next step is to squirt data to the AD9850 DDS chip. Given that using the Arduino’s hardware-assisted SPI doesn’t require much in the way of software, the wiring looks like this:

Nano to DDS schematic

Nano to DDS schematic

Not much to it, is there? For reference, it looks a lot like you’d expect:

AD9850 DDS Module - swapped GND D7 pins

AD9850 DDS Module – swapped GND D7 pins

There’s no point in building an asynchronous interface with SPI interrupts and callbacks and all that rot, because squirting one byte at 1 Mb/s (a reasonable speed for hand wiring; the AD9850 can accept bits at 140+ MHz) doesn’t take all that long and it’s easier to have the low-level code stall until the hardware finishes:

#define PIN_HEARTBEAT    9          // added LED

#define PIN_RESET_DDS    7          // Reset DDS module
#define PIN_LATCH_DDS    8          // Latch serial data into DDS

#define PIN_SCK        13          // SPI clock (also Arduino LED!)
#define PIN_MISO      12          // SPI data input
#define PIN_MOSI      11          // SPI data output
#define PIN_SS        10          // SPI slave select - MUST BE OUTPUT = HIGH

void EnableSPI(void) {
  digitalWrite(PIN_SS,HIGH);        // set SPI into Master mode
  SPCR |= 1 << SPE;
}

void DisableSPI(void) {
  SPCR &= ~(1 << SPE);
}

void WaitSPIF(void) {
  while (! (SPSR & (1 << SPIF))) {
    TogglePin(PIN_HEARTBEAT);
    TogglePin(PIN_HEARTBEAT);
    continue;
  }
}

byte SendRecSPI(byte Dbyte) {           // send one byte, get another in exchange
  SPDR = Dbyte;
  WaitSPIF();
  return SPDR;                          // SPIF will be cleared
}

With that in hand, turning on the SPI hardware and waking up the AD9850 looks like this:

void EnableDDS(void) {

  digitalWrite(PIN_LATCH_DDS,LOW);          // ensure proper startup

  digitalWrite(PIN_RESET_DDS,HIGH);         // minimum reset pulse 40 ns, not a problem
  digitalWrite(PIN_RESET_DDS,LOW);
  delayMicroseconds(1);                     // max latency 100 ns, not a problem

  DisableSPI();                             // allow manual control of outputs
  digitalWrite(PIN_SCK,LOW);                // ensure clean SCK pulse
  PulsePin(PIN_SCK);                        //  ... to latch hardwired config bits
  PulsePin(PIN_LATCH_DDS);                  // load hardwired config bits = begin serial mode

  EnableSPI();                              // turn on hardware SPI controls
  SendRecSPI(0x00);                         // shift in serial config bits
  PulsePin(PIN_LATCH_DDS);                  // load serial config bits
}

Given 32 bits of delta phase data and knowing the DDS output phase angle is always zero, you just drop five bytes into a hole in the floor labeled “SPI” and away they go:

void WriteDDS(uint32_t DeltaPhase) {

  SendRecSPI((byte)DeltaPhase);             // low-order byte first
  SendRecSPI((byte)(DeltaPhase >> 8));
  SendRecSPI((byte)(DeltaPhase >> 16));
  SendRecSPI((byte)(DeltaPhase >> 24));

  SendRecSPI(0x00);                         // 5 MSBs = phase = 0, 3 LSBs must be zero

  PulsePin(PIN_LATCH_DDS);                  // write data to DDS
}

In order to have something to watch, the loop() increments the output frequency in steps of 0.1 Hz between 10.0 MHz ± 3 Hz, as set by the obvious global variables:

      PrintFixedPtRounded(Buffer,ScanFreq,1);

      TestCount.fx_64 = MultiplyFixedPt(ScanFreq,CtPerHz);
      printf("%12s -> %9ld\n",Buffer,TestCount.fx_32.high);

      WriteDDS(TestCount.fx_32.high);

      ScanFreq.fx_64 += ScanStep.fx_64;

      if (ScanFreq.fx_64 > (ScanTo.fx_64 + ScanStep.fx_64 / 2)) {
        ScanFreq = ScanFrom;
        Serial.println("Scan restart");
      }

Which produces output like this:

DDS SPI exercise
Ed Nisley - KE4ZNU - May 2017

Inputs: 124999656 = 125000000-344
Osc freq: 124999656.000000000
Hz/Ct: 0.029103750
Ct/Hz: 34.359832926
0.1 Hz Ct: 3.435983287
Test frequency:  10000000.0000
Delta phase: 343598329

Scan limits
 from:   9999997.0
   at:  10000000.0
   to:  10000003.0

Sleeping for a while ...

Startup done!

Begin scanning

  10000000.0 -> 343598329
  10000000.1 -> 343598332
  10000000.2 -> 343598336
  10000000.3 -> 343598339
  10000000.4 -> 343598343
  10000000.5 -> 343598346
  10000000.6 -> 343598349
  10000000.7 -> 343598353
  10000000.8 -> 343598356
  10000000.9 -> 343598360
  10000001.0 -> 343598363
  10000001.1 -> 343598367
  10000001.2 -> 343598370
  10000001.3 -> 343598373
<<< snippage >>>

The real excitement happens while watching the DDS output crawl across the scope screen in relation to the 10 MHz signal from the Z8301 GPS-locked reference:

DDS GPS - 10 MHz -48 Hz offset - zero beat

DDS GPS – 10 MHz -48 Hz offset – zero beat

The DDS sine in the upper trace is zero-beat against the GPS reference in the lower trace. There’s no hardware interlock, but they’re dead stationary during whatever DDS output step produces exactly 10.0000000 MHz. The temperature coefficient seems to be around 2.4 Hz/°C, so the merest whiff of air changes the frequency by more than 0.1 Hz.

It’s kinda like watching paint dry or a 3D printer at work, but it’s my paint: I like it a lot!

The Arduino source code as a GitHub Gist:

,

3 Comments

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:

, ,

14 Comments

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:

,

3 Comments