声明一下,写下这些编程技巧,即不是什么祖传秘籍,也不是什么必杀招或绝招,在此只为方便同仁们在编程控制软件时,对此可以进行适当的斟酌。以下展现的编程思想及奉上的源代码都非常简易,但并不是随手写写,可都是经过实践的。若没有成功经验作后盾,我也就没有必要在此打字练五笔了。 事实上,正如一个编程大师所言( Michael Abrash ),当你的软件正常而且有效率的运行起来时,好像一切都是那么显而易见。故,在此,我仍坚持那句编程口号,将事情变得越简单越好,越简单就越有效率,越稳定。 在以下的介绍中,我将尽可能的展示本人的编程思想,最大可能的给出知其然也知其所然的解释。若你有更好的见解,希望能得到你的指正。人长大了明显标志就是变得不太负责,而且不敢承认自己还需要努力,害怕面对自己的错误。若是这样,放心,我还没长大。因为我无法保证我能面面俱到。 关于源代码的阅读,需要读者有一定的 C++ 编程基础,至少对以下表示形式不会产生误解: const char *pString; // 指定 pString 邦定的数据不能被修改 char * const pString; // 指定 pString 的地址不能被修改 const char * const pString; // 含上面两种指定功能 当然,随便提醒一下,这些源代码若需要加入你的软件工程当中,还需要作一些调整和修改,因此,这些源代码实质上称为伪代码也可以,之所以展现它们,是让程序员们有个可视化的快感,特别是那些认为源代码就是一切的程序员。 同时,为了提高针对性,大部分控制卡调用的函数会明确指出是邦定哪些卡的,实际应用时,程序员可自行选择,以体现一下自己的智商是可以写写软件的。 留个电话: 0755-26434329 E_mail: support@leisai.com 有更浓兴趣的上这个 QQ 号吗也行: 372161225 一、 控制卡类的单一实例实现 把控制卡类作一个类来处理,几乎所有 C++ 程序员都为举双手表示赞同,故第一个什么都没有的伪代码就此产生,如下表现: class CCtrlCard { public: …Function public: …attrib } 于是,用这个 CctrlCard 可以产生 n 多个控制卡实例,只要内存足够。然而,针对现实世界,情况并不那么美好。通常情况下, PC 机内只插同种类型的控制卡 1 到 2 张,在通过调用 d1000_board_init 或 d3000_board_init 函数时,它们会负责返回有效卡数 nCards ,然后从 0-nCards*4 - 1 自行按排好轴数。初始化函数就是 C++ 的 new 或 malloc 的操作,取得系统的资源,但是控制卡的资源与内存不一样,取得资源后必需要释放才可以再次获取,即控制卡资源是唯一的。 既然控制卡资源是唯一的,那么最好 Cctrlcard 产生的实例也是唯一的,这样,我们可以方便的需要定义一个全局变量即可 : CctrlCard g_Dmcard; 在其它需要调用的地方,进行外部呼叫: extern CctrlCard g_DmcCard; 以上方法实在太简单了,很多人都会开心起来。实质上,方法还有很多,即然可以产生 n 多对实例,我们的核心是只要保证调用 board_init 函数一次即可,故也可以单独定义一个 InitBoard 函数: class CctrlCard { public: static int InitBoard(); // 定义一个静态函数,以表警示 } int CctrlCard::InitBoard() { return d1000_board_init(); } 还有一种方法,情况稍加复杂,但表达的功能也要强一些,以下展现可以稍微安慰一下代码狂。 Class CctrlCard { public: CctrlCard(); // 请注意这个构造函数的定义 } CctrlCard::CctrlCard() {// 呵呵,也很明了 static int n(0); // 注意,是个静态变量 n++; // 每次调用 CctrlCard 生成实例时,都会计数一次 assert( n == 1 ); // 在 DEBUG 版本下,只有 n==1 的情况下可以通过 // 否则,会出现致命错误,还好,它会告诉你错在哪个文件, // 哪一行,呵呵,是个好东东啊。 } 通过强行报警处理,当你有 g_DmcCard 这个实例时,其它的所有控制卡的定义都只能是以引用或指针的方式进行了,不会再产生新有效的实例了,对于由小组编程的项目软件,而你又恰好负责编程控制卡这一块的话,以上的显性报警,会让其它人心领神会。当然,你也可以将上面的方法加入到 InitBoard 当中去,可以避你的无意识的多次调用了。 附:无意识的多次调用经常发生,特别是那些对 MFC 机制不明确的程序员,在多文档框架下,不知道这个 CctrlCard::InitBoard 函数到底是应该放在 CmainFrame 的 OnCreate 里面,还是应该放在 CchildFrame 的 OnCreate ,或者是 Cview 的 OnInitUpdate 里面进行调用。 在一言难尽 MFC 的情况下,我建议两个小方法: No.1 将 CctrlCard 的函数置于 Cmainframe 的 OnCreate 或者 Capp::Initstance 内调用 No.2 将 InitBoard 函数稍加改造成这样: Int CctrlCard::InitBoard() { static int n(-1000);// 注意, -1000 是控制卡函数不可能返回的值 if( n == -1000 ) n = d1000_board_init(); return n;// 这样,即使多次调用也不样怕了,呵呵,雕虫小技也可以除虫啊 } 必须额外声明一下,我们不是不重视资源的释放,而是作为一个 C++ 程序员写下这些代码是基本的义务(这也是我为什么要交待读者必须要有一定的 C++ 基础): class CctrlCard { public: ~CctrlCard() {// 定义析构函数,在此释放资源,对此,我不想再转到读者的眼球了 d1000_board_close(); } } 二、 数据结构及数据类型的定义,部分相关声明 调用控制卡驱动函数时,经常会有如下形式: 单轴相对运动 d1000_start_t_move( axis, pulse, start, speed, accel ); 单轴绝对运动 d1000_start_ta_move( axis, pulse, start, speed, accel ); 两轴相对插补 d1000_start_t_line( axisArray, distArray, start, speed, accel ); 两轴绝对插补 d1000_start_ta_line( axisArray, distArray, start, speed, accel ); 圆弧相对插补 d3000_start_t_arc( axisArray, C1, C2, E1,E2, dir, start, speed, accel ); 圆弧绝对插补 d3000_start_ta_arc( axisArray, C1, C2, E1,E2, dir, start, speed, accel ); 以上的调用,很多重复枯燥,又不直观,难于理解,并且在面向客户时,常常是指每分多少米,或者每秒多少毫米,很少有人问每秒多少脉冲,移动多少脉冲作距离,故需要单位之间的换算。显然,对于这些问题,我想,C++程序员应该找到用武之地了,所以我们一步一步来,慢慢统一各个问题。实质上,在以下的几个技巧,也需要在此澄清一些概念。 我们先来几个宏定义提高一下情绪: # define MAX_AXIS 4 // 最多轴数 # define XCH 0 // 定义X轴的值 # define YCH 1 # define ZCH 2 # define UCH 3 …..( 其它以次类推 ) # define M_ABS 0x01 // 定义一个绝对标志位 # define M_INP 0x02 // 定义一个插补位 接下来深入一点点,再来几个结构定义: typedef struct tag_ARC { tag_ARC( double ox=0.0, double oy=0.0, double ex=0.0, double ey=0.0, int dir=0 ): ox(ox), oy(oy), ex(ex), ey(ey), dir(dir)// 定义这样一个构造函数需要勇气,看似不合理,但是好用麻 { } double ox,oy; double ex,ey; int dir; }ARC; typedef struct tag_SPEED { tag_SPEED( double start=0.0, double speed=0.0, double accel=0.0, double decel=0.0, double scc=0.0 ) : start(start), speed(speed), accel(accel), decel(decel), scc(scc) { } double start; double speed; double accel; double decel; double scc; }SPEED; 以上两个 ARC 和 SPEED 的结构定义,把几个参数变成一个参数。比如要实现的单轴驱动函数,就变得非常明了: void Move( int nAxis, double fMM, const SPEED &speed, int nFlag = M_ABS );// 往后我们再具体完善其实现。 以上的结构具有类的特性,但是由于其每个成员都可以给外部直接使用,故就不需要什么类的 public 及其析构函数的定义了。之所以全都采用 double 的数据类型,是面向客户习惯及单位计算方便的。 接下来是对控制卡常用的单位计算及部分常用变量的声明: class Cctrlcard { public: …( 其它略去 ) public: // 属性 mutable int ORGIN; // 指定原点状态位 mutable int LIMIT_A, LIMIT_B; // 指定左右限位状态位 private: // 以下的属性不给外部访问的 struct tag_AXIS{// 单轴属性 double fUnitPM; // 脉冲当量 long nRP; // 每转脉冲数 double fJourey; // 行程 }; tag_AXIS m_axis[MAX_AXIS]; }; 定义 ORGIN , LIMIT_A, LIMIT_B 为变量,是有两个意义: No.1 当你访问它们的状态时,不需要每次调用 d1000_get_axis_status 函数,你可以这样: Int nStatus = d1000_get_axis( XCH ); If( nStatus & g_DmcCard.ORGIN == g_dmcCard.ORGIN ) If( nStatus & g_DmcCard.LIMIT_A == g_DmcCard.LIMIT_A ) If( nStatus & g_DmcCard.LIMIT_B == g_DmcCard.LIMIT_B ); No.2 你可以扩展不同的卡,当外部调用的程序逻辑已被确定时,当你需要从 DMC1000 控制卡升级到 DMC3000 控制卡时,只需要给 ORGIN 等状态位指定不同的值即可。指定状态位的值也有一个小小的技巧,以 ORGIN 为例,在 DMC1000 控制卡,其位值在 2 位,则可以这样: ORGIN = 1<<2; 在 DMC3000 控制卡,其值在第 9 位,则这样: ORGIN = 1<<9; 方法都很简单,关键是要想得到。 对于 tag_AXIS 定义,引出几个函数的声明,专门为其服务: void SetUP( nit nAxis, double fMM, double nPulse, double fMax );// 设定当量 double P2M ( int nAxis, long nPulse ); // 脉冲转成毫米 pulse to metric long M2P( int nAxis, double fMM ); // 毫米转成脉冲 mitric to pulse 现在,我们再回过头来完成 Move 函数的实现,以便获得一点点成就感,同时也展示一下以上的大堆表述是有其意义的。 void Move( int nAxis, double fMM, const SPEED &speed, int nFlag = M_ABS ) { ( nFlag & M_ABS == M_ABS ) ? d1000_start_ta_move( nAxis, // 绝对 M2P( nAxis, fMM), M2P( nAxis, speed.start ), M2P( nAxis, speed.speed), Speed.accel ): // 注意是冒号, ?: 是一个表达式 d1000_start_t_move( nAxis, // 相对 M2P( nAxis, fMM), M2P( nAxis, speed.start ), M2P( nAxis, speed.speed), Speed.accel ); } 是不是很简单呢,当外部调用时,客户的观念就直接面对 Metric 即可,如: Move( XCH, 10.0, SPEED(5,10,0.1), M_ABS );// 达到绝对位置 10.0 毫米处。 以上罗嗦了一大堆,对于刚开始 C++ 编程的程序员来说应该是收益不小,对于高手,则希望能够体会一下我的良苦用心。在以下的技巧介绍当中,我将变得很简易。一般来讲,程序员的基础不是太差的话,至少能够在 1 分钟内明白是什么道理。
状 态:
离线
公司简介
产品目录
公司名称:
深圳市雷赛智能控制股份有限公司
联 系 人:
梁邦敏
电 话:
755-26401178
传 真:
地 址:
深圳市南山区登良路天安南油工业区2栋3楼
邮 编:
518000
主 页: