vc 使用了SerialPort类的串口通信软件分析
实现串口通信,使用的类文件是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);//提交退出消息 }
发现自己的不足,善于利用找到的方法去扬长避短。行动起来。