整个过程都挺累的,持续的时间还这么长,最累的恐怕只有惠哥了,这些比赛完再说吧。这里介绍下在Donald写的一个串口调试程序FSonPC,主要是方便我们查看运行起来的小车的内部参数。
程序的作用是接收来自Freescale MC9SDG128单片机发送来的数据,并显示出来。分为两部分。一部分是普通的数据,ASCII数据,另一部分是实时的图像,也是以ASCII方式传输。程序可通过“模式”按钮设置工作在这两种模式。当进入CCD模式时,通过“连接”按钮触发DG128单片机发送图像信息,这时“连接”按钮变为“断开”,可通过其触发DG128单片机停止发送,降低单片机内部资源消耗。
程序中串口部分的编程参考的是《Visual C++串口通信技术与工程实践》,李现勇编著。书中对串口的编程讲得很清楚,除了C的,还有VB的。详细请参考该书。这里介绍本程序的一部分程序代码。
一、添加串口控件
本程序是在VC6下MFC实现,所以添加一个串口控件,方法是选择Project菜单下Add To Project子菜单中的 Components and Controls选项,在弹出的对话框中进入Registered ActiveX Controls目录,选择Microsoft Communications Control, version 6.0,,Insert到Project中就行了。然后可在窗体中添加串口控件,接着Ctrl+W到ClassWizard中为该控件添加一个对应的变量就行。
二、串口工作方式的改变
串口的工作方式有很多种,比如串口的选择、波特率、校验位、数据位和停止位等。这些的设置可以通过组合框实现,实现方法基本一样,Donald就以波特率说明下。
之前已经为波特率组合框对象IDC_COMBO_BAUD添加对应变量m_cboBaud。在程序主窗体初始化时写有如下代码。
// combo Baud init
m_cboBaud.AddString(_T("1200"));
m_cboBaud.AddString(_T("2400"));
m_cboBaud.AddString(_T("4800"));
m_cboBaud.AddString(_T("9600"));
m_cboBaud.AddString(_T("115200"));
m_cboBaud.SetCurSel(3);
m_strBaud = _T("9600");
再在ClassWizard中为IDC_COMBO_BAUD添加CBN_SELCHANGE消息处理函数,再在函数中添加代码。
switch (m_cboBaud.GetCurSel())
{
case 0:
m_strBaud = _T("1200");
break;
case 1:
m_strBaud = _T("2400");
break;
case 2:
m_strBaud = _T("4800");
break;
case 3:
m_strBaud = _T("9600");
break;
case 4:
m_strBaud = _T("115200");
break;
default:
m_strBaud = _T("9600");
}
InitCommPort();
通过switch选择不同的波特率。case的顺序按照初始化添加时的顺序,开始为0。
三、OnComm()消息处理函数
在串口控件中,最重要的就是OnComm()函数,这个函数是用来处理串口消息事件的,每当串口接收到数据,就会产生一个串口接收数据缓冲区中“有字符”的消息事件,只要在该函数内写好处理这些字符的代码就可以。
在Donald的这个程序里,该程序代码如下。代码的结构(switch结构)是参考该书。
VARIANT variant_inp;
COleSafeArray safearray_inp;
CByteArray arraySend;
LONG len,k;
BYTE rxdata[1024]; //An 8-bit integer that is not signed for Receive
CString strAdd;
int i;
m_strComRcv.Empty();
switch(m_com.GetCommEvent())
{
case 1: // comEvSend
arraySend.RemoveAll();
arraySend.SetSize(m_strEditTrn.GetLength());
for (i = 0; i < m_strEditTrn.GetLength(); i++)
{
arraySend.SetAt(i, m_strEditTrn.GetAt(i));
}
m_com.SetOutput(COleVariant(arraySend));
break;
case 2: // comEvReceive
variant_inp = m_com.GetInput(); // Read buffer
safearray_inp = variant_inp; // VARIANT to ColeSafeArray
len = safearray_inp.GetOneDimSize(); // Get effective length
// Store to EDIT_REV
for(k = 0; k < len; k++)
{
safearray_inp.GetElement(&k, rxdata + k); // Change to BYTE array
BYTE bt = *(char*)(rxdata+k); // BYTE type
strAdd += bt;
}
if (m_bCcdMode == FALSE){
m_myEdit.AppendText(strAdd);
} else{
g_strRcv = strAdd;
g_bReceive = TRUE;
}
break;
default: // Error
m_com.SetOutBufferCount(0);
//AfxMessageBox("Com Err");
break;
}
使用控件编程就是相对简单,系统的串口消息发生时,程序自动调用该函数。由switch语句判断,参数1为发送,参数2为接收。Donald主要介绍下接收的思路,由于接收时有两种模式,所以通过m_bCcdMode成员变量记录这两种模式。当其为FALSE时直接实时显示在窗口右边,就像Windows处事的超级终端。如果是TRUE,则不显示,将信息放到一个全局变量g_strRcv。其实尽量不要用全局变量,但Donald暂时也不懂线程间通信,只能简单这么做了。并置位全局变量g_bReceive。该变量是在CCD模式下一个线程处理的依据,实时显示图像。
四、文本框实时显示和发送串口字符
为了能实现Windows超级终端文本框的功能,我们必须手动修改Edit类,然后用CWnd::SubclassDlgItem提供的动态连接功能,将原来的文本框和修改的类连接起来。
利用ClassWizard新建一个CEdit类自己命名(如CMyEdit),并在这个类中添加WM_CHAR消息处理函数和。确定后,再在新建类的头文件中手动添加void AppendText (LPCSTR pText)函数,用于将串口收到的字符输出在文本框中。以及添加CByteArray arraySend成员变量,用于显示字符的临时存储。
Donald程序中,CMyEdit::AppendText(LPCSTR pText)的内容如下:
int nLen = GetWindowTextLength ();
CString strTmp = pText;
SetFocus ();
if(strTmp.GetAt(0) == (char)0X08){
SetSel (nLen - 1, nLen);
strTmp.Delete(0);
Clear();
} else{
SetSel (nLen, nLen);
ReplaceSel (pText);
}
修改CMyEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)函数,该函数实现在文本控件中输入一字符时自动由串口向外发送。代码如下:
arraySend.RemoveAll();
arraySend.SetSize(1);
arraySend.SetAt(0, (char)nChar);
((CFSonPCDlg*)AfxGetApp()->m_pMainWnd)->m_com.SetOutput(COleVariant(arraySend));
//CEdit::OnChar(nChar, nRepCnt, nFlags);
程序的第四行语句必是向Donald的窗体串口控件关联变量m_com输出字符,注意要调用COleVariant()函数。
五、实时接收图像线程
众所周知,如果一个程序用了while(1)语句的话,那么这个程序将是一个死循环,将严重占用CPU资源,而这里,我们的程序又需要用while来实时地接收来自单片机的串口信息,于是,最好的解决方法就是用线程。
调用线程的思路是这样,当程序处于文本框模式时,线程是关闭的。如果进行CCD模式,还没“连接”,程序状态同上,如果进行“连接”状态,则创建实时接收线程,并设置一定时器以在一定时间未接收到来自串口的握手信号时自动断开。如果从“连接”状态手动或自动“断开”,则将线程终止,向单片机发送终止传送图像信号并关闭定时器。至此,完成显示图像流程。
线程中首先是发送握手信号FS_CONNECTION。
arraySend.SetSize(1);
arraySend.SetAt(0, FS_CONNECTION);
g_bOvertime = FALSE;
// Send Connection Signal
((CFSonPCDlg*)AfxGetApp()->m_pMainWnd)->m_com.SetOutput(COleVariant(arraySend));
然后无限循环等待接收,用了两层while语句,第一个等待来自单片机的握手信号,第二个处理接收图像。
while(1)
{
// Wait for receiving
((CFSonPCDlg*)AfxGetApp()->m_pMainWnd)->GetDlgItem(IDC_STATIC_NOW)
->SetWindowText("等待...");
g_bReceive = FALSE;
SetTimer(AfxGetApp()->GetMainWnd()->m_hWnd, 1, 1000, NULL);
g_bOvertime = TRUE;
while(!g_bReceive);
KillTimer(AfxGetApp()->GetMainWnd()->m_hWnd, 1);
g_bReceive = FALSE;
g_bOvertime = FALSE;
if (-1 == g_strRcv.Find(FS_CONNECTION)){ // Not found
continue;
}else{
((CFSonPCDlg*)AfxGetApp()->m_pMainWnd)->GetDlgItem(IDC_STATIC_NOW)
->SetWindowText("连接完成, 接收中");
row = 0;
col = 0;
while(1)
{
SetTimer(AfxGetApp()->GetMainWnd()->m_hWnd, 1, 1000, NULL);
g_bOvertime = TRUE;
g_bReceive = FALSE;
while(!g_bReceive);
KillTimer(AfxGetApp()->GetMainWnd()->m_hWnd, 1);
g_bReceive = FALSE;
g_bOvertime = FALSE;
//AfxMessageBox(g_strRcv);
n = g_strRcv.Find(FS_TRANFINISH);
if(-1 == n){ // Not finish
n = g_strRcv.GetLength();
for(i = 0; i < n; i++){
cRcv[row][col++] = g_strRcv.GetAt(i);
if(col == MAX_CCD_Y){
col = 0;
if(++row == MAX_CCD_X){
row = 0;
}
}
}
}else{ // Finish
//n = g_strRcv.Find(FS_TRANFINISH);
for(i = 0; i < n; i++){
cRcv[row][col++] = g_strRcv.GetAt(i);
if(col == MAX_CCD_Y){
col = 0;
if(++row == MAX_CCD_X){
row = 0;
}
}
}
((CFSonPCDlg*)AfxGetApp()->m_pMainWnd)->m_ccdGraph.ChangeBit(cRcv);
((CFSonPCDlg*)AfxGetApp()->m_pMainWnd)->GetDlgItem(IDC_STATIC_NOW)
->SetWindowText("接收完毕");
// Show it
((CFSonPCDlg*)AfxGetApp()->m_pMainWnd)->m_ccdGraph.ShowGraph(
((CFSonPCDlg*)AfxGetApp()->m_pMainWnd)->m_dcViewS);
((CFSonPCDlg*)AfxGetApp()->m_pMainWnd)->m_dcViewB.StretchBlt(
0, 0, MAX_CCD_X * 4, MAX_CCD_Y * 4,
((CFSonPCDlg*)AfxGetApp()->m_pMainWnd)
->m_dcViewS.GetWindow()->GetDC(),
0, 0, MAX_CCD_X, MAX_CCD_Y, SRCCOPY);
break;
}
}
}
}
结束语
整个程序的总体思路便是如上,并没有对程序的每一个细节都作介绍。书上和网上也能查到很多各种串口的程序,写得相当优秀,Donald的程序比较粗糙,但基本能完成我们智能小车的调试功能,目的就达到了。如果感兴趣,可向Donald要源码。