浮点(双精度)值在具有其他值的字符串中损坏

Float (double) value gets corrupted in a string with other values

我正在使用 ATMEGA328PB AVR 微控制器,在通过 UART 发送某些浮点数时 运行 遇到了问题。

我已经使用我的编译器 (avr-gcc) 启用了浮点,它大部分都可以工作。

例如,这段代码:

floatVal = ((double)tempResult / (double)oversample) * 0.000457777;
char outbuffer[255];
sprintf(outbuffer, "%f\n", floatVal);
usart1TXString(outbuffer);

在 UART 上提供正确的输出:

3.866311

我也想打印一个32位的int。这也有效:

char outbuffer[255];
sprintf(outbuffer, "%ld\n", transform(combVal));
usart1TXString(outbuffer);

并在 UART 上提供正确的输出:

117864

(这两个输出正是我所期望的)

当我尝试将这两段代码组合成一个(甚至是连续的)传输时出现问题:

floatVal = ((double)tempResult / (double)oversample) * 0.000457777;
char outbuffer[255];
sprintf(outbuffer, "%f,%ld\n", floatVal, transform(combVal));
usart1TXString(outbuffer);

产生这个输出:

584991570000.000000,117864

大数字不会改变,除非我的 floatVal 是一个非常不同的值。

我试着把它分成两个不同的传输,但没有成功:

floatVal = ((double)tempResult / (double)oversample) * 0.000457777;
char outbuffer[255];
sprintf(outbuffer, "%f\n", floatVal);
usart1TXString(outbuffer);
char outbuffertwo[255];
sprintf(outbuffertwo, ",%ld\n", transform(combVal));
usart1TXString(outbuffertwo);

产生相同的错误结果。

584991570000.000000,117419

我也试过使用 dtostrf() 而不是 sprintf():

floatVal = ((double)tempResult / (double)oversample) * 0.000457777;
char outbuffer[255];
dtostrf(floatVal, 6, 4, outbuffer);
usart1TXString(outbuffer);

有效。但是一旦我尝试添加另一个值,它就会再次中断:

floatVal = ((double)tempResult / (double)oversample) * 0.000457777;
char outbuffer[255];
dtostrf(floatVal, 6, 4, outbuffer);
usart1TXString(outbuffer);
char outbuffertwo[255];
sprintf(outbuffertwo, ",%ld\n", transform(combVal));
usart1TXString(outbuffertwo);

返回 UART 输出的相同损坏值。

我尝试增加缓冲区的大小(即使它应该已经足够大了)。

我认为这不重要,但以防万一:

'tempResult' 是一个无符号的 64 位整数,它是从 ADC 接收到的 16 位值的累加。

'oversample' 是一个 16 位整数,它是指定的过采样量。目前 50.

'floatVal' 是双数。

'combVal' 是一个 32 位整数,包含一个 24 位 ADC 值,以二进制补码表示。

transform() 是一个函数,用来处理两个补码,看起来像这样:

int32_t transform(int32_t value)
    {
        if (value > MAX_VALUE)
        {
            value -= MODULO;
        }
        return value;
    }

损坏 %f 的 %ld 是怎么回事?

编辑:即使我只用一个包含正确数字的变量替换 transform(combVal),它仍然会破坏另一个值。

我发现了问题所在,这是我说我认为无关紧要的事情之一。具体是这样的:

'tempResult' is an unsigned 64 bit int that is an accumulation of 16 bit values received from an ADC.

来自 avr-libc 常见问题解答:

float and double are 32 bits (this is the only supported floating point format)

我将问题缩小到这一行:

floatVal = ((double)tempResult / (double)oversample) * 0.000457777;

因为如果我用这个替换它:

floatVal = 3.123456

一切如预期。

uint64_t tempResult; 更改为 uint32_t tempResult; 可以解决所有问题。我只需要不要对过采样太过分,一切都会好起来的。

长话短说:在 8 位 AVR 上滥用浮点数导致了 UB,这是 UB 喜欢做的事情(有时工作并导致其他人很困惑)。