历史上的今天
返回首页

历史上的今天

今天是:2025年02月11日(星期二)

正在发生

2020年02月11日 | STM32之 IP ICMP ETHERNET的实现

2020-02-11 来源:eefocus

1.前言

嵌入式以太网开发是一个很有挑战性的工作。通过几个月的学习,个人觉得大致有两条途径。第一条途径,通过高级语言熟悉socket编程,例如C#或C++,熟悉bind,listen,connect,accept等函数,在嵌入式系统中应用 lwIP协议栈。第二种途径,通过分析嵌入式以太网代码,结合TCPIP协议栈规范逐步实践协议栈代码。第一种途径效率高,开发周期短,编写出来的代码性能稳定,第二种途径花的时间长,开发出来的代码功能不完善,但是由于紧紧结合TCPIP规范,可以了解的内容较多,适合学习。本文通过分析和修改AVRNET源码并移植到STM32平台,逐步实现TCPIP协议栈的各个子部分,包括ETHERNET部分,ARP部分,IP部分,ICMP部分,UDP部分,TCP部分和HTTP部分。

【 STM32NET学习笔记——索引】【代码仓库】

本文先实现ethernet部分和ARP部分。


1.2 其他说明

【硬件平台】 STM32+ENC28J60

【编译平台】 IAR 6.5

【IP地址】在实践之前,需要通过ipconfig命令查看PC机的IP地址和MAC地址,AVR的IP地址设定必须和PC机在同一个网段中。例如 :

PC机IP:192.168.1.102

AVR IP: 192.168.1.115

【局域网访问 】

如果有STM32开发板或者其他CPU的开发板的话,可以把开发板的以太网端口连接到路由器LAN端口,只要保证开发板的IP地址和PC机的IP地址在同一个网段。

【广域网访问 】

如果有固定的电信网IP地址的话,可以在路由器中设置静态端口映射,把某个端口映射成局域网内的IP地址和端口号。若没有固定IP地址的话,可使用花生壳软件虚拟一个域名。


1.3 代码仓库

【代码仓库】——CSDN Code代码仓库。

2.初始化

以太网协议栈的实现离不开以太网驱动芯片。以太网驱动如何实现请参考——ENC28J60学习笔记。TCPIP的实现离不开两个基本地址,IP地址和MAC地址。在本例中通过以下代码定义和实现。


struct.h头文件中 相关定义:

[cpp] view plain copy// MAC地址结构体

#pragma pack(1)

typedef struct _MAC_ADDR

{

BYTE byte[6];

}MAC_ADDR;

// IP地址结构体

#pragma pack(1)

typedef struct _IP_ADDR

{

BYTE byte[4];

}IP_ADDR;

main.c函数中的初始化代码:

[cpp] view plain copy// 初始化MAC地址

stm32_mac.byte[0] = ‘S’;

stm32_mac.byte[1] = ‘T’;

stm32_mac.byte[2] = ‘M’;

stm32_mac.byte[3] = ‘N’;

stm32_mac.byte[4] = ‘E’;

stm32_mac.byte[5] = ‘T’;

// 初始化IP地址,固定IP地址

stm32_ip.byte[0] = 192;

stm32_ip.byte[1] = 168;

stm32_ip.byte[2] = 1;

stm32_ip.byte[3] = 115;

MAC地址和IP地址均为自定义的结构体,结构体中为一个字节数组。严格来说,MAC地址不能胡乱定义,应严格遵守相关规范,如果条件允许的话可以使用带有全球唯一的MAC地址的EEPROM芯片。

3.实现ETHERNET

TCPIP是一系列协议的组合,其中最有名的为TCP协议和IP协议。但是千万不要忽视最底层的协议结构——ETHERNET。ETHERNET包括14个字节,称之为以太网首部,其中前六个字节为目标MAC地址,紧着的6个字节为源MAC地址,最后的两个字节为协议类型。以太网的实现通信时必须要知道双方的MAC地址,发送方不明确接收方的地址便通过ARP协议寻找目标MAC地址,如果依然没有结果则可只能把该报文转发给路由器,让路由器处理该报文。协议类型只需关心两种,0800的IP协议和0806的ARP协议。


ethernet.h中相关宏定义

[cpp] view plain copy// 协议类型 ARP报文

#define ETH_TYPE_ARP_V 0x0806

#define ETH_TYPE_ARP_H_V 0x08

#define ETH_TYPE_ARP_L_V 0x06

// 协议类型 以太网报文

#define ETH_TYPE_IP_V 0x0800

#define ETH_TYPE_IP_H_V 0x08

#define ETH_TYPE_IP_L_V 0x00

// 以太网报文头部长度 14

#define ETH_HEADER_LEN 14

// 目标MAC地址

#define ETH_DST_MAC_P 0

// 源MAC地址

#define ETH_SRC_MAC_P 6

// 协议类型

#define ETH_TYPE_H_P 12

#define ETH_TYPE_L_P 13

ethernet.c中相关函数

[cpp] view plain copyvoid eth_generate_header ( BYTE *rxtx_buffer, WORD_BYTES type, BYTE *dest_mac )

{

BYTE i;

// 配置以太网报文 目标MAC地址和源MAC地址

for ( i=0; i《sizeof(MAC_ADDR); i++)

{

rxtx_buffer[ ETH_DST_MAC_P + i ] = dest_mac[i];

// avr_mac为全局变量

rxtx_buffer[ ETH_SRC_MAC_P + i ] = stm32_mac.byte[i];

}

// 配置协议类型 IP报文或ARP报文

rxtx_buffer[ ETH_TYPE_H_P ] = type.byte.high;

rxtx_buffer[ ETH_TYPE_L_P ] = type.byte.low;

}

eth_generate_header函数实现了填充以太网首部的功能,第一个输入参数为发送接收缓冲区。第二个参数为IP类型,在AVRNET项目中传入的参数不是0800的IP协议类型就是0806的ARP协议类型。第三个参数为目标MAC地址,由于本机MAC地址作为了全局变量,可以在函数内部填充到缓冲区中。


4.实现ARP

为了使用最少的代码实现TCPIP功能,假设通过IP发送报文时已经确认了目标的IP地址,设备总是先被动的通过ARP先让PC机知道其MAC地址,这样当PC机发送UDP或者TCP报文时,在报文中已经包含了PC机的IP地址,设备仅需从rxtx_buffer中取出PC机IP地址。ARP协议是一个找邻居的过程,是一个广播找MAC的过程。发出者通过广播报文确认某个IP的MAC地址。ARP首部包括,2字节硬件类型,2字节协议类型,1字节硬件长度,1字节协议长度,2字节操作码,6字节发送者硬件地址,4字节发送者IP地址,6字节目标硬件地址和4字节目标IP地址。


在使用ARP协议时需要注意三点:

第一,操作码分为两种——ARP请求和ARP响应,ARP请求的编码为1,ARP响应的编码为2,先有请求后有响应。第二,发送ARP协议请求时请求方明确对方IP地址,但是不明确对方MAC地址,所以在请求报文中MAC地址全部以0替代。第三,由于不知道对方的MAC地址,所以只能通过广播帧发送以太网数据,所以以太网首部的前6个字节被FF填充。


为了便于ARP功能的实现,在arp.h文件中定义了以下宏定义

[cpp] view plain copy#define ARP_PACKET_LEN 28

// ARP请求

#define ARP_OPCODE_REQUEST_V 0x0001

#define ARP_OPCODE_REQUEST_H_V 0x00

#define ARP_OPCODE_REQUEST_L_V 0x01

// ARP响应

#define ARP_OPCODE_REPLY_V 0x0002

#define ARP_OPCODE_REPLY_H_V 0x00

#define ARP_OPCODE_REPLY_L_V 0x02

// 硬件类型 10M以太网

#define ARP_HARDWARE_TYPE_H_V 0x00

#define ARP_HARDWARE_TYPE_L_V 0x01

// 协议类型 IPV4

#define ARP_PROTOCOL_H_V 0x08

#define ARP_PROTOCOL_L_V 0x00

// 硬件地址长度

#define ARP_HARDWARE_SIZE_V 0x06

// 协议地址长度

#define ARP_PROTOCOL_SIZE_V 0x04

// 硬件类型 2字节

#define ARP_HARDWARE_TYPE_H_P 0x0E

#define ARP_HARDWARE_TYPE_L_P 0x0F

// 协议类型 2字节

#define ARP_PROTOCOL_H_P 0x10

#define ARP_PROTOCOL_L_P 0x11

// 硬件地址 1字节

#define ARP_HARDWARE_SIZE_P 0x12

// 协议地址长度 1字节

#define ARP_PROTOCOL_SIZE_P 0x13

// 操作码 2字节

#define ARP_OPCODE_H_P 0x14

#define ARP_OPCODE_L_P 0x15

// 发送者硬件地址 6字节

#define ARP_SRC_MAC_P 0x16

// 发送者IP地址 4字节

#define ARP_SRC_IP_P 0x1C

// 目标硬件地址 6字节

#define ARP_DST_MAC_P 0x20

// 目标IP地址 6字节

#define ARP_DST_IP_P 0x26

在没有操作系统的支持下,一般通过一个无限循环实现子功能的实现。项目中通过某个process不断查询是否存在网卡数据,如果有网卡数据则立刻保存源MAC地址。因为项目中没有维护ARP表,所以必须及时记录发送方的MAC地址,以便向它返回数据。紧着便是查询该报文是否为ARP请求,如果是ARP请求则返回ARP响应。具体代码如下 :

[cpp] view plain copyvoid server_process ( void )

{

MAC_ADDR client_mac;

IP_ADDR client_ip;

WORD plen;

// 获得新的IP报文

plen = enc28j60_packet_receive( (BYTE*)&rxtx_buffer, MAX_RXTX_BUFFER );

if(plen==0) return;

// 保存客服端的MAC地址

memcpy ( (BYTE*)&client_mac, &rxtx_buffer[ ETH_SRC_MAC_P ], sizeof( MAC_ADDR) );

// 检查该报文是不是ARP报文

if ( arp_packet_is_arp( rxtx_buffer, (WORD_BYTES){ARP_OPCODE_REQUEST_V} ) )

{

// 向客户端返回ARP报文

arp_send_reply ( (BYTE*)&rxtx_buffer, (BYTE*)&client_mac );

return;

}

}


4.1 查询ARP报文

查询该报文是否是针对设备的ARP报文需要确认三点,第一:确认以太网首部中的协议类型是否为ARP协议类型,ARP协议类型的值为0806H。第二,查询该ARP报文是否为ARP请求,该步骤需要到ARP首部中查询ARP操作码,ARP请求的操作码为1。第三,查询该ARP请求中的MAC地址是否和本机MAC匹配。


最后通过宏定义ARP_DEBUD决定是否通过串口输出发起者IP地址和MAC地址。通过串口打印可以确认该ARP报文的发起者。


[cpp] view plain copyBYTE arp_packet_is_arp ( BYTE *rxtx_buffer, WORD_BYTES opcode )

{

BYTE i;

// 该报文为ARP报文

if( rxtx_buffer[ ETH_TYPE_H_P ] != ETH_TYPE_ARP_H_V || rxtx_buffer[ ETH_TYPE_L_P ] != ETH_TYPE_ARP_L_V)

return 0;

// 确认ARP操作码 ARP请求 1 ARP应答2

if ( rxtx_buffer[ ARP_OPCODE_H_P ] != opcode.byte.high || rxtx_buffer[ ARP_OPCODE_L_P ] != opcode.byte.low )

return 0;

// 匹配IP地址

for ( i=0; i《sizeof(IP_ADDR); i++ )

{

if ( rxtx_buffer[ ARP_DST_IP_P + i] != stm32_ip.byte[i] )

return 0;

}

// 通过串口输出

#if ARP_DEBUG

printf(“ARP Message!rn”);

printf(“Source IP:%d.%d.%d.%drn”,

rxtx_buffer[ARP_SRC_IP_P+0],rxtx_buffer[ARP_SRC_IP_P+1],

rxtx_buffer[ARP_SRC_IP_P+2],rxtx_buffer[ARP_SRC_IP_P+3]);

printf(“Source MAC:%02X-%02X-%02X-%02X-%02X-%02Xrn”,

rxtx_buffer[ARP_SRC_MAC_P+0],rxtx_buffer[ARP_SRC_MAC_P+1],

rxtx_buffer[ARP_SRC_MAC_P+2],rxtx_buffer[ARP_SRC_MAC_P+3],

rxtx_buffer[ARP_SRC_MAC_P+4],rxtx_buffer[ARP_SRC_MAC_P+5]);

#endif

return 1;

}


4.2 生成ARP首部

生成ARP首部还是紧紧围绕两个地址展开,即目标MAC地址和目标IP地址,在ARP响应过程中,源MAC地址和IP地址现在转变为了目标MAC地址和IP地址。

[cpp] view plain copyvoid arp_generate_packet ( BYTE *rxtx_buffer, BYTE *dest_mac, BYTE *dest_ip )

{

unsigned char i;

// 硬件类型 0001 10M以太网

rxtx_buffer[ ARP_HARDWARE_TYPE_H_P ] = ARP_HARDWARE_TYPE_H_V;

rxtx_buffer[ ARP_HARDWARE_TYPE_L_P ] = ARP_HARDWARE_TYPE_L_V;

// 协议类型

rxtx_buffer[ ARP_PROTOCOL_H_P ] = ARP_PROTOCOL_H_V;

rxtx_buffer[ ARP_PROTOCOL_L_P ] = ARP_PROTOCOL_L_V;

// 硬件地址长度

rxtx_buffer[ ARP_HARDWARE_SIZE_P ] = ARP_HARDWARE_SIZE_V;

// 协议地址长度

rxtx_buffer[ ARP_PROTOCOL_SIZE_P ] = ARP_PROTOCOL_SIZE_V;

// 目标硬件地址和源硬件地址

for ( i=0; i《sizeof(MAC_ADDR); i++)

{

rxtx_buffer[ ARP_DST_MAC_P + i ] = dest_mac[i];

rxtx_buffer[ ARP_SRC_MAC_P + i ] = stm32_mac.byte[i];

}

// 目标IP地址和源IP地址

for ( i=0; i《sizeof(IP_ADDR); i++)

{

rxtx_buffer[ ARP_DST_IP_P + i ] = dest_ip[i];

rxtx_buffer[ ARP_SRC_IP_P + i ] = stm32_ip.byte[i];

}

}


4.3 响应ARP请求

ARP响应可以体现出TCP IP报文产生的基本过程,即层层包装。先包装以太网首部,在包装ARP首部,最后通过ENC28J60发送即可。


[cpp] view plain copyvoid arp_send_request ( BYTE *rxtx_buffer, BYTE *dest_ip )

{

unsigned char i;

MAC_ADDR dest_mac;

// generate ethernet header

for ( i=0; i《sizeof(MAC_ADDR); i++)

dest_mac.byte[i] = 0xff;

eth_generate_header ( rxtx_buffer, (WORD_BYTES){ETH_TYPE_ARP_V}, (BYTE*)&dest_mac );

// generate arp packet

for ( i = 0 ; i 《 sizeof(MAC_ADDR) ; i++)

dest_mac.byte[i] = 0x00;

// set arp opcode is request

rxtx_buffer[ ARP_OPCODE_H_P ] = ARP_OPCODE_REQUEST_H_V;

rxtx_buffer[ ARP_OPCODE_L_P ] = ARP_OPCODE_REQUEST_L_V;

arp_generate_packet ( rxtx_buffer, (BYTE*)&dest_mac, dest_ip );

// send arp packet to network

enc28j60_packet_send ( rxtx_buffer, sizeof(ETH_HEADER) + sizeof(ARP_PACKET) );

}

5.测试

PC机通过ping命令发送一个ICMP报文,ping命令是确认网络是否连接的命令,例如发送ping 192.168.1.115,由于PC机不明确该IP地址的MAC地址,所以会先发送一个ARP请求。STM32设备可捕获该ARP请求,并通过串口输出发送ARP请求的设备的IP地址和MAC地址。此时先不用理会是否可以ping通,因为会在以后的文章中实现。


在开始之前可以通过ipconfig /all指令查询本机的IP地址和MAC地址,通过arp -a指令查询PC机中ARP缓冲表。如果有必要可使用arp –d清除缓冲表的所有内容。

推荐阅读

史海拾趣

Analogix Semiconductor公司的发展小趣事

Analogix Semiconductor是一家全球领先的高速接口解决方案供应商,专注于设计和制造创新的数字视频传输芯片和接口技术。以下是Analogix Semiconductor公司发展的相关故事:

  1. 成立与初期阶段:Analogix Semiconductor公司于2002年成立,总部位于美国加利福尼亚州圣何塞市。公司的创始人包括James Zhou博士和Bill Hata。公司成立之初,主要致力于研发和生产数字视频传输芯片,旨在为消费电子市场提供高性能的连接解决方案。

  2. 技术创新与产品推出:Analogix Semiconductor公司通过不断的技术创新和产品研发,迅速在行业中树立了良好的声誉。公司推出了一系列创新的高速接口解决方案,包括DisplayPort、HDMI、USB和MIPI等技术。这些产品以其高质量、高带宽和低功耗而受到市场的广泛认可。

  3. 市场拓展与全球业务:随着公司产品线的不断丰富和技术实力的增强,Analogix Semiconductor迅速拓展了全球市场。公司在亚洲、欧洲和北美等地建立了广泛的销售网络和合作伙伴关系,为客户提供定制化的解决方案和优质的服务。

  4. 应用领域与客户合作:Analogix Semiconductor的产品被广泛应用于消费电子、计算机、通信和汽车等领域。公司与全球领先的电子设备制造商、芯片设计公司和系统集成商等客户建立了长期稳定的合作关系。通过与客户的密切合作,Analogix Semiconductor不断了解市场需求,提供符合客户要求的创新产品和解决方案。

  5. 持续发展与未来展望:作为一家持续成长的公司,Analogix Semiconductor将继续致力于技术创新和产品优化,满足不断变化的市场需求。公司将继续加强研发投入,拓展产品应用领域,提升市场竞争力。在未来,Analogix Semiconductor将继续秉承“创新、质量、服务”的经营理念,为客户提供更加优质和可靠的解决方案,实现共同发展和成功。

Concord Semiconductor Corp公司的发展小趣事

为了进一步提升市场竞争力,Concord Semiconductor Corp积极寻求与其他企业的战略合作。通过与全球领先的电子设备制造商建立长期合作关系,公司成功将其产品打入国际市场,实现了业务的快速增长。同时,公司还与多家研究机构展开技术合作,共同研发新型半导体材料和技术,为公司的长远发展提供了有力支撑。

Aydin Corp公司的发展小趣事

Aydin Corp公司诞生于电子行业的初期,当时的市场充满了机遇与挑战。创始人凭借对电子技术的深厚理解和对市场需求的敏锐洞察,决定投身于这一领域。初创时期,公司面临着资金短缺、技术瓶颈和市场竞争等多重困难。然而,通过不懈的努力和持续的创新,Aydin Corp逐渐在市场中站稳了脚跟。

AEMC Instruments公司的发展小趣事

除了提供优质的产品外,AEMC Instruments公司还注重服务升级和客户关怀。公司建立了完善的客户服务体系,提供售前咨询、售后技术支持等全方位服务。同时,公司还定期举办技术培训和交流活动,帮助客户更好地使用和维护产品。这些举措不仅提高了客户的满意度和忠诚度,也促进了公司与客户的长期合作关系。

以上五个故事是基于AEMC Instruments公司的发展情况所做出的概括性描述,旨在展示公司在电子行业中的成长历程和取得的成就。当然,具体的发展故事还需要根据公司的实际情况和历史记录来深入挖掘和呈现。

博众电气(BZCN)公司的发展小趣事

博众电气自创立之初,就明确了技术立企的战略方向。公司投入大量资源进行技术研发,不断推出具有竞争力的电子产品。其中,一项关键技术的突破为博众电气的发展奠定了坚实基础。这项技术使得公司产品在性能上大幅超越同类产品,赢得了市场的广泛认可。凭借这一技术突破,博众电气逐渐在电子行业中崭露头角。

Compact公司的发展小趣事

随着全球环保意识的提高,电子行业对环保和可持续发展的要求也越来越高。Compact公司积极响应这一趋势,加大环保投入,采用环保材料和工艺,降低生产过程中的能耗和排放。同时,公司还致力于推动循环经济的发展,通过回收和再利用废旧电子产品,减少资源浪费和环境污染。这一举措不仅提升了公司的社会形象,还为公司的可持续发展奠定了坚实基础。

请注意,以上故事仅为示例,并不代表任何真实存在的企业情况。如果需要关于特定企业(如Compact公司)的准确信息,建议查阅相关公司的官方资料、行业报告或新闻报道。

问答坊 | AI 解惑

检测绝缘栅极双极型晶体管(IGBT)好坏的简易方法

1、判断极性首先将万用表拨在R×1KΩ挡,用万用表测量时,若某一极与其它两极阻值为无穷大,调换表笔后该极与其它两极的阻值仍为无穷大,则判断此极为栅极(G)。其余两极再用万用表测量,若测得阻值为无穷大,调换表笔后测量阻值较小。在测量阻值 ...…

查看全部问答>

很奇怪的问题 wince 串口测试应用程序 writefile(...)函数没有执行完就失败了,然后程序死掉了。

串口测试的writefile(...)函数没有执行完就失败了,没有任何返回结果。writefile的下一个语句做断点,没有执行到。 if(hComm!=INVALID_HANDLE_VALUE&&dwCharToWrite!=0)         {           &nbs ...…

查看全部问答>

终于研发成功

终于研发成功,cpu是s3c2410,64M   SDRAM,网口为cs8900 flash(k9f1208 64m)   实现了只用nand flash 就可以启动引导vxworks. 公司送bsp及全套开发文档 ,另可配公司的开发板 有意着QQ联系641328010 请注明 arm…

查看全部问答>

STC12LE5206AD 程序将引脚置为0 却检测到为高

有谁能够帮忙解决下啊? 本人qq:853887107…

查看全部问答>

RT_THREAD中的异常与中断

  异常是导致处理器脱离正常运行转向执行特殊代码的任何事件,如果系统不及时处理,系统轻则出错,重着导致系统毁灭性的瘫痪。所以正确地处理异常避免错误的发生是提高软件的鲁棒性重要的一方面,对于嵌入式系统更加如此。 异常可以分成两类 ...…

查看全部问答>

DIY导航避障车规整贴

DIY导航避障车小组今日下午展开第一次例会,在本次例会中大家集中讨论了无线的选择,避障的选择等等部分,并对小车进行了前景规划,以谷歌的自动驾驶车为终极目标,大家积极发言例会持续了一个半小时,并表示未来会采用各种新技术。下次再来把细节 ...…

查看全部问答>

11.06【每周讨论】软文——春风化雨、润物无声

您知道“软文”的含义吗?什么,不知道。赶紧百度一下吧,别被OUT了 [ 本帖最后由 longxtianya 于 2011-11-6 20:19 编辑 ]…

查看全部问答>

分享一些收集的msp430方面的书籍

最近接触了一下msp430的单片机从网上搜集了一些书籍希望对大家有帮助。…

查看全部问答>

泰克2012年度春季创新论坛北京站见闻

    4月24日阴雨天气,阿牛哥上午9点去北京朝阳区五洲皇冠大酒店参加泰克2012年度春季创新论坛。热情的工程师们朋友已经在会场里聆听泰克的测试测量行业趋势观点,泰克领先测试测量方案,还有一些工程师朋友在展厅参观泰克最新 ...…

查看全部问答>

全球智能机Q2出货1.5亿部 中兴进前五

本帖最后由 jameswangsynnex 于 2015-3-3 20:01 编辑 …

查看全部问答>