您当前的位置: 首页 >  数学

张巧龙

暂无认证

  • 3浏览

    0关注

    1208博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

单片机如何能运行如飞?一种高效实现数学函数的方式!

张巧龙 发布时间:2022-01-22 12:04:05 ,浏览量:3

今天给大家分享一下如何在资源紧张,算力较低的单片机上实现三角函数的算法。

下面我们来简单介绍一下整体的思路吧,因为硬件平台的资源比较紧张;

  • RAM比较少;

  • ROM比较少;

  • CPU处理速度比较慢;

所以这里比较常用的方法就是通过空间换时间,预先将sincos的值存储到数组中,需要用的时候,访问数组就可以得到具体的数据。这也就是我们经常会提到的查表法。

下面我们来详细介绍一下。

正弦表

这个正弦函数表达式是这样的,

具体如下图所示;

5ca2a6f19c5c81274d5a5e20e06d6822.png 正弦波

首先我们来简单分析一下这个波形:

  • 在蓝色框内是一整个周期的波形;

  • 在红色框内是四分之一个周期的波形;

其实不难发现,我们只要表示出这四分之一个波形的数据,其余剩下的波形都可以通过换算表示出来。

这样做就大大节省了查表法所需要的空间。

下面我们来介绍一下具体如何实现;

首先我们得搞清楚一个点,就是量纲,统一用归一化的形式来做。

  • y的范围是 [-1, 1]

  • x的范围是[0, 2π],当然,x的范围[-π, π]也是没问题的,下面会继续介绍;

而在实际的程序中,我们是无法这样去做的,这些数值我们期望通过整形类型去访问,所以我们要做到几点:

  • 尽量避免使用浮点运算;

  • 尽量避免除法;

  • 尽量避免乘法;

所以这里有必要先了解一下Q格式,用左移和右移去代替乘法和除法,提高运算效率;

对于X轴的数据,于是可以将[0, 2π]细分成 128 ,256,512或者 1024 等等;

这里我们先细分成1024等份,正如前面提到的,只需要选择前四分之一周期的内容即可;

#define POINT_NUM  256
 #define PI          3.141592f
 for (int i = 0; i  6;
 /**
  | hAngle    | angle  | std   |
  | (0,16384]   | U0_90  | (0,0.5] |
  | (16384,32767]  | U90_180  | (0.5,0.99]|
  | (-16384,-1]   | U270_360  | (0,-0.5]  |
  | (-16384,-32768] | U180_270  | (-0.5,-1) |
 */
//SIN_MASK        0x0300u
 switch ( ( uint16_t )( uhindex ) & SIN_MASK )
 {          
//0x0200u
   case U0_90:
  Local_Components.hSin = 
            hSin_Cos_Table[( uint8_t )( uhindex )];
  Local_Components.hCos = 
            hSin_Cos_Table[( uint8_t )( 0xFFu - ( uint8_t )( uhindex ) )];
  break;
//0x0300u
   case U90_180:
  Local_Components.hSin = 
            hSin_Cos_Table[( uint8_t )( 0xFFu - ( uint8_t )( uhindex ) )];
  Local_Components.hCos = 
            -hSin_Cos_Table[( uint8_t )( uhindex )];
  break;
//0x0000u
   case U180_270:
  Local_Components.hSin = 
            -hSin_Cos_Table[( uint8_t )( uhindex )];
  Local_Components.hCos = 
            -hSin_Cos_Table[( uint8_t )( 0xFFu - ( uint8_t )( uhindex ) )];
  break;
//0x0100u
   case U270_360:
  Local_Components.hSin =  
            -hSin_Cos_Table[( uint8_t )( 0xFFu - ( uint8_t )( uhindex ) )];
  Local_Components.hCos =  
            hSin_Cos_Table[( uint8_t )( uhindex )];
  break;
   default:
  break;
 }
 return ( Local_Components );
}

由于输入的hAngleQ1.15格式,所以这里可以简单画个图;下面是角度hAngle0x0000~0xFFFF的示意图,如下所示;

64e6ef237056e31b0a6bf12a7480b7cc.png 角度值

这里注意,负数是以补码形式进行保存的,正数的补码等于他本身;

负数的补码是除了符号位外,其他位取反,然后加上1;

所以可以算一下 0xFFFF表示-1

0x8000表示 -32768

因为Q格式中有无符号的范围和带符号的范围,所以这里的hAngle充分利用这个16 bit的数据,并且兼容了传入参数可以是有符号int16或者是无符号uint16,这里比较绕,先看下面这张图片;

0a2e989d464652526cc941696f20d0c5.png 有符号和无符号 对比

上图中;

  • 左边是有符号int16,右边是无符号数uint16

  • 两个圆形分别表示int16uint16的数值范围;

  • 左边绿色框内的波形相对应,橙色框内的波形相对应;

这里有几点我们要注意一下,无论是有符号和无符号,他们的周期都是相同的;

  • 有符号整数 int16 :-32768 ~ 32765 ,

  • 无符号整数 uint16 :0 ~ 65535,

所以这两者都使用 65536个数来表示正弦的一个周期,也就是 2π。

这里是比较关键的地方,因此对于 0x8000 这个关键点,有符号和无符号所表示的数值是不同的;

  • 有符号整数 int16 :0x8000 表示为 -32768;

  • 无符号整数 uint16 :0x8000 表示为 32768;

因此这他们刚好相差了一个周期 65536,所以表示的正弦数值y是相同的,正如上图中蓝色箭头①和②所示。

内部实现

由于有符号整数 int16 的最高位是符号位,所以这里我们先把它转化成无符号整形;

前面用 int32类型是为了防止数据溢出,这里加上32768,相当于对正弦波平移了半个周期,所以在下面y和x的映射关系需要根据实际情况来修改;

/* 10 bit index computation  */
shindex = ( ( int32_t )32768 + ( int32_t )hAngle );
uhindex = ( uint16_t )shindex;
//uhindex /= ( uint16_t )64;
uhindex = uhindex >> 6;

因为前面提高过正弦表的四分之一是256个数据,所以整个正弦周期应该是 1024 个细分数据,那也就是2的10次,就需要 10 bit;

  • 10 bit的数据范围是 0~1023;

  • 16 bit的数据范围是 0~65535;

为了获取有效的高10 bit数据,对数据右移 6 bit,具体如下所示;

ac5e447b4dceff9677fe1cd5db322853.png

所以,我们又可以得到以下这个数据的范围 0 ~ 10230 ~ 0x400

a93a0212d86196dd20b7113ca9d8f7e0.png

因此我们在程序中引入四个掩码,作为正弦波形落在哪个象限的标识位,这样也避免了使用除法运算,提高了效率,具体如下所示;

#define SIN_MASK        0x0300u
#define U0_90           0x0200u
#define U90_180         0x0300u                  
#define U180_270        0x0000u
#define U270_360        0x0100u

其中,U0_90表示 0° ~ 90°,以此类推;

那为什么是这个映射关系呢?

0~90°不应该是从 0x000u~0x100u吗?这里我们再简单解释一下;

前面有一个这样的操作,具体如下;

shindex = ( ( int32_t )32768 + ( int32_t )hAngle );
uhindex = ( uint16_t )shindex;

这里的hAngle加上32768,相当于加了一个π,正弦波形向左移动了半个周期;因此整体的映射关系要和原始的数据对应起来,具体如下所示;

ad95d7563231956b5e04952144a804e5.png

最后,既然我们已经知道波形在哪个象限了,就可以根据当前象限和我们正弦表的关系来得到新的波形,这里有中心对称,关于y轴对称,简单做一下变换就可以得到正弦值和余弦值;

//SIN_MASK        0x0300u
 switch ( ( uint16_t )( uhindex ) & SIN_MASK )
 {          
//0x0200u
   case U0_90:
  Local_Components.hSin = 
            hSin_Cos_Table[( uint8_t )( uhindex )];
  Local_Components.hCos = 
            hSin_Cos_Table[( uint8_t )( 0xFFu - ( uint8_t )( uhindex ) )];
  break;
//0x0300u
   case U90_180:
  Local_Components.hSin = 
            hSin_Cos_Table[( uint8_t )( 0xFFu - ( uint8_t )( uhindex ) )];
  Local_Components.hCos = 
            -hSin_Cos_Table[( uint8_t )( uhindex )];
  break;
//0x0000u
   case U180_270:
  Local_Components.hSin = 
            -hSin_Cos_Table[( uint8_t )( uhindex )];
  Local_Components.hCos = 
            -hSin_Cos_Table[( uint8_t )( 0xFFu - ( uint8_t )( uhindex ) )];
  break;
//0x0100u
   case U270_360:
  Local_Components.hSin =  
            -hSin_Cos_Table[( uint8_t )( 0xFFu - ( uint8_t )( uhindex ) )];
  Local_Components.hCos =  
            hSin_Cos_Table[( uint8_t )( uhindex )];
  break;
   default:
  break;
 }

fe1350054263ecd13db44ad84c136ef8.png

搞硬件,别吹牛了,好好做个规划!

1599f982609606bf44541c8ecb8de12f.png

如果再写for循环,我就锤自己!

c901750facd8c5bd10b023bef645f668.png

这是通信协议最"赤裸"的时刻!

d11b66ba04ab1ff5d62bdd8d5e3dc33a.png

读书到底为了什么,读研到底值不值?

关注
打赏
1665727216
查看更多评论
立即登录/注册

微信扫码登录

0.0497s