modbus是工业自动化领域常用的通讯协议,主机在从机发送命令的过程中,为了纠错,常常用crc校验。本文就主机部分展开;在MFC方法下可以这样实现;
第一步:在主机主界面增加Mscomm控件,并关联一个变量m_mscomm_1;且增加combo下拉框;
下拉框关联变量m_combo_1;
第二步:在初始化部分BOOL CXXXXXXXDlg::OnInitDialog()的主体部分
写串口的初始化代码,就是调用GetmyCOM()函数;下面为函数;
void CxxxxxxxDlg::GetmyCOM(void)//这里xx用你自己的对话框cpp名称代替
{
//程序启动时获取全部可用串口
HANDLE hCom;
int i,num,k;
CString str;
BOOL flag;
((CComboBox *)GetDlgItem(IDC_COMBO1))->ResetContent();
flag = FALSE;
num = 0;
for (i = 1;i <= 16;i++)
{ //此程序支持16个串口
str.Format(TEXT("\\\\.\\COM%d"), i);
hCom = CreateFile(str, 0, 0, 0,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if(INVALID_HANDLE_VALUE != hCom )
{ //能打开该串口,则添加该串口
CloseHandle(hCom);
str = str.Mid(4);
((CComboBox *)GetDlgItem(IDC_COMBO1))->AddString(str);
if (flag == FALSE)
{
flag = TRUE;
num = i;
}
}
}
i = ((CComboBox *)GetDlgItem(IDC_COMBO1))->GetCount();
if (i == 0)
{//若找不到可用串口则禁用“打开串口”功能
((CComboBox *)GetDlgItem(IDC_COMBO1))->EnableWindow(FALSE);
}
else
{
k = ((CComboBox *)GetDlgItem((IDC_COMBO1)))->GetCount();
((CComboBox *)GetDlgItem(IDC_COMBO1))->SetCurSel(k - 1);
}
}
这样所有机器上可用的串口都自动显示在了下拉列表框,程序运行时可以用鼠标点取;
第三步打开串口
voie Open_COM(void)
{
UpdateData(TRUE); //刷新对话框上的数据到变量
int index1 = m_combox_1.GetCurSel(); //串口号这个函数用于得到用户选择的是下拉列表中的第几
行,第一行的话,返回0,依次类推
CString strcom_number;
CString strCom;
m_combox_1.GetLBText(index1,strCom); //根据行号得到串口号字符串
int portnumb=0; //定义一个空的int变量用来装串口号
CString straa=NULL; //定义一个空字符串用来装COMX的X;
straa=(strCom.GetAt(3)); //取得X字符;
portnumb =_wtoi(straa); //把字符串COMX(X的值是1到16)的x转为数字
m_mscomm_1.put_CommPort(portnumb); //选择串口;对于连续排列的串口号有用
m_mscomm_1.put_InputMode(1); //串口二进制形式收发;
m_mscomm_1.put_Settings(_T("9600,n,8,1"));//设置串口字符发送形式,
if(!m_mscomm_1.get_PortOpen())
{
m_mscomm_1.put_PortOpen(TRUE);//打开串口;
}
}
第三步,准备发送控制字节数组;
unsigned char senddata[]={0x55,0x06,0x08,0xfa,0xaf,0xda,0,0};
定义一个char类型的数组,其中的数字要根据你下位机和上位机的协议进行
本例senddata[0]=0x55是下位机地址码,
senddata[0]=0x06是写线圈;
其他的字节自己定;
值得注意的事最后要留下两个字节的空位,先用0占住,以后放crd—modbus校验字,低字节在前高字节在后;以下是校验程序;
void CXXXXXXXXDlg::crc_modebus(unsigned char* charr, unsigned char len)
{
unsigned int temp ;
unsigned char n,i ;
unsigned char* p1;
p1=charr;
temp = 0x0000ffff;
for( n = 0; n < len; n++) //此处的Len是要校验的字节数。
{
temp = *charr ^ temp;
temp &= 0x0000ffff;
for( i = 0;i < 8;i++) //每一个字节8bit,每bit都要处理。
{
if(temp & 0x01)
{
temp = temp >> 1;
temp &= 0x0000ffff;
temp = temp ^ 0xa001;
temp &= 0x0000ffff;
}
else
{
temp = temp >> 1;
temp &= 0x0000ffff;
}
}
charr++;
}
*(p1+len)=temp;
*(p1+len+1)=temp>>8;
}
调用的时候用crc_modebus(senddata,sizeof(senddata)-2);这里-2就是除了最后的两个字节0,其他的字节都进行校验;
校验的值自动添加到了sendddata字节数组的最后两个字节了;
第四步发送数据;
void CXXXXXXDlg::senddata()
{
unsigned char senddata[]={0x55,0x06,0x08,0xfa,0xaf,0xda,0,0};
//以上是准备好的数组;
crc_modebus(senddata,sizeof(senddata)-2);//这是校验
CByteArray m_sendDate; //准备一个发送数据类型的变量;
for(int i=0;i<sizeof(senddata);i++)
{
m_sendDate.Add(senddata[i]); //把准备好的数组逐个交给发送类型变量
}
m_mscomm_1.put_Output(COleVariant(m_sendDate)); //发送十六进制数据
}
这样校验好的数据就发送出去了;