【Codesys-Runtime】-开发第一个外部库组件
在上篇文章中,初步看了一下 Codesys 方案中,下位机中的 Runtime 运行时包括了哪些组件,以及一个组件中最基本的代码结构是长什么样子的。今天继续这个话题,主要包括下面几个内容:[*]什么是外部库?
[*]开发一个外部库的宏观步骤有哪些?
[*]开发第一个外部库的详细步骤
(1)在 Linux 开发环境中生成编译模板;(2)在 IDE 中创建 IEC Library 工程,并导出 .c 和 .m4 文件;(3)在 Linux 开发环境中修改代码,编译,得到组件;(4)在 Runtime 中部署、注册组件;(5)在 IDE 中创建一个应用程序工程,验证组件是否正常工作;
01-外部库与组件Codesys 平台中 IEC 库的概念很简单,就是把一系列相同类型的指令进行封装,放在一个独立的 .library 文件中。然后终端用户可以导入这个库文件,并且调用库中的指令。首先,Codesys 官方就封装了很多的指令库,包括:Standard 库中包括了定时器、触发器、计数器等各种基本指令;SM3_Basic库中封装了运动控制、凸轮等各种运控相关指令。其次,PLC 厂家基于提高产品的易用性、功能性等角度,也会提供一些库,来提高产品的竞争优势。包括:一些行业相关的工艺库、对 Codesys 官方提供的运动控制相关的库进行二次封装,使用更便捷。以上所有这些库,都是需要进行代码开发的。在 IEC 库的开发过程中:1. 如果只使用到 IEC61131 规定的那几种编程语言来实现,一般就认为是普通的 IEC 库;2. 如果需要在下位机中进行一些配合,也就是:需要使用 C 语言开发一个组件,来配合这个 IEC 库中的指令完成特定的功能,那么这个库一般称作外部库。所以,简单来理解:外部库 = 上位机中的IEC Library + 下位机中的组件。IEC 库,一般后缀名是 .compiled_library,表示编译后的库,二进制格式。
可能有些同学会有这样一个疑问:
SDK是用来开发组件的,怎么又与外部库扯上关系了?可以这么来理解:
[*]组件是运行在 PLC 下位机中的,被 Runtime 这个可执行程序加载、执行。
[*]组件可以实现任何想要的功能,例如:可以用来监控 Linux 操作系统的各种资源使用情况,与其它进程进行通信等等,这样的组件与外部库就没有什么关系。
[*]组件也可以用来为 IEC Library 中特定的指令服务,例如:你在 IDE 界面中设置 PLC 的系统时间,那么在下位机的某个组件中一定存在一个相关的函数,来具体执行这条指令:把时间信息通过操作系统 API 函数写入到时钟硬件中。
刚开始,我是打算写一个独立的、纯粹的下位机组件的,但是这样就完全脱离了上位机的操作,全部是一些比较枯燥、无味的 C 代码。所以咱们先按照官方提供的 README 文档里的开发流程,把组件与 IEC 库指令进行结合,先从开发步骤上搞清楚组件的开发流程,以后有机会再深入介绍组件里的代码开发。有时候 Linux 开发环境与 PLC 下位机的 Runtime 执行环境,是在两台不同的 Linux 系统中,所以本文在描述文字上进行了区分。但是,在我的测试环境中,Linux 开发环境与 Runtime 运行环境,是在同一台 x86 Ubuntu 操作系统中。
02-开发外部库的宏观步骤之前的文章提到过 Codesys 提供的 SDK 中相关的文件内容,当时只看了 include 目录下的头文件,以及 src 目录下一个组件中 C 代码的基本结构。其实在 SDK 目录下,还有一个重量级的文档 README ,其中详细描述了开发一个外部库的详细步骤。这篇文章的主要操作流程与 README 里的描述过程几乎是一致的,所以如果想深入掌握这部分内容,可以先照着官方的 README 文档操作一遍,然后再看一下这篇文章,感触可能会更深一些。为了便于在大脑中对开发过程在宏观流程上有一个初步概念,我先把可能会存在的疑问解释一下。在本文刚开始的引言部分看到,在开发过程中:一会儿在 Linux开发环境下操作,一会儿在上位机 IDE 中操作,一会儿又在 Runtime 环境下操作。为什么要这么繁琐呢?主要是因为:
[*]Codesys 方案中,IEC Library 指令库中向用户提供的是 ST 语言的接口(当然也可以是其它语言),但是组件中的代码是 C 语言实现的。
[*]ST 语言的代码需要调用 C 语言的实现函数,因此需要能“调用到” C 语言函数,并且把参数正确传递给 C 语言函数。
上述的这个转换、调用逻辑是很复杂的,需要满足 Codesys 设计的一套规则才能正常工作。为了降低操作过程的复杂度,Codesys IDE 给我们提供了相关的工具,让这个过程变得简单、不容易出错。下面进入具体的操作步骤,每个步骤中的小标题序号,与 README 文档中是对一一对应的,方便对照查看。
03-步骤一:在 Linux 开发环境中生成编译模板(1)在 Linux 开发环境中安装必要的开发工具sudo apt-get install build-essential m4 sed dos2unix make
(2)把 ExtensionSDK 复制到 Linux 系统中在安装了 CodesysIDE 开发环境和对应的 package 包之后,可以在下面这个位置找到官方提供的 SDK:CODESYS 3.5.20.0\CODESYS\CODESYS Control SL Extension Package\4.16.0.0\ExtensionSDK需要把这个 SDK 手动放到 Linux开发环境中。如果使用的是虚拟机,可以通过共享文件夹复制过去即可。我这里放置的路径是:/root/codesys/ExtensionSDK
(3)在 ExtensionSDK 同级目录,创建一个文件夹,表示待开发的组件例如:/root/codesys/CmpFirstTest
(4)利用 ExtensionSDK 创建一个新的组件项目命令如下:cd /root/codesys/CmpFirstTestmake -f ../ExtensionSDK/makefile newproject这个操作的本质是:生成一个 makefile 文文件,内容如下(删掉了注释):MAJORVER=0
MINORVER=1
CMPVERSION=0x01000000
CMPID=0x2000
DOS2UNIX = dos2unix
M4 = m4
CC = gcc
#CFLAGS += -g
#INCLUDES += -I.
#LDFLAGS +=
#LDLIBS += -lc
SDKDIR=/root/codesys/ExtensionSDK
include ${SDKDIR}/makefile到目前为止,文件目录如下所示:
04-步骤二:在 IDE 中创建 IEC Library 工程,并导出 .c 和 .m4 文件(1)在上位机 IDE 中创建一个IEC Library库,名称是:CmpFirstTest
(2)添加两个指令当库工程创建之后,在 Function 目录下创建两个指令:MY_ADD1, MY_ADD2。这两个指令都是用来进行算术相加,但是第一条指令直接在 ST 代码里实现相加动作,第二条指令需要调用下位机中组件中的函数来实现相加动作。MY_ADD1指令的添加:
MY_ADD1指令的实现如下:
MY_ADD2指令的添加:(注意这里的指令名称后缀,参考一下 README 说明)
另外,需要把 MY_ADD2 指令(或者称作函数)设置为外部的(extern):
从上图的说明中也可以看到:外部实现(E),(稍后在 runtime 系统中链接)。MY_ADD2指令的实现如下图:可以看到:只有变量区的“声明”,代码实现部分是空的。因为指令实现被设置为外部的,所以在被调用的时候,会执行下位机组件的 .c 文件中对应的函数。这里,再注意一下库工程信息如下,在验证阶段导入 .library 库时可以看到匹配的标题、版本、缺省名称等信息。这些内容,都是可以改动的,比如:改为你们公司或个人的名称,目前先保持默认内容即可。
(3)产生外部库相关文件在 IDE 中的【编译】菜单下执行两个动作:【检查所有池对象】和【生成运行时系统文件】。【检查所有池对象】主要作用是检查 ST 代码中是否有错误。【生产运行时系统文件】:就是生成组件的基本代码,这一步也是核心操作!输出目录默认就是创建 IEC 库时的工程所在目录,注意勾选两个 Checkbox 复选框:如果执行没有报错,那么可以看到生成了下面几个文件:(4)把生成的文件(*.c and .m4 file)放到 Linux 开发环境中
05-步骤三:编译、部署组件
经过以上的操作步骤之后,在 Linux 开发环境中就有了组件的两个最重要内容:
[*] SDK
[*]组件代码 CmpFirstTest
查看一下 CmpFirstTest.c 中的初始内容:看起来很简单,就是三部分内容:
[*]包含几个头文件;
[*]定义一个函数(注意:函数名与指令名对应关系);
[*]函数的参数:一个结构体指针类型;
结构体类型的定义在 CmpFirstTestitf.m4 文件中:可以观察一下这个结构体中的成员变量:a 和 b 对应 ST 中指令的两个输入参数,MY_ADD2_cext 对应 ST 中指令的返回值。DEF_API 宏定义看起来也是挺怪异的,目前看看就可以,不要深究。我们把 CmpFirstTest.c 文件中函数体的代码修改为如下内容:
(1)编译组件 CmpFirstTestmake all编译指令输出信息如下:可以看到:最终生成的文件都在 out 目录下,最重要的就是 libCmpFirstTest.so 文件,它是一个 Linux 系统中标准的动态链接库,其它生成的文件都是一些中间文件。
(2)观察 out 目录下的生成文件如果你感兴趣,可以打开两个 .h 文件看一下,初始阶段不需要深究。
(3) 部署组件 libCmpFirstTest.so把.so文件放到 Runtime 的 lib 目录下。我的测试环境中是:/opt/codesyscontrol/lib/ 目录。
(4)停止 codesyscontrol README手册中执行的指令是:sudo /etc/init.d/codesyscontrol stop。因为我对 codesyscontrol 的固件目录进行了小小的调整,所以大家根据自己的情况来操作。最笨的办法就是通过 ps 指令找到 codesyscontrol 进程的 pid,然后使用 kill 指令来杀掉此进程。
(5)注册组件 libCmpFirstTest.so把这个组件登记到下面这个配置文件中:
/opt/codesyscontrol/config/CODESYSControl_User.cfg我的目录整理过,与默认的目录有一点不同。配置项如下所示:注意:
[*]在 这个 Section 下的每一行键值对,如果开头是分号“;”,表示这一行配置的组件是不生效的。
[*]每一个 Component. 后面的数字需要连续。比如:如果我把配置改成 Component.8=CmpFirstTest,那么这行配置就不会生效,因为从 6 到 8 不连续。
(6)启动 codesyscontrol这一步也没有什么好说的,执行启动脚本即可。
(7)验证 CmpFirstTest 组件被成功加载可以在启动打印信息中 或者 日志文件中看到下面这行信息,就说明组件被正确加载、执行了。<cmp>CmpFirstTest</cmp>, <id>0xffff2000</id><ver>1.0.0.0</ver>
06-步骤四:调用指令,验证外部库(1)在 IDE 开发环境中安装 IEC Library 库:CmpFirstTest.library 操作过程:【库存储】-【安装】,找到在步骤一中生成的 .library 文件。安装之后,可以在【杂项】中看到这个库,可以在【详细信息】中看到更加具体的信息。
(2)创建一个App,添加 IEC Library 库可以看到:
[*]名称、命名空间、版本等信息,都与前文中的描述一致;
[*]Function 目录下,有两个添加的指令;
(3)编写代码,调用两个 ST 指令
(4)下载 Application,执行RUN可以看到 result1 和 result2 的计算结果都是正确的,说明 CmpFirstTest 组件中的函数被正确执行了。再观察下位机的日志输出,可以看到组件中函数的打印信息,说明执行了来自 Application 的指令调用,如下:
至此,开发一个外部库组件的全部过程就介绍结束了,皆大欢喜!作为项目工程师,你一定理解、明白了 IEC 库中的一条指令到底是如何被 PLC 执行的;作为嵌入式软件工程师,你也理解了 Runtime 中的组件是如何开发的,既然已经到了 C 语言层面,那么理论上就可以做任何事情了。最后再说明两点:
[*]在 Codesys 官方的在线文档中,也有一份文档来描述外部库的开发过程,不过其中的部署环节是通过 IDE 把组件下载传输到下位机 PLC 中。这个过程与本文描述的原理是一致的,目的都是把 .so 文件放到下位机中,然后向 Runtime 运行时进行登记。只不过本文描述的过程(或者说,SDK 中 README 文档中描述的操作过程),是通过手动进行操作的。
[*]我们使用的 SDK,是 Codesys IDE 中提供的一个简化版本。使用 Codesys 方案的 PLC 厂家,一般会购买 Codesys Runtime Toolkit,里面会包含更多的内容,如下:
[*]
页:
[1]