目录
启动流程
通过阅读正点原子的无操作系统移植工程的源码,可以总结出LwIP的无操作系统的启动流程。
前面一些都是基于其他的外设的初始化,我们只关心这里lwip_comm_init(),这个函数的总流程先给出。
uint8_t lwip_comm_init(void)
{
uint8_t retry = 0;
struct netif *netif_init_flag; /* 调用netif_add()函数时的返回值,用于判断网络初始化是否成功 */
ip_addr_t ipaddr; /* ip地址 */
ip_addr_t netmask; /* 子网掩码 */
ip_addr_t gw; /* 默认网关 */
if (ethernet_mem_malloc())return 1; /* 内存申请失败*/
lwip_comm_default_ip_set(&g_lwipdev); /* 设置默认IP等信息 */
while (ethernet_init()) /* 初始化以太网芯片,如果失败的话就重试5次 */
{
retry++;
if (retry > 5)
{
retry = 0; /* 以太网芯片初始化失败 */
return 3;
}
}
lwip_init(); /* 初始化LWIP内核 */
#if LWIP_DHCP /* 使用动态IP */
ipaddr.addr = 0;
netmask.addr = 0;
gw.addr = 0;
#else /* 使用静态IP */
IP4_ADDR(&ipaddr, g_lwipdev.ip[0], g_lwipdev.ip[1], g_lwipdev.ip[2], g_lwipdev.ip[3]);
IP4_ADDR(&netmask, g_lwipdev.netmask[0], g_lwipdev.netmask[1], g_lwipdev.netmask[2], g_lwipdev.netmask[3]);
IP4_ADDR(&gw, g_lwipdev.gateway[0], g_lwipdev.gateway[1], g_lwipdev.gateway[2], g_lwipdev.gateway[3]);
printf("网卡en的MAC地址为:................%d.%d.%d.%d.%d.%d\r\n", g_lwipdev.mac[0], g_lwipdev.mac[1], g_lwipdev.mac[2], g_lwipdev.mac[3], g_lwipdev.mac[4], g_lwipdev.mac[5]);
printf("静态IP地址........................%d.%d.%d.%d\r\n", g_lwipdev.ip[0], g_lwipdev.ip[1], g_lwipdev.ip[2], g_lwipdev.ip[3]);
printf("子网掩码..........................%d.%d.%d.%d\r\n", g_lwipdev.netmask[0], g_lwipdev.netmask[1], g_lwipdev.netmask[2], g_lwipdev.netmask[3]);
printf("默认网关..........................%d.%d.%d.%d\r\n", g_lwipdev.gateway[0], g_lwipdev.gateway[1], g_lwipdev.gateway[2], g_lwipdev.gateway[3]);
#endif /* 向网卡列表中添加一个网口 */
netif_init_flag = netif_add(&g_lwip_netif, (const ip_addr_t *)&ipaddr, (const ip_addr_t *)&netmask, (const ip_addr_t *)&gw, NULL, ðernetif_init, ðernet_input);
if (netif_init_flag == NULL)
{
return 4; /* 网卡添加失败 */
}
else /* 网口添加成功后,设置netif为默认值,并且打开netif网口 */
{
netif_set_default(&g_lwip_netif); /* 设置netif为默认网口 */
if (netif_is_link_up(&g_lwip_netif))
{
netif_set_up(&g_lwip_netif); /* 打开netif网口 */
}
else
{
netif_set_down(&g_lwip_netif);
}
}
#if LWIP_DHCP /* 如果使用DHCP的话 */
g_lwipdev.dhcpstatus = 0; /* DHCP标记为0 */
dhcp_start(&g_lwip_netif); /* 开启DHCP服务 */
#endif
return 0; /* 操作OK. */
}
ethernet_mem_malloc:为描述符及缓冲区申请内存,除了这种用算法实现的内存申请函数可以申请内存之外,ST官方给出的例程是使用简单的使用数组来实现申请内存。
/**
* @breif 为ETH底层驱动申请内存
* @param 无
* @retval 0,正常
* 1,失败
*/
uint8_t ethernet_mem_malloc(void)
{
g_eth_dma_rx_dscr_tab = mymalloc(SRAMIN, ETH_RXBUFNB * sizeof(ETH_DMADescTypeDef)); /* 申请内存 */
g_eth_dma_tx_dscr_tab = mymalloc(SRAMIN, ETH_TXBUFNB * sizeof(ETH_DMADescTypeDef)); /* 申请内存 */
g_eth_rx_buf = mymalloc(SRAMIN, ETH_RX_BUF_SIZE * ETH_RXBUFNB); /* 申请内存 */
g_eth_tx_buf = mymalloc(SRAMIN, ETH_TX_BUF_SIZE * ETH_TXBUFNB); /* 申请内存 */
if (!(uint32_t)&g_eth_dma_rx_dscr_tab || !(uint32_t)&g_eth_dma_tx_dscr_tab || !(uint32_t)&g_eth_rx_buf || !(uint32_t)&g_eth_tx_buf)
{
ethernet_mem_free();
return 1; /* 申请失败 */
}
return 0; /* 申请成功 */
}
ethernet_init:配置以太网环境、初始化RMII的IO(通过调用的HAL_ETH_Init函数,它会调用HAL_ETH_MspInit函数初始化相应的接口IO以及复位PHY芯片管脚)、复位PHY芯片(非常重要,不复位以太网通信不能成功)
/**
* @brief 以太网芯片初始化
* @param 无
* @retval 0,成功
* 1,失败
*/
uint8_t ethernet_init(void)
{
uint8_t macaddress[6];
macaddress[0] = g_lwipdev.mac[0];
macaddress[1] = g_lwipdev.mac[1];
macaddress[2] = g_lwipdev.mac[2];
macaddress[3] = g_lwipdev.mac[3];
macaddress[4] = g_lwipdev.mac[4];
macaddress[5] = g_lwipdev.mac[5];
g_eth_handler.Instance = ETH;
g_eth_handler.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE; /* 使能自协商模式 */
g_eth_handler.Init.Speed = ETH_SPEED_100M; /* 速度100M,如果开启了自协商模式,此配置就无效 */
g_eth_handler.Init.DuplexMode = ETH_MODE_FULLDUPLEX; /* 全双工模式,如果开启了自协商模式,此配置就无效 */
g_eth_handler.Init.PhyAddress = ETHERNET_PHY_ADDRESS; /* 以太网芯片的地址 */
g_eth_handler.Init.MACAddr = macaddress; /* MAC地址 */
g_eth_handler.Init.RxMode = ETH_RXINTERRUPT_MODE; /* 中断接收模式 */
g_eth_handler.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE; /* 硬件帧校验 */
g_eth_handler.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII; /* RMII接口 */
if (HAL_ETH_Init(&g_eth_handler) == HAL_OK)
{
return 0; /* 成功 */
}
else
{
return 1; /* 失败 */
}
}
lwip_init:初始化LwIP内核,它是LwIP内核源码自带的函数,初始化了一系列函数,如sys_init()、mem_init()、pbuf_init()、netif_init()。
添加虚拟网卡
LwIP是软件,如何管理真正的以太网硬件呢,比如网络接口有多种,如WIFI接口、以太网接口,怎么管理这些实际的网络接口,LwIP抽象了一个虚拟网卡来管理这些各种硬件网络接口。
ethernetif_init:网卡初始化,设置虚拟网卡参数,该函数在ethernetif.c文件中已经帮我们实现好了整体框架。它会调用low_level_init函数,该函数初始化发送和接收描述符,并开启ETH中断。
err_t ethernetif_init(struct netif *netif) { struct ethernetif *ethernetif; LWIP_ASSERT("netif != NULL", (netif != NULL)); ethernetif = mem_malloc(sizeof(struct ethernetif)); if (ethernetif == NULL) { LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_init: out of memory\n")); return ERR_MEM; } #if LWIP_NETIF_HOSTNAME /* Initialize interface hostname */ netif->hostname = "lwip"; #endif /* LWIP_NETIF_HOSTNAME */ /* * Initialize the snmp variables and counters inside the struct netif. * The last argument should be replaced with your link speed, in units * of bits per second. */ MIB2_INIT_NETIF(netif, snmp_ifType_ethernet_csmacd, LINK_SPEED_OF_YOUR_NETIF_IN_BPS); netif->state = ethernetif; netif->name[0] = IFNAME0; netif->name[1] = IFNAME1; /* We directly use etharp_output() here to save a function call. * You can instead declare your own function an call etharp_output() * from it if you have to do some checks before sending (e.g. if link * is available...) */ #if LWIP_IPV4 netif->output = etharp_output; #endif /* LWIP_IPV4 */ #if LWIP_IPV6 netif->output_ip6 = ethip6_output; #endif /* LWIP_IPV6 */ netif->linkoutput = low_level_output; ethernetif->ethaddr = (struct eth_addr *) & (netif->hwaddr[0]); /* initialize the hardware */ low_level_init(netif); return ERR_OK; }
low_level_init:初始化发送和接收描述符,并开启ETH中断。
static void low_level_init(struct netif *netif) { netif->hwaddr_len = ETHARP_HWADDR_LEN; /* 设置MAC地址长度,为6个字节 */ /* 初始化MAC地址,设置什么地址由用户自己设置,但是不能与网络中其他设备MAC地址重复 */ netif->hwaddr[0] = g_lwipdev.mac[0]; netif->hwaddr[1] = g_lwipdev.mac[1]; netif->hwaddr[2] = g_lwipdev.mac[2]; netif->hwaddr[3] = g_lwipdev.mac[3]; netif->hwaddr[4] = g_lwipdev.mac[4]; netif->hwaddr[5] = g_lwipdev.mac[5]; netif->mtu = 1500; /* 最大允许传输单元,允许该网卡广播和ARP功能 */ /* 网卡状态信息标志位,是很重要的控制字段,它包括网卡功能使能、广播 */ /* 使能、 ARP 使能等等重要控制位 */ netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP; /* 广播 ARP协议 链接检测 */ HAL_ETH_DMATxDescListInit(&g_eth_handler,g_eth_dma_tx_dscr_tab,g_eth_tx_buf,ETH_TXBUFNB); /* 初始化发送描述符 */ HAL_ETH_DMARxDescListInit(&g_eth_handler,g_eth_dma_rx_dscr_tab,g_eth_rx_buf,ETH_RXBUFNB); /* 初始化接收描述符 */ HAL_ETH_Start(&g_eth_handler); /* 开启ETH */ }
ethernet_input:以太网数据包输入,这里LwIP默认已经帮我们实现好了框架。
开启虚拟网卡:通过netif_add添加一个网口该函数实现如下:
netif_init_flag = netif_add(&g_lwip_netif, (const ip_addr_t *)&ipaddr, (const ip_addr_t *)&netmask, (const ip_addr_t *)&gw, NULL, ðernetif_init, ðernet_input);
网口添加成功之后,设置netif为默认值,并且打开netif网口。
else /* 网口添加成功后,设置netif为默认值,并且打开netif网口 */ { netif_set_default(&g_lwip_netif); /* 设置netif为默认网口 */ if (netif_is_link_up(&g_lwip_netif)) { netif_set_up(&g_lwip_netif); /* 打开netif网口 */ } else { netif_set_down(&g_lwip_netif); } }
虚拟网卡控制块
发送流程
接收流程
接收有两种方式接收数据包,一种是查询式接收数据包,一种是中断方式接收数据包。
总结