基于ENC28J60+uIP1.0+STM32的UDP Server实现,服务器主动发送数据的实现,几个关键的问题可算整明白了!

这篇具有很好参考价值的文章主要介绍了基于ENC28J60+uIP1.0+STM32的UDP Server实现,服务器主动发送数据的实现,几个关键的问题可算整明白了!。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

    ENC28J60,是一款SPI接口的以太网PHY+MAC芯片,实现以太网物理层和MAC层硬件通信。uIP是一个TCP/IP软件协议栈,实现TCP、UDP、ARP、ICMP等网络协议。STM32F103RCT6通过SPI接口与ENC28J60通讯,并移植uIP协议,实现一个小型的UDP服务器。

    严格来说,UDP是面向无连接的对等通讯协议,不存在服务器与客户端之说,这里的服务器只是来形容STM32 SERVER系统的UDP的使用方式,即STM32充当服务器角色,接收同网段的不同IP和端口的UDP数据包,并将数据返回。

    至于ENC28J60+uIP移植,相信不少资料与例子可供参考,比如奋斗嵌入式,正点原子等等,本文不再赘述。这里只说我在移植uIP,调试软件时发现的几个问题。

1、不要TCP,只需要UDP、ARP、ICMP协议,移植哪些文件。

下图是移植uIP后实现上述协议的截图,没错,只要“uip.c”和“uip_arp.c”和必要的头文件就足够了!多一个文件都是浪费!头文件途径也是添加uip和unix文件夹就好了。

uip1.0,STM32开发,stm32,udp,嵌入式硬件,uIP1.0,UDP SERVERuip1.0,STM32开发,stm32,udp,嵌入式硬件,uIP1.0,UDP SERVER

2、UDP服务器,接收同网段不同IP和端口,要改uip.c文件的位置,以及怎么用。

打开“uip.c”文件,约1100行左右,#endif /* UIP_UDP_CHECKSUMS */和/* Demultiplex this UDP packet between the UDP "connections". */之间,添加如下代码:

	//UDP SERVER补丁,客户端IP和端口不受限制
	if((uip_udp_conn != 0)
		&&((uip_udp_conn->rport != UDPBUF->srcport)
		 ||!uip_ipaddr_cmp(uip_udp_conn->ripaddr, UDPBUF->srcipaddr)))  	//如果是已经连接并且和接收到的端口号或者IP地址不一致
	{
		uip_udp_remove(uip_udp_conn);											//删除连接
	  uip_udp_conn->rport = UDPBUF->srcport;            //将目的端口设置为收到的远端UDP包的源端口
		uip_udp_conn->lport = UDPBUF->destport;           //将本地端口设置为收到的远端UDP包的目的端口
		memcpy(uip_udp_conn->ripaddr, UDPBUF->srcipaddr, sizeof(uip_ipaddr_t ));	 //将目的IP地址设置为收到的远端UDP包的源IP地址
  }
	
  if(uip_udp_conn->rport == 0)									   		//如果首次接收到某个远端UDP包
	{
    uip_udp_conn->rport = UDPBUF->srcport;            //将目的端口设置为收到的远端UDP包的端口
		memcpy(uip_udp_conn->ripaddr,UDPBUF->srcipaddr,sizeof(uip_ipaddr_t ));	 //将目的IP地址设置为收到的远端UDP包的源IP地址
	}
	
	//uip_udp_conn = uip_udp_new(&ipaddr, HTONS(1000));	//建立到远程ipaddr,端口为1000的连接 
	if(uip_udp_conn != 0)  
	{
		uip_udp_bind(uip_udp_conn, UDPBUF->destport);//绑定本地端口为LPORT,也就是LPORT-->RPORT 发数据 
	}
  //end 补丁

改好后是这样的:

uip1.0,STM32开发,stm32,udp,嵌入式硬件,uIP1.0,UDP SERVER

这段代码参考了奋斗的程序,奋斗的程序有BUG,最开始的那个if语句

        if((uip_udp_conn != 0)
        &&((uip_udp_conn->rport != UDPBUF->srcport)
         ||!uip_ipaddr_cmp(uip_udp_conn->ripaddr, UDPBUF->srcipaddr)))      //如果是已经连接并且和接收到的端口号或者IP地址不一致

红的标记的部分,原来的奋斗的程序是“||uip_udp_conn->ripaddr!=UDPBUF->srcipaddr)”,ripaddr和srcipaddr是u16类型的指针地址,判断的是指针的地址是否相等,那么必然不等,所以即使同样的IP地址发来的包都会判断为真跳进if的执行语句中。改正后已经解决。

最后的if语句中:uip_udp_bind(uip_udp_conn, UDPBUF->destport);

目的是将收到的UDP包中,目的端口作为服务器的监听端口,每次都会根据客户端的UDP端口改变,即实现不同的IP、不同的源端口、目的端口发给服务器的UDP包,服务器都能处理后原路返回。如果需要固定服务器的监听端口,只需要将UDPBUF->destport改为固定的端口号即可,例如端口号2000,改为uip_udp_bind(uip_udp_conn, HTONS(2000));

再讲讲怎么用。在main函数初始化ENC28J60和uIP之后,随便监听一个端口就可以了,调用如下函数(NetParam.lUdpPort变量自行定义或替换监听端口号,但是其他端口也能监听)

/**
  * @brief  UDP服务器模式
  * @param  None
  * @retval None
*/
void udp_server_creat(void)
{
	uip_listen(HTONS(NetParam.lUdpPort));
	uip_udp_bind(uip_udp_conn, htons(NetParam.lUdpPort));//绑定本地端口为LPORT
}

3、STM32断电再上电后,PC发给STM32的第一包数据,PC收不到STM32回复的原因。

阿莫有个帖子:为什么uip中udp第一次主动发送数据的时候PC没有收到呢,第二次以后后就正常了 (amobbs.com 阿莫电子论坛 - 东莞阿莫电子网站)

    之前我也被这个问题困扰了一段时间,我做的udp server demo,在用PC端用网络调试助手随便发一个什么数据给STM32 SERVER,SERVER收到后,原路回复上电后收到数据的序号,以及收到数据的字节长度,但是每次下载程序后测试,第一个数据都不回复,第二个才开始回复,序号也是跳过0直接回1。

    原因是:STM32 SERVER端的ARP列表上电后被清空,而电脑端的ARP列表又是上一次缓存了STM32 SERVER的IP和MAC,所以电脑给STM32 SERVER发送UDP包时直接就把UDP发过来了而没有发起ARP,这是一方面。另一方面,void UipPro(void)这个函数,参考奋斗的程序,如下:

/**
  * @brief  中断触发读取网络接收缓存
  * @param  None
  * @retval None
*/
void UipPro(void)
{
	if(ETH_INT == 1){					//当网络接收到数据时,会产生中断
rep:;
		ETH_INT = 0;
		uip_len = tapdev_read();	//从网络设备读取一个IP包,返回数据长度
		if(uip_len > 0)			    //收到数据
		{
			/* 处理IP数据包(只有校验通过的IP包才会被接收) */
			if(BUF->type == htons(UIP_ETHTYPE_IP))   //是IP包吗?
			{
				uip_arp_ipin();		   //去除以太网头结构,更新ARP表
				uip_input();		   //IP包处理
				/*
					当上面的函数执行后,如果需要发送数据,则全局变量 uip_len > 0
					需要发送的数据在uip_buf, 长度是uip_len  (这是2个全局变量)
				*/
				if (uip_len > 0)		//有带外回应数据
				{
					uip_arp_out();		//加以太网头结构,在主动连接时可能要构造ARP请求
					tapdev_send();		//发送数据到以太网(设备驱动程序)
				}
			}
			/* 处理arp报文 */
			else if (BUF->type == htons(UIP_ETHTYPE_ARP))	//是ARP请求包
			{
				uip_arp_arpin();		//如是是ARP回应,更新ARP表;如果是请求,构造回应数据包
				/*
					当上面的函数执行后,如果需要发送数据,则全局变量 uip_len > 0
					需要发送的数据在uip_buf, 长度是uip_len  (这是2个全局变量)
				*/
				if (uip_len > 0)		//是ARP请求,要发送回应
				{
					tapdev_send();		//发ARP回应到以太网上
				}
			}
		}
  }
  else{	  //防止大包造成接收死机,当没有产生中断,而ENC28J60中断信号始终为低说明接收死机
  	 if(ENC28J60_INT_STA == 0) goto rep; 	
  }
}

uip_arp_ipin();           //去除以太网头结构,更新ARP表

这一行本意是,直接从收到的IP包里面获取源IP和源MAC,加入到本地ARP表中,然而全局搜索一下这个函数,发现其在uip_arp.h中是空定义的,上面的函数声明被注释了,

uip1.0,STM32开发,stm32,udp,嵌入式硬件,uIP1.0,UDP SERVER

再看uip_arp.c文件,#if 0也是将此函数注释了。

uip1.0,STM32开发,stm32,udp,嵌入式硬件,uIP1.0,UDP SERVER
有可能uIP作者调试后忘记将其解除注释,因此,uip_arp_ipin();           //去除以太网头结构,更新ARP表 预期没有实现。

因此,对于STM32来说,它直接收到一个UDP包,要回复给发来这个包的IP地址,但是它的ARP列表是空的,STM32直接就懵了,不知道对方的MAC是啥,于是STM32就放弃发送这包数据,改为发送ARP查询包,询问对方IP。在uip_arp_out()函数中有如下注释就是说了这个意思:

uip1.0,STM32开发,stm32,udp,嵌入式硬件,uIP1.0,UDP SERVER

了解原因后,将其释放出来,再编译,发现STM32 SERVER重新上电后第一包数据不回复问题解决。

uip1.0,STM32开发,stm32,udp,嵌入式硬件,uIP1.0,UDP SERVER

uip1.0,STM32开发,stm32,udp,嵌入式硬件,uIP1.0,UDP SERVER

uIP之uip_arp_ipin();函数的作用。-OpenEdv-开源电子网 这个帖子也是问了这个问题,我没有ID,只能在这里回答了。

4、主动发送UDP数据包的原理及实现。

uIP协议栈用于“收到后发送”的逻辑很好用,但是主动发送要费一番周折,看一遍uip.c这个文件,其发送的大概流程是

uip_process函数会周期调用,flag变量如果为“UIP_UDP_TIMER”状态,就会进入到回调函数UIP_UDP_APPCALL();,然后goto udp_send;进入发送数据流程,如果缓存数据长度变量uip_slen非0,则发送数据。

uip1.0,STM32开发,stm32,udp,嵌入式硬件,uIP1.0,UDP SERVER

uip1.0,STM32开发,stm32,udp,嵌入式硬件,uIP1.0,UDP SERVER

因此,想要主动发送UDP包,需要让uip_process函数进入UIP_UDP_TIMER流程,然后在回调函数中,注入要发送的数据和长度,之后就发出去了。逻辑就是这么简单,代码如下:

先定义俩个全局变量

u8 *pActiveSendData;  //UDP主动发送,数据指针
u16 ActiveSendLen;    //UDP主动发送数据长度

 实现主动发送函数,主要是配置一下端口,然后调用uip_process函数,使其能跳转到回调函数。

/**
  * @brief  主动发送udp包
  * @param  data 数据
  * @param  len  数据长度
  * @retval None
*/
void udp_active_send(u8 *data, u16 len)
{
	pActiveSendData = data;
	ActiveSendLen = len;
	
	//设置目标ip和端口
	uip_udp_conn->rport = HTONS(NetParam.rUdpPort);    //将目的端口设置为收到的远端UDP包的源端口
	uip_udp_conn->lport = HTONS(NetParam.lUdpPort);    //将本地端口设置为收到的远端UDP包的目的端口
	uip_ipaddr_t ipaddr;
	uip_ipaddr(ipaddr, NetParam.rIP[0], NetParam.rIP[1], NetParam.rIP[2], NetParam.rIP[3]);	
	memcpy(uip_udp_conn->ripaddr, ipaddr, sizeof(uip_ipaddr_t ));
	
	//唤起UDP发送处理
	uip_process(UIP_UDP_TIMER);
	
	/* 如果上面的函数调用导致数据应该被发送出去,全局变量uip_len设定值> 0 */
	if(uip_len > 0)
	{
		uip_arp_out();		//加以太网头结构,在主动连接时可能要构造ARP请求
		tapdev_send();		//发送数据到以太网(设备驱动程序)
		ActiveSendLen = 0;//发送完成数据清零
	}
}

回调函数向缓冲区写入数据,就是用UIP_UDP_APPCALL()宏定义的那个要自己加的函数

/**
  * @brief  UDP主函数
  * @param  None
  * @retval None
*/
void udp_server_appcall(void)
{
	//接收到一个新的udp数据包
	if(uip_newdata())//收到客户端发过来的数据
	{
		UDP_newdata();
	}
	else if(uip_poll())//主动发送数据
	{
		if(ActiveSendLen)
		{
			udp_send(pActiveSendData, ActiveSendLen);
		}
	}
}

发送函数udp_send

/**
  * @brief  UDP 数据包发送
  * @param  str:数据
  * @retval None
*/
void udp_send(u8 *str, u16 Len)
{
   struct udp_server_appstate *s = (struct udp_server_appstate *)&uip_udp_conn->appstate;

   s->textptr =(u8*)str;
   s->textlen =Len;
   uip_send(s->textptr, s->textlen);//发送udp数据包
//   uip_udp_send(s->textlen);
}

怎么用?主动发送直接调用udp_active_send,如果是先收再发建议还是用udp_send

udp_active_send("OK", 2);

最后附上测试图,1S间隔主动发送

uip1.0,STM32开发,stm32,udp,嵌入式硬件,uIP1.0,UDP SERVER

两个PC客户端,不同IP和源目的端口,轮流发送测试。

uip1.0,STM32开发,stm32,udp,嵌入式硬件,uIP1.0,UDP SERVER

本例程中ENC28J60与STM32F103RCT6硬件接线:

SPI4根线:CS:PA3   

                  SCK,MISO MOSI :PA5 PA6 PA7

ENC28J60中断  INT:PC4

IP参数定义位于udp_server.c文件中,根据需要自己改,lIP就是STM32 SERVER的IP,rIP是电脑的IP。

uip1.0,STM32开发,stm32,udp,嵌入式硬件,uIP1.0,UDP SERVER

最后的最后,放上程序:

链接:https://pan.baidu.com/s/1JmLrI4zj7Bs5ifaHQ3OxAg?pwd=ldp9 
提取码:ldp9

如果链接失效记得评论区喊我更新!文章来源地址https://www.toymoban.com/news/detail-760354.html

到了这里,关于基于ENC28J60+uIP1.0+STM32的UDP Server实现,服务器主动发送数据的实现,几个关键的问题可算整明白了!的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • E9—TEMAC IP实现千兆网口UDP传输2023-08-28

    E9—TEMAC IP实现千兆网口UDP传输2023-08-28

    Tri Mode Ethernet MAC是收费IP,打开IP后,当左下角显示Bought IP license available则IP可用。 应用搭建的场景是,上位机发送数据,首先发起arp请求,随后下位机给出arp应答响应,上位机get到下位机的mac地址之后,将发送框中的数据打包成udp数据包下发到下位机中,下位机收到udp包,将

    2024年02月10日
    浏览(12)
  • STM32--基于STM32的智能家居设计与实现

    STM32--基于STM32的智能家居设计与实现

    本文详细介绍基于STM32F103C8T6的智能家居设计与实现,详细设计资料见文末链接 一、功能模块介绍 智能家居系统系统图如下所示,主要包括温湿度传感器、OLED液晶显示,WIFI物联网模块、人体红外预警模块、烟雾传感器模块、蜂鸣器模块 (1)温湿度传感器 温湿度传感器选用

    2024年02月08日
    浏览(94)
  • 基于 Arduino 库实现 ESP32 TCP Server 应用例程

    基于 Arduino 库实现 ESP32 TCP Server 应用例程

    ESP32 开启 WiFi Station 模式连接路由器 连上路由器后将获取到分配的 IP 地址 基于分配的 IP 地址创建 TCP Server 手机与 ESP32 连接同一路由器 查看 UART0 日志打印,获取 TCP Server 的 IP 地址 使用手机端 TCP 调试 APP 与 ESP32 创建的 TCP Server 建立连接

    2024年02月12日
    浏览(15)
  • 【探索Linux】P.28(网络编程套接字 —— 简单的UDP网络程序模拟实现)

    【探索Linux】P.28(网络编程套接字 —— 简单的UDP网络程序模拟实现)

    在前一篇文章中,我们详细介绍了UDP协议和TCP协议的特点以及它们之间的异同点。 本文将延续上文内容,重点讨论简单的UDP网络程序模拟实现 。通过本文的学习,读者将能够深入了解UDP协议的实际应用,并掌握如何编写简单的UDP网络程序。让我们一起深入探讨UDP网络程序的

    2024年04月08日
    浏览(289)
  • STM32-OTA升级-基于STM32CubeMX+STM32F103(二)代码实现

    STM32-OTA升级-基于STM32CubeMX+STM32F103(二)代码实现

    0 引言 在上一篇文章中,我们已经讲述了STM32的启动流程、IAP的原理和OTA的原理(最后这部分直接分享了一些博客,因为前辈们已经写的非常好了),下面这篇主要用来记录STM32-OTA的实验步骤。 源码我大家自行下载即可。 链接:https://pan.baidu.com/s/1uemqEqDNI3-IjulZ4oNFlw?pwd=of3g 提取

    2024年02月04日
    浏览(27)
  • 【STM32】使用HAL库对ULN2003控制28BYJ-48步进电机

    【STM32】使用HAL库对ULN2003控制28BYJ-48步进电机

    步进电机是将电脉冲信号转变为角位移或线位移,通过控制施加在电机线圈上的电脉冲顺序、频率和数量,可以控制步进电机的转向、速度和旋转角度。 配合以直线运动执行机构(螺纹丝杆)或齿轮箱装置,更可以实现更加复杂、精密的线性运动控制要求。 在非超载的情况下,

    2024年02月16日
    浏览(13)
  • STM32CubeMX教程28 SDIO - 使用FatFs文件系统读写SD卡

    正点原子stm32f407探索者开发板V2.4 STM32CubeMX软件(Version 6.10.0) keil µVision5 IDE(MDK-Arm) ST-LINK/V2驱动 野火DAP仿真器 XCOM V2.6串口助手 使用STM32CubeMX软件配置STM32F407开发板 SDIO使用FatFs中间件读写4线SD卡,并实现以轮询方式读写SD卡或以DMA方式读取SD卡 FatFs文件系统相关知识请读者

    2024年02月19日
    浏览(13)
  • 基于STM32实现FLASH读写功能

    基于STM32实现FLASH读写功能

    今天要学习的是flash读写,闪存(Flash Memory)是一种长寿命的非易失性(在断电情况下仍能保持所存储的数据信息)的存储器。用途SD卡、固态硬盘、芯片内存存储单元存储代码。 Flash 接口可管理 CPU 通过 AHB I-Code 和 D-Code 对 Flash 进行的访问。该接口可针对 Flash 执行擦除和编

    2024年02月11日
    浏览(13)
  • 基于stm32的流水灯实现

    主控芯片:  正点原子STM32F103ZET6精英开发板 oled:中景园七针脚0.96寸oled 代码编程软件: keil5 代码下载地址: stm32流水灯项目 希望大家仔细看一看是否符合自己的需求,实际上本文中写到的很多东西已经能为课设所用,理清逻辑后自己编程也很方便的,但如果想直接copy我的项目

    2024年02月07日
    浏览(11)
  • 【STM32笔记】STM32的定时器开发基础(二)(基于STM32CubeMX实现定时器中断)

    【STM32笔记】STM32的定时器开发基础(二)(基于STM32CubeMX实现定时器中断)

      传统STM32外部中断 的设计步骤:  (1)将GPIO初始化为输入端口。  (2)配置相关I/O引脚与中断线的映射关系。  (3)设置该I/O引脚对印的中断触发条件。  (4)配置NVIC,并使能中断。  (5)编写中断服务函数。   基于STM32CubeMX的外部中断 设计步骤  (1)在STM3

    2024年02月20日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包