Using "long long" for DDS tuning word calculations

I am trying to generate a "tuning word" for a DDS chip which requires 64-bit integer manipulation. Before sending this value I am trying to print it out to verify the calculations are correct. I've been banging my head against the keyboard for over three hours without success.
I am using a BluePill with TFT so I can display directly on-screen. The STM32F103 is supposed to be a 32-bit processor, and "long long" is compiling okay. I am using Arduino 2.3.10 and STM MCU 3.0.0. Here is the test code:

   // test value 10 MHz - DDS uses 125 MHz oscillator
   uint32_t frequency = 10000000;
   long long intermediate = (long long)frequency * ((long long)(2e32) / (long long)(10e6));   // this should be 42,949,672,960
   uint32_t tuningWord = (uint32_t)(intermediate / (long long)125);   // this should be 343,597,383.68 which fits in uint32_t as 343,597,383
   char scratch[20];
   sprintf(scratch, "-> %lu", intermediate);
   tft.drawString(scratch, 10, 170, 4);
   sprintf(scratch, "-> %u", tuningWord);
   tft.drawString(scratch, 10, 200, 4);

The first drawstring call prints "-> 4,290,191,488" which is the wrong value.
The second drawstring call prints "-> 790,235,776" which is also the wrong value.
I appreciate your help with this. (Commas added for clarity.)

Probably sprintf(scratch, "-> %llu", intermediate);
But supposedly safer:

uint64_t intermediate;
sprintf(scratch, "-> " PRIu64 , intermediate);

(still dependent on whether the printf implementation supports 64bit numbers.)

2e32 is 2*10^32 not 2^32

{AE0B9EC8-9029-4AD1-A5FB-A42DA8AFA5BA}

{EBA5F3C9-02CD-4E65-8F1E-BF0495F67E1F}

by the way 2e32 exceeds long long capacity

I would write that without so many parentheses that can cause intermediate overflows

   uint32_t frequency = 10000000;
   long long intermediate = (long long)frequency * (long long)(1ULL << 32) / (long long)10e6;   // this should be 42,949,672,960
   uint32_t tuningWord = (uint32_t)(intermediate / (long long)125);   // this should be 343,597,383.68 which fits in uint32_t as 343,597,383
   char scratch[20];
   sprintf(scratch, "-> %lu", intermediate);
   tft.drawString(scratch, 10, 170, 4);
   sprintf(scratch, "-> %u", tuningWord);
   tft.drawString(scratch, 10, 200, 4);

output
-> 4294967296-> 34359738

edit apologies @westfw I intended to reply to @pwrgreg007

@pwrgreg007 intermediate is 2^32 = 4294967296 because frequency is 10e6 which means the leading multiplication and final division cancel themselves. Your expected numbers are off by 10.

How about no parentheses, no casts?

void setup() {
  Serial.begin(115200);
  int32_t frequency = 10000000;     // let's say from an argument or earlier calculation
  int64_t intermediate = frequency; // so that you'd have to copy it over
  intermediate <<= 32;  // not 2e32 nor 2LL<<32
  intermediate /= 1e6;  // not 10e6

  uint32_t tuningWord = intermediate / 125;
  Serial.println(intermediate);
  Serial.println(tuningWord);
}

void loop() {
  delay(10); // this speeds up the simulation
}

prints, as expected

42949672960
343597383

@pwrgreg007

long long is board/compiler dependent, although most often it will be 64 bit.
Equally an int is often 32 bit and sometimes 16 bit.

use uint64_t or int64_t as these are always 64 bit.

My GitHub - RobTillaart/printHelpers: Arduino library to help formatting data for printing · GitHub library has a print64() to print these numbers as not all boards support that.
(there is also a thread safe version).

Which DDS chip are you using?

I added a pic of my notes and formulae to the original posting. My algebra was okay but my implementation in C had some mistakes.

With "-> %llu" it prints "-> lu" so it doesn't like that.
With "-> " PRIu64 it prints "-> llu" so it doesn't like that either.
Edit: With "-> %" PRIu64 it prints "-> lu", the same as "%llu".

PRIu64 is a macro that resolves to "__PRI64(u)" in my environment.

Arduino is a great boon to those of us who like to build MCU projects, but being open-source it is difficult to find adequate documentation of some things. This is at the intersection of Arduino, STM32, and 64-bit integers. Perhaps it is a shortcoming of printf/sprintf. I'm still checking.

Edit: There is a good discussion of this macro at stackoverflow.com/questions/16859500/what-is-priu64-in-c. Since it prints "lu" in my environment, I'm left with the impression that it's not implemented, or is broken, in STM32 3.0.0.

My C skills/knowledge have degraded after using C# for so long. Thanks for clarifying the use of "e" and the shortcut of using "<<" to do what I had in mind for the power-of-2 part. The final number "tuningWord" is coming out correctly now but I still can't get it to sprintf a uint64_t. I'm still looking into it. It's not critical since it's a test to view this intermediate value.

I added a pic of my notes to the original post which documents the chip and the algebra I am using. I will look into print64(), thanks for the tips.

You and others have pointed out my incorrect use of "2e32." It's confusing when Calculator accepts 2^32 and prints what I meant, but in C the carat is XOR and "e" is doing power of 10. My mind ain't what it used to be! Thanks for pointing it out.

you may have a look at - GitHub - RobTillaart/AD985X: Arduino library for AD9850 and AD9851 function generators. · GitHub

Rob, I did see your project on Github. I am using the board (pic attached) that a friend gave me. I'm not even sure this sucker will work, because:

  1. They used a 125 MHz oscillator which is only supposed to work for an AD9850 at 5.0 volts, and
  2. They used an Abracon ASV-series ceramic oscillator which is supposed to work at 3.3 volts maximum.

Makes me wonder if this was some sort of class project gone wrong. I am going to wire the board so I can jumper it with 3.3 or 5.0 volts and run some tests. I will keep your library in mind as I go forward. I'm using parallel programming to start, all on BluePill port B.

The AD9850 specs say it's good to 62.5 MHz with 125 MHz oscillator and 5.0 volt supply. How did you come up with 40 MHz max?

The PRI* macros provide just the size and type parts of the format, you need to add the % and optionally flags, width and/or precision.

sprintf(scratch, "-> %" PRIu64 , intermediate);

I added % and it just prints "lu." Screenshot attached. I think PRIu64 is broken on STM32 3.0.0.

Think I got it from datasheet, I recall that for higher values the quality of the signals degrades.

I decided to try this with "double" instead of "unit64_t" so I could round-up the value. The "tuningWord" for 10 MHz is something like 343,597,383.68 and would be closer rounded up. Interesting that 1 has to be cast to uint64_t BEFORE the left-shift, otherwise it will sign-extend a 32-bit shifted (signed) value when it converts to a 64-bit value, giving a wrong result. I found this by hovering over it and intelli-sense (or whatever it's called in Arduino) showed it when the cast was outside the parentheses. C has gotchas all over the place! Shift into the sign bit can cause problems if the compiler doesn't know it's unsigned.

 // calculate using division first (by large part of denominator) to reduce scale
 double intermediate = (double)frequency * ((double)((uint64_t)1 << 32) / (double)(1e6));
 // final division (by small part of denominator) with round-up
 uint32_t tuningWord = (uint32_t)((intermediate / 125.0) + 0.5);
 char scratch[20];
 sprintf(scratch, "-> %" PRId64 "    ", intermediate);
 tft.drawString(scratch, 10, 170, 4);
 sprintf(scratch, "-> %u    ", tuningWord);
 tft.drawString(scratch, 10, 200, 4);

This gave the expected result of 343,597,384 for tuningWord.
Neither %lld nor PRId64 worked for intermediate, so it would seem that the whole 64-bit printf support in STM32 is broken, or I'm missing something. It prints "ld".

==>

tft.drawString(print64(intermediate), 10, 170, 4);

and #include "printHelpers" at the top (download library first of course)

Try changing Tools > C Runtime Library to Newlib Standard.
I think it supports printing 64-bit integers.

I tried it. Even with "smallest w/LTO" it doesn't fit in the available 64K space.

I certainly wouldn't go so far as to say there are no gotchas, but the rules regarding arithmetic, including promotion and overflow, are not arbitrary.

When in doubt, as you noticed, the code insight features can help

auto foo = 3LL * 0.5f;

Hovering should show (even without having to do a full compile)

  • Binary operators generally have the operands as the same type. Promotion will widen one of them if necessary to match
  • Integers are promoted to floating point
  • Then the calculation may still overflow; widening is not done to "preserve" the result
  • It's entirely redundant to cast the final result before assigning to a variable of the same type. It has to end up as that type anyway.

Again, with neither casts nor parentheses for grouping

void setup() {
  Serial.begin(115200);
  int32_t frequency = 10000000;
  double intermediate = frequency;
  intermediate *= 1LL << 32;  // LL suffix easier to read&write than cast
  intermediate /= 1e6;

  uint32_t tuningWord = intermediate / 125 + 0.5;
  Serial.println(intermediate);
  Serial.println(tuningWord);
}

void loop() {
  delay(10); // this speeds up the simulation
}

prints

ovf
343597384

overflow when printing with Serial.print(double), but the value is fine. So similar to your existing problem. Just to see when the overflow stops

void setup() {
  Serial.begin(115200);
}

void loop() {
  static int shift = 32;
  if (shift) {
    int32_t frequency = 10000000;
    double intermediate = frequency;
    intermediate *= 1LL << shift;
    intermediate /= 1e6;

    uint32_t tuningWord = intermediate / 125 + 0.5;
    Serial.print(shift);
    Serial.print('\t');
    bool stop = Serial.println(intermediate) > 5;
    Serial.println(static_cast<int64_t>(intermediate));
    Serial.println(tuningWord);
    if (stop) {
      shift = 0;
    } else {
      --shift;
    }
  }
  delay(10); // this speeds up the simulation
}

prints

32	ovf
42949672960
343597384
31	ovf
21474836480
171798692
30	ovf
10737418240
85899346
29	ovf
5368709120
42949673
28	2684354560.00
2684354560
21474836

Turns out there's a hard cap

(You can find this with three drill-ins using Go to Definition)