其实这个早在几年前就做过了,最近刚好又要重新验证一下网络电路,就再做一遍.实际上网络移植说内容不算多,主流的开源项目都是比较好移植的,就拿LWIP为例,其实只需要实现几个底层函数就行,和移植GUI库也没什么本质上的差别,我这里移植的平台是STM32F7,我大致浏览了一下,基本上F4之后的芯片,以太网基本功能都没怎么变化过.
从上至下来看需要移植的部分,先说一个简单的LWIP最小代码应该是怎样的,这个很容易找到例子.
struct netif gnetif; /* network interface structure */
ip_addr_t ipaddr;
ip_addr_t netmask;
ip_addr_t gw;
ip_addr_set_zero_ip4(&ipaddr);
ip_addr_set_zero_ip4(&netmask);
ip_addr_set_zero_ip4(&gw);
tcpip_init(NULL, NULL);
netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, &tcpip_input);
首先可见的是我们需要实现ethernetif_init,而其他函数都是LWIP本体的,而这个初始化函数,我们需要实现的目标是完善netif->output和netif->linkoutput,前面的方法发送的是IP包,如果硬件支持直接发送IP包加速,可以自己实现netif->output,否则也可以指向etharp_output这个LWIP内部实现,当然了STM32H7我看了是支持数据包Offload的,但是F7开始只支持自动校验和,F4就只有基本功能,但是我这里暂时不实现.而netif->linkoutput指向的就是直接把网络数据包发送出去,一般会定义为low_level_output.
那么输入怎么处理呢,在LWIP开了这么一个口子,把数据包包裹成pbuf格式,然后调用netif->input(p, netif),就可以把数据交给LWIP处理了.
根据上面的已知条件,可以先实现两个函数.
static struct pbuf* low_level_input(struct netif* netif)
{
struct pbuf* p = NULL;
// 中断接受数据后,读出来.
HAL_ETH_ReadData(&heth, (void**)&p);
return p;
}
static void ethernetif_input(void* pvParameters)
{
struct pbuf* p = NULL;
struct netif* netif = (struct netif*)pvParameters;
for (;;)
{
// 如果等到数据来了,交给网卡处理
if (xSemaphoreTake(RxPktSemaphore, portMAX_DELAY) == pdTRUE)
{
do
{
p = low_level_input(netif);
if (p != NULL)
{
if (netif->input(p, netif) != ERR_OK)
{
pbuf_free(p);
}
}
}
while (p != NULL);
}
}
}
static err_t low_level_output(struct netif* netif, struct pbuf* p)
{
// 包裹数据
HAL_ETH_Transmit_IT(&heth, &TxConfig);
// 等发送完成后,可以执行下面语句释放数据包
HAL_ETH_ReleaseTxPacket(&heth);
return errval;
}
可以看到利用了FreeRTOS,如果是其他OS就自行实现对应的信号量机制就可以了.
可是,网卡都还没初始化呢,我们一切目标是为了让网卡运行起来.所以在ethernetif_init这个调用里,我们还要初始化网卡的各种信息,包括MAC地址,网络Hostname,创建信号量,MDIO初始化等等.我暂且称为low_level_init函数.
static void low_level_init(struct netif* netif)
{
// 进行外设参数的配置
HAL_ETH_Init(&heth);
// 网卡信息配置
netif->hwaddr_len = ETH_HWADDR_LEN;
netif->hwaddr[0] = ETH_MAC_ADDR0;
netif->hwaddr[1] = ETH_MAC_ADDR1;
netif->hwaddr[2] = ETH_MAC_ADDR2;
netif->hwaddr[3] = ETH_MAC_ADDR3;
netif->hwaddr[4] = ETH_MAC_ADDR4;
netif->hwaddr[5] = ETH_MAC_ADDR5;
// 一些其他LWIP配置
LWIP_MEMPOOL_INIT(RX_POOL);
// 创建接收线程,我的接受里面是等中断,中断释放信号量,那么任务就需要处理数据,交给LWIP处理.
xTaskCreate(ethernetif_input, "EthIf", INTERFACE_THREAD_STACK_SIZE, netif, configMAX_PRIORITIES, NULL);
// 网卡寄存器配置
LAN8742_Init();
// 硬件全部设置好了,再把网卡拉起来.
netif_set_up(netif);
netif_set_link_up(netif);
}
至于深入MDIO怎么设置,这里不展开,到这里,硬件相关部分就移植完成了.
前面说了,用了FreeRTOS,那么LWIP不用OS行不行,是可以的,但是这样开发其实更麻烦,我们现在实现OS相关的函数,即所有sys_func风格的函数,并放在一个C文件里.我举个实现的例子.
u32_t sys_arch_sem_wait(sys_sem_t* sem, u32_t timeout)
{
TickType_t starttime = xTaskGetTickCount();
if (timeout != 0)
{
if (xSemaphoreTake(*sem, pdMS_TO_TICKS(timeout)) == pdPASS)
{
return (xTaskGetTickCount() - starttime);
}
else
{
return SYS_ARCH_TIMEOUT;
}
}
else
{
while (xSemaphoreTake(*sem, portMAX_DELAY) != pdPASS);
return (xTaskGetTickCount() - starttime);
}
}
当实现完所有这些函数后,移植完成度就达到90%了.
既然LWIP是一个非常庞大的单片机以太网协议栈,一般我们不会在单片机上运行所有功能,因此还要新建一个lwipopts.h来选择需要的功能.要注意了,如果要实现某些功能,可能还需要提供一些其他函数移植,比如日期时间设置函数等.
比如给予LWIP主任务多少的运行资源.
#define TCPIP_THREAD_NAME "TCP/IP"
#define TCPIP_THREAD_STACKSIZE 1000
#define TCPIP_MBOX_SIZE 6
#define DEFAULT_UDP_RECVMBOX_SIZE 6
#define DEFAULT_TCP_RECVMBOX_SIZE 6
#define DEFAULT_ACCEPTMBOX_SIZE 6
#define DEFAULT_THREAD_STACKSIZE 500
#define TCPIP_THREAD_PRIO configMAX_PRIORITIES + 1
到这里就完成度很高了,还需要引入一些平台特有的头文件,对于ARM这么知名的平台,LWIP是有提供的,可以直接复制过来.
红色框是我从LWIP源码里复制出来的,绿色是自己要写的,蓝色是配置文件.至于绿色的文件,前面说的只是修改思路,并非完整文件,比如要实现中断接受等,这些都是需要自行处理的.还有STM32的以太网描述符是怎样的,手册里也非常完整,至于移植到非STM32,所需的文件也是一样,差异无非是具体硬件实现.

最后引入需要的LWIP核心源码,比如我选择的这些,需要根据你实际需求选择.
set(LWIP_DIR "Middlewares/LwIP")
set(LWIP_SRCS
${LWIP_DIR}/src/core/init.c
${LWIP_DIR}/src/core/def.c
${LWIP_DIR}/src/core/dns.c
${LWIP_DIR}/src/core/inet_chksum.c
${LWIP_DIR}/src/core/ip.c
${LWIP_DIR}/src/core/mem.c
${LWIP_DIR}/src/core/memp.c
${LWIP_DIR}/src/core/netif.c
${LWIP_DIR}/src/core/pbuf.c
${LWIP_DIR}/src/core/raw.c
${LWIP_DIR}/src/core/stats.c
${LWIP_DIR}/src/core/sys.c
${LWIP_DIR}/src/core/altcp.c
${LWIP_DIR}/src/core/altcp_alloc.c
${LWIP_DIR}/src/core/altcp_tcp.c
${LWIP_DIR}/src/core/tcp.c
${LWIP_DIR}/src/core/tcp_in.c
${LWIP_DIR}/src/core/tcp_out.c
${LWIP_DIR}/src/core/timeouts.c
${LWIP_DIR}/src/core/udp.c
${LWIP_DIR}/src/core/ipv4/acd.c
${LWIP_DIR}/src/core/ipv4/autoip.c
${LWIP_DIR}/src/core/ipv4/dhcp.c
${LWIP_DIR}/src/core/ipv4/etharp.c
${LWIP_DIR}/src/core/ipv4/icmp.c
${LWIP_DIR}/src/core/ipv4/igmp.c
${LWIP_DIR}/src/core/ipv4/ip4_frag.c
${LWIP_DIR}/src/core/ipv4/ip4.c
${LWIP_DIR}/src/core/ipv4/ip4_addr.c
${LWIP_DIR}/src/api/api_lib.c
${LWIP_DIR}/src/api/api_msg.c
${LWIP_DIR}/src/api/err.c
${LWIP_DIR}/src/api/if_api.c
${LWIP_DIR}/src/api/netbuf.c
${LWIP_DIR}/src/api/netdb.c
${LWIP_DIR}/src/api/netifapi.c
${LWIP_DIR}/src/api/sockets.c
${LWIP_DIR}/src/api/tcpip.c
${LWIP_DIR}/src/netif/ethernet.c
${LWIP_DIR}/src/netif/bridgeif.c
${LWIP_DIR}/src/netif/bridgeif_fdb.c
)
写在最后,为什么不用CubeMX生成不是更简单吗,那你的板子可和官方配置差异可大呢,基本上只能用作部分参考,而且引入的CMSIS-OS API也是多一层包装,这个见仁见智,LWIP版本和OS版本通常也稍微老一些,我建议有能力还是需要自己移植测试一下.整体移植并没那么困难.