Modbus TCP协议是一种广泛应用于工业自动化、楼宇自控、能源管理等领域的数据通信协议。它基于TCP/IP网络,将Modbus协议封装在TCP/IP协议栈中,使用以太网作为物理层,通过TCP连接来实现设备之间的通信。
W5100S/W5500是一款集成全硬件 TCP/IP 协议栈的嵌入式以太网控制器,同时也是一颗工业级以太网控制芯片。本教程将介绍W5100S/W5500以太网FTP应用的基本原理、使用步骤、应用实例以及注意事项,帮助读者更好地掌握这一技术。
Modbus TCP实质上是Modbus协议(或Modbus RTU)在以太网TCP/IP网络上的运行。像Modbus RTU一样,Modbus TCP也采用客户端/服务器原理,但在这种情况下,客户端(主设备)会启动来自服务器(从设备)的请求和响应。任何设备都可以成为客户端或服务器。
Modbus TCP不需要计算校验和,因为较低层已经提供了校验和保护。它使用10 Mbps的以太网标准来传输Modbus消息的整个结构。Modbus TCP协议提供了在单个网络中的许多设备之间的快速通信。
Modbus TCP协议的数据帧可以分为两部分:MBAP和PDU。
MBAP(Modbus应用协议报文头):
事务处理标识:一个递增的数字,每次发送消息递增一下,尽量不重复,因为占用2个字节,所以范围是:0~65535。
协议标识:固定值0,表示Modbus TCP协议。
长度:等于后面字段的长度。
单元标识符:从机地址,也就是slave id。
PDU(协议数据单元):
功能码:Modbus规定了多个功能,每个功能都设定一个功能码。你要对从机做什么操作,那么就在这里设定好,从机读取到这个数据就知道要做什么。
数据:对于主机来说就是想要操作从机寄存器里的哪些数据。
Modbus TCP协议中定义了多种功能码,包括:
0x01:读线圈
0x05:写单个线圈
0x0F:写多个线圈
0x02:读离散量输入
0x04:读输入寄存器
0x03:读保持寄存器
0x06:写单个保持寄存器
0x10:写多个保持寄存器
每种功能码对应的操作和数据格式都有所不同。
例如,功能码0x01用于读取线圈,请求格式为“MBAP 功能码 起始地址H 起始地址L 数量H 数量L”
响应格式为“MBAP 功能码 数据长度 数据”。
在典型的Modbus TCP通信过程中:
MODBUS TCP协议的优点包括:
Modbus TCP协议的应用场景广泛,以下是一些主要的领域:
WIZnet 主流硬件协议栈以太网芯片参数对比
Model | Embedded Core | Host I/F | TX/RX Buffer | HW Socket | Network Performance |
---|---|---|---|---|---|
W5100S | TCP/IPv4, MAC & PHY | 8bit BUS, SPI | 16KB | 4 | Max.25Mbps |
W6100 | TCP/IPv4/IPv6, MAC & PHY | 8bit BUS, Fast SPI | 32KB | 8 | Max.25Mbps |
W5500 | TCP/IPv4, MAC & PHY | Fast SPI | 32KB | 8 | Max 15Mbps |
程序的运行框图如下所示:
软件
硬件
通过数据线连接PC的USB口(主要用于烧录程序,也可以虚拟出串口使用)
通过TTL串口转USB,连接UART0 的默认引脚:
使用模块连接RP2040 进行接线时
通过PC和设备都通过网线连接路由器LAN口
我们使用的是WIZnet官方的ioLibrary_Driver库。该库支持的协议丰富,操作简单,芯片在硬件上集成了TCP/IP协议栈,该库又封装好了TCP/IP层之上的协议,我们只需简单调用相应函数即可完成协议的应用。
第一步:在modbus_tcp.c文件中引用对应的库文件。
第二步:宏定义常量和定义全局变量。
第三步:定义两个函数,包括一个1秒定时器回调函数(用于处理DHCP超时处理),一个设置网络地址函数。
第四步:主函数首先是对串口和SPI进行初始化以及链路检测。然后是设置W5100S的网络地址,首先使用DHCP的方式进行获取,失败后使用预设的静态IP地址。然后将LED GPIO的初始化。最后则是在主循环里面跑Modbus TCP状态机程序。
第五步:在状态机中,首先是打开一个TCP Server模式的socket,然后等待客户端连接。在连接成功之后,会等待客户端的请求信息,接收到请求信息之后,会对包内容解析并作出响应的操作以及回复。
/* main function */
int main()
{
struct repeating_timer timer; // Define the timer structure
/*mcu init*/
stdio_init_all(); // Initialize the main control periphera
wizchip_initialize(); // spi initialization
wizchip_setnetinfo(&net_info); // Configure once first
/*dhcp init*/
DHCP_init(SOCKET_ID, ethernet_buf); // DHCP initialization
add_repeating_timer_ms(1000, repeating_timer_callback, NULL, &timer); // Add DHCP 1s Tick Timer handler
printf("wiznet chip modbus tcp server example.\r\n");
network_init(&net_info); // Configuring Network Information
print_network_information(&get_info); // Read back the configuration information and print it
/* LED gpio init */
gpio_init(PICO_DEFAULT_LED_PIN);
gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
while (true)
{
do_Modbus(SOCKET_ID);
}
}
/* Do_Modubs function */
void do_Modbus(uint8_t sn)
{
uint8_t state = 0;
uint16_t len;
getSIPR(lip);
state = getSn_SR(sn);
switch (state)
{
case SOCK_SYNSENT:
break;
case SOCK_INIT:
listen(sn);
if (!b_listening_printed)
{
b_listening_printed = 1;
printf("Listening on %d.%d.%d.%d:%d\r\n",
lip[0], lip[1], lip[2], lip[3], local_port);
}
break;
case SOCK_LISTEN:
break;
case SOCK_ESTABLISHED:
if (getSn_IR(sn) & Sn_IR_CON)
{
setSn_IR(sn, Sn_IR_CON);
printf("Connected\r\n");
getSn_DIPR(sn, rip);
port = getSn_DPORT(sn);
printf("RemoteIP:%d.%d.%d.%d Port:%d\r\n", rip[0], rip[1], rip[2], rip[3], port);
if (b_listening_printed)
b_listening_printed = 0;
}
len = getSn_RX_RSR(sn);
if (len > 0)
{
mbTCPtoEVB(sn);
}
break;
case SOCK_CLOSE_WAIT:
disconnect(sn);
break;
case SOCK_CLOSED:
case SOCK_FIN_WAIT:
close(sn);
socket(sn, Sn_MR_TCP, local_port, Sn_MR_ND); // Sn_MR_ND
break;
default:
break;
}
}
/* mbTCPtoEVB function */
void mbTCPtoEVB(uint8_t sn)
{
int32_t ret;
if (MBtcp2evbFrame() != 0) // Frame received complete
{
uint16_t maxsize = 0;
if (pucASCIIBufferCur[0] == 0x01)//Check whether the device address is 0x01
{
if ((uint8_t)pucASCIIBufferCur[1] == 0x05)//Write to a single device
{
if ((uint8_t)pucASCIIBufferCur[4] == 0xff)
{
gpio_put(PICO_DEFAULT_LED_PIN, 1);
printf("LED ON\r\n");
}
else if ((uint8_t)pucASCIIBufferCur[4] == 0x00)
{
printf("LED OFF\r\n");
gpio_put(PICO_DEFAULT_LED_PIN, 0);
}
send(sn, recv_data, recv_len);
}
else if ((uint8_t)pucASCIIBufferCur[1] == 0x01)//Read Write to a single device
{
if (recv_data[recv_len - 1] != 0x01)
{
printf("len error!\r\n");
}
else
{
printf("Read OK!\r\n");
send_data[0] = recv_data[0];
send_data[1] = recv_data[1];
send_data[2] = recv_data[2];
send_data[3] = recv_data[3];
send_data[4] = 0x00;
send_data[5] = 0x04;
send_data[6] = 0x01;
send_data[7] = 0x01;
send_data[8] = 0x01;
send_data[9] = gpio_get(PICO_DEFAULT_LED_PIN);
send_len = 10;
send(sn, (uint8_t *)send_data, send_len);
memset(send_data, 0, send_len);
}
}
else
{
printf("error code!\r\n");
}
}
else
{
printf("address error!\r\n");
}
}
}
1.用Modbus Poll连接到W5100S上
2.发送写指令,打开LED灯
3.设置读指令为0x01,读取个数为1
4.发送读取一次指令
5.读取成功,并在Modbus Poll上显示出LED的状态
(1)在library/ioLibrary_Driver/Ethernet/下找到wizchip_conf.h这个头文件,将_WIZCHIP_ 宏定义修改为W5500。
(2)在library下找到CMakeLists.txt文件,将COMPILE_SEL设置为ON即可,OFF为W5100S,ON为W5500。
WIZnet官网
WIZnet官方库链接
本章例程链接
想了解更多,评论留言哦!