***实际工程问题:关于串口接收帧数据及数据提取处理的问题,别错过!!!****
在使用串口接收数据,数据的传输协议中数据是按帧传送的,格式定义如下:
字节序号 内容 备注
0 帧头0 0xA5
1 帧头1 0x5A
2 速度高4位 分辨率:m/s/bit
3 速度低4位
4 高度高4位 分辨率:0.1m/s
5 高度低4位
6~9 经度 4字节,单精度浮点数,低字节在前,高字节在后
10 校验和 0~9累加和,值为0则本帧数据正确
所以一帧数据格式大致如下:
A5 5A 00 04 B3 02 00 A1 06 00
请问:
1、 串口接收时,该如何接收数据呢?是一个一个字符的接收(类似串口助手),然后判断帧头,那该如何提取一帧数据?还是怎么实现呢?
2、 一帧数数据判断校验和正确了后,数据提取的处理该怎么实现呢/?例如速度:是(data[2]×255+data[3])*1=V吗?那经度这种定义该怎么转换呢?
我是初次接触此类数据接收处理的编程,那些简单的串口接收发送的例子是会了,但是到这种工程性的问题就不知该怎样解决了,请各位有经验的GGJJ给予指导了,解决了可以另给分了,最好能给些例子代码了。谢谢啊!!!!!!!!!!11
问题点数:100、回复次数:26Top
1 楼whiteclouds(无聊的coding生活)回复于 2006-12-01 11:49:53 得分 0
1、一边接收一边计算校验和,计算为0了就是完整了,或者统计接收字节数,够11个就处理
2、速度我想应该是data[2]+data[3]/10000吧?高度:data[4]/10+data[5]/100000,经度:直接把这几个字节复制到float型变量的地址,不要告诉我你不会用指针Top
2 楼alfwolf(木马煞)回复于 2006-12-01 12:01:19 得分 0
通常是这样处理的:
1.设置一个循环缓冲区.
2.串口接收的数据先存入循环缓冲区中,修改写指针位置.
3.设置协议对象,扫描循环缓冲区中的特征字节(一般是报头).找到了然后看循环缓冲区读写指针的距离是否够一帧数据.
4.如果够一帧长度,则从报头按照报文长度进行数据校验和解析.
5.修改读指针位置.
注意,你可以通过单独的线程处理串口数据写入循环缓冲区.Top
3 楼zhoujie20153(jack)回复于 2006-12-01 12:06:03 得分 0
先确认帧头, 然后边接收边计算检验和,判断是否为0,
Top
4 楼lcxf(阿春)回复于 2006-12-01 12:07:24 得分 0
漏打了:速度的分辨率1m/s/bit.能给些具体的代码吗/Top
5 楼SeRapHiw(灯芯草)回复于 2006-12-01 12:53:23 得分 0
1.数据帧的判断,
alfwolf(在地狱中仰望天堂)说得清楚了
2.数据的传送,以经度为例。
a.经度可以把东、西经的0.0-180.0度转到0.0 - 360.0区间,
b.若保留7位小数,对这个float型的经度×10~7转成整数。
(注:360×10^7 < 2^(4×8字节)-1, 不会超出4个字节的存储字长限制)
c.把这个整数存入4个字节的数据帧里的相应位置。
d.传送数据帧
e.接收端解析数据
unsigned char data[11];//用于传送的数据帧
double fLongitude = 260.2258654; //假设经度
long int iLongitude; //一个long int为4字节,Turbo C,
iLong = (long int)(flongitude*10000000);
int i;
for(i=0;i<4;i++)
{
data[i+6] = (unsigned char)(iLong>>(i*8))
}
接收端进行上述变换的反变换就可以得到经度值了。
(程序片断没仔细推敲)
Top
6 楼lcxf(阿春)回复于 2006-12-04 12:16:10 得分 0
先顶一下!!Top
7 楼alexmayer(小豹)回复于 2006-12-04 12:34:20 得分 0
关于数据帧的判断,whiteclouds(OUTOUTOUT!) 兄弟的第一种说法似有商榷之处,如果有误码,岂不是有可能永远都收不完?应该是收取完整一帧报文,再进行报文合理性验证。像楼主这样的定长帧,应该使用第二种,统计接收字节数,够11个就处理。
Top
8 楼alexmayer(小豹)回复于 2006-12-04 12:36:59 得分 0
alfwolf(在地狱中仰望天堂)兄弟,你的说法能不能提供一个Demo?像楼主这样的例子是有特征字节,如果没有帧头、帧尾而且不定长呢?Top
9 楼alfwolf(木马煞)回复于 2006-12-04 14:42:34 得分 0
我曾经做了几年的电力系统自动化软件,主要负责的就是数据集结和规约(协议)转换,楼主的例子其实只能作定长帧报文处理,对于变长帧,可以参考IEC870-5-101规约的相关规定,主要是必须具备报头,帧长,控制字,状态字以及校验字和报尾等.
至于demo,我不可以提供,因为我没有测试例子,只有商业成品.也算遵守一些游戏规则吧.呵呵,真的很抱歉.Top
10 楼alfwolf(木马煞)回复于 2006-12-04 14:44:21 得分 0
"没有帧头、帧尾而且不定长呢"
设计这样协议的人应该被打,呵呵Top
11 楼alexmayer(小豹)回复于 2006-12-04 15:10:33 得分 0
"没有帧头、帧尾而且不定长呢"
设计这样协议的人应该被打,呵呵
ModBus的制订者也要被打吗?它的RTU模式下
地址 功能代码 数据数量 数据 1 ... 数据 n CRC 高字节 CRC 低字节
有帧头、帧尾标识字节吗?
Top
12 楼alexmayer(小豹)回复于 2006-12-04 15:13:21 得分 0
ModBus标准中这样写到
消息中字符间发送的时间间隔最长不能超过1秒,否则接收的设备将认为传输错误。……使用RTU模式,消息发送至少要以3.5个字符时间的停顿间隔开始。在网络波特率下多样的字符时间,这是最容易实现的(如下图的T1-T2-T3-T4所示)。传输的第一个域是设备地址。可以使用的传输字符是十六进制的0...9,A...F。网络设备不断侦测网络总线,包括停顿间隔时间内。当第一个域(地址域)接收到,每个设备都进行解码以判断是否发往自己的。在最后一个传输字符之后,一个至少3.5个字符时间的停顿标定了消息的结束。一个新的消息可在此停顿后开始。
整个消息帧必须作为一连续的流转输。如果在帧完成之前有超过1.5个字符时间的停顿时间,接收设备将刷新不完整的消息并假定下一字节是一个新消息的地址域。同样地,如果一个新消息在小于3.5个字符时间内接着前个消息开始,接收的设备将认为它是前一消息的延续。这将导致一个错误,因为在最后的CRC域的值不可能是正确的。
它是靠时间确定一帧一帧的报文的!Top
13 楼alexmayer(小豹)回复于 2006-12-04 15:20:16 得分 0
在单片机中、VB环境中,我都是用定时器定时扫描串口输入缓冲区,如果串口缓冲区中存在数据,并且在规定的时间间隔(比如3.5个字符时间)外的两次扫描缓冲区字符数量不变,就认为数据接收完整,于是将他们读出,校验、处理。但是VC该怎么做呢?
alfwolf(在地狱中仰望天堂)兄弟,你能给个好建议吗?我是作子站RTU的,看来你是作主站调度的?先握个手!Top
14 楼youyingbo()回复于 2006-12-04 15:21:50 得分 0
所以一帧数据格式大致如下:
A5 5A 00 04 B3 02 00 A1 06 00
------------------
你给的例子好像少一位啊
如果你们返回的数据每次就是1桢就好办,每次全接收完就行了
如果好多桢,一个一个接收,并放到全局数组里去,数组可以定义成11的长度。当当前接收的这个字符是5A,并且前一个是A5时,那么A5前的那些是一阵,发消息处理就行了。这是,清空全局数组,把前两位赋成 A5 5A,接着接收
至于提取数据,你的上司或定协议的人肯定清楚,那些数据是什么意思,怎么提取出来等等,最起码有个文档之类的吧Top
15 楼lcxf(阿春)回复于 2006-12-04 15:46:26 得分 0
谢谢各位对我问题的关注,
我的思路:
1)定义FrameData[11]为全局变量,作为自定义的缓冲区,
VARIANT vResponse;
if(m_Com.GetCommEvent()==2)
{
k=m_Com.GetInBufferCount();
for(int i=0;i<k;i++)
{
m_Com.SetInputLen();
vResponse=m_Com.GetInput();
然后将串口缓冲区的数据读到我定义的缓冲区FrameData[11]中,这里该如何实现,我不知该怎么写代码???????????
}
2.校验和判断
BYTE SUM=0;
for(int i=0;i<11;i++)
{
SUM+=FrameData[i];
}
if(SUM==0)
{
ProcData(FrameData);//如果数据完整,则数据处理并显示
}
3.ProcData()函数是数据处理,也就是根据协议完成数据转换,什么高4位,低四位,分辨率等,及精度的低字节在前高字节在后,该如何实现,一直没头绪.
我的理解也不知是否正确,请alexmayer(小豹) ( )/alfwolf(在地狱中仰望天堂) ( ) /SeRapHiw(灯芯草) ( ) whiteclouds(OUTOUTOUT!) ( ) 再关注!!!!!
alfwolf(在地狱中仰望天堂) ( ) ,能否发部分代码给我学习呢,chunch@tom.com,可另给分,拜托!!!
Top
16 楼lcxf(阿春)回复于 2006-12-04 15:50:01 得分 0
youyingbo() ( ) :
不好意思,我是少写了一位,我只是举个例子.
你给出的思路很好,谢谢.协议内容就这些啊.Top
17 楼alfwolf(木马煞)回复于 2006-12-04 18:43:38 得分 0
alexmayer(小豹):也握个手,modbus协议我的确没有接触过,但是可以看出这个规约并不能适应基于非实时操作系统下大数据量集结和处理上的,基于准实时操作系统的应用程序也很难使用这个协议,它可能比较适合用于单片机或者DSP.
我的确是做主站的,呵呵,曾经做过的工作也包括RTU方面,我是在WinCE下实现,里面使用了CDT规约和101规约.我觉得在Windows操作系统中仅仅根据间隔时间来区分报文是不现实的,至少并不可靠.Top
18 楼jj2003()回复于 2006-12-05 10:14:43 得分 0
这是用SerialPort类写的串口程序接受数据部分,里面有校验,你用的好像是MSComm控件,个人觉得使用不太方便,最好用api直接写,或者用这个类也不错
LONG CSerialPortTestDlg::OnComm(WPARAM ch, LPARAM port)
{
static char checksum=0,checksum1=0;
static int count1=0;//,count2=0,count3=0;
static unsigned char buf[20];
static char c1,c2; //用于计算半Byte校验
static int flag; //用于接收阶段标记
static int twoflag=0;
if(port == m_unPort)
{
if(ch>127) //是不是首字节
{
count1=0; //记录接收字符的个数
buf[count1]=ch;
checksum1= ch-128; //开始计算校验值
}
else
{
count1++;
buf[count1]=ch;
checksum1 = checksum1^ch;
if(count1==3) //包括校验字节在内的全部接收完毕
{
if(checksum1) //校验错
{ m_strEditReceiveMsg = "接收校验出错";
UpdateData(FALSE);
}
else
{
CString str;
unsigned char * temp=(unsigned char*)buf;
m_strEditReceiveMsg ="接收到的简单通信协议字节为:";
for(int i=0;i<4;i++)
{
str.Format("%02X ",*(buf+i));
m_strEditReceiveMsg += str;
}
UpdateData(FALSE);
}
if(count1>5) //防止出错
count1=0;
}
}
}
if(port==m_unPort2) //串口2的数据处理
{
m_strPortRXData2 += (char)ch;
switch(ch)
{
case '$':
checksum=0; //开始计算CheckSum
flag=0;
break;
case '*': //有效数据结束,可以$和*之间数据的半Byte校验值了
flag=2;
c2=checksum & 0x0f; c1=((checksum >> 4) & 0x0f);
if (c1 < 10) c1+= '0'; else c1 += 'A' - 10;
if (c2 < 10) c2+= '0'; else c2 += 'A' - 10;
break;
case CR: //这句必须加上,否则会出错的
break;
case LF: //数据包的最后一个字符
m_strPortRXData2.Empty();
break;
default:
if(flag>0) //注意:只有在接收到'*'后,flag才大于0
{
m_strChecksum2 += ch;
if(flag==1)
{
CString strCheck="";
strCheck.Format("%c%c",c1,c2);
if(strCheck!=m_strChecksum2) //校验计算不正确,说明接收数据出错
{
m_strPortRXData2.Empty();
}
else //校验计算正确则处理数据
{
CString strSwitchSetData;
strSwitchSetData = m_strPortRXData2.Mid(1,9);
//以下设置信号灯状态
for(int i=0;i<9;i++)
{
if(strSwitchSetData.Mid(i,1)=="1")
SetSwitchStatus(i+1,TRUE);
else
SetSwitchStatus(i+1,FALSE);
}
//以下取出位置与速度数据
CString strTemp;
strTemp = m_strPortRXData2.Mid(10,5);
char *temp=(char*)((LPCTSTR)strTemp);
char tbuf[4];
tbuf[0]=temp[0]; tbuf[1]=temp[1]; tbuf[2]=0;
m_unEditPosition2 = atoi(tbuf); //得到位置状态值
tbuf[0]=temp[2]; tbuf[1]=temp[3];
tbuf[2]=temp[4]; tbuf[3]=0;
m_unEditVelocity2 = atoi(tbuf); //得到速度值
//将接收到的数据包内容显示
m_strEditDispData2 = "接收到NMEA数据包:"+m_strPortRXData2;
//下面准备发送"简单自定义通信协议"数据包
unsigned char ucChar[4];
//首字节
ucChar[0]=0x80; //首字节最高位置1
if(strSwitchSetData.Mid(0,1)=="1") // 开关1状态
ucChar[0] |= 0x40; //0100 0000
else
ucChar[0] &= 0xBF; //1011 1111
if(strSwitchSetData.Mid(1,1)=="1") // 开关2状态
ucChar[0] |= 0x20; //0010 0000
else
ucChar[0] &= 0xDF; //1101 1111
if(m_unEditPosition2>31) //对位置值进行限值
m_unEditPosition2=31;
ucChar[0] &= 0xE0; //将首字节的低5位置0
ucChar[0] += m_unEditPosition2; //再加上位置值
ucChar[3] = ucChar[0]; //同时计算校验值
//第二字节
unsigned char ucTemp=0x40;
for(i=0; i<7; i++)
{
if(strSwitchSetData.Mid(2+i,1)=="1")
ucChar[1] |= ucTemp;
else
ucChar[1] &= (~ucTemp);
ucTemp >>= 1;
}
ucChar[1] &= 0x7F; //前面做了那么多运算,用这条语句保证一下最高位为0
ucChar[3] ^= ucChar[1]; //计算校验值
//第三字节
if(m_unEditVelocity2>120)
m_unEditVelocity2=120;
ucChar[2] = m_unEditVelocity2;
ucChar[3] ^= ucChar[2]; //计算校验值
//第四字节
ucChar[3] &= 0x7F;
//把"简单自定义通信协议"数据包发送出去
if(m_bSerialPortOpened2)
m_SerialPort2.WriteToPort(ucChar,4);
//同时把发送的内容显示
CString strTemp1;
strTemp= _T("发送的内容为: ");
for(int j=0;j<4;j++)
{
strTemp1.Format("0x%02X",ucChar[j]);
strTemp += strTemp1 + ",";
m_strEditDispData2 += strTemp;
UpdateData(FALSE);
}
m_strChecksum2.Empty();
}
//从'*'后收,flag=2,1次减1操作,正好将数据包的校验值保存在m_strChecksum中
flag--;
}
else
checksum=checksum^ch; //当flag<=0时,计算校验值
break;
}
}
return 0;
}Top
19 楼loseme915(郁闷)回复于 2006-12-05 10:54:44 得分 0
楼主你这个协议还是很简单的协议啊。至于上面几位说的什么规约好象不至于吧。因为在WINDOWS下面好多东西都是现成的,特别是数据采集的时候,你根本不需要管数据怎么收到,只需要写好怎么处理就可以了。楼主的数据结构很简单。首先定义一个宏。将高4位低4位的数据组合成正确的数据。然后定义一个联合,将浮点数据转换成字节数组。这样就可以方便处理数据结构了。至于如何确定一个包是否完整接收了,我想这也是很简单的问题,协议中既然有包头、长度、校验和的描述,那么就根据系统运行效率选择处理方式,如果效率要求不太高的,可以接收了定长的数据以后再处理。如果效率要求高一点的可以每个字节都处理。
当然楼主给的协议只是一小部分,完整协议还应该包括容错处理吧!!Top
20 楼lcxf(阿春)回复于 2006-12-05 11:30:08 得分 0
loseme915(郁闷) ( ) :
你好!!你应该做过类似的课题吧?
是的,我的问题的确不是与什么规约有关.我只需收到再处理就是了.
你说的定义宏和联合能否用具体的代码说明以下????
我也定义过我的数据处理函数:
ProcData(rxdata){
union{
byte b[4];
float f;
}test;
test.b[0]=rxdata[6];
test.b[1]=rxdata[7];
test.b[2]=rxdata[8];
test.b[3]=rxdata[9];
Lon(经度)=test.f;
.....
得不到想要的结果啊..
着急啊.......谁能HELP!
Top
21 楼lcxf(阿春)回复于 2006-12-06 12:01:05 得分 0
再顶一下,不要沉下去.Top
22 楼vincents()回复于 2006-12-06 12:19:27 得分 0
markTop
23 楼lcxf(阿春)回复于 2006-12-08 11:20:13 得分 0
请各位帮助啊!!!!Top
24 楼youyingbo()回复于 2006-12-12 10:36:51 得分 0
upTop
25 楼lcxf(阿春)回复于 2006-12-29 11:57:20 得分 0
??????????????/Top
26 楼youyingbo()回复于 2006-12-29 13:05:24 得分 0
还没结呢?
第一个问题应该解决了把。
第二个真是没办法了,你确实应该问问你那边的人啊。你接收的串口发出来的东西,那些东西的内容都是什么意思,只有编程序给你发数据的人最清楚啊,不然你叫别人怎么猜啊Top




