跳至主要内容

【转】感受面向对象编程的魅力(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系统总...

【转】GPIO编程模拟I2C入门

ARM编程:ARM普通GPIO口线模拟I2C  请教个问题: 因为需要很多EEPROM进行点对点控制,所以我现在要用ARM的GPIO模拟I2C,管脚方向我设 置的是向外的。我用网上的RW24C08的万能程序修改了一下,先进行两根线的模拟,SDA6, SCL6,但是读出来的数不对。我做了一个简单的实验,模拟SDA6,SCL6输出方波,在示波 器上看到正确方波,也就是说,我的输出控制是没问题的。 哪位大哥能指点一下,是否在接收时管脚方向要设为向内?(不过IOPIN不管什么方向都可 以读出当前状态值的阿) 附修改的RW24C08()程序: #define  SomeNOP() delay(300); /**/ /* *********************************  RW24C08   **************************************** */ /**/ /* ----------------------------------------------------------------------------- ---  调用方式:void I2CInit(void)   函数说明:私有函数,I2C专用 ------------------------------------------------------------------------------- -- */ void  I2CInit( void ) ... {  IO0CLR  =  SCL6;      // 初始状态关闭总线  SomeNOP();  // 延时   I2CStop();  // 确保初始化,此时数据线是高电平 }   /**/ /* ---------------------------------------------------------------------------- ----  调用方式:void I2CSta...

【转】cs8900网卡的移植至基于linux2.6内核的s3c2410平台

cs8900网卡的移植至基于linux2.6内核的s3c2410平台(转) 2008-03-11 20:58 硬件环境:SBC-2410X开发板(CPU:S3C2410X) 内核版本:2.6.11.1 运行环境:Debian2.6.8 交叉编译环境:gcc-3.3.4-glibc-2.3.3 第一部分 网卡CS8900A驱动程序的移植 一、从网上将Linux内核源代码下载到本机上,并将其解压: #tar jxf linux-2.6.11.1.tar.bz2 二、打开内核顶层目录中的Makefile文件,这个文件中需要修改的内容包括以下两个方面。 (1)指定目标平台。 移植前:         ARCH?= $(SUBARCH) 移植后: ARCH            :=arm (2)指定交叉编译器。 移植前: CROSS_COMPILE ?= 移植后: CROSS_COMPILE   :=/opt/crosstool/arm-s3c2410-linux-gnu/gcc-3.3.4-glibc-2.3.3/bin/arm-s3c2410-linux-gnu- 注:这里假设编译器就放在本机的那个目录下。 三、添加驱动程序源代码,这涉及到以下几个方面。(1)、从网上下载了cs8900.c和cs8900.h两个针对2.6.7的内核的驱动程序源代码,将其放在drivers/net/arm/目录下面。 #cp cs8900.c ./drivers/net/arm/ #cp cs8900.h ./drivers/net/arm/ 并在cs8900_probe()函数中,memset (&priv,0,sizeof (cs8900_t));函数之后添加如下两条语句: __raw_writel(0x2211d110,S3C2410_BWSCON); __raw_writel(0x1f7c,S3C2410_BANKCON3); 注:其原因在"第二部分"解释。 (2)、修改drivers/net/arm/目录下的Kconfig文件,在最后添加如...