漫谈Arduino内置的通讯协议

1、什么是通讯协议

如果按照百度百科里面的定义,那么通讯协议的解释如下:

通信协议又称通信规程,是指通信双方对数据传送控制的一种约定。约定中包括对数据格式, 同步方式,传送速度,传送步骤,检纠错方式以及控制字符定义等问题做出统一规定,通信双方 必须共同遵守,它也叫做链路控制规程。 好吧,如果抄袭到这里,我肯定不会收到读者的鲜花,而是板砖和臭鸡蛋。文抄公谁不会做?
那么,对于业余电子爱好者而言,如何来快速而又简单地理解通讯协议?
其实,我们可以简单地这么来理解,对于人类世界来说,在中国范围内,那么我们可以将普通话看成是一个通讯协议。
也就是说,当有一个人懂得汉语的人要对另外一个懂得汉语的人表达自己意见的时候,他可 以使用“普通话”这个通讯协议和另外一个懂得普通话的人进行沟通。
同样的,我们也可以将英语看成人类世界的另外一个版本的通讯协议。
而在电子世界中,我们通常所谓的通讯协议都是数字通讯协议,在数字通讯协议中,小到 各种电子零件,大到电脑,它们之间互相沟通其实都是通过 0 或者 1 两个电位水平来进行通讯的(当 然,还有别的表达方式,如差分电位,这里不细表,作为爱好者,我们将且这么认为罢)。

大家知道,当我们采用 0,1 来进行信息表达的时候,如果只表达一次,即每次只说 1 或者 0 。那么这次表达只能够蕴含两个意思,或者你说了 0,这就表示“没有”,或者你说了 1,这就表示“有”。
但是,我们总该知道,这个世界哪里这么简单?比如你女友问你现在温度是多少度,你却 回答说“没有”或者“有”,我相信,你的女友少不得给你来一记九阴白骨抓。
幸好,我们并没有走到绝路,其实,如果你有一个比较耐得住性子且愿意陪你搞怪的女友。
那么我们可以这样来解决“问温度”的问题:
我们这样设想,如果你事先和女朋友约定如下:

如果她问你温度,如果并且她会有足够的耐心会反复这样问:
现在的温度是 0°?
如果对了,你回答“是”,如果不对,你回答“否”
如果回答了否,她就将上次问的温度增加一度重新问你。(如第二次就该问是否是 1°)
直到你回答了“是”。
也就是说,当你回答了“是”的时候,你的女友也就知道了现在温度。

好吧,恭喜你,尽管这个例子非常地无聊。但是,你和你的女友已经共同创造了一种全新的“通 讯协议”。
当然,我们不能想得这么简单,这个世界非常复杂,所以,我们需要进行复杂的表达。
比如说,你女友正在问你温度问题的时候,你忽然发现她老娘在背后看着你们俩发傻,我 相信,明智的你肯定不会继续这样傻干下去了。这个时候,你肯定得想办法打个哈哈蒙混过关, 诸如“阿姨,您怎么亲自来了?”什么的。
人类世界的沟通,可以用语言来进行复杂的表达,而语言由于其音节音调的多样性。所以 可以进行复杂的表达。
普通话中的“你”“我”“他”三个字,有三种发音,听者肯定可以区分三者的含义。
但是,在电子的数字通讯世界里,只有 0 和1这两个基本元素。就如前面提到的,它只能在一个时刻里面只表达一次。怎么办?
解决的方法是,我们用一组 0 和 1 的组合来进行复杂意义的表达。
如,我们可以用 00001 表示现在温度是 1°,00010 表示现在是 2°,00011 表示现在是 3°。 当然,这种表达的方法是二进制的(关于二进制,八进制,十进制等数学进制的概念和互相转换, 请参考网上的文档)。
所以,简而言之,在电子的世界里面,所谓的通讯协议,其实就是一个事先规定的规则, 我发送什么样的 0 和 1 的组合代表什么意思。
你如果事先了解了这个规则,这就是所谓的你“兼容”这个通讯协议。如果不了解,那 就是所谓的“不兼容”。

2、一个通讯协议涉及到的关键要素

一个通讯协议包含哪些要素呢?在此罗列如下:

A、电压规范。

处于一个通讯网络下的各个电子零件在进行通讯的时候,首先必须要采用共同的电压 水平。因为,数字通讯的基本规则就是 LOW(通常是 0V,当然还有别的电压水平,如果是 0V,这个电压并非指确定的 0V,而是大约在 0V 左右)表示 0,表示“没有”。HIGH(事先 约定的高电平,如 3.3V,5V,12V 等。如果是 3.3V,那么这个值并非是确定的 3.3V 等,而 是电压高到大约 3.3V 左右)表示 1,表示“有”。

试想一下,如果两个电子零件相互之间连基本的工作电压都不一致,你还妄图让它们进 行通讯,那么除了冒烟或者是通讯失败,你几乎得到什么别的结果。

举个例子,如你手头的一个零件 A,输出 0V 表示开关闭合,输出 5V 表示开关打开。现 在另外一个零件 B,它认为 -12V 表示开关闭合,0V 代表开关打开。姑且不去讨论会不会烧掉 电路的问题。那么不管 A 发送什么电压给 B,B 会永远都认为 A 是处于开关打开的状态,因为 不管是 0V 还是 5V 都已经高于了 0V 这个限度。

继续举一个例子,如,你和你的女友约定,如果你拿手指头点点她的额头表示你现在很无奈(你的手指很温暖,女友很幸福)。如果你拿着一把烧红的烙铁打算点点她的额头,这还 会让她认为你打算让她认为你很无奈吗?
不过,需要注意的一点是:通常情况下,我们在网上可以下载到的各种 IEEE 的通讯协议 标准规范里面都不会对电压进行直接的规定。
其实,这也很好理解,我们只需要保证处于通讯中的双方采用同样的电压就行了。
这就好像,如果你和你的女友是超人和女超人,那么你拿个烙铁点她的额头,她还是会理 解为你是在表达你的无奈。
实际上的例子则是:在实际应用中,如 canbus 总线,有的总线的通讯电压是 3.3V,有的 在是 5V,甚至还有使用 12V。
而之所以在此提出这一点。是因为有很多的新人爱好者在使用数字传感器的时候,往往不 会去考察它的通讯电压。而这往往会导致通讯失败,甚至烧毁电路元器件。

B、帧长度

所谓的帧长度就是:到底一个信息用了多少个 0 和 1 来组成。
这个好理解,因为是采用一串的 0 和 1 的组合来代表意义,那么我们设想,信息的发送方 如果没有预定多少个用 0-1 来表示一个完整的组合。那么假设我们按照如下的方式发送信息呢:
一次性发送:0101001011100101001010100
如果拆分成 4 个一组呢?: 0101-0010-1100-1010-0101-0100
显然,前者你根本就不知道是什么意思。你哪里知道是多少个数字代表一个信息?
而后者,你虽然不知道意思,但是好歹,知道发送了 6 个信息过来。
在帧长度的实际应用中,有些通讯协议采用了停止位的方法,而所谓的停止位,类似于上 面的 0101-0010 之间的“-”,通常的做法是使用一个较长的低电平或者高电平。而有些的通 讯协议则是,事先约定了多少个 0-1 组合就是表示一个信息。也就是发送方一旦开始发送,接 收方直接照着固定的个数,自行将一整个的 0-1 序列拆分。 无奈(你的手指很温暖,女友很幸福)。如果你拿着一把烧红的烙铁打算点点她的额头,这还 会让她认为你打算让她认为你很无奈吗?

我们常见的序列长度有 8 个 0-1,16 个,32 个,64 个。看一下,正好是 2 的倍数。当然,前言 8 个的最常见,8 个 0-1 序列就可以排出 256 个可能。而我们必须要接触到的 ASCII 编码,其 基本的 0-1 序列长度就是 8 位。

C、通讯速率

关于这一点,我们可以使用一个简单的实验来得到体现。我们在 Arduino 中输入如下的 代码:

void setup(){Serial.begin(9600);}
void loop(){Serial.println(“Hello!”);delay(1000);}

如果在电脑中打开串口监视器,除非我们选择的波特率是 9600,否则,我们在串口监视 器中将只能看到一堆的乱码。

我们打一个比方:
好吧,你和你的女友郎情妾意,她在给你喂饭。如果你当时感到幸福,于是闭上了眼感觉 自己徜徉在幸福的海洋中,而女友也感到幸福,于是也闭眼享受和你一样的幸福。如果她这个 时候还给你继续喂饭呢?你知道她什么时候会喂给你,而你恰好张开嘴?于是饭勺子很可能就 直接戳你嘴皮上了。

当然,如果你们两个都具有大哲学家的冷静,事先约定了:

“亲,你每 5 秒给我喂一次哦”

那么上面煞风景的事情就不会发生。 这是因为,你们约定了喂饭的频率。

其实,处于通讯两端的两个电子零件也相当于这么一对闭眼享受幸福还要秀恩爱喂饭的男 女,一个在发送信息(喂饭)一个在接收信息(张嘴吃饭)。如果通讯双方不事先约定好频率, 那么就会出现信息丢失的现象。

D、校验

如果说,两个人互相之间是在扯谈,比如像我现在正在干的事情。那么说过了也就罢了, 没啥大问题。但是,如果是在非常重要和严肃的场合呢?万一听的人听错了,那肯定会出大问 题。在军事指挥中,有这么一种方法来防止出问题:命令复述。也就是指挥官下重要命令的时候, 听从命令者必须复述指挥官的命令以做确认。
同样的,在电子世界的通讯中,因为通信线路的干扰,信息发送方和接收方可能出偶尔的 问题,那么也会面临同样的问题—信息发送出现了失误(术语叫误码)。这个时候,我们就必 须想办法来解决。
当然,如果接收方在接收到信息之后原封不动的反馈给发送方,发送方比对,如果对了就 回复确认;如果错了就回复错误,然后重新发送,这种方法可以确保绝对的正确。但是,这种 方法显然是相当无效率的,除非是非常重要的,一点错都不能出的通讯场合,否则这种检验方 法很少会被采用。

幸好专家们想到了更有效率的方法,即所谓的校验。通常的校验方法有所谓的奇偶校验, 和值校验等等。
如奇偶校验,则是在发送信息的同时在末尾再带上这次发送信息的 0-1 中的 0 的个数或者 1 的个数的模值。

如前面的数据列

>0101-0010-1100-1010-0101-0100

如果为了确保信息发送的完全。我们可以采用在每个4位数的后面再加上一个奇偶校验位, 即一次性发送 5 个。

如果我们采用耦校验位,那么我们的上面的序列就变成了如下的串列:

>0101(0)-0010(1)-1100(0)-1010(0)-0101(0)-0100(1)

括号里面的 0-1 就是所谓的校验字(注:事实上,括号是不存在的)

采用数学的方法。如第一串 0101(0) 我们可以根据校验字来判断,前面的 0101 是否正确。

如果发生了错误,我们原本打算发送 0101,但是因为通讯出了问题,结果只接收成了0001,那么我们可以发现,这个时候的校验位却是 0 的话,显然,0001 绝对是错误的值。那 么这个串列就可以简单抛弃,然后要求重发了。 当然,在实际过程中。如 TTL 串口通讯。如果你要求不是那么高,那大可以不搞什么校验。 而如果你对奇偶校验有跟多的兴趣,可以参考维基百科。

E、握手

这个概念非常好理解。如果你在图书馆聚精会神地读书,忽然坐你边上的人开始说话,我 相信,你的第一反应肯定不会认为那个家伙是在对你说话,并且你几乎记不住那个家伙刚刚在 说什么。
但是,如果那个家伙先用手肘碰碰你,然后说:“喂”。等你抬头看着他,他再和你叽里 呱啦的时候,你肯定可以听到他对你说了什么。

在电子通讯世界里面也是如此,两个或者多个需要进行互相通讯的电子零件可能正在执行 各自的工作。结果,接收方正在进行某个工作的处理,结果发送方忽然发送了一大段的信息过去。 很有可能的结果就是接收方没法接受到这个信息。
解决这个问题的方法有几种。
一种是采用所谓的握手信号,有的是一个专门的电路,如 Arduino 中的 SPI 通讯,发送方 会使用 CS 引脚发送一个高电平,告诉接受方,我要开始和你通讯了。
还有一种类似于老师在课堂上点名回答问题,参与通讯的各个电子元件事先都规定好了各 自的 ID,当发送方发送信息的时候在开头的时候发送这个 ID。那么具有这个 ID 的接受者就会 根据 ID 判断这个信息是否是发送给自己的。这类似于在信封上面写地址一样。
再有就是没有握手信号。纯粹双方都具有专门的发送和接收的模块。如 Arduino 上的 RS232 中的 RX 端口,它是相对独立于 Arduino 的 CPU 的,一旦 Arduino 上电,它就会随时 监听来自于电线上的信号。一旦收到就立即存储起来供 CPU 调用。这就好像一个老板给自己配 了一个电话秘书,随时替老板接收电话,然后把电话内容记录下来供老板随时浏览。

F、并行通讯和串行通讯

关于这个,其实很好理解,如果只有一根导线,那么我们一次只能发送 0 或者 1。如果我 们要发送 0101,那么我们就需要按照先后顺序连续发送四次。这就是所谓的串行通讯。但是, 如果我们在通讯的双方连接 4 条导线呢?那么一次性我们就可以把 0101 中的第一位的 0 通过 第一根导线,第一位的 1 通过第二根,第三位的 0 通过第三根,第四位的 1 通过第四根一次性 地发送出去。 串行通讯的速度相对较慢,但是这节省连接电路,也就是说省钱。并行通讯速度相对较快, 但是这相对来说非常不省钱。并且,随着连接电路的数量增多,它的可靠性也成级数往下降。所以, 选择哪种通讯方式,这在于速度 VS 可靠和经济的权衡。 通常情况下,远距离、低速的通讯通常都是串行的。而近距离、高速的通讯通常都是并行的。

G、单工,半双工,全双工。

这三个概念也比较简单。
单工就是发送方只能发送,接收方只能接受。这种方式在现实的世界中比如说广播、电视。 这些就是单工。
半双工则是,双方都能够发送和接收,但是如果是发送信息,那么它在同一个时刻下只能 发送或者接收。无法在同一个时刻下同时干两件事情。如 Arduino 上的 IIC 总线(TWI)就是 半双工通讯。比较形象的就是步话机。

全双工则是,双方不仅能够发送和接收,而且发送和接收可以同时进行。在 Arduino 上 的 SPI 总线和 TTL RS232 都属于全双工。而现实中的例子如电话,互联网连接等。

3、了解这些对于应用 Arduino 的意义。

1、了解了关于电平的概念,那么我们在未来连接电路的时候就不会出现随便拿一个数字 传感器就往 Arduino 上接的低级错误。好歹我们得注意看一下,它的通讯电压是多少的。当然, 还有一些特例,如 CMOS 电平和 TTL 电平兼容,这些是后来的升级概念。我们这儿不做延伸说明。
2、了解了关于通讯速率的概念,那么我们就明白了,为什么需要设定 Serial.begin()。
3、了解了奇偶校验,那么我们在未来读取数字传感器的发来的数字的时候就会应用这 个位来判定读数值正确与否。
4、了解串行和并行通讯,其实,是为了让我们理解 shiftout() 函数。
5、了解握手的概念,这样我们才会理解为何 IIC 需要设定一个 ID 去发送或者接收,而 TTL RS232 和 SPI 则不需要。
6、了解了单工,半双工,全双工。好吧,这个概念貌似对于 Arduino 的初级应用的确没啥用。 只是纯粹为了补齐概念和凑字数。

很多的时候,很多的爱好者往往不清楚这些概念,所以总是会犯一些让业内人士嘲笑的 低级错误。但是,这些错误正是因为缺乏基础知识才会发生的错误。如何避免?难道要像那些 业内人士一样老老实实地抱着专业书开啃?
但是,我们真的只是爱好者,我们只需要搞清楚大概的理论框架就行了。所以,才 有了这个水煮通讯协议的扯谈篇。
希望能够给有需要的读者以帮助。

作者:唐乐

发表评论

电子邮件地址不会被公开。 必填项已用*标注


*