oldbeginner 发表于 2014-1-1 12:47:02

开源PLC学习笔记21(入门PLC简单算法在51上的实现)——2014_1_1

本帖最后由 oldbeginner 于 2014-1-1 22:02 编辑

在前面的学习中,都是本着有什么指令就看相应代码的思路,作为入门,效果是不错的。
其实,随着对代码的熟悉,就不经好奇他们是怎样组织的,逻辑关系是怎样建立的。


网上搜一下,软PLC编译系统的开发与实现
http://www.eepw.com.cn/article/163110_3.htm
虽然不够详细,但是已经可以入门了(我直接跳过了编译这些内容,编译原理对我来说太抽象了,虽然词法分析学了一些,非常不喜欢编译原理的表达方式)

这篇文章介绍了如何组织下面指令的实现
LD        X000
AND        X001
OR        X002
ANI        X003
OR        X004
OUT        Y000

在开源PLC中,执行顺序
void LD   (void)   
{ ACC_BIT <<= 1;
ACC_BIT |= RD_ppp(0x400);
}

void AND    (void)   
{ ACC_BIT &= (RD_ppp(0x401) | 0xfe);
}

void OR   (void)   
{ ACC_BIT |= RD_ppp(0x402);
}

void ANI    (void)   
{ ACC_BIT &= (~RD_ppp(0x403) | 0xfe);
}

void OR   (void)   
{ ACC_BIT |= RD_ppp(0x404);
}

void OUTYM(void)   
{ WR_YM(0x500 , ACC_BIT);
}

直接把要理解的内容摘录如下:
*******************************************
  PLC指令表程序的分析是通过对指令表程序的解释而获得程序的逻辑,并以对话框形式演示程序的逻辑状态。在解释过程中,构造2个变量,1个用于存储分支块的逻辑值,另1个用于存储分支块前面语句的逻辑值。同时构造1个堆栈用来存储解释过程中的结果,分支块前面的值保存在堆栈中,整个分支块的值保存在1个临时变量中。

PLC指令表程序的解释过程为:
1)当解释程序 发现LD或LDI指令时,将临时变量值压入堆栈,临时变量赋值为1,临时变量与指令后面的元素进行逻辑与操作,将结果保存在临时变量中;
2)当解释程序发 现AND或ANI指令时,临时变量与指令后的元素进行与操作,将结果保存到临时变量中;
3)当解释程序发现OR或ORI指令时,l临时变量与指令后的元素 进行或操作,将结果保存到临时变量中;
4)当解释程序发现ANB指令时,临时变量与栈顶的值进行与操作,将结果保存到临时变量中,同时堆栈将栈顶元素弹 出;
5)当解释程序发现ORB指令时,临时变量与栈顶的值进行或操作,将结果保存到临时变量中,同时堆栈将栈顶元素弹出;
6)当解释程序发现OUT指令 时,将临时变量与栈顶的值进行与操作,将结果保存在临时变量中。同时清空堆栈。

***********************************************

然后和开源PLC的代码进行对比理解,

临时变量就是ACC_BIT。

指令后面的元素就是一个函数,RD_ppp(unsigned int a)   

************************************************

2)当解释程序发 现AND或ANI指令时,临时变量与指令后的元素进行与操作,将结果保存到临时变量中;

void AND    (void)    // 4(AND指令, 4000+ppp, 扩展 Mp除外)
{
        ACC_BIT &= (RD_ppp(ppp) | 0xfe);
}

***************************************************

3)当解释程序发现OR或ORI指令时,l临时变量与指令后的元素 进行或操作,将结果保存到临时变量中;

void OR   (void)    // 6(OR指令, 6000+ppp, 扩展 Mp除外)
{
        ACC_BIT |= RD_ppp(ppp);
}

****************************************************

1)当解释程序 发现LD或LDI指令时,将临时变量值压入堆栈,临时变量赋值为1,临时变量与指令后面的元素进行逻辑与操作,将结果保存在临时变量中;

void LD   (void)    // 2(LD指令, 2000+ppp, 扩展 Mp除外)
{
        ACC_BIT <<= 1;

        ACC_BIT |= RD_ppp(ppp);
}
这里实现方式不太一致,但是结果是一样的。

*****************************************************

6)当解释程序发现OUT指令 时,将临时变量与栈顶的值进行与操作,将结果保存在临时变量中。同时清空堆栈。

void OUTYM(void)    // C (OUT指令, C000+ppp, 仅对Y,M有效)
{
        WR_YM(ppp,ACC_BIT);
}

*****************************************************

上面的结构理解后,再来具体理解如何实现
指令后面的元素就是一个函数,RD_ppp(unsigned int a)   

unsigned char RD_ppp(unsigned int a)    // (读入点内容)
{
unsigned char n;
unsigned char *p;
p = ADDR_int_ppp(a);
n = *p >> (a % 8);
return(n & 0x01);
}


char* ADDR_int_ppp(unsigned int a)    // (读入int点内容,返回地址绝对指针)
{ unsigned char *p;
a &= 0xfff;
if (a<0x400)
        { if (a < _S_num)
          { p= (unsigned char*)rS + (a / 8);
                }
        }
else if(a<0x500)
        { a -= 0x400;
          if (a < _X_num)
          { p = (unsigned char*)rX + (a / 8);
                }
        }
else if(a<0x600)
        { a -= 0x500;
          if (a < _Y_num)
          { p = (unsigned char*)rY + (a / 8);
                }
        }
else if(a<0x800)
        { a -= 0x600;
          if (a < _T_num)
          { p = (unsigned char*)rT + (a / 8);
                }
        }
else if(a<0xe00)
        { a -= 0x800;
          if (a < _M_num)
          { p = (unsigned char*)rM + (a / 8);
                }
        }
else if(a<0xf00)
        { a -= 0xe00;
          if (a < _C_num)
          { p = (unsigned char*)rC + (a / 8);
                }
        }
else
        { a -= 0xf00;
          if (a < _M8xxx_num)
          { p = (unsigned char*)rM8xxx + (a / 8);
                }
        }
return(p);
}

上面的两个函数在笔记19中理解过一次,复习一下。


********************************************

最后,就是理解输出
6)当解释程序发现OUT指令 时,将临时变量与栈顶的值进行与操作,将结果保存在临时变量中。同时清空堆栈。

void WR_YM(unsigned int a,unsigned char i)    // (写入Y,M点内容)
{ unsigned char *p;
a &= 0xfff;
i &= 0x01;
if ((a>=0x500) && (a<0x600))
        {
                a -= 0x500;
                if (a < _Y_num)
         { p = (unsigned char*)rY + (a / 8);
                       if (i == 0)   *p &= ~(1 << (a % 8));
               else*p |= 1 << (a % 8);
         }
        }
else if ((a>=0x800) && (a<0xe00))
        {
                a -= 0x800;
                if (a < _M_num)
         { p = (unsigned char*)rM + (a / 8);
                       if (i == 0)   *p &= ~(1 << (a % 8));
               else*p |= 1 << (a % 8);
         }
        }
else ;
}

开源PLC中,OUT指令没有清空堆栈,这样也可以理解为为什么
在LD指令中,有ACC_BIT <<= 1;

ACC_BIT <<= 1;执行效果,低位为0,相当于清空。

所以开源PLC中设计 LDx指令时,第一句都是ACC_BIT <<= 1,即清空堆栈。
这样OUT命令就不需要再清空堆栈了。



51EDA技术 发表于 2014-1-1 13:31:31

谢谢分享。支持!!!                                                                                                                              

waking 发表于 2014-1-1 15:04:55

板凳学习

sunliezhi 发表于 2014-3-6 19:13:47

谢谢老初的详细讲解!
自称老初,你谦虚了{:lol:}

ousoouso 发表于 2014-3-17 17:27:23

值得学习

linshengttt 发表于 2014-3-26 11:16:46

收藏下 谢谢

ousoouso 发表于 2015-6-17 08:31:58

有兴趣开发PLC的,请加我QQ:611017112,相互交流

dadian 发表于 2015-7-5 17:35:56

mark                  

zzsczz 发表于 2015-7-6 14:14:40

这个楼非常好

limaotaizi 发表于 2016-2-28 23:47:43

学习,有空慢慢看
页: [1]
查看完整版本: 开源PLC学习笔记21(入门PLC简单算法在51上的实现)——2014_1_1