51单片机的串口通信原理

一、并行和串行通信方式
通信有并行和串行两种通信方式。在单片机系统以及现代单片机测控系统中信息交换多采用串行通信方式。
1、并行通信方式
并行通信通常是将数据字节的各位用多条数据线同时进行传送,每一位数据都需要一条传输线。如下图所示,8位数据总线的通信系统,一次传送8位数据(1字节),需要8条数据线。还需要一条信号线和若干控制信号线,这种方式仅适合短距离的数据传输。
在这里插入图片描述
2、串行通信方式
串行通信方式是将数据字节分为一位一位的形式在一条传输线上逐个传输,此时只需要一条数据线,外加一条公共信号地线和若干控制信号线。因为一次只能传输一位,所以1字节的数据至少要分8位才能传输完毕。如下图所示
在这里插入图片描述
串行通信的必要过程是:发送时把并行数据变成串行数据发送到线路上,接收时把串行信号再变成并行数据,这样才能被计算机及其他设备处理。
串行通信有两种方式:异步串行通信和同步串行通信。
串行通信的制式:单工,半双工,全双工。
串行通信的错误校验:奇偶校验,代码和校验,循环冗余校验。
二、波特率与定时器初值的关系
1、波特率
单片机或计算机在串口通信时的速率用波特率表示,定义为每秒传输二进制代码的位数,即1波特=1位/秒,即bps(bit per second)。如果每秒钟传输240个字符,每个字符格式包含10位(1个起始位、1个停止位、8个数据位),这时的波特率为10位*240个/秒=2400bps。串行口或终端直接传送串行信息位流的传输距离随传输速率的增加而减小。
2、波特率的计算
在串行通信中,收发双方对发送或接收数据的速率要有约定。通过软件可对单片机串行口编程为四种工作方式,其中方式0和方式2的波特率是固定的,而方式1和方式3的波特率是可变的,由定时器T1的溢出率来决定。串行口的四种工作方式对应三种波特率。由于输入的移位时钟的来源不同,所以,各种方式的波特率计算公式也不相同。
方式0的波特率 = fosc/12
方式2的波特率 =(2^SMOD/64)· fosc
方式1的波特率 =(2^SMOD/32)·(T1溢出率)
方式3的波特率 =(2^SMOD/32)·(T1溢出率)
其中,fosc 为系统晶振频率,通常为12MHz或11.0592MHz;T1溢出率是定时器1溢出的频率。SMOD是PCON寄存器(不能位寻址)的最高位,PCON中只有一位SMOD与串行口工作有关 :
在这里插入图片描述
SMOD(PCON.7) 波特率倍增位。在串行口方式1、方式2、方式3时,波特率与SMOD有关,当SMOD=1时,波特率提高一倍。复位时,SMOD=0。
T1溢出率是定时器1溢出的频率,只要计算出T1定时器每溢出一次所需的时间T,那么T的倒数1/T就是它的溢出率。如:定时器T1每50ms溢出一次,那么溢出率就为1/0.05s=20Hz。
单片机在通信时,波特率通常都较高,因此T1溢出率也很高。如果使用定时器1的工作方式1在中断中装初值的方法来求T1溢出率,在进入中断、装值、出中断的过程中容易产生时间上微小的误差,多次操作时微小误差不断累积,终会产生错误。解决办法是使用T1定时器的工作方式2,8位初值自动重装的8位定时器/计数器,如下图:
在这里插入图片描述
在方式1中,当定时器计满溢出时,自动进入中断服务程序,然后需要手动再次给定时器装初值。而在方式2中(参考中断系统中定时器的介绍),当定时器计满溢出后,单片机会为其装初值,并且无须进入中断服务程序进行任何处理,这样定时器溢出的速率就会绝对稳定。方式2的工作过程:先设定M0M1选择定时器方式2,在THX和TLX中装入计算好的初值,启动定时器,然后TLX寄存器在时钟的作用下开始加1计数;当TLX计满溢出后,CPU会自动将THX中的数装入TLX中,继续计数。THX和TLX中装入的数值必须相同,因为每次计数溢出后TLX中装入的新值是从THX中取出的。
下面介绍在已知波特率下,如何计算定时器1方式2下计数寄存器中的初值?
例:已知串口通信在串口方式1下,波特率为9600bps系统晶振频率为11.0592MHz,求TL1和TH1中装入的数值是多少?
答:设装入的初值为X,则所计的数为256-X就溢出,每次计一个数的时间为一个机器周期,一个机器周期为12个时钟周期,所以计一个数的时间为12/11.0592MHz(s),那么定时器溢出一次的时间就为:(256-X)*12/11.0592MHz(s)=(256-X)*12/11059200Hz(s)
方式1的波特率的计算公式为:方式1的波特率 =(2^SMOD/32)·(T1溢出率),这里SMOD取0。代入公式得:
9600=(1/32)·11059200/(256-X)·12。求得X=253,十六进制为0xFD。
如果将SMOD变为1,那么X的值就变为250了。可见在不变化X值的状态下,SMOD由0变为1后,波特率便增加一倍。
三、51单片机串行口结构描述
1、串行口结构
51单片机的串行口是一个可编程全双工的通信接口,具有UART(通用异步收发器)的全部功能,能同时进行数据的发送和接收,也可作为同步移位寄存器使用。基本结构如下:
在这里插入图片描述
51单片机可以通过特殊功能寄存器SBUF对串行接收或串行发送寄存器进行访问,两个寄存器公用一个地址99H,注意:在物理上这是两个独立的寄存器, 只是共用一个地址而已,由指令操作决定访问哪一个寄存器。即a=SBUF接收和SBUF=a发送。
2、串行口控制寄存器SCON
SCON 是一个特殊功能寄存器,字节地址为98H,可位寻址,用以设定串行口的工作方式、接收/发送控制以及设置状态标志等,单片机复位时,SCON全部被清0,其各位定义如下:
在这里插入图片描述
SM0和SM1为工作方式选择位,可选择四种工作方式:
在这里插入图片描述
●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,则禁止接收。
●TB8,在方式2或方式3中,是发送数据的第九位,可以用软件规定其作用。可以用作数据的奇偶校验位,或在多机通信中,作为地址帧/数据帧的标志位。在方式0和方式1中,该位未用。
●RB8,在方式2或方式3中,是接收到数据的第九位,作为奇偶校验位或地址帧/数据帧的标志位。在方式1时,若SM2=0,则RB8是接收到的停止位。
●TI,发送中断标志位。在方式0时,当串行发送第8位数据结束时,或在其它方式,串行发送停止位的开始时,由内部硬件使TI置1,向CPU发中断申请。在中断服务程序中,必须用软件将其清0,取消此中断申请。
●RI,接收中断标志位。在方式0时,当串行接收第8位数据结束时,或在其它方式,串行接收停止位的中间时,由内部硬件使RI置1,向CPU发中断申请。也必须在中断服务程序中,用软件将其清0,取消此中断申请。
说明:通过上面串行口控制寄存器SCON的介绍,可以看出在使用串行口工作方式1的情况下,设置SCON各位如下:SM0=0,SM1=1,SM2=0,REN=1,其余各位TB8,RB8,TI,RI都不用设置即都为0。
四、串行口方式1编程与实现
串行口方式1是最常用的通信方式,所以这里主要介绍方式1,学会一种其他方式也是大同小异。其传送一帧数据的格式如下图所示:
在这里插入图片描述
方式1是10位数据的异步通信口,其中1位起始位,8位数据位,1位停止位。传送一帧数据共10位,1位起始位(0),8位数据位,最低位在前,高位在后,1位停止位(1),帧与帧之间可以有空闲,也可以无空闲。TXD为数据发送引脚,RXD为数据接收引脚,方式1数据输出时序图和数据输入时序图分别如下图所示:
(1)方式1数据输出时序图
在这里插入图片描述
当数据被写入SBUF寄存器后,单片机自动开始从起始位发送数据,发送到停止位的开始时,由内部硬件将TI置1,向CPU申请中断,接下来可在中断服务程序中做相应处理,也可选择不进入中断。
(2)方式1数据输入时序图
在这里插入图片描述
用软件置REN为1时,接收器以所选择波特率的16倍速率采样RXD引脚电平,检测到RXD引脚输入电平发生负跳变时,则说明起始位有效,将其移入输入移位寄存器(见上面串行口基本结构),并开始接收这一帧信息的其余位。接收过程中,数据从输入移位寄存器右边移入,起始位移至输入寄存器最左边时,控制电路进行最后一次移位。当RI=0,且SM2=0(或接收到的停止位为1)时,将接收到的9位数据的前8位数据装入接收SBUF,第9位(停止位)进入RB8,并置RI=1,向CPU请求中断。
五、编程实例
串口连接的电路图如下
在这里插入图片描述
串行口工作之前,应对其进行初始化,主要是设置产生波特率的定时器1、串行口控制和中断控制。具体步骤如下:
确定T1的工作方式(编程TMOD寄存器);
计算T1的初值,装载TH1、TL1;
启动T1(编程TCON中的TR1位);
确定串行口控制(编程SCON寄存器);
串行口在中断方式工作时,要进行中断设置(编程IE、IP寄存器)。
例1:在上位机上用串口调试助手发送数据,根据所发送不同的数据点亮不同的二极管。
(1)用扫描法

#include <reg52.h>
void main()
{
	TMOD=0x20;//设置定时器1为工作方式2,8位初值自动重装的8位定时器
	TH1=0xfd;//波特率为9600bps,SMOD=0时根据公式求得数值为253即十六进制的0xfd
	TL1=0xfd;
	TR1=1;//启动定时器1,由此可以看出我在中断时介绍在没有中断的情况下定时器也可以单独使用的
	REN=1;//允许串行口接收数据
	SM0=0;//设置串行口工作方式1
	SM1=1;
	while(1)
	{
		if(RI==1)//如果RI=1了说明单片机是接收到了数据,由硬件置1
		{
			RI=0;//只能由软件置0,如果不置0就会一直进入中断
			P1=SBUF;//单片机接收到上位机发送的数据,让P1口读取就点亮二极管了
		}
	}
}

用串口调试助手发送FD,可以看到第二个二极管被点亮。注意:串口在电脑管理设置中查看,要一致才能连接上;波特率也要一致不然发送时会出现乱码与想象中点亮的二极管不一致;因为是串口方式1,所以数据位为8,停止位为1;还要注意用十六进制发送,不然也会出现乱码。
(2)中断法

#include <reg52.h>
void main()
{
	TMOD=0x20;//设置定时器1为工作方式2,8位初值自动重装的8位定时器
	TH1=0xfd;//波特率为9600bps,SMOD=0时根据公式求得数值为253即十六进制的0xfd
	TL1=0xfd;
	TR1=1;//启动定时器1,由此可以看出我在中断时介绍在没有中断的情况下定时器也可以单独使用的
	REN=1;//允许串行口接收数据
	SM0=0;//设置串行口工作方式1
	SM1=1;
	ES=1;//开定时器中断
	EA=1;//开总中断
	while(1)
	{
	
	}
}

void ser() interrupt 4
{
	RI=0;
	P1=SBUF;
}

在这里插入图片描述
在这里插入图片描述
(3) 接收“固定协议”的串口程序框架
①固定协议
实际项目中,串口一个回合的收发数据量远远不止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。
②程序框架的四个要点分析
第一点:先接收后处理,开辟一块专用的内存数组。要处理一串数据,必须先征用一块内存数组专门用来缓存接收到的数据,等接收完此串数据再处理。
第二点:接头暗号。本节例子的数据头EB就是接头暗号。一旦接头暗号吻合,才会进入到下一步接收其它有效数据的步骤上。
第三点:如何识别接收一串数据的完毕。本节例子中,是靠“固定协议”提供的“数据长度”来判别是否已经接收完一串数据。中断函数接收完一串数据后,应该用一个全局变量来给外部main函数一个通知,让main函数里面的相关函数来处理此串数据。
第四点:接收数据中相邻字节之间通信超时的异常处理。如果接头暗号吻合之后,马上切换到“接受其它有效数据”的步骤,但是,如果在此步骤的通信过程中一旦发现通信不连贯,就应该及时退出当下“接受其它有效数据”的步骤,继续返回到刚开始的“接头暗号”的步骤,为下一次接收新的一串数据做准备。那么,如何识别通信不连贯?靠判断接收数据中相邻字节之间的时间是否超时来决定,详细内容请看下面的程序例程

#include "REG52.H"

#define RECE_TIME_OUT    2000  //通信过程中字节之间的超时时间2000ms
#define REC_BUFFER_SIZE  20    //接收数据的缓存数组的长度


void usart(void);  //串口接收的中断函数
void T0_time();    //定时器的中断函数

void UsartTask(void);    //串口接收的任务函数,放在主函数内

void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;

void BeepOpen(void);   
void BeepClose(void);
void VoiceScan(void);

sbit P2_3=P2^3;  //蜂鸣器端口

volatile unsigned char vGu8BeepTimerFlag=0;  
volatile unsigned int vGu16BeepTimerCnt=0;  

unsigned char Gu8ReceBuffer[REC_BUFFER_SIZE]; //开辟一片接收数据的缓存
unsigned long Gu32ReceCnt=0;  //接收缓存数组的下标
unsigned char Gu8ReceStep=0;  //接收中断函数里的步骤变量
unsigned char Gu8ReceFeedDog=1; //“喂狗”的操作变量。
unsigned char Gu8ReceType=0; //接收的数据类型
unsigned long Gu32ReceDataLength=0;  //接收的数据长度
unsigned char Gu8FinishFlag=0;  //是否已接收完成一串数据的标志
unsigned long *pu32Data; //用于数据转换的指针
volatile unsigned char vGu8ReceTimeOutFlag=0;//通信过程中字节之间的超时定时器的开关
volatile unsigned int vGu16ReceTimeOutCnt=0; //通信过程中字节之间的超时定时器,“喂狗”的对象

void main()
{
    SystemInitial();            
    Delay(10000);               
    PeripheralInitial();      
    while(1)  
    {  
        UsartTask();   //串口接收的任务函数
    }
}

void usart(void) interrupt 4   //串口接发的中断函数,中断号为4         
{        
   if(1==RI)  //接收完一个字节后引起的中断
   {
        RI = 0; //及时清零,避免一直无缘无故的进入中断。

/* 注释一:
* 以下Gu8FinishFlag变量的用途。
* 此变量一箭双雕,0代表正处于接收数据的状态,1代表已经接收完毕并且及时通知主函数中的处理函数
* UsartTask()去处理新接收到的一串数据。除此之外,还起到一种“自锁自保护”的功能,在新数据还
* 没有被主函数处理完毕的时候,禁止接收其它新的数据,避免新数据覆盖了尚未处理的数据。
*/
           if(0==Gu8FinishFlag)  //1代表已经完成接收了一串新数据,并且禁止接收其它新的数据
           {

/* 注释二:
* 以下Gu8ReceFeedDog变量的用途。
* 此变量是用来检测并且识别通信过程中相邻的字节之间是否存在超时的情况。
* 如果大家听说过单片机中的“看门狗”这个概念,那么每接收到一个数据此变量就“置1”一次,它的
* 作用就是起到及时“喂狗”的作用。每接收到一个数据此变量就“置1”一次,在主函数里,相关
* 的定时器就会被重新赋值,只要这个定时器能不断及时的被补充新的“能量”新的值,那么这个定时器
* 就永远不会变成0,只要不变成0就不会超时。如果两个字节之间通信时间超过了固定的长度,就意味
* 着此定时器变成了0,这时就需要把中断函数里的接收步骤Gu8Step及时切换到“接头暗号”的步骤。
*/
                  Gu8ReceFeedDog=1; //每接收到一个字节的数据,此标志就置1及时更新定时器的值。
                  switch(Gu8ReceStep)
                  {
                          case 0:     //接头暗号的步骤。判断数据头的步骤。
                                   Gu8ReceBuffer[0]=SBUF; //直接读取刚接收完的一个字节的数据。
                                   if(0xeb==Gu8ReceBuffer[0])  //等于数据头0xeb,接头暗号吻合。
                                   {
                                          Gu32ReceCnt=1; //接收缓存的下标
                                          Gu8ReceStep=1;  //切换到下一个步骤,接收其它有效的数据
                                   }
                                   break;               
                                       
                          case 1:     //数据类型和长度
                                   Gu8ReceBuffer[Gu32ReceCnt]=SBUF; //直接读取刚接收完的一个字节的数据。
                                   Gu32ReceCnt++; //每接收一个字节,数组下标都自加1,为接收下一个数据做准备
                                   if(Gu32ReceCnt>=6)  //前6个数据。接收完了“数据类型”和“数据长度”。
                                   {
                                            Gu8ReceType=Gu8ReceBuffer[1];  //提取“数据类型”
                                            //以下的数据转换,在第62节讲解过的指针法
                                                pu32Data=(unsigned long *)&Gu8ReceBuffer[2]; //数据转换,强制类型转换为长整型的指针,4字节即本节的00 00 00 08
                                                Gu32ReceDataLength=*pu32Data; //提取“数据长度”,把接收的数据值赋值给此变量
                                            if(Gu32ReceCnt>=Gu32ReceDataLength) //靠“数据长度”来判断是否完成
                                                {
                                                        Gu8FinishFlag=1; //接收完成标志“置1”,通知主函数处理。
                                                        Gu8ReceStep=0;   //及时切换回接头暗号的步骤
                                                }
                                                else   //如果还没结束,继续切换到下一个步骤,接收“其它数据”
                                                {
                                                        Gu8ReceStep=2;   //切换到下一个步骤
                                                }                                                        
                                   }
                                   break;               
                          case 2:     //其它数据
                                   Gu8ReceBuffer[Gu32ReceCnt]=SBUF; //直接读取刚接收完的一个字节的数据。
                                   Gu32ReceCnt++; //每接收一个字节,数组下标都自加1,为接收下一个数据做准备

                                    //靠“数据长度”来判断是否完成。也不允许超过数组的最大缓存的长度
                                   if(Gu32ReceCnt>=Gu32ReceDataLength||Gu32ReceCnt>=REC_BUFFER_SIZE)
                                   {
                                          Gu8FinishFlag=1; //接收完成标志“置1”,通知主函数处理。
                                          Gu8ReceStep=0;   //及时切换回接头暗号的步骤
                                   }
                                   break;        
                  }
       }
   }
   else  //发送数据引起的中断
   {
        TI = 0;  //及时清除发送中断的标志,避免一直无缘无故的进入中断。
        //以下可以添加一个全局变量的标志位的相关代码,通知主函数已经发送完一个字节的数据了。
   }                                                      
}  


void UsartTask(void)    //串口接收的任务函数,放在主函数内
{
    static unsigned int *pSu16Data; //数据转换的指针
    static unsigned int Su16Data;  //转换后的数据

    if(1==Gu8ReceFeedDog) //每被“喂一次狗”,就及时更新一次“超时检测的定时器”的初值
    {
        Gu8ReceFeedDog=0;               
        vGu8ReceTimeOutFlag=0;
        vGu16ReceTimeOutCnt=RECE_TIME_OUT;//更新一次“超时检测的定时器”的初值
        vGu8ReceTimeOutFlag=1;
    }
    else if(Gu8ReceStep>0 && 0==vGu16ReceTimeOutCnt) //超时,并且步骤不在接头暗号的步骤
    {
        Gu8ReceStep=0; //串口接收数据的中断函数及时切换回接头暗号的步骤
    }

        
    if(1==Gu8FinishFlag)  //1代表已经接收完毕一串新的数据,需要马上去处理
    {
        switch(Gu8ReceType)  //接收到的数据类型,此switch可根据发来的数据类型处理多种任务
        {
                case 0x01:   //驱动蜂鸣器
            //以下的数据转换,在第62节讲解过的指针法
                    pSu16Data=(unsigned int *)&Gu8ReceBuffer[6]; //数据转换。两字节数据
                    Su16Data=*pSu16Data; //提取“蜂鸣器声音的长度”,数组中两字节转换为了整型变量
                    vGu8BeepTimerFlag=0;  
                    vGu16BeepTimerCnt=Su16Data;   //让蜂鸣器鸣叫
                    vGu8BeepTimerFlag=1;  
                  break;
        }

        Gu8FinishFlag=0;  //上面处理完数据再清零标志,为下一次接收新的数据做准备
    }
}



void T0_time() interrupt 1     //定时器0中断
{
    VoiceScan();  

    if(1==vGu8ReceTimeOutFlag && vGu16ReceTimeOutCnt>0) //通信过程中字节之间的超时定时器
    {
        vGu16ReceTimeOutCnt--;        
    }  

    TH0=0xfc;  //1ms初值 
    TL0=0x66;   
}


void SystemInitial(void)
{
    unsigned char u8_TMOD_Temp=0;

    //以下是定时器0的中断的配置
    TMOD=0x01;  
    TH0=0xfc;   
    TL0=0x66;   
    EA=1;      
    ET0=1;      
    TR0=1;   

    //以下是串口接收中断的配置
    //串口的波特率与内置的定时器1直接相关,因此配置此定时器1就等效于配置波特率。
    u8_TMOD_Temp=0x20; //即将把定时器1设置为:工作方式2,初值自动重装的8位定时器。
    TMOD=TMOD&0x0f; //此寄存器低4位是跟定时器0相关,高4位是跟定时器1相关。先清零定时器1。
    TMOD=TMOD|u8_TMOD_Temp;  //把高4位的定时器1填入0x2,低4位的定时器0保持不变。
    TH1=256-(11059200L/12/32/9600);  //波特率为9600。11059200代表晶振11.0592MHz,
    TL1=256-(11059200L/12/32/9600);  //L代表long的长类型数据。根据芯片手册提供的计算公式。
    TR1=1;  //开启定时器1

    SM0=0;  
    SM1=1;  //SM0与SM1的设置:选择10位异步通信,波特率根据定时器1可变  
    REN=1;  //允许串口接收数据

    //为了保证串口中断接收的数据不丢失,必须设置IP = 0x10,相当于把串口中断设置为最高优先级,
    //这个时候,串口中断可以打断任何其他的中断服务函数实现嵌套,
    IP =0x10;  //把串口中断设置为最高优先级,必须的。

    ES=1;         //允许串口中断  
    EA=1;         //允许总中断
}

void Delay(unsigned long u32DelayTime)
{
    for(;u32DelayTime>0;u32DelayTime--);
}

void PeripheralInitial(void)
{

}

void BeepOpen(void)
{
    P2_3=0;  
}

void BeepClose(void)
{
    P2_3=1;  
}

void VoiceScan(void)
{

    static unsigned char Su8Lock=0;  

    if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
    {
        if(0==Su8Lock)
        {
            Su8Lock=1;  
            BeepOpen();
        }
        else  
        {     

            vGu16BeepTimerCnt--;         

            if(0==vGu16BeepTimerCnt)
            {
                Su8Lock=0;     
                BeepClose();  
            }

        }
    }         
}

在这里插入图片描述
当发送EB 01 00 00 00 08 03 E8这一串数据时蜂鸣器响2秒钟。

  • 11
    点赞
  • 88
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值