文章目录
一,使用以太网的库
为了再stm32中使用以太网进行通信,需要使用两个库的代码。
如图是stm32和互联网通信的模型,其中的lwip协议栈和驱动就是我们要移植的代码。
https://wws.lanzous.com/b01i3kcd,密码cmvn。
其中ST提供的以太网库负责处理配置stm32以太网功能,lan8720驱动phy芯片。而LWIP则负责在软件上实现网络层,传输层等上层协议。
二,ST以太网驱动库的移植
首先我使用的是正点原子的stm32f4探索者,开发板使用的phy芯片为LAN8720。下载好以太网驱动库和LAN8720的驱动文件。然后添加到工程中。
以太网驱动库
lan8720驱动
文件说明:
1,stm32f4x7_eth.c
stm32f4x7_eth.c中,主要使用的函数有:
void ETH_DeInit(void);
uint32_t ETH_Init(ETH_InitTypeDef* ETH_InitStruct, uint16_t PHYAddress); //初始化以太网
void ETH_StructInit(ETH_InitTypeDef* ETH_InitStruct); //结构体参数初始化
void ETH_SoftwareReset(void); //重置以太网mac的寄存器
void ETH_Start(void); //开启以太网功能
void ETH_Stop(void);
uint32_t ETH_GetRxPktSize(ETH_DMADESCTypeDef *DMARxDesc); //读取以太网接收到的数据包大小
另外还需稍微了解DMA描述符。
以太网外设接收到数据后会将数据放到DMA描述符的缓存中,描述符是链表的结构。发送数据时,软件将数据放入描述符的缓存区。发送和接收共两条描述符链。其定义如下,但被注释了。
2,lan8720.c
主要函数
u8 LAN8720_Init(void); //phy芯片初始化
FrameTypeDef ETH_Rx_Packet(void); //从以太网接收一个数据包
u8 ETH_Tx_Packet(u16 FrameLength); //发送一个数据包
u32 ETH_GetCurrentTxBuffer(void); //获取当前发送描述符的buff地址
u8 ETH_Mem_Malloc(void); //为以太网描述符分配内存
//以太网dma接收中断服务函数
void ETH_IRQHandler(void)
{
while(ETH_GetRxPktSize(DMARxDescToGet)!=0)
{
lwip_pkt_handle(); //通知lwip处理接收的数据
}
ETH_DMAClearITPendingBit(ETH_DMA_IT_R);
ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS);
}
注意:文件中重新声明了以上四个变量,并在函数ETH_Mem_Malloc()中为四个变量申请了内存。原因是这四个变量会占用芯片较多的内存,使用动态内存分配,为变量分配了片外RAM的内存,能提供运行速度。
u8 ETH_Mem_Malloc(void)
{
DMARxDscrTab=mymalloc(SRAMIN,ETH_RXBUFNB*sizeof(ETH_DMADESCTypeDef));
DMATxDscrTab=mymalloc(SRAMIN,ETH_TXBUFNB*sizeof(ETH_DMADESCTypeDef));
Rx_Buff=mymalloc(SRAMIN,ETH_RX_BUF_SIZE*ETH_RXBUFNB);
Tx_Buff=mymalloc(SRAMIN,ETH_TX_BUF_SIZE*ETH_TXBUFNB);
if(!DMARxDscrTab||!DMATxDscrTab||!Rx_Buff||!Tx_Buff)
{
ETH_Mem_Free();
return 1;
}
return 0;
}
三,移植LWIP协议栈
下载并添加lwip源码到工程中。
文件说明
1,lwip_comm.c
主要函数:
//初始化lwip
u8 lwip_comm_init(void)
{
struct netif *Netif_Init_Flag;
struct ip_addr ipaddr;
struct ip_addr netmask;
struct ip_addr gw;
if(ETH_Mem_Malloc())return 1; //为以太网dma描述符分配内存
if(lwip_comm_mem_malloc())return 1; //为lwip分配内存
if(LAN8720_Init())return 2; //phy芯片初始化
lwip_init(); //lwip协议栈初始化
lwip_comm_default_ip_set(&lwipdev); //暂时设置默认ip
//以下代码是根据情况获取ip地址
#if LWIP_DHCP
ipaddr.addr = 0;
netmask.addr = 0;
gw.addr = 0;
#else
IP4_ADDR(&ipaddr,lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);
IP4_ADDR(&netmask,lwipdev.netmask[0],lwipdev.netmask[1] ,lwipdev.netmask[2],lwipdev.netmask[3]);
IP4_ADDR(&gw,lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]);
#endif
//到此,获取到ip地址,创建一个netif接口,并为接口添加ip地址等,添加接口初始化函数和输入函数
Netif_Init_Flag=netif_add(&lwip_netif,&ipaddr,&netmask,&gw,NULL,ðernetif_init,ðernet_input);//
#if LWIP_DHCP
lwipdev.dhcpstatus=0;
dhcp_start(&lwip_netif);
#endif
if(Netif_Init_Flag==NULL)
return 3;
{
//完成netif设置
netif_set_default(&lwip_netif);
netif_set_up(&lwip_netif);
}
return 0;
}
该函数主要是调用其他初始化函数,完成硬件和软件的初始化配置。重点是netif_add创建了网络接口lwip_netif,并为该网络接口设置ip和以太网输入函数ethernet_input()。
在netif_add()中,调用ethernetif_init()函数对lwip_netif进行初始化
err_t ethernetif_init(struct netif *netif)
{
LWIP_ASSERT("netif!=NULL",(netif!=NULL));
#if LWIP_NETIF_HOSTNAME //LWIP_NETIF_HOSTNAME
netif->hostname="lwip"; //初始化名称
#endif
netif->name[0]=IFNAME0; //初始化变量netif的name字段
netif->name[1]=IFNAME1; //在文件外定义这里不用关心具体值
netif->output=etharp_output;//IP层发送数据包函数,由lwip提供,功能如下
netif->linkoutput=low_level_output;//ARP模块发送数据包函数
low_level_init(netif); //底层硬件初始化函数
return ERR_OK;
}
该函数为lwip_netif 设置了以太网输出函数etharp_output(),和ARP发送函数low_level_output(),这些函数是一个网络接口功能实现的逻辑。如下图:
在以太网dma中断中还会调用下面的函数,其实就是调用ethernetif_input(),这个函数待会分析
void lwip_pkt_handle(void)
{
ethernetif_input(&lwip_netif);
}
void lwip_periodic_handle(); //lwip轮询
void lwip_dhcp_process_handle(void) //dhcp处理任务
2,ethernetif.c
上文提到的lwip_netif功能实现的四个重要函数,在此分析
//从dma描述符缓存中读取数据到pbuf并返回pbuf
static struct pbuf * low_level_input(struct netif *netif)
{
struct pbuf *p, *q;
u16_t len;
int l =0;
FrameTypeDef frame;
u8 *buffer;
p = NULL;
frame=ETH_Rx_Packet();//调用以太网接收函数,获取一帧数据包
len=frame.length;//得到包大小
buffer=(u8 *)frame.buffer;//得到包数据地址
p=pbuf_alloc(PBUF_RAW,len,PBUF_POOL);//内存池分配新的pbuf
if(p!=NULL)
{
//将数据包复制到q
for(q=p;q!=NULL;q=q->next)
{
memcpy((u8_t*)q->payload,(u8_t*)&buffer[l], q->len);
l=l+q->len;
}
}
frame.descriptor->Status=ETH_DMARxDesc_OWN;//设置Rx描述符OWN位,buffer重归ETH DMA
if((ETH->DMASRÐ_DMASR_RBUS)!=(u32)RESET)//当Rx Buffer不可用位(RBUS)被设置的时候,重置它.恢复传输
{
ETH->DMASR=ETH_DMASR_RBUS;//重置ETH DMA RBUS位
ETH->DMARPDR=0;//恢复DMA接收
}
return p;
}
err_t ethernetif_input(struct netif *netif)
{
err_t err;
struct pbuf *p;
p=low_level_input(netif); //读取输入的一帧pbuf数据
if(p==NULL) return ERR_MEM;
err=netif->input(p, netif); //调用ethernet_input()将pbuf交给lwip内核
if(err!=ERR_OK)
{
LWIP_DEBUGF(NETIF_DEBUG,("ethernetif_input: IP input error\n"));
pbuf_free(p);
p = NULL;
}
return err;
}
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
u8 res;
struct pbuf *q;
int l = 0;
u8 *buffer=(u8 *)ETH_GetCurrentTxBuffer(); //获取当前要发送的DMA描述符中的缓冲区地址
//将pbuf中的数据复制到dma描述符
for(q=p;q!=NULL;q=q->next)
{
memcpy((u8_t*)&buffer[l], q->payload, q->len);
l=l+q->len;
}
res=ETH_Tx_Packet(l); //调用以太网外设的发送函数发送数据
if(res==ETH_ERROR)return ERR_MEM;//返回错误状态
return ERR_OK;
}
四,逻辑梳理
1,数据输出
当lwip输出数据时,先由上层调用netif->output(),将pbuf传递给netif,netif调用low_level_output()将数据发送到dma描述符,再由硬件发送出去。
2,数据输入
当有数据经过硬件进入stm32的以太网控制器时,会将数据放入dma描述符,并进入dma接收中断,调用low_level_input()将数据复制到pbuf中,netif再调用netif->input()将pbuf传递给上层协议。
总之还是这图