控制卡應用編程技巧幾招(1)
?聲明一下,寫下這些編程技巧,即不是什么祖?zhèn)髅丶膊皇鞘裁幢貧⒄谢蚪^招,在此只為方便同仁們在編程控制軟件時,對此可以進行適當?shù)恼遄?。以下展現(xiàn)的編程思想及奉上的源代碼都非常簡易,但并不是隨手寫寫,可都是經(jīng)過實踐的。若沒有成功經(jīng)驗作后盾,我也就沒有必要在此打字練五筆了。?
事實上,正如一個編程大師所言(Michael Abrash),當你的軟件正常而且有效率的運行起來時,好像一切都是那么顯而易見。故,在此,我仍堅持那句編程口號,將事情變得越簡單越好,越簡單就越有效率,越穩(wěn)定。?
在以下的介紹中,我將盡可能的展示本人的編程思想,最大可能的給出知其然也知其所然的解釋。若你有更好的見解,希望能得到你的指正。人長大了明顯標志就是變得不太負責,而且不敢承認自己還需要努力,害怕面對自己的錯誤。若是這樣,放心,我還沒長大。因為我無法保證我能面面俱到。?
關于源代碼的閱讀,需要讀者有一定的C++編程基礎,至少對以下表示形式不會產(chǎn)生誤解:?
const char *pString; //指定pString邦定的數(shù)據(jù)不能被修改?
char * const pString; //指定pString的地址不能被修改?
const char * const pString; //含上面兩種指定功能?
當然,隨便提醒一下,這些源代碼若需要加入你的軟件工程當中,還需要作一些調(diào)整和修改,因此,這些源代碼實質(zhì)上稱為偽代碼也可以,之所以展現(xiàn)它們,是讓程序員們有個可視化的快感,特別是那些認為源代碼就是一切的程序員。?
同時,為了提高針對性,大部分控制卡調(diào)用的函數(shù)會明確指出是邦定哪些卡的,實際應用時,程序員可自行選擇,以體現(xiàn)一下自己的智商是可以寫寫軟件的。?
?一、?控制卡類的單一實例實現(xiàn)?
把控制卡類作一個類來處理,幾乎所有C++程序員都為舉雙手表示贊同,故第一個什么都沒有的偽代碼就此產(chǎn)生,如下表現(xiàn):?
class CCtrlCard
{
public:
…Function
public:
…attrib
}
?于是,用這個CctrlCard可以產(chǎn)生n多個控制卡實例,只要內(nèi)存足夠。然而,針對現(xiàn)實世界,情況并不那么美好。通常情況下,PC機內(nèi)只插同種類型的控制卡1到2張,在通過調(diào)用d1000_board_init或d3000_board_init函數(shù)時,它們會負責返回有效卡數(shù)nCards,然后從0-nCards*4 - 1自行按排好軸數(shù)。初始化函數(shù)就是C++的new或malloc的操作,取得系統(tǒng)的資源,但是控制卡的資源與內(nèi)存不一樣,取得資源后必需要釋放才可以再次獲取,即控制卡資源是唯一的。?
既然控制卡資源是唯一的,那么最好Cctrlcard產(chǎn)生的實例也是唯一的,這樣,我們可以方便的需要定義一個全局變量即可:
CctrlCard g_Dmcard;
在其它需要調(diào)用的地方,進行外部呼叫:?
extern CctrlCard g_DmcCard;
?以上方法實在太簡單了,很多人都會開心起來。實質(zhì)上,方法還有很多,即然可以產(chǎn)生n多對實例,我們的核心是只要保證調(diào)用board_init函數(shù)一次即可,故也可以單獨定義一個InitBoard函數(shù):?
class CctrlCard
{
public:
static int InitBoard(); //定義一個靜態(tài)函數(shù),以表警示?
?}
int CctrlCard::InitBoard()
{
return d1000_board_init();
}
?還有一種方法,情況稍加復雜,但表達的功能也要強一些,以下展現(xiàn)可以稍微安慰一下代碼狂。?
?Class CctrlCard
{
public:
CctrlCard(); //請注意這個構造函數(shù)的定義?
}
?CctrlCard::CctrlCard()
{//呵呵,也很明了?
static int n(0); //注意,是個靜態(tài)變量?
?n++; //每次調(diào)用CctrlCard生成實例時,都會計數(shù)一次?
assert( n == 1 ); //在DEBUG版本下,只有n==1的情況下可以通過?
//否則,會出現(xiàn)致命錯誤,還好,它會告訴你錯在哪個文件,?
//哪一行,呵呵,是個好東東啊。?
}
通過強行報警處理,當你有g_DmcCard這個實例時,其它的所有控制卡的定義都只能是以引用或指針的方式進行了,不會再產(chǎn)生新有效的實例了,對于由小組編程的項目軟件,而你又恰好負責編程控制卡這一塊的話,以上的顯性報警,會讓其它人心領神會。當然,你也可以將上面的方法加入到InitBoard當中去,可以避你的無意識的多次調(diào)用了。?
?附:無意識的多次調(diào)用經(jīng)常發(fā)生,特別是那些對MFC機制不明確的程序員,在多文檔框架下,不知道這個CctrlCard::InitBoard函數(shù)到底是應該放在CmainFrame的OnCreate里面,還是應該放在CchildFrame的OnCreate,或者是Cview的OnInitUpdate里面進行調(diào)用。?
在一言難盡MFC的情況下,我建議兩個小方法:?
No.1 將CctrlCard的函數(shù)置于Cmainframe的OnCreate或者Capp::Initstance內(nèi)調(diào)用?
No.2 將InitBoard函數(shù)稍加改造成這樣:?
Int CctrlCard::InitBoard()
{
static int n(-1000);//注意,-1000是控制卡函數(shù)不可能返回的值?
if( n == -1000 )
n = d1000_board_init();
return n;//這樣,即使多次調(diào)用也不樣怕了,呵呵,雕蟲小技也可以除蟲啊?
}
?必須額外聲明一下,我們不是不重視資源的釋放,而是作為一個C++程序員寫下這些代碼是基本的義務(這也是我為什么要交待讀者必須要有一定的C++基礎):?
class CctrlCard
{
public:
~CctrlCard()
{//定義析構函數(shù),在此釋放資源,對此,我不想再轉(zhuǎn)到讀者的眼球了?
d1000_board_close();
}
}
?二、?數(shù)據(jù)結(jié)構及數(shù)據(jù)類型的定義,部分相關聲明?
調(diào)用控制卡驅(qū)動函數(shù)時,經(jīng)常會有如下形式:?
單軸相對運動?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 );
?以上的調(diào)用,很多重復枯燥,又不直觀,難于理解,并且在面向客戶時,常常是指每分多少米,或者每秒多少毫米,很少有人問每秒多少脈沖,移動多少脈沖作距離,故需要單位之間的換算。顯然,對于這些問題,我想,C++程序員應該找到用武之地了,所以我們一步一步來,慢慢統(tǒng)一各個問題。實質(zhì)上,在以下的幾個技巧,也需要在此澄清一些概念。?
我們先來幾個宏定義提高一下情緒:?
# define MAX_AXIS 4 //最多軸數(shù)?
# define XCH 0 //定義X軸的值?
# define YCH 1
# define ZCH 2
# define UCH 3
…..(其它以次類推)
?# define M_ABS 0x01 //定義一個絕對標志位?
# define M_INP 0x02 //定義一個插補位?
?接下來深入一點點,再來幾個結(jié)構定義:?
?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)//定義這樣一個構造函數(shù)需要勇氣,看似不合理,但是好用麻?
{
}
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的結(jié)構定義,把幾個參數(shù)變成一個參數(shù)。比如要實現(xiàn)的單軸驅(qū)動函數(shù),就變得非常明了:?
void Move( int nAxis, double fMM, const SPEED &speed, int nFlag = M_ABS );//往后我們再具體完善其實現(xiàn)。?
?以上的結(jié)構具有類的特性,但是由于其每個成員都可以給外部直接使用,故就不需要什么類的public及其析構函數(shù)的定義了。之所以全都采用double的數(shù)據(jù)類型,是面向客戶習慣及單位計算方便的。?
接下來是對控制卡常用的單位計算及部分常用變量的聲明:?
class Cctrlcard
{
public:
…(其它略去)
public:
//屬性?
mutable int ORGIN; //指定原點狀態(tài)位?
mutable int LIMIT_A, LIMIT_B; //指定左右限位狀態(tài)位?
private:
//以下的屬性不給外部訪問的?
struct tag_AXIS{//單軸屬性?
double fUnitPM; //脈沖當量?
long nRP; //每轉(zhuǎn)脈沖數(shù)?
double fJourey; //行程?
};
tag_AXIS m_axis[MAX_AXIS];
};
?
定義ORGIN,LIMIT_A, LIMIT_B為變量,是有兩個意義:?
No.1 當你訪問它們的狀態(tài)時,不需要每次調(diào)用d1000_get_axis_status函數(shù),你可以這樣:?
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 你可以擴展不同的卡,當外部調(diào)用的程序邏輯已被確定時,當你需要從DMC1000控制卡升級到DMC3000控制卡時,只需要給ORGIN等狀態(tài)位指定不同的值即可。指定狀態(tài)位的值也有一個小小的技巧,以ORGIN為例,在DMC1000控制卡,其位值在2位,則可以這樣:?
ORGIN = 1<<2;
在DMC3000控制卡,其值在第9位,則這樣:?
ORGIN = 1<<9;
方法都很簡單,關鍵是要想得到。?
?
對于tag_AXIS定義,引出幾個函數(shù)的聲明,專門為其服務:?
void SetUP( nit nAxis, double fMM, double nPulse, double fMax );//設定當量?
double P2M ( int nAxis, long nPulse ); //脈沖轉(zhuǎn)成毫米?pulse to metric
long M2P( int nAxis, double fMM ); //毫米轉(zhuǎn)成脈沖?mitric to pulse
?現(xiàn)在,我們再回過頭來完成Move函數(shù)的實現(xiàn),以便獲得一點點成就感,同時也展示一下以上的大堆表述是有其意義的。?
?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 );
}
?是不是很簡單呢,當外部調(diào)用時,客戶的觀念就直接面對Metric即可,如:?
?Move( XCH, 10.0, SPEED(5,10,0.1), M_ABS );//達到絕對位置10.0毫米處。?
?以上羅嗦了一大堆,對于剛開始C++編程的程序員來說應該是收益不小,對于高手,則希望能夠體會一下我的良苦用心。在以下的技巧介紹當中,我將變得很簡易。一般來講,程序員的基礎不是太差的話,至少能夠在1分鐘內(nèi)明白是什么道理。
?
控制卡應用編程技巧幾招(2)
發(fā)布日期:2009-3-9 0:42:58 ?瀏覽次數(shù):2416
?三、?插補和聯(lián)動函數(shù)?
當程序員決定需要幾軸進行插補時,盡量選擇最大插補軸數(shù),如在雕銑系統(tǒng)時,有時會用到兩軸插補,有時會進行三軸插補,在這個基礎上,為簡化編程,我的理論只使用三軸插補,當需要進行兩軸插補或聯(lián)動時,根據(jù)相對或絕對的坐標關系,將不運動軸填入0偏移或絕對位置即可。?
以下為XYZ三軸聯(lián)動和插補的函數(shù),由nFlag的M_INP位決定是否進行插補:?
?
void MoveXYZ( double fX, double fY, double fZ, const tag_SPEED &speed,
int nFlag = M_ABS )
{
short axisArray[]={ XCH, YCH, ZCH };
?
if( nFlag & M_INP == M_INP )
{//插補?
long distArray[]={ M2P(XCH, fX), M2P(YCH,fY), M2P(ZCH,fZ) };
long nStart, nSpeed;//計算新的矢量速度,參見DMC1000矢量速度的計算?
(…矢量速度計算在此略去)
?
( nFlag & M_ABS == M_ABS ) ?
d1000_start_ta_line( 3, axisArray, nStart, nSpeed, speed,accel )://絕對?
d1000_start_t_line(3, axisArray, nStart, nSpeed, accel );//相對?
}
else
{//聯(lián)動?
double fpos[]={ fX, fY, fZ};
for( int I(0); I<3; I++)//發(fā)三次單軸移動命令?
Move( axisArray[I], fpos[I], speed, nFlag );
}
}
在我給出的DMC3000控制卡類完整源代碼一文中,有其更完善的版本。通過以下的函數(shù)封裝,將插補和聯(lián)動,絕對位置,相對位置等等都很好的整合在一起,用戶在使用起來具體更準確的目標。?
?
四、?驅(qū)動軸狀態(tài)、位置讀取和設定?
對于驅(qū)動軸的狀態(tài),分為兩種:1、指脈沖輸出狀態(tài);2、指專用輸入信號電平狀態(tài)?
檢測脈沖輸出是否完成,可以寫成如下函數(shù),假設軟件總共只用到XYZ三軸:?
?int IsRunning( int nAxis = -1 )//默認為-1是有目的的?
{
if( nAxis != -1 )
return d1000_check_done( nAxis ) == 0 ;
//當nAxis == -1時,檢測三個軸是否有一個在運行,這種檢測在加工時常用?
return d1000_check_done( XCH ) == 0 ||
d1000_check_done( YCH ) == 0 ||
d1000_check_done( ZCH ) == 0;
}
?當用戶等待YCH脈沖發(fā)完,則用一個循環(huán)檢測即可:?
while( g_DmcCard.IsRuning( YCH ) ) ::DoEvents();
?別忘了,IsRuning是CctrlCard的成員函數(shù),而DoEvents函數(shù)在DMC1000不能響應系統(tǒng)消息的文章中有詳細實現(xiàn)和功能描述。?
在實際加工時,作插補時,常需要等待上次所有運動結(jié)束才開始新的運動。故有如下表現(xiàn):?
?
for( int I(0),step(0); I
{
DoEvents();
switch( m_nworkStatus ){
case Pause:
continue;
case Continue: m_nWorkStatus = Running;
case Running:
{
switch( step ){
case 0:
if( IsRunning() ) break;//檢測所有運動結(jié)束,否則繼續(xù)檢測?
MoveXYZ( data[I].x, data[I].y, data[I].z …… );
Step ++;
Break;
Case 1:
If( IsRunning() ) break;//同上?
I++; //準備下一段數(shù)據(jù),之所以放在此處,是需要考慮在運行過程中,有外部的暫停和繼續(xù)操作。?
Step = 0;//準備運行新的數(shù)據(jù)?
Break;
}
} break;
}
?
以上程序框架,有著非常廣闊的應用前景,非常簡單,可以讓程序員隨意控制,故而它又非常穩(wěn)定,比起線程的操作,它具體非常透明的可操作性。?此框架在雕刻,焊接,切割等許多場合都將成為經(jīng)典,當然,若你不曾深入了解它,則不會發(fā)現(xiàn)它的可愛之處。?
?
對于專用輸入信號狀態(tài)的檢測,幾乎沒有什么特別之處:?
int GetStatus( int nAxis )
{
return d1000_get_axis_status( nAxis );
}
?
位置的讀取和設定,對于DMC1000比較容易,故在此我將寫出DMC3000控制卡的這兩個函數(shù),當然用于DMC1000也是沒問題的。?
DMC3000控制卡的位置分為指令位置和物理位置(編碼器反饋的),所以函數(shù)需要有一個小小的選擇,先看看位置獲取函數(shù):?
?
Double GetPosition( int nAxis, BOOL bCmd = true )// bCmd == true時,讀取指令位置,否則為物理位置?
{
long pulse = (bCmd == true ) ?
d3000_get_command_pos( nAxis ):
d3000_get_encoder_pos(nAxis);
return P2M( nAxis, pulse );//脈沖轉(zhuǎn)成毫米然后返回?
}
?位置設定函數(shù)多了一點點動作:?
double SetPosition( int nAxis, double fMM, BOOL bCmd = true )
{
double pos = GetPosition( nAxis, bCmd );//先取得原來的位置?
( bCmd == true )?
D3000_set_command_pos( nAxis, M2P(nAxis, fMM )):
D3000_set_encoder_pos( nAxis, M2P(nAxis, fMM) );
Return pos;//返回舊的位置?
}
為什么這樣設計?當你用過CPen *pOldPen= pDC->SelectObject( &newPen );時,或者除了復位之外,你真正需要調(diào)用這個SetPosition函數(shù)時,你會發(fā)現(xiàn)這個設計,真是人情味實足。?
?
五、?復位,相對與絕對,?
在如今PC機開發(fā)控制卡軟件時代,設備上電不復位的幾乎沒有,在此談到復位這個問題確實有必要,實現(xiàn)上,復位動作因不同設備的工藝要求而定,故一般而言,控制卡提供的那個復位函數(shù)太過簡單,有點力不從心,所以,本人自己寫了個復位函數(shù),但是代碼寫起來將會占用很大的面版,故有此需要者,可以來電或E_mail索取。?
其基本思路是采用兩次找原點,第一次高速找,停止后退出,再次以較低的速度找原點。并且在執(zhí)行第二次復位時,會在離原點5毫米處減速(第一次執(zhí)行做不到)。?
?提供相對和絕對位置的概念是很有必要的,眾所周知,現(xiàn)在控制卡能作到最小單位為1個脈沖,當然,作為數(shù)字脈沖,到此已不能再小了,故為了提高精度,通常情況下要提高計算當量,即增加每轉(zhuǎn)脈沖數(shù),或減少每轉(zhuǎn)毫米數(shù)。?
不論怎么,我們將問題放大并明朗化,可以看看以下片段:?
?for( int I(0); I<10000; I++)// 走10000次?
move( 0.5 );//走相對0.5個脈沖的距離?
?結(jié)果是:1個脈沖也發(fā)不出,造成很大的累積誤差。?
若換成絕對方式:?
for( int I(0); I<10000; I++)
goto( I*0.5 );
?最后的誤差,最大也就是1個脈沖以內(nèi)。雖然還是有誤差,但總算達到可容忍的程序,再加上適當?shù)膹臀徊僮?,讓客戶至少不必再擔心這個巨大的累積誤差了。?
?實質(zhì)上,在整個軟件設計時最好采用絕對坐標系,即使要處理加工原點或工面起點等這些參數(shù),也要把它換算成絕對位置,唯手動移動設備可以例外。另外,在CNC系統(tǒng)中,除了有循環(huán)用到相對坐標系,其余都是用絕對坐標系為上策,實際上,在實現(xiàn)編程算法上,為統(tǒng)一起見,最好將相對的坐標關系全部轉(zhuǎn)成絕對的坐標關系,這樣也便于外部進行暫?;蚶^續(xù)的處理。?
?相信,到此為止,若你的設備在加工時有一定的誤差漂移,你會意識到自己應該是不是要檢查一下采用了什么坐標系了吧。?
?六、?輸出輸入及軟限位?
對于通用的I/O操作,沒有什么特別要說明的,只有兩點需要注意的,先給出兩個小函數(shù),以作參考:?
?int ReadBit( int nIO ); //讀指定通用輸入口的電平狀態(tài),返回1 或?0
int WriteBit( int nIO, int nStatus ); // 輸出電平到指定輸出端口?
?兩點注意:?
No.1 對于ReadBit若需要加入抗干擾處理,則寫一個函數(shù):?
?Int RealInput( int nIO, int nStatus, int di=50 )
{
if( ReadBit( nIO ) != nStatus )
return 0;
while( di -- );//耗上幾個CPU的周期時間,再讀一次?
return ReadBit( nIO ) == nStatus;
}
?
No.2 增加一個變量及函數(shù)擴展一下輸出功能:?
Long m_nOutStatus= 0x00000000;
再次改造一下WirteBit
void WriteBit( int nIO, int nStatus )
{
if( nStatus ){
m_nOutStatus |= (1<
}
else{
m_nOutStatus &= (~(1<
}
d1000_out_bit( nIO, nStatus );
}
添加的訪問輸出狀態(tài)函數(shù):?
int ReadOutbit( int nIO )
{
static int a;
a = 1<<(nIO-1);
a &= m_nOutStatus;
return a!=0;
}
?軟限位的思想原本是用于為客戶節(jié)省正負限位的光電開關成本而產(chǎn)生的,致使使用軟件限位正常的話,設備每個驅(qū)動軸只需要一個原點開關即可。當然,軟限位能正確運作是非常重要的,否則很容易撞壞設備。而其正確運行,就必須依賴正確的復位動作,以找到可靠的機械原點位置。?
軟件限位的基本算法非常簡單,特別是在一個絕對坐標系當中。其原理如下:?
?if( pos < minPos ) pos = minPos;
if( pos > maxPos ) pos = maxPos;
?實在沒有必要再詳說下去了。?
??編程技巧介紹至此算是一個了斷,若在未來的日子里,有更好的想法,我會拿出來給大家參考,請大家一起來支持這件事情,拿出自己的寶貴經(jīng)驗,算是給數(shù)控行業(yè)添加強有力的潤滑劑吧。?
?謝謝。
?
?抽像一點講,過程控制滲透在生活的各個角落。特別是在數(shù)控行業(yè),尤為明顯。早期在PLC時代,幾乎是一統(tǒng)天下。當大小不一的控制卡公司如雨后春筍般成長起來時,控制卡在過程控制逐步體現(xiàn)出強勁優(yōu)勢。?其編程手段往往邦定非常成熟的編程工具,如:CB,VC,Dephi,CB。故其推廣非常迅速. 很快擁有大量客戶群。?
但是,作為編程者,碰到同一程序需要控制多個同樣設備時(動作不一定同步),往往在Window多任務系統(tǒng)的影響下,很快想到多線程技術,其實多線程編程對系統(tǒng)依賴性較強,不同級別的處理,將導致程序的維護成本增加,并且還需要考慮線程之間的通訊,使編程起來較為麻煩。事實上,依靠過程編程的思想,會使程序獲得更簡單的做法。?
純粹從編程角度來看,過程控制編程應包含幾個要素,下面以C++偽碼方式來表示一個類,較易說明問題:?
?
/**************************
一個動作封裝類,包含一個?
設備的所有執(zhí)行動作?
**************************/
class CAction
{
public:
CAction():m_nWorkStep(-1000){}
~CAction(){}
public:
enum{ None, Inital, Start, Stop, Pause, Continue };//部分控制標記?
?
public:
int Control( int nStatus, void *pParam ); //要素2: 一定可以接受外部控制?
int Run( void *pParam ); //要素3: 絕對有一個不斷推動的執(zhí)行過程(這是整個過程編程的心臟所在)
?
public:
int m_nWorkStep; //要素1: 必須有一個工作執(zhí)行步?
private:
void * m_pMemberData; //要素4: 可選,邦定被操作的數(shù)據(jù)?
// ..其它輔助變量?
};
?
要素5:可選,為了能與外界用戶進行溝通,Control函數(shù)與Run函數(shù),須選擇一個參數(shù),滿足用戶的輸入輸出。但由于用戶可選項太多,故古人告訴我們,萬事皆空即是成功,對于C++而言,沒有比void *類型更有前途了(若想體會,需搭配類型轉(zhuǎn)換更有潛力)。早在一年前,本人寫過一個過程控制器編程,并有成功實例,但今次版本,更有精裝之特點。?
?
住下再參見一下Control函數(shù),及Run函數(shù)的一個小樣例,這將是程序員發(fā)揮強勁功能的場地所在。如下:?
?
int CAction::Control( int nStatus, void *pParam )
{//此處的設計需要一點技巧,當然完全取決程序員的經(jīng)驗,和對控制的理解?
switch( nStatus ){
case Inital:
m_pMemberData = pParam;//初始化邦定的用戶數(shù)據(jù)?
break;
case Start:
m_nWorkStep = 0; //一般令第0步作為開始較易理解?
break;
case Stop:
m_nWorkStep = -1000;//停止?
break;
case …//其它略去?
default: break;
}
return m_nWorkStep;//返回值可自定?
}
?
int CAction::Run( void *pParam )
{//以下為一個動作不斷返回,直至接受到停止指令?
switch( m_nWorkStep ){
case 0:
if( IsMove() ) break; //正在忙,不執(zhí)行?
Move(1600); //正向移動1600單位?
m_nWorkStep++; //跳至下一步?
break;
case 1:
if( IsMove() ) break; //正在忙,不執(zhí)行?
Move(-1600); //反向移動1600單位?
m_nWorkStep=0; //再回至第0步?
break;
case –1000: break;//停止?
default: break;//此句最好有,以便于理解?
}
return m_nWorkStep; //返回值可自定?
}
對于Run函數(shù)須謹記一點:不要有長時間的循環(huán)操作,更不要有死循環(huán),否則另外設備無法正確動作,因為另外的設備Run函數(shù)無法被激活執(zhí)行,心臟無法跳動,只好等死吧!?
當然,還須擔心一點:多個設備同時執(zhí)行時,其實時性會下降一點,若沒有特別高的要求,在現(xiàn)今CPU狂奔的時代,以上程序框架足已滿足90%的需求,還是有學習和理解的必要吧!?
?
?
以上的框架出來了,那么如何操作使設備驅(qū)動起來呢,不管在DOS亦或在Window系統(tǒng)環(huán)境里,以下的思路都一樣,參見偽代碼如下:?
?
const int nSize=10; //定義10臺設備對象?
CAction exeArray[nSize];
BOOL bRunning=FALSE;//控制整個驅(qū)動的標志?
?
Void OnRunAction()//啟動函數(shù)?
{
//初始化一下?
for( int i(0); i〈nSize; i++)
exeArray[i].Control( CAction::Inital, ( void *)userData );//userData由用戶選擇?
?
bRunning = true;//外部控制變量?
for( i=0; bRunning; i=(i+1)%nSize)//給予運行時間,即是驅(qū)動了?
{
exeArray[i].Run(( void *)userData); //不斷驅(qū)動Action設備(即激活心臟)?
DoEvents();//做其它事情,如系統(tǒng)消息檢測,以防止死機?
}
}
?
DoEvents函數(shù)功能的詳解可參見本人的〈〈DMC1000控制卡不能響應系統(tǒng)消息〉〉,上面有不同編程工具下的實現(xiàn)源代碼。?
?
void OnControlAction()
{
for( int i(0); i〈 nSize; i++)
exeArray[i].Control( Caction::(/*用戶想要的動作*/), ( void *)userData);
}
?
void OnOver()
{
bRunning = FALSE;//結(jié)束驅(qū)動?
}
?
注意:有很多程序員在不同編譯器下,需要不同的OnRunAction函數(shù)版本,以上實現(xiàn)最為簡單,但有一些較特殊。比如:在程序啟動時,就驅(qū)動所有設備,這點本人在VC6.0環(huán)境有過經(jīng)驗,可以分享一下,其它環(huán)境下,還需同胞們一起去努力發(fā)現(xiàn)。?
?
還有一點聲明,多線程技術也不是一無是處,在花費時間較頻繁的通訊編程方面,它的優(yōu)勢,還是有相當?shù)恼T惑力。
?
?
?
雷泰運動控制卡應用程序開發(fā)注意事項
概述:
??對于一些初次使用雷泰運動控制產(chǎn)品的客戶,由于對本公司產(chǎn)品的特點以及程序開發(fā)流程不夠熟悉,在應用程序的開發(fā)過程中,難免會疏漏一些細節(jié),從而產(chǎn)生各種問題,浪費很多寶貴的時間。
??如果在應用程序開發(fā)前,就可以考慮到那些既重要又容易疏漏的細節(jié),這樣可以避免很多不必要的問題產(chǎn)生,從而大大縮短程序的開發(fā)周期。本文總結(jié)以往的經(jīng)驗,以雷泰運動控制卡DMC2410B為例,為客戶在開發(fā)應用程序時的初始化過程給出了一些參考與建議(其他產(chǎn)品與此類似),其中包括運動控制卡的初始化、特殊參數(shù)的設置及各種信號的設置,如圖1虛線框內(nèi)所示,這些處理過程必須加載至應用程序的初始化過程中,不同編程環(huán)境下,應用程序的初始化過程略有不同,例如在VB6.0編程環(huán)境下,須在Form_Load()函數(shù)中做程序的初始化處理,而在VC6.0編程環(huán)境下,須在OnInitDialog()函數(shù)中做程序的初始化處理。
?
?
?
圖1 DMC2410B控制卡應用程序開發(fā)流程
??圖1所示的控制卡初始化過程中,實線框內(nèi)所示的參數(shù)設置或特殊信號的設置必須在初始化過程中加以處理,而虛線框內(nèi)的信號在未選擇使用時,可以不用設置,而當選擇使用這些信號時,必須進行正確設置。下面對這些初始化過程的方法及必要性做出簡要的說明。
一、?初始化運動控制卡
相關函數(shù):WORD d2410_board_init (void)
函數(shù)功能:為控制卡分配系統(tǒng)資源并初始化控制卡;
??若在應用程序中未初始化控制卡,則系統(tǒng)無法為控制卡分配資源,導致控制卡無法正常使用,程序在運行時提示錯誤,彈出如圖2所示對話框:
?
圖2 未初始化控制卡時的錯誤提示
?
??注意:程序在結(jié)束運行時,必須關閉運動控制卡,以釋放系統(tǒng)資源,否則控制卡將一直占用系統(tǒng)資源,導致再次運行該應用程序時產(chǎn)生錯誤。關閉控制卡的方法及說明如下:
相關函數(shù):Void d2410_board_close (void)
??函數(shù)功能:?釋放控制卡占用的系統(tǒng)資源。當程序結(jié)束時必須調(diào)用此函數(shù),它與d2410_board_init() 函數(shù)是一個相反的過程。
二、?脈沖參數(shù)設置
??脈沖參數(shù)包括指令脈沖類型、脈沖輸出有效電平以及方向控制邏輯電平,這些參數(shù)需根據(jù)電機驅(qū)動器的類型及參數(shù)來設置,若設置錯誤時,則會造成控制卡正常發(fā)出脈沖,而電機無法正常運轉(zhuǎn)、運轉(zhuǎn)方向錯誤或只能朝同一個方向運轉(zhuǎn)等現(xiàn)象。以下為脈沖參數(shù)設置的相關函數(shù)及說明:
相關函數(shù):Void d2410_set_pulse_outmode (WORD axis, WORD outmode )
函數(shù)說明:設置指定軸的脈沖輸出方式。
??參數(shù)說明:axis 指定軸號
Outmode 脈沖輸出方式選擇,其值如圖3所示:
?
圖3 脈沖類型的選擇
?
三、?特殊信號的設置
??特殊信號包括限位信號、原點信號、急停信號等,對這些特殊信號未進行設置或設置錯誤時,會導致控制卡無法正常輸出脈沖、電機無法正常運轉(zhuǎn)、原點回歸錯誤等現(xiàn)象,下面分別對這些信號的設置及用途加以說明:
1. 限位信號的設置
??相關函數(shù):Void d2410_config_EL_MODE (WORD axis, WORD el_mode)
函數(shù)功能:設置限位信號的有效電平及制動方式。
參數(shù)說明:axis 指定軸號;
el_mode 限位信號的有效電平和制動方式:
??0-立即停、低電平有效;1-減速停、低電平有效;
??2-立即停、高電平有效;3-減速停、高電平有效;
??限位信號的有效電平需根據(jù)所使用的限位開關的類型進行設置,DMC2410B控制卡默認為低電平有效,若有效電平設置錯誤,則控制卡會認為相應軸遇到限位信號而停止輸出脈沖。
2. 原點信號的設置
相關函數(shù):Void d2410_set_HOME_pin_logic (WORD axis, WORD org_logic, WORD filter)
函數(shù)功能:設置原點信號的有效電平以及允許/禁止濾波功能。
參數(shù)說明:axis 指定軸號;
org_logic 原點信號的有效電平:0-低電平有效;1-高電平有效;
filter 允許/禁止濾波功能:0-禁止;1-允許;
??原點信號的有效電平需根據(jù)所使用的原點開關的類型進行設置,DMC2410B控制卡默認為低電平有效,若有效電平設置錯誤,則會導致回原點運動無法啟動、回原點方向相反、未遇到原點信號時回原點動作立即結(jié)束等現(xiàn)象。
3. 急停信號的設置
??相關函數(shù):Void d2410_config_EMG_PIN (WORD cardno, WORD enable, WORD emg_logic)
函數(shù)功能:急停信號設置。急停信號有效時會立即停止所有軸脈沖的輸出。
參數(shù)說明:cardno 卡號
enable 電平設置使能:0-電平設置無效;1-電平設置有效;
emg_logic 急停信號的有效電平:0-低電平有效;1-高電平有效;
??急停信號的有效電平需根據(jù)所使用的急停開關的類型進行設置,DMC2410B控制卡默認為低電平有效,若有效電平設置錯誤,則控制卡會認為收到急停信號而停止所有軸的脈沖輸出。
四、?伺服專用信號的設置
??伺服專用信號是針對伺服電機和驅(qū)動器而設置的,包括伺服使能信號、報警信號、誤差清除信號、零相信號、編碼器反饋輸入模式等,當無特殊要求時,可以選擇不用控制卡去控制這些信號,但若選擇使用這些信號時,必須在程序的初始化過程中進行正確的設置,否則會導致控制卡無法正常輸出脈沖、伺服電機無法正常運轉(zhuǎn)、編碼器反饋計數(shù)無法讀取或計數(shù)錯誤等現(xiàn)象,下面分別對這些信號的設置加以說明:
1. 伺服使能(Sevon)信號的設置
??伺服使能信號的有效電平是通過控制卡的撥碼開關進行設置,如圖4所示,DMC2410B控制卡默認為高電平有效,若此電平設置錯誤,則會出現(xiàn)控制卡正常輸出脈沖,而伺服電機的軸未鎖緊而無法運轉(zhuǎn)的情況,此信號端子可用軟件控制,其相關函數(shù)及說明如下:
相關函數(shù):Void d2410_write_SEVON_PIN (WORD axis, WORD on_off);
函數(shù)功能:輸出對指定軸的伺服使能端子的控制
參數(shù)說明:axis 指定軸號
on_off 設定伺服使能電平狀態(tài):0-低電平;1-高電平。
注意:其中SEVON 輸出口初始狀態(tài)可選, 當撥碼開關對應的位置為OFF后, 則SEVON 的電平邏輯會取反。
4
圖4 伺服使能信號電平的設置
2. 伺服報警(Alarm)信號的設置
相關函數(shù):Void d2410_config_ALM_PIN (WORD axis, WORD alm_logic, WORD alm_action)
函數(shù)功能:設置伺服報警信號的邏輯電平及其工作方式
參數(shù)說明:axis 指定軸號
alm_logic ALM信號的有效電平:0-低電平有效;1-高電平有效;
alm_action ALM信號的制動方式:0-立即停止;1-減速停止;
ALM信號的有效電平需根據(jù)伺服驅(qū)動器進行設置,DMC2410B控制卡默認為低電平有效,若此電平設置錯誤,則控制卡會認為相應軸發(fā)出報警信號而停止輸出脈沖。
3. 伺服誤差清除(ERC)信號的設置
相關函數(shù):Void d2410_config_ERC_PIN (WORD axis, WORD enable, WORD erc_logic,
WORD erc_width, WORD erc_off_time)
函數(shù)功能:設置允許/禁止ERC 信號及其有效電平和輸出方式
參數(shù)說明:axis 指定軸號
enable: ?0-不自動輸出ERC 信號
1-自動輸出ERC 信號
erc_logic 設置ERC信號的有效電平:0-低電平有效;1-高電平有效;
erc_width ERC信號的有效輸出寬度:單位:us
erc_off_time ERC信號的關斷時間:單位:us
??伺服電機是根據(jù)位置誤差計數(shù)器運轉(zhuǎn)的,若將位置誤差計數(shù)器清零,則伺服電機立即停止運行,而誤差清除(ERC)信號便是用來清除位置誤差計數(shù)器的,若此信號的模式及有效電平設置錯誤,當控制卡正常輸出脈沖時,而伺服驅(qū)動器的位置誤差計數(shù)器一直處于清零狀態(tài),故電機無法運行。
?
4. 伺服零相(EZ)信號的設置
相關函數(shù):Void d2410_config_EZ_PIN (WORD axis, WORD ez_logic, WORD ez_mode)
函數(shù)功能:設置指定軸的?EZ信號的有效電平及其作用。
參數(shù)說明:axis 指定軸號
ez_logic EZ信號的有效電平:0-低電平有效;1-高電平有效;
ez_mode EZ信號的工作方式:0-EZ 信號無效;1-EZ 是計數(shù)器復位信號;
??EZ信號可以用來精確回原點運動及復位編碼器反饋位置計數(shù)器,若此信號的有效電平及工作方式設置錯誤,則控制卡會一直復位編碼器反饋位置計數(shù)器,導致無法讀取編碼器的反饋位置。
?
5. 編碼器反饋輸入模式的設置
相關函數(shù):Void d2410_counter_config (WORD axis, WORD mode)
函數(shù)功能:設置編碼器輸入口的計數(shù)方式。
參數(shù)說明:axis 指定軸號
??mode編碼器反饋輸入模式:
0-非?A/B相脈沖(脈沖+方向);
1-1倍?A/B相脈沖信號;
2-2倍?A/B相脈沖信號;
3-4倍?A/B相脈沖信號;
??DMC2410B控制卡的反饋位置計數(shù)器能接收兩種模式的脈沖輸入:非A/B相(脈沖+方向)模式和A/B相輸入模式,這需要根據(jù)編碼器的類型進行設置,若此模式設置錯誤,則會造成計數(shù)器無法計數(shù)或只能朝一個方向計數(shù)以及計數(shù)值錯誤等現(xiàn)象。
?總結(jié):
??綜上所述,若在應用程序的開發(fā)過程中未對控制卡進行正確初始化處理時,會給整個開發(fā)過程帶來很多不必要的麻煩,而細致全面的初始化過程則可以在程序開發(fā)的初期避免這些問題和麻煩的產(chǎn)生,從而使整個程序開發(fā)過程事半功倍!
??因此,建議大家自己創(chuàng)建一個初始化函數(shù),將上面描述的各項相關參數(shù)包容到其中,然后在應用程序里邊再調(diào)用,以方便當前項目開發(fā)和后續(xù)其他的項目開發(fā)。
?