51单片机的串口通信原理

2023-05-16

一、并行和串行通信方式
通信有并行和串行两种通信方式。在单片机系统以及现代单片机测控系统中信息交换多采用串行通信方式。
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秒钟。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

51单片机的串口通信原理 的相关文章

随机推荐

  • 网络编程--01--socket简介--套接字

    socket 套接字 什么是Socket 在计算机通信领域 xff0c socket 被翻译为 套接字 xff0c 它是计算机之间进行通信的一种约定或一种方式 通过 socket 这种约定 xff0c 一台计算机可以接收其他计算机的数据 x
  • 使用strtok函数和split函数来分割字符串

    使用 strtok 函数可以比较方便地实现字符串分割 具体来说 xff0c 可以将 strtok 函数看作一个状态机 xff0c 它会记录当前扫描到的字符串位置 xff0c 并根据指定的分隔符将其分割成多个子字符串 xff0c 并返回分割后
  • vagrant安装docker

    卸载系统之前的docker sudo yum remove docker docker client docker client latest docker common docker latest docker latest logrot
  • 图像识别的未来:机遇与挑战并存

    识别图像对人类来说是件极容易的事情 xff0c 但是对机器而言 xff0c 这也经历了漫长岁月 在计算机视觉领域 xff0c 图像识别这几年的发展突飞猛进 例如 xff0c 在PASCAL VOC物体检测基准测试中 xff0c 检测器的性能
  • 在Ubuntu中通过CV_bridge更改OpenCV版本

    由于最近一个demo使用的OpenCV版本高于Ubuntu1804melodic自带的OpenCV3 2版本 xff0c 需要调节OpenCV的版本 1 安装OpenCV 下载地址 xff1a Releases OpenCV选择自己需要的版
  • LQR控制算法的浅析

    目录 前言 一 知识点补充 1 拉格朗日乘子法 2 积分中值定理 3 向前欧拉法 xff0c 向后欧拉法 xff0c 中点欧拉法 4 向量的导数 5 矩阵求逆引理 记住就好 xff0c 推导见链接 二 连续时间下的LQR推导 1 系统状态方
  • Linux网络编程【UDP】

    文章目录 UDP C S 通信TCP和UDP对C S 通信相关函数recvfrom xff08 accept 43 read xff09 sendto xff08 connect 43 write xff09 bind UDP 服务代码参考
  • C语言结构体对齐

    计算机内存是以字节 xff08 Byte xff09 为单位划分的 xff0c 理论上CPU可以访问任意编号的字节 xff0c 但实际情况并非如此 cpu一次能读取多少内存要看数据总线是多少位 xff0c 如果是16位 xff0c 则一次只
  • 匿名飞控STM32版代码整理之Ano_Imu.c

    一 ANO Imu c文件 COPYRIGHT 2016 ANO Tech 作者 xff1a 匿名科创文件名 xff1a ANO IMU c描述 xff1a 姿态解算函数官网 xff1a www anotc com淘宝 xff1a anot
  • 匿名飞控码STM32版代码整理之Ano_AttCtrl.c

    代码 include Ano AttCtrl h include Ano Imu h include Drv icm20602 h include Ano MagProcess h include Drv spl06 h include A
  • realsense版本号问题

    问题 xff1a librealsense2 so 2 47 cannot open shared object file No such file or directory 打开驱动查看版本号 realsense span class t
  • YOLOv3

    YOLOv3 论文信息论文标题 xff1a 论文作者 xff1a 收录期刊 会议及年份 xff1a 论文学习YOLOv3 网络架构 xff1a YOLO 输出特征图解码 xff08 前向过程 xff09 xff1a 训练策略与损失函数 xf
  • 使用Gazebo进行移动机械臂抓取仿真

    该项目在Gazebo中搭建一款机器人模型 xff0c 其底盘使用turtlebot移动机器人底盘 xff0c 机械臂采用结构较为简单的turtlebot arm xff0c 并在底盘上添加kinect深度相机 最终该机器人可实现自主导航 物
  • 【树莓派——Ubuntu 20.04 系统安装及Windows远程桌面连接显示】

    前言 文章A主要是Raspberry Pi官方系统的安装和用VNC查看器远程连接显示 xff1b 这篇文章主要分享一下Ubuntu 20 04系统的安装以及用Windows 远程桌面连接显示 Ubuntu 20 04系统的下载和烧录 专门的
  • Abstract写作常用句式

    Abstract 摘要以最简洁的文字概括论文 xff0c 点明研究的目的 途径 结果 意义 xff0c 以便读者决定是否要阅读全文 摘要一般包含几项内容 xff1a 1 论文主题 xff08 1句 xff09 2 理论基础 途径 方法 xf
  • mysql备份与恢复:完全备份,增量备份,基于位置点恢复,基于时间点恢复

    理论介绍 数据备份的重要性数据库备份的分类 常见的备份方法MySQL完全备份数据库完全备份分类 数据库备份与恢复MySQL数据库完全备份与恢复mysqldump备份与恢复MySQL数据库增量备份恢复基于位置的恢复基于时间恢复 数据备份的重要
  • vue => element 进度条 自定义文字 三层数据渲染页面报错:Invalid prop: custom validator check failed for prop “percentage

    三层数据渲染页面报错 报错原因分析 xff1a 解决方案 xff1a span class token operator lt span span class token operator span span class token ope
  • 使用xfsdump进行文件的备份与恢复

    1 添加一块硬盘大小随意 2 sdb为新添加的硬盘 3 格式化为xfs格式 4 新建目录并永久挂载 mount dev sdb xfsdump 5 将sdb设备挂载到 xfsdump 目录 编辑 etc fstab 文件 将 挂载的信息卸写
  • Jetson Orin NX上手使用(Linux系统的配置)

    先介绍手上拿到的orin nx模块 xff1a Jetson Orin NX 16GB 100TOPS的算力听起来顶呱呱 摸过一些jetson系列套件的我拿到手自信开搞 然后就自闭了 他好像跟以前的不是一个路子啊 xff08 以前摸过的有n
  • 51单片机的串口通信原理

    一 并行和串行通信方式 通信有并行和串行两种通信方式 在单片机系统以及现代单片机测控系统中信息交换多采用串行通信方式 1 并行通信方式 并行通信通常是将数据字节的各位用多条数据线同时进行传送 xff0c 每一位数据都需要一条传输线 如下图所