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
2、LwIP簡要說明
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。
3、LwIP的帶作業系統基本移植
帶作業系統的移植首先是建立在無作業系統移植基礎之上的。在無作業系統移植時,定義的資料型別和巨集都是有效的,只需要對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在有作業系統情況下移植正常。
歡迎關注: