VC++ CLR 串口讀寫上位機(jī)例程
使用VC++ .net4.0編寫的串口讀寫上位機(jī),實(shí)現(xiàn)基本的配置讀取,寫入,以及連續(xù)的實(shí)時(shí)數(shù)據(jù)讀取顯示,波形顯示(采用異步操作,連續(xù)讀取實(shí)時(shí)數(shù)據(jù)的過程中,可以讀寫配置)。
1.總體界面
功能:系統(tǒng)串口選擇,串口連接,通信地址設(shè)置,采集周期設(shè)置功能,讀取配置,寫入配置。
功能:實(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)前串口沒有連接,開始連接串口
{
this->toolStripStatusLabel1->Text?=?"未連接"; //底部狀態(tài)
if?(g_mUART.UartNum?==?0) //沒有串口,無法連接
{
System::Windows::Forms::MessageBox::Show("沒有串口,無法連接!",?"錯(cuò)誤",?System::Windows::Forms::MessageBoxButtons::OK,
System::Windows::Forms::MessageBoxIcon::Error);
return;
}
pComName?=?USER_LIB.StringToChar(this->_UART_ComboBox->SelectedItem->ToString()); //獲取當(dāng)前選擇的串口名稱
if?(strlen(pComName)?>?6)?pComName[6]?=?0; //限制串口名稱長度
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?//斷開連接
{
g_mUART.UART_Close(g_mUartHandle); //斷開連接
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; //連接斷開,不允許配置
}
}
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.寫入配置 按鈕事件
?//寫入配置
private:?System::Void?button2_Click(System::Object^??sender,?System::EventArgs^??e)?{
this->UI_WriteConfig_Button_Click();//寫入配置
}//寫入配置
void?CLASS_NAME::UI_WriteConfig_Button_Click(void)
{
try
{
//先從界面獲取配置到全局緩沖區(qū)中
this->UI_GetConfig(this->pWriteConfig);
//如果沒有讀取過配置,則提示用戶,應(yīng)該先讀取配置
if?(this->isNotReadConfig?==?true)
{
System::Windows::Forms::MessageBox::Show("請(qǐng)先讀取配置,再寫入!",?"警告",?System::Windows::Forms::MessageBoxButtons::OK,
System::Windows::Forms::MessageBoxIcon::Warning);
return;
}
//檢查配置
if?(this->CheckConfig(this->pWriteConfig)?==?false)//檢查配置
{
System::Windows::Forms::MessageBox::Show("無效的配置",?"錯(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; //異步命令,需要寫入配置
}
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ù)讀取開關(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)?//沒有讀取-開始讀取
{
this->isReadRealData?=?true;
this->button3->Text?=?"讀取中...";
}
else?//已經(jīng)開啟了,關(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à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(); //開始執(zhí)行
}異步線程的初始化主要是添加一些事件,比如線程核心函數(shù),線程狀態(tài)更新回調(diào)函數(shù),線程結(jié)束后回調(diào)函數(shù),是否允許更新狀態(tài),此處必須允許更新狀態(tài),在異步線程中是不能直接訪問UI的,但是使用狀態(tài)更新可以實(shí)現(xiàn)異步刷新的目的,比如在異步線程中讀取配置,讀取車成功后觸發(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)?//寫配置
{
this->isWriteConfig?=?false; //清除寫命令
if?(this->CheckConfig(this->pWriteConfig)?==?false)//配置有誤,不能寫入
{
this->mStringBuilderError->Clear(); //清空字符
this->mStringBuilderError->Append("配置有誤,不允許寫入");
this->mBackgroundWorker->ReportProgress(3); //寫配置失敗
}
else?//配置無誤,寫入
{
if?(this->WriteConfig(this->pWriteConfig,?&pError)?==?true)
{
this->mBackgroundWorker->ReportProgress(2); //寫配置成功
}
else?//寫入失敗
{
this->mStringBuilderError->Clear(); //清空字符
this->mStringBuilderError->Append("寫入配置失敗,錯(cuò)誤:");
this->mStringBuilderError->Append(CharToString(pError));
this->mBackgroundWorker->ReportProgress(3); //寫入配置失敗
}
}
}
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; //配置讀取過,標(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://寫配置成功
{
this->toolStrip1->Enabled?=?true;
this->tabControl1->Enabled?=?true;
this->mMessageControl->Visible?=?false; //影藏讀取中提示窗口
this->toolStripStatusLabel1->Text?=?"寫入配置成功";
System::Windows::Forms::MessageBox::Show("寫入配置成功!",?"提示",?System::Windows::Forms::MessageBoxButtons::OK,
System::Windows::Forms::MessageBoxIcon::Information);
}break;
case?3: //寫入配置失敗
{
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ī)中類似的,此處我只需要實(shí)現(xiàn)底層的串口收發(fā)函數(shù),并初始化回調(diào)即可,注意在CLR程序中,托管的代碼必須使用類,但是托管的函數(shù)中不允許直接使用函數(shù)指針,此處我使用的C代碼(非類)來實(shí)現(xiàn)modbus所需的收發(fā)函數(shù)(函數(shù)是全局的,無需像類需要先實(shí)例化)。
CommInterface.c
#include?"StdAfx.h"
#include?"CommInterface.h"
#include?"UART.h"
#include?"SystemLog.h"
#include?"modbus_rtu.h"
UART_TYPE?g_mUART; //串口類
HANDLE?g_mUartHandle?=?0; //串口句柄
MODBUS_RTU?g_mModbus; //MODBUS-RTU?通信接口類
#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ù)長度
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這類串口分包導(dǎo)致數(shù)據(jù)丟失,串口波特率不一樣時(shí)等待的實(shí)際會(huì)不一樣,大數(shù)據(jù)包等待的時(shí)間會(huì)更長
DelayCnt?+=?PackDelay;
}
Sleep(20); //收完后再等待20ms防止PL2303這類串口分包導(dǎo)致數(shù)據(jù)丟失
TimeOut?=?1; //數(shù)據(jù)接收完畢,退出
DelayCnt?+=?20;
}
}
DelayCnt?+=?50;
if?(DelayCnt?>?5000)?break; //強(qiáng)制退出,5秒
}?while?(TimeOut);
//等待完畢
if?(cnt?==?0)? //沒有接收到數(shù)據(jù)
{
*DataLen?=?0; //返回接收數(shù)據(jù)長度
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ù)長度
g_mUART.MYUART_ClearRxBuff(g_mUartHandle); //清除接收緩沖區(qū)
return?false; //串口錯(cuò)誤
}
*DataLen?=?cnt; //返回接收數(shù)據(jù)長度
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; //串口類 extern?HANDLE?g_mUartHandle; //串口句柄 extern?MODBUS_RTU?g_mModbus; //MODBUS-RTU?通信接口類 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ù)
//讀取配置-通信過程
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;
}
//寫入配置-通信過程
bool?CLASS_NAME::WriteConfig(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++)
{
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ù)-通信過程
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.工程目錄說明
UserLib文件夾中都是我自己實(shí)現(xiàn)的一些工具類
GetConfigFromUI:用于從NumericUpDown獲取或顯示數(shù)據(jù),增加了異常與范圍限制功能。
MODBUS_RTU:MODBUS_RTU通信協(xié)議層
SystemLog:簡單的日志。
UART:串口操作相關(guān)類。
UserLib:常用的工具類。
11.測(cè)試效果
測(cè)試寄存器說明
寄存器0,通信地址
寄存器1,采集間隔
寄存器3,水位高16位
寄存器4,水位低16位
寄存器5,電壓值
可以獲取到系統(tǒng)串口,COM30 COM31為一對(duì)虛擬串口,用于測(cè)試。
讀取配置測(cè)試效果,使用了Modbus Slave虛擬的modbus從機(jī)進(jìn)行測(cè)試。
寫入配置測(cè)試,從機(jī)的寄存器0與1發(fā)生了同步的變化。
實(shí)時(shí)數(shù)據(jù)讀取與波形顯示,在實(shí)時(shí)數(shù)據(jù)讀取的過程中可以同時(shí)讀寫配置,由于使用了異步操作,界面不會(huì)卡頓,并且多個(gè)操作可以一起順序執(zhí)行,不用擔(dān)心連續(xù)讀取實(shí)時(shí)數(shù)據(jù)的時(shí)候影響配置讀寫。





