从单片机基础到程序框架(全集 2019pdf版).pdf - 第753页
第一百二十八节: 接收“固定协议”的串口程序框架。 【128.1 固定协议。 】 实际项目中 , 串口一个回合的收 发数据量远 远不止 1 个字 节, 而是往往携 带了某种“固定协议” 的一串 数据(专业 术语称“一帧 数据” ) 。一串数据 的“ 固定协议”因为 起到类似“ 校验”和“密 码确认 ”的功能, 因此在安全 可靠性方面 大大增强。 但是上一节也提到 , 单片机利用最底层硬件 的串口接口 , 一次 收发的 最小 单位是 “ …

上图 127.6.1 232 串口电路
单片机串口对外的引脚是与 IO 口的“P3.1、P3.0”共用的。P3.1 是串口的 TX 引脚,即对外发送数据的
引脚。P3.0 是串口的 RX 引脚,即接收外部数据的引脚。一旦项目中用了串口,那么这两个引脚就必须“专
脚专用”,只给串口单独使用,不再做 IO 口。平时通信的时候,跟其它单片机或者系统进行串口通信,除了
接 TX 和 RX 这两根数据线之外,必须一定把双方的负极 GND 也“共地”接上,否则双方建立不了同样的电压
参考点,通信毕然失败。因此,串口通信最低标配是 3 根线:RX,TX,GND。
如果两个甲乙单片机都布在一块板子上,距离不超过半米,他们两个要进行串口通信,怎么接线?把他
们的 GND 连起来,然后 RX 与 TX“交叉”对接,甲的 RX 接到乙的 TX,甲的 TX 接到乙的 RX。这种在短距离通
信的时候,不用增加任何外部辅助压差信号放大芯片,这种方式叫做“串口的 TTL”接线方式。
如果两个系统串口通信的距离比较远,比如在不同的板子上,1 米以上 10 米以下的距离,这时就不能采
用原始的“串口的 TTL”接线方式,因为线缆越长电阻越大,本身就要消耗一些压降,而 3.3V 的压降很容易
就会被消耗完,通信的可靠度和抗扰能力就会降低。为了解决这个问题,可以引用 232 标准的接线方式,外
部需接一个压差放大的芯片,把从原来 3.3V 的压差放大到一两倍左右,通信的距离就大大提高。具体 232
的细节,大家可以网上搜搜“RS232”。注意,采用 232 协议通信,也要注意“共地”和数据线“交叉”的两
个问题,232 通信的最低标配也是 3 根线:R,T,GND。上图 SP232E 就是一个压差信号放大的通信专用芯片。
第一百二十八节: 接收“固定协议”的串口程序框架。
【128.1 固定协议。】
实际项目中,串口一个回合的收发数据量远远不止 1 个字节,而是往往携带了某种“固定协议”的一串
数据(专业术语称“一帧数据”)。一串数据的“固定协议”因为起到类似“校验”和“密码确认”的功能,
因此在安全可靠性方面大大增强。但是上一节也提到,单片机利用最底层硬件的串口接口,一次收发的最小
单位是“1 个字节”,那么,怎么样在此基础上搭建一个能快速收发并且能快速解析数据的程序框架就显得尤
为重要。本节我跟大家分享我常用的串口程序框架,此框架主要包含“数据头,数据类型,数据长度,其它
数据”这四部分。比如,为了通过串口去控制单片机的蜂鸣器发出不同长度的声音,我专门制定了一串十六
进制的数据:EB 01 00 00 00 08 03 E8 ,下面以此串数据来跟大家详细分析。
数据头(EB):占 1 个字节,作为“起始字节”,起到“接头暗号”的作用,平时用来过滤无关的数据。
只有“接头暗号”吻合,单片机才会进入到接收其它有效数据的步骤,否则一直被“数据头”挡在门外视为
无效数据。注意,数据头不能用十六进制的 00 或者 FF,因为 00 和 FF 的密码等级太弱,很多单片机一上电
的瞬间因为硬件的某种不确定的原因,会直接误发送 00 或者 FF 这类干扰数据。
数据类型(01):占用 1 个字节。数据类型是用来定义这串数据的用途,比如,01 代表用来控制蜂鸣器
的,02 代表控制 LED 的,03 代表机器启动,等等功能,都可以用这个字节的数据进行分类定义。本例子用
01 代表控制蜂鸣器发出不同时间长度的声音。
数据长度(00 00 00 08):占 4 个字节。用来告诉通信的对方,这串数据一共有多少个字节。本例子中,
数据长度占用了 4 个字节,就意味着最大数据长度是一个 unsigned long 类型的数据范围,从 0 到 4294967295。
比如,本例子中一串数据的长度是 8 个字节(EB 01 00 00 00 08 03 E8 ),因此这“数据长度”四个字节
分别是 00 00 00 08,十六进制的 08 代表十进制的 8 字节。注意,51 单片机的内存是属于大端模式,因此
十进制的 8 在四字节 unsigned long 的内存排列顺序是 00 00 00 08,也就是低位放在数组的高下标。如果
是 stm32 的单片机,stm32 单片机的内存是属于小端模式,十进制的 8 在四字节 unsigned long 的内存排列
顺序是 08 00 00 00,低位放在数组的低下标。为什么强调这个?因为主要方便我们用指针的方法实现数据
的拆分和整合,这个知识点的内容我在前面第 62 节详细讲解过。
其它数据(03 E8):此数据根据不同的“数据类型”可以用来做不同的用途,根据具体的项目而定。本
例子十六进制的 03 E8,代表一个 unsigned int 的十进制数据 1000。此数据的大小用来控制蜂鸣器发声的
长度,1000 代表长叫 1000ms。如果想让蜂鸣器短叫 100ms,只需把这两个字节改为:00 64。
【128.2 程序框架的四个要点分析。】
第一点:先接收后处理,开辟一块专用的内存数组。要处理一串数据,必须先征用一块内存数组专门用
来缓存接收到的数据,等接收完此串数据再处理。
第二点:接头暗号。本节例子的数据头 EB 就是接头暗号。一旦接头暗号吻合,才会进入到下一步接收
其它有效数据的步骤上。
第三点:如何识别接收一串数据的完毕。本节例子中,是靠“固定协议”提供的“数据长度”来判别是
否已经接收完一串数据。中断函数接收完一串数据后,应该用一个全局变量来给外部 main 函数一个通知,
让 main 函数里面的相关函数来处理此串数据。
第四点:接收数据中相邻字节之间通信超时的异常处理。如果接头暗号吻合之后,马上切换到“接受其
它有效数据”的步骤,但是,如果在此步骤的通信过程中一旦发现通信不连贯,就应该及时退出当下“接受
其它有效数据”的步骤,继续返回到刚开始的“接头暗号”的步骤,为下一次接收新的一串数据做准备。那
么,如何识别通信不连贯?靠判断接收数据中相邻字节之间的时间是否超时来决定,详细内容请看下面的程
序例程。

【128.3 程序例程。】
上图 128.3.1 有源蜂鸣器电路
上图 128.3.2 232 串口电路
程序功能如下:
(1)在上位机的串口助手里,发送一串数据,控制蜂鸣器发出不同长度的声音。
(2)波特率 9600,校验位 NONE(无),数据位 8,停止位 1。
(3)十六进制的数据格式如下:
EB 01 00 00 00 08 XX XX
其中 EB 是数据头,01 是代表数据类型,00 00 00 08 代表数据长度是 8 个(十进制)。XX XX 代表
一个 unsigned int 的数据,此数据的大小决定了蜂鸣器发出声音的长度。比如:
让蜂鸣器鸣叫 1000ms 的时间,发送十六进制的: EB 01 00 00 00 08 03 E8
让蜂鸣器鸣叫 100ms 的时间,发送十六进制的: EB 01 00 00 00 08 00 64
#include "REG52.H"
#define RECE_TIME_OUT 2000 //通信过程中字节之间的超时时间 2000ms
#define REC_BUFFER_SIZE 20 //接收数据的缓存数组的长度