文献标识码: A
文章编号: 0258-7998(2013)05-0018-04
随着嵌入式系统功能的多样化及网络在各个领域中的广泛应用,具有网络功能的嵌入式终端拥有更高的使用价值和更强的通用性。μC/OS-Ⅲ是一个可裁剪、可固化、可剥夺型的实时内核,管理任务的数目不受限制[1]。作为μC/OS系列的最新版本,μC/OS-Ⅲ提供了实时内核所能提供的所有服务,可保证网络功能和其他诸多任务并发有序地执行。但μC/OS-Ⅲ仅仅是一个实时操作系统的内核,要实现网络功能还需移植一款符合嵌入式系统要求的以太网协议栈。LwIP是由瑞典计算机科学研究院开发的轻量型TCP/IP协议栈,其特点是保持了以太网的基本功能,通过优化减少了对片内存储资源的占用[2]。一般情况下,具有十几KB SRAM和几十KB Flash存储能力的微控制器即可运行LwIP协议栈[3]。该特点使其广泛使用于数据采集、工业控制等多个应用领域中。本文论述了使用LM3S9B95微控制器的嵌入式平台实现LwIP 1.4.0版本在μC/OS-Ⅲ上的移植。LM3S9B95是TI公司推出的基于ARM Cortex-M3内核的微控制器,其内部具有以太网控制器模块[4]。
1 LwIP的移植过程
LwIP的移植主要涉及两个方面:操作系统模拟层和硬件驱动层。LwIP在设计时已考虑到在不同操作系统中的可移植性,其内部使用的函数和数据结构均为抽象定义[5]。开发者可根据不同的操作系统要求来具体实现相关的函数和数据结构。同时,硬件相关的驱动同样预留了接口,开发者可针对实际使用情况编写网络控制芯片驱动函数。另外,对不同的编译环境,开发者还需要编写部分头文件定义相关数据结构和宏。LwIP在μC/OS-Ⅲ嵌入式系统中的结构如图1所示,其中的箭头框为移植工作需要实现的模块。
1.1 操作系统模拟层的编写
1.1.1 编写头文件cc.h
cc.h文件中包含处理器相关的变量类型、数据结构及字节对齐的相关宏。
LwIP中使用的基本变量类型均以位数进行命名,为抽象的变量定义,开发者需要根据所用处理器具体定义。基本变量的定义有两种方法:一种是将变量直接定义为C语言的基本类型,如unsigned char、int等;另一种是将变量定义为操作系统内对应的抽象变量。当使用操作系统时,应采用第二种方法。该方法的优点是变量对于处理器是“透明”的,应用程序更换硬件平台时无需修改操作系统模拟层内的定义。μC/OS-Ⅲ中对基本变量的定义在cpu.h文件中,均以CPU为命名前缀。对于这些变量在μC/OS-Ⅲ中具体如何定义本文不做讨论。LwIP要求定义8 bit、16 bit、32 bit和内存指针型变量:
typedef CPU_INT08U u8_t;
typedef CPU_INT08S s8_t;
typedef CPU_INT16U u16_t;
typedef CPU_INT16S s16_t;
typedef CPU_INT32U u32_t;
typedef CPU_INT32S s32_t;
typedef CPU_INT32U mem_ptr_t;
由于ARM处理器的编译环境默认对变量存储采取4 B对齐方式,而以太网数据包等结构体要求处理器按照变量的实际大小存储和访问,因此,需要定义相关的结构封装宏,使得结构体内的成员变量不以4 B对齐的方式进行存储。移植工作采用了IAR开发环境,需根据该环境定义如下相关的宏:
#if defined (__IAR_SYSTEMS_ICC__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#define PACK_STRUCT_USE_INCLUDES
1.1.2 编写头文件sys_arch.h
sys_arch.h文件要求定义操作系统相关的数据结构和宏。
LwIP多线程功能需要信号量和邮箱等结构体,用于多个任务的同步和消息的传递。μC/OS-Ⅲ中的信号量OS_SEM和消息队列OS_Q可实现相应的功能。LwIP 1.4.0版本中使用了互斥信号量管理共享的资源,而有些嵌入式操作系统中不包含互斥信号量的变量类型。为了适应不同的操作系统,LwIP定义了宏LWIP_COMPAT_MUTEX。LWIP_COMPAT_MUTEX的值定义为1,则LwIP使用二值信号量代替互斥信号量以及相关的功能函数。虽然μC/OS-Ⅲ包含了互斥信号量OS_MUTEX,但LwIP中两种数据结构可相互替换,选择使用二值信号量可以减少一定的移植工作。
#define LWIP_COMPAT_MUTEX 1
typedef OS_SEM sys_sem_t;
typedef OS_Q sys_mbox_t;
LwIP中包含有必须完整执行而不可被打断的代码,因此需要使用临界段代码保护的功能。μC/OS-Ⅲ中提供了关闭中断和锁定调度器两种临界段代码保护方法。LwIP中的临界段代码保护宏可直接定义为μC/OS-Ⅲ关闭中断的对应临界段代码保护宏。
#define SYS_ARCH_DECL_PROTECT() CPU_SR_ALLOC()
#define SYS_ARCH_PROTECT() OS_CRITICAL_ENTER()
#define SYS_ARCH_UNPROTECT() OS_CRITICAL_EXIT()
1.1.3 编写源文件sys_arch.c
sys_arch.c文件要求实现操作系统模拟层的接口函数,主要包括对信号量和邮箱等数据结构的操作以及LwIP线程的操作。
LwIP的信号量用于进程间的通信,相关操作主要包括以下几个函数:
sys_sem_new() //新建信号量
sys_sem_free() //释放信号量
sys_sem_signal() //发送信号量
sys_arch_sem_wait() //阻塞进程,等待指定信号量
sys_sem_valid() //检查信号量可用性
sys_sem_set_invalid() //设置信号量不可用
LwIP的邮箱用于缓存和传递数据报文,相关操作主要包括以下几个函数:
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() //设置邮箱不可用
LwIP使用了μC/OS-Ⅲ中的信号量OS_SEM和消息队列OS_Q结构,以上函数的实现调用了μC/OS-Ⅲ的操作函数,包括OS?Create()、OS?Del()、OS?Post()和OS?Pend()。在实现sys_?_new()和sys_?_free()函数时,需加入临界段代码保护以确保OS?Create()和OS?Del()在执行时不被打断,可避免出现系统资源管理错误。
err_t sys_?_new(……)
{
OS_ERR err;
CPU_SR_ALLOC(); //临界代码保护开始
CPU_CRITICAL_ENTER();
OS?Create(……,&err);
CPU_CRITICAL_EXIT(); //临界代码保护结束
return err;
}
void sys_?_free(……)
{
OS_ERR err;
CPU_SR_ALLOC(); //临界代码保护开始
CPU_CRITICAL_ENTER();
OS?Del(……, &err );
CPU_CRITICAL_EXIT(); //临界代码保护结束
}
LwIP 1.4.0版本新添加了sys_?_valid()和sys_?_set_
invalid(),这两个函数的实现无需调用操作系统内部的函数,可由开发者根据实际需求实现。另外,二值信号量替换了互斥信号量,相关的操作函数也无需在此文件内实现。在LwIP内核中的sys.h文件给出了详细的宏定义:
#if LWIP_COMPAT_MUTEX
#define sys_mutex_t sys_sem_t
#define sys_mutex_new(mutex) sys_sem_new(mutex, 1)
#define sys_mutex_lock(mutex) sys_sem_wait(mutex)
#define sys_mutex_unlock(mutex) sys_sem_signal(mutex)
#define sys_mutex_free(mutex) sys_sem_free(mutex)
#define sys_mutex_valid(mutex) sys_sem_valid(mutex)
#define sys_mutex_set_invalid(mutex) \
sys_sem_set_invalid(mutex)
LwIP建立新进程的接口函数sys_thread_new()要求成功建立一个任务并返回任务优先级。μC/OS-Ⅲ中加入了时间片轮转调度功能,使得同一优先级可建立多个任务,避免了优先级重复导致任务建立失败的情况。相比于使用μC/OS-Ⅱ或其他不支持同级任务的操作系统,sys_thread_new()的实现仅调用OSTaskCreate()即可,省略了一些查找可用优先级的容错操作。
操作系统模拟层的初始化函数sys_init()由开发者根据实际情况进行编写,没有固定的规范要求。该函数可不执行任何操作,但必须在文件内实现。
1.2 硬件驱动层的编写
LwIP内核文件中给出了驱动文件的参考模板ethernetif.c,开发者可根据其模板的架构结合实际使用的网络控制芯片来编写驱动。
以low_level为前缀的函数均为网络控制芯片相关的接口函数,主要包含初始化、接收、发送等操作。LM3S9B95的以太网控制器模块包含了MAC层和物理层,有别于传统的MCU+PHY芯片的结构。因此,实现驱动函数时可直接对相应的寄存器进行操作,无需再次封装PHY芯片的操作函数。
以ethernetif为前缀的函数要求开发者实现底层硬件与上层协议间的接口函数,包括底层设备描述结构体的相关操作、LwIP主线程和以太网中断服务函数等。
1.3 LwIP功能裁剪和定制
LwIP为开发者提供了一个功能定制的接口文件lwipopt.h,可根据系统实际需求定义宏的值、裁剪功能和配置参数。例如使用TCP和UDP功能,则需添加下列定义:
#define LWIP_TCP 1
#define LWIP_UDP 1
内核文件opt.h是lwipopt.h的设计模板,包含了所有LwIP功能配置的宏。opt.h文件对宏定义均采用了#ifndef预编译判断,当开发者在lwipopt.h中没有对某个宏给出定义时,该文件会定义一个默认值。
虽然修改opt.h中的宏定义和在lwipopt.h中编写宏定义均可实现剪裁和定制LwIP的功能,但由于修改内核文件会破坏协议栈的封装性,为今后的应用程序移植和维护造成隐患,所以开发者不应直接修改opt.h内的宏定义。
开发者在编写lwipopt.h时,由于每个宏的默认值并不能保证LwIP的正确运行,所以应对opt.h中给出的所有宏进行定义。例如opt.h中对于TCP的一些宏定义如下:
#ifndef TCPIP_THREAD_STACKSIZE
#define TCPIP_THREAD_STACKSIZE 0
#endif
#ifndef TCPIP_MBOX_SIZE
#define TCPIP_MBOX_SIZE 0
#endif
LwIP默认的TCPIP进程堆栈空间为0,TCPIP使用的邮箱空间为0。若开发者在lwipopt.h中不对这些宏进行定义,当tcpip_init()对LwIP进行初始化时,就会出现错误致使LwIP无法正确运行。
2 测试
测试工作使用LM3S9B95嵌入式平台作为TCP客户端,一台PC作为TCP主机端。测试程序中,嵌入式平台的IP地址设为172.21.28.250,主机IP为172.21.28.253,端口为1020。测试程序中创建了两个任务:一个是LwIP主线程,一个是测试任务。LwIP主线程处理以太网协议的数据包,测试任务负责接收主机端的数据并回传至主机端。
测试任务首先初始化底层硬件和协议栈,包括使能LM3S9B95以太网硬件模块和中断、调用协议栈内核初始化函数tcpip_init()、初始化网络接口的结构体。
void My_LwIP_Init(void)
{
/* 调用StellarisWare库函数进行硬件初始化 */
……
/* 调用内核初始化函数 */
tcpip_init();
/* 初始化netif,设置本机的IP、子网掩码、网关,绑定netif的回调函数 */
netif_add(……);
netif_set_default(……);
netif_set_up(……);
}
第二步是初始化客户端。首先创建一个网络连接结构体,再将其绑定至端口并连接到指定的服务器。
void TCP_Client_Init(void)
{
pstNetconn = netconn_new(NETCONN_TCP); //新建连接
netconn_bind(……); //绑定端口
netconn_connect(……); //连接主机
}
任务的主循环中调用了LwIP具有进程阻塞功能的函数netconn_recv()以接收来自主机的数据。若数据接收正确,则将数据发送回主机端的PC;若接收不正确,则删除当前的连接,重新连接到主机。
while(1)
{
err = netconn_recv(……); // 接收数据
if(err == ERR_OK) // 数据正确
{
netconn_write(……); // 发送数据
netbuf_delete(……); // 删除数据缓冲区
}
else
{
netconn_delete(……); // 删除当前连接
/* 重新连接 */
……
}
}
PC主机端使用了铭心软体工作室的网络调试助手,通过该软件向LM3S9B95客户端发送测试数据,客户端的回传数据也在该软件内显示。测试结果如图2所示。
LwIP是一款专为嵌入式系统设计的以太网协议栈,具有占用资源小、基本功能完备和便于移植等特点。其拥有很高的通用性,适用于多种嵌入式操作系统和硬件平台[6]。在运行实时操作系统的应用环境中,移植工作要求开发者实现操作系统模拟层和硬件驱动层两个部分。协议栈的主进程可作为实时操作系统的一个任务,完整地执行网络通信功能。μC/OS-Ⅲ是μC/OS系列的最新产品,同样是一款实时操作系统的内核,并不具备网络通信功能。LwIP移植到μC/OS-Ⅲ中,可使得运行该实时内核的嵌入式终端拥有网络通信功能,符合当今产品发展的趋势,具有更广泛的应用领域和更高的市场价值。
参考文献
[1] LABROSSE J J著.嵌入式实时操作系统μC/OS-Ⅲ[M]. 邵贝贝,译.北京:北京航空航天大学出版社,2012.
[2] DUNKELS A.Design and implementation of the LwIP TCP/ IP Stack[Z].Sweden:Swedish Institute of Computer Science,2001:21-30.
[3] 熊海泉.μC/OS II下LwIP协议的移植实现[J].科技广场,2005(2):78-79.
[4] Texas Instruments.Stellaris?誖LM3S9B95 microcontroller data sheet[Z].2011.
[5] 程明,余中华,苏艳苹,等.μC/OS II下LwIP协议栈的移植和测试[J].微计算机信息,2008(23):79-81.
[6] 余坤杰.LWIP网口通讯协议在LM3S8962网口上的移植实现[J].设计与分析,2011(27):155-156.