历史上的今天
返回首页

历史上的今天

今天是:2025年02月13日(星期四)

正在发生

2018年02月13日 | 在嵌入式实时操作系统uC/0SII上移植实现LwIP这套TCP/IP协议栈

2018-02-13 来源:eefocus

    内容摘要:结合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位MCU ez80和32位MCU ARM7TDMI上的实现为例进行说明。

    需要说明的是,笔者使用的ez80系统是Zilog公司的ez80190开发板,自带网络芯片。而ARM7系统是使用笔者参与开发的Skyeye,一个基于GDB的ARM7TDMI指令级软件仿真器。Skyeye小组最近为Skyeye加上了软件模拟的Ne2k兼容网络芯片,可以运行带网络支持的μcLinux和uC/0S II。以下的全部相关程序和代码都可以在Skyeye网站(hpclab.cs.tsinghua.edu.cn/~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 Computer Science)的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 short u16_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协议都已正确运行。


推荐阅读

史海拾趣

绿宝石(BERYL)公司的发展小趣事

绿宝石公司注重品牌建设和形象提升。公司加大了广告宣传的投入力度,通过电视、网络、户外广告等多种渠道进行品牌推广。此外,绿宝石公司还积极参与社会公益活动,履行企业社会责任。这些举措不仅提升了公司的知名度和美誉度,也增强了消费者对绿宝石品牌的认同感和忠诚度。

Electromagnetic Industries Llp公司的发展小趣事

作为一家有社会责任感的企业,EMI公司不仅关注经济效益的增长,还积极履行社会责任。公司积极参与各种公益活动和社会救助行动,为灾区捐款捐物、支持教育事业等。同时,公司还注重环保和可持续发展,通过引进环保技术和设备、加强废弃物处理等措施降低生产过程中的环境污染。这些行动展现了EMI公司的良好企业形象和社会责任感。

Aplus Integrated Circuits公司的发展小趣事

Aplus Integrated Circuits公司在成立之初,便专注于集成电路的研发与生产。公司创始人凭借在半导体行业多年的积累,带领团队攻克了一系列技术难题,成功研发出具有高性能、低功耗特点的集成电路产品。这一技术突破为公司赢得了市场的认可,奠定了其在行业内的地位。

CONTTEK Group GmbH公司的发展小趣事

随着市场竞争的加剧,CONTTEK Group GmbH公司意识到只有不断创新才能在行业中立足。因此,公司加大了对研发的投入,积极引进新技术、新材料和新工艺,不断推动产品的升级换代。通过持续的技术创新,公司成功推出了一系列具有创新性和竞争力的新产品,进一步巩固了其在电子连接器领域的领先地位。

Diodes Incorporated公司的发展小趣事

值得一提的是,Diodes Incorporated还荣获了亚洲金选奖殊荣。公司的超高功率密度(UHPD)充电器解决方案在亚洲金选奖中获得了金选节能系统功率半导体供货商项目公司奖。这一奖项的获得,不仅彰显了公司在节能系统功率半导体产品领域的领先地位,也进一步提升了公司的品牌形象和市场竞争力。

以上五个故事概述了Diodes Incorporated公司在电子行业中发展起来的相关事实。通过这些故事,我们可以看到公司在技术创新、市场拓展、财务表现以及品牌建设等方面所取得的成就和进步。

Artaflex公司的发展小趣事

随着电子行业的快速发展,市场竞争日益激烈。为了保持领先地位,Artaflex公司积极寻求与其他企业的技术合作。通过与一家知名半导体公司的合作,双方共同研发了一款新型芯片,不仅提升了产品的性能,还降低了生产成本。这一合作不仅增强了Artaflex的技术实力,也为其打开了更广阔的市场空间。

问答坊 | AI 解惑

新手求助,关于CCS编译出错

大家好,最近我主要在尝试把UCOS-II移植到DSP C6701平台上,使用的是UCOS-II V2.52的源代码,在创建工程之后直接将一些代码添加了进去,然后再用CCS进行编译,然后出现了N多对源代码报错的情况 大致都是这样的: OS_CORE.C 部分源代码:   ...…

查看全部问答>

pc硬件:键盘识别问题?

        当usb键盘插入pc上时,pc将其识别为一个usb键盘,而并非其他usb设备,应该是在电脑硬件上,事先注册了一些键值,当usb键盘插入电脑上时,电脑就将其识别为一个键盘。     请问这关键的键值是注册在哪里的 ...…

查看全部问答>

菜鸟学习笔记3---定时器,中断

前面一直用直接读写寄存器的方法编程,需要经常翻lm3s8962.h来查询宏名称,非常不方便,而且代码不易维护,下面我们学习用TI提供的driverlib来写程序。这个driverlib不仅可以用在8962上,也可以用在其他流明系列ARM上。驱动库的代码全部存放在Stell ...…

查看全部问答>

求助!万利199学习板,插入SD卡后,不能正常调试

求助!万利199学习板,插入SD卡后,调试时can\'t halt the core,不插入SD卡则能正常调试调试环境 MDK3.5 调试工具 自带的ST_LINK II …

查看全部问答>

忘了在STVP下怎么给STM8加密了

                                 STVP下给STM8S下程序很容易,但是没看到在哪块儿加密呀?…

查看全部问答>

智能电力巡线仪

详细的东西还没有啦…

查看全部问答>

usbasp下载器给AT89S52最小系统供电-无法进入编程模式

我在面包板上搭建了个AT89S52的最小系统,如下图,画线的表示实际导线的连接(REST电路没有标出来)。 USBASP编程器(驱动安装正常)接上后点progisp1.7智峰下载软件上的RD按钮(Read)出现错误 “进入编程模式失败.....” 同样的这块芯片和这个u ...…

查看全部问答>

谁有标准噪声文件啊? 求分享!!!谢谢!!

谁有标准噪声文件啊?  求分享!!!谢谢!!…

查看全部问答>

大神们帮忙

MAX232直接接12v  的电压会不会有什么问题 …

查看全部问答>