9

我正在用一些遗留软件(RSLogix 500,别问)对 PLC 进行编程,它本身不支持模数运算,但我需要一个。我无权访问:模数、整数除法、局部变量、截断操作(尽管我可以通过舍入来破解它)。此外,我可以使用的所有变量都按数据类型排列在表格中。最后,它应该适用于浮点小数,例如12345.678 MOD 10000 = 2345.678.

如果我们做我们的等式:

dividend / divisor = integer quotient, remainder

有两个明显的实现。

实现1:进行浮点除法:dividend / divisor = decimal quotient. 然后拼凑一个截断操作,这样你就可以找到integer quotient. 将它乘以divisor并找到 和 之间的差异dividend,从而得到remainder

我不喜欢这样,因为它涉及到一堆不同类型的变量。我不能将变量“传递”给子程序,所以我只需要分配位于多个不同变量表中的一些全局变量,这很难理解。不幸的是,“难以理解”很重要,因为它需要足够简单,让维护人员可以搞砸。

实现 2:创建一个循环,使得 while dividend > divisor divisor = dividend - divisor. 这是非常干净的,但它违反了 PLC 编程的一条重要规则,即永远不要使用循环,因为如果有人无意中修改了索引计数器,你可能会陷入无限循环,机器会发疯或出现不可恢复的故障。Plus 回路很难进行维护故障排除。另外,我什至没有循环指令,我必须使用标签和跳转。哇。

所以我想知道是否有人有任何聪明的数学技巧或比这些更聪明的模数实现。我可以访问 + - * /、指数、sqrt、三角函数、日志、abs 值和 AND/OR/NOT/XOR。

4

5 回答 5

6

你要处理多少位?您可以执行以下操作:

if dividend > 32 * divisor  dividend -= 32 * divisor
if dividend > 16 * divisor  dividend -= 16 * divisor
if dividend > 8 * divisor  dividend -= 8 * divisor
if dividend > 4 * divisor  dividend -= 4 * divisor
if dividend > 2 * divisor  dividend -= 2 * divisor
if dividend > 1 * divisor  dividend -= 1 * divisor
quotient = dividend

只需展开与dividend. 一定要小心那些溢出的乘法。这就像您的 #2 一样,只是它需要 log(n) 而不是 n 次迭代,因此完全展开是可行的。

于 2013-02-12T22:22:27.087 回答
5

如果您不介意过于复杂的事情并浪费计算机时间,您可以使用周期性三角函数计算模数:

atan(tan(( 12345.678 -5000)*pi/10000))*10000/pi+5000   =   2345.678

不过说真的,减去 10000 一次或两次(你的“实现 2”)会更好。一般浮点模数的常用算法需要许多位级操作,这对您来说可能是不可行的。参见例如http://www.netlib.org/fdlibm/e_fmod.c(算法很简单,但由于特殊情况,代码很复杂,因为它是为 IEEE 754 双精度数编写的,假设没有 64 位整数类型)

于 2013-02-13T18:13:52.190 回答
3

这一切似乎都过于复杂了。您有一个在 10000 处滚动的编码器索引,并且对象沿着您在任何给定点跟踪其位置的线滚动。如果您需要沿线转发项目停止点或动作点,只需添加所需的英寸数,如果您的目标结果大于 10000,则立即减去 10000。

或者或另外,每次 PLC 扫描时,您总是会获得一个新的编码器值。如果当前值和上一个值之间的差异为负值,您可以激活工作触点以标记回绕事件并对该扫描中的任何计算进行适当的更正。(**或增加一个辅助计数器,如下所示)

在不了解实际问题的情况下,很难提出更具体的解决方案,但肯定有更好的解决方案。我认为这里根本不需要 MOD。此外,地板上的人会感谢你没有用模糊的巫师东西填满机器。

我引用 :

最后,它必须适用于浮点小数,例如 12345.678 MOD 10000 = 2345.678

有一个出色的功能可以做到这一点 - 它是减法。为什么它需要比这更复杂?如果您的输送线实际上长于 833 英尺,则滚动第二个计数器,该计数器在主索引翻转时递增,直到您有足够的距离覆盖您需要的地面。

例如,如果您需要 100000 英寸的传送带内存,您可以有一个在 10 处翻转的辅助计数器。如上所述,可以轻松检测到主编码器翻转,并且每次都增加辅助计数器。那么,您的工作编码器位置是计数器值加上当前编码器值的 10000 倍。仅在扩展单元中工作,并使辅助计数器以您需要的任何值翻转,以免丢失任何部件。问题再次简化为一个简单的减法(如上所述)。

例如,我将此技术与行星齿轮旋转零件支架一起使用。我有一个编码器,每次初级旋转都会翻转一次,而行星齿轮卫星部件(它们本身围绕定子齿轮旋转)需要 43 次初级旋转才能返回到相同的起始方向。使用在主编码器翻转点处递增(或递减,取决于方向)的简单计数器,它可以让您完全绝对地测量零件所在的位置。在这种情况下,辅助计数器在 43 处翻转。

这对于直线输送机的工作原理相同,唯一的区别是直线输送机可以无限行驶。那么问题只需要受到线路上最坏情况部分所采用的最长线性路径的限制。

需要注意的是我从未使用过 RSLogix,这是一般的想法(我在这里使用了通用符号,我的语法可能有点错误,但你应该明白)

RSLogix 示例

有了上述内容,您最终会得到一个值,该值ENC_EXT基本上将您的编码器从 10k 英寸的编码器转换为 100k 英寸的编码器。我不知道您的输送机是否可以反向运行,如果可以,您还需要处理向下计数。如果您的程序的整个其余部分仅适用于该ENC_EXT值,那么您甚至不必担心您的编码器仅达到 10k 的事实。它现在达到 100k(或任何你想要的),并且可以用减法而不是模数来处理环绕。

后记:

PLC首先是状态机。PLC 程序的最佳解决方案通常是与此想法一致的解决方案。如果你的硬件不足以完全代表机器的状态,那么 PLC 程序应该尽最大努力用它所拥有的信息来填补缺失的状态信息的空白。上述解决方案做到了这一点——它采用了不足 10000 英寸的状态信息并将其扩展以适应流程的要求。

这种方法的好处是您现在保留了绝对状态信息,不仅针对输送机,还针对生产线上的任何部件。您可以向前和向后跟踪它们以进行故障排除和调试,并且您有一个更简单、更清晰的坐标系可用于未来的扩展。通过模数计算,您将丢弃状态信息并尝试以功能方式解决个别问题 - 这通常不是使用 PLC 的最佳方式。你不得不忘记你从其他编程语言中学到的东西,并以不同的方式工作。PLC 是一种不同的野兽,它们在这样处理时效果最好。

于 2013-02-13T11:43:24.160 回答
3

您可以使用子例程来完成您正在谈论的事情。你可以把棘手的代码藏起来,这样维护技术人员就永远不会遇到它。对于您和您的维护人员来说,这几乎肯定是最容易理解的。

自从我使用 RSLogix500 以来已经有一段时间了,所以我可能会弄错几个术语,但你会明白的。

为您的浮点和整数分别定义一个数据文件,并按照 MOD_F 和 MOD_N 的行给它们一些符号。如果你让这些足够吓人,维护技术人员就不要管它们了,你所需要的只是在数学过程中传递参数和工作空间。

如果你真的担心它们会弄乱数据表,有办法保护它们,但我忘记了它们在 SLC/500 上是什么。

接下来,如果可能的话,定义一个子程序,在数字上远离现在使用的子程序。将其命名为 MODULUS。同样,维护人员几乎总是远离 SBR,如果它们听起来像编程名称。

在 JSR 指令之前的梯级中,将要处理的变量加载到 MOD_N 和 MOD_F 数据文件中。注释这些梯级,说明它们为 MODULUS SBR 加载数据。让任何有编程背景的人都能清楚地看到这些评论。

仅在需要时有条件地调用您的 JSR。维护技术人员不会打扰非执行逻辑的故障排除,因此如果您的 JSR 未处于活动状态,他们很少会查看它。

现在,您拥有了自己的围墙小花园,您可以在其中编写循环而无需维护。只使用那些数据文件,不要假设任何东西的状态,但那些文件是你所期望的。换句话说,您不能信任间接寻址。只要您在 MODULUS JSR 中定义索引,索引寻址就可以。不要相信任何传入的索引。使用 MOD_N 文件中的一个单词、一个跳转和一个标签来编写 FOR 循环非常容易。您的整个实施#2 应该少于十个梯级左右。我会考虑使用表达式指令或其他东西......让您只需输入表达式的指令。该指令可能需要 504 或 505。适用于组合的浮点/整数数学。检查结果以确保四舍五入不会杀死您。

完成后,尽可能完美地验证您的代码。如果此代码曾经导致数学溢出并使处理器出现故障,您将永远不会听到它的结束。如果你有一个模拟器,用奇怪的值在模拟器上运行它(以防它们以某种方式弄乱函数输入的加载),并确保 PLC 没有故障。

如果你做了所有这些,没有人会意识到你在 PLC 中使用了常规的编程技术,你会没事的。只要它有效。

于 2013-02-16T20:45:34.557 回答
0

这是一个基于@Keith Randall 答案的循环,但它也保留了减法除法的结果。为了清楚起见,我保留了 printf。

#include <stdio.h>
#include <limits.h>
#define NBIT (CHAR_BIT * sizeof (unsigned int))

unsigned modulo(unsigned dividend, unsigned divisor)
{
unsigned quotient, bit;

printf("%u / %u:", dividend, divisor);

for (bit = NBIT, quotient=0; bit-- && dividend >= divisor; ) {
        if (dividend < (1ul << bit) * divisor) continue;
        dividend -= (1ul << bit) * divisor;
        quotient += (1ul << bit);
        }
printf("%u, %u\n", quotient, dividend);
return dividend; // the remainder *is* the modulo
}

int main(void)
{
modulo( 13,5);
modulo( 33,11);
return 0;
}
于 2013-02-13T12:21:25.253 回答