02-C语言实现一个简单PLC仿真小程序(下)

程序主体

第一步,要在控制台上实现一个初始画面,假定这个仿真PLC具有8个输入点,8个输出点,为X0-X7,Y0-Y7,我希望实现如图1的界面:


图1.PNG

第一行的 * 表示 数字量输入点 的信号,同时也表示该点的指示灯,第二行 * 表示 数字量输出点 的信号。
需要使用到 printf()函数,代码如下:

#include <stdio.h>

void initial()
{
    printf("########################################\n");
    printf("\n");
    printf("\n");
    printf("             PLC SIMULATOR              \n");
    printf("\n");
    printf("\n");
    printf("            * * * * * * * *             \n");
    printf("          # 0 1 2 3 4 5 6 7 #           \n");
    printf("          #                 #\n");
    printf("          #                 #\n");
    printf("          # 0 1 2 3 4 5 6 7 #           \n");
    printf("            * * * * * * * *             \n");
    printf("\n");
    printf("\n");
    printf("****************************************");
}

int main()
{
    initial();
    //gotoxy(0, 0);

    return 0;
}

接下来,定义两个长度为8的数组,以这两个数组作为输入输出的数据存储区,如下:

int input[8];
int ouput[8];

当值为 0 时表示对应的 IO 点的信号为 FALSE,当值为 1 时表示对应的 IO 点的信号为 TRUE;
同时,将 initial() 函数修改为用于打印显示的 show() 函数,并重写一个 initial() 函数来进行数据存储区的初始化,然后调用 show() 函数进行打印。
另外,也要编写一个 gotox() 函数来将光标移动到界面的第一行第一个列,这样在循环打印时就相当于完成了界面的刷新,程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

// IO array
int input[8];
int output[8];

void show()
{
    system("cls");

    printf("########################################\n");
    printf("\n");
    printf("\n");
    printf("             PLC SIMULATOR              \n");
    printf("\n");
    printf("\n");
    printf("            ");
    for(int i=0; i<8; i++)
    {
        if(!input[i])
            printf("  ");
        else
            printf("* ");
    }
    printf("             \n");
    printf("          # 0 1 2 3 4 5 6 7 #           \n");
    printf("          #                 #\n");
    printf("          #                 #\n");
    printf("          # 0 1 2 3 4 5 6 7 #           \n");
    printf("            ");
    for(int i=0; i<8; i++)
    {
        if(!output[i])
            printf("  ");
        else
            printf("* ");
    }
    printf("             \n");
    printf("\n");
    printf("\n");
    printf("****************************************");
}

void initial()
{
    for(int i=0; i<8; i++)
    {
        input[i]= 0;
        output[i]= 0;
    }

    show();
}

int main()
{
    initial();

    return 0;
}

运行后的初始化界面如图2:


图2.PNG

因为程序在控制台上执行,所以如果要做按键监控来监测输入信号比较麻烦,所以这里不做监测,仅简单的等待键盘输入信号即可。
首先实现一个按键的信号,程序如下:

#include <stdio.h>  // printf()
#include <stdlib.h>  // system()
#include <conio.h>  // getch()

#include <windows.h>

// IO array
int input[8];
int output[8];

void show()
{
    system("cls");

    printf("########################################\n");
    printf("\n");
    printf("\n");
    printf("             PLC SIMULATOR              \n");
    printf("\n");
    printf("\n");
    printf("            ");
    for(int i=0; i<8; i++)
    {
        if(input[i])
            printf("* ");
        else
            printf("  ");
    }
    printf("             \n");
    printf("          # 0 1 2 3 4 5 6 7 #           \n");
    printf("          #                 #\n");
    printf("          #                 #\n");
    printf("          # 0 1 2 3 4 5 6 7 #           \n");
    printf("            ");
    for(int i=0; i<8; i++)
    {
        if(output[i])
            printf("* ");
        else
            printf("  ");
    }
    printf("             \n");
    printf("\n");
    printf("\n");
    printf("****************************************\n");
}

void initial()
{
    for(int i=0; i<8; i++)
    {
        input[i] = 0;
        output[i] = 0;
    }

    show();
}

int checkInput(int inSignal)
{
    inSignal = inSignal - 48;
    if(inSignal<0 || inSignal>7)
        return 0;
    input[inSignal] = !input[inSignal];

    return 1;
}

int main()
{

    initial();

    int inSignal;
    int tmp=1;
    inSignal = getch();
    tmp = checkInput(inSignal);
    show();

    return 0;
}

以上程序仅实现输入信号的打印,下一步是实现输入信号接通,对应序号的输出信号也接通的效果,类似于三菱PLC执行程序:

LD X0
OUT Y0
LD X1
OUT Y1
···
LD X7
OUT Y7

实现代码如下:

#include <stdio.h>  // printf()
#include <stdlib.h>  // system()
#include <conio.h>  // getch()

#include <windows.h>

// IO array
int input[8];
int output[8];

void show()
{
    system("cls");

    printf("########################################\n");
    printf("\n");
    printf("\n");
    printf("             PLC SIMULATOR              \n");
    printf("\n");
    printf("\n");
    printf("            ");
    for(int i=0; i<8; i++)
    {
        if(input[i])
            printf("* ");
        else
            printf("  ");
    }
    printf("             \n");
    printf("          # 0 1 2 3 4 5 6 7 #           \n");
    printf("          #                 #\n");
    printf("          #                 #\n");
    printf("          # 0 1 2 3 4 5 6 7 #           \n");
    printf("            ");
    for(int i=0; i<8; i++)
    {
        if(output[i])
            printf("* ");
        else
            printf("  ");
    }
    printf("             \n");
    printf("\n");
    printf("\n");
    printf("****************************************\n");
}

void initial()
{
    for(int i=0; i<8; i++)
    {
        input[i] = 0;
        output[i] = 0;
    }

    show();
}

int checkInput(int inSignal)
{
    inSignal = inSignal - 48;
    if(inSignal<0 || inSignal>7)
        return 0;
    input[inSignal] = !input[inSignal];

    return 1;
}

void programRun()
{
    for(int i=0; i<8; i++)
    {
        output[i] = input[i];
    }
}

int main()
{

    initial();

    int inSignal;
    int tmp=1;
    inSignal = getch();
    tmp = checkInput(inSignal);
    programRun();
    show();

    return 0;
}

到此,程序在按下0-7按键之后,对应的输入、输出点会接通,但是还无法循环执行,现在将程序修改为循环执行的程序,按下0-7后对应的点会取反,按下其他按键后程序退出,仅需要修改 main()函数,如下:

int main()
{

    initial();

    int inSignal;
    int tmp=1;
    while(tmp)
    {
        inSignal = getch();
        tmp = checkInput(inSignal);
        programRun();
        show();
    }

    return 0;
}

解释器

接下来是程序语句解释器的部分。
以指令表(IEC61131-3标准中称为 IL 语言)为基础,定义 FNC值 与 指令助记符 的对应关系,如下表:
· 0 - END
· 1 - LD
· 2 - OUT
目前先实现这两个指令的解释器,来将上一步的程序完善好。
作为测试程序,这里就不采用申请内存空间的做法来定义程序存储区,而是直接用一个长度为 100 的数组替代,如:int proData[100];
按三菱 PLC 的编程习惯,取 X0 的指令为 LD X0,输出 Y0 的指令为 OUT Y0,那么它们的数据应该分别为 1, x2, x
这里有个问题,如果直接将 X0 和 Y0 都定为 0,这样是不行的,所以要么将指令扩展为 1, 0, 02, 1, 0 来区分 X0 和 Y0,要么是像 PLC 中的存储一样,将IO点映射到内存中,就像 0000~0007 表示 X0X7,00080015表示 Y0~Y7 这样的定义方式。
这里采用第一种方式,即三个数据,一个表示指令,一个表示数据类型,一个表示数据。
· 0 - 输入位
· 1 - 输出位
这样,我们要写一个输入映射到输出的程序,指令表的程序代码为:

LD X0
OUT Y0
LD X1
OUT Y1
LD X2
OUT Y2
LD X3
OUT Y3
LD X4
OUT Y4
LD X5
OUT Y5
LD X6
OUT Y6
LD X7
OUT Y7

如果写一个编译器,编译出来的程序应该是这样的:

1 0 0
2 1 0
1 0 1
2 1 1
1 0 2
2 1 2
1 0 3
2 1 3
1 0 4
2 1 4
1 0 5
2 1 5
1 0 6
2 1 6
1 0 7
2 1 7

为了方便测试起见,直接定义程序代码,为:

int programZrea[1000] = {
    1, 0, 0,
    2, 1, 0,
    1, 0, 1,
    2, 1, 1,
    1, 0, 2,
    2, 1, 2,
    1, 0, 3,
    2, 1, 3,
    1, 0, 4,
    2, 1, 4,
    1, 0, 5,
    2, 1, 5,
    1, 0, 6,
    2, 1, 6,
    1, 0, 7,
    2, 1, 7
};

这个就作为程序存储区,相当于编译好的PLC程序。

然后为了翻译程序语句,实现一个虚拟机功能,按汇编语言的处理方式,需要定义一个寄存器,如下:

int regA;

仿真工作的顺序是,先判断输入区信号,然后扫描PLC程序,这里使用 programRun() 函数来读取PLC程序用于扫描,扫描PLC程序的时候按行执行,即读一行程序,然后调用解析器函数 Language(int command, int type, int data),返回后再读下一行程序,这样循环,直到解析器判断到读取的程序指令是 0,表示 END,结束循环,programRun() 函数返回。
解析器函数 Language(int command, int type, int data) 先判断第一个数据,即指令,根据指令调用对应的处理函数。
指令 0 表示 END,程序结束,直接返回;
指令 1 表示 LD 指令,调用取指令处理函数 LDcommand(int type, int data),该函数返回取得的数值;
指令 2 表示 OUT 指令,调用输出指令处理函数 OUTcommand(int type, int data),如果正常返回 0,如果错误返回 1。
这部分的程序代码如下:

// 取指令处理函数
int LDcommand(int type, int data)
{
    int reData;

    switch(type)
    {
        case 0: reData = input[data]; break;  // 取输入寄存器的数值
        case 1: reData = output[data]; break; // 取输出寄存器的数值
        default: reData = 0;
    }

    return reData;
}

// 输出指令处理函数
int OUTcommand(int type, int data)
{
    int reData = 0;

    switch(type)
    {
        case 0: reData = 1; break;  // 输入寄存器的数值是不能修改的
        case 1: output[data] = regA; break; // 将寄存器A的数值输出到输出寄存器
        default: reData = 1;
    }

    return reData;
}

// 解析器
int Language(int command, int type, int data)
{
    int endFlag = 1;
    switch(command)
    {
        case 0: endFlag = 0; break; // END 指令
        case 1: regA = LDcommand(type, data); break; // LD 取指令 调用 取指令处理函数
        case 2: OUTcommand(type, data); break; // OUT 输出指令 调用 输出指令处理函数

        default: endFlag = 0;
    }
    return endFlag;
}

// 扫描程序
void programRun()
{
    int pointToPro = 0;
    int proCommand;
    int proType;
    int proData;

    int endFlag = 1;
    while(endFlag)
    {
        // 取一行程序
        proCommand = programZrea[pointToPro];
        proType = programZrea[pointToPro+1];
        proData = programZrea[pointToPro+2];

        // 调用解析器
        endFlag = Language(proCommand, proType, proData);

        // 指针下移
        pointToPro = pointToPro + 3;
    }
}

最后一步是加入文件读取功能,完成后的整体程序如下:

#include <stdio.h>  // printf()
#include <stdlib.h>  // system()
#include <conio.h>  // getch()
#include <windows.h>

// 输入输出存储区
int input[8];
int output[8];

// 程序存储区
int programZrea[1000] = {
    1, 0, 0,
    2, 1, 0,
    1, 0, 1,
    2, 1, 1,
    1, 0, 2,
    2, 1, 2,
    1, 0, 3,
    2, 1, 3,
    1, 0, 4,
    2, 1, 4,
    1, 0, 5,
    2, 1, 5,
    1, 0, 6,
    2, 1, 6,
    1, 0, 7,
    2, 1, 7
};

int regA;  // 寄存器A

// 刷新屏幕函数
void show()
{
#ifdef _WIN32
    system("cls");
#endif

    printf("########################################\n");
    printf("\n");
    printf("\n");
    printf("             PLC SIMULATOR              \n");
    printf("\n");
    printf("\n");
    printf("            ");
    for(int i=0; i<8; i++)
    {
        if(input[i])
            printf("* ");
        else
            printf("  ");
    }
    printf("             \n");
    printf("          # 0 1 2 3 4 5 6 7 #           \n");
    printf("          #                 #\n");
    printf("          #                 #\n");
    printf("          # 0 1 2 3 4 5 6 7 #           \n");
    printf("            ");
    for(int i=0; i<8; i++)
    {
        if(output[i])
            printf("* ");
        else
            printf("  ");
    }
    printf("             \n");
    printf("\n");
    printf("\n");
    printf("****************************************\n");
}

// 初始化函数
void initial()
{
    for(int i=0; i<8; i++)
    {
        input[i] = 0;
        output[i] = 0;
    }

    // 初始化完成后要刷新一次
    show();
}

// 检查输入
int checkInput(int inSignal)
{
    inSignal = inSignal - 48;
    if(inSignal<0 || inSignal>7)
        return 0;
    input[inSignal] = !input[inSignal];

    return 1;
}

// LD 指令执行函数
int LDcommand(int type, int data)
{
    int reData;

    switch(type)
    {
        case 0: reData = input[data]; break;
        case 1: reData = output[data]; break;
        default: reData = 0;
    }

    return reData;
}

// OUT 指令执行函数
int OUTcommand(int type, int data)
{
    int reData = 0;

    switch(type)
    {
        case 0: reData = 1; break;  // input register cannot out
        case 1: output[data] = regA; break; // out value to output register
        default: reData = 1;
    }

    return reData;
}

// 解析器
int Language(int command, int type, int data)
{
    int endFlag = 1;
    switch(command)
    {
        case 0: endFlag = 0; break;
        case 1: regA = LDcommand(type, data); break;
        case 2: OUTcommand(type, data); break;

        default: endFlag = 0;
    }
    return endFlag;
}

// 扫描器
void programRun()
{
    int pointToPro = 0;
    int proCommand;
    int proType;
    int proData;

    int endFlag = 1;
    while(endFlag)
    {
        proCommand = programZrea[pointToPro];
        proType = programZrea[pointToPro+1];
        proData = programZrea[pointToPro+2];

        endFlag = Language(proCommand, proType, proData);

        pointToPro = pointToPro + 3;
    }
}

// 读取程序文件
int openPro()
{
    char path[50];
    printf("Please Input Your Program File Path: ");
    scanf("%s", &path);

    FILE *fp;
    if((fp=fopen(path, "r"))== NULL)
        return 1;

    char ch;
    int num;
    int proPoint = 0;
    while((ch=fgetc(fp))!=EOF)
    {
        num = ch - 48;
        if(num>9 || num<0)
            continue;

        programZrea[proPoint] = num;
        proPoint++;
    }
    
    fclose(fp);
    return 0;
}

int main()
{
    // 打开文件
    int reError =openPro();
    while(reError)
    {
        printf("Path ERROR!\n");
        printf("Please Input a Program File Path: ");
        reError =openPro();
    }
    // 初始化
    initial();

    int inSignal;
    int tmp=1;
    while(tmp)
    {
        // 获取按键信号
        inSignal = getch();
        // 检查输入
        tmp = checkInput(inSignal);
        // 扫描 PLC 程序
        programRun();
        // 刷新
        show();
    }

    return 0;
}

然后,写一个 txt 文档作为 PLC 程序,内容为:

1, 0, 0,
2, 1, 0,
1, 0, 1,
2, 1, 1,
1, 0, 2,
2, 1, 2,
1, 0, 3,
2, 1, 3,
1, 0, 4,
2, 1, 4,
1, 0, 5,
2, 1, 5,
1, 0, 6,
2, 1, 6,
1, 0, 7,
2, 1, 7,
2, 1, 4

执行程序进行测试,输入 PLC程序 文件路径,通过 0-7 按键来接通、断开输入信号,修改 PLC程序,可以看到 PLC逻辑也产生对应的变化。

程序和源码:PLC SIMULATOR

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,175评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,674评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,151评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,597评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,505评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,969评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,455评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,118评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,227评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,213评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,214评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,928评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,512评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,616评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,848评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,228评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,772评论 2 339

推荐阅读更多精彩内容