运行效果
具体操作步骤:打开程序,点击设置按钮,设置串口号,波特率,奇偶校验。完成后点击打开串口,可发送和接受串口数据。
实现功能:串口发送只能以字符串方式发送,串口接收数据需要以AA为帧头后跟9位字符。如果把串口tx和rx直接相连,在程序中测试会产生如下效果:
该工程的基础一篇百度文库的文档,链接如下:http://wenku.baidu.com/link?url=NkLGGRkSMk5TLd0exqQGVNN6KmQ8wwurD8QbsiWc68e6Kz0vXKEzrXqsVtFwrOn6uvwVoG3yUwepdY8yuqV13Wu87J2g-gX3T2gqsjIdxuG
实现代码
设置按键:
void CserialportDlg::OnBnClickedButtonset()
{
// TODO: 在此添加控件通知处理程序代码
CString strStatus,strTemp;
double dblBand;
if(m_setupdlg.DoModal()==IDOK) //模态对话框
{
ComNumber=m_setupdlg.m_Com+1;
dblBand=pow(2,(double)m_setupdlg.m_BandRate);
dblBand=115200/dblBand;
strStatus.Format(_T("%.0f"),dblBand);
BandRate=strStatus;
switch(m_setupdlg.m_Parity)
{
case 0:
{
Parity="N";
break;
}
case 1:
{
Parity="O";
break;
}
case 2:
{
Parity="E";
break;
}
}
strStatus="COM";
strTemp.Format(_T("%d"),ComNumber);
strStatus+=strTemp;
strStatus+=",";
strStatus+=BandRate;
strStatus+=",";
strStatus+=Parity;
strStatus+=",8,1";
m_Para=strStatus;
UpdateData(false);
}
///write config file:string and test
CString m_strSetPath;
CString test;
m_strSetPath=("C:\\config.ini");
WritePrivateProfileString(_T("setting"),("compara"),m_Para,m_strSetPath);
//结构名,变量名,要写的字符串,路径
GetPrivateProfileString(_T("setting"),_T("compara"), NULL,test.GetBuffer(100),100,m_strSetPath);
/*
//test
m_Edit.SetSel(10000,10000);
m_Edit.ReplaceSel(test);
m_Edit.ReplaceSel(_T("\n"));
UpdateData(false);
*/
//write config file:struct and test
/*
struct Data
{
int a1;
CString a2;
CString a3;
};
Data d1, d2;
*/
d1.a1=ComNumber;
d1.a2=BandRate;
d1.a3=Parity;
WritePrivateProfileStruct(_T("Struct"),_T("test"),&d1,sizeof(Data),m_strSetPath);
//写入配置文件
if(m_setupdlg.m_bgridoff)
{
m_Edit.ReplaceSel(_T("gridoff"));
UpdateData(false);
}
if(m_setupdlg.m_bgridon)
{
m_Edit.ReplaceSel(_T("gridon"));
UpdateData(false);
}
}
打开串口按键:
void CserialportDlg::OnBnClickedButtonopen()
{
// TODO: 在此添加控件通知处理程序代码
if(m_mscomm.get_PortOpen()) //如果串口是打开的,则行关闭串口
{
m_mscomm.put_PortOpen(FALSE);
}
m_mscomm.put_CommPort(ComNumber);
CString strPara;
strPara=BandRate;
strPara+=",";
strPara+=Parity;
strPara+=",8,1";
m_mscomm.put_Settings(strPara);
m_mscomm.put_InBufferSize(1024);//接收缓冲区
m_mscomm.put_InBufferCount(0);
m_mscomm.put_OutBufferSize(1024);//发送缓冲区
m_mscomm.put_InputLen(0);//设置当前接收区数据长度为0,表示全部读取
m_mscomm.put_InputMode(1);//以二进制方式读写数据 0是字符
m_mscomm.put_RThreshold(1);//接收缓冲区有1个及1个以上字符时,将引发接收数据的 OnComm
m_mscomm.put_PortOpen(TRUE);
AfxMessageBox(_T("串口打开成功"));
GetDlgItem(IDC_BUTTONCLOSE)->EnableWindow(TRUE); //按钮可见
}
串口关闭按钮:
void CserialportDlg::OnBnClickedButtonclose()
{
// TODO: 在此添加控件通知处理程序代码
m_mscomm.put_PortOpen(FALSE);
AfxMessageBox(_T("串口关闭成功"));
GetDlgItem(IDC_BUTTONCLOSE)->EnableWindow(FALSE); //按钮可见
}
串口如果关闭不成功,可能会影响别的程序使用该串口,所以关闭成功一定要以消息框的形式显示。
串口发送数据按钮:
void CserialportDlg::OnBnClickedButtonsend()
{
// TODO: 在此添加控件通知处理程序代码
UpdateData(true);
m_mscomm.put_Output(COleVariant(m_EditSend));
m_EditSend.Empty();
UpdateData(false);
}
串口发送只能以字符串方式发送,如果要以16进制发送,可以在此写处理代码,把16进制转换成字符串方式。
接收数据:
void CserialportDlg::OnCommMscomm()
{
// TODO: 在此处添加消息处理程序代码
int nEvent;
nEvent = m_mscomm.get_CommEvent();
switch(nEvent)
{
case 2: //接收缓冲区有数据
g_bCommDataRcv = true; //为了控制数据接收线程
break;
case 3:
break;
default:
break;
}
VARIANT vResponse;
int len, i;
unsigned char buf[200];
COleSafeArray Oledata;
BYTE data[200];
len =m_mscomm.get_InBufferCount(); //获取串口接收缓冲区数据字节数
if (len > 0)
{
vResponse = m_mscomm.get_Input(); //读取缓冲区数据
Oledata = vResponse;
len = Oledata.GetOneDimSize();
for(i=0; i<len; i++)
{
Oledata.GetElement((long *)&i, data+i);
}
for(i=0; i<len; i++)
{
char a = *(char *)(data+i);
buf[i] = (unsigned char)a;
}
//串口数据进入字符队列
int temp=BYTEsequence.GetCount();
for(int k=0;k<len;k++)
BYTEsequence.InsertAt(temp++,buf[k]);
}
bool bHead = FALSE;
int location=0;
int tt=0;
char information[300]; //跟随length变化
CString medit;
while (BYTEsequence.GetCount() > 2*length+10) //从全局数据队列BYTEsequence查找一个完整的合法数据包
{
len=BYTEsequence.GetCount();
//验证数据包的同步头
if (BYTEsequence.GetAt(tt)==0x41 &&BYTEsequence.GetAt(tt+1) ==0x41) //帧头AA
{
bHead = TRUE;
location=tt;
}
tt++;
if(tt>length+2)
tt=0;
if (bHead)
{
for(int k=0;k<length+1;k++)
{
information[k]=BYTEsequence.GetAt(location+2+k);
if(k==length)
information[k]='\0';
}
BYTEsequence.RemoveAt(location,length+2); //移除数据长加帧头帧尾长度
if(location>0)
for(int k=location-1;k>=0;k--)
BYTEsequence.RemoveAt(k,1);
//输出information
medit.Format(_T("%s"),information);
m_Edit.SetSel(10000,10000);
m_Edit.ReplaceSel(medit);
m_Edit.ReplaceSel(_T("\n"));
UpdateData(false);
bHead=FALSE;
}
}
}
数据接收的函数编程工作量最大,其基本思路就是当串口缓存区有数据时触发这个函数,将接收到的数据写入字符队列,然后判断字符队列长度,一旦超过了两帧数据,就进行解析。首先识别帧头,把帧头前面的数据全部舍弃,从帧头开始取出一帧数据的长度,并把它显示在接收区。
用mscomm32控件操作串口有点复杂,这个程序对刚接触串口通信的人来说有点难度,所以我写了一个在stm32中的串口中断函数,思想和上面这个一样。
void UART3_IRQHandler()
{
//这个的数据格式是16进制,帧头D0+数据长度(01-FF)+目标地址(两位16进制数,如FF FF)+数据(16进制,超出数据长度的忽略)
uint8 temp;
temp = 1;
if (1)
{
temp = uart_getchar();
//TODO: put your code here
if (RcvState == 0)
{
if (temp == 0xD0)
RcvState = 1;
}
else if (RcvState == 1)
{
RcvSum = temp + 0x02;
RcvState = 2;
}
else if (RcvState == 2)
{
if (RcvCnt < RcvSum)
{
RcvMsg[RcvCnt] = temp;
RcvCnt++;
}
if (RcvCnt == RcvSum)
{
AnalyseInfo(); //在这个函数中进行数据处理,如果数据发送太快,而处理时间比较长,可以另开一个优先级比较低的中断处理,确保数据不会丢失。
RcvCnt = 0;
RcvSum = 0;
RcvState = 0;
}
}
}
}
关于读串口的操作分为两种方式,轮询方式和中断方式。轮询方式很少使用,除非你只干读串口这一件事。中断方式比较普遍,但要注意如果还有其他比较耗时的中断,需要将串口中断优先级设置的高一点,以免漏过数据。
关于串口通信,这一篇只是一个简单的例子,以后会有更加高级的例子和一些我用过的比较好的串口通信模块。