lwip协议的深入理解(结合目前主流的stm32,ti,esp8266等芯片讲解)

  主流的物联网产品及芯片主要支持lwip协议栈,这款专为嵌入式开发的轻量级协议栈为flash和ram都不是很强大cpu提供了一个可靠的数据传输,本人目前主要是在嵌入式网络通信方面做开发工作,今天开始与大家一起探讨lwip协议这套适用于嵌入式开发的轻量级协议栈。希望对读者以后理解lwip有帮助,有错误希望提出,及时更正,共同进步,最后谢谢大家花时间看本文。


  一、lwip协议栈的数据类型:u8_t,s8_t,u16_t,u32_t等等;由于所选用的编译器和处理器的不同,这些数据类型必须重新定义。方便读者了解,我分别采用了esp8266,stm32的lwip协议栈部分的数据类型重定义

esp8266:

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 unsigned long   mem_ptr_t;

stm32-m3:

typedef unsigned   char    u8_t;
typedef signed     char    s8_t;
typedef unsigned   short   u16_t;
typedef signed     short   s16_t;
typedef unsigned   int    u32_t;
typedef signed     long    s32_t;
typedef u32_t mem_ptr_t;
typedef int sys_prot_t;

tm4c129:

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 u8_t                sys_prot_t;

这是在lwip协议cc.h中的文件定义,都做了数据的重定义。



二、随后是信号量与邮箱操作相关的函数,裸机跑lwip就比较麻烦,需要自己建立一套信号量和邮箱相关机制,这套流程随后的文章中讲解,目前的主流芯片都有自己的一套进程通信机制;lwip使用邮箱和信号量来实现上层应用和协议栈间,下层硬件驱动与协议栈间的信息交互。lwip是一个进程内实现了各个层次的所有工作。

  具体如下:lwip完成相关的初始化→阻塞在一个邮箱上→等待接收数据进行处理。邮箱取得数据后lwip会对数据进行解析,依次分析使用相关处理函数,处理结束后,lwip继续阻塞在邮箱等待下一批数据。这里存在一个问题就是等待超时,其实不然,在lwip初始化进程的时候会对相关的超时函数初始化,事件超时后会自动调用一些超时处理函数做相关处理。关于超时机制的处理我们没必要深追,设计者已经实现,对于我们本身只是一个函数的调用。

  lwip建立在多线程的操作系统之上,典型的lwip应用系统包括三个进程:上层应用程序进程,lwip协议栈进程,底层硬件数据接收发送进程;注意,lwip协议栈进程一般具有最高优先级,以便实时正确的对数据进行响应。

  最后是底层网络的驱动函数实现。这取决于芯片所使用的网络接口芯片,我们要做相应的封装,将接收到得数据包封装为lwip协议栈熟悉的数据结构,将发送的数据包分解为芯片熟悉的数据结构。



三、结合rtk8711分析lwip初始化,联网等一系列操作和函数调用,本人自己的开发代码,有不合理的地方可以提出,及时修正。

  8711默认的的初始化操作,rtl_cryptoEngine_init(暂时未知,类似于加密),console_init(初始化uart和命令服务),    pre_example_entry(预处理程序的应用),    wlan_network(网口初始化操作),在wlan_network中起了一个init_thread的任务

               if(xTaskCreate(init_thread, ((const char*)"init"), STACKSIZE, NULL, tskIDLE_PRIORITY + 3 + PRIORITIE_OFFSET, NULL) != pdPASS)
printf("\n\r%s xTaskCreate(init_thread) failed", __FUNCTION__);

xTaskCreate解析,init_thread为指向任务的入口函数;((const char*)"init")任务的名字;STACKSIZE堆栈大小一般默认;NULL指针用于传向创建函数;tskIDLE_PRIORITY + 3 + PRIORITIE_OFFSET任务运行优先级,NULL传递一个处理。



  init_thread线程中包含了lwip_init初始化,wifi_on配置wifi函数,wifi_set_autoreconnect()自动重连接网络,uart交互,删除任务。串口交互的任务在默认状态是禁用的;这一线程中有几点是我们以后编程中可取的,初始化任务的建立可以利用#if宏定义。

void init_thread(void *param)
{


#if CONFIG_INIT_NET
#if CONFIG_LWIP_LAYER
/* Initilaize the LwIP stack */
LwIP_Init();
#endif
#endif


#if CONFIG_WLAN
wifi_on(RTW_MODE_STA);
#if CONFIG_AUTO_RECONNECT
//setup reconnection flag
wifi_set_autoreconnect(1);
#endif
printf("\n\r%s(%d), Available heap 0x%x\n\r", __FUNCTION__, __LINE__, xPortGetFreeHeapSize());
#endif


#if CONFIG_INTERACTIVE_MODE
  /* Initial uart rx swmaphore*/
vSemaphoreCreateBinary(uart_rx_interrupt_sema);
xSemaphoreTake(uart_rx_interrupt_sema, 1/portTICK_RATE_MS);
start_interactive_mode();
        printf("uart rx svmaphore ok!...............................\n");
#endif


/* Kill init thread after all init tasks done */
vTaskDelete(NULL);
}



  init_thread内部初始化的讨论:

1.lwip_init():在lwip中是通过一个叫做netif的网络结构体来描述一个硬件网络接口的,这个接口的结构如下

struct netif {
  struct netif *next;   // 指向下一个netif结构的指针
  struct ip_addr ip_addr;     // IP地址相关配置
  struct ip_addr netmask;
  struct ip_addr gw;
 
  err_t (* input)(struct pbuf *p, struct netif *inp);  //调用这个函数可以从网卡上取得一个
                                          // 数据包
  err_t (* output)(struct netif *netif, struct pbuf *p,  // IP层调用这个函数可以向网卡发送
       struct ip_addr *ipaddr);                 // 一个数据包
 
  err_t (* linkoutput)(struct netif *netif, struct pbuf *p);  // ARP模块调用这个函数向网
                                              // 卡发送一个数据包
  void *state;   // 用户可以独立发挥该指针,用于指向用户关心的网卡信息
  u8_t hwaddr_len; // 硬件地址长度,对于以太网就是MAC地址长度,为6各字节
  u8_t hwaddr[NETIF_MAX_HWADDR_LEN];   //MAC地址
  u16_t mtu;   // 一次可以传送的最大字节数,对于以太网一般设为1500
  u8_t flags;   // 网卡状态信息标志位
 
  char name[2]; // 网络接口使用的设备驱动类型的种类
  u8_t num;    // 用来标示使用同种驱动类型的不同网络接口
};

由于各个芯片的移植不一样所以在定义结构体时产生不同的定义方式,但是netif的结构体本身是完全按照上面的方式。next字段是指向下一netif结构的指针,lwip会把所有芯片网卡结构体链成一个链表进行管理,有一个netif_list的全局变量指向该链表的头部,next字段就是用于链表。关于链表的理解我简单说一下,链表为一种常见的重要数据结构,一般的链表有head头指针,结点,NULL结尾,每个结点的结构体为数据和下一个结点的地址;具体的理解还请上网加深理解。ip_addr,netmask,gw三个字段用于发送和接收数据包用,分别表示ip地址和子网掩码,网关地址,input字段指向一个函数,output字段指向一个函数,linkoutput字段指向一个函数,state字段指向用户关心的设备信息,mtu字段表示该网络一个可以传送的最大字节数,flags字段是网卡状态信息标志位,name[]字段用于保存每一个网络接口的名字。


  lwip_init中在rtk8711中首先做了tcpip堆栈的一个初始化,之后通过neti_add初始化与底层硬件网络接口有关的,至此可以正常接收和发送数据包了,对于lwip_init内部的一个函数初始化等的分析,我们随后讨论。


  lwip_init初始化之后,到wifi_on的函数调用,该函数初始化了wlan的一些信息,wlan即无线局域网,之前的lwip_init中只是初始化了网络接口,但是8711为wifi芯片本身是带有wlan的,所以我们继而要去初始化,一些必须走以太网接口的设备是不需要初始化该处的。


  wifi_on函数之后是wifi_set_autoreconnect的函数,此函数从字面意思很好理解即为wifi重连机制,一般的设备我们考虑的情况是配网连接和启动自动连接,这个标志位允许我们可以在flash读取ssid等信息自动连接ap,这是目前无线设备必须保有的机制,我们在开发的过程中,需要好好看看该芯片是否有自动重连的封装函数还是需要自行构建,在此不做累赘。


四、lwip相关的初始化等操作已经完成,接下来就是联网与数据的收发,由于涉及公司,具体的情况不便说明。

  我们采用rtk8711是串口通信的机制,所以在初始化过程中需要将串口也一并初始化。接下来就是我们的联网收发过程。

  首先我们创建了一个基本的任务,该任务等级低于lwip等级。


你可能感兴趣的:(lwip协议的深入理解(结合目前主流的stm32,ti,esp8266等芯片讲解))