网络通讯---MCU移植LWIP

打印 上一主题 下一主题

主题 1114|帖子 1114|积分 3342

使用的MCU型号为STM32F429IGT6,PHY为LAN7820A
目标是通过MCU的ETH给LWIP提供输入输出从而实现基本的Ping应答

OK废话不多说我们直接开始
下载源码


  • LWIP包源码:lwip源码
    -在这里下载

  • ST官方支持的ETH包:ST-ETH支持包
    这里下载

创建工程

这里我使用我的STM32外扩RAM工程,若是手里无有外扩内存的板卡也可以直接使用点灯工程

参加ETH支持包

将刚刚下载的ETH支持包里
   STM32F4x7_ETH_LwIP_V1.1.1\Libraries\STM32F4x7_ETH_Driver
  目次下有/inc /src 两个文件夹,分别存放着ETH驱动的源文件和头文件
把他们对应的参加源码工程中
   \Libraries\STM32F4xx_StdPeriph_Driver
  的 /inc 和 /src中
然后将stm32f4x7_eth_conf_temp.h重定名为stm32f4x7_eth_conf.h
在keil工程中参加他们!
修改stm32f4x7_eth_conf.h

直接编译会报错,由于没ETH使用的delay函数,这里直接不使用ETH的delay,直接注释掉USE_Delay

修改stm32f4x7_eth.c

在这个文件的一开始会发现

搜索这里的宏界说是发现这些描述符和Buffer占用了大量的空间,描述符占用了320byte,由于后面用DMA搬运以是使用片内RAM,而这里的Buffer一共占用了约莫38Kbyte,这非常大,以是一般放在外部RAM,这里我使用的片外SRAM是IS42S16400J 拥有8M内存,以是可以放在片外SRAM,以是这里先注释掉,稍后使用malloc分配内存给它们,如果移植的板卡无片外扩展SRAM则不用管这里,直接放在内部RAM

然后注释的后面添加指针
  1. ETH_DMADESCTypeDef *DMARxDscrTab;
  2. ETH_DMADESCTypeDef *DMATxDscrTab;
  3. uint8_t *Tx_Buff;
  4. uint8_t *Rx_Buff;
复制代码
这里需要使用malloc.c和malloc.h
malloc.c
  1. #include "malloc.h"
  2. #include "stdio.h"
  3. //         
  4. //内存池(4字节对齐)
  5. #pragma pack(4)
  6.         u8 mem1base[MEM1_MAX_SIZE];
  7.         u8 mem2base[MEM2_MAX_SIZE] __attribute__((at(0xD0000000))); //外部SRAM内存池
  8. #pragma pack()
  9. //内存管理表
  10. u16 mem1mapbase[MEM1_ALLOC_TABLE_SIZE];                                                                                                        //内部SRAM内存池MAP
  11. u16 mem2mapbase[MEM2_ALLOC_TABLE_SIZE] __attribute__((at(0xD0000000+MEM2_MAX_SIZE)));        //外部SRAM内存池MAP
  12. //内存管理参数          
  13. const u32 memtblsize[SRAMBANK]={MEM1_ALLOC_TABLE_SIZE,MEM2_ALLOC_TABLE_SIZE};        //内存表大小
  14. const u32 memblksize[SRAMBANK]={MEM1_BLOCK_SIZE,MEM2_BLOCK_SIZE};                                        //内存分块大小
  15. const u32 memsize[SRAMBANK]={MEM1_MAX_SIZE,MEM2_MAX_SIZE};                                                        //内存总大小
  16. //内存管理控制器
  17. struct _m_mallco_dev mallco_dev=
  18. {
  19.         mymem_init,                                                        //内存初始化
  20.         mem_perused,                                                //内存使用率
  21.         mem1base,mem2base,                        //内存池
  22.         mem1mapbase,mem2mapbase,//内存管理状态表
  23.         0,0,                                                           //内存管理未就绪
  24. };
  25. //复制内存
  26. //*des:目的地址
  27. //*src:源地址
  28. //n:需要复制的内存长度(字节为单位)
  29. void mymemcpy(void *des,void *src,u32 n)
  30. {
  31.         u8 *xdes = des;
  32.         u8 *xsrc = src;
  33.         while(n--) *xdes++ = *xsrc++;
  34. }
  35. //设置内存
  36. //*s:内存首地址
  37. //c :要设置的值
  38. //count:需要设置的内存大小(字节为单位)
  39. void mymemset(void*s,u8 c,u32 count)
  40. {
  41.         u8 *xs = s;
  42.         while(count--) *xs++=c;
  43. }
  44. //内存管理初始化  
  45. //memx:所属内存块
  46. void mymem_init(u8 memx)
  47. {
  48.         mymemset(mallco_dev.memmap[memx],0,memtblsize[memx]*2); //内存状态表清零
  49.         mymemset(mallco_dev.membase[memx], 0,memsize[memx]);        //内存池所有数据清零  
  50.         mallco_dev.memrdy[memx]=1;                                                                //内存管理初始化OK  
  51. }
  52. //获取内存使用率
  53. //memx:所属内存块
  54. //返回值:使用率(0~100)
  55. u8 mem_perused(u8 memx)  
  56. {  
  57.     u32 used=0;  
  58.     u32 i;  
  59.     for(i=0;i<memtblsize[memx];i++)  
  60.     {  
  61.         if(mallco_dev.memmap[memx][i])used++;
  62.     }
  63.     return (used*100)/(memtblsize[memx]);  
  64. }
  65. //内存分配(内部调用)
  66. //memx:所属内存块
  67. //size:要分配的内存大小(字节)
  68. //返回值:0XFFFFFFFF,代表错误;其他,内存偏移地址
  69. u32 mymem_malloc(u8 memx,u32 size)  
  70. {  
  71.     signed long offset=0;  
  72.     u16 nmemb;        //需要的内存块数  
  73.                 u16 cmemb=0;//连续空内存块数
  74.     u32 i;  
  75.     if(!mallco_dev.memrdy[memx])mallco_dev.init(memx);//未初始化,先执行初始化
  76.     if(size==0)return 0XFFFFFFFF;//不需要分配
  77.     nmemb=size/memblksize[memx];          //获取需要分配的连续内存块数
  78.     if(size%memblksize[memx])nmemb++;  
  79.     for(offset=memtblsize[memx]-1;offset>=0;offset--)//搜索整个内存控制区  
  80.     {     
  81.                 if(!mallco_dev.memmap[memx][offset])cmemb++;//连续空内存块数增加
  82.                 else cmemb=0;                                                                //连续内存块清零
  83.                 if(cmemb==nmemb)                                                        //找到了连续nmemb个空内存块
  84.                 {
  85.             for(i=0;i<nmemb;i++)                                          //标注内存块非空
  86.             {  
  87.                 mallco_dev.memmap[memx][offset+i]=nmemb;  
  88.             }  
  89.             return (offset*memblksize[memx]);//返回偏移地址  
  90.                 }
  91.     }  
  92.     return 0XFFFFFFFF;//未找到符合分配条件的内存块  
  93. }  
  94. //释放内存(内部调用)
  95. //memx:所属内存块
  96. //offset:内存地址偏移
  97. //返回值:0,释放成功;1,释放失败;  
  98. u8 mymem_free(u8 memx,u32 offset)  
  99. {  
  100.         int i;  
  101.   if(!mallco_dev.memrdy[memx])//未初始化,先执行初始化
  102.         {
  103.                 mallco_dev.init(memx);   
  104.     return 1;//未初始化  
  105.   }  
  106.   if(offset<memsize[memx])//偏移在内存池内.
  107.   {  
  108.                 int index=offset/memblksize[memx];                        //偏移所在内存块号码  
  109.     int nmemb=mallco_dev.memmap[memx][index];        //内存块数量
  110.     for(i=0;i<nmemb;i++)                                                  //内存块清零
  111.     {  
  112.                         mallco_dev.memmap[memx][index+i]=0;  
  113.     }  
  114.     return 0;  
  115.   }else return 2;//偏移超区了.  
  116. }  
  117. //释放内存(外部调用)
  118. //memx:所属内存块
  119. //ptr:内存首地址
  120. void myfree(u8 memx,void *ptr)  
  121. {  
  122.         u32 offset;  
  123.         printf("myfree\r\n");       
  124.     if(ptr==NULL)return;//地址为0.  
  125.         offset=(u32)ptr-(u32)mallco_dev.membase[memx];  
  126.     mymem_free(memx,offset);//释放内存     
  127. }  
  128. //分配内存(外部调用)
  129. //memx:所属内存块
  130. //size:内存大小(字节)
  131. //返回值:分配到的内存首地址.
  132. void *mymalloc(u8 memx,u32 size)  
  133. {  
  134.   u32 offset;                                                                               
  135.         offset=mymem_malloc(memx,size);                                    
  136.   if(offset==0XFFFFFFFF)return NULL;  
  137.   else return (void*)((u32)mallco_dev.membase[memx]+offset);  
  138. }  
  139. //重新分配内存(外部调用)
  140. //memx:所属内存块
  141. //*ptr:旧内存首地址
  142. //size:要分配的内存大小(字节)
  143. //返回值:新分配到的内存首地址.
  144. void *myrealloc(u8 memx,void *ptr,u32 size)  
  145. {  
  146.     u32 offset;  
  147.     offset=mymem_malloc(memx,size);  
  148.     if(offset==0XFFFFFFFF)return NULL;     
  149.     else  
  150.     {                                                                            
  151.             mymemcpy((void*)((u32)mallco_dev.membase[memx]+offset),ptr,size);        //拷贝旧内存内容到新内存   
  152.         myfree(memx,ptr);                                                                                                            //释放旧内存
  153.         return (void*)((u32)mallco_dev.membase[memx]+offset);                                  //返回新内存首地址
  154.     }  
  155. }
复制代码
malloc.h
  1. #ifndef _MALLOC_H
  2. #define _MALLOC_H
  3. #include "stm32f4xx.h"
  4. #ifndef NULL
  5. #define NULL 0
  6. #endif
  7. //定义三个内存池
  8. #define SRAMIN         0  //内部内存池
  9. #define SRAMEX         1  //外部内存池
  10. #define SRAMBANK  2 //定义支持的SRAM块数
  11. //mem1内存参数设定,mem1完全处于内部SRAM里面
  12. #define MEM1_BLOCK_SIZE        32                          //内存块大小为32字节
  13. #define MEM1_MAX_SIZE                30*1024         //最大管理内存 10k
  14. #define MEM1_ALLOC_TABLE_SIZE MEM1_MAX_SIZE/MEM1_BLOCK_SIZE  //内存表大小
  15. //mem2内存参数设定,mem2处于外部SRAM里面
  16. #define MEM2_BLOCK_SIZE        32                          //内存块大小为32字节
  17. #define MEM2_MAX_SIZE                500*1024         //最大管理内存 500k
  18. #define MEM2_ALLOC_TABLE_SIZE MEM2_MAX_SIZE/MEM2_BLOCK_SIZE  //内存表大小
  19. //内存管理控制器
  20. struct _m_mallco_dev
  21. {
  22.         void (*init)(u8);                  //初始化
  23.         u8 (*perused)(u8);                 //内存使用率
  24.         u8 *membase[SRAMBANK]; //内存池,管理SRAMBANK个区域的内存
  25.         u16 *memmap[SRAMBANK];  //内存状态表
  26.         u8 memrdy[SRAMBANK];   //内存管理是否就绪
  27. };
  28. extern struct _m_mallco_dev mallco_dev;  //在malloc.c里面定义
  29. void mymemset(void *s,u8 c,u32 count);         //设置内存
  30. void mymemcpy(void *des,void *src,u32 n);//复制内存     
  31. void mymem_init(u8 memx);                                         //内存管理初始化函数(外/内部调用)
  32. u32 mymem_malloc(u8 memx,u32 size);                 //内存分配(内部调用)
  33. u8 mymem_free(u8 memx,u32 offset);                 //内存释放(内部调用)
  34. u8 mem_perused(u8 memx);                                 //获得内存使用率(外/内部调用)
  35. //用户调用函数
  36. void myfree(u8 memx,void *ptr);                          //内存释放(外部调用)
  37. void *mymalloc(u8 memx,u32 size);                        //内存分配(外部调用)
  38. void *myrealloc(u8 memx,void *ptr,u32 size);//重新分配内存(外部调用)
  39. #endif
复制代码
然后在main函数中使用

修改stm32f4x7_eth.h

#include “stm32f4x7_eth.h” 的最后添加 extern 使得外部文件可以使用

至此 ETH的DMA描述符,缓存,接收帧内存 都可以使用了
参加LWIP包

在工程源目次中参加LWIP文件夹, 并且把lwip包的文件全部复制到源码目次的LWIP文件夹里
添加lwip源码


在keil中创建相对应的Group并且在keil中参加这些路径


  • lwip/core
    需要单独参加ipv4的内容,不加ipv6的内容

    lwip/netif 参加这些中的ethernet.c文件,注意只加ethernet.c

  • lwip.api
    参加这些
    - lwip/arch
    这个文件夹是单独创建在User中的arch文件夹,这里存放着lwip与用户的接口
    在我的文件夹中的User/arch 文件夹中,直接复制过去

添加lwip头文件路径

在keil工程中参加头文件路径

添加lwip时钟更新

在这里我使用的是我10ms的定时器驱动的一个任务调度器,没软件定时器的可以直接放入10ms定时器中.

把上图的函数放入10ms任务中,其中lwip_localtime+=10表示的是10ms更新的时基。
添加以太网底层驱动

以太网初始化

初始化GPIO

初始化GPIO并且选择RMII接口的SYSCFG
  1. RCC->AHB1ENR |= RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOC|RCC_AHB1Periph_GPIOG;
  2.         RCC->APB2ENR |=RCC_APB2Periph_SYSCFG;//使能SYSCFG时钟
  3.        
  4.         SYSCFG->PMC=(uint32_t)(0x800000);//MAC和PHY之间使用RMII接口
  5.        
  6.         GPIOA->MODER|=(uint32_t)(0x8028);                 //PA1 PA2 PA7
  7.         GPIOB->MODER|=(uint32_t)(0x800000);                //PB11
  8.         GPIOC->MODER|=(uint32_t)(0xA08);                //PC1 PC4 PC5
  9.         GPIOG->MODER|=(uint32_t)(0x28000000);        //PG13 PG14
  10.         GPIOA->AFR[0]|=(uint32_t)(0xB0000BB0);//PA1 PA2 PA7
  11.         GPIOB->AFR[1]|=(uint32_t)(0xB000);                        //PB11
  12.         GPIOC->AFR[0]|=(uint32_t)(0xBB00B0);                //PC1 PC4 PC5
  13.         GPIOG->AFR[1]|=(uint32_t)(0xBB00000);                //PG13 PG14
  14.         GPIOA->OSPEEDR|=(uint32_t)(0xC03C);         //PA1 PA2 PA7
  15.         GPIOB->OSPEEDR|=(uint32_t)(0xC00000);         //PB11
  16.         GPIOC->OSPEEDR|=(uint32_t)(0xF0C);         //PC1 PC4 PC5
  17.         GPIOG->OSPEEDR|=(uint32_t)(0x3C000000); //PG13 PG14
复制代码
初始化以太网MAC_DMA

  1. //初始化ETH MAC层及DMA配置
  2. //返回值:ETH_ERROR,发送失败(0)
  3. //                ETH_SUCCESS,发送成功(1)
  4. u8 ETH_MAC_DMA_Config(void)
  5. {
  6.         u8 rval;
  7.         ETH_InitTypeDef ETH_InitStructure;
  8.         //使能以太网时钟
  9.         RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_ETH_MAC | RCC_AHB1Periph_ETH_MAC_Tx |RCC_AHB1Periph_ETH_MAC_Rx, ENABLE);
  10.         ETH_DeInit();                                                                  //AHB总线重启以太网
  11.         ETH_SoftwareReset();                                                  //软件重启网络
  12.         while (ETH_GetSoftwareResetStatus() == SET);//等待软件重启网络完成
  13.         ETH_StructInit(&ETH_InitStructure);                  //初始化网络为默认值  
  14.         ///网络MAC参数设置
  15.         ETH_InitStructure.ETH_AutoNegotiation = ETH_AutoNegotiation_Enable;                           //开启网络自适应功能
  16.         ETH_InitStructure.ETH_LoopbackMode = ETH_LoopbackMode_Disable;                                        //关闭反馈
  17.         ETH_InitStructure.ETH_RetryTransmission = ETH_RetryTransmission_Disable;                 //关闭重传功能
  18.         ETH_InitStructure.ETH_AutomaticPadCRCStrip = ETH_AutomaticPadCRCStrip_Disable;         //关闭自动去除PDA/CRC功能
  19.         ETH_InitStructure.ETH_ReceiveAll = ETH_ReceiveAll_Disable;                                                //关闭接收所有的帧
  20.         ETH_InitStructure.ETH_BroadcastFramesReception = ETH_BroadcastFramesReception_Enable;//允许接收所有广播帧
  21.         ETH_InitStructure.ETH_PromiscuousMode = ETH_PromiscuousMode_Disable;                        //关闭混合模式的地址过滤  
  22.         ETH_InitStructure.ETH_MulticastFramesFilter = ETH_MulticastFramesFilter_Perfect;//对于组播地址使用完美地址过滤   
  23.         ETH_InitStructure.ETH_UnicastFramesFilter = ETH_UnicastFramesFilter_Perfect;        //对单播地址使用完美地址过滤
  24.         ETH_InitStructure.ETH_ChecksumOffload = ETH_ChecksumOffload_Enable;                         //开启ipv4和TCP/UDP/ICMP的帧校验和卸载   
  25.         //当我们使用帧校验和卸载功能的时候,一定要使能存储转发模式,存储转发模式中要保证整个帧存储在FIFO中,
  26.         //这样MAC能插入/识别出帧校验值,当真校验正确的时候DMA就可以处理帧,否则就丢弃掉该帧
  27.         ETH_InitStructure.ETH_DropTCPIPChecksumErrorFrame = ETH_DropTCPIPChecksumErrorFrame_Enable; //开启丢弃TCP/IP错误帧
  28.         ETH_InitStructure.ETH_ReceiveStoreForward = ETH_ReceiveStoreForward_Enable;     //开启接收数据的存储转发模式   
  29.         ETH_InitStructure.ETH_TransmitStoreForward = ETH_TransmitStoreForward_Enable;   //开启发送数据的存储转发模式  
  30.         ETH_InitStructure.ETH_ForwardErrorFrames = ETH_ForwardErrorFrames_Disable;             //禁止转发错误帧  
  31.         ETH_InitStructure.ETH_ForwardUndersizedGoodFrames = ETH_ForwardUndersizedGoodFrames_Disable;        //不转发过小的好帧
  32.         ETH_InitStructure.ETH_SecondFrameOperate = ETH_SecondFrameOperate_Enable;                  //打开处理第二帧功能
  33.         ETH_InitStructure.ETH_AddressAlignedBeats = ETH_AddressAlignedBeats_Enable;          //开启DMA传输的地址对齐功能
  34.         ETH_InitStructure.ETH_FixedBurst = ETH_FixedBurst_Enable;                                    //开启固定突发功能   
  35.         ETH_InitStructure.ETH_RxDMABurstLength = ETH_RxDMABurstLength_32Beat;                     //DMA发送的最大突发长度为32个节拍   
  36.         ETH_InitStructure.ETH_TxDMABurstLength = ETH_TxDMABurstLength_32Beat;                        //DMA接收的最大突发长度为32个节拍
  37.         ETH_InitStructure.ETH_DMAArbitration = ETH_DMAArbitration_RoundRobin_RxTx_1_1;
  38.         rval=ETH_Init(&ETH_InitStructure,LAN8720_PHY_ADDRESS);                //配置ETH
  39.         if(rval==ETH_SUCCESS)//配置成功
  40.         {
  41.                 ETH_DMAITConfig(ETH_DMA_IT_NIS|ETH_DMA_IT_R,ENABLE);          //使能以太网接收中断       
  42.                 NVIC_InitTypeDef NVIC_InitStructure;
  43.                 NVIC_InitStructure.NVIC_IRQChannel = ETH_IRQn;  //以太网中断
  44.                 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //中断寄存器组2最高优先级
  45.                 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  46.                 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  47.                 NVIC_Init(&NVIC_InitStructure);
  48.                 ETH_MACAddressConfig(ETH_MAC_Address0,lwipdev.mac);
  49.                 printf("ETH Init Sucess\r\n");
  50.         }
  51.         return rval;
  52. }
复制代码

  • 这里 设置MAC地点 很重要,否则以太网无法接收本身的IP地点所对应的包!
   ETH_MACAddressConfig(ETH_MAC_Address0,lwipdev.mac);
  的是lwipdev.mac
这里的lwipdev.mac在lwip_comm.h中,在main函数中调用lwip_comm_init() 用来初始化lwip的底层设备和lwip内核,MAC地点在这个函数的lwip_comm_default_ip_set(&lwipdev); 中修改。
2. 这里一定要 开启ETH的DMA中断并且使能ETH_IRQn
设置以太网DMA描述符 & DMA缓存的对应关系

  1.         rval=ETH_MAC_DMA_Config();
  2.         if(rval==ETH_SUCCESS){
  3.                 printf("ETH init OK  ");
  4.         }
  5.         else{
  6.                 printf("ETH init Failed  ");
  7.         }
  8.         ETH_DMATxDescChainInit(DMATxDscrTab,Tx_Buff,ETH_TXBUFNB);//将接收描述符和接收缓存区关联起来 串成链式结构 初始化了发送追踪描述符
  9.         ETH_DMARxDescChainInit(DMARxDscrTab,Rx_Buff,ETH_RXBUFNB);//将发送描述符和发送缓存区关联起来 串成链表          初始化了接收追踪描述符
  10.          for(uint8_t i=0; i<ETH_TXBUFNB; i++)
  11.     {
  12.               ETH_DMATxDescChecksumInsertionConfig(&DMATxDscrTab[i], ETH_DMATxDesc_ChecksumTCPUDPICMPFull);
  13.                 ETH_DMATxDescCRCCmd(&DMATxDscrTab[i],ENABLE);
  14.     }
  15.         ETH_Start();
复制代码
后面
   for(uint8_t i=0; i<ETH_TXBUFNB; i++)
{
ETH_DMATxDescChecksumInsertionConfig(&DMATxDscrTab,ETH_DMATxDesc_ChecksumTCPUDPICMPFull);
ETH_DMATxDescCRCCmd(&DMATxDscrTab,ENABLE);
}
  设置了以太网TX发送描述缓存帧的和校验,这步是我在前面测试的时间发现的问题若没这段程序,以太网只可以发送ARP哀求,TCP/UDP/ICMP等都发送出去的帧都是0和校验,形成错误帧,以是一定要开启TX缓存的和校验!
另一个需要注意的是一定要开启ETH!
   ETH_Start();
  在这里我贴出的这段程序,可以获得PHY芯片和外部协商的结果,可以验证设置的以太网是是否和外部PHY芯片通讯上。
  1. //得到8720的速度模式
  2. //返回值:
  3. //001:10M半双工
  4. //101:10M全双工
  5. //010:100M半双工
  6. //110:100M全双工
  7. //其他:错误.
  8. void LAN8720_Get_Speed(void)
  9. {
  10.         u8 speed;
  11.         speed=((ETH_ReadPHYRegister(PHY_BCR,PHY_SR)&0x1C)>>2); //从LAN8720的31号寄存器中读取网络速度和双工模式
  12.         switch(speed){
  13.                 case 1: printf("10M半双工\r\n"); break;
  14.                 case 5: printf("10M全双工\r\n"); break;
  15.                 case 2: printf("100M半双工\r\n"); break;
  16.                 case 6: printf("100M全双工\r\n"); break;
  17.                 default: printf("ETH 初始化失败 %d\r\n",speed); break;
  18.         }
  19. }
复制代码
以太网接收中断函数

在前初始化了以太网中断,这里编写以太网中断函数
  1. //以太网中断服务函数
  2. void ETH_IRQHandler(void){
  3.         if(ETH_CheckFrameReceived()){
  4.                 ETH_flag=1;
  5.         }
  6.         ETH_DMAClearITPendingBit(ETH_DMA_IT_R);         //清除DMA中断标志位
  7.         ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS);        //清除DMA接收中断标志位
  8. }  
复制代码
LWIP初始化

在LWIP底层硬件初始化

在这个函数中参加ETH初始化
   static void low_level_init(struct netif *netif)
  

设置LWIP底层输入/ 输出函数

底层输入函数:
   static struct pbuf * low_level_input(struct netif *netif)
  底层输出函数:
   static err_t low_level_output(struct netif *netif, struct pbuf *p)
  修改这两个函数即可使用LWIP适配以太网(从这里看LWIP实在可以通过任何通讯方式运行,不只局限于ETH,只要是输入的是以太网帧格式的数据就可以,但是需要修改的部分较多,需要重新界说DMA描述符、追踪描述符、缓存的数据方向)。
LWIP输入处理

在while(1)中检测ETH中断函数的flg并且做出反应
  1. if(ETH_flag){
  2.                         ETH_flag = 0;
  3.                         ethernetif_input(&lwip_netif);//调用网卡接收函数
  4.                 }
复制代码
有两种ETH输入数据分解的方法, ETH中断是其中一种,但是实测发现了一个问题,当我把我的以太网线插入开发板<—>电脑 之间后,由于电脑一边从WIFI中获取数据一边会从以太网中尝试获取数据,于是会发生ETH超级频繁的进入ETH中断导致ETH这次的任务还没有处理完,下一次的任务标志位又被置为了导致出现了原子操作,也就是会出现数据量大的时间可能漏掉网络哀求的情况,如图:

于是发现可以不使用ETH中断,禁用掉ETH中断,改用频率更高的while(1)大循环中循环检测,如图

在这里每一次大循环都会检测ETH输入帧是否收到,大大提高了实时响应性,ping下令不会出现遗漏的现象。
测试程序

ETH是否正常接收可以查看debug,这里贴出两个测试程序用来测试ETH是否可以正常发送
  1. void ethernet_sendtest1(void)
  2.   {
  3.       uint8_t frame_data[] =
  4.       {
  5.           /* 以太网帧格式 */
  6.           0x50,0xFA,0x84,0x15,0x3C,0x3C,                            /* 远端MAC */
  7.           0x0,0x80,0xE1,0x0,0x0,0x0,                                /* 本地MAC */
  8.           0x8,0x0,                                                  /* ip类型 */
  9.           0x45,0x0,0x0,0x26/*l*/,0x0,0x0,0x0,0x0,0xFF,0x11,0x0,0x0, /* UDP报头 */
  10.           0xC0,0xA8,0x2,0x8,                                        /* 本地IP */
  11.           0xC0,0xA8,0x2,0xC2,                                       /* 远端IP */
  12.           0x22,0xB0,                                                /* 本地端口 */
  13.           0x22,0xB1,                                                /* 远端端口 */
  14.           0x0,0x12,                                                 /* UDP长度 */
  15.           0x0,0x0,                                                  /* UDP校验和 */
  16.           0x68,0x65,0x6C,0x6C,0x6F,0x20,0x7A,0x6F,0x72,0x62         /* 数据 */
  17.       };
  18.          
  19.       struct pbuf *p;
  20.       
  21.       /* 分配缓冲区空间 */
  22.       p = pbuf_alloc(PBUF_TRANSPORT, 0x26 + 14, PBUF_POOL);
  23.       
  24.       if (p != NULL)
  25.       {
  26.           /* 填充缓冲区数据 */
  27.          pbuf_take(p, frame_data, 0x26 + 14);
  28.   
  29.           /* 把数据直接通过底层发送 */
  30.           lwip_netif.linkoutput(&lwip_netif, p);
  31.   
  32.           /* 释放缓冲区空间 */
  33.           pbuf_free(p);
  34.       }
  35.   }
  36.   
  37.   
  38.   
  39. void ethernet_sendtest2(void)
  40.   {
  41.       uint8_t dstAddr[6] = {0x50,0xFA,0x84,0x15,0x3C,0x3C};         /* 远端MAC */
  42.       
  43.       uint8_t frame_data[] =
  44.       {
  45.           /* UDP帧格式 */
  46.           0x45,0x0,0x0,0x26/*l*/,0x0,0x0,0x0,0x0,0xFF,0x11,0x0,0x0, /* UDP报头 */
  47.           192,168,1,68,                                        /* 本地IP */
  48.           192,168,1,11,                                       /* 远端IP */
  49.           0x22,0xB0,                                                /* 本地端口 */
  50.           0x22,0xB1,                                                /* 远端端口 */
  51.           0x0,0x12,                                                 /* UDP长度 */
  52.           0x0,0x0,                                                  /* UDP校验和 */
  53.           1,2,3,4,5,6,6,6,6,6         /* 数据 */
  54.       };
  55.          
  56.       struct pbuf *p;
  57.       
  58.       /* 分配缓冲区空间 */
  59.       p = pbuf_alloc(PBUF_TRANSPORT, 0x26, PBUF_POOL);
  60.       
  61.       if (p != NULL)
  62.       {
  63.           /* 填充缓冲区数据 */
  64.           pbuf_take(p, frame_data, 0x26);
  65.          
  66.           /* 把数据进行以太网封装,再通过底层发送 */
  67.           ethernet_output(&lwip_netif, p, (const struct eth_addr*)lwip_netif.hwaddr,
  68.               (const struct eth_addr*)dstAddr, ETHTYPE_IP);
  69.   
  70.           /* 释放缓冲区空间 */
  71.           pbuf_free(p);
  72.      }
  73. }
  74.   
复制代码
至此,ETH已经具备了运行LWIP并且可以PING了。
这里我修改IP地点和MAC,防止电脑内部MAC / ARP表影响测试结果

ping了好多次发现都可以,实验乐成

总结一下,这里有很重要但是也很有可能会疏漏的几点:

  • 设置为MCU设置MAC地点,有专门的一个函数用来为MCU设置本身ETH的MAC地点,否则无法接收到本身IP地点包。
  • 配置DMA的描述符和缓存的关系,这也是有专门的函数用来初始化对应的关系,否则描述符无法和缓存对应起来,接收到的是乱码或者直接进入硬件错误中断
  • 如果是外部SRAM的板子,ETH的TX/RX的30K缓存可以放到外部SRAM中,而占用300字节的设备描述符不可以放入外部SRAM,由于这些描述符是直接与内部ETH的DMA交互的,将这些描述符的内存指向外部RAM会导致读取不到描述符出现直接无法读取的现象。
  • 开启每个TX缓存的和校验,有专门的函数,如果不设置TX缓存和校验会导致TX发送的全部数据的和校验都是0x00 在抓包软件中出现的是错误。
  • 如果使用片内RAM存储缓存,可以调治ETH_TXBUFNB 或者 ETH_RXBUFNB 调治有多少个缓存区从而调治缓存区大小
  • 经过我的实测,将RX/TX缓存BUFFER存放在外部SRAM中会有几率ping失败或者超时几个,存放在内部RAM会不停可以使用,怀疑是SRAM的速率影响了DMA搜索地点传输的速率,缓存存放在内部RAM服从高,失误率低,(是否有可能是cache的作用?)

处理以太网数据帧的三种方式对比

续:在写完这篇博客之后我再次重新理解了以太网响应数据的方式,在以太网使用中断检测可用的帧数据&大循环处理 / 直接在大循环中检测可用的帧&处理 / 以太网中断接收到置为标志位&大循环中处理这三种方法我都尝试了一下,
中断检测可使用的帧 & 大循环处理:

这里使用的是在中断中
   //以太网中断服务函数
void ETH_IRQHandler(void){
if(ETH_CheckFrameReceived()){
ETH_flag=1;
}
ETH_DMAClearITPendingBit(ETH_DMA_IT_R); //清除DMA中断标志位
ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS); //清除DMA接收中断标志位
}
  大循环中
   if(ETH_flag==1){
ETH_flag=0;
if(ETH_CheckFrameReceived()){
ethernetif_input(&lwip_netif);//调用网卡接收函数
}
}
  如许实际测试发现由于在ETH_CheckFrameReceived( ) 这个函数中有如许一段
   /* check if last segment */
if(((DMARxDescToGet->Status & ETH_DMARxDesc_OWN) == (uint32_t)RESET) &&
((DMARxDescToGet->Status & ETH_DMARxDesc_LS) != (uint32_t)RESET))
{
DMA_RX_FRAME_infos->Seg_Count++;
if (DMA_RX_FRAME_infos->Seg_Count == 1)
{
DMA_RX_FRAME_infos->FS_Rx_Desc = DMARxDescToGet;
}
DMA_RX_FRAME_infos->LS_Rx_Desc = DMARxDescToGet;
return 1;
}
  这里可以看到它会不断更新是否是最新的缓存区域,如果不是,则++缓存到另一个,但是如许当中断正在检测frame的时间又发生了中断,会直接导致frame检测混乱,count++了之后又++,正在处理上一个变乱的时间又被后半部分指到了下一个变乱的数据,以是 ETH_CheckFrameReceived( ) 这个函数不可重入!!,实际测发现如许有概率乐成ping通几个包,大部分由于网络频繁进中断导致了无法ping通,那么在中断检测帧在大循环处理这个方法PASS
直接在大循环里检测 & 处理

例如:
   if(ETH_CheckFrameReceived()){
ethernetif_input(&lwip_netif);//调用网卡接收函数
}
  直接在while(1)中不断检测,如许的好处是可以不停检测,有任何帧被发现都会处理,缺点是如许会占用大量MCU资源,当任务多了之后会发现检测不是很及时,并且会拖累其他任务的响应,直接导致体系响应慢。
中断置位标志位累加 & 大循环处理

这里我使用的是在中断中给需要处理的变乱++
   //以太网中断服务函数
void ETH_IRQHandler(void){
if(ETH_CheckFrameReceived()){
ETH_flag++;
}
ETH_DMAClearITPendingBit(ETH_DMA_IT_R); //清除DMA中断标志位
ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS); //清除DMA接收中断标志位
}
  如许相当于记录了有多少个变乱应该被处理
在大循环中处理
   if(ETH_flag){
ETH_flag–;
ethernetif_input(&lwip_netif);//调用网卡接收函数
printf(“ETH_flag=%d\r\n”,ETH_flag);
}
  如许既不会发生frame检测重入,又可以纵然处理全部缓存中的变乱!!
实测效果如下:
在debug界面,最多发生一次剩余缓存未处理,并且可以看到后面已经纵然处理了

在ping响应中,来回小于1ms

之前方法2全部在while中处理的时间的时间是2-3ms:

以是方法3无论是响应速率或者是处理数据的数量来说都是比较公道的,如果又更好的方法接待私信我!

源码获取

文件链接:
通过网盘分享的文件:CSDN_ETH.rar
链接: https://pan.baidu.com/s/15UMS1rLIsaaPfxGsWYXK9Q?pwd=kg2m 提取码: kg2m

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

笑看天下无敌手

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表