标题：多重网络
多重网络
多重网络是指包括使用各不相同的通信媒体的一个以上的网络群组，
对网络群组内存在的多个家电器件进行整合控制的网络管理器；将从各网络群组接收的数据进行转换并传送给网络管理器，同时将从网络管理器接收的数据转换为适合于各网络群组的数据并进行传送，从而在使用各不相同的通信媒体的网络群组之间实现通信连接的通信媒体转换器。
多重网络——现场设备网络
在现场网络化领域一直没有找到具有连贯性或稳定性的解决方法。当我们还刚刚认为现场网络工作不可能稳定的时候，2004年和2005年基于以太网的网络化发生了令人难以置信的发展。
搭建多重网络设备应该是2005年最实际的设备网络化方案了。系统要保证legacy串口与CAN网的兼容性，同时提供基于CAN网程序的网络服务器接口。基于浏览器安装，能发现并解决问题以及在线文档都可以系统自身解决。具体方法如下：  ?一、多重网络的硬件平台要建设多重网络，需要许多战略设备，包括子卡、CAN外部控制器和网关。一种新型的可行方法是选择内嵌以太网和CAN控制器的处理平台。这些处理器将串口、CAN和以太网的信号连接一个信号包，使其价位相对于离线以太网或CAN解决方案来说更具竞争力。
来自Freescale半导体的Cold Fire系列32位处理器是双重网络控制器的典型代表。它们多数都将以太网和CAN控制器整合到同一模型中，其中有些内嵌了闪存。Freescale的MCF523x处理器系列是下一代ColdFire嵌入式存储器的典型。二、RTOS与TCP/IP协议栈实时操作系统RTOS是分配CPU完成控制与通信任务的嵌入式系统。在讨论RTOS之前，很有必要说明一下许多系统放弃在现场网络中使用RTOS的两个原因：花费太高而且产品简单是他们不使用RTOS的原因。但是像Freescale的ColdFire产品这样的高级处理器没有RTOS是不能实现的，所以2005年很少会提及它的花费——这是人们放弃它的主要原因。事实上，喜爱使用RTOS方案的用户了解真实的价格状况，而非RTOS方法会隐瞒其价格。首先，任何以太网方案都需要TCP/IP协议栈。不要小看这个免费的协议栈，其实在进出的时候总是收费的。当购买了TCP/IP协议栈后，你会发现它的花费未必少，甚至更多，这是因为其实RTOS中包含TCP/IP协议栈。第二，没有RTOS系统，设计者经常需要执行一些内部日程安排表。没有RTOS的系统实际都是比较复杂的，他们使用自产的RTOS来替代，这会造成庞大的后续支出。随着时间的推移，想在自产操作系统中添加新特性变得十分困难，这是由于它不易改变的性质所造成的。结果没有RTOS方案的花费会超过产品的生命周期。
缺少复杂性是RTOS不被采用的另一个原因。本文中，我们讨论的是包含以太网、CAN以及一个或更多串行通信端口的多重网络系统。从定义上来说，拥有这些网络接口的系统本身就是复杂的，并且需要RTOS对网络接口的复杂性和完全服务进行管理。如果系统可以提供DeviceNet、以太网/IP和Modbus TCP通信的话，系统是不可能简单的。
多数加强控制程序（包括多数控制部件移动的系统）需在三种要计算的情况下分配CPU资源：高级控制算法、低级控制功能和网络连接。高级控制算法是计算程序如何被控制的，这些算法是十分复杂的，需要占用大量的内存和CPU资源。低级控制功能（比如定时功能）似乎十分简单，但是需要较长的等待时间。为了保证服务，低级控制功能很可能干扰信号，而这些由低级控制功能造成的中断干扰将会影响高级控制算法的执行与表现。
ColdFire处理器支持将高级控制算法和低级控制功能集成于统一块新品上。当ColdFire 的CPU执行RTOS和高级控制算法时，将会启用名为eTPU的协处理器（内嵌定时处理单元）来处理所有定时干扰并完成低级控制功能。低级控制功能不会受到转换带来的影响，同时高级控制也不会被高速干扰中断。
相比而言，其他多数处理器没有处理低延迟干扰的资源，因此必须占用CPU资源来满足实时要求。协处理器eTPU解决了核心问题，使ColdFire CPU可以专心解决高级控制代码。三、选择网络应用层协议不管是否选择RTOS处理器平台，多重网络系统都需要一套网络协议。这些软件可以处理位数据与字节数据的转换，并通过网络将数据传送到相应的程序上去。对于现场网络来说，只需要一小套应用层协议。总体来看有以下几个协议。
Modbus（串口）。Modbus是所有网路通信最早的标准，而且是列表中唯一的串口通信标准。每个设备都是由寄存器（16位无符号整数）和线圈（位）组成的。协议是由一套由简练信息帧构成的指令集。设计者根据Modbus的容量执行串口驱动程序，所以从这些新的遗留设备中发送或接受的信号仍然十分重要。
DeviceNet（CAN）。DeviceNet是当今美国最重要的输入/输出（I/O）协议，是基于CAN技术的应用层协议。CAN支持单总线降压拓扑结构。尽管CAN可以支持数千个结点并使数据的传输速率达到1M波特，但是DeviceNet会将网络结点数限制为64个，波特率控制为125K、250K和500K。DeviceNet是以主从关系为连接基础的网络，主站设备向从站发出请求连接，可为非控数据和输入输出数据提供协议连接。一旦建立了输入输出连接，主站可通过论询、循环或逢变则报通信模式与从站进行连接。
CANopen（CAN）。CANopen是另一个CAN应用层协议。不同的是DeviceNet更具连接导向，而CANopen更具信息导向。CANopen支持两类基础信息：服务数据信息和进程数据信息。服务数据信息是大型低优先级信息，而进程数据信息是小型高优先级数据。CANopen不是基于连接的，而是支持对等信息的协议。在量产模式下，CANopen设备只负责生产数据，而不关心谁去使用它。CANopen被欧洲广泛接受，在美国的认可度也在提升。
EtherNet/IP。EtherNet/IP是应用于ControlNet和DeviceNet的控制与信息协议（CIP）技术的升级版。网络中每个设备都被视为一系列对象的属性值。EtherNet/IP使用TCP（传输控制协议）来传递信息数据，用UDP（非连接设备协议）来传递输入输出数据。EtherNet/IP是以太网与Rockwell自动化现场设备的通信标准。
Modbus/TCP。Modbus/TCP就是应用在TCP/IP之上的Modbus。Modbus/TCP使用与Modbus有相同属性和功能的寄存器（16位无符号整数）和线圈（位）。除了Modbus/TCP的信息中不包含循环冗余检查字段（CRC）外，Modbus和Modbus/TCP的所有信息都是一样的。由于Modbus很简单，它可以在现场设备系统中广泛实现。四、高效创建多重网络系统多重网络系统需要附加硬件组件和分层软件模块来提供超过产品生命周期的可靠的、可扩展系统。完成这个任务的一个办法是选择多厂家的物理接口、RTOS系统、TCP/IP栈和应用层协议。但当是单网络系统时，这个方法是很危险的，其危险性甚至超过使用多物理接口和多重应用层协议来处理多重网络。
一个更加高效、危险性小的方法的是选择同一厂家提供的整合了所有物理接口、RTOS系统和应用层协议的包。比如Freescale的联合设计项目（DAP），他们与ROTS和网络软件厂家结成战略伙伴，为用户提供了大量选择。这样设计的多重网络系统可以选择任意DAP项目厂家的软件模块，这些软件模块已经被整合到Freescale的硬件平台上。
不管你喜欢哪个厂家的芯片产品，如果你想建设多重网络平台，那就应该选择高集成度的平台，这样不但能减少危险，降低消耗和资源使用还能节约你进入市场所需的时间。
实时操作系统uC/0S II下TCP/IP协议栈的实现
摘要：结合ez80和ARM7两种系统上的具体实现，说明了如何在嵌入式实时操作系统uC/0SII上移植实现LwIP这套TCP/IP协议栈，使uC/0S II成为支持网络的RTOS。
1 引言
随着嵌入式系统与网络的日益结合，在嵌入式实时操作系统中引入TCP/IP协议栈，以支持嵌入式设备接入网络，成为嵌入式领域重要的研究方向。uC/0S II是近年来发展迅速的一个开放源码实时操作系统，但它只是一个实时的任务调度及通信内核，缺少对外围设备和接口的支持，如没有文件系统、网络协议、图形界面。笔者在多个嵌入式项目的开发过程中，以开源TCP/IP协议栈LwIP为基础，给uC/0S II加上了网络支持。下面就以uC/0S II +LwIP分别在8位MCUez80和32位MCU ARM7TDMI上的实现为例进行说明。
需要说明的是，笔者使用的ez80系统是Zilog公司的ez80190开发板，自带网络芯片。而ARM7系统是使用笔者参与开发的Skyeye，一个基于GDB的ARM7TDMI指令级软件仿真器。Skyeye小组最近为Skyeye加上了软件模拟的Ne2k兼容网络芯片，可以运行带网络支持的μcLinux和uC/0S II。以下的全部相关程序和代码都可以在Skyeye网站下载。
2 基于uC/0S II的网络平台概述
嵌入式操作系统uC/0S II是一个公开源代码的占先式多任务的微内核RTOS，其性能和安全性可以与商业产品竞争。uC/0S II的特点可以概括为以下几个方面：公开源代码，代码结构清晰、明了，注释详尽，组织有条理，可移植性好。可裁剪，可固化。内核属于抢占式，最多可以管理60个任务。uC/0S II自1992年的第一版（uC/0S）以来已经有好几百个应用，是一个经实践证明好用且稳定可靠的内核。目前国内对uC/0S II的研究和应用都很多。
TCP/IP是Internet的基本协议，以其实用性、高效性已经成为事实上的工业标准。嵌入式设备要与Internet网络直接交换信息，就必须支持TCP/IP协议。目前嵌入式设备上TCP/IP方案有很多种，但面向低端应用的开源嵌入式网络平台还很少见。
uC/0S II是一个富有开放色彩的RTOS，只要买一本书就可获得源代码，对学校和教育的使用完全免费，商业应用的费用相对也很低。但是它目前的一些第三方TCP/IP支持都是完全商业化的，用户需要付费才能获得，很少给出源代码，这影响了uC/0S II的研究和推广。通过把开放源代码的TCP/IP协议栈LwIP移植到uC/0S II上来，就获得了一套可免费研究、学习的嵌入式网络软件平台。
3 开源TCP/IP协议栈LwIP简介
LwIP是瑞士计算机科学院（Swedish Institute of ComputerScience）的Adam Dunkels等开发的一套用于嵌入式系统的开放源代码TCP/IP协议栈。LwIP的含义是Light Weight(轻型)IP协议。LwIP可以移植到操作系统上，也可以在无操作系统的情况下独立运行。LwIP TCP/IP实现的重点是在保持TCP协议主要功能的基础上减少对RAM的占用，一般它只需要几十K的RAM和40K左右的ROM就可以运行，这使LwIP协议栈适合在低端嵌入式系统中使用。
LwIP的特性如下：
(1) 支持多网络接口下的IP转发
(2) 支持ICMP协议
(3) 包括实验性扩展的的UDP（用户数据报协议）
(4) 包括阻塞控制，RTT估算和快速恢复和快速转发的TCP（传输控制协议）  ?
(5) 提供专门的内部回调接口（Raw API）用于提高应用程序性能
(6) 可选择的Berkeley接口API（多线程情况下）
我们目前使用的是LwIP的最新稳定版V0.5.3。有关LwIP的详细内容，可以参考其代码和网站上的文档。
4 LwIP在uC/0S II下的实现
4.1 概述
LwIP协议栈在设计时就考虑到了将来的移植问题，因此把所有与硬件、OS、编译器相关的部份独立出来，放在/src/arch目录下。因此LwIP在uC/0S II上的实现就是修改这个目录下的文件，其它的文件一般不应该修改。下面分几部份分别说明相应文件的实现原理和过程。具体的代码限于篇幅没有给出，Skyeye网站上有完整的代码和说明。
4.2 与CPU或编译器相关的include文件
/src/arch/include/arch目录下cc.h、cpu.h、perf.h中有一些与CPU或编译器相关的定义，如数据长度，字的高低位顺序等。这应该与用户实现uC/0S II时定义的数据长度等参数是一致的。
#define BYTE_ORDER LITTLE_ENDIAN //ARM7默认为小端存储系统
//数据类型长度的定义
typedef unsigned char u8_t;
typedef signed char s8_t;
typedef unsigned shortu16_t;
typedef signed short s16_t;
typedef unsigned int u32_t;
typedef signed int s32_t;
此外还有一点：一般情况下C语言的结构体struct是4字节对齐的，但是在处理数据包的时候，LwIP使用的是通过结构体中不同数据的长度来读取相应的数据的，所以，一定要在定义struct的时候使用_packed关键字，让编译器放弃struct的字节对齐。LwIP也考虑到了这个问题，所以，在它的结构体定义中有几个PACKED_FIELD_xxx宏，默认的时候这几个宏都是空的，可以在移植的时候添加不同的编译器所对应的_packed关键字。比如在Skyeye（ARM7）上对应gcc编译器的定义：
#define PACK_STRUCT_FIELD(x) x __attribute__((packed))
#define PACK_STRUCT_STRUCT __attribute__((packed))
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_END
4.3 sys_arch操作系统相关部份
sys_arch.[ch]中的内容是与OS相关的一些结构和函数，主要可以分为四个部份：
(1) sys_sem_t 信号量
LwIP中需要使用信号量通信，所以在sys_arch中应实现信号量结构体和处理函数：
struct sys_sem_t
sys_sem_new（） //创建一个信号量结构
sys_ sem _free（） //释放一个信号量结构
sys_ sem _signal（） //发送信号量
sys_ arch_sem _wait（） //请求信号量
由于uC/0SII已经实现了信号量OS_EVENT的各种操作，并且功能和LwIP上面几个函数的目的功能是完全一样的，所以只要把uC/0SII的函数重新包装成上面的函数，就可以直接使用了。
(2) sys_mbox_t 消息
LwIP使用消息队列来缓冲、传递数据报文，因此要在sys_arch中实现消息队列结构sys_mbox_t，以及相应的操作函数：
sys_mbox_new（） //创建一个消息队列
sys_mbox_free（） //释放一个消息队列
sys_mbox_post（） //向消息队列发送消息
sys_arch_mbox_fetch（） //从消息队列中获取消息
uC/0SII同样实现了消息队列结构OSQ及其操作，但是uC/0SII没有对消息队列中的消息进行管理，因此不能直接使用，必须在uC/0SII的基础上重新实现。为了实现对消息的管理，我们定义了以下结构：
typedef struct {
OS_EVENT* pQ;
void* pvQEntries[MAX_QUEUE_ENTRIES];
} sys_mbox_t；
在以上结构中，包括OS_EVENT类型的队列指针（pQ）和队列内的消息（pvQEntries）两部分，对队列本身的管理利用uC/0SII自己的OSQ操作完成，然后使用uC/0SII中的内存管理模块实现对消息的创建、使用、删除回收，两部分综合起来形成了LwIP的消息队列功能。
(3) sys_arch_timeout 函数
LwIP中每个与外界网络连接的线程都有自己的timeout属性，即等待超时时间。这个属性表现为每个线程都对应一个sys_timeout结构体队列，包括这个线程的timeout时间长度，以及超时后应调用的timeout函数，该函数会做一些释放连接，回收资源的工作。如果一个线程对应的sys_timeout为空（NULL），说明该线程对连接做永久的等待。
timeout结构体已经由LwIP自己在sys.h中定义好了，而且对结构体队列的数据操作也由LwIP负责，我们所要实现的是如下函数：
struct sys_timeouts * sys_arch_timeouts(void)
这个函数的功能是返回目前正处于运行态的线程所对应的timeout队列指针。timeout队列属于线程的属性，因此是OS相关的函数，只能由用户实现。
(4) sys_thread_new 创建新线程
LwIP可以是单线程运行，即只有一个tcpip线程（tcpip_thread），负责处理所有的tcp/ucp连接，各种网络程序都通过tcpip线程与网络交互。但LwIP也可以多线程运行，以提高效率，降低编程复杂度。这时就需要用户实现创建新线程的函数：
void sys_thread_new(void (* thread)(void *arg), void *arg);
在uC/0S II中，没有线程（thread）的概念，只有任务（Task）。它已经提供了创建新任务的系统API调用OSTaskCreate，因此只要把OSTaskCreate封装一下，就可以实现sys_thread_new。需要注意的是LwIP中的thread并没有uC/0S II中优先级的概念，实现时要由用户事先为LwIP中创建的线程分配好优先级。
4.4 lib_arch中库函数的实现
LwIP协议栈中用到了8个外部函数，这些函数通常与用户使用的系统或编译器有关，因此留给用户自己实现。如下：
u16_t htons(u16_t n); //16位数据高低字节交换
u16_t ntohs(u16_t n);
u32_t htonl(u32_t n); //32位数据大小头对调
u32_t ntohl(u32_t n);
int strlen(const char *str); //返回字符串长度
int strncmp(const char *str1, const char *str2, int len); //字符串比较
void bcopy(const void *src, void *dest, int len); //内存数据块之间的互相拷贝
void bzero(void *data, int n); //内存中指定长度的数据块清零
前四个函数通常由用户自己实现。Skyeye（ARM7）中，由于使用了gcc编译器，gcc的lib库里已经有了后四个函数。而ez80的编译器函数库中缺少bcopy和bzero两个，需要自己编写。用户在其它CPU上实现时应根据自己的编译器来决定。
4.5 网络设备驱动程序
ez80开发板自带的网络芯片为RealTek的8019as芯片，这是ISA 10BASE－T的以太网芯片，与Ne2k兼容。而我们在AT91模拟器Skyeye中所仿真的网络芯片也是Ne2k，所以目前实现的网络设备驱动是针对Ne2k的，其它类型的网络芯片驱动可以在LwIP的网站上找到。LwIP的网络驱动有一定的模型，/src/netif/ethernetif.c 文件即为驱动的模板，用户为自己的网络设备实现驱动时应参照此模板。
在LwIP中可以有多个网络接口，每个网络接口都对应了一个struct netif，这个netif包含了相应网络接口的属性、收发函数。LwIP调用netif的方法netif->input()及netif->output()进行以太网packet的收、发等操作。在驱动中主要做的，就是实现网络接口的收、发、初始化以及中断处理函数。驱动程序工作在IP协议模型的网络接口层，它提供给上层（IP层）的接口函数如下：
//网卡初始化函数
void ethernetif_init(struct netif *netif)
//网卡接收函数，从网络接口接收以太网数据包并把其中的IP报文向IP层发送
//在中断方式下由网卡ISR调用
void ethernetif_input(struct netif *netif)
//网卡发送函数，给IP层传过来的IP报文加上以太网包头并通过网络接口发送
err_t ethernetif_output(struct netif *netif, struct pbuf *p, struct ip_addr *ipaddr)
//网卡中断处理函数ISR
void ethernetif_isr(void);
以上的函数都可以分为协议栈本身的处理和对网络接口硬件的操作两部份，但硬件操作是对上层屏蔽的，具体参见RTL8019as、DM9008等Ne2k网络芯片的数据手册。驱动程序可以到Skyeye或LwIP的网站下载。
5 应用实例的建立和测试
做完上面的移植修改工作以后，就可以在uC/0SII中初始化LwIP，并创建TCP或UDP任务进行测试了。这部份完全是C语言的实现，因此这部份在ez80和ARM7上基本都是一样的。值得注意的是LwIP的初始化必须在uC/0SII完全启动之后也就是在任务中进行，因为它的初始化用到了信号量等OS相关的操作。关键部份的代码和说明如下：
main(){
OSInit();
OSTaskCreate(lwip_init_task, &LineNo11, &lwip_init_stk[TASK_STK_SIZE-1], 0);
OSTaskCreate(usr_task,&LineNo12,&usr_stk[TASK_STK_SIZE-1],1);
OSStart();
}
主程序中创建了lwip_init_task初始化LwIP任务（优先级0）和usr_task用户任务（优先级1）。lwip_init_task任务中除了初始化硬件时钟和LwIP之外，还创建了tcpip_thread（优先级5）和tcpecho_thread（优先级6）。实际上tcpip_thread才是LwIP的主线程，多线程的Berkley API也是基于这个线程实现的，即上面的tcpecho_thread线程也要依靠tcpip_thread线程来与外界通信，这样做的好处是编程简单，结构清晰。
实用Berkley API实现的tcpecho_thread是一个TCP echo服务器，*7号端口，程序框架如下：
void tcpecho_thread(void *arg){
conn = netconn_new(NETCONN_TCP); //创建新的连接标识
netconn_bind(conn, NULL, 7); //绑定到7号端口
netconn_listen(conn); //开始*端口
while(1){
newconn = netconn_accept(conn); //接收外部到来的连接
buf = netconn_recv(newconn) //获取数据
……. //处理数据
netconn_write(newconn, data, len, NETCONN_COPY); //发送数据
netconn_delete(newconn); //释放本次连接
}
}
编译运行后，用ping ip地址命令可以得到ICMP reply响应。用telnet ip地址 7（登录7号端口）命令可以看到echo server的回显效果。说明ARP、ICMP、IP、TCP协议都已正确运行