VC++ CLR 串口讀寫(xiě)上位機(jī)例程
使用VC++ .net4.0編寫(xiě)的串口讀寫(xiě)上位機(jī),實(shí)現(xiàn)基本的配置讀取,寫(xiě)入,以及連續(xù)的實(shí)時(shí)數(shù)據(jù)讀取顯示,波形顯示(采用異步操作,連續(xù)讀取實(shí)時(shí)數(shù)據(jù)的過(guò)程中,可以讀寫(xiě)配置)。
1.總體界面
功能:系統(tǒng)串口選擇,串口連接,通信地址設(shè)置,采集周期設(shè)置功能,讀取配置,寫(xiě)入配置。
功能:實(shí)時(shí)數(shù)據(jù)讀取并顯示,同步顯示波形數(shù)據(jù)。
2.串口獲取
在?toolStripComboBox1 控件的 DropDown事件中,獲取系統(tǒng)的串口,并顯示。
//刷新串口 private:?System::Void?toolStripComboBox1_DropDown(System::Object^??sender,?System::EventArgs^??e)?{ this->UI_RefreshCom(); //刷新串口 }
//刷新串口 void?CLASS_NAME::UI_RefreshCom(void) { String?^SelectUartName; bool?isDefault?=?true; try { SelectUartName?=?this->_UART_ComboBox->SelectedItem->ToString();//獲取上次的串口號(hào) this->UI_comboBoxGetCom(); //重新刷新串口 //查找刷新前的串口是否存在,如果存在則選擇之前的串口 for?(int?i?=?0;?i?<?this->_UART_ComboBox->Items->Count;?i++) { if?(this->_UART_ComboBox->Items[i]->ToString()?==?SelectUartName)//找到了之前的串口 { this->_UART_ComboBox->SelectedIndex?=?i; isDefault?=?false; break; } } if?(isDefault?==?true)?//需要選擇默認(rèn)的 { if?(this->_UART_ComboBox->Items->Count?!=?0) //如果串口數(shù)量不為0,則選中第一個(gè) { this->_UART_ComboBox->SelectedIndex?=?0; //默認(rèn)選擇第一個(gè)串口 } } } catch?(Exception^?e) { SYS_LOG.Write(__FILE__?+?__LINE__?+?"?t:"?+?e->Message?+?e->StackTrace); } }
3.連接或者關(guān)閉串口,按鈕事件
?//連接或關(guān)閉串口 private:?System::Void?toolStripButton1_Click(System::Object^??sender,?System::EventArgs^??e)?{ this->UI_OpenAndCloseUart_Button_Click();//連接或關(guān)閉串口 }
//連接或關(guān)閉串口 void?CLASS_NAME::UI_OpenAndCloseUart_Button_Click(void) { String?^SelectUartName; bool?isDefault?=?true; DWORD?Status; WCHAR?ComName[8]; char?*pComName; try { System::ComponentModel::ComponentResourceManager^??resources?=?(gcnew?System::ComponentModel::ComponentResourceManager(MainForm::typeid)); if?(g_mUartHandle?==?0) //當(dāng)前串口沒(méi)有連接,開(kāi)始連接串口 { this->toolStripStatusLabel1->Text?=?"未連接"; //底部狀態(tài) if?(g_mUART.UartNum?==?0) //沒(méi)有串口,無(wú)法連接 { System::Windows::Forms::MessageBox::Show("沒(méi)有串口,無(wú)法連接!",?"錯(cuò)誤",?System::Windows::Forms::MessageBoxButtons::OK, System::Windows::Forms::MessageBoxIcon::Error); return; } pComName?=?USER_LIB.StringToChar(this->_UART_ComboBox->SelectedItem->ToString()); //獲取當(dāng)前選擇的串口名稱(chēng) if?(strlen(pComName)?>?6)?pComName[6]?=?0; //限制串口名稱(chēng)長(zhǎng)度 USER_LIB.CharToWchar(pComName,?ComName); g_mUartHandle?=?g_mUART.UART_Init(ComName,?9600,?4096,?&Status); if?(g_mUartHandle?toolStripStatusLabel1->Text?=?"連接成功"; //底部狀態(tài) this->_UART_ComboBox->Enabled?=?false; //串口連接后,禁用串口選擇 this->tabControl1->Enabled?=?true; //連接成功了,允許配置 //按鈕圖片變?yōu)橐呀?jīng)連接狀態(tài) this->toolStripButton1->Image?=?(cli::safe_cast(resources->GetObject(L"toolStripButton2.Image"))); } else?//斷開(kāi)連接 { g_mUART.UART_Close(g_mUartHandle); //斷開(kāi)連接 g_mUartHandle?=?0; //句柄清零 this->toolStripStatusLabel1->Text?=?"未連接"; //底部狀態(tài) this->_UART_ComboBox->Enabled?=?true; //串口關(guān)閉后,啟用串口選擇 //顯示關(guān)閉圖標(biāo) this->toolStripButton1->Image?=?(cli::safe_cast(resources->GetObject(L"toolStripButton1.Image"))); this->tabControl1->Enabled?=?false; //連接斷開(kāi),不允許配置 } } catch?(Exception^?e) { SYS_LOG.Write(__FILE__?+?__LINE__?+?"?t:"?+?e->Message?+?e->StackTrace); } }
4.讀取配置 按鈕事件
?//讀取配置 private:?System::Void?button1_Click(System::Object^??sender,?System::EventArgs^??e)?{ this->UI_ReadConfig_Button_Click(); //讀取配置 }
//讀取配置 void?CLASS_NAME::UI_ReadConfig_Button_Click(void) { try { //禁用界面,并彈出讀取中窗口提示 this->toolStrip1->Enabled?=?false; this->tabControl1->Enabled?=?false; this->mMessageControl->Visible?=?true; //顯示讀取中提示窗口 this->isReadConfig?=?true; //異步命令,需要讀取配置 } catch?(Exception^?e) { SYS_LOG.Write(__FILE__?+?__LINE__?+?"?t:"?+?e->Message?+?e->StackTrace); System::Windows::Forms::MessageBox::Show(e->Message,?"錯(cuò)誤",?System::Windows::Forms::MessageBoxButtons::OK, System::Windows::Forms::MessageBoxIcon::Error); } }
讀取配置采用異步操作,異步線程中不停的判斷?this->isReadConfig 是否有效,如果有效將會(huì)進(jìn)行異步的讀取操作。
5.寫(xiě)入配置 按鈕事件
?//寫(xiě)入配置 private:?System::Void?button2_Click(System::Object^??sender,?System::EventArgs^??e)?{ this->UI_WriteConfig_Button_Click();//寫(xiě)入配置 }
//寫(xiě)入配置 void?CLASS_NAME::UI_WriteConfig_Button_Click(void) { try { //先從界面獲取配置到全局緩沖區(qū)中 this->UI_GetConfig(this->pWriteConfig); //如果沒(méi)有讀取過(guò)配置,則提示用戶,應(yīng)該先讀取配置 if?(this->isNotReadConfig?==?true) { System::Windows::Forms::MessageBox::Show("請(qǐng)先讀取配置,再寫(xiě)入!",?"警告",?System::Windows::Forms::MessageBoxButtons::OK, System::Windows::Forms::MessageBoxIcon::Warning); return; } //檢查配置 if?(this->CheckConfig(this->pWriteConfig)?==?false)//檢查配置 { System::Windows::Forms::MessageBox::Show("無(wú)效的配置",?"錯(cuò)誤",?System::Windows::Forms::MessageBoxButtons::OK, System::Windows::Forms::MessageBoxIcon::Error); return; } //禁用界面,并彈出讀取中窗口提示 this->toolStrip1->Enabled?=?false; this->tabControl1->Enabled?=?false; this->mMessageControl->Visible?=?true; //顯示操作中提示窗口 this->isWriteConfig?=?true; //異步命令,需要寫(xiě)入配置 } catch?(Exception^?e) { SYS_LOG.Write(__FILE__?+?__LINE__?+?"?t:"?+?e->Message?+?e->StackTrace); System::Windows::Forms::MessageBox::Show(e->Message,?"錯(cuò)誤",?System::Windows::Forms::MessageBoxButtons::OK, System::Windows::Forms::MessageBoxIcon::Error); } }
同讀取配置一樣,采用異步操作。
6.實(shí)時(shí)數(shù)據(jù)讀取 按鈕事件
?//實(shí)時(shí)數(shù)據(jù)讀取開(kāi)關(guān) private:?System::Void?button3_Click(System::Object^??sender,?System::EventArgs^??e)?{ this->UI_ReadRealData_Button_Click(); //讀取實(shí)時(shí)數(shù)據(jù) }
//讀取實(shí)時(shí)數(shù)據(jù) void?CLASS_NAME::UI_ReadRealData_Button_Click(void) { try { if?(this->isReadRealData?==?false)?//沒(méi)有讀取-開(kāi)始讀取 { this->isReadRealData?=?true; this->button3->Text?=?"讀取中..."; } else?//已經(jīng)開(kāi)啟了,關(guān)閉讀取 { this->isReadRealData?=?false; this->button3->Text?=?"讀取關(guān)閉"; } } catch?(Exception^?e) { SYS_LOG.Write(__FILE__?+?__LINE__?+?"?t:"?+?e->Message?+?e->StackTrace); System::Windows::Forms::MessageBox::Show(e->Message,?"錯(cuò)誤",?System::Windows::Forms::MessageBoxButtons::OK, System::Windows::Forms::MessageBoxIcon::Error); } }
同讀取配置一樣,采用異步操作。
7.異步操作介紹
異步操作采用的?System::ComponentModel::BackgroundWorker^ mBackgroundWorker; 異步工作線程實(shí)現(xiàn)的,工作中線程屬于后臺(tái)線程,在主線程關(guān)閉后會(huì)自動(dòng)停止。
//異步線程初始化 void?CLASS_NAME::BackgroundWorker_Init(void) { this->mBackgroundWorker?=?(gcnew?System::ComponentModel::BackgroundWorker()); //異步線程初始化 this->mBackgroundWorker->WorkerReportsProgress?=?true; //運(yùn)行更新?tīng)顟B(tài) this->mBackgroundWorker->WorkerSupportsCancellation?=?true; //允許異步結(jié)束 this->mBackgroundWorker->DoWork?+=?gcnew?System::ComponentModel::DoWorkEventHandler(this,?&CLASS_NAME::BackgroundWorker_DoWork); this->mBackgroundWorker->ProgressChanged?+=?gcnew?System::ComponentModel::ProgressChangedEventHandler(this,?&CLASS_NAME::BackgroundWorker_ProgressChanged); this->mBackgroundWorker->RunWorkerCompleted?+=?gcnew?System::ComponentModel::RunWorkerCompletedEventHandler(this,?&CLASS_NAME::BackgroundWorker_RunWorkerCompleted); this->mBackgroundWorker->RunWorkerAsync(); //開(kāi)始執(zhí)行 }
異步線程的初始化主要是添加一些事件,比如線程核心函數(shù),線程狀態(tài)更新回調(diào)函數(shù),線程結(jié)束后回調(diào)函數(shù),是否允許更新?tīng)顟B(tài),此處必須允許更新?tīng)顟B(tài),在異步線程中是不能直接訪問(wèn)UI的,但是使用狀態(tài)更新可以實(shí)現(xiàn)異步刷新的目的,比如在異步線程中讀取配置,讀取車(chē)成功后觸發(fā)一個(gè)狀態(tài),在BackgroundWorker_ProgressChanged中刷新界面。
//線程-運(yùn)行核心 System::Void?CLASS_NAME::BackgroundWorker_DoWork(System::Object^??sender,?System::ComponentModel::DoWorkEventArgs^??e) { char?*pError; CONFIG_TYPE?TempConfig; //臨時(shí)配置緩沖區(qū) try { while?(1) { try { this->dt?=?System::DateTime::Now; //更新系統(tǒng)時(shí)間 if?(g_mUartHandle?isReadConfig?==?true) //需要讀取配置 { this->isReadConfig?=?false; //清除狀態(tài) if?(ReadConfig(&TempConfig,?&pError)?==?true) //讀取成功了 { memcpy(this->pReadConfig,?&TempConfig,?sizeof(CONFIG_TYPE)); //配置讀取成功了 this->mBackgroundWorker->ReportProgress(0); //讀取配置成功 } else?//讀取失敗了 { this->mStringBuilderError->Clear(); //清空字符 this->mStringBuilderError->Append("讀取配置失敗,錯(cuò)誤:"); this->mStringBuilderError->Append(CharToString(pError)); this->mBackgroundWorker->ReportProgress(1); //讀取配置失敗 } } else?if?(this->isWriteConfig?==?true)?//寫(xiě)配置 { this->isWriteConfig?=?false; //清除寫(xiě)命令 if?(this->CheckConfig(this->pWriteConfig)?==?false)//配置有誤,不能寫(xiě)入 { this->mStringBuilderError->Clear(); //清空字符 this->mStringBuilderError->Append("配置有誤,不允許寫(xiě)入"); this->mBackgroundWorker->ReportProgress(3); //寫(xiě)配置失敗 } else?//配置無(wú)誤,寫(xiě)入 { if?(this->WriteConfig(this->pWriteConfig,?&pError)?==?true) { this->mBackgroundWorker->ReportProgress(2); //寫(xiě)配置成功 } else?//寫(xiě)入失敗 { this->mStringBuilderError->Clear(); //清空字符 this->mStringBuilderError->Append("寫(xiě)入配置失敗,錯(cuò)誤:"); this->mStringBuilderError->Append(CharToString(pError)); this->mBackgroundWorker->ReportProgress(3); //寫(xiě)入配置失敗 } } } else?if?(this->isReadRealData?==?true) //需要讀取實(shí)時(shí)數(shù)據(jù) { if?(this->ReadRealData(this->pRealData,?&pError)?==?true) //讀取成功了 { this->mBackgroundWorker->ReportProgress(4); //讀取配置成功 } else?//讀取失敗了 { this->mStringBuilderError->Clear(); //清空字符 this->mStringBuilderError->Append("讀取實(shí)時(shí)數(shù)據(jù),錯(cuò)誤:"); this->mStringBuilderError->Append(CharToString(pError)); this->mBackgroundWorker->ReportProgress(5); //讀取配置失敗 } Sleep(500); } Sleep(100); } } catch?(Exception^?e) { SYS_LOG.Write(__FILE__?+?__LINE__?+?"t異步線程崩潰:"?+?e->Message?+?e->StackTrace); Sleep(3000); } } } catch?(Exception?^e1) { SYS_LOG.Write(__FILE__?+?__LINE__?+?"t:"?+?e1->Message?+?e1->StackTrace); } } //線程-狀態(tài)改變 System::Void?CLASS_NAME::BackgroundWorker_ProgressChanged(System::Object^??sender,?System::ComponentModel::ProgressChangedEventArgs^??e) { char?buff[24]; try { switch?(e->ProgressPercentage) { case?0: //讀取成功了 { this->toolStrip1->Enabled?=?true; this->tabControl1->Enabled?=?true; this->mMessageControl->Visible?=?false; //影藏讀取中提示窗口 this->UI_ShowConfig(this->pReadConfig); //顯示配置到界面 this->isNotReadConfig?=?false; //配置讀取過(guò),標(biāo)志清零 this->toolStripStatusLabel1->Text?=?"讀取配置成功"; System::Windows::Forms::MessageBox::Show("讀取配置成功!",?"提示",?System::Windows::Forms::MessageBoxButtons::OK, System::Windows::Forms::MessageBoxIcon::Information); }break; case?1: //讀取失敗了 { this->toolStrip1->Enabled?=?true; this->tabControl1->Enabled?=?true; this->mMessageControl->Visible?=?false; //影藏讀取中提示窗口 this->toolStripStatusLabel1->Text?=?this->mStringBuilderError->ToString(); System::Windows::Forms::MessageBox::Show(this->mStringBuilderError->ToString(),?"錯(cuò)誤",?System::Windows::Forms::MessageBoxButtons::OK, System::Windows::Forms::MessageBoxIcon::Error); }break; case?2://寫(xiě)配置成功 { this->toolStrip1->Enabled?=?true; this->tabControl1->Enabled?=?true; this->mMessageControl->Visible?=?false; //影藏讀取中提示窗口 this->toolStripStatusLabel1->Text?=?"寫(xiě)入配置成功"; System::Windows::Forms::MessageBox::Show("寫(xiě)入配置成功!",?"提示",?System::Windows::Forms::MessageBoxButtons::OK, System::Windows::Forms::MessageBoxIcon::Information); }break; case?3: //寫(xiě)入配置失敗 { this->toolStrip1->Enabled?=?true; this->tabControl1->Enabled?=?true; this->mMessageControl->Visible?=?false; //影藏讀取中提示窗口 this->toolStripStatusLabel1->Text?=?this->mStringBuilderError->ToString(); System::Windows::Forms::MessageBox::Show(this->mStringBuilderError->ToString(),?"錯(cuò)誤",?System::Windows::Forms::MessageBoxButtons::OK, System::Windows::Forms::MessageBoxIcon::Error); }break; case?4: //讀取成功了,顯示實(shí)時(shí)數(shù)據(jù) { this->UI_ShowRealData(this->pRealData); //顯示讀取到的實(shí)時(shí)數(shù)據(jù)到界面 this->toolStripStatusLabel1->Text?=?"讀取實(shí)時(shí)數(shù)據(jù)成功"; //底部狀態(tài)提示 }break; case?5: //讀取配置失敗了 { this->toolStripStatusLabel1->Text?=?this->mStringBuilderError->ToString(); }break; } } catch?(Exception^?e1) { SYS_LOG.Write(__FILE__?+?__LINE__?+?"t:"?+?e1->Message?+?e1->StackTrace); } } //線程-結(jié)束 System::Void?CLASS_NAME::BackgroundWorker_RunWorkerCompleted(System::Object^??sender,?System::ComponentModel::RunWorkerCompletedEventArgs^??e) { try { } catch?(Exception^?e1) { SYS_LOG.Write(__FILE__?+?__LINE__?+?"t:"?+?e1->Message?+?e1->StackTrace); } }
8.modbus-RTU
modbus-RTU協(xié)議使用了回調(diào)函數(shù),跟單片機(jī)中類(lèi)似的,此處我只需要實(shí)現(xiàn)底層的串口收發(fā)函數(shù),并初始化回調(diào)即可,注意在CLR程序中,托管的代碼必須使用類(lèi),但是托管的函數(shù)中不允許直接使用函數(shù)指針,此處我使用的C代碼(非類(lèi))來(lái)實(shí)現(xiàn)modbus所需的收發(fā)函數(shù)(函數(shù)是全局的,無(wú)需像類(lèi)需要先實(shí)例化)。
CommInterface.c
#include?"StdAfx.h" #include?"CommInterface.h" #include?"UART.h" #include?"SystemLog.h" #include?"modbus_rtu.h" UART_TYPE?g_mUART; //串口類(lèi) HANDLE?g_mUartHandle?=?0; //串口句柄 MODBUS_RTU?g_mModbus; //MODBUS-RTU?通信接口類(lèi) #define??BAUD_RATE 9600 //串口波特率 //串口發(fā)送函數(shù) bool?UART_SendData(BYTE?*pData,?DWORD?DataLen) { try { g_mUART.MYUART_ClearTxBuff(g_mUartHandle); //清空發(fā)送緩沖區(qū)? if?(g_mUART.MYUART_SendData(g_mUartHandle,?pData,?DataLen)?==?false) //調(diào)用串口發(fā)送數(shù)據(jù) { return?false; //串口錯(cuò)誤 } Sleep(DataLen?*?8?*?1000?/?BAUD_RATE); g_mUART.MYUART_ClearRxBuff(g_mUartHandle); //清除接收緩沖區(qū) return?true; } catch?(Exception^?e) { SYS_LOG.Write(__FILE__?+?__LINE__?+?"?t:"?+?e->Message?+?e->StackTrace); } return?false; } //清除接收緩沖區(qū) void?UART_ClearRxBuff(void) { g_mUART.MYUART_ClearRxBuff(g_mUartHandle); //清除接收緩沖區(qū) } //串口接收數(shù)據(jù) bool?UART_ReadData(BYTE?*pData,?DWORD?*DataLen) { DWORD?cnt?=?0; DWORD?TimeOut?=?500?/?50; //超時(shí)時(shí)間 DWORD?DelayCnt?=?0; //延時(shí)計(jì)數(shù)器,最大等待5秒 DWORD?PackDelay?=?(32?*?10?*?1000?*?2)?/?BAUD_RATE; //包延時(shí)間隔 try { //等待數(shù)據(jù)返回 do { cnt?=?g_mUART.MYUART_GetRxCnt(g_mUartHandle); //獲取接收到的數(shù)據(jù)長(zhǎng)度 Sleep(50); //延時(shí)10ms if?(cnt?==?g_mUART.MYUART_GetRxCnt(g_mUartHandle)) //完成接收數(shù)據(jù)了,退出等待 { TimeOut--; if?((cnt?>?0)?&&?(TimeOut?!=?0)) { if?(cnt?>?30) { Sleep(PackDelay); //收完后再等待200ms防止CH340這類(lèi)串口分包導(dǎo)致數(shù)據(jù)丟失,串口波特率不一樣時(shí)等待的實(shí)際會(huì)不一樣,大數(shù)據(jù)包等待的時(shí)間會(huì)更長(zhǎng) DelayCnt?+=?PackDelay; } Sleep(20); //收完后再等待20ms防止PL2303這類(lèi)串口分包導(dǎo)致數(shù)據(jù)丟失 TimeOut?=?1; //數(shù)據(jù)接收完畢,退出 DelayCnt?+=?20; } } DelayCnt?+=?50; if?(DelayCnt?>?5000)?break; //強(qiáng)制退出,5秒 }?while?(TimeOut); //等待完畢 if?(cnt?==?0)? //沒(méi)有接收到數(shù)據(jù) { *DataLen?=?0; //返回接收數(shù)據(jù)長(zhǎng)度 g_mUART.MYUART_ClearRxBuff(g_mUartHandle); //清除接收緩沖區(qū) return?true; //返回超時(shí) } //讀取數(shù)據(jù) if?(g_mUART.MYUART_ReadData(g_mUartHandle,?pData,?cnt)?==?-1)//讀取串口接收到的數(shù)據(jù) { *DataLen?=?0; //返回接收數(shù)據(jù)長(zhǎng)度 g_mUART.MYUART_ClearRxBuff(g_mUartHandle); //清除接收緩沖區(qū) return?false; //串口錯(cuò)誤 } *DataLen?=?cnt; //返回接收數(shù)據(jù)長(zhǎng)度 g_mUART.MYUART_ClearRxBuff(g_mUartHandle); //清除接收緩沖區(qū) return?true; //讀取數(shù)據(jù)成功 } catch?(Exception^?e) { SYS_LOG.Write(__FILE__?+?__LINE__?+?"?t:"?+?e->Message?+?e->StackTrace); } *DataLen?=?0; return?false; } //MODBUS通訊接口初始化 void?MODBUS_InterfaceInit(void) { //初始化Modbus-rtu的回調(diào)函數(shù)指針?? g_mModbus.InterfaceInit(UART_SendData,?UART_ReadData); }
CommInterface.h
#pragma?once #include?"UserLib.h" #include?"windows.h" #include?"UART.h" #include?"modbus_rtu.h" extern?UART_TYPE?g_mUART; //串口類(lèi) extern?HANDLE?g_mUartHandle; //串口句柄 extern?MODBUS_RTU?g_mModbus; //MODBUS-RTU?通信接口類(lèi) bool?UART_SendData(BYTE?*pData,?DWORD?DataLen); //串口發(fā)送函數(shù) bool?UART_ReadData(BYTE?*pData,?DWORD?*DataLen); //串口接收數(shù)據(jù) void?UART_ClearRxBuff(void); //清除接收緩沖區(qū) void?MODBUS_Int
9.使用modbus-RTU協(xié)議讀取配置與數(shù)據(jù)
//讀取配置-通信過(guò)程 bool?CLASS_NAME::ReadConfig(CONFIG_TYPE?*pConfig,?char?**pError) { int?Retry; MRTU_ERROR?Status; WORD?RegBuff[5]; try { //調(diào)用modbus讀取數(shù)據(jù),失敗重試3次 for?(Retry?=?0;?Retry?<?3;?Retry?++) { Status?=?g_mModbus.ReadMultReg(HOLD_REG,?1,?0,?2,?RegBuff,?pError); //讀取保持寄存器0,1 if?(Status?==?MRTU_OK)?//讀取成功 { pConfig->Addr?=?RegBuff[0]; //寄存器0,通信地址 pConfig->Time?=?RegBuff[1]; //寄存器1,采集間隔 return?true; //返回成功 } Sleep(200); //失敗了,延時(shí)200ms并重試 } } catch?(Exception^?e) { *pError?=?USER_LIB.StringToChar(e->Message); } return?false; } //寫(xiě)入配置-通信過(guò)程 bool?CLASS_NAME::WriteConfig(CONFIG_TYPE?*pConfig,?char?**pError) { int?Retry; MRTU_ERROR?Status; WORD?RegBuff[5]; try { //調(diào)用modbus寫(xiě)入數(shù)據(jù),失敗重試3次 for?(Retry?=?0;?Retry?<?3;?Retry++) { RegBuff[0]?=?pConfig->Addr; //寄存器0,通信地址 RegBuff[1]?=?pConfig->Time; //寄存器1,采集間隔 Status?=?g_mModbus.WriteMultReg(1,?0,RegBuff,?2,??pError); //讀取保持寄存器0,1 if?(Status?==?MRTU_OK)?//讀取成功 { return?true; //返回成功 } Sleep(200); //失敗了,延時(shí)200ms并重試 } } catch?(Exception^?e) { *pError?=?USER_LIB.StringToChar(e->Message); } return?false; } //讀取實(shí)時(shí)數(shù)據(jù)-通信過(guò)程 bool?CLASS_NAME::ReadRealData(REAL_DATA_TYPE?*pData,?char?**pError) { int?Retry; MRTU_ERROR?Status; WORD?RegBuff[5]; //寄存器3,4:水位,寄存器5:電壓 try { //調(diào)用modbus讀取數(shù)據(jù),失敗重試3次 for?(Retry?=?0;?Retry?<?3;?Retry++) { Status?=?g_mModbus.ReadMultReg(HOLD_REG,?1,?3,?3,?RegBuff,?pError); //讀取保持寄存器0,1 if?(Status?==?MRTU_OK)?//讀取成功 { pData->WaterLevel?=?RegBuff[0]; //寄存器3,水位高16位 pData->WaterLevel?<WaterLevel?|=?RegBuff[1]; //寄存器4,水位低16位 pData->Vol?=?RegBuff[2]; //寄存器5,電壓值 return?true; //返回成功 } Sleep(200); //失敗了,延時(shí)200ms并重試 } } catch?(Exception^?e) { *pError?=?USER_LIB.StringToChar(e->Message); } return?false; }
10.工程目錄說(shuō)明
UserLib文件夾中都是我自己實(shí)現(xiàn)的一些工具類(lèi)
GetConfigFromUI:用于從NumericUpDown獲取或顯示數(shù)據(jù),增加了異常與范圍限制功能。
MODBUS_RTU:MODBUS_RTU通信協(xié)議層
SystemLog:簡(jiǎn)單的日志。
UART:串口操作相關(guān)類(lèi)。
UserLib:常用的工具類(lèi)。
11.測(cè)試效果
測(cè)試寄存器說(shuō)明
寄存器0,通信地址
寄存器1,采集間隔
寄存器3,水位高16位
寄存器4,水位低16位
寄存器5,電壓值
可以獲取到系統(tǒng)串口,COM30 COM31為一對(duì)虛擬串口,用于測(cè)試。
讀取配置測(cè)試效果,使用了Modbus Slave虛擬的modbus從機(jī)進(jìn)行測(cè)試。
寫(xiě)入配置測(cè)試,從機(jī)的寄存器0與1發(fā)生了同步的變化。
實(shí)時(shí)數(shù)據(jù)讀取與波形顯示,在實(shí)時(shí)數(shù)據(jù)讀取的過(guò)程中可以同時(shí)讀寫(xiě)配置,由于使用了異步操作,界面不會(huì)卡頓,并且多個(gè)操作可以一起順序執(zhí)行,不用擔(dān)心連續(xù)讀取實(shí)時(shí)數(shù)據(jù)的時(shí)候影響配置讀寫(xiě)。