现在,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在有操作系统情况下移植正常。