1.前言
对于工控而言,有时候会需要和单片机进行通信,最近就测试了一下市面上常见的西门子PLC和STM32F103VET6的单片机进行通信。
2.硬件架构
STM32+W5500+ModbusTCP+西门子1200PLC 中间需要交换机来互联网口
首先通信是要基于协议的,对于单片机和PLC而言之间的通信协议可以有很多,串口232,485,ModbusRTU/TCP等等。现在工控都是组网时代了,ModbusTCP作为一款开源的协议资料也很多便于第三方移植。
PLC端现在大多数支持网口,西门子1200,1500系列PLC自带网口,支持Modbus协议,通信起来也方便一些。
单片机端选择需要带网口,我用的是野火指南者开发板,本身自己没有带网口,但是现在以太网模块很多,这里选择W5500的以太网模块来实现组网。W5500可以实现基于硬件的TCP/IP协议,可以避免解析复杂的TCP/IP协议,例程也多方便移植。其实本质就是基于W5500模块的ModbusTCP移植。这部分有很多参考资料,主要参考这篇文章,十分感谢https://blog.csdn.net/jordan20052009/article/details/46967451?ops_request_misc=%25257B%252522request%25255Fid%252522%25253A%252522160744407419726891153872%252522%25252C%252522scm%252522%25253A%25252220140713.130102334.pc%25255Fall.%252522%25257D&request_id=160744407419726891153872&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v29-25-46967451.pc_search_result_cache&utm_term=freemodbus%20tcp
3.移植分析
需要理解Modbus协议的相关内容,W5500的使用,PLC端主站的建立。
其实分3大块:
1.Modbus协议源码移植
2.W5500TCP/IP移植
3.PLC端主站代码的编写
熟悉PLC的朋友应该知道,第3点其实不难,而且是在PLC里面写跟单片机其实没有太大关系,实际上在单片机跑通之后,就可以用Modbus scan32一类的工具去测试连接了,只要调试助手能读取成功就证明单片机端代码没有问题。
那就只剩下第1,2点。我是这么做的,先单独完成Modbus RTU的移植,这样可以方便了解Modbus协议的工作原理,再单独完成W5500TCP/IP的协议移植这样可以理解W5500的使用方法,最后再两者结合,就可以实现。不了解的同学可以尝试分步实现。一定要理解Modbus 因为我们就是在W5500的函数基础上添加Modbus内容。TCP/IP实现底层的收发,Modbus再对其进行处理。
4.移植流程
1.W5500可以直接参考官方的例程,下面是main函数
在这里插入代码片int main( void )
{
eMBErrorCode eStatus;
systick_init(72); // 初始化滴答定时器
USART1_Config(); // 初始化USART1:115200@8-N-1
reset_break_gpio_init(); // 复位与中断管脚初始化
spi_gpio_init(); // SPI管脚初始化
spiinitailize(); // SPI配置初始化
printf("\r\n 炜世科技--WIZnet W5500官方代理商。全程技术支持,价格优势大!\r\n\r\n");
reset_w5500(); // w5500硬件复位
PHY_check(); // 网线检测程序
set_w5500_mac(); // 设置w5500MAC地址
set_w5500_netinfo(); // 设置w5500网络参数
socket_buf_init(txsize, rxsize); // 初始化4个Socket的发送接收缓存大小
printf("\r\n W5500为TCP Server,建立侦听。等待PC作为TCP Client建立连接。\r\n");
printf(" TCP Server IP:%d.%d.%d.%d。\r\n",remote_ip[0],remote_ip[1],remote_ip[2],remote_ip[3]);
printf(" TCP Server Port:%d。\r\n",remote_port);
printf(" 正常现象:连接成功,TCP Client发送数据给W5500,W5500将返回对应数据。\r\n");
eStatus = eMBTCPInit( 2 ); //Modbus相关TCP初始化
eStatus =eMBEnable(); //Modbus协议栈使能
while(1)
{
modbus_tcps(); //主函数
}
}
这其实是官方的例程,Modbus是后添加的,while中的循环函数我也进行了修改,是跟Modbus相关的。其实执行到最后一条Prtinf语句后,W5500就已经配置完成了,已经可以ping通了,这就可以证明W5500运转正常了,之后再去做下一步动作。官方的例程基本不需要修改,拿来直接用就可以,只是SPI部分的硬件接线要根据自己实际板子来修改部分代码。关于W5500可以参考官网的教程和网络资料也比较多,这里不多说。说一下IP地址
/*MAC地址首字节必须为偶数
如果多块w5500网络适配板在同一现场工作,请使用不同的MAC地址
*/
uint8 mac[6]={0x00,0x08,0xdc,0x11,0x11,0x11};
/*定义默认IP信息*/
uint8 local_ip[4] = {192,168,1,8}; // 定义w5500默认IP地址
uint8 subnet[4] = {255,255,255,0}; // 定义w5500默认子网掩码
uint8 gateway[4] = {192,168,1,1}; // 定义w5500默认网关
uint8 dns_server[4] = {114,114,114,114}; // 定义w5500默认DNS
uint16 local_port = 502; // 定义本地端口
注意单片机IP要和本地PLC在同一网段。端口号要和PLC端一致,常用502.
mac地址如果是多设备注意不要重复。下面是while中的函数。和RTU不同,TCP是一直处在监听状态,一旦建立了连接就会一直查询,检测到数据长度ucTCPRequestLen>0就开始接受数据,接受到数据之后,再驱动empoll函数即可 进入到Modbus的内部处理流程,处理完了再通过send函数发送出去。Send和recv都是W5500的库函数。
void modbus_tcps(void)
{
uint16 len=0;
switch(getSn_SR(SOCK_TCPS)) // 获取socket的状态
{
case SOCK_CLOSED: // socket处于关闭状态
socket(SOCK_TCPS ,Sn_MR_TCP,local_port,Sn_MR_ND); // 打开socket
break;
case SOCK_INIT: // socket已初始化状态
listen(SOCK_TCPS); // socket建立监听
break;
case SOCK_ESTABLISHED: // socket处于连接建立状态
if(getSn_IR(SOCK_TCPS) & Sn_IR_CON)
{
setSn_IR(SOCK_TCPS, Sn_IR_CON); // 清除接收中断标志位
}
ucTCPRequestLen=getSn_RX_RSR(SOCK_TCPS); // 定义len为已接收数据的长度
if(ucTCPRequestLen>0)
{
recv(SOCK_TCPS,ucTCPRequestFrame, ucTCPRequestLen); //W5500接收数据
xMBPortEventPost(EV_FRAME_RECEIVED); //发送EV_FRAME_RECEIVED事件,以驱动eMBpoll()函数中的状态机
eMBPoll(); //处理EV_FRAME_RECEIVED事件
eMBPoll(); //处理EV_EXECUTE事件
if(bFrameSent)
{
bFrameSent = FALSE; //W5500发送Modbus应答数据包
send(SOCK_TCPS,ucTCPResponseFrame,ucTCPResponseLen);
}
}
break;
case SOCK_CLOSE_WAIT: // socket处于等待关闭状态
disconnect(SOCK_TCPS); // 断开当前TCP连接
close(SOCK_TCPS); // 关闭当前所使用socket
break;
}
}
其实如果完全不了解Modbus协议的话,移植起来还是比较困难,建议一定要了解一些之后再移植,可以先做ModbusRTU的移植。