水电站VC
在中小型電站系統就地控制中,比如水電站中如果我們要進行各種設備控制的話,串口數量就可能比較多了,有的地方加上載波甚至可以達到10個以上,很多的解決方法是將某些功能設備并行接到一個串口上面盡量減少串口的數量,然后進行數據采集的時候采取環的方法進行。但是工業控制要求實時性比較高,比如報警和各種控制,如果不能在盡可能短的時間里面進行處理可能引發大的后果,我們覺得還是應該將各種不同設備接入不同的串口,比如水電站中間各個機組的PLC和機組的調速器通訊等就接入不同串口。如果某個相同設備數量很多,如溫度裝置,有的1個發電機組可能超過20個溫度點,我們可以采用接入2個或者多個串口的方法處理。
????為了使初學者能夠更容易看懂串口通訊的處理過程,我采用援助非洲剛果(布)姆古古魯水電站的溫度表為實例進行程序的分析。在我們這個項目中有4臺發電機組,每個機組溫度表有20個點。由于這個與上位機通訊串口安排極多,我們只能將20個溫度表并行接入串口進行通訊。在進行硬件通訊之前我們首先要看懂改硬件的通訊協議。
通訊協議就是上位機向改外圍設備進行讀取數據和進行某種功能控制時候的一系列指令和外圍設備返回上位機的各數據位代表的意思。比如那個位是控制碼,哪個位是數據,是什么數據等。
????首先啟動VC新建一個給予SDI的工程,然后加入SerialPort類。由于要進行多串口通訊,我們需要對SerialPort進行一些簡單的修改,由于在與硬件通訊過程中一般通訊協議都采用BYTE類型數據傳送,我們可以將改類中間的發送和接收數據類型修改成為BYTE類型。我修改了下面部分內容,詳細改動請見附錄提供的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臺溫度設備,在進行通訊的時候是通過發送某個地址的設備命令進行讀取數據。我們首先對硬件設置相應的地址,這里我們設置0到19號地址。采集的時候采用循環的方式從0號地址向19號地址進行讀取數據。當收到相應的數據包的時候我們進行相應的地址的數據解包處理。然后發送下一個地址的要數據命令。當地址為最后一臺設備的時候我們將地址清0處理就可以了。但是如果我們這個20臺設備中間某一個或者多個設備由于故障或者電源沒開的話,上述通訊就會出現問題,我們發送沒有運行的地址設備就會收不到相應的報文,我們就不會發送下一個地址的要數據命令,這是程序就會不走下去了。解決方法可以是我們從外部去判斷是否對當前地址的發送要數據命令和收到數據命令是否超時。如果超時就進行跳過然后發送下一個地址要數據命令。當出現規定幾個循環的時候進行該設備的采集參數清0等工作這個就可以隨自己定義考慮了。具體實現如下:
定義SERIALPORT類對象,創建線程進行通訊。
????CSerialPort m_Ports;
????int??nColtAddr,這個用來存放當前采集設備地址。nColts;這個用來存放當前緩沖區收到的字節數目
????HANDLE????m_pThread;外部控制線程
????BYTE m_RecBuff[1000];接收緩沖區
????float fVal[20];處理解包內容,這里可以根據實際情況進行定義。
啟動串口監視線程和外部控制線程
nColtAddr = 0 ;
????nColts = 0;
????if(m_Ports.InitPort(this,1,4800,'N',8,1,EV_RXCHAR|EV_RXFLAG,1024))
????{
????????this->m_Ports.StartMonitoring();啟動監視線程
????????SetCommVal();發送第一臺設備數據命令
????}????
????????下面是啟動外部控制線程
????unsigned int nDummy;
????m_pThread=(HANDLE) _beginthreadex(NULL,0,CommThread,this,CREATE_SUSPENDED,&nDummy);//開辟外部控制線程
????ResumeThread(m_pThread); 運行線程
外部控制線程控制當前設備發送要數據命令和收到數據報文是否超時
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();發送下一臺設備要數據命令或者進行其他的相關處理
????????Sleep(100);
????}
}
發送串口數據命令,這里要根據外部設備的制定的通訊協議來進行。這次溫度表采用的是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碼的一些簡單變換,我們進行一下簡單的封裝,方便調用:
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)進行數據處理,WPARAM,LPARAM類型是多態性數據(polymorphic data type),在WIN32中為32位,支持多種數據類型,根據需要自動適應,這樣程序就有很強的適應性。再次我們這里理解成為BYTE類型(與外圍設備通訊協議保持一致,方便解包)。每當串口接收緩沖區內有一個字符的時候,就會產生一個WM_COMM_RXCHAR消息,觸發OnCommunication函數,下面我們可以根據我們的需要進行解包處理了;
LONG CMy11View::OnCommunication(WPARAM ch, LPARAM port)
{
????if(port == 1)
????{
????????m_RecBuff[nColts] += (BYTE)(char *)(ch);
????????nColts++;
????????if(nColts == 24)這里根據通訊協議規定的發送定制要數據命令就會上傳24個字節的數據報文內容。這里可以根據不同外部設備進行不同的設置
????????{
????????????DataProcessTemp(m_RecBuff);處理解包
????????????nColts = 0;緩沖區指針清0,準備接收下一臺設備數據
????????????ResetBuffVal();清空緩沖區內容
????????????SetCommVal();????發送下一臺設備內容????????
????????}
????}
????return 0;
}
數據解包處理,這里就必須根據外部設備定義的通訊協議來處理了。
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;
}
至此,基本的通訊外圍程序基本完成,如果我們要擴充多個串口多線程的話,我們可以做如下修改:
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);
????}
我們對各種發送命令函數進行載入形參的方法來解決
總結
- 上一篇: 学习笔记——Linux简介以及ubunt
- 下一篇: 对症下药:删除顽固文件夹方法汇总