精华内容
下载资源
问答
  • STM32F4x7VGT6-DP83848 demo原理图;对应的pdf格式PCB参考文件,可查看位号,布局位置等
  • STM32F4+DP83848以太网通信指南系列

    千次阅读 2019-04-11 10:03:27
    转自 ... DP83848 Ethernet Boardhttp://www.waveshare.net/wiki/DP83848_Ethernet_Board STM32F4+DP83848以太网通信指南系列(一):知识储备 https://blog.csdn.net/m0_37...

    转自 https://blog.csdn.net/m0_37777700/article/details/83620671

    DP83848 Ethernet Board http://www.waveshare.net/wiki/DP83848_Ethernet_Board

    STM32F4+DP83848以太网通信指南系列(一):知识储备 https://blog.csdn.net/m0_37777700/article/details/83620671

    STM32F4+DP83848以太网通信指南系列(二):系统时钟 https://www.hexcode.cn/article/show/stm32-ethernet2

    STM32F4+DP83848以太网通信指南系列(三):中断向量 https://www.hexcode.cn/article/show/stm32-ethernet3

    STM32F4+DP83848以太网通信指南系列(四):PHY配置 https://www.hexcode.cn/article/show/stm32-ethernet4

    STM32F4+DP83848以太网通信指南系列(五):MAC+DMA配置 https://www.hexcode.cn/article/show/stm32-ethernet5

    STM32F4+DP83848以太网通信指南系列(六):Wireshark使用 https://www.hexcode.cn/article/show/stm32-ethernet6

    STM32F4+DP83848以太网通信指南系列(七):发包流程 https://www.hexcode.cn/article/show/stm32-ethernet7

    STM32F4+DP83848以太网通信指南系列(八):收包流程 https://www.hexcode.cn/article/show/stm32-ethernet8

    STM32F4+DP83848以太网通信指南系列(九):自己写一个ARP协议 https://www.hexcode.cn/article/show/stm32-ethernet9

     

    展开全文
  • STM32F4+ucosIII+LWIP+DP83848

    2015-11-21 15:50:25
    STM32F407、ucosIII、LWIP、DP83848
  • STM32F407+DP83848网口设计电路,demo板电路图纸,含有网口,SD卡,USB等相关外围接口电路
  • 先回顾一下之前的章节我们做好的准备工作,在《STM32F4+DP83848以太网通信指南第五章:MAC+DMA配置》结束时我们封装了一个DP83848的初始化函数,该函数完成了PHY的配置,MAC层的配置,DMA的配置,并且启用了以太网...

    本章为系列指南的第七章,讲述如何在之前的基础上,编写程序在STM32上发送一个网络包,并使用WireShark进行验证。

    先回顾一下之前的章节我们做好的准备工作,在《STM32F4+DP83848以太网通信指南第五章:MAC+DMA配置》结束时我们封装了一个DP83848的初始化函数,该函数完成了PHY的配置,MAC层的配置,DMA的配置,并且启用了以太网中断,函数命名为DP83848Init(),那么今天,我们要做的主要任务就是编写一个类似的DP83848Send(u8* data, u16 length)函数。

    可以在本章的一开始跟大家剧透一个好消息,有了《STM32F4+DP83848以太网通信指南第四章:PHY配置》 和 《STM32F4+DP83848以太网通信指南第五章:MAC+DMA配置》 的基础,我们本章最终实现的DP83848Send(u8* data, u16 length)函数,只有两行代码,非常非常简单。这两行代码我暂时先不贴出来,我们来顺着原来的思路,根据相关文档和官方示例代码,顺藤摸瓜,一步一步深入了解以太网发包的流程,最终理解体系结构后,也就水到渠成能够写出来了。

    在 《STM32F4+DP83848以太网通信指南第五章:MAC+DMA配置》 最后一部分提到在LWIP官方样例中,路径为STM32F4x7_ETH_LwIP_V1.1.1\Utilities\Third_Party\lwip-1.4.1\port\STM32F4x7\Standalone\ethernetif.c的文件中,第76行有个low_level_init()函数,该函数调用ETH库函数对MAC底层及DMA进行了初始化。同样的,这份文件的138行,有个名为low_level_output(struct netif *netif, struct pbuf *p)的函数,疑似是向外输出网络包的函数,下面就对这部分代码进行分析,并试着用其中的核心逻辑进行测试。

    因为ethernetif.c这份代码本身隶属于LWIP,而我们是不使用LWIP的,所以这份代码只能尽量去看懂和借鉴,想要原封不动地使用是不可以的。

    我们先完整地贴出这个函数:

    /**
     * This function should do the actual transmission of the packet. The packet is
     * contained in the pbuf that is passed to the function. This pbuf
     * might be chained.
     *
     * @param netif the lwip network interface structure for this ethernetif
     * @param p the MAC packet to send (e.g. IP packet including MAC addresses and type)
     * @return ERR_OK if the packet could be sent
     *         an err_t value if the packet couldn't be sent
     *
     * @note Returning ERR_MEM here if a DMA queue of your MAC is full can lead to
     *       strange results. You might consider waiting for space in the DMA queue
     *       to become availale since the stack doesn't retry to send a packet
     *       dropped because of memory failure (except for the TCP timers).
     */
    
    static err_t low_level_output(struct netif *netif, struct pbuf *p) {
        err_t errval;
        struct pbuf *q;
        u8 *buffer =  (u8 *)(DMATxDescToSet->Buffer1Addr);
        __IO ETH_DMADESCTypeDef *DmaTxDesc;
        uint16_t framelength = 0;
        uint32_t bufferoffset = 0;
        uint32_t byteslefttocopy = 0;
        uint32_t payloadoffset = 0;
    
        DmaTxDesc = DMATxDescToSet;
        bufferoffset = 0;
    
        /* copy frame from pbufs to driver buffers */
        for(q = p; q != NULL; q = q->next) {
            /* Is this buffer available? If not, goto error */
            if((DmaTxDesc->Status & ETH_DMATxDesc_OWN) != (u32)RESET) {
                errval = ERR_BUF;
                goto error;
            }
    
            /* Get bytes in current lwIP buffer */
            byteslefttocopy = q->len;
            payloadoffset = 0;
    
            /* Check if the length of data to copy is bigger than Tx buffer size*/
            while( (byteslefttocopy + bufferoffset) > ETH_TX_BUF_SIZE ) {
                /* Copy data to Tx buffer*/
                memcpy( (u8_t *)((u8_t *)buffer + bufferoffset), (u8_t *)((u8_t *)q->payload + payloadoffset), (ETH_TX_BUF_SIZE - bufferoffset) );
    
                /* Point to next descriptor */
                DmaTxDesc = (ETH_DMADESCTypeDef *)(DmaTxDesc->Buffer2NextDescAddr);
    
                /* Check if the buffer is available */
                if((DmaTxDesc->Status & ETH_DMATxDesc_OWN) != (u32)RESET) {
                    errval = ERR_USE;
                    goto error;
                }
    
                buffer = (u8 *)(DmaTxDesc->Buffer1Addr);
    
                byteslefttocopy = byteslefttocopy - (ETH_TX_BUF_SIZE - bufferoffset);
                payloadoffset = payloadoffset + (ETH_TX_BUF_SIZE - bufferoffset);
                framelength = framelength + (ETH_TX_BUF_SIZE - bufferoffset);
                bufferoffset = 0;
            }
    
            /* Copy the remaining bytes */
            memcpy( (u8_t *)((u8_t *)buffer + bufferoffset), (u8_t *)((u8_t *)q->payload + payloadoffset), byteslefttocopy );
            bufferoffset = bufferoffset + byteslefttocopy;
            framelength = framelength + byteslefttocopy;
        }
    
        /* Note: padding and CRC for transmitted frame
           are automatically inserted by DMA */
    
        /* Prepare transmit descriptors to give to DMA*/
        ETH_Prepare_Transmit_Descriptors(framelength);
    
        errval = ERR_OK;
    
    error:
    
        /* When Transmit Underflow flag is set, clear it and issue a Transmit Poll Demand to resume transmission */
        if ((ETH->DMASR & ETH_DMASR_TUS) != (uint32_t)RESET) {
            /* Clear TUS ETHERNET DMA flag */
            ETH->DMASR = ETH_DMASR_TUS;
    
            /* Resume DMA transmission*/
            ETH->DMATPDR = 0;
        }
        return errval;
    }

    这个函数的官方注释描述的就是用来向外发送以太网包的,函数中说要发的包在第二个参数,类型为pbuf结构体指针的参数p中,并且说了p可能是个链表,我们看到函数的两个入参都是结构体参数,这两个结构体的定义我们不需要管,是LWIP自己封装的一个结构体。我们去寻迹参数p的用法,在代码片段的30行,使用q变量和for循环遍历p,因此我们能够确定p就是个头尾相接的pbuf链表。继续观察遍历体中的操作逻辑,我们看到整个for循环的主要目的就是在尝试将q->payload中的byte,利用函数memcopy()向buffer变量中堆,并且做了一些长度的校验,我们继而去观察一下buffer变量的定义,第19行的u8 *buffer = (u8 *)(DMATxDescToSet->Buffer1Addr);是一个比较重要的线索,由此我们可以抽丝剥茧出整体的逻辑,应该就是将首尾相接的p遍历出来,取其中每个元素的payload区域,向DMATxDescToSet->Buffer1Addr中压。最后,第73行的ETH_Prepare_Transmit_Descriptors(framelength);调用了ETH库中的函数,实现了最终的结局,将网络包发出去,入参的framelength应该就是需要发出去的包长度,包内容应该就是通过DMA技术,将内存中的DMATxDescToSet->Buffer1Addr发出去了。

    有了以上针对low_level_output()函数的分析,我们来做实验印证一下,因为我们从零开始构建的项目没有LWIP,也没有ethernetif.c,更没有low_level_output()函数,因此,函数内部的逻辑都需要我们自己手动实现,慢着,不要一看到「手动实现」就头疼,你以为手动实现就很复杂吗?不,LWIP把事情搞复杂了,又是pbuf又是链表的,还有长度判断导致的Buffer2NextDescAddr切换(详见第43-62行一整段,不过不重要),如果我们手动写这段逻辑,放弃一些异常处理,再放弃那些跟LWIP强相关的结构体,我们整个发包函数只要两行就行:

    void DP83848Send(u8* data, u16 length){
        memcpy((u8 *)DMATxDescToSet->Buffer1Addr, data, length);
    
        /* Prepare transmit descriptors to give to DMA*/
        ETH_Prepare_Transmit_Descriptors(length);
    }

    这里附带说明一下,并不是LWIP原版代码又臭又长,LWIP要做一个TCP/IP全栈协议,还要考虑包长度溢出的众多问题,我们精简版的协议很多不需要考虑,因此可以放弃很多繁琐的操作。

    有了上述DP83848Send()函数,下面来做个小程序试验一下:

    int main() {
        u8 MyMacAddr[6] = {0x08, 0x00, 0x06, 0x00, 0x00, 0x09};
        /* 下面是一段60byte大小的ARP报文,手动构建的 */
        u8 mydata[60] = {    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
                         0x00, 0x01, 0x08, 0x06, 0x00, 0x01, 0x08, 0x00, 0x06, 0x04,
                         0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc0, 0xa8,
                         0x02, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8,
                         0x02, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
        u32 clock;
    
        /* 默认调用SystemInit,系统时钟168MHz */
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);        //4位抢占,0位响应
    
        DP83848Init(MyMacAddr);
    
        while(1){
            DP83848Send(mydata, 60);
    
            clock = 42000000;    //1s延时,while中每个步进需要4个周期
            while(clock--);
        }
    
    }

    使用Keil编译,用JLink下载到STM32F407中,给开发板接上网线,用WireShark就可以在网口中观察到STM32每隔1秒钟向外发送ARP报文了,虽然这段报文几乎没有任何意义。

    我使用WireShark截图如下:

    总结一下,这一章我们完成了一个DP83848Send()发包函数,这个函数可以接受一个字节buffer,一个字节buffer的长度,将这个buffer通过以太网发送出去,buffer内部的内容全部需要我们手工构建。DP83848Send()函数的设计思路来自于分析LWIP官网示例,主要是ethernetif.c中的代码。下一章我们同样根据这份代码,分析收包逻辑,实现STM32对以太网上数据的监听。

     

     

     

    展开全文
  • STM32F4+DP83848以太网通信指南系列(一):知识储备

    万次阅读 多人点赞 2018-11-01 15:04:39
    STM32F4+DP83848以太网通信指南系列(一):知识储备 前言:项目需求使用STM32F407进行以太网通信,并涉及到数据链路层的工业以太网通信,使用LWIP协议栈并不能满足需求,因此需要自己摸清...

    丁丁的博客:https://www.hexcode.cn/article/show/stm32-ethernet1

    2017-12-09 17:32:19

    STM32F4+DP83848以太网通信指南系列(一):知识储备

    前言:项目需求使用STM32F407进行以太网通信,并涉及到数据链路层的工业以太网通信,使用LWIP协议栈并不能满足需求,因此需要自己摸清STM32F407调用外部PHY进行网络收发包的过程,并在此基础上尝试自己构建适用于项目的网络协议栈。我基本上是从零开始着手这个项目的,之前只有一些STC51系列和STM32F1系列单片机开发的经验,项目开发过程中学习、参考、借鉴了很多网络上的教程和博客,在此尤其感谢正点原子团队发布的相关视频教程。公司买的开发板主芯片是一颗STM32F407,搭配了一颗DP83848的PHY,因此本系列教程将使用DP83848进行适配,同时原子哥的学习板和教程中是使用LAN8720这颗PHY进行适配的,本系列指南也会花一些篇幅介绍各种PHY与STM32芯片进行适配的方法。

    为了您更好地阅读本系列,请点击原创连接进行浏览:

    本章为系列指南第一章,主要是介绍一下项目思路,并且尽可能列出从零开始着手开发这个项目过程中,所需要理解的各类知识点,关于这些知识点,如果需要更详细的介绍,请列为看官自行百度谷歌。

    STM32F407简介

    • STM32F407主频168MHz,主频这个数值的意义可以这么理解:每秒执行168,000,000步简单指令,也就是每毫秒168,000次,每微妙168次。我们工业以太网进入实时通信后的数据传输周期大概每2ms交互4次,每次60个Byte,最差的算法复杂度计算下来需要每毫秒4 / 2 * 60byte = 120步(事实上用不了这么多步),而这颗STM32F07的芯片提供每毫秒168,000次操作,相差3个数量级,因此大致上看来这颗芯片是完全可以胜任的。

    • STM32F407可以通过多个时钟配置方案达到最高168MHz,一般是使用外部8MHz晶振,通过锁相环倍频到168MHz,这个我们后面章节会讲。

    • 目前STM32芯片有三种代码编写的方案:第一是寄存器方案,通过内部各种地址定义的宏来操作,晦涩难懂,需要不停地查资料才能理解;第二种是标准库函数方案,沿用一份很久没有更新的标准库进行开发,标准库ST公司已经不打算维护升级了,但是网络上资料非常丰富;第三种是HAL库函数方案,继标准库推出多年之后,ST公司推出了HAL库函数版本的SDK,这份SDK保持维护和更新,并且ST公司在F7系列芯片上只允许这使用HAL库函数编程,目前为止该库函数版本的书籍资料并不是很丰富,值得庆幸的是正点原子团队也率先推出了STM32F1,F4,F7全系列的HAL库函数教程,参见:http://openedv.com/thread-13912-1-1.html 。但是针对本项目,我们依然使用标准库函数进行开发,理由是可以参考借鉴大量的以太网通信实验代码,以及ST官方的LWIP协议栈代码,毕竟这些现有成熟的代码还都是基于标准库函数进行编写的。

    • STM32文档资料方面,请自行下载以下文件:

    1. ST-RM0090 STM32F4参考手册中文版.pdf
    2. 正点原子-STM32F4开发指南-标准库函数中文版.pdf
    3. 正点原子-STM32F4标准库函数例程.rar

    以太网层次的理解,白话版

    以太网分为很多层(按照OSI模型有七层,按照TCP/IP协议有五层),我们只需要了解其中这么几个:

    1. 最底下的PHY物理层:发送和接受光、电、电磁波等各类模拟量的信号,使其在介质上传输,尽最大可能保证其完整和准确。
    2. PHY上方的MAC层:构建有意义的帧格式,提供一定能力的校验功能。
    3. 网络层:为整个网络拓扑服务,一般运行在后台,用户无感知,为网络结构提供路由(ARP)、通路测试(ICMP:Ping)等基础服务。
    4. 除了上述三个层,其余的层我们可以一并理解为应用层,因为其余各层都是由应用程序根据不同的协议自行构建报文的。应用层所有的报文可分为两个大类,TCP和UDP,TCP提供三次握手+四次挥手,因此网络资源消耗比较大,但是提供了确切的成功或者不成功的信息;UDP只需要发送方发送一次报文就可以,因此发送和接受成功与否并不可知。构建于TCP之上的协议有著名的HTTP、FTP、Telnet等;构建于UDP之上的协议相对来说不是很常见,像SNMP、TFTP这些简单协议基本上都用不到,原因很简单,UDP提供的是不确定的网络通信,一般网络交互都需要非常确切的成功失败的返回结果,记得曾几何时OICQ(暴露年龄)的聊天信息就是通过UDP协议传送的,现在估计早就换成TCP了。

    STM32F407以太网架构

    有了以上以太网层次的理解后,我们将STM32F407的网络架构与之对照理解。

    • STM32F407内置了MAC层的处理能力,并且使用DMA技术强化了MAC层能力。
    • DMA(Direct Memory Access,直接内存存取),是现代处理器一个很重要的技术,它允许不同速度的硬件装置来沟通,也不需要依赖于CPU的大量中断负载。如果没有DMA,CPU需要从来源把输入数据复制到暂存器,然后把它们再次写回到需要使用的地方。在这个时间中,CPU对于其他的工作来说完全无法使用。—以上节选自百度百科
    • 关于DMA的进一步理解:STM32F407的总线架构中,单独分配了一个MAC层的DMA数据总线,也就是无论你用不用,核心逻辑总会在固定的时间段释放对总线数据的访问,由一段MAC层控制器来对数据总线上的数据进行存取处理。这段处理,是独立于168MHz主频之外的。如图所示(图片来自RM0090ST官方STM32F4手册P50):
    • 拥有DMA能力的MAC层,可以在用户代码完全不干预的情况下,将DMA中的数据发送给PHY,同时在PHY接到数据后,将PHY的数据读取到DMA中,并通知中断。以上操作的前提是:用户提前配置好MAC,PHY,DMA,以及中断向量,这部分工作是这个系列的核心内容,涉及到的内容比较多,会在后续章节逐一介绍。

    PHY模块

    • STM32F407内置的MAC层让使用者封装字节数据为网络帧结构,但STM32F407并没有能力将其发送到电缆上去,特别是涉及到光电电磁等模拟量的信号,没法处理,因此需要外部PHY模块进行辅助。我们知道STM32F407是一个纯数字电路芯片,而PHY需要处理的又是模拟信号,功耗比纯数字电路大很多(摸上去发烫)。因此从成本、体积、工艺等各方面考虑,ST公司并未在STM32系列芯片中增加PHY模块,而是建议用户在PCB板上自行增加PHY模块。目前已经有一些芯片公司推出了内置PHY的芯片,不过比较少,可自行百度谷歌或者,淘宝,对,你没听错,搜这方面资料时有时候淘宝比较厉害。
    • PHY芯片可选型号比较丰富,有纯粹的PHY芯片(DP83848,LAN8720),也有集成MAC层+PHY层的芯片(ENC28J60,DM9000),还有内置TCP/IP协议栈硬解码的MAC+PHY芯片(W5500)。这里要注意的是,我们这个项目涉及到工业以太网协议,IO数据通过OSI模型中的链路层收发,因此千万不能选择集成TCP/IP协议栈的PHY芯片,那种芯片通过I2C或者SPI将TCP/UDP报文抽象出来发送给上位机,已经将链路层不符合TCP/UPD规则的过滤掉了。
    • MAC跟PHY通信是使用RMII或者MII接口,前者引脚使用少,频率高;后者引脚使用多,频率低。一般使用前者。DP83848可以使用RMII或者MII,LAN8720只可以使用RMII。无论RMII还是MII,你都只需要做个了解,在配置时根据电路引脚的接法,配置好。具体的时序图不需要看,不需要你直接控制,有现成的函数帮忙。
    • PHY有地址的概念,MAC理论上最多能控制32个PHY(可以自己做一个32端口的交换机了)。DP83848的42-46引脚读入自己的ADDR,MAC在通信时可以指定其中某一ADDR进行通信,ADDR不匹配的PHY自动忽略信号。
    • DP83848的datesheet下载:DP83848C.pdf

    Ethernet库函数

    • STM32的开发环境是Keil5,通过配置Keil5,可以下载获得STM32F4系列的SDK,也就是标准库函数的SDK,但是这份标准库函数里面是没有有关Ethernet方面的函数的,为什么?因为ST公司认为PHY都没集成到CPU里面,库函数就更没办法集成进去了,包括PHYADDR,MII,RMII的选择,有的PHY甚至不使用MII或者RMII接口,而是高度封装后使用SPI进行通信。因此ST没法为所有用户提供一个通用的函数库,但是官方提供了一个DEMO程序,该程序作用非常大,到ST官网,搜索框输入LWIP就能搜到这个DEMO,官方文档编号是STSW-STM32070,适用于STM32F4系列的以太网通信DEMO,使用LWIP协议栈,适配PHY为DP83848,正好是我们使用的这一款。
    • 上述页面下载时需要注册ST,用户名密码登录,而ST的响应速度超慢,可能还需要科学上网,列位可以到我上传的地址下载:en.stsw-stm32070.zip
    • 这份文档里/STM32F4x7_ETH_LwIP_V1.1.1/Libraries/STM32F4x7_ETH_Driver下面的stm32f4x7_eth.c以及配套的.h文件是比较关键的,类似于标准库提供的那些I2C,UART,SPI等库函数文件。该文件的使用方法和理解,后续章节会详细介绍。

    LWIP协议栈

    • LWIP是一个精简版的TCP/IP协议栈,广泛用于嵌入式设备中,占用内存少。
    • LWIP通过调用MAC层来实现网络通信,调用的库函数就定义在上述的STM32F4x7_ETH_Driver文件中。
    • LWIP可以解决大多数应用层场景的通信需求,比如TCP和UDP。
    • LWIP无法解决链路层的通信问题,因此我们本项目并没有移植LWIP,而是参考借鉴LWIP调用MAC层部分的代码,从而自己构建协议栈,完成收发包任务。

    总结

    以上就是整个STM32以太网通信的基础知识,知识点比较零碎,我尽量用白话文把他们罗列出来,因为我也是从零开始学着做的,期间看过不少晦涩难懂的文档。在着手写这个系列的时候,我们公司的这个项目已经被我搞定了,虽然具体实现的协议,应用的场景需要保密无法透露,但我仍然希望就STM32嵌入式以太网通信这个通用的问题,就我的能力讲解出来,帮助有需要的朋友。

    展开全文
  • 本章为系列指南的第八章,讲述如何使用STM32F407芯片配合DP83848进行以太网数据的收包流程,将监听到的网络包数据通过UART传给PC,同时辅以WireShark监听对比验证。 关于UART,也就是串口...在《STM32F4+DP83848以...

    本章为系列指南的第八章,讲述如何使用STM32F407芯片配合DP83848进行以太网数据的收包流程,将监听到的网络包数据通过UART传给PC,同时辅以WireShark监听对比验证。

    关于UART,也就是串口通信的使用,这里不做赘述,我们这里预设两个函数分别为UART6Init()和UART6Send(),实现的功能是串口6的初始化和发送。

    以太网中断

    在《STM32F4+DP83848以太网通信指南第五章:MAC+DMA配置》中,我们已经添加了以太网中断,其思路就是想让每次以太网上有收到包都能触发中断,我们可以在中断中将DMA中的数据包取出来进行分析,然后复位,让芯片下一次继续响应中断。

    配置中断的代码非常简单,跟其他任何中断都一样,这里再复习一次:

    void ETH_NVIC_Config(void) {
        NVIC_InitTypeDef   NVIC_InitStructure;
    
        /* Enable the Ethernet global Interrupt */
        NVIC_InitStructure.NVIC_IRQChannel = ETH_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);
    }

    整个工程的优先级组别选用的NVIC_PriorityGroup_4,有4位抢占位,0位响应位,也就是可以分配16个可以互相嵌套的中断等级。这里以太网中断的主优先级为1,相当于第二高,前面预留了个优先级为0的,用来分配给系统计时器,毕竟不能因为以太网数据的响应影响系统走时。

    在UART6Init()串口初始化函数中,给串口的中断等级是2,低于以太网中断,因为串口的波特率是9600,要远远低于以太网速率,如果给串口的优先级过高,会影响以太网的使用。

    配置了中断后,我们还需要知道中断的入口函数,这个函数名是固定死的,不能乱写,我们去找找。

    在《STM32F4+DP83848以太网通信指南第五章:MAC+DMA配置》我提到,中断配置代码中的ETH_IRQn变量,我们可以在stm32f4xx.h文件中找到定义,是在一个枚举结构中。那与之对应的中断入口名称该怎么找呢,原来所有的中断入口的定义,都用汇编入口的方式定义在启动文件中,这份启动文件我们之前一直没有关注过,现在打开看一看,在startup_stm32f40_41xxx.s中148行有其定义,截图如下。

    中断中的数据处理

    配置好了以太网中断,也知道了中断入口函数的名称,下面我们就来编写以太网中断函数。打开工程中的stm32f4xx_it.c文件,一般每一个使用了中断的STM32工程都会有这么一个文件,用来集中管理中断入口。追加以下代码:

    /**
      * @brief  This function handles ethernet DMA interrupt request.
      * @param  None
      * @retval None
      */
    void ETH_IRQHandler(void)
    {
        /* Handles all the received frames */
        /* check if any packet received */
          while(ETH_CheckFrameReceived()){ 
            /* process received ethernet packet */
            Pkt_Handle();
        }
        /* Clear the Eth DMA Rx IT pending bits */
        ETH_DMAClearITPendingBit(ETH_DMA_IT_R);
        ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS);
    }

    上面的代码配上英文的注释也很好理解,以太网中断中首先检查是否接受到以太网的数据包,如果是,就调用Pkt_Handle()函数进行下一层的分析和处理,最后两行Clear复位中断标记,让下一次中断能够产生。那么问题就集中到Pkt_Handle()函数上来了。kt_Handle()函数是我自己命名的,这个函数的原型取自LWIP中的LWIP_Pkt_Handle(),我们先来观察一下LWIP中的包分析函数怎么写的:在STM32F4x7_ETH_LwIP_V1.1.1/Project/Standalone/udp_echo_client/src/netconf.c中,有如下代码:

    /**
    * @brief  Called when a frame is received
    * @param  None
    * @retval None
    */
    void LwIP_Pkt_Handle(void)
    {
      /* Read a received packet from the Ethernet buffers and send it to the lwIP for handling */
      ethernetif_input(&gnetif);
    }

    可以看到LWIP继续调用了下层的ethernetif_input(),继续追踪到我们之前提到的最底层文件ethernetif.c,是不是有种似曾相识的感觉,这个文件前几章我们不止一次遇到过,分别为我们提供了low_level_init、low_level_output多个重要函数,我们现在又一次遇到它了,看上去它这次要为我们的以太网监听提供low_level_input了。

    果不其然,在ethernetif_input函数中,我们看到了这个预料中的函数调用,截图如下:

    我这里把路径为STM32F4x7_ETH_LwIP_V1.1.1/Utilities/Third_Party/lwip-1.4.1/port/STM32F4x7/Standalone/ethernetif.c的原版low_level_input()的所有代码都贴出来,感兴趣的朋友可以仔细研读:

    /**
     * Should allocate a pbuf and transfer the bytes of the incoming
     * packet from the interface into the pbuf.
     *
     * @param netif the lwip network interface structure for this ethernetif
     * @return a pbuf filled with the received packet (including MAC header)
     *         NULL on memory error
     */
    static struct pbuf * low_level_input(struct netif *netif)
    {
      struct pbuf *p, *q;
      u16_t len;
      int l =0;
      FrameTypeDef frame;
      u8 *buffer;
      uint32_t i=0;
      __IO ETH_DMADESCTypeDef *DMARxNextDesc;
    
    
      p = NULL;
    
      /* get received frame */
      frame = ETH_Get_Received_Frame();
    
      /* Obtain the size of the packet and put it into the "len" variable. */
      len = frame.length;
      buffer = (u8 *)frame.buffer;
    
      /* We allocate a pbuf chain of pbufs from the Lwip buffer pool */
      p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
    
      /* copy received frame to pbuf chain */
      if (p != NULL)
      {
        for (q = p; q != NULL; q = q->next)
        {
          memcpy((u8_t*)q->payload, (u8_t*)&buffer[l], q->len);
          l = l + q->len;
        }    
      }
    
      /* Release descriptors to DMA */
      /* 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;
      }
      return p;
    }

    同样的,配合注释应该也容易理解。22行的frame变量取到了以太网数据包,ETH_Get_Received_Frame函数,不用LWIP也是有的,在之前我们提到过的stm32f4x7_eth.c文件中,len和buffer两个变量一个是包长度,一个是包内容头指针。下面/* copy received frame to pbuf chain */那一段是用链表遍历的方式,将以太网包数据放入LWIP处理数据的pbuf链表中,方便LWIP上层逻辑获取数据,这里我们不使用LWIP,这一段可忽略。接下来所有的操作都是针对DMA进行的,将DMA复位,因此我们需要保留,否则会一直产生重复的中断。

    通过以上的分析,我们可以轻松写出自己的Pkg_Handle()函数了:

    void Pkt_Handle(void) {
        FrameTypeDef frame;
    
        /* 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;
    
        printf("0011%d0022\n", receiveLen);    //将每一个的包长度发往串口
    
        if(receiveBuffer[41] == 201){        //如果第42字节是十进制201,则将整个包内容发往串口
            for (i = 0; i < receiveLen; i++) {
                printf("%c", receiveBuffer[i]);
            }
        }
    
        /* 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;
        }
    }

    验证和总结

    上述函数,配合ETH_IRQHandler中断中的调用,完成了以太网的收包,并且将接受的包的长度使用0011%d0022通过printf函数通过UART发往了PC端,因为如果将整个包内容发往PC的话,串口数据会非常多。同时如果为了验证buffer中的内容能正确获取,我们写了一个if判断,判断如果数据包中的第42个字节为201,则将包内容转发到串口中去。

    我们用JLink烧录进STM32F4,将PC的有线网卡与STM32直接,打开WireShark和串口通信助手进行观察和验证,截图如下。

    红色框框部分是关注重点,我们在CMD命令窗口ping 192.168.1.201,可以触发一个ARP包,这个包中的第42个字节就是201,因此可以触发STM32中的if判断,将包内容通过串口转发给PC,而其他普通包,STM32则使用0011%d0022的格式将包长度发给了PC,整个实验顺利完成。

    总结一下,本章我们依旧分析到了ethernetif.c文件,这次是观察的它的low_level_input()函数,借助这个函数,我们编写了我们自己的处理包的逻辑函数Pkg_Handle(),并通过以太网中断入口函数ETH_IRQHandler调用它,最后我们成功的使用WireShark配合串口进行了收包的验证。

    我们可以发现这个系列教程的后半段几乎都在不停地围绕LWIP库中的ethernetif.c文件进行分析,到目前为止,它的几个重要底层函数low_level_init、low_level_output、low_level_input已经分别为我们的以太网初始化、以太网发包、以太网收包等代码提供了重要的核心逻辑。

    下一章,是我们这个系列的最后一章,我们将在STM32F4上,利用之前实验过的各个功能,自己构建一个能响应ARP协议的程序。有了ARP协议的处理过程,我们就能在此基础上扩展更多其他的协议,即使遇到工业以太网跑在链路层的各个协议,我们也能捡其重点,按照自己的意愿,随心所欲的搭建了。

     

     

     

     

    展开全文
  • 本章为系列指南第二章,主要是介绍一下STM32F4的时钟配置。时钟是一个嵌入式产品从零开始开发的基石,一切逻辑都在时钟的节奏中安静地弹奏着,时钟为整个电路带来了欢快的「心跳」。开发者如果对时钟没有控制能力,...
  • 目的:实现STM32F407+FreeRTOS+Ethernet(DP83848)+Lwip实现socket通信,在实现之前我们先来了解下几点储备知识 一. 以太网行业标准MII/RMII 1 以太网接口MII,RMII MII即“媒体独立接口”,也叫“独立于介质的...
  • 本章为系列指南第一章,主要是介绍一下项目...STM32F407主频168MHz,主频这个数值的意义可以这么理解:每秒执行168,000,000步简单指令,也就是每毫秒168,000次,每微秒168次。我们工业以太网进入实时通信后的数据传...
  • 我们在第一章知识储备章节说到,STM32F407会在168MHz主频之外分配一定的时间释放总线数据用来处理DMA,这其中就包含MAC层的DMA,复习一下STM32F4的总线架构图,(图片来自RM0090ST中文STM32F4手册P50): ...
  • 首先我们关注一下PHY的配置,前面讲到,我们的工程使用了开发板上的一颗DP83848芯片。 RMII和ADDR的确定 接下来我们来看开发板的原理图: 通过电路原理图可以看到接线方式是使用RMII接口模式接线的,因此接下来...
  • 单片机型号:STM32F407VGT 本章为系列指南第一章,主要是介绍一下项目思路,并且尽可能列出从零开始着手开发这个项目过程中,所需要理解的各类知识点,关于这些知识点,如果需要更详细的介绍,请列为看官自行百度...
  • 我移植了 STM32F4 + ucosii + lwip + lan8720, 编译过了,发现网卡ping不通。 单步发现,网卡初始化都没过. 卡死在下面的实现 while (ETH_GetSoftwareResetStatus() == SET); 通过单步能正常运行的第三方工程,发现...
  • 本章为系列指南的第三章,这一章将会在正式进入以太网的配置和使用之前,复习一下STM32的中断以及中断向量,因为我们以后要在中断中响应以太网收包。 中断—嵌入式中的多线程 从51单片机到ARM架构的32位微芯片,到...
  • 本章为系列指南的第六章,这一章我们暂时离开Keil,离开STM32,离开C语言,这一章我们要去了解一些以太网相关的知识,特别是学习使用大名鼎鼎的调试以太网通信程序的利器:WireShark。 帧结构 我们从小就听说过...
  • 本章为系列指南的第九章,终结篇,本章主要来分析一下完整的ARP协议,并在STM32F4中实现一个精简的ARP协议响应流程。 ARP协议的本质是使局域网内的其他主机能够知道我在哪儿,比如在局域网上有人冲着所有人喊了一句...
  • 在学习stm32f4xx开的的时候,大家可能遇到,demo板,有的是DP83848,有的是LAN8742,在移植st官方例程的时候,大家可能有个疑问,他们芯片特性有啥区别,以及驱动有啥区别,下面一一介绍介绍。 芯片特新的区别, 1...
  • 转自 https://www.hexcode.cn/article/show/stm32-ethernet8 在实际操作上边网址里的教程时,需要注意: 1. 接收模式可以选择中断或轮询,因为我们采用接收中断来处理,所以这里选择Interrupt Mode 2. 计算机...

空空如也

空空如也

1 2 3
收藏数 44
精华内容 17
关键字:

dp83848stm32f4