如果 Arduino 的库有 32 位限制,我如何在 Arduino 上输出 64 位数字?

How do I output a 64bit number on Arduino if its libraries have 32 bit restrictions?

为 Arduino 提供的编译器似乎生成可以使用 64 位数字的代码,但是 I/O 类,例如 Serial.Print(),似乎不能超过 32 位数字。

如何克服这些限制并将所有可能的 64 位无符号整数打印为十进制数?

一些背景知识:

对于 Arduino 项目,我需要在 Serial 上发送一个适合 uint64_t 变量类型的数字,但出于某种原因,Arduino 串行端口无法处理大于 32 位的类型(但设备支持 6 位类型)。

因此,我只需要在串行端口上将该(64 位)数字打印为十进制。为此,我正在考虑将数字分成两个 32 位块,每个块使用按位运算。然后在Serial口上依次发送每个数字,但是很明显得到的数字和原来的64位数字不一样

比如我使用一个无符号的8位数字如下:

11100010 <- 226 in Decimal

我需要将它发送到我的串口,但它最多只能处理 4 位数字。

因此我尝试用按位掩码将数字分成两部分:

1110 <- MSBs (14 in Decimal)
0010 <- LSBs (2 in Decimal)

因此,当我在序列号上按顺序发送时,我看到的是 142,但我想要的是 226。

那么,有没有一种算法,给定一个数作为输入,返回的结果是将前一个二进制数分解成高位和低位的两个数?

附录:

我正在使用 streaming.h 进行串行通信。


正如@Infixed 正确猜测的那样,我正在尝试:

How do I print a 64 bit number in decimal given 32 bit integer limitations on formatted decimal output?

到目前为止我发现:

uint64_t val;
uint32_t MSBval, LSBval;
MSBval = (uint32_t)(val / 10000000000LL);
LSBval = (uint32_t)(val % 10000000000LL);
Serial << F("[") << MSBval << LSBval << F("]") << endl;

如您所见,我还没有找到正确的解决方案。对于更高的数字,公式不应该正常工作,但出于某种原因(我仍在尝试找出原因)当我在序列号上发送结果时,它 returns 正确的数字,即使是最高的数字(255 255 255 255 255 255 255 255 [18446744073709551615]).


检查已接受的答案以获得更多不依赖于 Streaming.h 库的解决方案。


解决方案

这个解决方案不是很优雅,但至少适合我的目的。 如果需要,它可以被概括为与所有类型的整数一起使用(取决于您的 Arduino 板微控制器类型)。

目前仅在 Arduino Uno R3 (ATmega328P) 上使用 uint64_t 进行测试。

由于这似乎是我在网上找到的解决此类问题的唯一完整解决方案,所以如果有人发现任何错误或希望改进它,请随时回复(或编辑) 这个 post.

代码如下:

uint8_t MLSBval;
uint64_t val;
char buffer[11];

//val = 18440000009000100000LL; // Test 64bit split
val = 18440000009099999999LL;   // Test 64bit split

sprintf(buffer, "%010lu", MLSBval); // Formatting zeros
Serial << F("[") << (uint32_t)(val / 10000000000LL); // Output MSBval

val = val % 10000000000LL;                         // Calculate LSBval
if (val != 0){
    MLSBval = (uint8_t)(val / 100000000LL);
    val = (val % 100000000LL);
    sprintf(buffer, "%02u", MLSBval); // Formatting zeros
    Serial << buffer;
}
sprintf(buffer, "%08lu", (uint32_t)(val));    // Formatting zeros
Serial << (buffer) << F("]") << endl;         // Data to Serial Monitor;

然后稍微解释一下:

最大的问题在于十进制数的长度。如果数字的长度(例如 99 长度为 2;1294 长度为 4;等等)是奇数,我们需要将 if statement 代码与 MSBval 结果放在一起,然后在序列中使用它。

相反(如代码所示),如果数字是偶数,我们需要将 if statement 代码与 LSBval 放在一起,然后使用结果。

这适用于所有类型的整数。

如果您使用另一种 int 类型(32 位、16 位等)作为 val 类型,您需要更改(减少)与上述相关的分频器和模块编号例如。

在这种情况下,if statement 之前的除法器和模块的零数对应于适合 uint64 类型的最大数字长度的一半(在十进制中,然后是十个零)。相反,包含在 if statement 中的那些在 less(八个零)中有两个零。

显然我还没有用所有涉及的数字测试代码,但我希望这对所有人都有效!

这个问题的答案实际上取决于什么正在接收串口发送的数据class。

如果是人眼阅读,那么您显然希望实际打印一个十进制数。

如果您试图将其传递给另一个程序,该程序将两个或多个消息重新组合成一个 64 位数字,那就是另一个问题了。

但是如果它是供人类阅读的,那么来自 arduino 论坛的示例草图是关于如何打印 long long

long long x = 999999999999LL;  // note the double LL

void setup()
{
  Serial.begin(115200);
  Serial.println("I am Arduino");
  char buffer[100];
  sprintf(buffer, "%0ld", x/1000000L);
  Serial.print(buffer); 
  sprintf(buffer, "%0ld", x%1000000L);
  Serial.println(buffer); 
}

void loop() {}

http://forum.arduino.cc/index.php?topic=58697.msg422207#msg422207

编辑 评论中观察到可以使用 1,000,000,000(十亿)的模数代替 1,000,000(一百万)以允许更大的范围是正确的。但是由于一个无符号的 64 位数字最多可以表示 18446744073709551615,这仍然是十亿平方的 18 倍以上。所以即使十亿的模数也不是完美的,除以十亿后,结果将不适合 32 位整数。十亿十亿可以装进一个60位的数字

[学术附件,因为这个问题似乎还没有结束]

可以通过三种方式完成,更多的是看如何,而不是优化 第三个可能足以成为真正的答案

// Here is the totally unoptimized version to help you understand
// the low level of what needs to be done.   Lots of leading zeros
// because its easier that way.
// no one would do it this way except to learn the basic idea
void PrintNumberV1(unsigned long long num)
{
    Serial.print((unsigned)((num / 10000000000000000000LL) % 10));
    Serial.print((unsigned)((num /  1000000000000000000LL) % 10));
    Serial.print((unsigned)((num /   100000000000000000LL) % 10));
    Serial.print((unsigned)((num /    10000000000000000LL) % 10));
    Serial.print((unsigned)((num /     1000000000000000LL) % 10));
    Serial.print((unsigned)((num /      100000000000000LL) % 10));
    Serial.print((unsigned)((num /       10000000000000LL) % 10));
    Serial.print((unsigned)((num /        1000000000000LL) % 10));
    Serial.print((unsigned)((num /         100000000000LL) % 10));
    Serial.print((unsigned)((num /          10000000000LL) % 10));
    Serial.print((unsigned)((num /           1000000000LL) % 10));
    Serial.print((unsigned)((num /            100000000LL) % 10));
    Serial.print((unsigned)((num /             10000000LL) % 10));
    Serial.print((unsigned)((num /              1000000LL) % 10));
    Serial.print((unsigned)((num /               100000LL) % 10));
    Serial.print((unsigned)((num /                10000LL) % 10));
    Serial.print((unsigned)((num /                 1000LL) % 10));
    Serial.print((unsigned)((num /                  100LL) % 10));
    Serial.print((unsigned)((num /                   10LL) % 10));
    Serial.print((unsigned)((num /                    1LL) % 10));
}

// This is the way they'll show you in CompSci 101 when they want to
// to teach you about recursion, neglecting to mention how much stack
// and time it takes.  No leading zeros, because it's easier that way
void PrintNumberV2(unsigned long long num)
{
    int digit = num % 10;

    // do any more significant digits
    if (num > 9) PrintNumberV2(num / 10);

    // Print the digit this level of recursion has decided on
    Serial.print(digit);
}

// The way a C programmer might do it if he didn't have printf right there
// For extra fun you can use any base between 2 and 16
// No leading zeros because its easier
void PrintNumberV3(int base, unsigned long long num )
{
    const static char toAscii[] = "0123456789ABCDEF";

    if ((base < 2) || (base>16))
    {
        Serial.print("[DON'T USE THAT BASE!!!]");
        return;
    }

    char buffer[65];            //because you might be doing binary
    char* p = &buffer[64];      //this pointer writes into the buffer, starting at the END

    // zero to terminate a C type string
    *p = 0;

    // do digits until the number reaches zero
    do
    {
        // working on the least significant digit
        //put an ASCII digit at the front of the string
        *(--p) = toAscii[(int)(num % base)];

        //knock the least significant digit off the number
        num = num / base;
    } while (num != 0);

    //print the whole string
    Serial.print(s);
}

警告:None 已经在实际的 Arduino 上尝试过