水电站VC
在中小型電站系統(tǒng)就地控制中,比如水電站中如果我們要進(jìn)行各種設(shè)備控制的話,串口數(shù)量就可能比較多了,有的地方加上載波甚至可以達(dá)到10個(gè)以上,很多的解決方法是將某些功能設(shè)備并行接到一個(gè)串口上面盡量減少串口的數(shù)量,然后進(jìn)行數(shù)據(jù)采集的時(shí)候采取環(huán)的方法進(jìn)行。但是工業(yè)控制要求實(shí)時(shí)性比較高,比如報(bào)警和各種控制,如果不能在盡可能短的時(shí)間里面進(jìn)行處理可能引發(fā)大的后果,我們覺(jué)得還是應(yīng)該將各種不同設(shè)備接入不同的串口,比如水電站中間各個(gè)機(jī)組的PLC和機(jī)組的調(diào)速器通訊等就接入不同串口。如果某個(gè)相同設(shè)備數(shù)量很多,如溫度裝置,有的1個(gè)發(fā)電機(jī)組可能超過(guò)20個(gè)溫度點(diǎn),我們可以采用接入2個(gè)或者多個(gè)串口的方法處理。
????為了使初學(xué)者能夠更容易看懂串口通訊的處理過(guò)程,我采用援助非洲剛果(布)姆古古魯水電站的溫度表為實(shí)例進(jìn)行程序的分析。在我們這個(gè)項(xiàng)目中有4臺(tái)發(fā)電機(jī)組,每個(gè)機(jī)組溫度表有20個(gè)點(diǎn)。由于這個(gè)與上位機(jī)通訊串口安排極多,我們只能將20個(gè)溫度表并行接入串口進(jìn)行通訊。在進(jìn)行硬件通訊之前我們首先要看懂改硬件的通訊協(xié)議。
通訊協(xié)議就是上位機(jī)向改外圍設(shè)備進(jìn)行讀取數(shù)據(jù)和進(jìn)行某種功能控制時(shí)候的一系列指令和外圍設(shè)備返回上位機(jī)的各數(shù)據(jù)位代表的意思。比如那個(gè)位是控制碼,哪個(gè)位是數(shù)據(jù),是什么數(shù)據(jù)等。
????首先啟動(dòng)VC新建一個(gè)給予SDI的工程,然后加入SerialPort類。由于要進(jìn)行多串口通訊,我們需要對(duì)SerialPort進(jìn)行一些簡(jiǎn)單的修改,由于在與硬件通訊過(guò)程中一般通訊協(xié)議都采用BYTE類型數(shù)據(jù)傳送,我們可以將改類中間的發(fā)送和接收數(shù)據(jù)類型修改成為BYTE類型。我修改了下面部分內(nèi)容,詳細(xì)改動(dòng)請(qǐng)見(jiàn)附錄提供的SERIALPORT類。
//
// Write a string to the port
//
void CSerialPort::WriteToPort(BYTE bWriteBuffer[],int nWriteBufferSize)
{????????
????assert(m_hComm != 0);
????int nSize = sizeof(bWriteBuffer)/sizeof(BYTE);
????m_nWriteBufferSize = nWriteBufferSize;
????for(int i = 0 ; i < nWriteBufferSize ; i ++)
????????m_bWriteBuffer[i] = bWriteBuffer[i];
????// set event for write
????SetEvent(m_hWriteEvent);
}
。。。。。
????
由于我們改串口接入了20臺(tái)溫度設(shè)備,在進(jìn)行通訊的時(shí)候是通過(guò)發(fā)送某個(gè)地址的設(shè)備命令進(jìn)行讀取數(shù)據(jù)。我們首先對(duì)硬件設(shè)置相應(yīng)的地址,這里我們?cè)O(shè)置0到19號(hào)地址。采集的時(shí)候采用循環(huán)的方式從0號(hào)地址向19號(hào)地址進(jìn)行讀取數(shù)據(jù)。當(dāng)收到相應(yīng)的數(shù)據(jù)包的時(shí)候我們進(jìn)行相應(yīng)的地址的數(shù)據(jù)解包處理。然后發(fā)送下一個(gè)地址的要數(shù)據(jù)命令。當(dāng)?shù)刂窞樽詈笠慌_(tái)設(shè)備的時(shí)候我們將地址清0處理就可以了。但是如果我們這個(gè)20臺(tái)設(shè)備中間某一個(gè)或者多個(gè)設(shè)備由于故障或者電源沒(méi)開(kāi)的話,上述通訊就會(huì)出現(xiàn)問(wèn)題,我們發(fā)送沒(méi)有運(yùn)行的地址設(shè)備就會(huì)收不到相應(yīng)的報(bào)文,我們就不會(huì)發(fā)送下一個(gè)地址的要數(shù)據(jù)命令,這是程序就會(huì)不走下去了。解決方法可以是我們從外部去判斷是否對(duì)當(dāng)前地址的發(fā)送要數(shù)據(jù)命令和收到數(shù)據(jù)命令是否超時(shí)。如果超時(shí)就進(jìn)行跳過(guò)然后發(fā)送下一個(gè)地址要數(shù)據(jù)命令。當(dāng)出現(xiàn)規(guī)定幾個(gè)循環(huán)的時(shí)候進(jìn)行該設(shè)備的采集參數(shù)清0等工作這個(gè)就可以隨自己定義考慮了。具體實(shí)現(xiàn)如下:
定義SERIALPORT類對(duì)象,創(chuàng)建線程進(jìn)行通訊。
????CSerialPort m_Ports;
????int??nColtAddr,這個(gè)用來(lái)存放當(dāng)前采集設(shè)備地址。nColts;這個(gè)用來(lái)存放當(dāng)前緩沖區(qū)收到的字節(jié)數(shù)目
????HANDLE????m_pThread;外部控制線程
????BYTE m_RecBuff[1000];接收緩沖區(qū)
????float fVal[20];處理解包內(nèi)容,這里可以根據(jù)實(shí)際情況進(jìn)行定義。
啟動(dòng)串口監(jiān)視線程和外部控制線程
nColtAddr = 0 ;
????nColts = 0;
????if(m_Ports.InitPort(this,1,4800,'N',8,1,EV_RXCHAR|EV_RXFLAG,1024))
????{
????????this->m_Ports.StartMonitoring();啟動(dòng)監(jiān)視線程
????????SetCommVal();發(fā)送第一臺(tái)設(shè)備數(shù)據(jù)命令
????}????
????????下面是啟動(dòng)外部控制線程
????unsigned int nDummy;
????m_pThread=(HANDLE) _beginthreadex(NULL,0,CommThread,this,CREATE_SUSPENDED,&nDummy);//開(kāi)辟外部控制線程
????ResumeThread(m_pThread); 運(yùn)行線程
外部控制線程控制當(dāng)前設(shè)備發(fā)送要數(shù)據(jù)命令和收到數(shù)據(jù)報(bào)文是否超時(shí)
UINT??C××××View::CommThread(LPVOID pParam)
{
????C××××View *pView = (C××××View *)pParam;????
????while(1)
????{
????????CTime cNowTime = CTime::GetCurrentTime();
????????tNow = cNowTime.GetTime();
????????struct _timeb timebuffer;
????????_ftime(&timebuffer);
????????int nNowMillSecond = timebuffer.millitm;
????????///
????????tLast = cLastColtTime[0].GetTime();
????????if((tNow - tLast)*1000 + (nNowMillSecond - nMillSecond[0]) > 800)
????????????pView->SetCommVal();發(fā)送下一臺(tái)設(shè)備要數(shù)據(jù)命令或者進(jìn)行其他的相關(guān)處理
????????Sleep(100);
????}
}
發(fā)送串口數(shù)據(jù)命令,這里要根據(jù)外部設(shè)備的制定的通訊協(xié)議來(lái)進(jìn)行。這次溫度表采用的是ASCII的形式通訊。
void C××××View::SetCommVal()
{
????int HAddr,LAddr,m_Xnh;
????int nHAdd,nLAdd;
????nHAdd = ExchangeAscII((nColtAddr>>4)&0x0f);
????nLAdd = ExchangeAscII(nColtAddr&0x0f);
????m_Xnh = nHAdd^nLAdd^0x52^0x44;
????HAddr = ExchangeAscII((m_Xnh>>4)&0x0f);
????LAddr = ExchangeAscII(m_Xnh&0x0f);
????BYTE OutBuff[8] = {0x40,nHAdd,nLAdd,0x52,0x44,HAddr,LAddr,0x0d};
????m_Ports.WriteToPort(OutBuff,8);
????cLastColtTime = CTime::GetCurrentTime();
????nColtAddr++;
????if(nColtAddr > 19)//19 define max addr numbers
????????nColtAddr = 0;
}
ASCII碼的一些簡(jiǎn)單變換,我們進(jìn)行一下簡(jiǎn)單的封裝,方便調(diào)用:
BYTE C××××View::ExchangeAscII(BYTE bInput)
{
????BYTE bRef = 0;
????if(bInput > 9)????
????????bRef = bInput+0x37;
????else
????????bRef = bInput+0x30;
????return bRef;
}
BYTE C××××View::ExchangeAscIItoNormal(BYTE bInput)
{
????BYTE bRef = 0;
????if(bInput > 0x39)????
????????bRef = bInput-0x37;
????else
????????bRef = bInput-0x30;
????return bRef;
}
LONG C×××View::OnCommunication(WPARAM ch, LPARAM port)進(jìn)行數(shù)據(jù)處理,WPARAM,LPARAM類型是多態(tài)性數(shù)據(jù)(polymorphic data type),在WIN32中為32位,支持多種數(shù)據(jù)類型,根據(jù)需要自動(dòng)適應(yīng),這樣程序就有很強(qiáng)的適應(yīng)性。再次我們這里理解成為BYTE類型(與外圍設(shè)備通訊協(xié)議保持一致,方便解包)。每當(dāng)串口接收緩沖區(qū)內(nèi)有一個(gè)字符的時(shí)候,就會(huì)產(chǎn)生一個(gè)WM_COMM_RXCHAR消息,觸發(fā)OnCommunication函數(shù),下面我們可以根據(jù)我們的需要進(jìn)行解包處理了;
LONG CMy11View::OnCommunication(WPARAM ch, LPARAM port)
{
????if(port == 1)
????{
????????m_RecBuff[nColts] += (BYTE)(char *)(ch);
????????nColts++;
????????if(nColts == 24)這里根據(jù)通訊協(xié)議規(guī)定的發(fā)送定制要數(shù)據(jù)命令就會(huì)上傳24個(gè)字節(jié)的數(shù)據(jù)報(bào)文內(nèi)容。這里可以根據(jù)不同外部設(shè)備進(jìn)行不同的設(shè)置
????????{
????????????DataProcessTemp(m_RecBuff);處理解包
????????????nColts = 0;緩沖區(qū)指針清0,準(zhǔn)備接收下一臺(tái)設(shè)備數(shù)據(jù)
????????????ResetBuffVal();清空緩沖區(qū)內(nèi)容
????????????SetCommVal();????發(fā)送下一臺(tái)設(shè)備內(nèi)容????????
????????}
????}
????return 0;
}
數(shù)據(jù)解包處理,這里就必須根據(jù)外部設(shè)備定義的通訊協(xié)議來(lái)處理了。
void CMy11View::DataProcessTemp(BYTE m_Inbuff[])
{
????int nTempAddr = nColtAddr - 1;
????if(nTempAddr < 0)
????????nTempAddr = 19;
????int nHAdd,nLAdd;
????nHAdd = ExchangeAscII((nTempAddr>>4)&0x0f);
????nLAdd = ExchangeAscII(nTempAddr&0x0f);
????if(m_Inbuff[0] == 0x40)
????{
????????if(m_Inbuff[1] == nHAdd && m_Inbuff[2] == nLAdd)
????????{
????????????if(m_Inbuff[3] == 0x52 && m_Inbuff[4] == 0x44)
????????????{??
????????????????int nzTemp[5];
????????????????float fTemp;
????????????????nzTemp[0] = m_Inbuff[7];
????????????????nzTemp[1] = m_Inbuff[8];
????????????????nzTemp[2] = m_Inbuff[9];
????????????????nzTemp[3] = m_Inbuff[10];
????????????????for(int i = 0 ; i < 4; i ++)
????????????????{
????????????????????if(nzTemp[i] > 0x39)
????????????????????????nzTemp[i] -= 0x37;
????????????????????else
????????????????????????nzTemp[i] -= 0x30;
????????????????}
????????????????fTemp=float(nzTemp[1]+(nzTemp[0]<<4)+(nzTemp[3]<<8)+(nzTemp[2]<<12))/10;
???????????????? fVal[nTempAddr] = fTemp;
???????????????? RedrawWindow();
????????????}
????????}
????}
}
void CMy11View::ResetBuffVal()
{
????for(int i=0;i<1000;i++)
????????m_RecBuff[i] = 0;
}
至此,基本的通訊外圍程序基本完成,如果我們要擴(kuò)充多個(gè)串口多線程的話,我們可以做如下修改:
CSerialPort???????? m_Ports[20];
????BYTE????????????????m_RecBuff[20][1000];
????BYTE????????????????m_SendBuff[5][1000];
????int????????????????????nColts[20];
????int????????????????????nZBKType[24];
????int????????????????????nWrongCount[20][20];
????int????????????????????nColtAddr[20];
????HANDLE????????????????m_pThread;
//Protect Device
????if(this->m_Ports[0].InitPort(this,2,9600,'N',8,1,EV_RXCHAR|EV_RXFLAG,1024))
????{????????
????????this->m_Ports[0].StartMonitoring();
????????SetComBufferVal(0);
????}
????//Diandu Device
????if(this->m_Ports[1].InitPort(this,4,1200,'E',8,1,EV_RXCHAR|EV_RXFLAG,1024))
????{????????
????????this->m_Ports[1].StartMonitoring();
????????SetComBufferVal(1);
????}
我們對(duì)各種發(fā)送命令函數(shù)進(jìn)行載入形參的方法來(lái)解決
總結(jié)
- 上一篇: 学习笔记——Linux简介以及ubunt
- 下一篇: c语言拓扑多边形自动生成,拓扑多边形生成