Return 值不符合预期。 AVR C计算

Return value not as expected. AVR C calculation

我正在尝试使用 ATmega328P MCU 执行以下计算。

= 1000 · 0 + 2000 · 1 +⋯+ 8000 · 7 / 0+1+⋯+7

在主例程中(如此处所示):

int main(void)
{
    //variables
    uint16_t raw_values[8];
    uint16_t position = 0;
    uint16_t positions[8];
    char raw[] = " raw";
    char space[] = ", ";
    char channelString[] = "Channel#: ";
    char positionString[] = "Position: ";

    //initialize ADC (Analog)
    initADC();

    //initialize UART
    initUART(BAUD, DOUBLE_SPEED);

    //give time for ADC to perform & finish 1st conversion
    //8us x 25 = 200us
    delay_us(200);

    while(1)
    {
        //get the raw values from the ADC for each channel
        for(uint8_t channel = 0; channel < 8; channel++)
        {
            raw_values[channel] = analog(channel);

            //invert the raw value
            raw_values[channel] = DIVISOR - raw_values[channel];
        }

        for(uint8_t channel = 0; channel < 8; channel++)
        {
            //print the channel#
            transmitString(channelString);
            printDec16bit(channel);
            transmitString(space);

            //print the raw value from the ADC conversion
            printDec16bit(raw_values[channel]);
            transmitString(raw);
            transmitString(space);

            //calculate the position value at each sensor
            transmitString(positionString);
            positions[channel] = (uint16_t)((POSITION_REF/DIVISOR) * raw_values[channel]);
            printDec16bit(positions[channel]);
            printCR();
        }

        printCR();

        //calculate and display 'position'
        position = calculatePosition(positions);
        printDec16bit(position);
        printCR();
        printCR();


        //add a delay
        delay_ms(2000);
    }
}

我正在调用以下函数,但我得到的 return 值有很大偏差。

uint16_t calculatePosition(uint16_t* channel_positions)
{
    uint32_t intermediates[8];
    uint32_t temp_sum = 0;
    uint16_t divisor = 0;
    uint16_t value = 0;

    for(uint8_t i = 0; i < 8; i++)
    {
        intermediates[i] = channel_positions[i] * ((i + 1) * 1000);
    }

    for(uint8_t j = 0; j < 8; j++)
    {
        temp_sum = temp_sum + intermediates[j];
    }

    for(uint8_t k = 0; k < 8; k++)
    {
        divisor = divisor + channel_positions[k];
    }

    value = temp_sum/divisor;

    return value;
}

或者,我什至尝试过这段代码,得到的结果不是我所期望的。

uint16_t calculatePosition(uint16_t* channel_positions)
{
    uint16_t position;
    position = ((1000 * channel_positions[0]) + 
                (2000 * channel_positions[1]) + 
                (3000 * channel_positions[2]) + 
                (4000 * channel_positions[3]) + 
                (5000 * channel_positions[4]) + 
                (6000 * channel_positions[5]) + 
                (7000 * channel_positions[6]) + 
                (8000 * channel_positions[7])) /
                (channel_positions[0] + 
                 channel_positions[1] + 
                 channel_positions[2] + 
                 channel_positions[3] + 
                 channel_positions[4] + 
                 channel_positions[5] + 
                 channel_positions[6] + 
                 channel_positions[7]);
    return position;
}

我做错了什么?对于诸如 {15, 12, 5, 16, 11, 35, 964, 76} 之类的值数组,我期望得到 6504 的结果,但我得到的是 200 的值(或其他一些奇怪的值)。

查看您的输入数组:{15, 12, 5, 16, 11, 35, 964, 76}

具体来说,查看 964 的元素。该元素乘以 7000 是 6748000,这大于 uint16_t 可以处理。

有多种解决方案。其中之一正在更改为 uint32_t。如果这不是一个选项,您可以提取因子 1000,如下所示:

position = 1000 *(
            ((1 * channel_positions[0]) + 
            (2 * channel_positions[1]) + 
            (3 * channel_positions[2]) + 
            (4 * channel_positions[3]) + 
            (5 * channel_positions[4]) + 
            (6 * channel_positions[5]) + 
            (7 * channel_positions[6]) + 
            (8 * channel_positions[7])) /
            (channel_positions[0] + 
             channel_positions[1] + 
             channel_positions[2] + 
             channel_positions[3] + 
             channel_positions[4] + 
             channel_positions[5] + 
             channel_positions[6] + 
             channel_positions[7]));

请注意,这不会消除问题,但可能会减少问题,以便在合理的输入下不会出现问题。

对循环版本采用相同的思路,我们得到:

uint16_t calculatePosition(uint16_t* channel_positions)
{
    uint16_t temp_sum = 0;
    uint16_t divisor = 0;

    for(uint8_t i = 0; i < 8; i++) {
        temp_sum  += (channel_positions[i] * (i+1));
        divisor += channel_positions[i];
    }

    return 1000*(temp_sum/divisor);
}

请注意,由于使用整数除法进行舍入,您在此过程中会损失一些准确性。由于您在指定宽度时非常小心,我假设您不愿意更改输入数组的类型。此代码应该以最少的额外内存使用量为您提供最大的准确性。但是,如果您 运行 此函数经常在 16 位计算机上运行,​​它会对性能产生相当大的影响。

uint16_t calculatePosition(uint16_t* channel_positions)
{
    // Use 32 bit for these
    uint32_t temp_sum = 0;
    uint32_t divisor = 0;

    for(uint8_t i = 0; i < 8; i++) {
        // Copy the value to a 32 bit number
        uint32_t temp_pos = channel_positions[i];
        temp_sum  += temp_pos * (i+1);
        divisor += temp_pos;
    }

    // Moved parenthesis for better accuracy
    return (1000*temp_sum) / divisor;
}

如果结果可以放在 uint16_t 中,这个版本失败的可能性绝对为零,因为 1000*temp_sum 的最大可能值是 2,359,260,000 并且最大值它可以容纳 4,294,967,295.

关于 MRE 的旁注(最小的、可重现的示例)

MRE:s 描述如下:https://whosebug.com/help/minimal-reproducible-example

在这个例子中,问题中 post 的主要函数是:

#include <stdio.h>

int main() 
{ 
    uint16_t positions[] = {15, 12, 5, 16, 11, 35, 964, 76}; 
    uint16_t pos = calculatePosition(positions); 
    printf("%d\n", pos); 
}

足以证明您遇到的问题,仅此而已。

正如所说,问题出在整数溢出上。

在使用整数数学时,将乘数移到外面时要小心! (A * 1000) / B 不等于 (A / B) * 1000.

最简单的解决方案,将每个操作中的第一个操作数转换为更宽的类型。其他人将被隐式转换。 E.q.

    ...
    position = ((1000UL * channel_positions[0]) + 
                (2000UL * channel_positions[1]) + 
                (3000UL * channel_positions[2]) + 
                (4000UL * channel_positions[3]) + 
                (5000UL * channel_positions[4]) + 
                (6000UL * channel_positions[5]) + 
                (7000UL * channel_positions[6]) + 
                (8000UL * channel_positions[7])) /
                ((uint32_t)channel_positions[0] + 
                 channel_positions[1] + // no need to convert, it will be converted implicitly
                 channel_positions[2] + // since previous operand is wider
                 channel_positions[3] + 
                 channel_positions[4] + 
                 channel_positions[5] + 
                 channel_positions[6] + 
                 channel_positions[7]);