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.)
@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.
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
}
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.
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.
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:
They used a 125 MHz oscillator which is only supposed to work for an AD9850 at 5.0 volts, and
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.
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".
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
}