我浪费了几天与我的16mhz 8bit AVR(mega 2560)作战。
目标是规范我接收的值(加速度计,磁力计等) 这些值是16位有符号(int16),之后我想要一个0.0f-1.0f的浮点数 我用这个用于3D IMU。
常用方法:
int32_t tmp = (int32_t)a*a+b*b+c*c;
float magnitude = sqrt(tmp);
float a_v = a / magnitude;
float b_v = b / magnitude;
float c_v = c / magnitude;
更快的方法:
int32_t tmp = (int32_t)a*a+b*b+c*c;
float imagnitude = InvSqrt(tmp); // like the 'tricky' one for ID software quake source
float a_v = a * imagnitude;
float b_v = b * imagnitude;
float c_v = c * imagnitude;
第二个有一些优点,因为它使用aproximiation而不是1 / sqrt(但也有aproximated sqrts)并且它需要3次乘法而不是分段,这是好的,因为AVR支持MUL而不是DIV。 另一方面,由于浮点和32位计算,它无论如何都非常慢。
这样的函数通常需要1-2毫秒,这对我的代码产生了巨大的影响,它试图在一个应该持续最多2.4ms的周期中解决许多额外的任务和规范化
我经常挖掘并尝试了许多不同的近似和想法,但无论我尝试过什么,代码执行起来都太慢了。
也许还有另一种方法来规范我的传感器值。
有特殊问题(加速计量级)的人更新 没有floting point和sqrt我现在正在这个工作:(忽略额外的演员:) int16 cal []保存3轴的校准加速度计值。
int16 average_sq_1g = CONST_1G / 256;
uint32_t work = (int32_t)((int32_t)cal[0]*cal[0] + (int32_t)cal[1]*cal[1] + (int32_t)cal[2]*cal[2])/256;
work = work * 100L / average_sq_1g;
attitude.acc_magnitude = work;
这对我的事业非常专业,因为我正在努力获得加速度,我知道1G(大约16000)的价值 因此,公式(X ^ 2 + Y ^ 2 + Z ^ 2)* 100 / 1G ^ 2返回幅度(100 =没有额外的加速度,可以不使用浮点数。) 我没有检查性能差异,但它应该快得多。
答案 0 :(得分:3)
固定点肯定是要走的路。唯一的困难是保持精度。
在这种情况下 - 如果精度很重要 - 我将使用以下相当快的算法:
这可能听起来很复杂,但它可以实现,例如,
sign = b3 & 0x80;
exponent = b3 << 1;
if (b2 & 0x80)
exponent |= 1;
else
b2 |= 0x80;
mantissa = join_to_word(b1, b2);
其中b0
.. b3
是浮点数的单个八位字节(b3
是带符号的那个,请参阅IEEE754浮点结构)。 b2
乘以0x80是由浮点表示中的隐藏位引起的。函数join_to_word
是将两个字节组合成一个单词的东西。这不应该导致机器代码中的单个指令,因为只有编译器需要知道两个八位字节所在的位置。 (实现这一目标的一种方法是使用工会。)
现在我们知道了指数:
所以,此时我们有:
整数域中剩下的是通过将绝对值向左移15位并将它们除以范数来执行除法。然后将得到的矢量按2 ^ 16缩放。
然后我们转换到左边的浮点数。每个数字的基本步骤
整个算法应该在几百个时钟周期内运行。
如果你真的很匆忙,首先要看的是汇编代码。可能存在不必要的库调用,不必要的零字节等,具体取决于您编写代码的方式以及C编译器认为或不认为的内容。第二步是在汇编中编写这个例程,但通常可以避免。
但有一点需要考虑:是否绝对有必要使用浮点数?它们很慢。
答案 1 :(得分:2)
使用修复点算术。
按合理的位数缩放输入并使用整数运算。 (还有整数sqare根计算的算法)
例如,如果你的值范围从-10m到10m并且要求至少mm分辨率,我会添加11位(用2048缩放)
#define VEC_SHIFT 11
#define VEC_SCALE (1 << (VEC_SHIFT))
int16_t a = 7 * VEC_SCALE;
int16_t b = 3 * VEC_SCALE;
int16_t c = 10 * VEC_SCALE;
// calculations have to be done in larger data type so they do not overflow
int32_t snorm = (int32_t)a * a + (int32_t)b*b + (int32_t)c*c; // snorm now is scaled by VEC_SCALE*VEC_SCALE (2*VEC_SHIFT)
int16_t norm = intsqrt(snorm); // norm is scaled with VEC_SCALE
// since norm and a,b,c is in VEC_SCALE, you have to scale up the divident so that one VEC_SCALE is chanceled out by division
int16_t as = (((int32_t)a) * VEC_SCALE )/norm;
int16_t bs = (((int32_t)b) * VEC_SCALE )/norm;
int16_t cs = (((int32_t)c) * VEC_SCALE )/norm;