AVR的有效逆(1 / x)


12

我试图找到一种有效的方法来计算AVR上的逆(或近似)。

我正在尝试计算步进电机的脉冲周期,以便可以线性地改变速度。周期与速度(p = K/v)的倒数成正比,但我想不出一种快速计算此速度的好方法。

我的公式是

p = 202/v + 298; // p in us; v varies from 1->100

在Arduino上测试时,似乎完全忽略了该部分,而将其p固定为固定298(尽管在avr-gcc中可能会有所不同)。我还尝试过对v循环求和,直到超过202,然后对循环计数,但这非常慢。

我可以生成一个查找表并将其存储在闪存中,但是我想知道是否还有另一种方法。

编辑:也许标题应该是“有效划分” ...

更新:正如pingswept指出的那样,我将周期映射到速度的公式不正确。但是主要的问题是除法运算。

编辑2:在进一步调查中,除法在arduino上起作用,问题是由于上面的公式不正确以及其他地方的int溢出。


2
v是整数还是浮点数?
mjh2007'9

一个整数,但由于它给了我们一个周期,因此整数除法在此足够准确。
彼得·吉布森

如果您确实关心速度,则可以预先计算100个整数的值,并制作一个预分频器查找表以进行乘法运算。当然,需要进行内存折衷。
RYS 2015年

Answers:


7

关于除法的一件好事是,或多或少每个人都在做。这是C语言的核心功能,即使微控制器没有硬件除法指令,诸如AVR-GCC(由Arduino IDE调用)之类的编译器也会选择可用的最佳除法算法。

换句话说,除非您有一个非常奇怪的特殊情况,否则您不必担心如何实现除法。


如果您确实担心,那么您可能会喜欢阅读Atmel官方建议的除法算法(一种针对代码大小进行了优化的算法,另一种针对执行速度进行了优化的算法;都不占用任何数据存储空间)。他们在:

http://www.atmel.com/dyn/resources/prod_documents/doc0936.pdf

这是Atmel页面上列出的应用笔记“ AVR200:乘法和除法例程”,因为其(合理的大)Atmega处理器,例如标准Arduinos中使用的Atmega 168和Atmega 328。数据表和应用笔记列表位于:

http://www.atmel.com/dyn/products/product_card.asp?part_id=4720


4

在我看来,您所需要的只是一个100个条目的查找表。没有比这快得多的了。

#define VALUE_FOR_V_EQUALS_ZERO 0
uint16_t formula_lookup[100] = {VALUE_FOR_V_EQUALS_ZERO, 500, 399, 365, 348, ..., 300};

...

//"calculate" formula
p = formula_lookup[v > 67 ? 67 : v];

实际上,您只编辑一个68值查找表,因为v大于67的值总是计算为300。


正如我在问题中说的那样,我想知道是否还有另一种方式
Peter Gibson


3

您的函数似乎无法提供所需的结果。例如,值50大约返回302,而值100大约返回300。这两个结果将几乎不会改变电动机的速度。

如果我对您的理解正确,那么您实际上是在寻找一种将数字1-100映射到300-500(大约)范围的快速方法,例如1映射到500,而100映射到300。

也许尝试:p = 500-(2 * v)

但是我可能会误会-您是否要计算恒定频率方波的导通时间?298是什么?


是的,谢谢,公式是错误的。关键是要通过在每个时间间隔内以恒定的速度(例如速度++)改变目标速度,来从步进电机的输出获得线性加速度。这必须映射到+ ve边沿发送到步进电机控制器的周期(频率)-因此,反比关系(p = 1 / v)。
彼得·吉布森

您是指恒定加速度,即线性增加的速度吗?
2010年

啊,是的,持续不断的加速,我最初写问题时就把它搞糊涂了,也记得在那里也解决了它
Peter Gibson

3

近似划分的一种有效方法是轮班制。例如,如果x = y / 103; 除以103等于乘以0.0097087,因此要对此进行近似估算,请选择一个“好”移位数(即以2为底的数字,2、4、8、16、32等)

对于此示例,1024是一个很好的选择,因为我们可以说10/1024 = 0.009765,然后可以进行编码:

x =(y * 10)>> 10;

当然要记住确保变量y在相乘时不会溢出。它不精确,但快速。


这与timrorr提供的链接中的技术相似,并且可以很好地用于除以常数,但不适用于在编译时除以未知值的情况。
彼得·吉布森

3

另外要注意的是,如果您尝试在不支持除法的CPU上进行除法,那么在此Wiki文章中有一种非常酷的方法。

http://en.wikipedia.org/wiki/Multiplicative_inverse

为了近似x的倒数,仅使用乘法和减法,就可以猜出一个数y,然后用2y-xy2重复替换y。一旦y的变化变得足够小(并保持),y就是x倒数的近似值。


有趣的是,我想知道与其他方法相比,
彼得·吉布森

1

尽管此过程可能需要一些移植,但此处看起来对mcu友好。

尽管看起来LUT会更容易。您只需要100字节,如果使用一些插值,则需要更少的字节,并且由于LUT充满了常量,因此编译器甚至可以将其定位在代码区域而不是数据区域中。


我尝试过类似的方法来对除数求和,直到等于或超过除数,但发现它相当慢。看起来LUT将成为一种方法-使用avr-gcc,您需要<avr / progmem.h>中的特殊宏以将其存储在闪存中。
彼得·吉布森

1

检查以确保除法被执行为浮点数。我使用Microchip而不是AVR,但是使用C18时,您需要强制将文字视为浮点数。例如。尝试将公式更改为:

p = 202.0/v + 298.0;


1

您想要快点,所以这里走吧.....由于AVR无法高效地进行规范化(向左移动,直到您无法再移位),因此请忽略任何伪浮点算法。在AVR中进行非常精确和最快的整数除法的最简单方法是通过相互查找表。该表将存储按比例缩放的倒数(例如2 ^ 32)。然后,您在汇编器中实现了unsigned32 x unsigned32 = unsigned 64乘法,因此答案=(分子* inverseQ32 [denominator])>>32。
我使用内联汇编器实现了乘法函数(包装在ac函数中)。GCC确实支持64位“ long long”,但是,要得到结果,您必须将64位乘以64位,而不是32位32位的C语言所致,而不是32x32 = 64。

这种方法的缺点是,如果要除以1到4096之间的整数,将使用4K x 4 = 16K的Flash。

现在,在C中以大约300个周期实现了非常精确的无符号除法。

您可以考虑使用24位或16位可缩放整数,以提高速度,降低准确性。


1
p = 202/v + 298; // p in us; v varies from 1->100

您的方程式的返回值已经是p=298因为编译器先除法然后加法,请使用整数muldiv分辨率,即:

p = ((202*100)/v + (298*100))/100 

使用此方法是相同的乘法a*f,其中a =整数f =分数。

那样会产生收益,r=a*ff=b/c后来r=a*b/c却不起作用,因为运算符的位置会产生final r=(a*b)/c或muldiv函数,这是一种仅使用整数来计算分数数字的方式。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.