我目前正在从事一个项目,其中包括使用linux中的SDCC编译器在stm-8微控制器上进行裸金属编程。芯片中的内存很低,所以我试图让事情变得更精简。我用了8位和16位变量,一切都进行得很顺利。但最近我遇到了一个问题,我真的需要一个浮动变量。所以我写了一个函数,它接收一个16位的值,转换成一个浮点数,做我需要的数学运算,然后返回一个8位的数字。这使得我在MCU上的最后编译代码从1198字节上升到3462字节。现在,我知道使用浮点数是内存密集型的,可能需要调用许多函数来处理浮点数的使用,但是将程序的大小增加那么大似乎很疯狂。我想要一些帮助,了解这是为什么和发生了什么。
规格: MCU stm8151f2编译器: SDCC具有--opt_code_size选项
int roundNo(uint16_t bit_input)
{
float num = (((float)bit_input) - ADC_MIN)/124.0;
return num < 0 ? num - 0.5 : num + 0.5;
}发布于 2020-03-31 22:51:54
要确定为什么特定工具链上的代码如此之大,您需要查看生成的程序集代码,并查看它所调用的FP支持,然后查看映射文件以确定每个函数的大小。
例如,在哥德波特 for AVR上,使用GCC 5.4.0和-Os (Godbolt不支持STM8或SDCC,因此这是作为一个8位体系结构进行比较),您的代码生成6364字节,而空函数为4081字节。因此,代码体所需的附加代码为2283字节。现在,考虑到您使用的是不同的编译器和架构,它们与您的结果并没有太大的不同。请参阅生成的代码(下面)中的rcall到子程序(如__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 ;
}与空函数相比,在哥德波特 2283字节处。仍然有点大,但最有可能的问题是,AVR缺少DIV指令,因此称为__divmodhi4。STM8有一个16位红利和8位除数的DIV,因此它很可能会更小(更快)。
发布于 2020-03-31 21:28:22
好的,一个实际工作的不动点版本:
// 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)。
或者从web抓取一个更好的完整功能定点库:)
发布于 2020-04-01 08:55:04
(Update)写完这篇文章后,我注意到@Clifford提到您的微控制器本机支持这个DIV指令,在这种情况下,这样做是多余的。无论如何,我将把它保留为一个概念,它可以应用于DIV作为外部调用实现的情况,或者对于DIV周期过长、目标是使计算速度更快的情况。
无论如何,如果你需要挤出一些额外的周期,移动和加法可能比除法更快。因此,如果您从124几乎等于4096/33这一事实开始(误差因子为0.00098,即0.098%,小于千分之一),则可以使用33和移位12位(除以4096)实现除法。此外,33是32+1,这意味着乘以33等于左移5并再次添加输入。
示例:您希望将5000除以124,而5000/124是接近的。40.323。我们要做的是:
注意,这只适用于正数。还注意到,如果您真的在代码中执行大量乘法操作,那么具有单个extern mul或div函数从长远来看可能会导致更小的整体代码,特别是在编译器不擅长优化的情况下。如果编译器可以在这里发出一个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产生这,即所有对extern mul或div函数的调用都没有了,但似乎不支持在一条指令中移动多个位(它发射的循环分别移动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;
}https://stackoverflow.com/questions/60958440
复制相似问题