实现串口通信,使用的类文件是SerialPort.cpp。在项目中使用mscomm控件的时候,串口连续传递若干数据后,会出现卡死的情况,关闭串口再打开,继续读取的话可以正常通信。
为了解决这个问题,想到就用SerialPort串口类来实现会好吧。当然,完全用windows的api函数来实现也可以,太麻烦吧,我也没用过。用微软的一些控件编程虽然容易了,但是也不熟悉底层。
软件是用的vc6.0平台。
软件主界面为:
点了自动发送单选框后:
点开始发送按钮:
一个界面就知道很好实现。
借这个例子,重点来梳理一下串口类的使用。
1, 主对话框头文件中引入 #include"SerialPort.h"
2, 声明串口类对象 CSerialPort m_SerialPort;//串口类对象
3, 头文件中自己添加的函数和变量:
// CCOMTOOLDlg dialogclass 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_MSGDECLARE_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 Win32m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); }
7,主对话框初始化函数中,
// TODO: Add extra initialization herem_SendPeriodCtrl.EnableWindow(m_bAutoSend);//禁用周期设置文本框m_OpenCloseCtrl.SetWindowText(_T("打开串口"));//按钮上显示的文本m_DescriptionCtrl.SetWindowText("");// if(m_bHexS)GetDlgItem(IDC_SendEdit)->ModifyStyle(0,ES_UPPERCASE,0);//ModifyStyle,调用这个函数修改窗口的风格,此函数的厉害之处在于可以在窗口创建完成后修改窗口风格,虽然也有一些属性改不了。elseGetDlgItem(IDC_SendEdit)->ModifyStyle(ES_UPPERCASE,0,0);if(m_bHexR)GetDlgItem(IDC_ReceiveEdit)->ModifyStyle(0,ES_UPPERCASE,0);elseGetDlgItem(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 herem_SerialPort.InitPort(this,MaxSerialPortNum);//初始化最大串口数量PostQuitMessage(0);//提交退出消息 }
9,清除文本框显示内容
//清除文本框显示内容 void CCOMTOOLDlg::OnClearSendEdit() {// TODO: Add your control notification handler code hereUpdateData(true);m_strSend=_T("");UpdateData(false); } //清除文本框显示内容 void CCOMTOOLDlg::OnClearReceiveEdit() {// TODO: Add your control notification handler code hereUpdateData(true);m_strReceive=_T("");UpdateData(false); }
10,清除计数
//清除计数 void CCOMTOOLDlg::OnClearCounter() {// TODO: Add your control notification handler code hereUpdateData(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::LineScrollvoid LineScroll(int nLine,int nChars = 0);参数: nLine 指定纵向滚动的行数。 nChars 指定水平滚动的字符数。如果编辑控件使用ES_RIGHT或ES_CENTER风格,此值无效。说明: 调用此成员函数滚动多行编辑控件的文本。 此成员函数仅用于多行编辑控件。 编辑控件的纵向滚动不能超过该文本的最后一行,如果当前行号加上由nLines指定的行数超过编辑控件中的总行数,则它的值被调整而使得文本的最后一行滚动达到编辑控件窗口的顶端。 此函数可以水平滚动经过每行的最后一个字符。
12,自动发送单选框响应函数
//自动发送单选框响应函数 void CCOMTOOLDlg::OnBAutoSend() {// TODO: Add your control notification handler code hereUpdateData(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 defaultUpdateData(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 hereUpdateData(true);if(m_bHexS)GetDlgItem(IDC_SendEdit)->ModifyStyle(0,ES_UPPERCASE,0);elseGetDlgItem(IDC_SendEdit)->ModifyStyle(ES_UPPERCASE,0,0); } //十六进制接收 void CCOMTOOLDlg::OnBHexR() {// TODO: Add your control notification handler code hereUpdateData(true);if(m_bHexR)GetDlgItem(IDC_ReceiveEdit)->ModifyStyle(0,ES_UPPERCASE,0);elseGetDlgItem(IDC_ReceiveEdit)->ModifyStyle(ES_UPPERCASE,0,0); }
15,自动发送单选框响应函数:
//自动发送单选框响应函数 void CCOMTOOLDlg::OnBAutoSend() {// TODO: Add your control notification handler code hereUpdateData(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 herem_SerialPort.InitPort(this,MaxSerialPortNum);//初始化最大串口数量,关闭串口PostQuitMessage(0);//提交退出消息 }