oldbeginner 发表于 2013-11-18 21:09:24

开源PLC学习笔记09(再从51开始 主函数)——2013_11_18

本帖最后由 oldbeginner 于 2013-11-18 21:27 编辑

理解的主函数框架如下,

void main(void)
{
    I0口复位;

    RAM复位;

    中断复位;

    串口初始化;

    PLC版本核查;

   while(下载模式条件)
         {
               FX1NProcessing();
            
               if(下载模式条件不满足)
                  {
                     软件复位并从用户程序空间启动;
                  }
         }
   
   PLC 代码初次扫描;

   while(1)
          {
               输入输出刷新;
               
               main_PLC();
            }
}

****************************************
按照顺序来理解,

一、首先I0口复位,为简单和简洁去除了显示器。

void reset_IO(void)             // I,O口初始化
{
    P0M0= 0x00;
    P0M1= 0xff;
    P0    = 0x00;
    P1    = 0xff;
    P2    = 0xff;
    P3    = 0xff;
}

P0M0和P1M1是MPC82G56芯片自己的寄存器,

P0M0:端口0模式寄存器0;
P0M1:端口0模式寄存器1;

P0口采用上拉输出,可以想到,P0对应的输出就是Y。

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

二、 RAM复位,只看X和Y

void reset_RAM(void)
{
   for (i=0; i<_Y_BYTE; i++)
            {
               rY.BYTE = 0;
            rY1.BYTE = 0;
            }

Timer_5ms   = 0;         // 5ms时基计数器,5ms Timer0中断计数
Timer_10ms    = 0;         // 10ms时基计数器,5ms Timer0中断计数
Timer_100ms   = 0;         // 100ms时基计数器,5ms Timer0中断计数
Pulse_val_Sec = 0;         // 1s时基计数器
Pulse_val_Min = 0;         // 1min时基计数器
CODE_ERROR    = 0;

input_IO();

for (i=0; i<_X_BYTE; i++)
             rX1.BYTE = rX.BYTE;
}

因为对rX和rY的结构比较熟悉了,所以容易理解。但是为什么把rX1的赋值放在后面,它前面还有一个input_IO函数,可以想到,input_IO会影响到rX。为了简单,去掉了键盘输入和显示器。

//初始化,输入输出,内存处理 子函数                                                //
//-------------------------------------------------------------------------------------//
void input_IO(void)         // X输入,Y输出刷新
{
unsigned char i;
i = P2;
i = ((i << 1) & 0xaa) | ((i >> 1) & 0x55);
i = ((i << 2) & 0xcc) | ((i >> 2) & 0x33);
rX.BYTE = ~((P1 & 0x0f) | (i & 0xf0));
rX.BYTE = ~(i & 0x0f) & 0x0f;

P0 = rY.BYTE;
}

看了前4行, i 到底是什么?



真巧,4年前的今天。

搜了一下啊,http://wenku.baidu.com/view/fea1e985b9d528ea81c779ee.html,很不幸,目前没有精力顾及。

暂时理解其功能,变换未按照次序输入输出的IO口。

oldbeginner 发表于 2013-11-18 21:49:30

这个PLC加了好多外设,键盘和显示器,如果作为学习那负担承受不起,在学习中,只要不影响核心功能的外设都会被和谐。

继续中断复位,

void reset_interrupt(void)
{
IP   = 0x00;
IE   = 0x00;
TCON = 0x00;               // 定时器控制寄存器, 注意:TCON只需操作一次
TMOD = 0x01;
T0= Value_T0_cons;       // 装入5ms Timer0中断常数
PT0 = 1;
ET0 = 1;
TR0 = 1;
}


中断是很常规的那种。

然后是串口复位,串口复位在笔记05中理解过,串口使用了定时器1,而中断复位采用定时器0
void UartInit(void)
{
      //停止定时器
    TR1=0;
         
/******电源管理PCON寄存器************
位      7            6            5      4      3      2      1      0
寄存器    SMOD    X             X      X      X      X      X      X
      ————————————————————   
取值      1      0            0      0      0      0      0      0
说明    波特率加倍
************************************/
PCON=0x80;
   
/*****定时器TMOD寄存器**********
位            7         6            5            4                3      2            1      0
寄存器    GATE    C/T          M1      M0      GATE         C/T      M1      M0
      ——————————————----         |———————————   
                定时器1的设置                  定时器0的设置
取值    0                0            1         0               0         0      0            0
说明      使用定时器1,工作方式2            |            没用使用            
**********************************/
   TMOD=TMOD | 0x20;

//定时器1工作方式2
//在下面的PCON设置上增加了倍频,所以晶振位22.1184Mhz
/**********波特率设置表(22.1184Mhz)************
波特率    2400    4800    9600    19200
TH1      E8      F4      FA      FD
TL1      E8      F4      FA      FD
说明,因为选取波特率9600,所以TH1和TL1都取FA
*************************************************/
    TH1=0xFA;
    TL1=0xFA;

//串口工作方式1
/*******串口SCON寄存器***************
位      7            6                5                  4                3             2                  1                        0
寄存器    SM0      SM1          SM2                REN            TB8            RB8                TI                        RI
      ——————————————————————————————————————
             工作方式控制|         多机通信|       接受标志位|发第9位|   收第9位|          发送中断标志位接受中断标志位
取值   0            1               0                     1                0                0                     0                            0
说明      工作方式1            开始接受
**************************************/
SCON=0x50;   


//启动定时器1   
   TR1=1;             

    UartReceiveCounter=0;
    UartRxTimerStartFlag=0;
}

也好理解。

这样,几个复位函数先理解。后面的函数就感觉复杂了。

LSZD 发表于 2013-11-18 22:22:21

{:smile:}{:smile:}

kinsno 发表于 2013-11-18 22:28:50

不得不顶楼主,你的精研劲头值得大家学习。

oldbeginner 发表于 2013-11-18 22:42:40

kinsno 发表于 2013-11-18 22:28 static/image/common/back.gif
不得不顶楼主,你的精研劲头值得大家学习。

谢谢鼓励,具有精研劲头应该是开源PLC的人,我只不过是来学习一下的,另外写成笔记感觉效率更好些。

kinsno 发表于 2013-11-18 22:49:43

oldbeginner 发表于 2013-11-18 22:42 static/image/common/back.gif
谢谢鼓励,具有精研劲头应该是开源PLC的人,我只不过是来学习一下的,另外写成笔记感觉效率更好些。 ...

哈哈,其实你这样也蛮不错,至少能给后面搞PLC的人一个启示或少走一些弯路啊。
其实,PLC现在这些远远不够,只能说先仿仿,最终还是要取其精华,用在我们自己的产品上,或形成我们自己的东西吧;PLC这个行业越来越难啊,西门子最近在成都搞了一个大工厂,S200系列将全部在这里生产,听说现在200已经有大降价了,比以前。

minzhuzhongguo 发表于 2013-11-18 23:48:41

睡不着,上来就碰到楼主的笔记,这段时间虽然还来不及学习,但是先说声感谢!

canxauto 发表于 2013-11-19 19:20:58

好东西学习了

lmt50211 发表于 2013-11-21 13:46:41

建议楼主把笔记做PDF文档,这样文便阅读和学习。。。

oldbeginner 发表于 2013-11-26 09:28:41

继续PLC版本检测,
PLCType=IAPFlashReadMode(PLCTypeAddr);      //    上电,核实 PLC 硬件版本号

复习一下地址和函数,
#define PLCTypeAddr            (MCUFLASHSIZE-MCUISPFLASHSIZE-512)    //    即IAP倒数第一页.

unsigned char IAPFlashReadMode(unsigned int codeaddr)    // 读模式

读取地址为PLCTypeAddr的内容,这个内容就是PLC版本号。

因为写PLC初始化内容时,第一个字符是0,如果不是,表示没有初始化,

if(PLCType!=0)                              //    第一次使用. IAP -- "FX1N PLC\r\n".
    {
      写入PLC初始化代码(第一个字符为0);
      UartSendString(ArrFirstused);
    }
else UartSendString(ArrPass);

上面发送的字符串笔记简单,
unsigned char code ArrFirstused[]="First used!\0";
unsigned char code ArrPass[]="PASS!\0";

初始化内容有,
      ErasurePLC(ErasureALL);                  //    全部擦除    初始PLC程序为空
      IAPFlashProgremMode(PLCTypeAddr,0);      //    更新硬件版本号标识符标志
      for(i=0;i<PLCTypeArrayLen;i++)            //    更新硬件版本号
      { IAPFlashProgremMode(PLCTypeAddr+1+i,PLCTypeArray);
        }
      for(i=0;i<46;i++)                        //    写入PLC初始化代码            查询地址0x8000. 0x802e.
      { IAPFlashProgremMode(0x8000+i,OrderSend3);
          IAPFlashProgremMode(0x802e+i,OrderSend4);
      }
1、全部擦除    初始PLC程序为空
2、更新硬件版本号标识符标志(即第一个字符为0)
3、更新硬件版本号
4、写入PLC初始化代码            查询地址0x8000. 0x802e.

上面的代码都是比较好理解的。
******************************************
然后,
for(i=0;i<PLCTypeArrayLen;i++)
    { PLCTypeArray=IAPFlashReadMode(PLCTypeAddr+1+i);
    }
把版本信息再读出来

UartSendByte(PLCTypeArray,PLCTypeArrayLen);
UartSendString("\r\n");
再发送出去。

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

while (((P1 & 0x03) == 0) && (RUN == 0))
    { FX1NProcessing();                        //    PLC下载通信处理
      if ((P1 & 0x03) != 0)   
          { delay_ms(20);
            if ((P1 & 0x03) != 0)
            { RUN = 1;
                IFMT= 0;          //
                ISPCR = 0x20;      // SWBS= 0; 选择AP空间, SWRST = 1; 软复位
                while (1) { ; }
            }
          }
    }

这就是通讯判断函数,是不是接收上位机指令。
(P1 & 0x03) == 0) && (RUN == 0)
P1的第三个端口为低电平,同时设置的位变量RUN也是0时,就开始进入通讯核心函数FX1NProcessing。
      if ((P1 & 0x03) != 0)   
          {
             延时消抖;
             从AP空间复位;
             }
P1的第三个端口应该是一个按键,可以用来复位。复位函数是参考该芯片手册上的,不同芯片有区别。

******************************************
然后,
CODE_scan();
这是一个比较复杂的函数,
//   PLC 代码 初次扫描,求出标号 Pn 地址函数main_PLC();                                                      //
//-------------------------------------------------------------------------------------//

void CODE_scan (void)
理解这个函数,需要复习笔记07里的内容。

CODE_p = (unsigned char code *)CODE_START;
其中,
#define   CODE_START   0x8000         // PLC执行代码缓冲区首地址
然后,
      orderL = *CODE_p;
      CODE_p++;
      orderH = *CODE_p;
      CODE_p++;
      ppp = order & 0xfff;
可以看出,求出了偏移地址ppp。
具体如图所示,



从中可看出,利用 order & 0xfff求出偏移地址,
再利用orderH>>=4求出映射表(散转函数表)对应的位置。

然后很多if else if语句,这些语句暂时忽略。

最后,
CSP_Pn = (unsigned char code *)CODE_START;
这个语句需要结合PLC_main()一起理解,暂时略过。

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


oldbeginner 发表于 2013-11-26 10:11:59

oldbeginner 发表于 2013-11-26 09:28
继续PLC版本检测,
PLCType=IAPFlashReadMode(PLCTypeAddr);      //    上电,核实 PLC 硬件版本号


最后就是 ,省掉非核心代码和非相关变量。
while (1)
    {
      input_IO();
         
      main_PLC();

      mov_to_old();
    }

这段代码就是PLC正常执行时的循环。

首先,
*******************************************
void input_IO(void)         // X输入,Y输出刷新
这个功能是必要的

因为只考虑x和y,其余不考虑,包括外设
void input_IO(void)         // X输入,Y输出刷新
{
unsigned char i;
i = P2;
i = ((i << 1) & 0xaa) | ((i >> 1) & 0x55);
i = ((i << 2) & 0xcc) | ((i >> 2) & 0x33);
rX.BYTE = ~((P1 & 0x0f) | (i & 0xf0));
rX.BYTE = ~(i & 0x0f) & 0x0f;

P0 = rY.BYTE;
   }
这种碟形算法还不会,因为不理解对应关系,暂时略过,打算把该算法换成容易理解的语句。

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

然后进入main_PLC函数,
void main_PLC (void)
{
CODE_p = (unsigned char code *)CODE_START;
Pi = 0x01;
do{
      orderL = *CODE_p;
          CODE_p++;
      orderH = *CODE_p;
          CODE_p++;

      ppp = order & 0xfff;

      (*key_list)();

    } while((CODE_p < CODE_END) && (CODE_p != CODE_START));
}
因为在笔记07中理解过,在功能上基本都能理解。
需要增加的一个知识点就是函数指针,

http://www.amobbs.com/forum.php? ... 2715&extra=page%3D1上说过函数指针重要性,但是作为入门材料并不好,我推荐的文章是
http://wenku.baidu.com/view/28d11678a26925c52cc5bf71.html
有图有例子,非常好懂。

程序中定义如下,
code (*key_list)();

按照我的理解,改成
code void (*key_list)(void); 更好些

main_PLC 把 CODE_START和 CODE_END 之间的代码执行了一遍。因为CODE_p++会等于CODE_END,不是死循环,只是执行一遍。

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

void mov_to_old(void)
{
unsigned char i;
for (i=0; i<_X_BYTE; i++)
             { rX1.BYTE = rX.BYTE; }
for (i=0; i<_Y_BYTE; i++)
            { rY1.BYTE = rY.BYTE; }
}

根据定义
volatile TYPE_BIT_BYTE   datarX , rY;       //位元件 X,Y 存储位
volatile TYPE_BIT_BYTE   datarX1, rY1;      //位元件 X,Y 存储位上一步备份

从代码和功能上可以理解,备份作用。
不过rX1和rX和容易混淆,把rX1改成 rXbk或许好些。

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

因为只理解x y LD SET OUT RST等几个变量和命令,并且不考虑外设,所以代码量小了很多,这样就完成了整个程序的第一遍理解。从理论上说,已经具备把理解的程序移植到STC12C516系列芯片上,因为该芯片已经有相应的PLC程序,所以下一步进行移植,可以完成:

1、对开源PLC程序的第二遍理解;
2、更新通讯协议;
3、和已有STC12C516程序的对比(现有的STC12C516程序没有详细的注解,文件安排也比较乱);
4、利用三菱梯形图软件完成LD X02
                                    OUT Y5
                                    END
能够把命令传递给STC12C516上,并且该单片机可以正常工作。

oldbeginner 发表于 2013-12-13 16:41:04

oldbeginner 发表于 2013-11-26 09:28
继续PLC版本检测,
PLCType=IAPFlashReadMode(PLCTypeAddr);      //    上电,核实 PLC 硬件版本号



因为之前只理解LD和OUT,所以省去了很多相关代码,现在需要把PLC指令逐步添加,就有必要重新理解Code_scan函数了。

先把散转列表复习一下,
code (*key_list)()={
          CMDFNC ,   // 0 (FNC应用指令)   
          CMDP   ,   // 1 (P 应用指令)
          LD   ,   // 2 (LD指令, 2000+ppp, 扩展 Mp除外)
          LDI    ,   // 3 (LDI指令, 3000+ppp, 扩展 Mp除外)   
          AND    ,   // 4 (AND指令, 4000+ppp, 扩展 Mp除外)   
          ANI    ,   // 5 (ANI指令, 5000+ppp, 扩展 Mp除外)   
          OR   ,   // 6 (OR指令, 6000+ppp, 扩展 Mp除外)   
          ORI    ,   // 7 (ORI指令, 7000+ppp, 扩展 Mp除外)   
          CMDER,   // 8 (多字指令,第二字及以后有效)
          CMDER,   // 9
          CMDER,   // A (多字指令,第二字及以后有效, 仅对M1536-M3071有效,需加偏移量200)
          CMDER,   // B (Pn指令, 仅对CJ,CALL有效)
          OUTYM,   // C (OUT指令, 仅对Y,M有效)
          SETYM,   // D (SET指令, 仅对Y,M有效)
          RSTYM,   // E (RST指令, 仅对Y,M有效)
          CMDCH };   // F (纯单字指令)

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

void CODE_scan (void)
{
unsigned char pa, pb;

CODE_p = (unsigned char code *)CODE_START;

do{
      orderL = *CODE_p;
          CODE_p++;

      orderH = *CODE_p;
          CODE_p++;

      ppp = order & 0xfff;

分别取出偏移地址和命令码(散转表数组下标)
继续,
          if ((orderH>>4) <= 0x1)
                {
                        if (ppp == 0x00f)      //   (END指令, 000F )
                            {
                               END();
                      }
(orderH>>4) <= 0x1,即命令码是0或者1,CMDFNC或CMDP命令,或者是END命令。因为END指令 00 0f,这时再通过验证偏移地址来确认是否是END指令。
继续,

          else if (ppp <= 0x00e)
            {
                pa = (unsigned char)ppp;

            if ((pa <= 0x001) || (pa == 0x00e)) ;

                else if ((pa == 0x00a) || (pa == 0x00d))
                         {
                               CODE_p += 4;
                      }
                    else
                        {
                                CODE_p++;
                              CODE_p++;
                        }
            }
原因不知道(还需要确认一下),肯定没有相对应的指令,跳过4个字节或2个字节,应该类似空行。

          else if ((ppp>= 0x1c0) && (ppp <= 0x1cf))
            {
               pa = (unsigned char)(ppp - 0x1c0);
            
               if ((pa <= 0x001) || (pa == 0x008) || (pa == 0x009)) ;
            
               else
                {
               CODE_p++;
                  CODE_p++;
                }
            }
类似上面,继续执行或跳过2个字节

          else if ((ppp >= 0x600) && (ppp < 0x800))      // 三字指令
            {
               CODE_p += 4;
            }
判断出是三字指令,则指针+4。为什么?

看来要打断一下,找到原因再继续这里的理解。
http://www.amobbs.com/thread-3303497-1-1.html

oldbeginner 发表于 2013-12-14 15:45:39

oldbeginner 发表于 2013-12-13 16:41
因为之前只理解LD和OUT,所以省去了很多相关代码,现在需要把PLC指令逐步添加,就有必要重新理解Code_sca ...

借鉴数学常用的反证法,如果(orderH>>4) > 0x1,是哪些指令?

单字指令
                  LD   2000+ppp         ;(扩展 Mp除外)
                  LDI    3000+ppp         ;(扩展 Mp除外)
                  AND    4000+ppp         ;(扩展 Mp除外)
                  ANI    5000+ppp         ;(扩展 Mp除外)
                  OR   6000+ppp         ;(扩展 Mp除外)
                  ORI    7000+ppp         ;(扩展 Mp除外)
                  Pn   B000+(N)         ; N=0-127
                  OUT    C000+ppp         ;(仅对Y,M有效)
                  SET    D000+ppp         ;(仅对Y,M有效)
                  RST    E000+ppp         ;(仅对Y,M有效)

纯单字指令
                  ANB    FFF8
                  ORB    FFF9
                  MPS    FFFA
                  MRD    FFFB
                  MPP    FFFC
                  INV    FFFD
                  NOP    FFFF

************************************************
其余的都是(orderH>>4) <= 0x1),有哪些?

纯单字指令
                  END    000F

单字指令
          CMDFNC ,   // 0 (FNC应用指令)   
          CMDP   ,   // 1 (P 应用指令)
基本应用指令=(FNC.No.n+8)*2   
D 应用指令   =(FNC.No.n+8)*2 +1
P 应用指令   =(FNC.No.n+8)*2 +1000

双字指令
                  OUT    0002 8000+ppp      ;(仅对M8xxx有效)
                  OUT    0002 A000+ppp      ;(仅对Mp有效)
                  SET    0003 8000+ppp      ;(仅对M8xxx有效)
                  SET    0003 A000+ppp      ;(仅对Mp有效)
                  RST    0004 8000+ppp      ;(仅对M8xxx有效)
                  RST    0004 A000+ppp      ;(仅对Mp有效)
                  OUT    0005 8000+ppp      ;(仅对S有效)
                  SET    0006 8000+ppp      ;(仅对S有效)
                  RST    0007 8000+ppp      ;(仅对S有效)
                  PLS    0008 8000+ppp      ;(仅对Y,M有效)
                  PLF    0009 8000+ppp      ;(仅对Y,M有效)
                  MC   000A 8000+(N) 8000+ppp ;(仅对Y,M有效)
                  MCR    000B 8000+(N)          ; N=0-7
                  RST    000C 8000+ppp          ;(仅对T,C,Cp有效)
                  RST    000D 8m00+xx8n00+yy;(仅对 D 有效,包含Z,V)

                  LD   01C2 A000+ppp      ;(仅对Mp有效)
                  LDI    01C3 A000+ppp      ;(仅对Mp有效)
                  AND    01C4 A000+ppp      ;(仅对Mp有效)
                  ANI    01C5 A000+ppp      ;(仅对Mp有效)
                  OR   01C6 A000+ppp      ;(仅对Mp有效)
                  ORI    01C7 A000+ppp      ;(仅对Mp有效)

                  LDP    01CA 8000+ppp      ;(扩展 Mp除外)
                  LDP    01CA A000+ppp      ;(仅对 Mp有效)
                  LDF    01CB 8000+ppp      ;(扩展 Mp除外)   
                  LDF    01CB A000+ppp      ;(仅对 Mp有效)   
                  ANDP   01CC 8000+ppp      ;(扩展 Mp除外)   
                  ANDP   01CC A000+ppp      ;(仅对 Mp有效)   
                  ANDF   01CD 8000+ppp      ;(扩展 Mp除外)   
                  ANDF   01CD A000+ppp      ;(仅对 Mp有效)   
                  ORP    01CE 8000+ppp      ;(扩展 Mp除外)   
                  ORP    01CE A000+ppp      ;(仅对 Mp有效)   
                  ORF    01CF 8000+ppp      ;(扩展 Mp除外)   
                  ORF    01CF A000+ppp      ;(仅对 Mp有效)


三字及五字指令
                  OUTTK      0600+(T)8000+xx 8000+yy
                  OUTCK      0E00+(C)8000+xx 8000+yy
                  OUTCp K      0000+(Cp) 8000+xx 8000+yy 8000+zz 8000+ww//忽略
**************************************************************

所以现在可以理解为何当 if ((orderH>>4) <= 0x1) 时,会有很多if。。。else if。。。else if。。。语句,是因为不同类型指令字节长度不一样。


研究一个例子看看,
例如 OUT T1 K5
指令码应该是 0601 8000 8005





再回到Code_scan函数,
          else if ((ppp >= 0x600) && (ppp < 0x800))      // 三字指令
            {
                CODE_p += 4;
            }
因为ppp=0x601,所以执行这句;这样指针就跳过8000 和8005指令,指向下一条指令。

这样证明Code_scan只是检验命令格式是否正确。

用类似的方法可以把其它命令都确认一下。但是没有返回信息,感觉这样子确认有些多余。

如果指令格式不对(很简单只考虑指针是否超出,并不全面),会有
if (CODE_p >= CODE_END)
    {
        CODE_ERROR =1;
        }
这样在主函数中,
          if (CODE_ERROR == 0)
      {
                main_PLC();
        }

Code_scan作用有限。

但是从上面OUT T1 K5例子中引出了一个类似的但更复杂函数,

code (*key_list)()={
          CMDFNC ,   // 0 (FNC应用指令)
        。。。。
        }

void CMDFNC(void)   // 0 (应用指令)   
{
        很多代码
        。。。
}

oldbeginner 发表于 2013-12-14 16:17:02

oldbeginner 发表于 2013-12-14 15:45
借鉴数学常用的反证法,如果(orderH>>4) > 0x1,是哪些指令?

单字指令


/********************CMDFNC指令散转*******************************************/

void CMDFNC(void)   // 0 (应用指令)   
{
        END指令();

       (ppp <= 0x00e)();

        ((ppp>= 0x1c0) && (ppp <= 0x1cf))();

        三字指令T();

        三字指令C();

        基本应用指令();

        D应用指令();
}

还是按照例子OUT T1 K5来理解,
这里会选择

三字指令T()

else if ((ppp >= 0x600) && (ppp < 0x800))      // 三字指令
    {
        WR_TK(ppp - 0x600);       // OUTT,K      0000+(T)8000+xx 8000+yy
    }
因为ppp=0x601,则调用 WR_TK(1);

其中
void WR_TK(unsigned int a)       // ,(K值写入T)
{
代码();
}

至此,OUT T1 K5才得到执行。

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

如果是基本应用指令,则还要复杂一些,

在CMDFNC中,还要散转一下,调用
          (*key_list_3)();          //应用指令散转

code (*key_list_3)() ={
       FNC_CJ    ,    // 0
       FNC_CALL,    // 1
       FNC_SRET,    // 2   
       FNC_IRET,    // 3
       。。。。
}
根据下标,再调用相应函数。

还有其它情况的散转函数,这里暂时不展开,目的只是先把大脉络理解,然后把PLC指令逐步加入到笔记19中,笔记19将是51下的PROTEUS仿真,并且能接收三菱梯形图指令。

litrtleway 发表于 2015-4-7 14:05:23

mark,楼主很牛哦!

yuan1999 发表于 2015-7-24 10:52:11

mark,楼主很牛!顶一个.好资料
页: [1]
查看完整版本: 开源PLC学习笔记09(再从51开始 主函数)——2013_11_18