本文为ProfiNet介绍文章《嵌入式环境 实现ProfiNet/PN工业以太网》的应用实例,使用ESM7000+ETA104+UR205PN
如图1,将ETA104V5和UR205PN,以及EM7000核心板,安装至SBC884底板
图1
如图2,连接编程电脑,交换机,组态用PC,及SBC884板卡系统。
图2,系统组成
将GSD压缩文件解压并放置到任意目录中,目录中有如下三个文件
打开博图,并在菜单栏中,通过“选项->管理通用站点描述文件”进入gsd管理。
选择第一步GSD文件的解压路径,并勾选文件,安装。
完成后,可以在博图的硬件目录中,“其他现场设备-> PROFINETIO -> I/O-> UreateAutomatic -> VARIABLE I/O ->前端模块”中找到新添加的设备。
首先新建一个博图项目,然后在拓扑视图中,添加PLC,交换机,和PN模块。我这里选择的PLC是1211C。而模块的位置如下图:
将PLC和UR205PN模块连接起来。
然后转到网络视图中,添加他们的连接关系,如图:
接着双击UR205PN模块,进入设备视图,并在设备插槽中,添加4个数据交换模块
设备插槽中插入数据交换模块,数据交换模块中插入数据对象。数据对象可以从“1/2/4字节输入”或者“1/2/4字节输出”中选择。
接下来分别为每个数据模块添加数据对象,如下:
分别是1个1字节输入(两路DI输入),1个1字节输出(对应一路继电器输出),两个2字节输入(对应4-20MA输入)。
这里添加的四个模块,对应的是ETA104模块的输入输出通路。其对应关系如下:
ETA104 | UR205PN | 信息备注 |
PKW module_1 | PKW问询式通信通道 | |
1路继电器输出 | inpupt 1 byte(bit0) | 继电器0=数据位0 |
2路空触头输入 | Output 1byte(bit0 ,bit1) | 输入0= 数据位0 输入1= 数据位1 |
电流输入1 | Input 2 byte | 16位电流输入值 |
电流输入2 | Input 2 byte | 16位电流输入值 |
为模块添加变量。
首先我们要找到UR205PN各模块的数据地址。
在博图的设备视图中选择UR205PN模块,并点击各模块,即可查看模块数据在PLC中的位置.例如下图展示的是PKW模块的示意图:
上图中,说明PKW模块的输入,是从PLC数据地址68到75,共8个字节。PKW模块的输出,是从64到71,共8个字节。
以此类推,可以找到所有数据的地址.本例中数据地址如下表
数据模块 | 输入地址 | 输出地址 |
PKW | 68-75 | 64-71 |
DI :Input 1 byte | 76 | -- |
Do:Output 1byte | -- | 72 |
AI0:Input 2 byte | 77,78 | -- |
AI1:Input 2 byte | 79,80 | -- |
根据数据表,我们在PLC中建立如下变量表:
建立项目的强制列表,该表用于设置输出值。
建立项目的监视表,该表用于读取输入值。
数据变量分为两个区域:PKW 和 周期通信。 周期通信比较好理解,就是每次通信都会交换的周期数据,也是我们在插槽中插入的模块数据:如DI,DO,AI,AO。
PKW_开头的数据对象,则属于PKW访问过程。该访问基于一问一答,过程和定义如下:
所以,PLC如果要发起PKW通信,需要如下步骤:
1. PKW_RW_req 设为“0:读” 或者 “1:写”
2. PKW_INDEX_req 设为需要访问的数据索引
3. PKW_VALUE_req 如果为写操作,这里需要输入写入的值
4. PKW_COUNT_req 自增1,以表明是新的PKW访问,触发PN操作
当接收数据时:
1. 等待PKW_COUNT_rsp与PKW_COUNT_req一致
2. 对比回应的INDEX是否等于请求INDEX
3. 若是读取操作的回应帧,则获取PKW_VALUE_rsp
完成后将组态编译并下载到PLC。
本案例使用的嵌入式编译开发环境式如下:
操作系统 | WIN11 64位 |
虚拟机操作系统 | WSL+Ubuntu-20.04 |
开发环境 | VSCODE WSL 远程连接 |
C编译链接 | arm-poky-linux-gnueabi-gcc |
其搭建过程可参考如下文档:《VSCODE嵌入式编译环境搭建》
PC连接嵌入式板的过程,参考英创工控板使用说明。
解压缩案例代码:test_DIO_pn.rar。
通过VSCODE ,WSL方式远程打开该项目。
项目的文件组成如下图所示:
对应用层而言,通信都是建立在周期数据交换帧上。其内部划分为两个功能区:PKW,和周期通信区。这两个功能区在周期通信帧中,他们的位置如下:
这两个功能区,对应了嵌入式内的两种通信对象:PKW索引访问对象和周期通信访问对象。
PKW: 数据以<索引-值>的方式存放在嵌入式端,用户可以为某个参数指明特定的索引号,PLC通过问答方式,访问该索引。这种方式适用于交换时间不敏感的数据。例如一些参数。其通信帧定义如下:、
周期通信对象:数据每个通信周期都会交换一次,适合时间敏感的通信对象,往往都是一些输入输出值,如DI,DO,AI,AO等。其在帧中定义如下:
在案例中,用户代码区域主要有以下代码段需要维护:
1. 周期通信模块定义
uint8_t UMList[4]={ //模块类型 模块数据存储区 MODULE_1BYTE_INPUT, //1个字节的DI输入 MODULE_1BYTE_OUTPUT, //1个字节的DO输出 MODULE_2BYTE_INPUT, //16位AI输入 MODULE_2BYTE_INPUT //16位AI输出 };
用户通过该数组定义周期通信模块的数量和类型。本案例中,模块如下表:
模块序号 | 模块类型 | 模块描述 | 对应数据对象 |
0 | MODULE_1BYTE_INPUT | 一个字节的输入模块 | BIT0 = DI0 BIT1 = DI1 |
1 | MODULE_1BYTE_OUTPUT | 一个字节的输处模块 | BIT0=DO0 |
2 | MODULE_2BYTE_INPUT | 两个字节的输入 | 4-20MA输入1 |
3 | MODULE_2BYTE_INPUT | 两个字节的输入 | 4-20MA输入2 |
我们目前支持的模块类型有:
#define MODULE_1BYTE_INPUT 0X01 //1字节输入 #define MODULE_2BYTE_INPUT 0X02 //2字节输入 #define MODULE_4BYTE_INPUT 0X04 //4字节输入 #define MODULE_1BYTE_OUTPUT 0X81 //1字节输出 #define MODULE_2BYTE_OUTPUT 0X82 //2字节输出 #define MODULE_4BYTE_OUTPUT 0X84 //4字节输出
(注意这里的输出是以PLC为主站视角,从PLC到嵌入式为输出;从嵌入式到PLC为输入。)
2. PKW索引访问区域模块定义
sPKWNode UPKWList[]= { {0x0001,PKW_R|PKW_W,0}, //索引为1的可读写寄存器 {0x0012,PKW_R,0}, //索引为8001H的只读寄存器 {0x8002,PKW_W,0} //索引为8002H的只写寄存器 };
3. 周期通信回调函数user_Oncycle
当周期通信发生时,会调用该回调函数,用户在这里将需要传输的值,与PN上的通信值做交换。
例如在本案例中,回调函数如下:
//切换大小端 //pDes 指向切换后的结果存储区 //pSrc 来源 //SIZE 数据字节长度 void sw_bigsmall(uint8_t *pDes, uint8_t *pSrc, uint8_t size) { uint8_t *pD; uint8_t *pS; for(int i=0;i<size;i++) { pD = pDes+i; pS = pSrc+size-1-i; *pD = *pS; } } //用户在该函数中,交换模块和外部设备之间的数据 void user_Oncycle(sPNModule *pModule, int modulecount) { uint8_t *src8; uint8_t *des8; //这里处理周期数据 if(pModule[0].moduletype == MODULE_1BYTE_INPUT)//确保模块类型正确 { *pModule[0].pmoduleBuff = ETA105_DI ; } if(pModule[1].moduletype == MODULE_1BYTE_OUTPUT)//确保模块类型正确 ETA105_DO = *(pModule[1].pmoduleBuff) ; if(pModule[2].moduletype == MODULE_2BYTE_INPUT)//确保模块类型正确 sw_bigsmall((pModule[2].pmoduleBuff),(uint8_t*)&(ETA105_AI[0]),2); if(pModule[3].moduletype == MODULE_2BYTE_INPUT)//确保模块类型正确 sw_bigsmall((pModule[3].pmoduleBuff),(uint8_t*)&(ETA105_AI[1]),2); }
4. PKW读索引回调函数user_onReadIndex
当PKW中有新的读取请求时,会执行该回调函数。用户在次函数对该索引数据赋值并执行一些自定义操作。本案例中程序如下
void user_onReadIndex(sPKWNode *pNode) { uint8_t *src8; uint8_t *des8; if(pNode == NULL) return ; switch(pNode->index) { case 0x0001: //这里添加读0001索引对象时的程序 break; case 0x0012: //这里添加读0012索引对象时的程序 假定是波特率 //这里注意从小端切换成大端 sw_bigsmall((uint8_t*)&(pNode->value),(uint8_t*)&(m_Serial.baudrate),4); break; case 0x8002: //0x8002不可读,不操作 break; } }
5 .PKW写操作回调函数
当有PKW写请求时,系统调用该回调函数,用户在此函数中,将PLC传输来得值写入,并执行相应得自定义操作。
//用户定义的写缩影回调函数 void user_onWriteIndex(sPKWNode *pNode) { if(pNode == NULL) return ; switch(pNode->index) { case 0x0001: // break; case 0x0012: //该对象只读,不操作 break; case 0x8002: // break; } }
入口:
m_Serial.CDC_ACM_number =0; //初始化通信对象 m_Serial.baudrate = 115200; m_Serial.oncycle = user_Oncycle; //注册回调函数 m_Serial.onPKWRead = user_onReadIndex; m_Serial.onPKWWrite = user_onWriteIndex; m_Serial.startPN(); //创建PN工作线程
退出处理:
m_Serial.stopPN( );
本项目为英创公司与第三方公司合作项目。ProfiNet专业技术支持可以联系:
电子邮件 99@ureate.com
电话 028-85550280-815
成都英创信息技术有限公司 028-8618 0660