STM32+enc28j60+uip 实现 单片机 ping PC端

1. 前言

临近毕业,多年在csdn等各大论坛闯荡(学习)的我,终于下定决心,开始写自己人生中的第一篇博客。

在学习了一段时间的uip协议栈后,走了很多弯路,所以想与大家分享自己的学习经历。本人没啥文笔,只能将自己所学所感与大家分享,本文的部分内容也是通过csdn等各大论坛收集整理而来,忠心希望大家能将意见或者建议在评论区与我分享,与大家共勉。

2. 实验简介

本次实验主要采用stm32最小系统开发板,MCU为stm32f103c8t6,搭载了enc28j60以太网模块,工程代码基于uip协议栈,实现了实现单片机 ping PC端。

3. uip简介

关于uip的学习,可参考xukai871105大神的博客—【uIP学习笔记

4. icmp简介

4.1 icmp介绍

ICMP(Internet Control Message Protocol),网络控制消息协议。它是TCP/IP协议簇的一个子协议,用于在IP主机、路由器之间传递控制消息。ICMP的协议号为1。

ICMP协议的功能主要有:
(1)确认IP包是否成功到达目标地址。
(2)通知在发送过程中IP包被丢弃的原因。

ICMP报文是在IP报文内部的!!!
ICMP报文分为查询报文和差错报文。

4.2 请求回显或回显应答报文格式介绍

注:本次实验主要实现的是ping功能,用到的是ICMP查询报文中的请求回显或回显应答报文(Echo or Echo Reply Message),所以对icmp其他报文类型不做展开。

报文内容的开始,是以太网帧头,包括目的主机的mac地址,源主机的mac地址,协议类型,共14Bytes(PC端的网卡MAC地址可通过cmd命令:ipconfig/all 查看,由于enc28j60没有唯一的mac标识,在实验时可随机设置)。

其次是报文类型,该处字符若为0x8000,说明该报文是IPv4类型。

接着是IP首部字段(IP Header),总长20Bytes。首部字段包括:

(1)IP Version:4,说明是IPv4),1Bytes;
(2)包头长度(Header Length),1Bytes;
(3)区分服务领域(Differentiated Services Field),1Bytes;
(4)总长度(Total Length),1Bytes;
(5)标识符(Identification),2Bytes;
(6)标记字段(Flags),2Bytes;
(8)报文生存时间(TTL),1Bytes;
(9)报文所用协议类型(Protocol),1Bytes;
(10)IP包头检验和(IP Header checksum),2Bytes,
(11)源IP(发送方IP),4Bytes;
(12)目的IP(接收方IP),4Bytes。

其中,IP包头检验和计算方法如下:
1.checksum的初始值自动被设置为0
2.接着,以16bit为单位,两两相加,对于该例子,即为:E34F + 2396 + 4427 + 99F3 = 1E4FF
3.若计算结果大于0xFFFF,则将,高16位加到低16位上,对于该例子,即为0xE4FF + 0x0001 = E500

注:校验和部分很重要,如果校验和出错,会导致报文被过滤,从而使得接收方接收不到该报文。

再接着是ICMP字段,总长40Bytes。其中包括:
(1)类型Type(Type: 8 表示icmp echo request,请求回显),1Bytes;
(2)代码值(code,code: 0x00表示请求回显),1Bytes;
(3)校验和(checksum),2Bytes;
(4)Identifier(用于区分不同的PING进程),2Bytes,对于unix以及类unix操作系统来说,icmp Identifier的内容就是ping的进程号,对于windows系统来说,具体参考如下:
Microsoft Windows NT - 256
Microsoft Windows 98/98SE - 512
Microsoft Windows 2000 - 512
Microsoft Windows ME - 768
Microsoft Windows 2000 Family with SP1 - 768
既然windows系统的icmp Identifier是固定不变的,那么系统如何区别不同的Ping进程呢?实际上windows系统就不在根据Identifier来区别ping进程了,它是根据Sequence Number field来区分的。
(5)序列号(Sequence number),2Bytes,区分发送顺序,与IP Header中的标识符类似。
(6)数据段(data),32Bytes,作为icmp 请求回显或回显应答报文的话,发送数据Data的内容可以是随机的。

看完了格式内容之后,同学们可以动动手,用wireshark抓取icmp包,看看报文中各个部分的具体内容。

5. 实验环境

单片机部分:stm32+enc28j60

PC端部分:win10,串口调试助手,wireshark

其他:单片机与PC端网线直连(并保证单片机与PC在同一网段)
单片机IP: 192.168.1.8
PC端IP: 192.168.1.5
网关: 192.168.1.1

6. 实验内容

6.1 实验方案

本次实验,主要分为请求回显报文的发送和回显应答报文的接收两部分,已经知道了报文的具体内容之后,我们便可以自己构建报文内容。模仿uip协议栈的uip_buf机制,构建请求回显报文内容,往uip_buf(或者自己定义的buf变量)中填充数据,再通过enc20j60底层发送函数进行发送;对于接收回显应答报文,可以分步对其进行数据解析,最后通过串口打印ping的结果。

6.2 请求回显报文的发送

构造请求回显报文,主要有以下几个方面:

  1. 定义相关结构体,这些结构体中的变量是根据报文的格式内容来定义的;
  2. 声明相关全局变量,如报文各个部分的长度;
  3. 校验和函数的定义;
  4. 报文内容的封装;
  5. 对封装好的报文进行预发送处理,在预发送过程中,要判断在arp表中是否有目的ip的mac地址,如果有,则以封装好的请求回显报文进行发送;如果没有,就要构造ARP请求进行发送。

详细代码如下:

/************************  icmp ***************************************************/

struct ethip_headr 
{
   
  struct uip_eth_hdr ethhdr;
  /* IP header. */
  u8_t vhl,
    tos,
    len[2],
    ipid[2],
    ipoffset[2],
    ttl,
    proto;
  u16_t ipchksum;
  u16_t srcipaddr[2],
    destipaddr[2];
};

struct arp_header
{
   
	struct uip_eth_hdr ethhdr;
	u16_t hwtype;
	u16_t protocol;
	u8_t hwlen;
	u8_t protolen;
	u16_t opcode;
	struct uip_eth_addr shwaddr;
	u16_t sipaddr[2];
	struct uip_eth_addr dhwaddr;
	u16_t dipaddr[2];
	
};

struct icmp_header
{
   
	u8_t type;				//icmp 类型
	u8_t code;				//代码值
	u16_t icmpchksum;		//校验和
	u8_t ide[2];			//用于区分不同ping进程
	u8_t seq[2];			//echo 序列号
	char data[28];			//数据段
};

/**********************************************************************/
/********************   icmp echo request      ************************/
#define UIP_ICMP_BUFSIZE		200
#define ICMP_DATA_SIZE			32
#define ICMP_IPD_LLH_LEN		17			//以太网+IP
#define ICMP_ETH_LEN     		14			//以太网帧头长度
#define ICMP_IPH_LEN			20			//IPHead长度
#define UIP_ICMP_LEN			40			//ICMP帧长度
u8_t uip_icmp_buf[UIP_ICMP_BUFSIZE + 2];
u16_t uip_icmp_len;

#define ICMP_ARP_BUF	((struct arp_header *)&uip_icmp_buf[0])		//主动连接时,替换ICMP_IP_BUF
#define ICMP_IP_BUF		((struct ethip_headr *)&uip_icmp_buf[0])
#define ICMP_BUF    	((struct icmp_header *)&uip_icmp_buf[ICMP_ETH_LEN + ICMP_IPH_LEN])

/**********************************************************************/
volatile u8_t FLAG_icmp_arpout = 0;
extern u16_t chksum(u16_t sum, const u8_t *sdata, u16_t len);
static u16_t icmp_ipid;
static u16_t icmp_seq;
static u8_t j;
u16_t icmp_ide = 0;

/**********************************************************************/
//iphead check
static u16_t short_checksum(u16_t sum, const u8_t *sdata, u16_t len)
{
   
  u16_t t;
  const u8_t *dataptr;
  const u8_t *last_byte;

  dataptr = sdata;
  last_byte = sdata + len - 1;
  
  while(dataptr < last_byte) {
   	/* At least two more bytes */
    t = (dataptr[0] << 8) + dataptr[1];
    sum += t;
    if(sum < t) {
   
      sum++;		/* carry */
    }
    dataptr += 2;
  }
  
  if(dataptr == last_byte) {
   
    t = (dataptr
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值