单片机通信过程中的控制命令扩展方案
在制作东西的过程中用通信来进行数据和控制命令传输是经常的事,即使需要传输的内容全部是数据,我们也希望能有一个起始信号标志一帧数据的开始。 但在实际实际使用时经常会遇到控制命令不好选择的情况,就拿传输数据的起始信号来说,假设这里用的是串口传输,传输的单位是字节。 如果传输的数据有范围限制,我们就可以从数据不可能涉及到的范围中选取控制信号。比如之前做过的一个机器人(http://v.youku.com/v_show/id_XMzA2NjUyNDU2.html 两年前做的,大家瞧瞧哈)需要通过串口传输各个舵机的脉宽值,脉宽值相当于数据,它的范围只能能在50~250之间,在0~49 : 251~255都可以作为控制信号。实际使用正是用0xff标志机器人全身17个脉宽信号的到来。 这是简单的情况,如果说数据可能涉及到的范围覆盖了0~255,那就不能再从中选取控制信号了,因为接收方无法判断到底是数据信号还是控制信号。 今天的内容就是讨论如何在数据信号覆盖到的范围内扩展出控制信号。
1、能想到的最简单的方法就是加线。添加一根新的线来标志传输的开始,或者用加出来的线的高低电平区分控制信号和数据信号。 2、还有一种常用的方法就是接收端通过计时判断一帧数据的开始。因为发送端在连续发送数据时,数据之间的时间间隔是非常小的,接收端每次接收到数据都开始计时,如果下一个数据来临比较快则认为是紧接着上个数据连续传输的,而如果隔一段时间没有数据传输则从头开始计数。 3、如果对数据精度要求不高,可以腾出一个数据值作为控制信号。比如把0xff腾出来作为控制信号,而数据中如果出现0xff一律换成0xfe。
以上几种方法都用过,各有优缺点,最近又想出一种新方法——添加上层协议,规定一个通用的传输协议,只要在原来传输的基础上套用一下这个协议就可以从数据范围内扩展出控制信号。 这种方法貌似比上面3种都好,不需要像1那样扩展硬件,也不需要像2那样不能连续发送太快,也不会降低数据精度。 下面就介绍一下我用的方法: 假设数据信号可能是0~255任意一个数值,现在我要扩展出若干个控制信号。 首先,我选取一段控制信号范围,定义一个常量MinCtrl,选取MinCtrl ~ 255都可作为控制信号,0 ~ MinCtrl-1 作为数据信号。 这样扩展出了255-MinCtrl+1个控制信号(实际可以使用的控制信号有255-MinCtrl个,后面再介绍原因),但也带来一个问题:如果数据信号在MinCtrl ~ 255 之间改怎么传输? 我采用的方法是这样的:发送端检测到数据dat在MinCtrl ~ 255之间就会先发送一个MinCtrl,再发送一个dat – MinCtrl。接收端接收到MinCtrl之后认为下一个字节加上MinCtrl才是一个完整的数据。 发送端的拓扑图如下:
接收端的拓扑图如下:
说明: 1、MinCtrl作为协议自己使用的控制信号,用户实际可使用的控制信号是MinCtrl+1 ~ 255。 2、为了保证“数据和信号值不可能相同”的原则,MinCtrl最小值是128(0x80),也就是说最多可扩展出127个可使用的控制信号。 2、如果数据在0 ~ MinCtrl-1 范围内,只需发送一个字节就可传输数据;如果数据在MinCtrl ~ 255 范围内,需要发送两个字节传输数据。 所以,MinCtrl取得越小,需要2个字节传输数据的概率就越大,所以根据实际需要选取MinCtrl的值,不要选得过小,这样不利于快速传输。
下面是协议驱动程序:
/* 单字节传输数据时,如果数据可能会占用0~255所有的值,则起始信号判断是个普遍问题。 本文件是发送端和接收端的数据处理程序,在原有单字节传输的基础上套用本协议,可以从单字节传输中扩展出最多127个控制信号。 */ /*最小控制信号MinCtrl 采用的方法是这样的:从 0~MinCtrl-1 仍然作为数据,这个范围内的数据一个字节便可传输; 从MinCtrl到0xff腾出来作为控制信号,其中MinCtrl是特殊的控制信号,它标志着 下一个字节的数据要加上MinCtrl作为真正接收到的数据。在发送端,当要发送的数据 dat>=MinCtrl时,将分为两个字节发送,第一个字节发送MinCtrl,第二个字节发送 dat-MinCtrl。 在 ( MinCtrl, 0xff ] 范围内的数可以单独作为控制信号。 注: MiniCtrl取得越小,可以容纳的控制信号越多,但是数据需要发送两个字节的概率 就越大。所以根据具体传输的需求,让MinCtrl尽可能大,这样需要两个字节传输数据的 概率就小,会提高传输速度; MinCtrl最小值是0x80,如果MinCtrl小于0x80,则下一字节的数据会大于MinCtrl, 为了不让数据与控制信号出现同样的值,规定 MinCtrl>=0x80; 发送端和接收端的 MinCtrl 必须定义相同的值。 *///发送端: #define MinCtrl 0xfe //( MinCtrl, 0xff ] 可用作控制信号,发送端和接收端这个定义应当相同 /********************扩展处控制信号的数据发送函数***************************/void ExpendedSend(unsigned char dat) { if(dat<MinCtrl) //可以直接发送 { //下面是原来发送一个字节的函数 SCI_sendB(dat); } else //说明在控制信号范围内,要分两次发送 { //下面是原来发送一个字节的函数 SCI_sendB(MinCtrl); delayForSend(); //这是连续发送两字节的延时,根据接收端的响应速度调整 SCI_sendB(dat-MinCtrl); } } /***************************************************************************/ /*************************扩展出控制信号的数据接收函数********************* 把接收到的数据传进此函数,在此函数中判断是控制信号还是数据,以及根据约定好的协议 得出数据的值。 /***************************************************************************/void ExpendedRec(unsigned char get) { static unsigned char FlagMinCtrl=0; //标志本次接收到的数据是否需要加上MinCtrl,0-不要,1-要 unsigned char dat; //这是经过协议计算后的真正接收到的数据 if(get==MinCtrl) //说明下一个字节加上MinCtrl就是一个数据 { FlagMinCtrl=1; goto RecEnd; } else if(get>MinCtrl) //说明在控制信号范围内 goto DealCommand; else //get在数据范围内,肯定标志着接收到了一个数据,但还要判断是否需要加上MinCtrl { if(FlagMinCtrl==1) //要 { FlagMinCtrl=0; dat=get+MinCtrl; goto DealData; } else if(FlagMinCtrl==0) //不要,本次数据就是一个小于MinCtrl的数据 { dat=get; goto DealData; } } DealCommand: //当前的get就是命令,下面是对命令的处理 return; DealData: //当前dat的值就是接收到的数据,下面对其处理 return; RecEnd: //结束 return; } /********************************************************************************/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80 |
#define MinCtrl 0xfe //( MinCtrl, 0xff ] 可用作控制信号,发送端和接收端这个定义应当相同
void ExpendedSend(unsigned char dat)
{
if (dat<MinCtrl)
{
SCI_sendB(dat);
}
else
{
SCI_sendB(MinCtrl);
delayForSend();
SCI_sendB(dat-MinCtrl);
}
}
void ExpendedRec(unsigned char get)
{
static unsigned char FlagMinCtrl=0;
unsigned char dat;
if (get==MinCtrl)
{
FlagMinCtrl=1;
goto RecEnd;
}
else if (get>MinCtrl)
goto DealCommand;
else
{
if (FlagMinCtrl==1)
{
FlagMinCtrl=0;
dat=get+MinCtrl;
goto DealData;
}
else if (FlagMinCtrl==0)
{
dat=get;
goto DealData;
}
}
DealCommand:
return ;
DealData:
return ;
RecEnd:
return ;
}
|
使用方法: 发送端只要将需要发送的数据传给这里的发送函数,这里的发送函数会根据协议调用原来的发送函数发送数据。 接收端只要将接收到的原始数据传递给这里的接收函数,在接收函数的指定位置加入相应的对数据和命令的响应代码就行了。
扩展控制命令的方法有很多,这里介绍的只是我用的方法,希望能够抛砖引玉
发送端拓扑图画错了,dat<MinCtrl? 改为 dat>=MinCtrl? 尽量都在用Modbus的协议,协议开放,资料好找,而且串口,以太网都有涉及,还是国家推荐的一个工业标准通讯协议。 做数据采集的时候各厂家的通讯协议都不一样,每个设备都要单独写接口,有时候给的资料不全,或者是错的,在现场还要去改掉,痛苦得很。 有一个标准的协议至少你也有理由可以要求别的厂家来按你的要求去做,测试时候也方便,不用专门针对通讯协议写模拟器。
通讯我觉得发送好做接收难,一般接收,遇到的通讯定长,固定头这两种比较多。
定长,每次通讯数据包长度固定,数着收到一定的字节数之后解析他内容就可以了,粘包的时候比较麻烦些,一般定时发送比较多,超过多少时间没有收到就认为一个包结束了。
固定头,数据包长度有些固定,有些不固定,不过有一个可以识别的头,也有些有尾。
定长的数据包相比更好处理些,固定头的数据有时候为了防止头重码,有些是用多字节来当数据头的。
我电脑上的处理一般都采取同一办法,超时检查,超过一定的时间没有数据进来,我就认为通讯结束了,不论是定长的数据,还是有头尾的数据,接收不完整的时候你程序只能在那里等,这样处理时间上浪费了一些,但是通用性好一些,而且一般采集上也没时间要求太高的场合。
关于通讯校验,有些通讯是加校验,防止传送过程中出错,有些通讯就不加校验了,全靠拼速度,速度够快下一包的数据就把前一次的错误盖过去了,不同地方可以采集不同办法处理。
|