一、引言
随着现场总线技术的不断发展,诸如计算机技术、通信技术等不断地引入到自动控制领域,大大地促进了新一代控制系统——现场总线控制系统(FCS)的发展。
随着嵌入式系统与网络的日益结合,在嵌入式实时操作系统(RTOS)中引入TCP/IP协议栈,以支持嵌入式设备接入网络,成为嵌入式领域重要的研究方向。
本文将介绍基于嵌入式实时操作系统的现场总线网关的设计。其中所介绍的现场总线是WorldFIP现场总线;实时操作系统(RTOS)是μC/OS-Ⅱ。
二、WorldFIP现场总线和μC/OS-Ⅱ简介
IP现场总线简介
WorldFIP FCS是一种用于自动化系统的现场总统提供现场设备和控制器以及控制器之间的数字化连接。WorldFIP FCS适合各种应用结构:集中、分散和主从;其开放性使不同制造厂家的设备能够互操作。WorldFIP除了具备现场总线的一般特点,如数字化,标准化,节省布线,安装方便,维护性好,互换性和互操作性等,还具有很多独到之处,如协议单一、抗干扰能力强、带调度的总线访问控制等。
2.实时操作系统μC/OS-Ⅱ简介
嵌入式实时操作系统μC/OS-Ⅱ是基于优先级的抢占式实时多任务操作系统,其内核可以简单地看做是一个多任务的调度器,在这个任务调度器之上完善并添加了和实时多任务操作系统相关的一些系统服务,如任务管理、时间管理、信号量、邮箱等。它的90%的代码都是用C语言编写的,因此μC/OS-Ⅱ具有良好的可移植性。移植工作的绝大部分都集中在多任务切换的实现上,由于这部分代码主要是用来保存和恢复处理器现场(即相关寄存器),与处理器硬件相关,因此只能使用特定的处理器汇编语言完成。在本文嵌入式网关的开发过程中,以开源TCP/IP协议栈LwIP为基础,实现了μC/OS-Ⅱ的网络支持功能。LwIP实现了 TCP/IP的主要协议如TCP、UDP、ICMP、IP、ARP,其优点是在保持TCP协议主要功能的基础上减少对资源的占用,使得LwIP协议栈非常适合在低端嵌入式系统中使用。
三、嵌入式网关的设计
本设计所设计的嵌入式网关的主要功能是实现WorldFIP现场总线和Internet之间的相互通信时的协议转换,从而实现工业控制中管理层和现场控制层之间的无缝结合,使得WorldFIP现场总线成为一个真正开放的工业控制网络。控制系统模型如图1所示。
网关的设计过程中,自上而下分为三层:应用/任务层、操作系统层和硬件层。分层结构如图2所示。
(一)硬件层设计
硬件层设计主要包括选择合适的芯片及进行原理设计。在本设计中,选用Samsung公司的ARM 32bits芯片s3c451Ob微处理器,它的运行速率为5OMHz,带有Ethernet控制器;网卡芯片使用ICS1890;WorldFIP侧选用的芯片是符合WorldFIP现场总线协议的专用芯片(Fullfip2、FIPDUAL等);CPLD选用ALTERA公司的EPM7128SLC84,主要用于s3c4510b和Fullfip2之间的时序匹配和逻辑组合;SDRAM选用Hynix的HY57v64323220,主要用于任务堆栈和中间变量;flash选用SST的39VF169,主要用于存放程序;SRAM选用ISSI的IC61C1024,主要用于存放Fullfip2的微码和中间变量。硬件结构如图3所示。
(二)操作系统层设计
操作系统层设计的主要内容是在系统硬件平台上构建一个工作平台,在这个平台上建立应用程序、创建任务等。操作系统层设计主要包括三部分:
(1) 嵌入式实时操作系统μC/OS-Ⅱ的移植;
(2) TCP/IP协议栈LwIP的移植;
(3)网络设备驱动程序。
协议栈的移植
为了使WorldFIP现场总线可以和Internet通信,在设计中必须把TCP/IP协议栈移植到μC/OS-Ⅱ操作系统中(μC/OS-Ⅱ移植将在后面介绍)。为了节省对系统资源的占用,设计中选用了开源的嵌入式TCP/IP协议栈——LwIP。LwIP协议栈是一个轻量的TCP/IP协议栈,一般只需要几十千字节的RAM和40K左右的ROM就可以运行,这使LwIP协议栈适合在低端嵌入式系统中使用。LwIP的源码可以到相关的网站上下载,目前的版本是0.6.3。LwIP的特性如下:
(1)支持多网络接口下的IP转发;
(2)提供专门的内部回调接口(Raw API)用于提高应用程序性能;
(3)可选择的Berkeley接API。
为了方便移植,LwIP协议栈把所有与硬件、OS、编译器相关的部分单独放在/src/arch目录下,因此移植时只需修改这个目录下的文件即可,其他源文件一般不需要修改。在移植的过程中,最主要的是和OS相关的一些函数和结构,这些函数和结构在arch目录下的sys_arch.h和sys_arch.c中定义和实现。主要有:
(l)sys_arch_timeout函数。
这个函数描述的是LwIP中与外界网络连接的线程等待超时timeout属性,该属性与OS相关。每个线程的timeout属性对应着一个在sys.h中定义的结构体sys_timeout,我们所要实现的是如下函数:
struct sys_timeouts * sys_arch_timeouts(void)
该函数的功能是返回目前正处于运行态的线程所对应的timeout队列指针。
(2)sys_sem_t信号量。
在LwIP中用信号量来实现同步,因此需要在sys_arch中实现sys_sem_t结构体和处理函数:
typedef OS_EVENT * sys_sem_t;
sys_sem_t sys_sem_new (lNT8U count);
void sys_sem_signal (sys_sem_t sem);
INTl6U sys_arch_sem_wait (sys_sem_t sem,INTl6U timeout);
void sys_sem_free (sys_sem_t sem);
μC/OS-Ⅱ已经实现了信号量的各种操作,而且功能和LwIP中的几个函数完全一样的,所以在LwIP中只需把μC/OS-Ⅱ中的函数封装打包。
(3) sys_mbox_t消息。
在LwIP中实现缓冲、数据传递要用到消息队列,所以在sys_arch中实现sys_mbox_t结构体和处理函数:
typedef struct
{
OS_EVENT * pQ;
void * pvQEntries [MAX _ QUEUE _ EN_ TRIES];
} sys_mbox_t;
sys_mbox_t;
sys_mbox_t sys_mbox_new (void);
void sys_mbox_post(sys_mbox_t mbox,void * msg);
INT16U sys_arch_mbox_fetch (sys_mbox_tmbox,void * * msg,INT16U timeout);
void sys_mbox_free (sys_mbox_t mbox);
在μC/OS-Ⅱ中同样实现了消息队列结构OSQ及其相应操作函数,但在μC/OS-Ⅱ并没有对消息队列中的消息进行管理,因此不能直接使用,所以sys_mbox_t结构定义如上面所述。对队列本身的管理利用μC/OS-Ⅱ的OSQ操作完成;对消息的创建、使用、删除回收使用μC/OS-Ⅱ中的内存管理模块实现,两部分综合起来形成了LwIP的消息队列功能。
2.网络设备驱动程序
驱动程序主要包括:
(1) 初始化CPU芯片s3c4510b的ethernet controller的寄存器和PHY芯片ICS1890的控制寄存器;
(2) 中断处理函数;
(3) 接收数据包和发送数据包处理函数。
四、μC/OS-Ⅱ在s3c4510b上的移值
μC/OS-Ⅱ是一个开源的RTOS,其主要工作流程如图4所示。任务切换的核心是利用出栈指令将每个任务的工作现场恢复,并且调整PC指针来完成任务的切换。μC/OS-Ⅱ移值的关键就是如何构造任务堆栈及切换任务时的出栈顺序。在这个流程图中我们可以看到在移值过程中需编写的几个子程序的位置。
由图4的工作流程可以看出要实现μC/OS-Ⅱ的移值,必须对系统源码做必要的修改,主要集中在以下三个文件中。
(一) OS CPU.H文件
OS_CPU.H中包括了用#define定义的与处理器相关的常量、宏、数据类型、堆栈单位和堆栈增长方向。
1.数据类型定义
不同的处理器有不同的字长,而且数据字长和所用的编译器也有关系,因此在移植中要定义一些数据类型。在本网关的设计中所用的编译器是开源的GNU下的arm-elf-gCC,用户需要做的就是查看所用的编译器手册,确定对应于μC/OS-Ⅱ的标准C数据类型。
2.堆栈单位及增长方向
任务堆栈的数据类型必须和CPU的寄存器长度一致,通过为OS_STK声明正确的数据类型来完成的,在本设计中的声明如下:
typedef unsigned int OS_STK;
/ * Each stack entrY is 32_bit wide */
堆栈的增长用OS_STK_GROWTH来设置,OS_STK_GROWTH为O表示堆栈从下向上增长,为1表示堆栈从上向下增长,在本设计中的定义如下:
#define OS_STK_GROWTH l
3.宏定义
宏定义包括开关中断及任务切换的宏定义:
#define OS_ENTER_CRITICAL( )ARMDis-ableInt( )
#define OS_EXIT_CRITICAL( )ARMEna-bleInt( )
#define OS_TASK_SW( )OSCtXSw( )
(二)OS_CPU_C.C
OS_CPU_C.C主要包括任务堆栈的初始化,及系统钩子函数的定义。在这个文件中包括以下6个函数:
OSTaskStkInit( )
OSSTaskCreateHook( )
OSTaskDelHook( )
OSTaskSwHook( )
OSTaskStatHook( )
OSTimeTickHook( )
其中5个HOOK函数如果没有特殊要求,只需声明为空函数,必须修改的是OSTaskStkInit ( )。 OSTaskStkInit( )的实现如下:
void * OSTaskStkInit (void (* task) (void * pd),void * pdata,void * ptos,INTl6Uopt)
{
unsigned int * stk;
opt=opt;
stk= (unsigned int * ) Ptos;
*--stk= (unsigned int) task;
*- -stk=0;
*--stk=0,
*--stk=0,
*--stk=0,
*--stk=0;
*--stk=0;
*--stk=0;
*--stk=0;
*--stk=0;
*--stk=0;
*--stk=0;
*--stk=0;
*--stk=0 (unsigned int) Pdata;
*--stk= (SVCMODE | OxO);
*--stk= (SVCMODE | OxO);
return( (void*) stk);
在 OSTaskCreate( )或 OSTaskCreateEXt( )中调用该函数来初始化任务的堆栈,返回堆栈指针STK。
(三)OS_CPU_A.S
μC/OS-Ⅱ的移植工作的重点和难点主要体现在O_CPU_A.S文件的实现上,主要编写下面四个汇编语言函数:
OSStanHighRdy( )
OSCtxSw( )
OSIntCtxSw( )
OSTickISR( )
其中最困难的工作又集申在OSIntCtxSw( )和OSTickISR( )两个函数,因为这两个函数不仅和相关硬件定时器、中断积存器的设置有关,而且集中体现了移植的思路。下面分别介绍(具体代码限于篇幅不详细列出)。
tHighRdy( )
此函数只在多任务启动时由OSStart( )函数调用一次,功能是从最高优先级任务的TCB中得到该任务的堆栈指针sp,并用该指针恢复CPU现场,使得最高优先级任务处于就绪状态。
w( )
该函数实现了任务级的任务切换,流程如图4所示。任务级的任务切换是通过发软中断指令来完成的,软中断矢量指向该函数。在μC/OS-Ⅱ系统中,如果一个任务调用了某个函数而使得更高优先级任务进入了就绪状态,系统就会通过发SWI来找到OSCtxSw( );在该任务的最后,系统会调用OSShed( )将最高优先级任务的地址加载到OSTCBHighRdy中,再调用OS_TASK_SW( )来执行软中断调用OSCtxSw( )进行任务切换。
3.0SIntCtxSw( )
该函数实现了中断级任务切换,流程如图4(b)所示。在ISR中,可能会引起任务的切换,如果需要进行任务切换,则在ISR的最后通过0S_IntEXit( )调用OSIntCtxSw( )实现中断级任务切换。此函数和OSCtxSw( )函数类似,不同的是该函数进行的是中断级任务切换,在系统进入中断时,CPU现场己被保存过了,因此在该函数中不再进行CPU现场保护,只需对堆栈指针做相应的调整即可。
ISR( )
该函数用来实现定时器时钟中断处理,但在本系统的设计过程中,把定时器中断设为IRQ中断模式,并且IRQ中断包括好多中断源。因引,把该函数作为IRQ中断的ISR,再根据中断号来调用具体的各个中断服务程序(如定时器中断、MAC中断等)。
另外,在系统的启动文件中,需要建立每种异常的异常向量表,并强制ARM处理器状态位svc管理模式。
五、总 结
本设计在s3c451Ob的硬件平台上成功移植了μC/OS-Ⅱ,并且移植了嵌入式TCP/IP协议栈LwIP,实现了WorldFIP现场总线和Internet之间的相互通信,使得WorldFIP现场总线成为真正开放的工业现场总线。在网关的整体设计中,由于系统比较大,任务数较多,用实时内核来管理调度这些任务,要消耗较多的CPU使用率和系统的内存,具有一定的局限性,但在CPU足够快,内存足够大的情况下,使用嵌入式网关设计,在保证了工业现场总线的实时性和可靠性的基础上,更有利于系统的集成度和再次开发。