作者:sumjess
一、并行与串行基本通信方式:
随着单片机系统的广泛应用和计算机网络技术的普及,单片机的通信功能愈来愈显得重要。单片机通信是指单片机与计算机或单片机与单片机之间的信息交换,通常单片机与计算机之间的通信我们用的较多。
通信有并行和串行两种方式。在单片机系统以及现代单片机测控系统中,信息的交换多采用串行通信方式。
1.并行通信方式
并行通信通常是将数据字节的各位用多条数据线同时进行传送,每一位数据都需要一条传输线,如下图所示,8位数据总线的通信系统,一次传送8位数据(1个字节),将需要8条数据线。此外,还需 要一条信号线和若干控制信号线,这种方式仅适合于短距离的数据传输,如比较老式的打印机就是通过并口方式与计算机连接,现在都用传输速度非常快的USB
2.0接口通信了。由于并口通信已经用得较少,因此我们在这里也仅做简单介绍,大家只需了解即可。
并行通信控制简单、相对传输速度快,但由于传输线较多,长距离传送时成本高且收、 发方的各位同时接收存在困难。
2.串行通信方式
串行通信是将数据字节分成一位一位的形式在一条传输线上逐个地传送,此时只需要一条数据线,外加一条公共信号地线和若干控制信号线。因为一次只能传送一位,所以对于一个字节的数据,至少要分8位才能传送完毕,如下图所示。
串行通信的必要过程是:发送时,要把并行数据变成串行数据发送到线路上去,接收时,要把串行信号再变成并行数据,这样才能被计算机及其他设备处理。
串行通信传输线少,长距离传送时成本低,且可以利用电话网等现成的设备,但数据的传送控制比并行通信复杂。
串行通信又有两种方式:异步串行通信和同步串行通信。
3.异步串行通信方式
异步串行通信是指通信的发送与接收设备使用各自的时钟控制数据的发送和接收过程。为使双方收、发协调,要求发送和接收设备的时钟尽可能一致,如下图所示。
异步通信是以字符(构成的帧)为单位进行传输,字符与字符之间的间隙(时间间隔) 是任意的,但每个字符中的各位是以固定的时间传送的,即字符之间不一定有“位间隔"的 整数倍关系, 但 同 一字符内的各位之间的距离均为“ 位间隔"的整数倍。
异步通信一帧字符信息由 4 部分组成: 起始位、数据位、奇偶校验位和停止位,如下图所示。有的字符信息也有带空闲位形式, 即在字符之间有空闲字符。
异步通信的特点:不要求收发双方时钟的严格一致,实现容易,设备开销较小,但每个字符要附加2~3位,用于起止位、校验位和停止位,各帧之间还有间隔,因此传输效率不高。
在单片机与单片机之间,单片机与计算机之间通信时,通常采用异步串行通信方式。
4.同步串行通信方式
同步通信时要建立发送方时钟对接收方时钟的直接控制,使双方达到完全同步。此时,传输数据的位之间的距离均为"位间隔"的整数倍,同时传送的字符间不留间隙,即保持位同步关系,也保持字符同步关系。发送方对接收方的同步可以通过外同步和自同步两种方法实现,分别如下图左和下图右所示。
面向字符的同步格式如下图所示。
此时,传送的数据和控制信息都必须由规定的字符集(如ASCII码)中的字符所组成。图6.1.7中帧头为1个或2个同步字符SYN(ASCII码为16H)。SOH为序始字符(ASCII码为OlH),表示标题的开始,标题中包含源地址、目标地址和路由指示等信息。STX为文始字符(ASCII码为02H),表示传送的数据块开始。数据块是传送的正文内容,由多个字符组成,数据块后面是组终字符ETB(ASCII码为17H)或文终字符ETX(ASCII码为03H),然后是校验码,典型的面向字符的同步规程如IBM的二进制同步规程BSC。
面向位的同步格式如下图所示。
此时,将数据块看做数据流,并用序列01111110作为开始和结束标志。为了避免在数据流中出现序列01111110时引起的混乱,发送方总是在其发送的数据流中每出现5个连续的1就插入一个附加的0;接收方则每检测到5个连续的1并且其后有一个0时,就删除该0。
典型的面向位的同步协议如ISO的高级数据链路控制规程HDLC和IBM的同步数据链路控制规程SDLC。
面向位的同步通信的特点是以特定的位组合01111110作为帧的开始和结束标志,所传输的一帧数据可以是任意位。它传输的效率较高,但实现的硬件设备比异步通信复杂。
5.串行通信的制式
(1)单工。单工是指数据传输仅能沿一个方向,不能实现反向传输。
(2)半双工。半双工是指数据传输可以沿两个方向,但需要分时进行。
(3)全双工。全双工是指数据可以同时进行双向传输。三种制式分别如下图左、下图中和下图右所示。
6.串行通信的错误校验
(1)奇偶校验
在发送数据时,数据位尾随的1位为奇偶校验位(1或0)。奇校验时,数据中1的个数与校验位1的个数之和应为奇数;偶校验时,数据中1的个数与校验位1的个数之和应为偶数。接收字符时,对1的个数进行校验,若发现不一致,则说明传输数据过程中出现了差错。
(2)代码和校验
代码和校验是发送方将所发数据块求和(或各字节异或),产生一个字节的校验字符(校验和)附加到数据块末尾。接收方接收数据时同时对数据块(除校验字节外)求和(或各字节异或),将所得的结果与发送方的“校验和“进行比较,相符则无差错,否则即认为传送过程中出现了差错。
(3)循环冗余校验
这种校验是通过某种数学运算实现有效信息与校验位之间的循环校验,常用于对磁盘信息的传输、存储区的完整性校验等。这种校验方法纠错能力强,广泛应用于同步通信中。
二、RS-232 电平与TTL 电平的转换:
关于RS-232电平与TTL电平的特性在前面已经讲过,本节主要讲解使用较多的计算机RS-232电平与单片机TTL电平之间的转换方式。早期的MC1488, 75188等芯片可实现TTL电平到RS-232电平的转换;MC1489, 75189等芯片可实现RS-232电平到TTL电平的转换。但是现在用的较多的是MAX232, MAX202, HIN232等芯片,它们同时集成了RS-232电平和TTL电平之间的互转。为丰富大家的知识,下面首先讲解在没有M心(232这种现成电平转换芯片时,如何用二极管、三极管、电阻、电容等分立元件搭建一个简单的RS-232电平与TTL电平之间的转换电路。
1.分立元件实现RS-232电平与TTL电平转换电路(下图)
集成芯片内部都是由最基本电子元件组成,如电阻、电容、二极管、三极管等元件,为了方便用户使用,制造商把这些具有一定功能的分立元件封装到一个芯片内,这样就制成了我们使用的各种芯片。学会本电路后,我们也就基本搞清了MAX232芯片内部的大致结构。
MAX232是把TTL电平从ov和5V转换到3V15V或-3V-15 V之间。分析图下图,首先TTL电平TXD发送数据时,若发送低电平0,这时Q3导通,PCRXD由空闲时的低电平变高电平(如PC用中断接收的话会产生中断),满足条件。发送高电平1时,TXD为高电平,Q3截止,由于PCRXD内部高阻,而PCTXD平时是-3~-15V,通过D1和R7将其拉低PCRXD至-3-15V,此时计算机接收到的就是1。下面再反过来,PC发送信号,由单片机来接收信号。当PCTXD为低电平-3-(-15V)时,Q4截止,单片机端的RXD被R9拉到5V高电平;当PCTXD变高时,Q4导通,RXD被Q4拉到低电平,这样便实现的双向转换,这是一个很好的电路,值得大家学习。
2. MAX232芯片实现RS-232电平与TTL电平转换
MAX232芯片是MAXIM公司生产的、包含两路接收器和驱动器的IC芯片,它的内部有一个电源电压变换器,可以把输入的+5V电源电压变换成为RS-232输出电平所需的+10V电压。所以,采用此芯片接口的串行通信系统只需单一的+5V电源就可以了。对千没有+12V电源的场合,其适应性更强,加之其价格适中,硬件接口简单,所以被广泛采用。
MAX232芯片实物如图6.2.2和6.2.3所示,其引脚结构和外围连接分别如图6.2.4和图6.2.5
所示。
图6.2.5中上半部分电容C1,C2,C3,C4及V+,V-是电源变换电路部分。在实际应用中,器件对电源噪声很敏感,因此Vee必须要对地加去耦电容Cs,其值为0.lµF。按芯片手册中介绍,电容C1,C2,C3,C4应取1.0µF/16V的电解电容,经大量实验及实际应用,这4个电容都可以选用O.lµF的非极性瓷片电容代替1.0µF/16V的电解电容,在具体设计电路时,这4个电容要尽量靠近MAX232芯片,以提高抗干扰能力。
图6.2.5下半部分为发送和接收部分。实际应用中,T1IN,T2IN可直接连接TTL/CMOS电平的51单片机串行发送端TXD;R10UT,R20UT可直接连接TTL/CMOS电平的51单片机的串行接收端RXD;T10UT,T20UT可直接连接PC机的RS-232串口的接收端RXD;R1IN,R2IN可直接连接PC机的RS-232串口的发送端TXD。
现从MAX232芯片中两路发送、接收中任选一路作为接口。要注意其发送、接收的引脚要对应。如使T1IN连接单片机的发送端TXD,则PC机的RS-232接收端RXD一定要对应接
T10UT引脚。同时,R10UT连接单片机的RXD引脚,PC机的RS-232发送端TXD对应接R1 IN引脚。
TX-1C实验板串口部分原理图如图6.2.6所示,实验板上实物如图6.2.7所示。
其数据传输过程如下:MAX232的11脚T1IN接单片机TXD端P3.1,TTL电平从单片机的TXD端发出,经过M心(232转换为RS-232电平后从MAX232的14脚T1OUT发出,再连接到实验板上串口座的第3脚,再经过随板配送的交叉串口线后,连接至PC机的串口座的第
2脚RXD端,至此计算机接收到数据。PC机发送数据时从PC机串口座第3脚TXD端发出数据,再逆向流向单片机的RXD端P3.0接收数据。
这里需要注意的是,MAX232与串口座连接时,无论是数据输出端,还是数据输入端,连接串口座的第2引脚或第3引脚都可以,选用不同的连接方法时,单片机与计算机之间的串口线都要谨慎选择,是选择平行串口线还是交叉串口线、是选择母头对母头串口线还是母头对公头串口线这些都要非常注意,每种选择都有对应的电路,但无论哪种搭配方式,大家必须要明白,在单片机与计算机之间必须要有一条数据能互相传输的回路,只要把握好每个交接点就一定能通信成功。
三、波特率与定时器初值的关系:
1.波特率
单片机或计算机在串口通信时的速率用波特率表示,它定义为每秒传输二进制代码的位数,即1波特=1位/秒,单位是bps(位/秒)。如每秒钟传送240个字符,而每个字符格式包含10位(1个起始位、1个停止位、8个数据位),这时的波特率为10位X240个/秒=2400bps。
串行接口或终端直接传送串行信息位流的最大距离与传输速率及传输线的电气特性也有关。当传输线使用每0.3m(约1英尺)有50pF电容的非平衡屏蔽双绞线时,传输距离随传输速率的增加而减小。当比特率超过1000bps时,最大传输距离迅速下降,如9600bps时最大距离下降到只有76m(约250英尺)。因此我们在做串口通信实验选择较高速率传输数据时,尽量缩短数据线的长度,为了能使数据安全传输,即使是在较低传输速率下也不要使用太长的数据线。
2.波特率的计算
在串行通信中,收、发双方对发送或接收数据的速率要有约定。通过编程可对单片机串行口设定为4种工作方式,其中方式0和方式2的波特率是固定的,而方式1和方式3的波特率是可变的,由定时器T1的溢出率来决定。
串行口的4种工作方式对应三种波特率。由于输入的移位时钟的来源不同,所以各种方式的波特率计算公式也不相同,以下是4种方式波特率的计算公式。
方式0的波特率=fos/12。
方式1的波特率=(2^SMOD/32) x(T1溢出率)。
方式2的波特率=(2^SMOD/64)Xfoc。
方式3的波特率=(2SMOD/32)x(Tl溢出率)。
式中,fosc为系统晶振频率,通常为12MHz或11.0592MHz; SMOD是PCON寄存器的最高位(关于PCON寄存器请看下一个知识点);T1溢出率即定时器Tl溢出的频率。
知识点:电源管理寄存器PCON
电源管理寄存器在特殊功能寄存器中,字节地址为87H,不能位寻址,PCON用来管理单片机的电源部分,包括上电复位检测、掉电模式、空闲模式等。单片机复位时PCON全部被清0。其各位的定义如下表所示。
SMOD—该位与串口通信波特率有关。
SMOD=0: 串口方式1, 2,3时,波特率正常。
SMOD=1: 串口方式1, 2,3时,波特率加倍。
(SMOD0),(LVDF),(P0F)—这三位是STC单片机特有的功能,请查看相关手册,其他单片机保留未使用。
GF1,GF0—两个通用工作标志位,用户可以自由使用。
PD—掉电模式设定位。
PD=0:单片机处于正常工作状态。
PD=1:单片机进入掉电(PowerDown)模式,可由外部中断低电平触发或由下降沿触发或者硬件复位模式唤醒,进入掉电模式后,外部晶振停振,CPU、定时器、串行口全部停止工作,只有外部中断继续工作。
IDL—空闲模式设定位。
IDL=0:单片机处于正常工作状态。
IDL=1:单片机进入空闲(Idle)模式,除CPU不工作外,其余仍继续工作,在空闲模式下可由任一个中断或硬件复位唤醒。
T1溢出率就是T1定时器溢出的频率,只要算出T1定时器每溢出一次所需的时间T,那么T的倒数1/T就是它的溢出率。这个问题还是比较容易理解的,在第3章讲解过定时器T0和T1方式1的操作方法,若我们设定定时器T1每50ms溢出一次,那么其溢出率就为20Hz,再将20代入串口波特率计算公式中即可求出相应的波特率,当然也可根据波特率反推出定时器的溢出率,进而计算出定时器的初值。通常单片机在通信时,波特率都较高,因此T1溢出率也必定很高,如果我们使用定时器1的工作方式1在中断中装初值的方法来求T1溢出率的话,在进入中断、装值、出中断这个过程中很容易产生时间上微小的误差,当多次操作时微小的误差不断累积,终会产生错误。有效的解决办法是,使用T1定时器的工作方式2,8位初值自动重装的8位定时器/计数器,定时器方式2逻辑结构图如图6.3.1所示。
在学习定时器方式2时可参考3.5节讲解的定时器方式1,在方式1中,当定时器计满溢出时,自动进入中断服务程序,然后我们需要手动再次给定时器装初值,而在方式2中,当定时器计满溢出后,单片机会自动为其装初值,并且无须进入中断服务程序进行任何处理,这样定时器溢出的速率就会绝对稳定。方式2的工作过程是:先设定M0M1选择定时器方式2,在TLX和THX中装入计算好的初值,启动定时器,然后TLX寄存器便在时钟的作用下开始加1计数,当TLX计满溢出后,CPU会自动将THX中的数装入TLX中,继续计数。因此我们在启动定时器之前必须先将TLX和THX中装好合适的数值,以让定时器输出产生的溢出率,这里TLX和THX中装的数值必须是一样的,因为每次计数溢出后TLX中装入的新值是从THX中取出的。
下面我们举一个例子来讲解根据已知波特率,如何计算定时器1 方式2下计数寄存器中的初值。
【例6.3.1】已知串口通信在串口方式1下,波特率为9600bps, 系统晶振频率为11.0592MHz,求TL1和TH1中装入的数值是多少?
解:设所求的数为X,则定时器每计256-X个数溢出一次,每计一个数的时间为一个机器周期,一个机器周期等于12个时钟周期,所以计一个数的时间为12/11.0592MHz(s), 那么定时器溢出一次的时间为[256-X]x12/11.0592MHz(s),T1的溢出率就是它的倒数,方式1的波特率=[(2SMOD)/32]x(T1溢出率),这里我们取SMOD=0,则2SMOD=1,将已知的数代入公式后得9600=(1/32)x11059200/[256-X]x12,求得X=253,转换成十六进制为0xFD。上面若将SMOD置1的话,那么X的值就变成250了。可见,在不变化X值的状态下,SMOD由0变1后,波特率便增加一倍。
大家一定要搞明白上面这段由波特率计算定时器初值的方法,通常波特率都是固定的一些数据,如1200,2400,4800,9600等,所以都是根据所要使用的波特率来求定时器初值,而没有说根据定时器初值来求波特率的,以后大家若要使用不同的波特率来做单片机和单片机之间或单片机与计算机之间的通信实验时,可参考上面的计算方法来求定时器初值。
大家可能会有疑惑,为什么单片机系统的晶振要选11.0592MHz 呢?通过上面的计算可能有些人已经明白了,我们用一个小知识点来为大家解答这个疑惑。
知识点:为什么51系列单片机常用11.0592MHz的晶振设计?
常用波特率通常按规范取为1200, 2400,4800,9600,…,若采用晶振12MHz或6MHz,计算得出的T1定时初值将不是一个整数,这样通信时便会产生积累误差,进而产生波特率误差,影响串行通信的同步性能。解决的方法只有调整单片机的时钟频率fosc,通常采用11.0592MHz晶振。因为用它能够非常准确地计算出T1定时初值,即使对于较高的波特率(19600,19200),不管多么古怪的值,只要是标准通信速率,使用11.0592MHz的晶振可以得到非常准确的数值。
表6.3.2列出了串口方式1定时器1方式2产生常用波特率时,TL0和TH0中所装入的值。
四、51 单片机串行口结构描述:
1、串行口结构
51 单片机的串行口是一个可编程全双工的通信接口, 具有UART (通用异步收发器)的全部功能, 能同时进行数据的发送和接收, 也可作为同步移位寄存器使用。
51 单片机的串行口主要由两个独立的串行数据缓冲寄存器SBUF ( 一个发送缓冲寄存器,一个接收缓冲寄存器)和发送控制器、接收控制器、输入移位寄存器及若干控制门电路组成。串行口基本结构如图 6.4.1 所示。
51单片机可以通过特殊功能寄存器SBUF对串行接收或串行发送寄存器进行访问,两个寄存器共用一个地址99H,但在物理上是两个独立的寄存器,由指令操作决定访问哪一个寄存器。执行写指令时,访问串行发送寄存器;执行读指令时,访问串行接收寄存器。接收器具有双缓冲结构,即在从接收寄存器中读出前一个已收到的字节之前,便能接收第二个字节,如果第二个字节已经接收完毕,第一个字节还没有读出,则将丢失其中一个字节,编程时应引起注意。对千发送器,因为数据是由CPU控制和发送的,所以不需要考虑。
与串行口紧密相关的一个特殊功能寄存器是串行口控制寄存器SCON,它用来设定串行口的工作方式、接收/发送控制以及设置状态标志等。
知识点:串行口控制寄存器SCON
串行口控制寄存器SCON在特殊功能寄存器中,字节地址为98H,可位寻址,SCON用以设定串行口的工作方式、接收/发送控制以及设置状态标志等。单片机复位时SCON全部被清0。其各位的定义如表6.4.1所示。
SM2—多机通信控制位。
SM2主要用于方式2和方式3。当接收机的SM2=1时,可以利用收到的RB8来控制是否激活RI(RB8=0时不激活RI,收到的信息丢弃;RB8=1时收到的数据进入SBUF,并激活RI,进而在中断服务中将数据从SBUF读走)。当SM2=0时,不论收到的RB8是0还是1,均可以使收到的数据进入SBUF,并激活RI(即此时RB8不具有控制RI激活的功能)。通过控制SM2, 可以实现多机通信。在方式0时,SM2必须是0。在方式1时,若SM2=1,则只有接收到有效停止位时,RI才置1。
REN-允许串行接收位。
REN=1:允许串行口接收数据;
REN=0:禁止串行口接收数据。
TBS—方式2,3中发送数据的笫9位。
在方式2或方式3中,是发送数据的笫9位,可以用软件规定其作用。可以用做数据的奇偶校验位,或在多机通信中,作为地址帧/数据帧的标志位。在方式0和方式1中,该位未用。
RBS—方式2,3中接收数据的笫9位。
在方式2或方式3中,是接收数据的笫9位,可作为奇偶校验位或地址帧/数据帧的标志位。在方式l时,若SM2=0,则RBS是接收到的停止位。
TI—发送中断标志位。
在方式0时,当串行发送笫8位数据结束时,或在其他方式,串行发送停止位的开始时,由内部硬件使TI置1,向CPU发出中断申请。在中断服务程序中,必须用软件将其清0,取消此中断申请。
RI—接收中断标志位。
在方式0时,当串行接收笫8位数据结束时,或在其他方式,串行接收停止位的中间时,由内部硬件使RI置1,向CPU发出中断申请。也必须在中断服务程序中,用软件将其清0,取消此中断申请。
2.串口方式简介
在这里对串口4种方式仅做简单介绍,在下一节将重点介绍串口方式1,在后面的篇章对其他几种方式再做详细介绍。
(1)方式0。方式0时,串行口为同步移位寄存器的输入/输出方式,主要用于扩展并行输入或输出口。数据由RXD(P3.0)引脚输入或输出,同步移位脉冲由TXD(P3.1)引脚输出。发送和接收均为8位数据,低位在先,高位在后,波特率固定为fosc/12。
(2)方式1。方式1是10位数据的异步通信口,其中1位起始位,8 位数据位,1位停止位。TXD(P3.1)为数据发送引脚,RXD(P3.0)为数据接收引脚。其传输波特率是可变的,对于51单片机,波特率由定时器1的溢出率决定。通常我们在做单片机与单片机串口通信、单片机与计算机串口通信、计算机与计算机串口通信时,基本都选择方式1,因此这种方式大家务必要完全掌握。
(3)方式2,3。方式2,3时为11位数据的异步通信口。TXD(P3.1)为数据发送引脚,RXD(P3.0)为数据接收引脚。这两种方式下,起始位1位,数据9位(含1位附加的第9位,发送时为SCON中的TBS,接收时为RBS),停止位1位,一帧数据为11位。方式2的波特率固定为晶振频率的1/64或1/32,方式3的波特率由定时器Tl的溢出率决定。
方式2和方式3的差别仅在于波特率的选取方式不同,在两种方式下,接收到的停止位与SBUF,RBS及RI都无关。
五、串行口方式1编程与实现:
串行口方式1是最常用的通信方式,其传送一帧数据的格式如图6.5.1所示。
串行口方式1传送一帧数据共10位****,1位起始位(0),8位数据位,最低位在前,高位在后,1位停止位(1),帧与帧之间可以有空闲,也可以无空闲。方式1数据输出时序图和数据输入时序图分别如图6.5.2和图6.5.3所示。
当数据被写入SBUF寄存器后,单片机自动开始从起始位发送数据,发送到停止位的开始时,由内部硬件将TI置1,向CPU申请中断,接下来可在中断服务程序中做相应处理,也可选择不进入中断。
用软件置REN为1时,接收器以所选择波特率的16倍速率采样RXD引脚电平,检测到RXD引脚输入电平发生负跳变时,则说明起始位有效,将其移入输入移位寄存器,并开始接收这一帧信息的其余位。接收过程中,数据从输入移位寄存器右边移入,起始位移至输入移位寄存器最左边时,控制电路进行最后一次移位。当Rl=0,且SM2=0(或接收到的停止位为1)时,将接收到的9位数据的前8位数据装入接收SBUF,第9位(停止位)进入RB8,并置Rl=1,向CPU请求中断。
在具体操作串行口之前,需要对单片机的一些与串口有关的特殊功能寄存器进行初始化设置,主要是设置产生波特率的定时器1、串行口控制和中断控制。具体步骤如下:
① 确定T1的工作方式(编程TMOD寄存器);
②计算T1的初值,装载TH1,TL1;
③启动T1(编程TCON中的TR1位);
④确定串行口工作方式(编程SCON寄存器);
⑤串行口工作在中断方式时,要进行中断设置(编程IE, IP寄存器)。
下面我们用一个实例讲解串口方式1的具体使用方法和操作流程,在TX-IC实验板上实现如下功能。
【例6.5.1】在上位机上用串口调试助手发送一个字符X, 单片机收到字符后返回给上位机"I get X",串口波特率设为9600bps。新建文件Sumjess2.4_1.c,程序代码如下:
分析如下:
① "uchar code table[]=“I get”;“定义了一个字符类型的编码数组,数组中的元素为字符串时,用双撇号将字符串引起来,空格也算一个字符。字符串由一个一个字符组成,因此也可写成另外一种方式"uchar code table[]={‘I’,’’,‘g’,‘e’,‘t’,’’};”,这里的每个字符用两个单撇号引起来,元素之间要用逗号隔开,空格也算一个字符。为叙述简便,建议大家选择第一种方式。
② 初始化函数"void init()"中各句解释如下:
TMOD=0x20; //设定Tl定时器工作方式2
TH1=0xfd; //定时器装初值
TL1=0xfd; //定时器装初值
TR1=1; II启动Tl定时器
REN=1; II允许串口接收
SM0=0; II设定串口工作方式1
SM1=1; II同上
EA=1; II开总中断
ES=1; II开串口中断
这里我们没有看到开定时器1中断的语旬,因为定时器1工作在方式2时为8位自动重装方式,我们进中断后无事可做,因此无须打开定时器1的中断,更无须写定时器1的中断服务程序。
③ "void ser () interrupt 4"为串口中断服务程序,在本程序中完成三件事:RI清0,因为程序既然产生了串口中断,那么肯定是收到或发送了数据,在开始时没有发送任何数据,那必然是收到了数据,此时RI会被硬件置1,进入串口中断服务程序后必须由软件清0,这样才能产生下一次中断;将SBUF中的数据读走给a,这才是进入中断服务程序中最重要的目的;**将标志位flag置1,**以方便在主程序中查询判断是否已经收到数据。
④ 进入大循环while()语句后,一直在检测标志位flag是否为1, 当检测到为1时,说明程序已经执行过串口中断服务程序,即收到了数据,否则始终检测flag的状态。当检测到flag置1后,先是将ES清0,原因是接下来要发送数据,若不关闭串口中断,当发送完数据后,单片机同样会申请串口中断,便再次进入中断服务程序,flag又被置1,主程序检测到flag为1,又回到这里再次发送,如此重复下去,程序便成了死循环,造成错误的现象,因此我们在发送数据前把串口中断关闭,等发送完数据后再打开串口中断,这样便可以安全地发送数据了。大家可亲自做实验,不要关闭串口,观察实验结果。
⑤ 在发送数据时,当发送前面6个固定的字符时,使用了一个for循环语句,将前面数组中的字符依次发送出去,后面再接着发送从中断服务程序中读回来的SBUF中的数据时,当向SBUF 中写入一个数据后,使用"while(!TI);"等待是否发送完毕,因为当发送完毕后TI会由硬件置1,然后才退出"while(!TI);"接下来我们再将TI手动清0。
⑥ 当接收数据时,我们写"a=SBUF;"语句,单片机便会自动将串口接收寄存器中的数据取走给a;当发送数据时,我们写"SBUF=a;“语句,程序执行完这条语句便自动开始将串口发送寄存器中的数据一位位从串口发送出去。很多初学者搞不明白,在这里再强调一下,SBUF是共用一个地址的两个独立的寄存器,单片机识别操作哪个寄存器的关键语句就是
“a=SBUF” 和"SBUF=a”。
串口调试助手设置及实验实拍照片如下图所示。
六、串行口打印在调试程序中的应用:
串行口打印功能通常用在程序调试中,举个例子说明它的用途:我们正在用单片机调试一个A/D芯片,单片机的外围只接了A/D芯片和串行口,当我们写好单片机程序下载后让其运行,可是我们根本不知道这个A/D芯片工作了没有?更不知道A/D芯片采集回来的数值对不对?这时如果我们使用串口打印功能,将单片机采集回来的A/D值经过处理后,发送到上位机上,在上位机上用一个简单的串口工具就可看见数据,这样我们在调试程序时便会方便许多。其次我们在调试其他程序时,在整个程序的不同地方,或是关键地方使用串口打印功能输出给上位机一个关键数据,我们就可知道程序中某些变量的实时数值,进一步得知程序运行的状况。
6.5节中我们将"I get"放在一个数组中,通过一个for循环连续发送给上位机,这实际上也算是串口打印功能的应用,但我们还有更方便的方法来运用它,本节我们将为大家介绍几个C51库函数中自带的与串口有关的非常有用的函数。
下面通过一个实例完整讲述串口打印功能的用法及相关注意事项,利用上位机与TX-1C实验板实现如下功能。
【例6.6.1】单片机上电后等待从上位机串口发送来的命令,同时在数码管的前三位以十进制方式显示A/D采集的数值,在未收到上位机发送来的启动A/D转换命令之前数码管始终显示000。
当收到上位机以十六进制发送来的01后,向上位机发送字符串"Turn on ad!",同时间隔一秒读取一次A/D的值,然后把A/D采集回来的8位二进制数转换成十进制数表示的实际电压浮点数,并且从串口发送给上位机,形式如"The voltage is 3.398438V",发送周期也是一秒一次,同时在数码管上也要每秒刷新显示的数值。
当收到上位机以十六进制发送来的02后,向上位机发送字符串"Turn off ad!",然后停止发送电压值,数码管上显示上次结束时保持的值。
当收到上位机发来的其他任何数时,向上位机发送字符串"Error!"。
下面是实现上述功能的完整例程,在调试本程序的时候遇到了许多很奇怪的错误现象,我在经过大量反复实验后总结出了一些非常重要的知识点,在例程的后面将一一描述,我们先来看例程,新建文件Sumjess2.4_2.c,程序代码如下:
编译程序下载到实验板,打开串口调试助手,分别发送01,02,03,当开启A/D转换后,适当调节实验板上A/D电压调节电位器,可看到返回的电压实际值在变化,最终界面显示如图6.6.1所示。
分析如下:在分析本节例程之前,先来看上一节的例程。其实在6.5节串口测试程序中有个小问题:如果我们先打开串口调试助手软件,再打开实验板上电源的话,会看到上位机软件会在实验板刚一上电的时候收到一串字符’'I get",可是这时我们并没有向单片机发送任何命令,而单片机为何为主动发数据呢?也许大多数人并没有注意到这个现象,如果作为一个产品的话,这样的系统肯定不能算稳定工作的,6.5节的问题若没有解决,本节的例程将不可能调试成功,因此我们必须解决掉任何一个不正常的问题。
(1)先来解决6.5节的问题。我们在串口初始化函数中有这样几条语句:
REN=1;
SM0=0;
SM1=1;
这三位都是串行口控制寄存器SCON里面的,单片机刚上电时SCON被清0,因为串口方式为方式0,串行口为同步移位寄存器的输入/输出方式,当执行完REN置1这条语句后,它便直接开始从RXD引脚接收数据,并不管与它连接的系统有无发送数据,这时SM0和SM1还未被操作,可单片机串口寄存器已经收到数据,并且已经产生了串口中断,因此串口中断中的标志位flag将被置1,当运行完下面两条指令后,串口方式才被设置为方式1,这时才终止串口接收数据。当程序运行到while(1)大循环中时,因为串口中断服务程序中的标志位flag已经被置1,所以接下来将发送里面的"I get",至于后面的a被发送到上位机之后为什么变成了一个空格,这个由大家自己来研究。
本问题解决办法如下:
① 将上面三条语句顺序改为
SM0=0;
SM1=1;
REN=1;
先设置串口模式,再允许串口接收,这样就会避开串口方式0接收数据。
② 不要对SCON寄存器进行位操作,而是直接对整个寄存器进行设置,如SCON=0x50。大家可亲自做实验体检各种现象,若将本节实验中这三句改回原样后实验,产生的错误便不会如6.5节一样简单了,将会影响整个系统的正常运行,大家务必尝试一下。像这种看似很小的问题,在没有解决之前可能需要花费很长的时间才能找到问题的根源,我也不隐讳地告诉大家,我在调试本例程时,花了近3个小时才将它调试成功,无论如何也没有想到错误的原因竟然是因为把"REN=1;“这条语句提前写了两行,当然每解决一个问题,我们学到的知识都将更进一步,甚至更多,单片机是硬件,必须要经过大量的实验方可掌握,像这种现象光靠学书本上的理论,是永远都学不到的。
(2)#include<stdio.h>头文件中包含有我们要使用的函数printf()和puts()。我们到Keil\C51\INC文件夹下打开STDIO.H, 可看到里面申明了一些外部函数,内容如下:
extern char _getkey(void);
extern char getchar(void);
extern char ungetchar(char);
extern char putchar(char);
extern int printf(const char*,…);
extern int sprintf(char*,const char*,…);
extern int vprintf(const char*,char*);
extern int vsprinf(char*,const char*,char*);
extern chargets(char,int n);
extern int scanf(const char*,…);
extern int sscanf(char*,const char*,…);
extern int puts(const char*);
extern表示在这里申明的是一个外部函数,外部函数的函数体不在本文件中,而是在其他某个文件中写有这个函数的实现部分。
在本例中用到的printf()和puts()函数,在它内部都是由putchar()这个函数实现的,关于这两个函数的用法大家请看C51的帮助文件。在Keil\C5l\LIB文件夹下打开PUTCHAR.C文件可看到内容如下:
这个函数的主要作用是通过串口发送一个字符,在代码的最后我们看到有等待TI为1才将字符发送出去,否则一直等待下去,这也就是我们在本节例程中看到的每次在调用printf()和puts()函数之前先要手动将TI置1的原因,这一点至关重要,大家可改变例程亲自做实验,体验去掉TI=1后的现象。
(3)printf()和puts()的区别:从串口调试助手收到的数据我们可以看出,两个函数在参数中都加有”\n",即回车的意思。而puts()函数输出到上位机后还多了一个换行,这是区别之一;区别之二是,printf()可以在后面追加要输出的变量,而puts()只能输出字符串。
(4)在使用stdio.h这个头文件中的函数之前,必须将串口部分初始化完毕,最好将串口设置为方式1,波特率与上位机一致。
(5)在每次调用完printf()和puts()函数后,必须检测是否发送完毕,即检测TI是否为1,当发送完毕后要把TI清0,否则程序会出错,大家可自行验证。
(6)每次调用printf()和puts()函数之前,必须将串口中断先关闭。若不关闭串口中断,每发送一个字节,程序就会申请进入串口中断,从而导致程序出错。
(7)“ad_val=get_ad();“是将A/D采集回来的8位二进制数赋给ad_val,”(float)ad_val"的意思是将字符型变量ad_val的值强制转换成浮点型,然后经过”(float)ad_val*5.0/256.0"运算得出以浮点数表示的A/D实际采集到的电压标准值。注意:在浮点数运算中,原来是整数的常量后面需要加".0变成浮点数,原来是整数的变量需要强制转换成浮点数再进行运算。