移植前必知必会:
1.移植分成以下两类:(本文是第一类)
第一类移植:只移植内核核心,此时用户应用程序编写只能基于Raw/Callback API进行;
==》完成几个头文件的定义,同时根据网卡的具体情况完成ethernetif.c中的函数(网卡驱动)的编写。
第二种移植:既移植内核核心,也移植上次API函数模块,此种方式三种API接口都可以使用;
==》除了实现第一种移植中的所有文件和函数外,还必须使用操作系统提供的邮箱和信号量机制,完成操作系统模拟层文件ssys_arch.c和sys_arch.h的编写。该种移植必须在操作系统的基础上进行。
2.网卡驱动的作用:
(1)控制网卡接收数据并将数据封装为lwip熟悉的格式pbuf,然后递交给内核。
(2)将上层发送的数据包解析为网卡熟悉的结构并控制网卡发送数据。
3.网卡功能函数:
(1)网卡初始化函数;
(2)网卡数据发送函数;
(3)网卡数据接收函数;
移植的步骤:
一、新建三个头文件cc.h、lwipopts.h、perf.h
1.新建cc.h文件添加如下代码:
// cc.h
该段代码对于不是OS的移植是通用的,直接复制使用
#ifndef __CC_H__
#define __CC_H__
#include "lwip_cpu.h"
#include "stdio.h"
#include "includes.h" //OS需要
//定义与平台无关的数据类型
typedef unsigned char u8_t;
typedef signed char s8_t;
typedef unsigned short u16_t;
typedef signed short s16_t;
typedef unsigned long u32_t;
typedef signed long s32_t;
typedef u32_t mem_ptr_t;
typedef int sys_prot_t;
//OS需要的临界区保护,该处未定义这个宏
#if CPU_CFG_CRITICAL_METHOD == 1
#define SYS_ARCH_DECL_PROTECT(lev)
#define SYS_ARCH_PROTECT(lev) CPU_INT_DIS()
#define SYS_ARCH_UNPROTECT(lev) CPU_INT_EN()
#endif
#if CPU_CFG_CRITICAL_METHOD == 3
#define SYS_ARCH_DECL_PROTECT(lev) u32_t lev
#define SYS_ARCH_PROTECT(lev) lev = CPU_SR_Save() //UCOS IIIÖнøÈëÁÙ½çÇø,¹ØÖжÏ
#define SYS_ARCH_UNPROTECT(lev) CPU_SR_Restore(lev) //UCOS IIIÖÐÍ˳öAÁÙ½çÇø£¬¿ªÖжÏ
#endif
//根据不同编译器定义一些符号
#if defined (__ICCARM__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#define PACK_STRUCT_USE_INCLUDES
#elif defined (__CC_ARM)
#define PACK_STRUCT_BEGIN __packed
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#elif defined (__GNUC__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT __attribute__ ((__packed__))
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#elif defined (__TASKING__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#endif
//使用printf调试时候使用到的
#define U16_F "4d"
#define S16_F "4d"
#define X16_F "4x"
#define U32_F "8ld"
#define S32_F "8ld"
#define X32_F "8lx"
//一些需要的宏定义
#ifndef LWIP_PLATFORM_ASSERT
#define LWIP_PLATFORM_ASSERT(x) //do { if(!(x)) while(1); } while(0)
//#define LWIP_PLATFORM_ASSERT(x) \
// do \
// { printf("Assertion \"%s\" failed at line %d in %s\r\n", x, __LINE__, __FILE__); \
// } while(0)
#endif
#ifndef LWIP_PLATFORM_DIAG
#define LWIP_PLATFORM_DIAG(x) do {printf x;} while(0)
#endif
#endif /* __CC_H__ */
2.新建perf.h并添加代码
#ifndef __PERF_H__
#define __PERF_H__
#define PERF_START /* null definition */
#define PERF_STOP(x) /* null definition */
3.新建lwipopts.h,并添加代码,该文件的很多选项在opt.h中都有的,当该文件出现时候opt.h文件中的相同选项都不使用了。
#ifndef __LWIPOPTS_H__
#define __LWIPOPTS_H__
//线程优先级
#ifndef TCPIP_THREAD_PRIO
#define TCPIP_THREAD_PRIO 5
#endif
#undef DEFAULT_THREAD_PRIO
#define DEFAULT_THREAD_PRIO 2
#define SYS_LIGHTWEIGHT_PROT 1
#define NO_SYS 0
#define MEM_ALIGNMENT 4
#define MEM_SIZE 16000
#define MEMP_NUM_PBUF 20
#define MEMP_NUM_UDP_PCB 6
#define MEMP_NUM_TCP_PCB 10
#define MEMP_NUM_TCP_PCB_LISTEN 6
#define MEMP_NUM_TCP_SEG 15
#define MEMP_NUM_SYS_TIMEOUT 8
//pbuf选项
#define PBUF_POOL_SIZE 20
#define PBUF_POOL_BUFSIZE 512
#define LWIP_SUPPORT_CUSTOM_PBUF 1
#define LWIP_TCP 1 //ʹÓÃTCP
#define TCP_TTL 255 //Éú´æʱ¼ä
#undef TCP_QUEUE_OOSEQ
#define TCP_QUEUE_OOSEQ 0 //µ±TCPµÄÊý¾Ý¶Î³¬³ö¶ÓÁÐʱµÄ¿ØÖÆλ,µ±É豸µÄÄÚ´æ¹ýСµÄʱºò´ËÏîӦΪ0
#undef TCPIP_MBOX_SIZE
#define TCPIP_MBOX_SIZE MAX_QUEUE_ENTRIES //tcpip´´½¨Ö÷Ïß³ÌʱµÄÏûÏ¢ÓÊÏä´óС
#undef DEFAULT_TCP_RECVMBOX_SIZE
#define DEFAULT_TCP_RECVMBOX_SIZE MAX_QUEUE_ENTRIES
#undef DEFAULT_ACCEPTMBOX_SIZE
#define DEFAULT_ACCEPTMBOX_SIZE MAX_QUEUE_ENTRIES
#define TCP_MSS (1500 - 40) //×î´óTCP·Ö¶Î,TCP_MSS = (MTU - IP±¨Í·´óС - TCP±¨Í·´óС
#define TCP_SND_BUF (4*TCP_MSS) //TCP·¢ËÍ»º³åÇø´óС(bytes).
#define TCP_SND_QUEUELEN (2* TCP_SND_BUF/TCP_MSS) //TCP_SND_QUEUELEN: TCP·¢ËÍ»º³åÇø´óС(pbuf).Õâ¸öÖµ×îСΪ(2 * TCP_SND_BUF/TCP_MSS)
#define TCP_WND (4*TCP_MSS) //TCP·¢ËÍ´°¿Ú
#define LWIP_ICMP 1 //ʹÓÃICMPÐÒé
#define LWIP_DHCP 0 //ʹÓÃDHCP
#define LWIP_UDP 1 //ʹÓÃUDP·þÎñ
#define UDP_TTL 255 //UDPÊý¾Ý°üÉú´æʱ¼ä
#define LWIP_STATS 0
#define LWIP_PROVIDE_ERRNO 1
//各种硬件帧校验和
#define CHECKSUM_BY_HARDWARE
#ifdef CHECKSUM_BY_HARDWARE
//CHECKSUM_GEN_IP==0: Ó²¼þÉú³ÉIPÊý¾Ý°üµÄ֡УÑéºÍ
#define CHECKSUM_GEN_IP 0
//CHECKSUM_GEN_UDP==0: Ó²¼þÉú³ÉUDPÊý¾Ý°üµÄ֡УÑéºÍ
#define CHECKSUM_GEN_UDP 0
//CHECKSUM_GEN_TCP==0: Ó²¼þÉú³ÉTCPÊý¾Ý°üµÄ֡УÑéºÍ
#define CHECKSUM_GEN_TCP 0
//CHECKSUM_CHECK_IP==0: Ó²¼þ¼ì²éÊäÈëµÄIPÊý¾Ý°ü֡УÑéºÍ
#define CHECKSUM_CHECK_IP 0
//CHECKSUM_CHECK_UDP==0: Ó²¼þ¼ì²éÊäÈëµÄUDPÊý¾Ý°ü֡УÑéºÍ
#define CHECKSUM_CHECK_UDP 0
//CHECKSUM_CHECK_TCP==0: Ó²¼þ¼ì²éÊäÈëµÄTCPÊý¾Ý°ü֡УÑéºÍ
#define CHECKSUM_CHECK_TCP 0
//CHECKSUM_CHECK_ICMP==1:Ó²¼þ¼ì²éÊäÈëµÄICMPÊý¾Ý°ü֡УÑéºÍ
#define CHECKSUM_GEN_ICMP 0
#else
//CHECKSUM_GEN_IP==1: Èí¼þÉú³ÉIPÊý¾Ý°ü֡УÑéºÍ
#define CHECKSUM_GEN_IP 1
// CHECKSUM_GEN_UDP==1: Èí¼þÉú³ÉUDOPÊý¾Ý°ü֡УÑéºÍ
#define CHECKSUM_GEN_UDP 1
//CHECKSUM_GEN_TCP==1: Èí¼þÉú³ÉTCPÊý¾Ý°ü֡УÑéºÍ
#define CHECKSUM_GEN_TCP 1
// CHECKSUM_CHECK_IP==1: Èí¼þ¼ì²éÊäÈëµÄIPÊý¾Ý°ü֡УÑéºÍ
#define CHECKSUM_CHECK_IP 1
// CHECKSUM_CHECK_UDP==1: Èí¼þ¼ì²éÊäÈëµÄUDPÊý¾Ý°ü֡УÑéºÍ
#define CHECKSUM_CHECK_UDP 1
//CHECKSUM_CHECK_TCP==1: Èí¼þ¼ì²éÊäÈëµÄTCPÊý¾Ý°ü֡УÑéºÍ
#define CHECKSUM_CHECK_TCP 1
//CHECKSUM_CHECK_ICMP==1:Èí¼þ¼ì²éÊäÈëµÄICMPÊý¾Ý°ü֡УÑéºÍ
#define CHECKSUM_GEN_ICMP 1
#endif
//使能API接口功能
#define LWIP_NETCONN 1
#define LWIP_SOCKET 1
#define LWIP_COMPAT_MUTEX 1
#define LWIP_SO_RCVTIMEO 1 //防止线程阻塞
//系统有关的选项 ,在opt,h中默认为0的
#define TCPIP_THREAD_STACKSIZE 1000 //内核任务堆栈大小
#define DEFAULT_UDP_RECVMBOX_SIZE 2000
#define DEFAULT_THREAD_STACKSIZE 512
//LWIP调试相关选项
#define LWIP_DEBUG 0 //¹Ø±ÕDEBUGÑ¡Ïî
#define ICMP_DEBUG LWIP_DBG_OFF //¿ªÆô/¹Ø±ÕICMPdebug
#endif /* __LWIPOPTS_H__ */
二、网卡驱动的编程框架(
五个函数组成的架构)
1.网卡驱动的实现准备材料和需预先知道的知识点
1.1 SPI接口的网卡(非硬件TCP/IP协议网卡,只是内部包含MAC和PHY的)
需要事先由厂商提供三个关于网卡的函数:
(1)XXX_init(char *macaddr); //网卡初始化函数完成网卡内部设置:发送缓冲区,接收缓冲区,中断允许,设置MAC地址,并完成其他的初始化工作。
(2)XXX_PacketSend(int len,char *packet); //网卡发送函数将packet写入网卡RAM并启动发送
(3)XXX_PacketReceive(int len,char *packet);//网卡接收函数:接收数据
1.2 RMII接口的网卡(单片机内部含有MAC,网卡为PHY的,一般为STM32 的库,此处为HAL库),
(1)HAL_ETH_IsRxDataAvailable(&LAN8720_ETHHandle); //接收
(2)HAL_ETH_Transmit(&LAN8720_ETHHandle,&TxConfig,0); //发送
(3)HAL_ETH_SetMACConfig(&LAN8720_ETHHandle,&MACConf); // 初始化
1.3 网卡框架形式
1.3.1 主要函数列表
ethernetif.c(改动基本在这个文件中,还有sys_now())
——void low_level_init(struct netif *netif);
——err_t low_level_output(struct netif *netif, struct pbuf *p);
——struct pbuf * low_level_input(struct netif *netif);
——err_t ethernetif_input(struct netif *netif);
——err_t ethernetif_init(struct netif *netif);
1.3.2 函数之间的关系(以RMII的hal库来说的)
1.3.2.1
接收的框架
lwip_pkt_handle();
含
ethernetif_input(&lwip_netif);
含
low_level_input(netif);
含
HAL_ETH_IsRxDataAvailable(&LAN8720_ETHHandle)。
lwip_pkt_handle()一般为ETH接收中断或者轮询中放置
1.3.2.2
初始化的的框架
lwip_comm_init
含
Netif_Init_Flag=netif_add(&lwip_netif,&ipaddr,&netmask,&gw,NULL,ðernetif_init,&tcpip_input);//向网卡列表中添加一网卡
含
ethernetif_init
含
low_level_init(netif);
含
HAL_ETH_SetMACConfig(&LAN8720_ETHHandle,&MACConf); //设置MAC。
lwip_comm_init为初始化时候调用
1.3.2.3 输出的框架
static void etharp_arp_input(struct netif *netif, struct eth_addr *ethaddr, struct pbuf *p)//用在回复ARP
static err_t etharp_send_ip(struct netif *netif, struct pbuf *p, struct eth_addr *src, struct eth_addr *dst) 、
err_t etharp_raw(struct netif *netif, const struct eth_addr *ethsrc_addr,
const struct eth_addr *ethdst_addr,
const struct eth_addr *hwsrc_addr, const ip_addr_t *ipsrc_addr,
const struct eth_addr *hwdst_addr, const ip_addr_t *ipdst_addr,
const u16_t opcode)、
含
low_level_output即netif->linkoutput(netif, p);
含
HAL_ETH_Transmit(&LAN8720_ETHHandle,&TxConfig,0);
该底层发送函数一般都是被内部回调使用。
1.4 网卡的函数详细解读
1.4.1.static void low_level_init(struct netif *netif) 函数:
设置netif以及网卡属性相关字段
ETH_MACConfigTypeDef MACConf; //有些变量没有列出
//==========设置netif网络管理接口四大项:MAC长度,MAC值,网络包大小,属性字段====
//======MAC的位数与6位MAC值======
netif->hwaddr_len=ETHARP_HWADDR_LEN;
netif->hwaddr[0]=lwipdev.mac[0];
netif->hwaddr[1]=lwipdev.mac[1];
netif->hwaddr[2]=lwipdev.mac[2];
netif->hwaddr[3]=lwipdev.mac[3];
netif->hwaddr[4]=lwipdev.mac[4];
netif->hwaddr[5]=lwipdev.mac[5];
netif->mtu=ETH_MAX_PAYLOAD;//最大传输包大小
netif->flags|=NETIF_FLAG_BROADCAST|NETIF_FLAG_ETHARP|NETIF_FLAG_LINK_UP;//设置网络接口的属性字段
广播 ARP 网络接口电路使能
//====接收buf初始化============
for(idx=0;idx<ETH_RX_DESC_CNT;idx ++)
{
HAL_ETH_DescAssignMemory(&LAN8720_ETHHandle,idx,Rx_Buff[idx],NULL);
rx_pbuf[idx].custom_free_function=pbuf_free_custom;
}
//======发送buf初始化============
memset(&TxConfig,0,sizeof(ETH_TxPacketConfig));
TxConfig.Attributes=ETH_TX_PACKETS_FEATURES_CSUM|ETH_TX_PACKETS_FEATURES_CRCPAD;
TxConfig.ChecksumCtrl=ETH_CHECKSUM_IPHDR_PAYLOAD_INSERT_PHDR_CALC;
TxConfig.CRCPadCtrl=ETH_CRC_PAD_INSERT;
//===========网卡参数配置===============
PHYLinkState=LAN8720_GetLinkState();
switch (PHYLinkState)
{
case LAN8720_STATUS_100MBITS_FULLDUPLEX:
duplex=ETH_FULLDUPLEX_MODE;
speed=ETH_SPEED_100M;
break;
case LAN8720_STATUS_100MBITS_HALFDUPLEX:
duplex=ETH_HALFDUPLEX_MODE;
speed=ETH_SPEED_100M;
break;
case LAN8720_STATUS_10MBITS_FULLDUPLEX:
duplex=ETH_FULLDUPLEX_MODE;
speed=ETH_SPEED_10M;
break;
case LAN8720_STATUS_10MBITS_HALFDUPLEX:
duplex=ETH_HALFDUPLEX_MODE;
speed=ETH_SPEED_10M;
break;
default:
break;
}
HAL_ETH_GetMACConfig(&LAN8720_ETHHandle,&MACConf);
MACConf.DuplexMode=duplex;
MACConf.Speed=speed;
HAL_ETH_SetMACConfig(&LAN8720_ETHHandle,&MACConf); //配置网卡信息参数:
HAL_ETH_Start_IT(&LAN8720_ETHHandle);
netif_set_up(netif);
netif_set_link_up(netif);
1.4.2 static err_t low_level_output(struct netif *netif, struct pbuf *p):
网卡数据包发送函数将pbuf通过netif网卡发送出去
//pbuf为应用程序传下来的
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
uint32_t i=0, framelen = 0;
struct pbuf *q; //链表的一个节点,用于轮询pbuf链表,处理
err_t errval=ERR_OK;
ETH_BufferTypeDef Txbuffer[ETH_TX_DESC_CNT]; //宏为4,该数组组装成一个链表了
for(q=p;q!=NULL;q=q->next) //轮询链表
{
if(i>=ETH_TX_DESC_CNT)
return ERR_IF;
Txbuffer[i].buffer=q->payload; //数据
Txbuffer[i].len=q->len; //长度
framelen+=q->len; //帧长
if(i>0)
{
Txbuffer[i-1].next=&Txbuffer[i];
}
if(q->next == NULL)
{
Txbuffer[i].next=NULL;
}
i++;
}
TxConfig.Length=framelen;
TxConfig.TxBuffer=Txbuffer;
SCB_CleanInvalidateDCache(); //
HAL_ETH_Transmit(&LAN8720_ETHHandle,&TxConfig,0);
return errval;
}
其中pbuf为:
struct pbuf {
struct pbuf *next; //指向下一包
void *payload; //有效数据
u16_t tot_len; //总长度
u16_t len; //当前长度
u8_t type;
u8_t flags;
u16_t ref;
};
其中Txbuffer
typedef struct __ETH_BufferTypeDef
{
uint8_t *buffer; //数据
uint32_t len; //数据长度
struct __ETH_BufferTypeDef *next; //指向下一个
}ETH_BufferTypeDef;
其中TxConfig为发送的数据包,有些东西会在init中初始化
typedef struct
{
uint32_t Attributes;
uint32_t Length;
ETH_BufferTypeDef *TxBuffer;
uint32_t SrcAddrCtrl;
uint32_t CRCPadCtrl;
uint32_t ChecksumCtrl;
uint32_t MaxSegmentSize;
uint32_t PayloadLen;
uint32_t TCPHeaderLen;
uint32_t VlanTag;
uint32_t VlanCtrl;
uint32_t InnerVlanTag;
uint32_t InnerVlanCtrl;
}ETH_TxPacketConfig;
1.4.3 static struct pbuf * low_level_input(struct netif *netif):
接收数据包转化成pbuf
static struct pbuf * low_level_input(struct netif *netif)
{
struct pbuf *p = NULL;
ETH_BufferTypeDef RxBuff;
uint32_t framelength = 0;
if(HAL_ETH_IsRxDataAvailable(&LAN8720_ETHHandle))//获取数据包
{
SCB_CleanInvalidateDCache();
HAL_ETH_GetRxDataBuffer(&LAN8720_ETHHandle,&RxBuff);//放入接收buff
HAL_ETH_GetRxDataLength(&LAN8720_ETHHandle,&framelength);//得到接收到的数据长度
//分配内存并且转存到rx_pbuf中,首节点地址为p
p=pbuf_alloced_custom(PBUF_RAW,framelength,PBUF_POOL,
&rx_pbuf[current_pbuf_idx],RxBuff.buffer, framelength);
if(current_pbuf_idx<(ETH_RX_DESC_CNT-1))
{
current_pbuf_idx++;
}
else
{
current_pbuf_idx=0;
}
return p;
}
else
{
return NULL;
}
}
1.4.4 err_t ethernetif_input(struct netif *netif):
调用low_level_input()接收数据包,然后解析该包的类型,区分ARP/IP递交给相应的上层。调用一次完成一个数据包的接收与递交
err_t ethernetif_input(struct netif *netif)
{
err_t err;
struct pbuf *p;
p=low_level_input(netif);
if(p==NULL) return ERR_MEM;
err=netif->input(p, netif); //调用网络接口交给内核
if (err!=ERR_OK)
{
LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
pbuf_free(p);
p=NULL;
}
HAL_ETH_BuildRxDescriptors(&LAN8720_ETHHandle);
return err;
}
1.4.5.err_t ethernetif_init(struct netif *netif);
err_t ethernetif_init(struct netif *netif)
{
LWIP_ASSERT("netif != NULL", (netif != NULL));
#if LWIP_NETIF_HOSTNAME
netif->hostname="lwip"; //Ö÷»úÃû×Ö
#endif
netif->name[0]=IFNAME0;
netif->name[1]=IFNAME1;
netif->output=etharp_output;
netif->linkoutput=low_level_output;
low_level_init(netif);
return ERR_OK;
}
三、最后需要完善的小点
1.sys_now函数:
系统中引入sys_check_timeouts来处理内核的各种定时事件。该函数要求移植者两次sys_now返回时间差来判断事件是否达到。所以只需将某变量放在一个20ms中断的定时器到中断就+20,然后sys_now返回它就可以了。
2.协议栈初始化:
u8 lwip_comm_init(void)
{
u8 retry=0;
struct netif *Netif_Init_Flag;
struct ip_addr ipaddr;
struct ip_addr netmask;
struct ip_addr gw;
if(lwip_comm_mem_malloc())return 2;
lwip_comm_default_ip_set(&lwipdev);
while(LAN8720_Init())
{
retry++;
if(retry>5) {retry=0;return 3;}
}
tcpip_init(NULL,NULL);
#if LWIP_DHCP //DHCP 获取动态IP
ipaddr.addr = 0;
netmask.addr = 0;
gw.addr = 0;
#else //静态IP等网络参数
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]);
Netif_Init_Flag=netif_add(&lwip_netif,&ipaddr,&netmask,&gw,NULL,ðernetif_init,&tcpip_input);
if(Netif_Init_Flag==NULL)return 4;
else
{
netif_set_default(&lwip_netif); //添加网卡
netif_set_up(&lwip_netif); //启动网卡
}
return 0;
}
3.交互方式:
(1)中断方式(响应快,但是数据量大的话,容易一直打断别的程序)
void ETH_IRQHandler(void)
{
lwip_pkt_handle();
__HAL_ETH_DMA_CLEAR_IT(&LAN8720_ETHHandle,ETH_DMA_NORMAL_IT);
__HAL_ETH_DMA_CLEAR_IT(&LAN8720_ETHHandle,ETH_DMA_RX_IT);
__HAL_ETH_DMA_CLEAR_IT(&LAN8720_ETHHandle,ETH_DMA_TX_IT);
}
(2)轮询方式(可能丢包,可控制时间)
while(1)
{
lwip_pkt_handle();
}