stm32F103+EncEthernet+ENC28J60驱动+ping

最近的目标是完成FreeModbus在以太网上面的移植,计划用1块stm32F103的开发板+ENC28J60的以太网控制器来实现。
购买的ENC28J60上面带有一个在stm32上的web浏览器的demo例程,所以参考模块的例程,完成一个arp协议和ICMP协议下的ping测试,用来测试一下ENC28J60模块是否可以正常工作,也顺便分析一下ENC28J60的用法,为后面移植FreeModbus做准备。

main.c

从main.c出发
首先定义工程相关变量

/* mac地址和ip地址在局域网内必须唯一,否则将与其他主机冲突,导致连接不成功 */
static unsigned char mymac[6] = {0x54, 0x55, 0x58, 0x10, 0x00, 0x24};
static unsigned char myip[4] = {192, 168, 1, 15};

/* 发送数据缓冲区 */
#define BUFFER_SIZE 1500
static unsigned char buf[BUFFER_SIZE + 1];

接下来是main函数

int main(void)
{
  /* 配置系统时钟为72M */
  SystemInit();

  /* ENC28J60 SPI 接口初始化 */
  SPI_Enc28j60_Init();

  /* 初始化 enc28j60 的MAC地址(物理地址),这个函数必须要调用一次 */
  enc28j60Init(mymac);

  /* 初始化以太网 IP 层 */
  init_ip_arp_udp_tcp(mymac, myip);

  while (1)
  {
    unsigned int plen = 0;

    // get the next new packet:
    plen = enc28j60PacketReceive(BUFFER_SIZE, buf);

    // plen will ne unequal to zero if there is a valid packet (without crc error)
    if (plen == 0)
    {
      continue;
    }

    // arp is broadcast if unknown but a host may also
    // verify the mac address by sending it to
    // a unicast address.
    if (eth_type_is_arp_and_my_ip(buf, plen))
    {
      make_arp_answer_from_request(buf);
      continue;
    }

    if (buf[IP_PROTO_P] == IP_PROTO_ICMP_V && buf[ICMP_TYPE_P] == ICMP_TYPE_ECHOREQUEST_V)
    {
      // a ping packet, let's send pong  DOS 下的 ping 命令包
      make_echo_reply_from_request(buf, plen);
      continue;
    }
  }
}

下面分析一下每个函数的作用。

SystemInit()

在野火开发板的main函数里面都会有这个函数,作用是将系统频率设定为72M

SPI_Enc28j60_Init()

enc28j60芯片与主控芯片的通讯接口为SPI,这个函数的作用是初始化stm32的SPI接口,工程里面用到的是SPI2

enc28j60Init(mymac)

这个函数的作用是初始化enc28j60芯片,直接调用的是enc28j60的库文件,其中mymac是上面定义的以太网卡的mac地址

init_ip_arp_udp_tcp(mymac, myip)

这个函数的作用是初始化EncEthernet协议栈,

至此enc28j60的移植已经完成。接下来就是在while(1)循环里面做应用层。因为终极目标是基于LWIP和FreeModbus的Modbus TCP,所以这里的应用层只做了ARP协议和ICMP协议的ping的测试

enc28j60PacketReceive(BUFFER_SIZE, buf);

plen = enc28j60PacketReceive(BUFFER_SIZE, buf);

在while(1)循环里面首先调用enc28j60PacketReceive(BUFFER_SIZE, buf)函数获取存放在enc28j60缓冲区的数据包,存放在buf数组里面,返回值是数据包的大小,单位是byte,若返回值为0则代表未接收到数据。

下面就是对以太网数据包也就是buf数据里面的数据进行解析

ARP协议的实现

if (eth_type_is_arp_and_my_ip(buf, plen))
    {
      make_arp_answer_from_request(buf);
      continue;
    }
为什么还要加上arp协议

以太网的OSI模型把网络工作分为七层,彼此不直接打交道,只通过接口(layer interface)。IP地址工作在第三层,MAC地址工作在第二层。当协议在发送数据包时,需要先封装第三层IP地址,第二层MAC地址的报头,但协议只知道目的节点的IP地址,不知道目的节点的MAC地址,又不能跨第二、三层,所以得用ARP协议服务,来帮助获取到目的节点的MAC地址。

主机在第一次向目标IP发送数据包前,需要用到arp协议获取目标IP的物理地址(MAC地址),之后会把该IP地址与MAC地址的对应关闭保存在arp缓存表中,下一次再向该IP地址发送数据时,则不需要再次发送arp请求。

eth_type_is_arp_and_my_ip(buf, plen)

这个函数是判断arp请求,以及请求的IP地址是否为板卡的IP地址

unsigned char eth_type_is_arp_and_my_ip(unsigned char *buf,unsigned  int len)
{
    unsigned char i=0;

    if (len<41)
    {
        return(0);
    }
    if(buf[ETH_TYPE_H_P] != ETHTYPE_ARP_H_V || buf[ETH_TYPE_L_P] != ETHTYPE_ARP_L_V) //判断是否为arp协议
    {
        return(0);
    }
    while(i<4)
    {
        if(buf[ETH_ARP_DST_IP_P+i] != ipaddr[i]) //判断IP地址是否为板卡的IP的地址
        {
            return(0);
        }
        i++;
    }
    return(1);
}
make_arp_answer_from_request(buf)

若判断接收到的数据包为对板卡IP地址的arp请求,则调用该函数构建arp响应帧。

void make_arp_answer_from_request(unsigned char *buf)
{
    unsigned char i=0;
    //
    make_eth(buf);
    buf[ETH_ARP_OPCODE_H_P]=ETH_ARP_OPCODE_REPLY_H_V;   //arp
    buf[ETH_ARP_OPCODE_L_P]=ETH_ARP_OPCODE_REPLY_L_V;
    // fill the mac addresses:
    while(i<6)
    {
        buf[ETH_ARP_DST_MAC_P+i]=buf[ETH_ARP_SRC_MAC_P+i];
        buf[ETH_ARP_SRC_MAC_P+i]=macaddr[i];
        i++;
    }
    i=0;
    while(i<4)
    {
        buf[ETH_ARP_DST_IP_P+i]=buf[ETH_ARP_SRC_IP_P+i];
        buf[ETH_ARP_SRC_IP_P+i]=ipaddr[i];
        i++;
    }
    // eth+arp is 42 bytes:
    enc28j60PacketSend(42,buf); 
}

ping请求及其实现

ping的工作原理

利用网络上机器IP地址的唯一性,给目标IP地址发送一个数据包,再要求对方返回一个同样大小的数据包来确定两台网络机器是否连接相通,时延是多少。

ICMP报文通用格式如下:
类型:1个字节。8表示回显请求报文,0表示回显响应报文。
代码:1个字节。回显请求报文、回显响应报文 时均为0。
校验和:2个字节。非重点,略过。
标识符:2个字节。发送ICMP报文的客户端进程的id,服务端会回传给客户端。因为同一个客户端可能同时运行多个ping程序,这样客户端收到回响报文,可以知道是响应给哪个客户端进程的。
序列号:2个字节。从0开始,客户端每次发送新的回显请求时+1。服务端原样会传。
数据:6个字节。客户端记录回显请求的发送时间,服务端记录响应的发送时间

ping请求响应的实现
if (buf[IP_PROTO_P] == IP_PROTO_ICMP_V && buf[ICMP_TYPE_P] == ICMP_TYPE_ECHOREQUEST_V)
//首先判断以太网帧的IP_PROTO_P(0x17)位和ICMP_TYPE_P(0x22)位
//buf[IP_PROTO_P] == IP_PROTO_ICMP_V:0x17位等于1时,表示为ICMP协议
//buf[ICMP_TYPE_P] == ICMP_TYPE_ECHOREQUEST_V):0x22等于8时表示为ICMP请求帧,0x22等于0时表示为响应帧
    {
      //构建ICMP响应帧
      make_echo_reply_from_request(buf, plen);
      continue;
    }
void make_echo_reply_from_request(unsigned char *buf,unsigned  int len)
{
    make_eth(buf);//构建以太网帧头
    make_ip(buf);//构建IP帧头
    buf[ICMP_TYPE_P]=ICMP_TYPE_ECHOREPLY_V;//将ICMP帧类型修改为响应帧
    //更改校验位
    if (buf[ICMP_CHECKSUM_P] > (0xff-0x08))
    {
        buf[ICMP_CHECKSUM_P+1]++;
    }
    buf[ICMP_CHECKSUM_P]+=0x08;
    enc28j60PacketSend(len,buf);//发送以太网帧
}

make_echo_reply_from_request(buf, plen) 构建ICMP响应帧
ICMP的ping命令请求帧与响应帧的数据段内容是一样的,需要更改的地方有几点
1、以太网帧头:源MAC地址与目标MAC地址交换
2、IP帧头:源IP地址与目标IP地址交换
3、ICMP帧类型:将请求帧标志(8)改为(0)
4、更改校验和

关于ping响应帧校验和的计算
先贴一下TCP/IP中校验和的计算方法:
(1)把校验和字段置为0;
(2)把需校验的数据看成以16位为单位的数字组成,依次进行二进制反码求和;
(3)把得到的结果存入校验和字段中。

而在这个ping响应帧的校验和计算用了一个比较巧妙方法,这也是这个工程里面一个比较有意思的地方。

//更改校验位
    if (buf[ICMP_CHECKSUM_P] > (0xff-0x08))
    {
        buf[ICMP_CHECKSUM_P+1]++;
    }
    buf[ICMP_CHECKSUM_P]+=0x08;

在ping请求帧与响应帧的数据段里面,可以发现唯一不同的地方就在于响应帧的帧类型位是0而请求帧的帧类型是8。换言之,只要将请求帧的校验和+0x08,就得到了响应帧的校验和,而不需要重新再计算校验和。还有一种情况是当buf[ICMP_CHECKSUM_P]+0x08 > 0xff,即当校验和的低八位 + 0x08后溢出,此时则要再校验和的高位+1.

ping测试

以太网控制器的网口使用网线与PC的网口连接,更改PC的网络设置,将PC的IP地址改成跟控制器的设定IP地址在同一个网段,但不能一样,否则会造成IP地址冲突,无法正常建立网络连接。
这是我的电脑网络设置。
在这里插入图片描述

进入电脑的控制台尝试ping 开发板的IP地址,成功ping通
在这里插入图片描述

为了了解整个ping通的过程,再深入一点,使用wireshark抓取网口的数据流。
在这里插入图片描述
通过抓包可以发现,在控制台发出ping请求后,电脑首先发出一个arp请求板卡IP地址(192.168.1.15)对应的MAC地址,板卡进行arp响应发送本地的MAC地址,arp过程完成后才进行ping的ICMP请求。若板卡不带arp协议,无法响应PC的arp请求,PC无法获取MAC地址,后面的ICMP请求就不会发出,即无法ping通。
最后贴一下整个ping过程的数据报文。

arp请求:
在这里插入图片描述
arp响应:
在这里插入图片描述
ping请求:
在这里插入图片描述
ping响应:
在这里插入图片描述

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
实现 PC 与 STM32f103+ENC28J60 的 UDP 通信,需要先了解 ENC28J60 的使用和 UDP 协议的基本知识。 ENC28J60 是一款低成本、低功耗的以太网控制器,它可以通过 SPI 接口与 STM32f103 等单片机进行通信,实现以太网通信。UDP(User Datagram Protocol)是一种无连接的、不可靠的传输协议,它不保证数据可靠的到达目标,但是具有传输速度快的优点。 以下是实现 PC 与 STM32f103+ENC28J60 的 UDP 通信的主要步骤: 1. 配置 ENC28J60 首先需要配置 ENC28J60 来与 STM32f103 进行通信,包括配置 SPI 接口、MAC 地址、IP 地址等。这个过程需要参考 ENC28J60 的数据手册和相关资料。 2. 实现 UDP 通信 在 STM32f103 上实现 UDP 通信,需要使用 UDP 库函数。可以使用标准的 socket 函数库,也可以使用第三方库,如 LWIP。在代码中需要设置本地端口和目标端口,以及本地 IP 地址和目标 IP 地址。 3. 实现数据传输 在 UDP 通信中,需要发送和接收数据。在 STM32f103 上实现数据传输,可以使用 DMA 传输或者中断传输。需要在代码中设置缓冲区来存储发送和接收的数据,并且需要处理数据包的校验和等相关信息。 4. 实现数据处理 在数据传输完成后,需要对接收到的数据进行处理,包括解析数据包、处理数据内容等。可以根据具体的需求进行数据处理。 总的来说,实现 PC 与 STM32f103+ENC28J60 的 UDP 通信需要对 ENC28J60 和 UDP 协议有一定的了解,同时需要使用相关的库函数和处理方法,才能够实现可靠的通信。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值