1

我目前正在开发一个项目,该项目包括在 stm-8 微控制器上使用 linux 中的 SDCC 编译器进行裸机编程。芯片中的内存非常低,所以我试图保持精简。我已经使用了 8 位和 16 位变量,一切都很顺利。但最近我遇到了一个问题,我真的需要一个浮点变量。因此,我编写了一个函数,该函数将 16 位值转换为浮点数,并执行我需要的数学运算并返回一个 8 位数字。这导致我在 MCU 上的最终编译代码从 1198 字节变为 3462 字节。现在我明白了使用浮点是内存密集型的,并且可能需要调用许多函数来处理浮点数的使用,但是将程序的大小增加这么多似乎很疯狂。

规格:MCU stm8151f2 编译器:带有 --opt_code_size 选项的 SDCC

int roundNo(uint16_t bit_input) 
{ 
    float num = (((float)bit_input) - ADC_MIN)/124.0;
    return num < 0 ? num - 0.5 : num + 0.5; 
}
4

3 回答 3

2

要确定为什么代码在您的特定工具链上如此之大,您需要查看生成的汇编代码,并查看 FP 支持调用它的内容,然后查看映射文件以确定每个函数的大小。

作为使用 GCC 5.4.0 的Godbolt-Os for AVR 的示例(Godbolt 不支持 STM8 或 SDCC,因此这是作为 8 位架构进行比较),您的代码生成 6364 字节,而空函数生成 4081 字节。所以代码体所需的附加代码是 2283 字节。现在考虑到您同时使用不同的编译器和体系结构这一事实,这些与您的结果并没有什么不同。在生成的代码(下面)中rcall查看子例程的 s,例如__divsf3- 这些是大部分代码所在的位置,我怀疑 FP 除法是迄今为止更大的贡献者。

roundNo(unsigned int):
        push r12
        push r13
        push r14
        push r15
        mov r22,r24
        mov r23,r25
        ldi r24,0
        ldi r25,0
        rcall __floatunsisf
        ldi r18,0
        ldi r19,0
        ldi r20,0
        ldi r21,lo8(69)
        rcall __subsf3
        ldi r18,0
        ldi r19,0
        ldi r20,lo8(-8)
        ldi r21,lo8(66)
        rcall __divsf3
        mov r12,r22
        mov r13,r23
        mov r14,r24
        mov r15,r25
        ldi r18,0
        ldi r19,0
        ldi r20,0
        ldi r21,0
        rcall __ltsf2
        ldi r18,0
        ldi r19,0
        ldi r20,0
        ldi r21,lo8(63)
        sbrs r24,7
        rjmp .L6
        mov r25,r15
        mov r24,r14
        mov r23,r13
        mov r22,r12
        rcall __subsf3
        rjmp .L7
.L6:
        mov r25,r15
        mov r24,r14
        mov r23,r13
        mov r22,r12
        rcall __addsf3
.L7:
        rcall __fixsfsi
        mov r24,r22
        mov r25,r23
        pop r15
        pop r14
        pop r13
        pop r12
        ret

您需要对工具链生成的代码执行相同的分析才能回答您的问题。毫无疑问,SDCC 能够生成汇编列表和映射文件,这将允许您准确确定正在生成和链接的代码和 FP 支持。

最终,尽管在这种情况下您完全没有必要使用 FP:

int roundNo(uint16_t bit_input) 
{ 
  int s = (bit_input - ADC_MIN) ;
  s += s < 0 ? -62 : 62 ;
  return s / 124 ;
}

空函数相比,Godbolt 2283 字节。仍然有点大,但最有可能的问题是 AVR 缺少DIV指令所以调用__divmodhi4. STM8 具有DIV16 位除数和 8 位除数,因此在您的目标上它可能会明显更小(并且更快)。

于 2020-03-31T22:51:54.767 回答
0

好的,一个实际工作的定点版本:

// Assume a 28.4 format for math.  12.4 can be used, but roundoff may occur.

// Input should be a literal float (Note that the multiply here will be handled by the  
// compiler and not generate FP asm code. 
#define TO_FIXED(x) (int)((x * 16))

// Takes a fixed and converts to an int - should turn into a right shift 4.
#define TO_INT(x)   (int)((x / 16))

typedef int FIXED;
const uint16_t ADC_MIN = 32768;

int roundNo(uint16_t bit_input) 
{ 
  FIXED num = (TO_FIXED(bit_input - ADC_MIN)) / 124;
  num += num < 0 ? TO_FIXED(-0.5) : TO_FIXED(0.5);
  return TO_INT(num);
}

int main()
{
  printf("%d", roundNo(0));

  return 0;
}

请注意,我们在这里使用了一些 32 位值,因此它会大于您当前的值。不过要小心,如果可以仔细管理舍入和溢出,它可能会转换回 12.4(16 位 int)。

或者从网上获取一个更好的全功能定点库:)

于 2020-03-31T21:28:22.010 回答
0

(更新)写完这篇文章后,我注意到@Clifford 提到你的微控制器DIV本身就支持这个指令,在这种情况下这样做是多余的。无论如何,我将把它作为一个概念,它可以应用于DIV作为外部调用实现的情况,或者用于DIV需要太多周期并且目标是加快计算速度的情况。

无论如何,如果您需要挤压一些额外的周期,移位和添加可能比除法更快。因此,如果您从124几乎等于4096/33(误差因子为 0.00098,即 0.098%,因此小于千分之一)的事实开始,您可以通过单次乘法33和移位 12 位来实现除法(除以4096)。此外,33is 32+1,表示乘以33等于左移 5 并再次添加输入。

示例:您想除以5000124并且5000/124大约是。40.323. 我们将要做的是:

  1. 5,000 << 5 = 160,000
  2. 160,000 + 5,000 = 165,000
  3. 165,000 >> 12 = 40

请注意,这只适用于正数。另请注意,如果您确实在整个代码中进行了大量乘法运算,那么从长远来看,使用单个 externmuldiv函数可能会导致整体代码更小,尤其是在编译器不是特别擅长优化的情况下。如果编译器可以在DIV这里发出一条指令,那么你唯一能得到的就是速度的一点点提高,所以不要为此烦恼。

#include <stdint.h>
#define ADC_MIN 2048

uint16_t roundNo(uint16_t bit_input) 
{ 
    // input too low, return zero
    if (bit_input < ADC_MIN)
        return 0;

    bit_input -= (ADC_MIN - 62);
    uint32_t x = bit_input;

    // this gets us x = x * 33        
    x <<= 5;
    x += bit_input;

    // this gets us x = x / 4096
    x >>= 12;

    return (uint16_t)x;
}

具有大小优化的 GCC AVR 会产生this,即所有对 extern mul 或 div 函数的调用都消失了,但似乎 AVR 不支持在一条指令中移动多个位(它发出分别移动 5 次和 12 次的循环)。我不知道你的编译器会做什么。

如果您还需要处理bit_input < ADC_MIN此案,我会单独处理这部分,即:

#include <stdint.h>
#include <stdbool.h>
#define ADC_MIN 2048

int16_t roundNo(uint16_t bit_input) 
{ 
    // if subtraction would result in a negative value,
    // handle it properly
    bool negative = (bit_input < ADC_MIN);
    bit_input = negative ? (ADC_MIN - bit_input) : (bit_input - ADC_MIN);

    // we are always positive from this point on
    bit_input -= (ADC_MIN - 62);

    uint32_t x = bit_input;
    x <<= 5;
    x += bit_input;
    x >>= 12;

    return negative ? -(int16_t)x : (int16_t)x;
}
于 2020-04-01T08:55:04.787 回答