實現串口通信,使用的類文件是SerialPort.cpp。在項目中使用mscomm控件的時候,串口連續傳遞若干數據后,會出現卡死的情況,關閉串口再打開,繼續讀取的話可以正常通信。
為了解決這個問題,想到就用SerialPort串口類來實現會好吧。當然,完全用windows的api函數來實現也可以,太麻煩吧,我也沒用過。用微軟的一些控件編程雖然容易了,但是也不熟悉底層。
軟件是用的vc6.0平台。
軟件主界面為:
點了自動發送單選框后:
點開始發送按鈕:
一個界面就知道很好實現。
借這個例子,重點來梳理一下串口類的使用。
1, 主對話框頭文件中引入 #include"SerialPort.h"
2, 聲明串口類對象 CSerialPort m_SerialPort;//串口類對象
3, 頭文件中自己添加的函數和變量:
// CCOMTOOLDlg dialog class CCOMTOOLDlg : public CDialog { // Construction public: CString DevideHexChar(char HexChar);// char CombineHexChar(char CharH,char CharL);// void HexStringFilter(CString &str);// CString ChangeCharstr2Hexstr(CString Charstr);// int m_nReceiveBytes;//接收字節數計數 int m_nSendBytes;//發送字節數計數 CSerialPort m_SerialPort;//串口類對象
4,頭文件中 主對話框中 用到的控件 設置變量:
// Dialog Data //{{AFX_DATA(CCOMTOOLDlg) enum { IDD = IDD_COMTOOL_DIALOG }; CStatic m_DescriptionCtrl;//顯示描述串口參數的 靜態文本框 CButton m_SendCtrl;//發送控制按鈕控件 CButton m_OpenCloseCtrl;//打開關閉按鈕控件 CComboBox m_StopBits;//停止位 組合框 CComboBox m_Parity;//校驗位 組合框 CComboBox m_PortNO;//端口號 組合框 CComboBox m_BaudRate;//波特率 組合框 CComboBox m_DataBits;//數據位 組合框 CEdit m_SendPeriodCtrl;//發送周期控制文本框控件 CString m_strSend;//要發送的字符串顯示 CString m_strReceive;//接收的字符串顯示 BOOL m_bHexR;//十六進制接收 BOOL m_bHexS;//十六進制發送 BOOL m_bAutoSend;//自動發送標志位 long m_nSendPeriod;//發送周期 CString m_strStatus;//狀態顯示 CString m_strSendBytes;//發送字節計數顯示 CString m_strReceiveBytes;//接收字節計數顯示 CString m_strPortNO;//端口號顯示 CString m_strBaudRate;//波特率顯示 CString m_strDataBits;//數據位顯示 CString m_strStopBits;//停止位顯示 CString m_strParity;//校驗位顯示 //}}AFX_DATA
頭文件中控件響應函數;
// Implementation protected: HICON m_hIcon; // Generated message map functions //{{AFX_MSG(CCOMTOOLDlg) virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); afx_msg void OnAbout();//關於 afx_msg void OnQuit();//退出 afx_msg void OnClearSendEdit();//清除發送顯示 afx_msg void OnClearReceiveEdit();//清除接收顯示 afx_msg void OnBAutoSend();//自動發送 afx_msg void OnOpenClose();//打開關閉 afx_msg void OnClearCounter();//清除計數 afx_msg void OnReceiveChar(UINT ch, LONG port);//串口接收函數 afx_msg void OnSend();//手工發送函數 afx_msg void OnTimer(UINT nIDEvent);//定時器函數 afx_msg void OnBHexS();//十六進制發送 afx_msg void OnBHexR();//十六進制接收 //}}AFX_MSG DECLARE_MESSAGE_MAP()
5,源文件中對串口參數的相關定義:
//波特率 數組 int BaudRate[]={300,600,1200,2400,4800,9600,14400,19200,38400,56000,57600,115200,230400,460800,921600}; //校驗位選擇數 int ParitySelNum=5; //校驗位 數組 CString Parity[]={_T("None"),_T("Odd"),_T("Even"),_T("Mark"),_T("Space")}; //數據位 int DataBits[]={5,6,7,8}; //停止位 int StopBits[]={1,2};
6,手動添加相關變量的初始化:
// CCOMTOOLDlg dialog //手動添加 相關變量的初始化 CCOMTOOLDlg::CCOMTOOLDlg(CWnd* pParent /*=NULL*/) : CDialog(CCOMTOOLDlg::IDD, pParent) { //{{AFX_DATA_INIT(CCOMTOOLDlg) m_strSend = _T(""); m_strReceive = _T(""); m_bHexR = true; m_bHexS = true; m_bAutoSend = FALSE; m_nSendPeriod = 1000; m_strStatus = _T("關閉"); m_strSendBytes = _T("0"); m_strReceiveBytes = _T("0"); m_strPortNO = _T(""); m_strBaudRate = _T(""); m_strDataBits = _T(""); m_strStopBits = _T(""); m_strParity = _T(""); m_nSendBytes=0; m_nReceiveBytes=0; //}}AFX_DATA_INIT // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); }
7,主對話框初始化函數中,
// TODO: Add extra initialization here m_SendPeriodCtrl.EnableWindow(m_bAutoSend);//禁用周期設置文本框 m_OpenCloseCtrl.SetWindowText(_T("打開串口"));//按鈕上顯示的文本 m_DescriptionCtrl.SetWindowText("");// if(m_bHexS) GetDlgItem(IDC_SendEdit)->ModifyStyle(0,ES_UPPERCASE,0);//ModifyStyle,調用這個函數修改窗口的風格,此函數的厲害之處在於可以在窗口創建完成后修改窗口風格,雖然也有一些屬性改不了。 else GetDlgItem(IDC_SendEdit)->ModifyStyle(ES_UPPERCASE,0,0); if(m_bHexR) GetDlgItem(IDC_ReceiveEdit)->ModifyStyle(0,ES_UPPERCASE,0); else GetDlgItem(IDC_ReceiveEdit)->ModifyStyle(ES_UPPERCASE,0,0); CString temp; //顯示波特率 for(int i=0;i<sizeof(BaudRate)/sizeof(int);i++)//掃描所有的 { temp.Format("%d",BaudRate[i]);//從波特率數組中取出 數據 m_BaudRate.AddString((LPCTSTR)temp);//加到 組合框控件變量中 } temp.Format("%d",9600); m_BaudRate.SetCurSel(m_BaudRate.FindString(0,temp)); //顯示奇偶校驗 for (i=0;i<ParitySelNum;i++) { m_Parity.AddString((LPCTSTR) Parity[i]);//加到 組合框控件變量中 } m_Parity.SetCurSel(m_Parity.FindString(0,_T("None"))); //顯示停止位 for(i=0;i<sizeof(StopBits)/sizeof(int);i++) { temp.Format("%d",StopBits[i]); m_StopBits.AddString((LPCTSTR)temp);//加到 組合框控件變量中 } temp.Format("%d",1); m_StopBits.SetCurSel(m_StopBits.FindString(0,(LPCTSTR)temp)); //顯示數據位 for(i=0;i<sizeof(DataBits)/sizeof(int);i++) { temp.Format("%d",DataBits[i]); m_DataBits.AddString((LPCTSTR)temp);//加到 組合框控件變量中 } temp.Format("%d",8); m_DataBits.SetCurSel(m_DataBits.FindString(0,(LPCTSTR)temp)); //顯示串口設置 for(i=1;i<=MaxSerialPortNum-1;i++) { if(m_SerialPort.InitPort(this,i))//初始化串口號 { temp.Format("COM%d",i); //組合成COM1,COM2文本 m_PortNO.AddString((LPCTSTR)temp);//加到 組合框控件變量中 } } if(m_PortNO.GetCount())//獲取端口號數量 { m_SerialPort.InitPort(this,MaxSerialPortNum);//初始化串口號最大數量 m_PortNO.SetCurSel(0);//默認選中的串口號索引為0 } return TRUE; // return TRUE unless you set the focus to a control }
8,退出按鈕
void CCOMTOOLDlg::OnQuit() { // TODO: Add your control notification handler code here m_SerialPort.InitPort(this,MaxSerialPortNum);//初始化最大串口數量 PostQuitMessage(0);//提交退出消息 }
9,清除文本框顯示內容
//清除文本框顯示內容 void CCOMTOOLDlg::OnClearSendEdit() { // TODO: Add your control notification handler code here UpdateData(true); m_strSend=_T(""); UpdateData(false); } //清除文本框顯示內容 void CCOMTOOLDlg::OnClearReceiveEdit() { // TODO: Add your control notification handler code here UpdateData(true); m_strReceive=_T(""); UpdateData(false); }
10,清除計數
//清除計數 void CCOMTOOLDlg::OnClearCounter() { // TODO: Add your control notification handler code here UpdateData(true); m_nSendBytes=0;//發送字節數 m_nReceiveBytes=0;//接收字節數 m_strSendBytes=_T("0");//發送字節字符串 m_strReceiveBytes=_T("0");//接收字節字符串 UpdateData(false); }
11,串口接收函數
源文件中,消息響應:
BEGIN_MESSAGE_MAP(CCOMTOOLDlg, CDialog) //{{AFX_MSG_MAP(CCOMTOOLDlg) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_BN_CLICKED(IDC_ABOUT, OnAbout) ON_BN_CLICKED(IDC_QUIT, OnQuit) ON_BN_CLICKED(IDC_ClearS, OnClearSendEdit) ON_BN_CLICKED(IDC_ClearR, OnClearReceiveEdit) ON_BN_CLICKED(IDC_BAutoSend, OnBAutoSend)//自動發送按鈕消息響應 ON_BN_CLICKED(IDC_OpenClose, OnOpenClose) ON_BN_CLICKED(IDC_ClearCounter, OnClearCounter)//清除計數響應 ON_MESSAGE(WM_COMM_RXCHAR,OnReceiveChar)//串口接收消息響應 ON_BN_CLICKED(IDC_Send, OnSend)//手動發送按鈕消息響應 ON_WM_TIMER() ON_BN_CLICKED(IDC_BHexS, OnBHexS)//單選框點擊消息響應 ON_BN_CLICKED(IDC_BHexR, OnBHexR)//單選框點擊消息響應 //}}AFX_MSG_MAP END_MESSAGE_MAP()
頭文件中:消息映射// Generated message map functions
afx_msg void OnReceiveChar(UINT ch, LONG port);//串口接收函數
源文件中響應函數:
//串口接收函數 void CCOMTOOLDlg::OnReceiveChar(UINT ch, LONG port) { UpdateData(true); m_nReceiveBytes++;//接收一個字節就增加一次計數 CString temp; temp.Format("%d",m_nReceiveBytes);//計數顯示 m_strReceiveBytes=temp;//計數顯示 if(m_bHexR)//十進制接收顯示 m_strReceive+=DevideHexChar(ch)+_T(" ");//十六進制接收顯示,中間放一個空格。 else// m_strReceive+=ch;//直接顯示收到的字符,將字符加入到接收顯示字符串變量的尾部。 UpdateData(false); ((CEdit*)GetDlgItem(IDC_ReceiveEdit))->LineScroll( m_strReceive.GetLength()/(((CEdit*)GetDlgItem(IDC_ReceiveEdit))->LineLength())); }
字符-->字符串:
//字符-->字符串 CString CCOMTOOLDlg::DevideHexChar(char HexChar) { CString result=_T(""); int temp=(HexChar&0xF0)>>4; if(temp<10) result+=(temp+'0'); else result+=(temp+'A'-10); temp=HexChar&0x0F; if(temp<10) result+=(temp+'0'); else result+=(temp+'A'-10); return result; }
文本框中的相關操作
int m_iLineCurrentPos=((CEdit*)(GetDlgItem(IDC_SHOWMESSAGE)))->GetLineCount();//獲得ID為IDC_SHOWMESSAGE的控件類型為edit的文本行數 ((CEdit *)(GetDlgItem(IDC_SHOWMESSAGE)))->LineScroll(m_iLineCurrentPos);//設滾動到文本末尾
CEdit::LineScroll void LineScroll(int nLine,int nChars = 0); 參數: nLine 指定縱向滾動的行數。 nChars 指定水平滾動的字符數。如果編輯控件使用ES_RIGHT或ES_CENTER風格,此值無效。 說明: 調用此成員函數滾動多行編輯控件的文本。 此成員函數僅用於多行編輯控件。 編輯控件的縱向滾動不能超過該文本的最后一行,如果當前行號加上由nLines指定的行數超過編輯控件中的總行數,則它的值被調整而使得文本的最后一行滾動達到編輯控件窗口的頂端。 此函數可以水平滾動經過每行的最后一個字符。
12,自動發送單選框響應函數
//自動發送單選框響應函數 void CCOMTOOLDlg::OnBAutoSend() { // TODO: Add your control notification handler code here UpdateData(true); m_SendPeriodCtrl.EnableWindow(m_bAutoSend);//文本框不可用 if(m_bAutoSend) //自動發送 { // m_SendCtrl.SetWindowText("開始自動發送");//設置按鈕上的文本 } else //停止自動發送 { m_SendCtrl.SetWindowText("手動發送"); KillTimer(1);//停止自動發送,手動發送 } }
13,定時發送
//定時器,定時發送 void CCOMTOOLDlg::OnTimer(UINT nIDEvent) { // TODO: Add your message handler code here and/or call default UpdateData(true); CString temp; temp=m_strSend;//要發送的字符串 if(m_bHexS)//十六進制發送 temp=ChangeCharstr2Hexstr(temp);//字符型字符串-->十六進制字符串 m_SerialPort.WriteToPort(temp.GetBuffer(temp.GetLength()),temp.GetLength());//寫到串口(內存,長度) m_nSendBytes+=temp.GetLength();//發送計數 m_strSendBytes.Format("%d",m_nSendBytes);//顯示計數 UpdateData(false); CDialog::OnTimer(nIDEvent); }
//字符型字符串-->十六進制字符串 CString CCOMTOOLDlg::ChangeCharstr2Hexstr(CString Charstr) { CString Hexstr=_T(""); Charstr.MakeUpper(); HexStringFilter(Charstr); int Length=Charstr.GetLength(); if(Length%2) Charstr.Delete(Length-1); Length=Charstr.GetLength(); for(int i=0;i<Length/2;i++) { Hexstr+=CombineHexChar(Charstr.GetAt(i*2),Charstr.GetAt(i*2+1)); } return Hexstr; }
//十六進制字符過濾 void CCOMTOOLDlg::HexStringFilter(CString &str) { BOOL bOK; for(int i=0;i<str.GetLength();) { bOK=((str.GetAt(i)>='0')&&(str.GetAt(i)<='9'))|| ((str.GetAt(i)>='A')&&(str.GetAt(i)<='F'))|| ((str.GetAt(i)>='a')&&(str.GetAt(i)<='f')); if(!bOK) str.Delete(i); else i++; } } //比較十六進制字符 char CCOMTOOLDlg::CombineHexChar(char CharH,char CharL) { char result; CString temp; if(CharH>='0'&&CharH<='9') result=(CharH-'0'); else if(CharH>='a'&&CharH<='f') result=(CharH-'a'+10); else if(CharH>='A'&&CharH<='F') result=(CharH-'A'+10); else result=0; result<<=4; if(CharL>='0'&&CharL<='9') result+=(CharL-'0'); else if(CharL>='a'&&CharL<='f') result+=(CharL-'a'+10); else if(CharL>='A'&&CharL<='F') result+=(CharL-'A'+10); else result+=0; return result; }
14,十六進制接收,發送:
//十六進制發送 void CCOMTOOLDlg::OnBHexS() { // TODO: Add your control notification handler code here UpdateData(true); if(m_bHexS) GetDlgItem(IDC_SendEdit)->ModifyStyle(0,ES_UPPERCASE,0); else GetDlgItem(IDC_SendEdit)->ModifyStyle(ES_UPPERCASE,0,0); } //十六進制接收 void CCOMTOOLDlg::OnBHexR() { // TODO: Add your control notification handler code here UpdateData(true); if(m_bHexR) GetDlgItem(IDC_ReceiveEdit)->ModifyStyle(0,ES_UPPERCASE,0); else GetDlgItem(IDC_ReceiveEdit)->ModifyStyle(ES_UPPERCASE,0,0); }
15,自動發送單選框響應函數:
//自動發送單選框響應函數 void CCOMTOOLDlg::OnBAutoSend() { // TODO: Add your control notification handler code here UpdateData(true); m_SendPeriodCtrl.EnableWindow(m_bAutoSend);//時間文本框 if(m_bAutoSend) //自動發送 { // m_SendCtrl.SetWindowText("開始自動發送");//設置按鈕上的文本 } else //停止自動發送 { m_SendCtrl.SetWindowText("手動發送"); KillTimer(1);//停止自動發送,手動發送 } }
改變了發送數據按鈕文本內容,,,,開始自動發送,,,手動發送,,,這兩種方式。
16,打開關閉串口按鈕:
//打開關閉串口按鈕 void CCOMTOOLDlg::OnOpenClose() { // TODO: Add your control notification handler code here CString temp; m_OpenCloseCtrl.GetWindowText(temp);//獲得按鈕上文本內容 UpdateData(true); if(temp==_T("關閉串口"))//關閉串口 { m_SerialPort.InitPort(this,MaxSerialPortNum);//關閉串口 關閉串口 m_OpenCloseCtrl.SetWindowText(_T("打開串口"));// m_strStatus=_T("關閉");//狀態為:關閉 UpdateData(false); m_DescriptionCtrl.SetWindowText(""); m_SendCtrl.GetWindowText(temp); if(temp=="停止自動發送")//停止自動發送 { KillTimer(1); m_SendCtrl.SetWindowText("開始自動發送"); } } else if( m_PortNO.GetCount())//打開串口 { int SelPortNO,SelBaudRate,SelDataBits,SelStopBits; char SelParity; UpdateData(true); temp=m_strPortNO;//串口號 temp.Delete(0,3);// SelPortNO=atoi(temp);//字符串到整型 SelBaudRate=atoi(m_strBaudRate);// SelDataBits=atoi(m_strDataBits); SelParity=m_strParity.GetAt(0);//校驗位 SelStopBits=atoi(m_strStopBits); //串口初始化 if(m_SerialPort.InitPort(this,SelPortNO,SelBaudRate,SelParity,SelDataBits,SelStopBits,EV_RXCHAR|EV_CTS,512)) { //啟動串口監控 m_SerialPort.StartMonitoring(); //按鈕文本設置 m_OpenCloseCtrl.SetWindowText(_T("關閉串口")); //狀態顯示設置 m_strStatus=_T("打開"); UpdateData(false); //要顯示的字符串組合 temp=m_strPortNO+" , 波特率: "+m_strBaudRate+"bps, 校驗位: "+m_strParity+ ", 數據為: "+m_strDataBits+" , 停止位: "+m_strStopBits; m_DescriptionCtrl.SetWindowText(temp); } else AfxMessageBox("該串口已經被其他應用程序所占用!\n請選擇其它的串口"); } }
18,退出對話框,關閉串口
void CCOMTOOLDlg::OnQuit() { // TODO: Add your control notification handler code here m_SerialPort.InitPort(this,MaxSerialPortNum);//初始化最大串口數量,關閉串口 PostQuitMessage(0);//提交退出消息 }