跳至主要内容

【转】感受面向对象编程的魅力(uCosII C++类封装篇)

感受面向对象编程的魅力(uCosII C++类封装篇)
作者:exdata 

      OO思想已经出现了20多年,各种成功的实践告诉我们它是软件开发的必然趋势。面向对象编程(OOP)的一个关键原则之一就是封装(encapsulation),把暴露的数据封装起来,尽可能的让对象管理它们自己的状态,因为过多的依存性会造就紧耦合(highly coupled)系统,使得任何一点小小改动都可能造成许多无法预料的结果。而数据隐藏/封装机制是一个控制对象数据和状态强而有力的方法,它对外部世界隐藏其内部细节,这就意味着每一个对象都应该尽可能少的了解系统的其他部分或者被其他部分所了解,这样一来一旦发生了变化,需要了解这一个变化的对象会比较少,因此变化也就相对容易地进行。
       C++之父Bjarne Stroustrup也曾说过:"你使用一个语言特征是因为你需要它,而不是因为它存在"。设计ucClass(uCosII封装类)的初衷是由于过去很多的实现都是在C++中完成,深切感受到面向对象分析、设计乃至编程的独特魅力。回想微软的MFC1为win32程序设计所带来的便利、DriverWork2在PC硬件的驱动开发的高效,促使完成ucClass的设计(这里头没有什么高深的学问),其实更重要的一点是它能改进现有的设计模式,能让今后的uCosII相关开发及维护变得更加轻松和高效!
       对于OO我也就几年的开发经验,没有做过什么"巨项",对于ARM也是接触时间不长,不过有一个概念一直影响着我,"ARM不就是复杂一点的单片机吗",我想也不应该难到那里去。于是有空就玩玩,对于很多做这行做开发的对C++这样的面向对象编程语言大多不甚了解,要想用OO思想来武装自己又谈何容易啊,这就是一种挑战习惯、挑战思维方式的行为,等于放弃过去曾经为你有效解决难题完成工作的开发模式。在此我也没有什么灵丹妙药可改变这些处境,唯一的建议就是坚持及不断实践、只有这样的思想基础才能提高成功的机会。
       下面简单设计一个ucClass的demo程序(当然会可能隐藏了一些bug or error),目标板使用的是菲利普LPC2100芯片(周立功的EasyARM 2100开发板),有些头文件这里就不贴了,旨在看看主程序的结构及设计过程,就让一些软件的大虾们见笑了。
        若有机会的我还准备写几个基于OO之上的在嵌入式开发的应用demo……

设计需求:
使用ucClass设计一个demo程序完成下列任务:

任务编号                      任务指责和功能描述                        物理资源占用
1    每间隔1秒蜂鸣器短促发声两次,每10秒间隔使用信号量通知任务2,随后自身进入挂起等待状态。    蜂鸣器及IO端口
2    无限等待由任务1触发的信号量,当有信号时引发1秒蜂鸣器长鸣,其后恢复任务1,自身再次进入信号量的无限等待状态中。    蜂鸣器及IO端口
3    孤立任务,简单的一秒间隔led1闪烁。    Led1及IO端口
4    孤立任务,通过检查key1按键状态控制led4的二值状态(亮/灭)。    key1,led4及IO端口

// main.cpp文件,ucClass测试主程序
// exdata 2004-8-18

#include "config.h"            // 一些硬件相关的声明
#include "GPIO.H"            // GPIO类头文件
#include "ucClass.H"        // uCosII的收集类库头文件
/*
// 在浏览demo程序时要注意以下几点:
// 1,带参数的对象构造
// 2,静态函数调用
// 3,带参数的派生类构造
// 4,虚拟函数重载
// 5,特别注意一些头文件(不在此文档中)包含写法及原有C函数声明方法,关注extern "C"
// 6,不拘泥于传统的设计思想(模式)
// 7,比较不使用class时候又是如何处理的?分析每个对象背后隐藏了什么?
*/

/************************** 全局变量 *****************************/

// 定义几个task对象,这是实例化对象,隐藏并实现了任务栈的构造
CTask     t1(128);        // 参数是任务堆栈分配大小(在ARM中以32bit为单位)
CTask     t2(64);
CTask     t3(64);

CSem      t2WaitEvent;        // 定义一个信号量对象用于任务t1、t2通信
CGPIO     buzz("P0.7",1);    // 定义一个GPIO对象,输出模式,用于控制蜂鸣器
/*
// 前置声明几个任务运行函数,这个与C模式下没有什么区别。
// 还保留这种处理方法仅仅为求兼顾一些使用uCosII习惯,建议参考CMyTask的
// 设计及t4的定义,使用子类设计实现具体任务的处理方法要比C模式的的全局
// 孤立启动函数更具有设计上及维护上的优点,当做一个中大型项目时候效果尤
// 为明显。
*/
void t1Run(void *p);
void t2Run(void *p);
void t3Run(void *p);

/*******************************************************************/

/*
// t1任务运行函数。
// 功能:每间隔1秒蜂鸣器短促发声两次,每10秒间隔使用信号量t2WaitEvent通
// 知任务t2,随后自身进入挂起等待状态。
*/
void t1Run(void *p)
{
    TargetInit();        // 初始化硬件目标板
    
    INT32U i = 0;
    
    while(1)
    {
        CuCos::TimeDlyHMSM(0,0,1,0);
        
        if(++i == 10)
        {
            i = 0;
            t2WaitEvent.Post();
            t1.Suspend();
        }
        
        // 蜂鸣器短促发声两次
        buzz.Clear();
        CuCos::TimeDlyHMSM(0,0,0,50);
        buzz.Set();
        CuCos::TimeDlyHMSM(0,0,0,50);
        buzz.Clear();
        CuCos::TimeDlyHMSM(0,0,0,50);
        buzz.Set();
    }
    
    p = p;
}
/*
// t2任务运行函数。
// 功能:无限等待信号量t2WaitEvent,当有信号时引发1秒蜂鸣器长鸣,其后恢
// 复t1任务,任务t2再次自身进入信号量t2WaitEvent的无限等待状态。
*/
void t2Run(void *p)
{
    INT8U err = 0;
    
    while(1)
    {
        t2WaitEvent.Pend(0,&err);

        buzz.Clear();
        CuCos::TimeDlyHMSM(0,0,1,0);
        buzz.Set();
        
        CuCos::TimeDlyHMSM(0,0,0,50);
        
        t1.Resume();
    }
    
    p = p;
}
/*
// t3任务运行函数。
// 功能:简单的一秒间隔led1闪烁。
*/
void t3Run(void *p)
{
    static CGPIO led1("P0.22",1);
    
    while(1)
    {
        led1.Set();
        CuCos::TimeDlyHMSM(0,0,1,0);
        led1.Clear();
        CuCos::TimeDlyHMSM(0,0,1,0);
    }
    
    p = p;
}

/*
// 为求原汁原味体现一个task对象的封装,下面设计一个子类实现一个特定任务
// 的处理,在此我们可以认为一个任务就是一个子系统,通过一个子类表现一个
// 特有任务的具体属性和行为,注意这种设计方法与传统的面向对象编程模式是
// 有区别的。子系统是一个有自主能力主体,例如在MyTask子系统中我们有两
// 个IO资源m_key1和m_led4分别对应着电路板上的按键Key1和发光二极管Led4,
// 通过检查key1按键状态控制led4的二值状态(亮/灭)。重载基类了Run函数为
// 求体现类的封装特性及表现一个任务的内聚能力,它是一个强制的运行接口,
// 当任务启动时便能自动地从重载的Run函数开始运行(具体调用的中转过程在
// 基类中已经实现)。
*/
class CMyTask : public CTask    // 注意这里的派生关系!!!
{
public:
CMyTask(INT32U StkSize) : CTask(StkSize),m_key1("P0.16",0),m_led4("P0.25",1,0)
    {
        // 注意带参数的构造处理方法!!!
    }
    
protected:
    virtual void Run(void *p);    // 需要重载Run()函数实现任务执行
    
private:
// 这是任务用到的一些资源定义,当然添加子系统的其他的属性和方法也是允许的
    CGPIO m_key1;
    CGPIO m_led4;
};
/*
// t4任务运行函数。
// 通过函数重载实现重写Run函数,该函数也就是任务运行函数。
// 功能:通过检查key1按键状态控制led4的二值状态(亮/灭)。
*/
void CMyTask::Run(void *p)                    // Run函数将会被uCosII系统自动运行!
{
    while(1)
    {
        if(m_key1.GetStatus() == 0)            // 判断按键是否按下
        {
            if(m_led4.GetStatus() == 0)
            {
                m_led4.Set();
            }
            else
            {
                m_led4.Clear();
            }
            
            while(m_key1.GetStatus() == 0)
            {
                CuCos::TimeDly(20);            // 等待按键释放
            }
            
        }
        else
        {
            CuCos::TimeDly(10);
        }
    }
    
    p = p;
}
/*
// 注意一般应该把上面的CMyTask定义及实现另行写在其他的文件中!把它写在
// 这里仅仅为求说明方便。
*/

/*-------------------------------------------------------------------------------------------------*/

CMyTask     t4(128);    //注意任务对象t4是由CMyTask实例化的对象

// 看看这个简洁的main函数,这时候您想到了什么?

int main()
{
    CuCos::Init();        // uCosII的系统函数,用于初始化uCosII,等效于OSInit()
    
    t2WaitEvent.Create();        // 创建t2WaitEvent的信号量
    
    // 创建任务(参数指定任务函数启动地址、任务函数参数和优先级)
    t1.Create(t1Run,NULL,1);    
    t2.Create(t2Run,NULL,2);
    t3.Create(t3Run,NULL,3);
    
    t4.Create(NULL,4);            // 创建任务,注意Create函数的重载
    
    CuCos::Start();            // 等效于OSStart()
    
    return 0;
    
}



关于我……:
    我是一个不折不扣硬件的狂热者、焊机派,自会使用圆珠笔的年龄开始就爱上了烙铁,开始玩弄万用表,在高考后足足整理了两箱电路板运回老家,因为要到外地上学我这些家当父母都帮我好好的"封存"。我可以自信及自豪地对那些比我大10岁的硬件工程师说:我焊板子的时候你还不知道什么是电阻电容呢!虽然对硬件有丰富感性认识及解不开的情结,但我现在清楚的明白到一点,就算是天天在长江黄河里泡大的孩子也不是个个都能登上奥运游泳项目的领奖台,近些年硬件的发展也很快,现在自己在硬件上的工作也相对较少,没有什么锻炼的机会,三人行,必有我师,现在身边牛人不少,也是一个学习的机会。对于软件开发我也不是行家(更谈不上什么程序员),比起有十几年开发经验的老前辈还是太嫩!用老江的话说就是too simple、sometimes naive!不过有爱好就可以发烧(在低于0度的室温下对着黑底白字的debug窗口熬上几个夜晚通宵不是一件容易的事情……这样一不小心就回发烧~~~)。以前做51也是从ASM开始,马老和Franklin带给我第一次[硬件+C]的喜悦。在大二的时候就和毕业生混在一起搞毕业设计,现在想起来这分明就是害了人家,不分明是一个抢手么?虽然不是求什么利益,只在乎有发挥和动手的机会,对这些锻炼我乐此不疲,接下来的大学生活都是在项目中度过(错失了很多陪mm花前月下的机会~~~),那时候软硬件都做(专门有中间人联系或者朋友介绍),就大三一年下来的开发报酬剩下来的就大于10K(对于很多工作后的人来说这也算不了什么,明显我也不是一个挥霍的人)。那时候在学校带领着一个学生的科技团体,为学校、组织争得不少得荣誉,这当然也包括为自己。回想过去,更值得怀念得是那些志同道合的朋友们,有师兄、有同学还有师弟和体谅我的学院和老师……,无论是技术还是生活上都不能缺少,有着开心快乐的日子……!现在在一家公司做开发,软件硬件也不怎么分了,自从做了一个硬件项目后就好像转向了软件开发(怀疑在掉价,他们都说做硬件的吃香、干得久),反正现开的工作性质还算开心愉快、又不怎么需要养家糊口的,所以生活还算轻松(就是回家后要自己做饭,呜呜~~~)。
    说了这么多,我只想说明的一点是我并不是写了几个hello world就用C++在这里大呼小叫,一些经验一些想法都是实践的体会,拿上来献丑不要介意p,很多时候只想分享一下开发中的愉悦,为求抛砖引玉。

    技术从来都只是一种手段,一个好的方法可让您少掉好几根头发,养妻活儿我可不敢包,但是做一个sohu的开发人员总是可以的吧,若是自娱自乐的则有更高的境界和追求。
多掌握一门技能就就多一条出路,生活多了一把小刀,工作也就少一点磨难。知识和学问最终以经验的形式沉淀在你的大脑中,是你终生受用!

    说多了,就此罢了。注意:如有雷同,实属巧合,切勿对号入座。

    愿大家工作愉快!

    BTW,今天开学了,非常怀念我远方的同学和朋友,愿他们身体健康,在生活及技术道路上一马平川!

评论

此博客中的热门博文

【转】AMBA、AHB、APB总线简介

AMBA 简介 随着深亚微米工艺技术日益成熟,集成电路芯片的规模越来越大。数字IC从基于时序驱动的设计方法,发展到基于IP复用的设计方法,并在SOC设计中得到了广泛应用。在基于IP复用的SoC设计中,片上总线设计是最关键的问题。为此,业界出现了很多片上总线标准。其中,由ARM公司推出的AMBA片上总线受到了广大IP开发商和SoC系统集成者的青睐,已成为一种流行的工业标准片上结构。AMBA规范主要包括了AHB(Advanced High performance Bus)系统总线和APB(Advanced Peripheral Bus)外围总线。   AMBA 片上总线        AMBA 2.0 规范包括四个部分:AHB、ASB、APB和Test Methodology。AHB的相互连接采用了传统的带有主模块和从模块的共享总线,接口与互连功能分离,这对芯片上模块之间的互连具有重要意义。AMBA已不仅是一种总线,更是一种带有接口模块的互连体系。下面将简要介绍比较重要的AHB和APB总线。 基于 AMBA 的片上系统        一个典型的基于AMBA总线的系统框图如图3所示。        大多数挂在总线上的模块(包括处理器)只是单一属性的功能模块:主模块或者从模块。主模块是向从模块发出读写操作的模块,如CPU,DSP等;从模块是接受命令并做出反应的模块,如片上的RAM,AHB/APB 桥等。另外,还有一些模块同时具有两种属性,例如直接存储器存取(DMA)在被编程时是从模块,但在系统读传输数据时必须是主模块。如果总线上存在多个主模块,就需要仲裁器来决定如何控制各种主模块对总线的访问。虽然仲裁规范是AMBA总线规范中的一部分,但具体使用的算法由RTL设计工程师决定,其中两个最常用的算法是固定优先级算法和循环制算法。AHB总线上最多可以有16个主模块和任意多个从模块,如果主模块数目大于16,则需再加一层结构(具体参阅ARM公司推出的Multi-layer AHB规范)。APB 桥既是APB总线上唯一的主模块,也是AHB系统总线上的从模块。其主要功能是锁存来自AHB系统总...

【转】C++/CLI程序进程之间的通讯

 现在,把大型软件项目分解为一些相交互的小程序似乎变得越来越普遍,程序各部分之间的通讯可使用某种类型的通讯协议,这些程序可能运行在不同的机器上、不同的操作系统中、以不同的语言编写,但也有可能只在同一台机器上,实际上,这些程序可看成是同一程序中的不同线程。而本文主要讨论C++/CLI程序间的通讯,当然,在此是讨论进程间通讯,而不是网络通讯。    简介   试想一个包含数据库查询功能的应用,通常有一个被称为服务端的程序,等待另一个被称为客户端程序发送请求,当接收到请求时,服务端执行相应功能,并把结果(或者错误信息)返回给客户端。在许多情况中,有着多个客户端,所有的请求都会在同一时间发送到同一服务端,这就要求服务端程序要更加高级、完善。   在某些针对此任务的环境中,服务端程序可能只是众多程序中的一个程序,其他可能也是服务端或者客户端程序,实际上,如果我们的数据库服务端需要访问不存在于本机的文件,那么它就可能成为其他某个文件服务器的一个客户端。一个程序中可能会有一个服务线程及一个或多个客户线程,因此,我们需小心使用客户端及服务端这个术语,虽然它们表达了近似的抽象含义,但在具体实现上却大不相同。从一般的观点来看,客户端即为服务端所提供服务的"消费者",而服务端也能成为其他某些服务的客户端。    服务端套接字   让我们从一个具体有代表性的服务端程序开始(请看例1),此程序等待客户端发送一对整数,把它们相加之后返回结果给客户端。   例1: using namespace System; using namespace System::IO; using namespace System::Net; using namespace System::Net::Sockets; int main(array<String^>^ argv) { if (argv->Length != 1) { Console::WriteLine("Usage: Server port"); Environment::Exit(1); } int port = 0; try { port = Int32::Parse(argv[0]); } catch (FormatException^ e) { Console::Wri...

【转】VxWorks入门

1.VxWorks开发方式:交叉开发,即将开发分为主机(host)和目标机(target)两部分。 类似于dos下C语言程序的开发。 合并开发的优点:简单 缺点:资源消耗量大,CPU支持,非标准体系的支持 host (Tornado) target(vxWork) 小程序模块 vxWorks实际采用开发模式 Tornado提供:编辑,编译,调试,性能分析工具,是vxWorks的开发工具 vxWorks:面向对象可以剪裁的实际运行操作系统 2.vxWorks启动方式 <1>Rom方式 (vxWork_rom) vxWorks直接烧入rom <2>Rom引导方式(bootrom+vxWorks) 其中bootrom烧入rom,vxWorks可以通过从串口,网口,硬盘,flash等下载!这里的bootrom不是开发环境中的bootable,在开发环境里bootable指的是vxWorks,downloadable指application 3.调试 <1>attachs/20060907_164540_564.rar 用来在多任务调试时将调试对象绑定到某个任务 <2>任务级调试(attachs/20060907_164540_564.rar taskName) 单个任务的调试不会影响到其他任务的运行,主要用来调用户的应用程序。 全局断点:在调另一任务或本任务时,系统运行本任务断点,则停下。各任务要配合使用。 任务断点:调本任务时,系统运行到本任务断点,则停下。如果没有attachs/20060907_164540_564.rar到本任务,不起作用。 一次性断点:跑到一次之后自动删除。 <3>系统级调试(attachs/20060907_164540_564.rar system) 把所有task和系统core、中断看成一个整体,可用于调试系统和中断。对中断调试,如果不是系统级调试,无论是那种断点都不起作用 !wdbAgent不在调试范围内,当任务级调试时工作在中断方式,系统级调试工作在轮询方式。 !可是使用命令行方式的调试,参看crossWind教程。 4.调度 优先级调度(无条件) 时间片:同优先级,如果时间片没有打开,任务采取先到先运行,运行完毕在交出cpu,如果打开,则轮流使用cpu。 !死循环使比它...