1. 程式人生 > 實用技巧 >LwIP應用開發筆記之十:LwIP帶作業系統基本移植

LwIP應用開發筆記之十:LwIP帶作業系統基本移植

  現在,TCP/IP協議的應用無處不在。隨著物聯網的火爆,嵌入式領域使用TCP/IP協議進行通訊也越來越廣泛。在我們的相關產品中,也都有應用,所以我們結合應用實際對相關應用作相應的總結。

1、技術準備

  我們採用的開發平臺是STM32F407和LwIP協議棧。在開始之前,我們需要做必要的準備工作。

  首先要獲得LwIP的原始碼,在網上有很多,不同版本及不同平臺的都有,不過我們還是建議直接從官方網站獲得。其官方網站如下:

  http://savannah.nongnu.org/projects/lwip/

  其次,需要硬體平臺,我們採用了STM32F407ZG+DM9161的網路介面方式,這並不是必須的,其他硬體平臺也是一樣的。

  最後,因為我們後面要在作業系統下移植,採用的作業系統是FreeRTOS,所以還需下載FreeRTOS的原始碼。同樣簡易從官網下載:

  https://www.freertos.org/index.html

2LwIP簡要說明

  LwIP是一款免費的TCP/IP協議棧,但它的功能趨勢十分完備。LwIP 具有三種應用程式設計介面 (API):

  • Raw API:為原始的 LwIP API。它通過事件回撥機制進行應用開發。該 API 提供了最好的效能和優化的程式碼長度,但增加了應用開發的複雜性。
  • Netconn API:為高層有序 API,需要實時作業系統 (RTOS)的支援 (提供程序間通訊的方法)。 Netconn API 支援多執行緒工作。
  • BSD Socket API:類似 Berkeley 的套接字 API (開發於 Netconn API 之上) 。

  對於以上三種介面,前一種只需要裸機即可呼叫,後兩種需要作業系統才能呼叫。所以據此LwIP存在兩種移植方式:一是,只移植核心,此時應用程式的編寫只能基於RAW/Callback API進行。二是,移植核心和上層API,此時應用程式編寫可以使用3種API,即:RAW/Callback API、Sequential API和Socket API。

3LwIP的帶作業系統基本移植

  帶作業系統的移植首先是建立在無作業系統移植基礎之上的。在無作業系統移植時,定義的資料型別和巨集都是有效的,只需要對lwipopts.h配置檔案做簡單修改,並根據sys_arch.txt移植說明檔案編寫sys_arch.c和sys_arch.h兩個檔案以實現作業系統模擬層就可以了。

  作業系統模擬層的功能再以為協議棧提供郵箱、訊號量、互斥量等機制,用以保證核心與上層API的通訊。這些作業系統模擬層函式均在sys.h中已經宣告,我們一般在sys_arch.c檔案中完成其定義。所以,我們很清楚,帶作業系統的移植就是在無作業系統的基礎上新增作業系統模擬層。在接下來我們就看看作業系統模擬層的編寫。

  在作業系統已經正確移植的基礎上,我們根據sys_arch.txt移植說明檔案的描述,還需要移植的巨集定義及函式等如下:

名稱

屬性

功能

sys_mbox_t

資料型別

指標型別,指向系統郵箱

sys_sem_t

資料型別

指標型別,指向系統訊號量

sys_mutex_t

資料型別

指標型別,指向系統互斥量

sys_thread_t

資料型別

系統任務標識

SYS_MBOX_NULL

巨集

郵箱指標指向的空值

SYS_SEM_NULL

巨集

訊號量指標指向的空值

sys_init

函式

初始化系統模擬層

sys_sem_new

函式

生成一個訊號量

sys_sem_free

函式

刪除一個訊號量

sys_sem_signal

函式

釋放一個訊號量

sys_arch_sem_wait

函式

等待一個訊號量

sys_sem_valid

函式

判斷一個訊號量是否有效

sys_sem_set_invalid

函式

將一個訊號量置為無效

sys_mutex_new

函式

生成一個新的互斥量

sys_mutex_free

函式

刪除一個互斥量

sys_mutex_lock

函式

鎖住一個互斥量

sys_mutex_unlock

函式

解鎖一個互斥量

sys_mutex_valid

函式

判斷一個互斥量是否有效

sys_mutex_set_invalid

函式

將一個互斥量置為無效

sys_mbox_new

函式

新建一個郵箱

sys_mbox_free

函式

刪除一個郵箱

sys_mbox_post

函式

向郵箱投遞訊息,阻塞

sys_mbox_trypost

函式

嘗試向郵箱投遞訊息,不阻塞

sys_arch_mbox_fetch

函式

從郵箱獲取訊息,阻塞

sys_arch_mbox_tryfetch

函式

嘗試從郵箱獲取訊息,不阻塞

sys_mbox_valid

函式

判斷一個郵箱是否有效

sys_mbox_set_invalid

函式

將一個郵箱設定為無效

sys_thread_new

函式

建立新程序

sys_arch_protect

函式

臨界區保護

sys_arch_unprotect

函式

退出臨界區保護

  從上表中我們可以發現,這些變數和函式主要是面向訊號量、互斥量及郵箱,包括新建、刪除、釋放、獲取等各類操作,我們需要根據作業系統的規定來實現這些函式,我們在這裡使用的FreeRTOS,所以我根據FreeRTOS對訊號量、互斥量及郵箱的操作來實現這些函式。我們列舉郵箱的各操作函式實現如下:

  1 /*建立一個空的郵箱。*/
  2 err_t sys_mbox_new(sys_mbox_t *mbox, int size)
  3 {
  4   osMessageQDef(QUEUE, size, void *);
  5  
  6   *mbox = osMessageCreate(osMessageQ(QUEUE), NULL);
  7  
  8 #if SYS_STATS
  9       ++lwip_stats.sys.mbox.used;
 10       if (lwip_stats.sys.mbox.max < lwip_stats.sys.mbox.used) {
 11          lwip_stats.sys.mbox.max = lwip_stats.sys.mbox.used;
 12          }
 13 #endif /* SYS_STATS */
 14  if (*mbox == NULL)
 15   return ERR_MEM;
 16  
 17  return ERR_OK;
 18 }
 19  
 20 /*重新分配一個郵箱。如果郵箱被釋放時,郵箱中仍有訊息,在lwIP中這是出現編碼錯誤的指示,並通知開發人員。*/
 21 void sys_mbox_free(sys_mbox_t *mbox)
 22 {
 23        if( osMessageWaiting(*mbox) )
 24        {
 25               portNOP();
 26 #if SYS_STATS
 27            lwip_stats.sys.mbox.err++;
 28 #endif /* SYS_STATS */
 29        }
 30  
 31        osMessageDelete(*mbox);
 32  
 33 #if SYS_STATS
 34      --lwip_stats.sys.mbox.used;
 35 #endif /* SYS_STATS */
 36 }
 37  
 38 /*傳送訊息到郵箱*/
 39 void sys_mbox_post(sys_mbox_t *mbox, void *data)
 40 {
 41   while(osMessagePut(*mbox, (uint32_t)data, osWaitForever) != osOK);
 42 }
 43  
 44 /*嘗試將訊息傳送到郵箱*/
 45 err_t sys_mbox_trypost(sys_mbox_t *mbox, void *msg)
 46 {
 47     err_t result;
 48  
 49    if ( osMessagePut(*mbox, (uint32_t)msg, 0) == osOK)
 50    {
 51       result = ERR_OK;
 52    }
 53    else {
 54       result = ERR_MEM;
 55                     
 56 #if SYS_STATS
 57       lwip_stats.sys.mbox.err++;
 58 #endif /* SYS_STATS */
 59                     
 60    }
 61  
 62    return result;
 63 }
 64  
 65 /*阻塞程序從郵箱獲取訊息*/
 66 u32_t sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout)
 67 {
 68   osEvent event;
 69   uint32_t starttime = osKernelSysTick();;
 70  
 71   if(timeout != 0)
 72   {
 73     event = osMessageGet (*mbox, timeout);
 74    
 75     if(event.status == osEventMessage)
 76     {
 77       *msg = (void *)event.value.v;
 78       return (osKernelSysTick() - starttime);
 79     }
 80     else
 81     {
 82       return SYS_ARCH_TIMEOUT;
 83     }
 84   }
 85   else
 86   {
 87     event = osMessageGet (*mbox, osWaitForever);
 88     *msg = (void *)event.value.v;
 89     return (osKernelSysTick() - starttime);
 90   }
 91 }
 92  
 93 /*嘗試從郵箱獲取訊息*/
 94 u32_t sys_arch_mbox_tryfetch(sys_mbox_t *mbox, void **msg)
 95 {
 96   osEvent event;
 97  
 98   event = osMessageGet (*mbox, 0);
 99  
100   if(event.status == osEventMessage)
101   {
102     *msg = (void *)event.value.v;
103     return ERR_OK;
104   }
105   else
106   {
107     return SYS_MBOX_EMPTY;
108   }
109 }
110  
111 /*判斷一個郵箱是否有效*/
112 int sys_mbox_valid(sys_mbox_t *mbox)         
113 {     
114   if (*mbox == SYS_MBOX_NULL)
115     return 0;
116   else
117     return 1;
118 }
119  
120 /*設定一個郵箱無效*/                                             
121 void sys_mbox_set_invalid(sys_mbox_t *mbox)  
122 {                                            
123   *mbox = SYS_MBOX_NULL;                     
124 }                                             
125 
126 //  建立一個新的訊號量。而 "count"引數指示該訊號量的初始狀態
127 err_t sys_sem_new(sys_sem_t *sem, u8_t count)
128 {
129   osSemaphoreDef(SEM);
130  
131   *sem = osSemaphoreCreate (osSemaphore(SEM), 1);
132       
133   if(*sem == NULL)
134   {
135 #if SYS_STATS
136       ++lwip_stats.sys.sem.err;
137 #endif /* SYS_STATS */ 
138               return ERR_MEM;
139   }
140       
141   if(count == 0)  // Means it can't be taken
142   {
143     osSemaphoreWait(*sem,0);
144   }
145  
146 #if SYS_STATS
147        ++lwip_stats.sys.sem.used;
148       if (lwip_stats.sys.sem.max < lwip_stats.sys.sem.used) {
149               lwip_stats.sys.sem.max = lwip_stats.sys.sem.used;
150        }
151 #endif /* SYS_STATS */
152              
153        return ERR_OK;
154 }

  此外還有一些函式也是協議棧需要的函式,特別是sys_thread_new函式,不但協議棧在初始化是需要用到,在後續我們實現各類基於LwIP的應用時也需要用到,其實現如下:

 1 sys_thread_t sys_thread_new(const char *name, lwip_thread_fn thread , void *arg, int stacksize, int prio)
 2 {
 3   const osThreadDef_t os_thread_def = { (char *)name, (os_pthread)thread, (osPriority)prio, 0, stacksize};
 4   return osThreadCreate(&os_thread_def, arg);
 5 }
 6 osThreadId osThreadCreate (const osThreadDef_t *thread_def, void *argument)
 7 {
 8   TaskHandle_t handle;
 9  
10 #if( configSUPPORT_STATIC_ALLOCATION == 1 ) &&  ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
11   if((thread_def->buffer != NULL) && (thread_def->controlblock != NULL)) {
12     handle = xTaskCreateStatic((TaskFunction_t)thread_def->pthread,(const portCHAR *)thread_def->name,
13               thread_def->stacksize, argument, makeFreeRtosPriority(thread_def->tpriority),
14               thread_def->buffer, thread_def->controlblock);
15   }
16   else {
17     if (xTaskCreate((TaskFunction_t)thread_def->pthread,(const portCHAR *)thread_def->name,
18               thread_def->stacksize, argument, makeFreeRtosPriority(thread_def->tpriority),
19               &handle) != pdPASS)  {
20       return NULL;
21     }
22   }
23 #elif( configSUPPORT_STATIC_ALLOCATION == 1 )
24  
25     handle = xTaskCreateStatic((TaskFunction_t)thread_def->pthread,(const portCHAR *)thread_def->name,
26               thread_def->stacksize, argument, makeFreeRtosPriority(thread_def->tpriority),
27               thread_def->buffer, thread_def->controlblock);
28 #else
29   if (xTaskCreate((TaskFunction_t)thread_def->pthread,(const portCHAR *)thread_def->name,
30                    thread_def->stacksize, argument, makeFreeRtosPriority(thread_def->tpriority),
31                    &handle) != pdPASS)  {
32     return NULL;
33   }    
34 #endif
35  
36   return handle;
37 }

  至此,基於FreeRTOS作業系統的LwIP移植結算完成了,我們編譯下載就可以對其進行驗證。

4、結論

  前面已經移植了基於作業系統的LwIP,那怎麼知道我們的移植是否成功呢?接下來我們對它進行必要的驗證。

  首先我們檢視目標板在網路上的配置是否正確。我們開啟命令列視窗,執行ipconfig命令,檢視MAC地址和IP地址配置:

  我們配置的MAC地址00:08:E1:00:00:00和IP地址192.168.2.110顯示正常。接下來我們採用ping命令測試網路連結:

  上圖顯示網路連線正常,經此測試,說明我們的LwIP在有作業系統情況下移植正常。

歡迎關注: