近期做一个项目正好涉及MFC编写串口上位机,主要用于动态显示曲线和陀螺仪三维信息,想做飞思卡尔或者四旋翼的小伙伴可以借鉴一下,首先贴个结果图:
下面来简单讲解一下这个上位机的核心步骤:
1、首先新建一个串口通信的程序,网上的示例代码有很多,详细的教学文档下载:
http://download.csdn.net/detail/plutus_lee/4525446
2、自动搜索可用串口
新建一个Combo-box Control控件,ID为IDC_COMBO_COM,并输入可选择数据
在OnInitDialog()中调用MYUART_GetComNum()函数即可获得当前可用的串口号,并显示在IDC_COMBO_COM控件中
MYUART_GetComNum()函数如下:
bool CfuzhikejiDlg::MYUART_GetComNum(void)
{
long lReg;
HKEY hKey;
DWORD MaxValueLength;
DWORD dwValueNumber;
lReg = RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("HARDWARE\\DEVICEMAP\\SERIALCOMM"),
0, KEY_QUERY_VALUE, &hKey);
if (lReg != ERROR_SUCCESS) //成功时返回ERROR_SUCCESS,
{
//MessageBox(TEXT("未自动找到串口!\nOpen Registry Error!\n"));
return FALSE;
}
lReg = RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, NULL, NULL,
&dwValueNumber, &MaxValueLength, NULL, NULL, NULL);
if (lReg != ERROR_SUCCESS) //没有成功
{
//MessageBox(TEXT("未自动找到串口!\nGetting Info Error!\n"));
return FALSE;
}
TCHAR *pValueName, *pCOMNumber;
DWORD cchValueName, dwValueSize = 10;
for (int i = 0; i < dwValueNumber; i++)
{
cchValueName = MaxValueLength + 1;
dwValueSize = 10;
pValueName = (TCHAR*)VirtualAlloc(NULL, cchValueName, MEM_COMMIT, PAGE_READWRITE);
lReg = RegEnumValue(hKey, i, pValueName,
&cchValueName, NULL, NULL, NULL, NULL);
if ((lReg != ERROR_SUCCESS) && (lReg != ERROR_NO_MORE_ITEMS))
{
//MessageBox(TEXT("未自动找到串口!\nEnum Registry Error or No More Items!\n"));
return FALSE;
}
pCOMNumber = (TCHAR*)VirtualAlloc(NULL, 6, MEM_COMMIT, PAGE_READWRITE);
lReg = RegQueryValueEx(hKey, pValueName, NULL,
NULL, (LPBYTE)pCOMNumber, &dwValueSize);
if (lReg != ERROR_SUCCESS)
{
//MessageBox(TEXT("未自动找到串口!\nCan not get the name of the port"));
return FALSE;
}
CString str(pCOMNumber);
int len=str.GetLength();
str = str.Right(len - 3);
((CComboBox*)GetDlgItem(IDC_COMBO_COM))->SetCurSel(atoi(str)-1);//把获取的值加入到ComBox控件中
VirtualFree(pValueName, 0, MEM_RELEASE);
VirtualFree(pCOMNumber, 0, MEM_RELEASE);
}
return TRUE;
}
说明:
pCOMNumber即为可用的COM口号,如果在设备管理器里面有COM3口可用,那么CString str(pCOMNumber)执行后,str的值为“COM3”
2、更改波特率等参数
新建Combo-box Control控件,ID分别为IDC_COMBO_BOTELV、IDC_COMBO_JIAOYANWEI、IDC_COMBO_SHUJUWEI 、IDC_COMBO_TINGZHIWEI,并输入可选择数据如下:
在OnInitDialog()中设置初始值
((CComboBox*)GetDlgItem(IDC_COMBO_BOTELV))->SetCurSel(0);
((CComboBox*)GetDlgItem(IDC_COMBO_JIAOYANWEI))->SetCurSel(2);
((CComboBox*)GetDlgItem(IDC_COMBO_SHUJUWEI))->SetCurSel(3);
((CComboBox*)GetDlgItem(IDC_COMBO_TINGZHIWEI))->SetCurSel(0);
添加“打开串口按钮”,添加点击事件如下:
void CfuzhikejiDlg::OnBnClickedButtonOpencom()
{
// TODO: 在此添加控件通知处理程序代码
if (m_cComm.get_PortOpen()) //如果发现串口本来是打开的,则关闭串口
m_cComm.put_PortOpen(FALSE);
CString m_Combo_COM;
GetDlgItemText(IDC_COMBO_COM, m_Combo_COM);
int len = m_Combo_COM.GetLength();
m_Combo_COM = m_Combo_COM.Right(len - 3);
m_cComm.put_CommPort(atoi(m_Combo_COM)); //选择COM端口
m_cComm.put_InputMode(1); //输入方式为二进制方式
m_cComm.put_InBufferSize(1024); //设置输入缓冲区
m_cComm.put_OutBufferSize(1024); //设置输出缓冲区
CString str;
int m_Combo_BOTELV = GetDlgItemInt(IDC_COMBO_BOTELV);
str.Format(_T("%d,"), m_Combo_BOTELV);
CString m_Combo_JIAOYANWEI, m_Combo_SHUJUWEI, m_Combo_TINGZHIWEI;
GetDlgItemText(IDC_COMBO_JIAOYANWEI, m_Combo_JIAOYANWEI);
GetDlgItemText(IDC_COMBO_SHUJUWEI, m_Combo_SHUJUWEI);
GetDlgItemText(IDC_COMBO_TINGZHIWEI, m_Combo_TINGZHIWEI);
str = str + m_Combo_JIAOYANWEI+_T(",")+ m_Combo_SHUJUWEI+_T(",")+ m_Combo_TINGZHIWEI;
m_cComm.put_Settings(str);//波特率,无校验,个数据位,个停止位
//m_cComm.put_Settings(TEXT("9600,n,8,1"));//波特率,无校验,个数据位,个停止位
if (!m_cComm.get_PortOpen())
{
m_cComm.put_PortOpen(TRUE); //打开串口
m_cComm.put_RThreshold(16); //每当接收缓冲区有个字符则接收串口数据
m_cComm.put_InputLen(0); //设置当前缓冲区长度为0
m_cComm.get_Input(); //预读缓冲区以清除残留数据
AfxMessageBox(_T("串口打开成功!"));
}
else
AfxMessageBox("打开端口失败!", MB_ICONSTOP, 0);
}
红色标注部分为更改参数得代码,都是些简单的语句,就不做介绍了。
每次接收16个字节,最后两位为CRC16校验位,由单片机中发送的。
3、接收到数据的处理
void CfuzhikejiDlg::OnComm()
{
// TODO: 在此处添加消息处理程序代码
//从串口接收数据并显示在编辑框中
VARIANT variant_inp;
COleSafeArray safearray_inp;
long len, k;
byte rxdata[512]; //设置BYTE数组
CString strtemp;
unsigned short CRC16 = 0;
short temp[4];
short temp1[3];
float temp_y_axis[4];
if (m_cComm.get_CommEvent() == 2) //值为表示接收缓冲区内有字符
{
variant_inp = m_cComm.get_Input(); //读缓冲区消息
safearray_inp = variant_inp; //变量转换
len = safearray_inp.GetOneDimSize(); //得到有效的数据长度
for (k = 0; k < len; k++)
safearray_inp.GetElement(&k, rxdata + k);
CRC16 = CRC_CHECK(rxdata, len);//CRC16校验
if (CRC16 == 0&& View_Flag ==TRUE)
{
for (k = 0; k < 4; k++) //将数组转换为CString型变量,不包含校验位
{
temp[k] = (short)((rxdata[2 * k + 1] << 8 )| (rxdata[2 * k]));
temp_y_axis[k] = (float)temp[k];
m_plot.AddNewPoint(m_time, temp_y_axis[k], k);
}
m_time += 0.20f;
}
if (CRC16 == 0)
{
temp1[0] = (short)((rxdata[9] << 8) | (rxdata[8]));
temp1[1] = (short)((rxdata[11] << 8) | (rxdata[10]));
temp1[2] = (short)((rxdata[13] << 8) | (rxdata[12]));
m_OpenGL.m_xAngle = temp1[1] / 10;
m_OpenGL.m_yAngle = -temp1[2] / 10;
m_OpenGL.m_zAngle = temp1[0] / 10;
m_OpenGL.InvalidateRect(NULL, FALSE);
}
for (k = 0; k < len; k++) //将数组转换为CString型变量
{
if (k == len - 1)
{
char bt = *(char*)(rxdata + k); //字符型
strtemp.Format("%c", bt); //将字符送入临时变量strtemp存放
m_strRecvData += strtemp; //加入接收编辑框对应字符串
}
}
}
SetDlgItemText(IDC_EDIT_RECV, m_strRecvData);
//UpdateData(FALSE); //更新编辑框内容
m_strRecvData.Empty();
}
黄色背景处代码为:绘制动态曲线的添加点的代码,红色背景为显示陀螺仪三维欧拉角的代码。
16个8位数据储存内容为,第1-8个8位数据为4个16字节绘制动态曲线的数据,一共可绘制四条,第9-14个8位数据为3个16字节陀螺仪三维欧拉角的数据,对应翻滚角,俯仰角和偏航角。
4、绘制动态曲线
主要是参考这篇博文编的http://blog.csdn.net/nuaazdh/article/details/7857223
自己加入按住右键拖动,方向键左右移动,滚动鼠标中键放大缩小等功能。
void CPlot::OnMouseMove(UINT nFlags, CPoint point)
{
if (LBTNDOWN_FLAG)
{
LBTNMOWE_FLAG = TRUE;
m_midPoint = point;
OnPaint();//重绘
}
if (RBTNDOWN_FLAG)
{
m_RendPoint = point;
m_XLwLmt_Trace -= (m_RendPoint.x - m_RstartPoint.x)*m_vppXAxis;
m_XUpLmt_Trace -= (m_RendPoint.x - m_RstartPoint.x)*m_vppXAxis;
m_YLwLmt_Trace += (m_RendPoint.y - m_RstartPoint.y)*m_vppYAxis;
m_YUpLmt_Trace += (m_RendPoint.y - m_RstartPoint.y)*m_vppYAxis;
m_axisX.SetAxisRange(m_XLwLmt_Trace, m_XUpLmt_Trace);
m_axisY.SetAxisRange(m_YLwLmt_Trace, m_YUpLmt_Trace);
OnPaint();
m_RstartPoint = m_RendPoint;
}
}
void CPlot::OnLButtonUp(UINT nFlag,CPoint point)
{
LBTNDOWN_FLAG = FALSE;
LBTNMOWE_FLAG = FALSE;
m_endPoint = point;
ScaleProcess();// 缩放处理
OnPaint(); // 重绘
TRACE("Left Button Up.Point Coordinate:x=%d,y=%d.\n",point.x,point.y);
CStatic::OnLButtonUp(nFlag,point);
}
void CPlot::OnLButtonDblClk( UINT nFlags, CPoint point )
{
m_bAdjustable = TRUE;// 重新进行自调整
m_axisX.SetAxisRange(m_originXLwLmt,m_originXUpLmt);
m_axisY.SetAxisRange(m_originYLwLmt,m_originYUpLmt);
m_XLwLmt_Trace = m_originXLwLmt;
m_XUpLmt_Trace = m_originXUpLmt;
m_YLwLmt_Trace = m_originYLwLmt;
m_YUpLmt_Trace = m_originYUpLmt;
OnPaint(); // 重绘
CStatic::OnLButtonDblClk(nFlags,point);
}
BOOL CPlot::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
// TODO: Add your message handler code here and/or call default
float proportion=(m_rectPlot.right- m_rectPlot.left)/(m_rectPlot.bottom- m_rectPlot.top);
m_XLwLmt_Trace -= 2.0*zDelta / 120;
m_XUpLmt_Trace += 2.0*zDelta / 120;
m_YLwLmt_Trace -= 2.0* proportion*zDelta / 120;
m_YUpLmt_Trace += 2.0* proportion*zDelta / 120;
m_axisX.SetAxisRange(m_XLwLmt_Trace, m_XUpLmt_Trace);
m_axisY.SetAxisRange(m_YLwLmt_Trace, m_YUpLmt_Trace);
OnPaint();
//InvalidateRect(NULL, FALSE);//效果一样
return CStatic::OnMouseWheel(nFlags, zDelta, pt);
}
BOOL CPlot::PreTranslateMessage(MSG* pMsg)
{
// TODO: 在此添加专用代码和/或调用基类
float proportion = (m_rectPlot.right - m_rectPlot.left) / (m_rectPlot.bottom - m_rectPlot.top);
if (pMsg->message == WM_KEYDOWN)
{
switch (pMsg->wParam)
{
case VK_UP:
m_YLwLmt_Trace -= 2.0* proportion;
m_YUpLmt_Trace -= 2.0* proportion;
m_axisX.SetAxisRange(m_XLwLmt_Trace, m_XUpLmt_Trace);
m_axisY.SetAxisRange(m_YLwLmt_Trace, m_YUpLmt_Trace);
OnPaint();
return TRUE;
break;
case VK_DOWN:
m_YLwLmt_Trace += 2.0* proportion;
m_YUpLmt_Trace += 2.0* proportion;
m_axisX.SetAxisRange(m_XLwLmt_Trace, m_XUpLmt_Trace);
m_axisY.SetAxisRange(m_YLwLmt_Trace, m_YUpLmt_Trace);
OnPaint();
return TRUE;
break;
case VK_LEFT:
m_XLwLmt_Trace += 2.0;
m_XUpLmt_Trace += 2.0;
m_axisX.SetAxisRange(m_XLwLmt_Trace, m_XUpLmt_Trace);
m_axisY.SetAxisRange(m_YLwLmt_Trace, m_YUpLmt_Trace);
OnPaint();
return TRUE;
break;
case VK_RIGHT:
m_XLwLmt_Trace -= 2.0;
m_XUpLmt_Trace -= 2.0;
m_axisX.SetAxisRange(m_XLwLmt_Trace, m_XUpLmt_Trace);
m_axisY.SetAxisRange(m_YLwLmt_Trace, m_YUpLmt_Trace);
OnPaint();
return TRUE;
break;
default: MessageBox("Press the arrow keys only");
return TRUE;
break;
}
}
return FALSE;
}
void CPlot::OnRButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_RstartPoint = point;
RBTNDOWN_FLAG = TRUE;
CStatic::OnRButtonDown(nFlags, point);
}
void CPlot::OnRButtonUp(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
RBTNDOWN_FLAG = FALSE;
CStatic::OnRButtonUp(nFlags, point);
}
5、OPENGL绘制三维图
参考绘制代码下载:http://download.csdn.net/download/qian365013263/2276152
我主要是基于以上的绘制方法改动得到自己的绘制结果的,因为上面是单文档程序需要我们转成对话框程序,转换并不难但是记得最后调用析构函数去除野指针:
重载函数如下:
OpenGLView::~OpenGLView()
{
if (::wglMakeCurrent(0, 0) == FALSE)
MessageBox("Could not make RC non-current");
if (::wglDeleteContext(m_hRC) == FALSE)
MessageBox("Could not delete RC");
if (m_pDC)
delete m_pDC;
m_pDC = NULL;
}
最后经过我的各种融合,总算是完成了这个工程,虽然不是所有代码都是自己编的,但是也没有看到相关方面的工程,姑且算个原创吧!
链接: http://pan.baidu.com/s/1bnINhxl 密码: w4z2
我的工程文件105M,为什么这么大啊,求解??