uip+freemodbus网络通信

前言:

TCP/IP协议通过uip协议栈来实现,应用层的modbus协议使用freemodbus来实现,另外还需要一个网卡(笔者使用的是KSZ8851网卡控制器)来实现底层的数据传输(包括物理层、数据链路层)。
下面先介绍uip协议栈和freemodbus库的使用,后续移植过程和测试结果再更新

一、uip协议栈

uIP协议栈去掉了完整的TCP/IP中不常用的功能,简化了通讯流程,但保存了网络通讯必须使用的协议,设计重点放在了IP/TCP/ICMP/UDP/ARP这些网络层和传输层协议上,保证了其代码的通用性和结构的稳定性。UIP提供的是封装的策略。
由上往下逐步封装用户的数据,如:
应用层----------传输层--------网络层------数据链路层-----物理层
应用数据—TCP封装头部—IP封装头部-----mac封装+尾部-----发送

  1. uIP协议栈与底层的交互
    uIP提供了三个函数到底层,既uip_init(), uip_input() 和uip_periodic(),同时还提供了许多函数与堆栈交互。当设备驱动放一个输入包到包缓存里(up_buf),系统将调用uip_input()函数.函数将会处理这个包和需要时调用应用程序。当uip_input()返回,一个输出包放在包缓存里。包的大小由全局变量uip_len约束。如果uip_len是0,则说明没有包要发送。周期计时是用于驱动所有的uIP内部时钟事件。当周期计时激发,每一个TCP连接应该调用uIP函数uip_periodic()。连接编号传递的是作为自变量给uip_periodic()函数的。类似于uip_input()函数,当uip_periodic()函数返回,输出的IP包要放到包缓存里。任何的事物需要经过一定的初始阶段,在UIP协议里面通过uip_init()来初始化。
  2. uIP协议栈与应用程序的接口
    应用程序作为单独的模块由用户实现,uIP协议栈提供一系列接口函数供用户程序调用。用户需要将应用层入口程序作为接口提供给uIP协议栈,定义为宏UIP_APPCALL()。uIP在接受到底层传来的数据包后,若需要送到上层应用程序处理,它就调用UIP_APPCALL()。在uIP协议中有一个uip_buf缓冲用来接收和发送数据。
  3. uIP 提供的接口函数有:
     初始化 uIP 协议栈:uip_init()
     处理输入包:uip_input()
     处理周期计时事件:uip_periodic()
     开始监听端口:uip_listen()
     连接到远程主机:uip_connect()
     接收到连接请求:uip_connected()
     主动关闭连接:uip_close()
     连接被关闭:uip_closed()
     发出去的数据被应答:uip_acked()
     在当前连接发送数据:uip_send()
     在当前连接上收到新的数据:uip_newdata()
     告诉对方要停止连接:uip_stop()
     连接被意外终止:uip_aborted()

二、应用层modbus协议(FreeModbus)

FreeModbus是针对通用的Modbus协议栈在嵌入式系统中应用的一个实现。Modbus协议是一个在工业制造领域中得到广泛应用的一个网络协议。一个Modbus通信协议栈包括两层:定义了数据结构和功能Modbus应用协议和网络层。目前版本的FreeModbus支持如下的功能码:读输入寄存器 (0x04);读保持寄存器 (0x03);写单个寄存器 (0x06);写多个寄存器 (0x10);读/写多个寄存器 (0x17);读取线圈状态 (0x01);写单个线圈 (0x05);写多个线圈 (0x0F);读输入状态 (0x02);报告从机标识 (0x11);
应用Modbus TCP协议,当准备处理一个新数据帧的时候,移植层就必须首先向协议栈发送一个事件标志。然后,协议栈调用一个返回值为接收到的Modbus TCP数据帧的函数,并且开始处理这个数据帧。如果数据有效,则相应的Modbus反馈帧将提供给移植层生成反馈帧。最后,该反馈被发送到客户端。
FreeModbus必须首先调用初始化功能eMBInit()函数(使用modbus TCP模式时使用eMBTCPInit()函数),然后调用eMBEnable(),最后在循环体中调用eMBPoll()函数。

  1. eMBTCPInit()函数
    该函数主要设置侦听的端口和对需要调用的函数指针进行复制:
    pvMBFrameStartCur = eMBTCPStart;
    pvMBFrameStopCur = eMBTCPStop;
    peMBFrameReceiveCur = eMBTCPReceive;
    peMBFrameSendCur = eMBTCPSend;
    pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBTCPPortClose : NULL;
    ucMBAddress = MB_TCP_PSEUDO_ADDRESS;
    eMBCurrentMode = MB_TCP;
    eMBState = STATE_DISABLED;
    右边的函数如eMBTCPStart、eMBTCPStop等定义在mbtcp.c中
  2. eMBEnable()函数
     检查modbus功能是否都被关闭,如果不是关闭(可能是没有初始化或者已经打开),就返回错误;
     如果是disable状态,主要做两件事:首先调用pvMBFrameStartCur,在eMBTCPInit()函数中指向了eMBTCPStart函数;然后将eMB状态改为使能状态,即初始化结束。
  3. eMBPoll()总线侦听函数
    检查是否有事件发送,如果有,则根据不太类型的事件响应:
     EV_READY事件,表示系统刚刚进入侦听状态,则什么都不做;
     EV_FRAME_RECEIVED事件,表示接收到完整的帧,做两件事情:调用peMBFrameReceiveCur()函数,解析出地址、数据和长度;然后检查地址,如果是广播地址或本机地址(广播地址表示从机接收,本机地址表示从机发送),就调用xMBPortEventPost( EV_EXECUTE ),将接收器的状态更改为EV_EXECUTE;
     EV_EXECUTE事件,在函数列表中检查,有没有与命令字段相符合的函数,若有则执行响应的函数,否则返回非法功能码;然后再次检查地址,如果不等于广播地址(即表示从机为发送状态),则调用peMBFrameSendCur()函数,开始发送数据包
     EV_FRAME_SENT事件,直接break(响应写进EV_EXECUTE事件中)

三、移植过程

先展示一下加入的uip协议栈和freemodbus库在工程中的示例
keil中列表
运行的主函数

int main()
{
	timer_typedef periodic_timer, arp_timer;
	uip_ipaddr_t ipaddr;
		
	/* 设定查询定时器 ARP定时器 */
	/* TCP定时器修改为100ms */
	timer_set(&periodic_timer, CLOCK_SECOND / 10);
	timer_set(&arp_timer, CLOCK_SECOND * 10);
	/* IO口初始化 主要是为了避免SPI总线上的其他设备 */
	
	
	/* 配置systic作为1ms中断 *///uIP协议栈需要使用时钟,为TCP和ARP的定时器服务。
	//需根据芯片调整
	
	/* 初始化SPI2 */
	//我这里的网卡和芯片的通信是spi协议,所以还需要初始化spi口
	
	/* KSZ8851初始化 */
	//网卡初始化
	
	/* UIP协议栈初始化 */
	uip_init();
	
	/* 设置本机IP地址 */
	uip_ipaddr(ipaddr, 192,168,1,15);	
	uip_sethostaddr(ipaddr);
    /* 设置默认路由器IP地址 */
	uip_ipaddr(ipaddr, 192,168,1,1);		 
	uip_setdraddr(ipaddr);
    /* 设置网络掩码 */
	uip_ipaddr(ipaddr, 255,255,255,0);		 
	uip_setnetmask(ipaddr);	
    
	// MODBUS TCP侦听默认端口 502
	eMBTCPInit(MB_TCP_PORT_USE_DEFAULT);      
	eMBEnable();
	/* 打印本机IP地址 */
//	printf("\r\nuip start!\r\n");
//	printf("ipaddr:192.168.1.15\r\n");
	
		while (1)
	{	
		/*总线侦听*/
		eMBPoll();
		
		/* 读取以太网数据包,返回数据长度 */
		uip_len = tapdev_read();
		/* 收到数据	*/
		if(uip_len > 0)			    
		{
			/* 收到IP数据包,调用uip_input()处理IP数据包 */
			if(BUF->type == htons(UIP_ETHTYPE_IP))
			{
				uip_arp_ipin();
				uip_input();
        
				/* 处理完成后如果UIP_BUF里有数据,即uip_len>0,则发送出去*/
				if (uip_len > 0)
				{
					uip_arp_out();
					tapdev_send();
				}
			}
			/* 收到ARP数据包,调用uip_arp_arpin处理ARP报文 */
			else if (BUF->type == htons(UIP_ETHTYPE_ARP))
			{
				uip_arp_arpin();
				 /* 查看是否有要发送的数据并发送*/
				if (uip_len > 0)
				{
					tapdev_send();
				}
			}
		}
        
		/* 0.5秒定时器超时 查看0.5s是否到了,调用uip_periodic处理TCP超时程序*/
		if(timer_expired(&periodic_timer))			
		{
			timer_reset(&periodic_timer);
			/* 处理TCP连接, UIP_CONNS缺省是10个 */
			for(uint8_t i = 0; i < UIP_CONNS; i++)
			{
				/* 处理TCP通信事件 */
				uip_periodic(i);		
				if(uip_len > 0)
				{
					uip_arp_out();
					tapdev_send();
				}
			}	
			/* 定期ARP处理 10s到了就处理ARP*/
			if (timer_expired(&arp_timer))
			{
				timer_reset(&arp_timer);
				uip_arp_timer();
			}
		}
	}
}

四、测试

  1. 测试环境
    局域网下板卡与路由器采用网线连接,PC端模拟上位机通过wifi接入局域网,使用Modbus Poll模拟modbus主机,向从机发出查询指令。
  2. 测试内容
     串口打印信息:初始化各模块、建立连接、收到请求、关闭连接
     Wireshark抓包信息:TCP三次握手、keep-alive帧、modbus的收发。
     Modbus poll读值:Modbus poll作为modbus主机能读到从机对应传感器值的变化
  3. 测试结果
     串口打印信息
    在这里插入图片描述
     Wireshark抓包信息
    下图中192.168.2.2是PC的ip地址,而192.168.2.10为板卡设置的ip地址。
    在这里插入图片描述
     Modbus poll读值
    下图中左边一列为传感器的名称,右边为读取相应传感器的数据。
    在这里插入图片描述
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值