ARM CMSIS DSP库函数arm_sin_cos_f32的BUG
王强
2016-05-10
本人从事电力电子产品的研发,使用的是STM32F4系列的CPU,带浮点运行,进行park变换或逆变换的时候,需要用到sin和cos,为了方便就采用了arm_sin_cos_f32这个函数。
本文为原创,未经作者允许不得转载。
1. BUG发现
输出的电流会突然出现一个很大负向电流,已经在PC上通过C语言仿真,不会出现算法上的错误,把问题定位到角度到了,因为角度是反馈算出来的,可能会出问题,通过打印却看到sin和cos的值不在-1到1范围,判定是arm_sin_cos_f32这个函数造成的,并不是角度异常造成的,而且角度值为-720度。
2. 源代码分析
遇到上面的问题后,在ARM CMSIS官网上下载了源代码,找到arm_sin_cos_f32这个函数的源代码如下:
void arm_sin_cos_f32(
float32_t theta,
float32_t * pSinVal,
float32_t * pCosVal)
{
float32_t fract, in; /* Temporary variables for input, output */
uint16_t indexS, indexC; /* Index variable */
float32_t f1, f2, d1, d2; /* Two nearest output values */
int32_t n;
float32_t findex, Dn, Df, temp;
/* input x is in degrees */
/* Scale the input, divide input by 360, for cosine add 0.25 (pi/2) to read sine table */
in = theta * 0.00277777777778f;
/* Calculation of floor value of input */
n = (int32_t) in;
/* Make negative values towards -infinity */
if(in < 0.0f)
{
n--;
}
/* Map input value to [0 1] */
in = in - (float32_t) n;
/* Calculation of index of the table */
findex = (float32_t) FAST_MATH_TABLE_SIZE * in;
indexS = ((uint16_t)findex) & 0x1ff;
indexC = (indexS + (FAST_MATH_TABLE_SIZE / 4)) & 0x1ff;
/* fractional value calculation */
fract = findex - (float32_t) indexS;
/* Read two nearest values of input value from the cos & sin tables */
f1 = sinTable_f32[indexC+0];
f2 = sinTable_f32[indexC+1];
d1 = -sinTable_f32[indexS+0];
d2 = -sinTable_f32[indexS+1];
Dn = 0.0122718463030f; // delta between the two points (fixed), in this case 2*pi/FAST_MATH_TABLE_SIZE
Df = f2 - f1; // delta between the values of the functions
temp = Dn*(d1 + d2) - 2*Df;
temp = fract*temp + (3*Df - (d2 + 2*d1)*Dn);
temp = fract*temp + d1*Dn;
/* Calculation of cosine value */
*pCosVal = fract*temp + f1;
/* Read two nearest values of input value from the cos & sin tables */
f1 = sinTable_f32[indexS+0];
f2 = sinTable_f32[indexS+1];
d1 = sinTable_f32[indexC+0];
d2 = sinTable_f32[indexC+1];
Df = f2 - f1; // delta between the values of the functions
temp = Dn*(d1 + d2) - 2*Df;
temp = fract*temp + (3*Df - (d2 + 2*d1)*Dn);
temp = fract*temp + d1*Dn;
/* Calculation of sine value */
*pSinVal = fract*temp + f1;
}
当theta = -720时,in = 1, findex = 512.0,indexS = 0,fract = 512。
上面的函数采用的方法是线性插值的方法,所以fract应该只在0到1,所以产生了上面的BUG。
3. 对应方法
Arm_sin_cos_f32这个函数的输入范围是-180到180,但官网上也写了,如果超出这个范围,函数将自动调整回来,所以我的代码中没有对输入范围有限制,比较在-720度时出现了这个问题,仔细分析代码,如果在-180到180之间是不会出问题的。那么解决方法就是将输入限制在-180到180之间就可以了。
4. 总结
权威也不一定权威。
追加:
上面的应对方法还是会存在BUG,因为还有一个BUG点就是-0.000001,这个时候也会造成in = 1,从而使fract = 512。所以将输入角度设置为-180到180也是有问题的,必须检测0点附近,如果在0附近时,强制给一个0。但看代码发现,正数是都不会出问题,所以原则上把角度范围限制在[0~无穷大),也是可行的。以上的方法都是通过规避这个BUG来解决问题,当然也有修复的办法,这里给出一种,在下面这行代码
/* Map input value to [0 1] */
in = in - (float32_t) n;
在代码中找到这行代码,在代码下面加入以下代码,
if ( in >= 1 )
{
in = 0;
}
这样应该可以彻底解决,而且对角度也没有限定了。大家可以把上面的代码直接放在自己的工程中,当然函数名需要修改下,比如my_sin_cos_f32,头文件也需要追加
#include "../../DSP_LIB/include/Arm_math.h"
#include "../../DSP_LIB/include/Arm_common_tables.h"
测试了下,0点附近不会再出问题了。