GUTTA Ladder To C


简介:

本软件可以将PLC编程中的“梯形图”或“指令表”转换成等效的C语言。也就是说,对于单片机的开发者,只要掌握好了底层硬件驱动的开发,上层的控制逻辑可以交由GUTTA Ladder To C来完成。GUTTA Ladder To C所生成的代码被设计为尽量硬件无关且完全符合C99语言标准。因为是标准C语言,这份代码适用于任何单片机。GUTTA Ladder To C同时还是一个开放性的平台,您可以在PLC类型CODE-EC20(实际上是假想的PLC类型)的基础上构建自己的PLC类型,并可针对特定的单片机做出代码优化。之所以能够实现这一点是因为GUTTA Ladder To C的变量配置、指令配置、C代码生成规则全部由XML文本文件给出,而用户使用任何一款文本编辑器即可修改XML文件。

屏幕截图:

软件的下载和安装:

[下载页面]

为了避免和软件GUTTA Ladder Editor的文档文件名后缀发生冲突(文档文件名后缀都是*.vcw),软件GUTTA Ladder To C没有提供安装包,而是以压缩包的形式提供下载。您可以将这个压缩包解压到本地硬盘的任何位置,运行其中的GuttaLadToC.exe文件即可。因为可以直接解压运行,GUTTA Ladder To C是一个绿色软件。

使用介绍:

在正式开始之前,您可能需要了解以下信息:

现在让我们正式开始把!让我一步一步演示GUTTA Ladder To C是如何工作的。为了讨论方便,这里假定我们需要设计程序的硬件是PLC仿真器CPU-EC20 (8051)。由于生成的C代码是完全符合C99标准的,你实际上可以将GUTTA Ladder To C用于任何一个带C编译器的处理器。

CPU-EC20 (8051)的详细硬件信息请参考这里:[基于8051的试验板]

附带一张CPU-EC20 (8051)的照片:

至于编译器的选择、程序下载的方法在上面提及的文档《AN2103 基于GUTTA一步一步实现一个最小PLC系统》中有详细的介绍,这里就不重复叙述了。

首先,我们做一个最简单的IO测试吧,打开软件GUTTA Ladder To C,编辑一个类似于下面的梯形图:

用过PLC的都知道,这是一个“起跑停”电路,也就是按下I1.0后,Q0.0自保输出,按下I1.1后,Q0.0自保取消不输出。如果对编辑环境不太熟悉,可以参考文档《IN1003 PLC编程初级教程》,这里面有对梯形图编辑器GUTTA Ladder Editor使用的详细介绍(也适用于本软件)。一切顺利的话点击主菜单“PLC”下的命令“全部编译”,GUTTA Ladder To C会根据上面的梯形图生成两段代码:

第一段代码是头文件(Header File):

   
//////////// <Macro>
   #define __gBV(bit)                (1U << (bit))
   #define __gBT(sfr, bit)           ((sfr) & __gBV(bit))
   #define __gBF(sfr, bit)           (!__gBT(sfr, bit))
   #define __gBS(sfr, bit, var) { \
               if(var) (sfr) |=  __gBV(bit); \
                   else (sfr) &= ~__gBV(bit); \
               }

   #define __gBST(sfr, bit)          (sfr) |= __gBV(bit)
   #define __gBSF(sfr, bit)          (sfr) &= ~__gBV(bit)
   #define __gBL(sfr, len, bit)      (((sfr) >> (bit)) & (__gBV(len) - 1))
   #define __gADDR_PTR(x)            (__gParPtr[x])
   #define __gADDR_VAL_INT8(x)       (*(__gint8_t*)__gADDR_PTR(x))
   #define __gADDR_VAL_INT16(x)      (*(__gint16_t*)__gADDR_PTR(x))
   #define __gADDR_VAL_INT32(x)      (*(__gint32_t*)__gADDR_PTR(x))
   #define __gADDR_VAL_UINT8(x)      (*(__guint8_t*)__gADDR_PTR(x))
   #define __gADDR_VAL_UINT16(x)     (*(__guint16_t*)__gADDR_PTR(x))
   #define __gADDR_VAL_UINT32(x)     (*(__guint32_t*)__gADDR_PTR(x))
   #define __gADDR_VAL_BIT(x)        (__gParBit[x])
   #define __gADDR_VAL_BIT_BT(x)     __gBT(__gADDR_VAL_INT8(x),__gADDR_VAL_BIT(x))
   #define __gADDR_VAL_BIT_BF(x)     __gBF(__gADDR_VAL_INT8(x),__gADDR_VAL_BIT(x))
   #define __gADDR_VAL_BIT_BS(x,var) __gBS(__gADDR_VAL_INT8(x),__gADDR_VAL_BIT(x),var)
   #define __gADDR_VAL_BIT_BST(x)    __gBST(__gADDR_VAL_INT8(x),__gADDR_VAL_BIT(x))
   #define __gADDR_VAL_BIT_BSF(x)    __gBSF(__gADDR_VAL_INT8(x),__gADDR_VAL_BIT(x))
   #define __gLGC_HELPER_LD      0x00
   #define __gLGC_HELPER_A       0x01
   #define __gLGC_HELPER_O       0x02
   #define __gLGC_HELPER_B       0x00
   #define __gLGC_HELPER_W       0x01
   #define __gLGC_HELPER_D       0x02
   #define __gLGC_HELPER_EQ      0x00
   #define __gLGC_HELPER_NE      0x01
   #define __gLGC_HELPER_GT      0x02
   #define __gLGC_HELPER_LT      0x03
   #define __gLGC_HELPER_GE      0x04
   #define __gLGC_HELPER_LE      0x05
//////////// </Macro>
//////////// <Public Memeber Declare>
   typedef signed char   __gint8_t;
   typedef unsigned char __guint8_t;
   typedef signed int    __gint16_t;
   typedef unsigned int  __guint16_t;
   typedef signed long   __gint32_t;
   typedef unsigned long __guint32_t;
   typedef __guint8_t       __gregister_t;
   typedef __guint8_t       __gregister8_t;
   typedef __guint16_t      __gregister16_t;
   typedef __guint32_t      __gregister32_t;
   extern __gregister16_t   __gTimer[3];
   extern __gregister16_t   __gTimerTick;
   extern __gregister16_t   __gTimerTickRec[3];
   extern __guint8_t        __gI[8];
   extern __guint8_t        __gQ[8];
   extern __guint8_t        __gAI[16];
   extern __guint8_t        __gAQ[16];
   extern __guint8_t        __gM[128];
   extern __guint8_t        __gT[32];
   extern __guint8_t        __gC[16];
   extern __guint8_t        __gSM[16];
   extern __guint8_t        __gJ[16];
   extern __guint8_t        __gK[128];
   extern __guint8_t        __gL[32];
   extern __gregister16_t   __gStackData;
   extern __gregister16_t   __gStackLogic;
   extern __guint8_t*       __gParPtr[4];
   extern __gregister_t     __gParBit[4];
   extern __guint8_t        __gSaveT[32/2];
   extern __guint8_t        __gSaveC[16/2];
//////////// </Public Memeber Declare>
//////////// <Public Function Declare>
   __gregister_t __gLgcGetTimer(__gregister16_t arSaveT);
   void __gLgcExcuteIns_HelperCompare(__gregister_t arWhat);
   void __gTimerISR(void);
   void __gTimerScanCheck(void);
//////////// </Public Function Declare>

   void __gInitK(void);
   void __glgcExcuteIns_ALD(void);
   void __glgcExcuteIns_LD(void);
   void __glgcExcuteIns_O(void);
   void __glgcExcuteIns_LDN(void);
   void __glgcExcuteIns_C(void);
   void __gINT0(void);

第二段代码是源文件(Source File):

   
//////////// <Public Member Define>
   __gregister16_t   __gTimer[3];
   __gregister16_t   __gTimerTick;
   __gregister16_t   __gTimerTickRec[3];
   __guint8_t         __gI[8];
   __guint8_t         __gQ[8];
   __guint8_t         __gAI[16];
   __guint8_t         __gAQ[16];
   __guint8_t         __gM[128];
   __guint8_t         __gT[32];
   __guint8_t         __gC[16];
   __guint8_t         __gSM[16];
   __guint8_t         __gJ[16];
   __guint8_t         __gK[128];
   __guint8_t         __gL[32];
   __gregister16_t    __gStackData;
   __gregister16_t    __gStackLogic;
   __guint8_t*        __gParPtr[4];
   __gregister_t      __gParBit[4];
   __guint8_t         __gSaveT[32/2];
   __guint8_t         __gSaveC[16/2];
//////////// </Public Member Define>
//////////// <Public Function Define>
   __gregister_t __gLgcGetTimer(__gregister16_t arSaveT) {
       if (arSaveT < 1) return 0;
       if (arSaveT < 5) return 1;
       return 2;
       }
   void __gLgcExcuteIns_HelperCompare(__gregister_t arWhat) {
       __guint8_t lcBit = 0;
       switch (__gBL(arWhat, 5, 0)) {
           case (__gLGC_HELPER_B<<3)|__gLGC_HELPER_EQ:
               lcBit = (__gADDR_VAL_UINT8(0) == __gADDR_VAL_UINT8(1));
               break;
           case (__gLGC_HELPER_B<<3)|__gLGC_HELPER_NE:
               lcBit = (__gADDR_VAL_UINT8(0) != __gADDR_VAL_UINT8(1));
               break;
           case (__gLGC_HELPER_B<<3)|__gLGC_HELPER_GT:
               lcBit = (__gADDR_VAL_UINT8(0) > __gADDR_VAL_UINT8(1));
               break;
           case (__gLGC_HELPER_B<<3)|__gLGC_HELPER_LT:
               lcBit = (__gADDR_VAL_UINT8(0) < __gADDR_VAL_UINT8(1));
               break;
           case (__gLGC_HELPER_B<<3)|__gLGC_HELPER_GE:
               lcBit = (__gADDR_VAL_UINT8(0) >= __gADDR_VAL_UINT8(1));
               break;
           case (__gLGC_HELPER_B<<3)|__gLGC_HELPER_LE:
               lcBit = (__gADDR_VAL_UINT8(0) <= __gADDR_VAL_UINT8(1));
               break;
           case (__gLGC_HELPER_W<<3)|__gLGC_HELPER_EQ:
               lcBit = (__gADDR_VAL_INT16(0) == __gADDR_VAL_INT16(1));
               break;
           case (__gLGC_HELPER_W<<3)|__gLGC_HELPER_NE:
               lcBit = (__gADDR_VAL_INT16(0) != __gADDR_VAL_INT16(1));
               break;
           case (__gLGC_HELPER_W<<3)|__gLGC_HELPER_GT:
               lcBit = (__gADDR_VAL_INT16(0) > __gADDR_VAL_INT16(1));
               break;
           case (__gLGC_HELPER_W<<3)|__gLGC_HELPER_LT:
               lcBit = (__gADDR_VAL_INT16(0) < __gADDR_VAL_INT16(1));
               break;
           case (__gLGC_HELPER_W<<3)|__gLGC_HELPER_GE:
               lcBit = (__gADDR_VAL_INT16(0) >= __gADDR_VAL_INT16(1));
               break;
           case (__gLGC_HELPER_W<<3)|__gLGC_HELPER_LE:
               lcBit = (__gADDR_VAL_INT16(0) <= __gADDR_VAL_INT16(1));
               break;
           case (__gLGC_HELPER_D<<3)|__gLGC_HELPER_EQ:
               lcBit = (__gADDR_VAL_INT32(0) == __gADDR_VAL_INT32(1));
               break;
           case (__gLGC_HELPER_D<<3)|__gLGC_HELPER_NE:
               lcBit = (__gADDR_VAL_INT32(0) != __gADDR_VAL_INT32(1));
               break;
           case (__gLGC_HELPER_D<<3)|__gLGC_HELPER_GT:
               lcBit = (__gADDR_VAL_INT32(0) > __gADDR_VAL_INT32(1));
               break;
           case (__gLGC_HELPER_D<<3)|__gLGC_HELPER_LT:
               lcBit = (__gADDR_VAL_INT32(0) < __gADDR_VAL_INT32(1));
               break;
           case (__gLGC_HELPER_D<<3)|__gLGC_HELPER_GE:
               lcBit = (__gADDR_VAL_INT32(0) >= __gADDR_VAL_INT32(1));
               break;
           case (__gLGC_HELPER_D<<3)|__gLGC_HELPER_LE:
               lcBit = (__gADDR_VAL_INT32(0) <= __gADDR_VAL_INT32(1));
               break;
           }
       switch (__gBL(arWhat, 2, 5)) {
           case __gLGC_HELPER_LD:
               __gStackData <<= 1;
               if (lcBit)
                   __gBST(__gStackData, 0);
               break;
           case __gLGC_HELPER_A:
               if (!lcBit)
                   __gBSF(__gStackData, 0);
               break;
           case __gLGC_HELPER_O:
               if (lcBit)
                   __gBST(__gStackData, 0);
               break;
           }
       }
   void __gTimerISR(void) {
     ++ __gTimerTick;
     }
   void __gTimerScanCheck(void) {
       __gregister16_t lcTickRec;

       lcTickRec = __gTimerTick;

       __gTimer[0] = (lcTickRec - __gTimerTickRec[0]) / 1;
       if (__gTimer[0]) {
           __gTimerTickRec[0] += __gTimer[0] * 1;
           }

       __gTimer[1] = (lcTickRec - __gTimerTickRec[1]) / 10;
       if (__gTimer[1]) {
           __gTimerTickRec[1] += __gTimer[1] * 10;
           }

       __gTimer[2] = (lcTickRec - __gTimerTickRec[2]) / 100;
       if (__gTimer[2]) {
           __gTimerTickRec[2] += __gTimer[2] * 100;
           }
     }
//////////// </Public Function Define>

   void __gInitK(void) {
       }

   void __glgcExcuteIns_ALD(void)
   {
       if (__gBT(__gStackData, 0))
           __gStackData >>= 1;
       else
       {
           __gStackData >>= 1;
           __gBSF(__gStackData, 0);
       }
   }

   void __glgcExcuteIns_LD(void)
   {
       __gStackData <<= 1;
       if (__gADDR_VAL_BIT_BT(0))
           __gBST(__gStackData, 0);
   }

   void __glgcExcuteIns_O(void)
   {
       if (__gADDR_VAL_BIT_BT(0))
           __gBST(__gStackData, 0);
   }

   void __glgcExcuteIns_LDN(void)
   {
       __gStackData <<= 1;
       if (!__gADDR_VAL_BIT_BT(0))
           __gBST(__gStackData, 0);
   }

   void __glgcExcuteIns_C(void)
   {
       __gADDR_VAL_BIT_BS(0, __gBT(__gStackData, 0));
   }

   void __gINT0(void) {
       // NETWORK:
       __gStackData = 0;
       __gStackLogic = 0;

       // INSTRUCTION:
       __gParPtr[0] = &__gI[1];
       __gParBit[0] = 1;
       __glgcExcuteIns_LDN();;

       // INSTRUCTION:
       __gParPtr[0] = &__gI[1];
       __gParBit[0] = 0;
       __glgcExcuteIns_LD();;

       // INSTRUCTION:
       __gParPtr[0] = &__gQ[0];
       __gParBit[0] = 0;
       __glgcExcuteIns_O();;

       // INSTRUCTION:
       __glgcExcuteIns_ALD();;

       // INSTRUCTION:
       __gParPtr[0] = &__gQ[0];
       __gParBit[0] = 0;
       __glgcExcuteIns_C();;

       // NETWORK:
       __gStackData = 0;
       __gStackLogic = 0;

       // NETWORK:
       __gStackData = 0;
       __gStackLogic = 0;

       // NETWORK:
       __gStackData = 0;
       __gStackLogic = 0;

       // NETWORK:
       __gStackData = 0;
       __gStackLogic = 0;

       // NETWORK:
       __gStackData = 0;
       __gStackLogic = 0;

       // NETWORK:
       __gStackData = 0;
       __gStackLogic = 0;

       // NETWORK:
       __gStackData = 0;
       __gStackLogic = 0;

       }

为了避免可能出现的名字冲突(C语言不存在名字空间),GUTTA Ladder To C生成的C代码中所有的宏定义、变量、函数都可以加上一个可自定义的前缀。前缀的名称可以通过修改ManagerSrc.XML文件的<Prefix>字段来实现。软件默认是“__g”,故生成的源代码中,所有的宏、变量、函数都以“__g”开头。

仔细观擦这两个文件的变量申明和定义就能发现,对于特定的变量域,源代码都是按照PLC类型变量的最大可用空间来分配内存的(不论是否真正使用了),因此对于内存比较紧张的系统,可以根据实际的变量使用情况来减小每个变量域的大小,以节约内存。

将上面生成的代码复制下来,分别保存到文件“guttaladder.h”、“guttaladder.c”,然后新建一个“guttamain.c”文件,输入以下代码:

#include <8052.h>
#include "guttaladder.h"
#include "guttaladder.c"

void GetInput();
void SetOutput();

void main(void) {
   while (1) {
       GetInput();
       __gINT0(); // The PLC main loop!
       SetOutput();
       }
   }

void GetInput() {
       // I1.0
       P2_0 = 1;
       __gBS(__gI[1], 0, !P2_0);

       // I1.1
       P2_1 = 1;
       __gBS(__gI[1], 1, !P2_1);
   }

void SetOutput() {
       // Q0.0
       if (__gQ[0] & __gBV(0))
           P2_4 = 0;
       else
           P2_4 = 1;
   }

主函数“main”相对来说比较简单。“GetInput”函数用于将单片机输入管脚状态更新为PLC的输入映像区的值,“SetOutput”函数用于将PLC输出映像区的值更新为单片机输出管脚的状态。单片机在超级循环中,不断的执行获得输入主循环扫描设置输出这三个任务。这就是一个最小的PLC循环周期。

由于没有对PLC的内存进行裁减,目前使用的内存大于8051的片内可用的256字节的内存,故需要使用外部存储器。好在宏晶的单片机IAP12C5A60AD集成了1K字节的外部存储器,我们不需要改变任何硬件,只需要在编译这个项目的时候,加上“--model-large”参数即可:

sdcc --model-large guttamain.c

如果没有任何提示,表示编译成功,用STC-ISP将SDCC生成的“guttamain.ihx”下载到CPU-EC20 (8051),然后按下I1.0键,观察Q0.0是否输出,继续按下I1.1键,观察Q0.0是否继续输出……

例子工程的下载:

   SDCC工程:Sample1_SDCC.zip     SDCC Version 2.9.0

   Keil工程:Sample1_Keil.zip     Keil C51 Version 9.01

上面演示了使用GUTTA Ladder To C的一种最简单的情况,就是用GUTTA Ladder To C来进行逻辑运算,用梯形图来实现位逻辑,是不是比用C语言来实现位逻辑更加直观更好理解呢?不过需要注意的是,这种情况下PLC系统是没有时间概念的,要让PLC实现时间判断的功能(例如应用定时器),就必须给PLC系统加入系统脉搏。

看看下面这个梯形图程序:

在这个程序中,定时器T3计时达到后激活定时器T4开始工作,T4定时到达重置定时器T3。由于T3的延时是0.5秒(50x10ms),T4的延时是1秒(100x10ms),故整个工作周期是1.5秒。M0.1做为T3的输出位,在一个周期中前0.5秒断开,后1秒闭合。最后,我们将中间变量M0.1的值输出到Q0.0,PLC运行后Q0.0应该是闪烁的,且接通的时间比断开的时间要长一倍。

和前面的例子一样,我们将GUTTA Ladder To C生成的代码分别保存在文件“guttaladder.h”、“guttaladder.c”中。然后新建一个文件“guttamain.c”,写入如下内容:

#include <8052.h>
#include "guttaladder.h"
#include "guttaladder.c"

void GetInput();
void SetOutput();

  // 2^16-11.0592*1000/12 (1/1000s)
#define TL0_VALUE   ((64614>>0)&0xff)
#define TH0_VALUE   ((64614>>8)&0xff)

void main(void) {

   ET0 = 1;
   TMOD |= T0_M0;
   TL0 = TL0_VALUE;
   TH0 = TH0_VALUE;
   TR0 = 1;
   EA = 1;

   __gInitK();
   while (1) {
       GetInput();
       __gTimerScanCheck();
       __gINT0(); // The PLC main loop!
       SetOutput();
       }
   }

void GetInput() {
       // I1.0
       P2_0 = 1;
       __gBS(__gI[1], 0, !P2_0);

       // I1.1
       P2_1 = 1;
       __gBS(__gI[1], 1, !P2_1);
   }

void SetOutput() {
       // Q0.0
       if (__gQ[0] & __gBV(0))
           P2_4 = 0;
       else
           P2_4 = 1;
   }

void ISR_TIMER0_OVF(void) __interrupt(1) __using(0) {
   TL0 = TL0_VALUE;
   TH0 = TH0_VALUE;
   __gTimerISR();
   }

这个程序和前面的略有不同。首先,在“main”函数进入超级循环(死循环)之前,调用了软件提供的函数“__gInitK()”,这是因为在梯形图中,T3和T4的参数都包含了常数(整数50和整数100),系统在进入主循环扫描之前,必须先初始化这些常数。

为了给PLC系统加入脉搏,系统在进入主循环扫描之前,初始化单片机的定时器T0每1ms产生一个中断。而在中断中,调用了软件提供的中断服务“__gTimerISR”。在每次开始主循环扫描前,调用了函数“__gTimerScanCheck”,用于计算上次主循环扫描和当前主循环扫描的时间差。

和前面一样,调用SDCC编译器编译我们的程序:

sdcc --model-large guttamain.c

如果没有任何提示,表示编译成功,用STC-ISP将SDCC生成的“guttamain.ihx”下载到CPU-EC20 (8051),观察Q0.0是否周期性的闪烁呢?

例子工程的下载:

   SDCC工程:Sample2_SDCC.zip     SDCC Version 2.9.0

   Keil工程:Sample2_Keil.zip     Keil C51 Version 9.01

GUTTA Ladder To C 相关下载

   GUTTA Ladder To C 软件下载