STM32开发笔记55:STM32F4+DP83848以太网通信指南系列(九):自己写一个ARP协议

本文是STM32以太网通信系列的最后一章,重点分析ARP协议并实现了一个简单的ARP响应流程。介绍了ARP协议的作用,如何处理ARP请求和响应数据包,并提供了编码实现的思路。该教程回顾了整个系列,从STM32初始化到TCP/IP协议栈的ARP协议,为后续协议扩展提供指导。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本章为系列指南的第九章,终结篇,本章主要来分析一下完整的ARP协议,并在STM32F4中实现一个精简的ARP协议响应流程。

ARP协议的本质是使局域网内的其他主机能够知道我在哪儿,比如在局域网上有人冲着所有人喊了一句「IP为XXXX的家伙,你在哪儿」,我一听,XXXX不是我的IP吗,我得回答他啊,于是我冲着所有人(也可以是单独的这个人)喊一句「我在这儿呢,我的MAC是YYYY」,这样局域网内所有用户,包括交换机就知道IP为XXXX的家伙MAC地址是YYYY,并且交换机知道了我连在它的第N个端口上,以后有人要通过交换机向IP为XXXX的我发信息,交换机就把数据包发到N port上去了。没有上面这套流程,局域网上就没人知道你是谁,你的IP多少,你的MAC多少,你连在交换机大佬的第几个端口上,自然你也收不到任何非广播包数据了。

ARP请求

如果我要向一个之前没有任何联系的主机主动发一个数据包,一般情况下都会触发一个ARP询问,比如我们上一章《STM32F4+DP83848以太网通信指南第八章:收包流程》中,最后的实验,我打开CMD命令行,输入了一个指令ping 192.168.1.201,这时候PC的底层设备(一般是网卡)首先判断一下自己的缓存中有没有192.168.1.201这个IP的缓存信息,如果没有通信记录或者时间过长失效了,就得在网络上广播一下,找找看当前有没有谁在用192.168.1.201这个IP地址,包内容如下:

ff ff ff ff ff ff 00 0e c6 d4 1d d4 08 06 00 01
08 00 06 04 00 01 00 0e c6 d4 1d d4 c0 a8 01 c8
00 00 00 00 00 00 c0 a8 01 C9

WireShark分析以上报文的解析如下图所示:

ARP响应

目标主机的MAC地址为FF FF FF FF FF FF时,交换机会将此包广播到所有端口,这时候所有节点的网卡上都能收到这个广播包,如果某一节点的IP地址为192.168.1.201则需要对此包进行响应,判断依据为上述包的以下特征:

第一行倒数第4,第3字节:0806,代表的是ARP协议

第二行第6字节:01,代表的是ARP请求

第三行最后4字节:c0 a8 01 C9,代表的是192.168.1.1,跟自己寄存器中的值匹配

响应数据包为以下内容:

00 0e c6 d4 1d d4 00 11 0e 0b 03 8a 08 06 00 01
08 00 06 04 00 02 00 11 0e 0b 03 8a c0 a8 01 c9
00 0e c6 d4 1d d4 c0 a8 01 c8 00 00 00 00 00 00
00 00 00 00 00 00 00 00 20 20 20 20

上述中,以下数据是动态变化的:

第一行前6个字节:00 0e c6 d4 1d d4,根据请求包中的第7-12六个字节决定,表示目标MAC地址

第一行第7-12六个字节:00 11 0e 0b 03 8a,自由设置的自己的MAC地址

第二行第7-12六个字节:00 11 0e 0b 03 8a,自由设置的自己的MAC地址

第二行倒数4个字节:c0 a8 01 c9,自己的IP地址,也就是请求包中所呼唤的IP地址,192.168.1.201

第三行前6个字节:00 0e c6 d4 1d d4,根据请求包中的第7-12六个字节决定,表示目标MAC地址

第三行第7-10四个字节:c0 a8 01 c8,根据请求包第二行倒数四个字节决定,表示目标IP地址

这些信息都可以用WireShark分析出来,WireShark对每一个字节的含义都有明确的解释。

编码实现

有了特征匹配及填充规则,配合我们之前的STM32发包和收包DEMO,我们就可以编程实现ARP数据包的响应了。

#define LEN_ARP 42

/* 以下为业务逻辑需要用到的全局变量 */
u8 IPAddr[4];

vu8  SystemStatus; /* 系统状态,bit0:是否需要发送被动响应数据包,bit1:是否打开了PNIO开关,bit2:是否需要发送PNControl request */
vu16 sendLen;      /* 发送包长度 */
u8 sendBuffer[1024];   /* 发送包BUFFER */
u8 arp_answer[LEN_ARP]={
    0,0,0,0,0,0, /* ArpAskerMac */ 0,0,0,0,0,0, /* myMac */ 0x08, 0x06, 0x00, 0x01,
    0x08, 0x00, 0x06, 0x04, 0x00, 0x02, 0,0,0,0,0,0, /* myMac */ 0,0,0,0, /* IP address */
    0,0,0,0,0,0, /* ArpAskerMac */ 0,0,0,0/* ArpAskerIP */
};

int main() {
    SystemStatus = SS_NOTHING;
    sendLen = 0;    

    /* 默认调用SystemInit,系统时钟168MHz */
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);        //4位抢占,0位响应

    DelayInit(168);        //初始化168MHz

    NVIC_SetPriority (SysTick_IRQn, 0);        //系统滴答定时器优先级最高  

    DP83848Init(MyMacAddr);        //初始化DP83848

    while(1) {
        if (0x01 == (SystemStatus & 0x01)){
            /* 如果需要发送被动网卡响应 */
            DP83848Send(sendBuffer, sendLen);            
            sendLen = 0;
            SystemStatus = SystemStatus & 0xFE; /* bit0置为0 */
        }
    }
}

void Pkt_Handle(void) {
    FrameTypeDef frame;
    __IO ETH_DMADESCTypeDef *DMARxNextDesc;

    /* get received frame */
    frame = ETH_Get_Received_Frame();
    /* Obtain the size of the packet and put it into the "len" variable. */
    receiveLen = frame.length;
    receiveBuffer = (u8 *)frame.buffer;

    if (Match(receiveBuffer, ArpProtocol, 12, 2) && Match(receiveBuffer, IPAddr, 38, 4)){
        /* ARP */
        //printf("ARP test received\n");
        memcpy(sendBuffer, arp_answer, LEN_ARP);

        for (int i = 0; i < 6; i++) {
            /* ArpAskerMac */
            sendBuffer[i] = receiveBuffer[6 + i];    
            sendBuffer[32 + i] = receiveBuffer[6 + i];
        }
        for (int i = 0; i < 4; i++) {
            /* ArpAskerIP */
            sendBuffer[38 + i] = receiveBuffer[28 + i];    
        }

        sendLen = LEN_ARP;
        SystemStatus = (SystemStatus | 0x01); /* 打开网卡被动数据发送开关 */
    }

    /* Check if frame with multiple DMA buffer segments */
    if (DMA_RX_FRAME_infos->Seg_Count > 1) {
        DMARxNextDesc = DMA_RX_FRAME_infos->FS_Rx_Desc;
    } else {
        DMARxNextDesc = frame.descriptor;
    }

    /* Set Own bit in Rx descriptors: gives the buffers back to DMA */
    for (i = 0; i < DMA_RX_FRAME_infos->Seg_Count; i++) {
        DMARxNextDesc->Status = ETH_DMARxDesc_OWN;
        DMARxNextDesc = (ETH_DMADESCTypeDef *)(DMARxNextDesc->Buffer2NextDescAddr);
    }

    /* Clear Segment_Count */
    DMA_RX_FRAME_infos->Seg_Count = 0;

    /* When Rx Buffer unavailable flag is set: clear it and resume reception */
    if ((ETH->DMASR & ETH_DMASR_RBUS) != (u32)RESET) {
        /* Clear RBUS ETHERNET DMA flag */
        ETH->DMASR = ETH_DMASR_RBUS;
        /* Resume DMA reception */
        ETH->DMARPDR = 0;
    }
}

编码的总体思想就是中断中快进快出,用变量来标记状态,主循环中不断判断状态并复位状态。

使用这种方式,可以继续扩展ICMP协议,实现PING指令,以及更多按需裁剪的TCP/IP协议,当然扩展的协议越多,这种纯粹的if判断也会越多,以后会细分各个层,在层次中继续if判断,当到了顶层应用层之后,就只管TCP/UDP协议了,内容一般都是应用协议自己规划的比如HTTP,FTP这些协议,只需要把上层协议的数据结构填充到下层协议的内容区域就行了。当遇到数据包比较庞大的时候,还需要分包传输,暂时我们没有用到,LWIP中有相应的实现,有需求的话可以去研究。此外,我们实验中的代码都是基于数组进行数据装箱拆箱操作的,而网络包的协议是按层次来的,使用数组不断的进行数据的填充是非常消耗性能的,因此在遇到数据量比较大,需要实现的协议比较多时,仍然建议按照LWIP的思路使用收尾相接的链式结构进行拆装箱。

系列总结

到此我们这个系列就算告一段落了,后面的工业以太网的协议的分析和实现我不方便做公开的教程,也是自己一步一步摸索过来的,目前自己在设计PCB板,后期这个工程需要制板贴片并做成最终的工业产品。

我们来回顾总结一下这个系列教程,首先我们对STM32F407的时钟、中断等相关知识做了一个梳理,接着我们认识了STM32中的MAC,以及跟MAC搭配的PHY,同时还对DMA技术做了一个粗略的了解。试着初始化相关的GPIO,使能了MAC,DMA,PHY,这样一个DP83848Init()函数就搞定了;再后来我们又完成DP83848Send()函数,能够发包了,接着配置了以太网中断,能够在Pkt_Handle()函数中进行以太网收包了,以上函数的编写,我们都是参考的ethernetif.c文件,它的几个底层函数low_level_init、low_level_output、low_level_input给我们提供了重要的线索。最后对TCP/IP协议栈中的ARP协议进行了分析,并运用之前的全部知识,进行了ARP响应的编码实现,并对以后其他协议的扩展实现提出了思路和优化建议。

好了,期待今后有更多的系列教程跟大家分享。再见!

<think>好的,用户需要关于STM32F4微控制器的以太网示例程序和配置方法。首先,我需要回忆一下STM32F4系列以太网模块相关知识。STM32F4通常使用MAC控制器,需要外接PHY芯片,比如LAN8720A。用户可能已经有一些例程,但需要移植到自己的项目或者GD32F407上,这可能涉及到硬件差异的调整。 用户提供的参考引用中提到移植例程时修改了GPIO配置,这说明硬件引脚可能不同,需要检查时钟和引脚映射。另外,他们提到了使用LAN8720和LwIP协议栈,还有FreeModbus,这可能涉及到TCP/IP协议栈的配置。 接下来,我需要整理配置步骤。首先,硬件连接部分,确认PHY芯片的接口是否正确,比如RMII所需的引脚是否正确连接。然后,软件配置方面,可能需要使用STM32CubeMX生成初始化代码,配置MAC和PHY,接着移植LwIP,并编应用层代码如TCP服务器。 还要考虑用户可能的常见问题,比如时钟配置是否正确,PHY地址是否匹配,以及软件移植时的库函数调整。此外,参考引用中的外部资源链接可能需要被包含,以帮助用户找到示例代码。 用户的问题还可能涉及具体代码示例,比如如何初始化以太网外设,如何配置中断,以及如何验证连接。需要确保提供的代码片段正确,并指出关键点,比如使用HAL库函数、检查PHY状态等。 最后,生成的相关问题需要围绕移植中的常见挑战、LwIP的优化、PHY芯片的选择以及协议栈的选择,这些都能帮助用户进一步解决可能遇到的问题。</think>### STM32F4以太网功能开发指南 #### 一、硬件配置要点 1. **PHY芯片选型**:STM32F4系列内置以太网MAC控制器,需外接PHY芯片(常用型号:LAN8720A、DP83848) 2. **接口标准**:推荐使用RMII接口(减少引脚占用),需连接以下号: - $REF\_CLK$(50MHz时钟) - $TXD[0:1]/RXD[0:1]$ - $CRS\_DV$ 和 $RX\_ER$ 3. **硬件连接示例**: ```c // RMII接口引脚映射(以STM32F407为例) GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_ETH); // REF_CLK GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_ETH); // MDIO GPIO_PinAFConfig(GPIOC, GPIO_PinSource1, GPIO_AF_ETH); // MDC ``` #### 二、软件配置流程 1. **CubeMX配置**(关键步骤): - 启用ETH外设 - 选择RMII模式 - 配置PHY地址(LAN8720默认为0x01) - 生成初始化代码 2. **PHY初始化代码示例**: ```c void MX_ETH_Init(void) { heth.Instance = ETH; heth.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE; heth.Init.PhyAddress = LAN8720_PHY_ADDRESS; // PHY地址设置[^1] HAL_ETH_Init(&heth); // 检查PHY连接状态 if(HAL_ETH_ReadPHYRegister(&heth, PHY_BSR, &regvalue) == HAL_OK) { if(regvalue & PHY_LINKED_STATUS) { // 连接成功处理 } } } ``` #### 三、LwIP协议栈移植 1. 推荐使用CubeMX自带的LwIP中间件 2. 关键配置参数: - 内存池大小(`MEM_SIZE`建议≥16KB) - TCP窗口大小(`TCP_WND`) - ARP表项数量 3. **TCP服务器示例**: ```c void tcp_server_init() { struct tcp_pcb *pcb = tcp_new(); tcp_bind(pcb, IP_ADDR_ANY, 8080); tcp_listen(pcb); tcp_accept(pcb, server_accept); } err_t server_accept(void *arg, struct tcp_pcb *newpcb, err_t err) { tcp_recv(newpcb, recv_callback); return ERR_OK; } ``` #### 四、移植注意事项 1. **时钟配置**:必须保证PHY的REF_CLK输入精确50MHz,可通过PLL配置实现 2. **中断处理**:需使能ETH全局中断并实现`HAL_ETH_IRQHandler` 3. **内存管理**:建议使用零拷贝模式提升网络吞吐量 #### 五、参考资源 1. ST官方例程:`STM32Cube_FW_F4_Vxx/Projects/ETH_Application` 2. 移植实例参考:GD32F407移植记录(含PHY驱动修改说明) 3. 完整项目示例:STM32F407+LAN8720+LWIP移植实例[^2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值