#循循渐进学51单片机#UART串口通信#not.10

1、能够理解UART串口通信的基本原理和通信过程。

1)串行通信的初步认识

并行通信:通信时数据的各个位同时传送,可以实现字节为单位通信,但是通信线占用资源太多,成本高。

串行通信:一次只能发送一位,要发送8次才能发送一个字节。

#循循渐进学51单片机#UART串口通信#not.10_第1张图片
2、通过IO口模拟UART串口通信把通信的底层操作原理弄明白。

首先是对通信的波特率的设定,在这里我们配置的波特率是9600,那么串口调试助手也得是9600。配置波特率的时候,我们用的是定时器T0的模式2。模式2中,不再是TH0代表高8位,TL0代表低8位了,而只有TL0在进行计数,当TL0溢出后,不仅仅会让TF0变1,而且还会将TH0中的内容重新自动装到TL0中。这样有一个好处,就是我们可以把想要的定时器初值提前存在TH0中,当TL0溢出后,TH0自动把初值就重新送入TL0了,全自动的,不需要程序中再给TL0重新赋值了,配置方式很简单,大家可以自己看下程序并且计算一下初值。

      波特率设置好以后,打开中断,然后等待接收串口调试助手下发的数据。接收数据的时候,首先要进行低电平检测while (PIN_RXD),若没有低电平则说明没有数据,一旦检测到低电平,就进入启动接收函数StartRXD()。接收函数最开始启动半个波特率周期,初学可能这里不是很明白。大家回头看一下,如果在数据位电平变化的时候去读取,因为时序上的误差以及信号稳定性的问题很容易读错数据,所以我们希望在信号最稳定的时候去读数据。除了信号变化的那个沿的位置外,其它位置都很稳定,那么我们现在就约定在信号中间位置去读取电平状态,这样能够保证我们读的一定是正确的。

      一旦读到了起始信号,我们就把当前状态设定成接收状态,并且打开定时器中断,第一次是半个周期进入中断后,对起始位进行二次判断一下,确认一下起始位是低电平,而不是一个干扰信号。以后每经过1/9600秒进入一次中断,并且把这个引脚的状态读到RxdBuf里边。等待接收完毕之后,我们再把这个RxdBuf加1,再通过TXD引脚发送出去,同样需要先发一位起始位,然后发8个数据位,再发结束位,发送完毕后,程序运行到while (PIN_RXD),等待第二轮信号接收的开始。

#include 

sbit PIN_RXD = P3^0;  //½ÓÊÕÒý½Å¶¨Òå
sbit PIN_TXD = P3^1;  //·¢ËÍÒý½Å¶¨Òå

bit RxdOrTxd = 0;  //ָʾµ±Ç°×´Ì¬Îª½ÓÊÕ»¹ÊÇ·¢ËÍ
bit RxdEnd = 0;    //½ÓÊÕ½áÊø±êÖ¾
bit TxdEnd = 0;    //·¢ËͽáÊø±êÖ¾
unsigned char RxdBuf = 0;  //½ÓÊÕ»º³åÆ÷
unsigned char TxdBuf = 0;  //·¢ËÍ»º³åÆ÷

void ConfigUART(unsigned int baud);
void StartTXD(unsigned char dat);
void StartRXD();

void main()
{
    EA = 1;   //¿ª×ÜÖжÏ
    ConfigUART(9600);  //ÅäÖò¨ÌØÂÊΪ9600
    
    while (1)
    {
        while (PIN_RXD);    //µÈ´ý½ÓÊÕÒý½Å³öÏֵ͵çƽ£¬¼´Æðʼλ
        StartRXD();         //Æô¶¯½ÓÊÕ
        while (!RxdEnd);    //µÈ´ý½ÓÊÕÍê³É
        StartTXD(RxdBuf+1); //½ÓÊÕµ½µÄÊý¾Ý+1ºó£¬·¢ËÍ»ØÈ¥
        while (!TxdEnd);    //µÈ´ý·¢ËÍÍê³É
    }
}
void ConfigUART(unsigned int baud)
{
	TMOD &= 0XF0;
	TMOD |= 0X02;
	TH0 = 256 -(11059200/12)/baud;
}
void   StartRXD()
{
	TL0 = 256 - ((256-TH0)>>1);
	ET0 = 1;
	TR0 = 1;
	RxdEnd = 0;
	RxdOrTxd = 0;
}
void StartTXD(unsigned char dat)
{
	RxdBuf = dat;
	TL0 = TH0;
	ET0 = 1;
	TR0 = 1;
	PIN_RXD = 0;
	TxdEnd = 0;
	RxdOrTxd = 1;
}
void InterruptTimer0() interrupt 1
{
	static unsigned char cnt = 0;
  if(RxdOrTxd)
	{
	 cnt++;
		if(cnt <= 8)
		{
		PIN_TXD = TxdBuf & 0x01;
		TxdBuf >>= 1;
		}
		else if(cnt ==9)
		{
		PIN_TXD = 1;
		}
		else
		{
		cnt = 0;
		TR0 = 1;
		TxdEnd = 1;			
		}
	}
	else
 {
 if(cnt == 0)
 {
     if(!PIN_RXD)
		 {
		 RxdEnd = 0;
			 cnt++;
		 }
		 else
			{
		  TR0 = 0;
		  }
 }
 else if(cnt <= 8)
 {
  RxdBuf >>= 1;
	 if(PIN_RXD)
	 {
       RxdBuf |= 0x80;
	 }
	 cnt++;
 }
 else
 {
            cnt = 0;
            TR0 = 0;       
            if (PIN_RXD)    
            {
                RxdEnd = 1; 
            }
 }
 }
}

3、学会通过配置寄存器,实现串口通信的基本操作过程。

UART模块介绍

      IO口模拟串口通信,让大家了解了串口通信的本质,但是我们的单片机程序却需要不停的检测扫描单片机IO口收到的数据,大量占用了单片机的运行时间。这时候就会有聪明人想了,其实我们并不是很关心通信的过程,我们只需要一个通信的结果,最终得到接收到的数据就行了。这样我们可以在单片机内部做一个硬件模块,让它自动接收数据,接收完了,通知我们一下就可以了,我们的51单片机内部就存在这样一个UART模块,要正确使用它,当然还得先把对应的特殊功能寄存器配置好。

      51单片机的UART串口的结构由串行口控制寄存器SCON、发送和接收电路三部分构成,先来了解一下串口控制寄存器SCON。如表11-1表11-2所示。

                                      SCON——串行控制寄存器的位分配(地址0x98、可位寻址)

7

6

5

4

3

2

1

0

符号

SM0

SM1

SM2

REN

TB8

RB8

TI

RI

复位值

0

0

0

0

0

0

0

0

    

                                                   SCON——串行控制寄存器的位描述

符号

描述

7

SM0

这两位共同决定了串口通信的模式0~模式3共4种模式。我们最常用的就是模式1,也就是SM0=0,SM1=1,下边我们重点就讲模式1,其它模式从略。

6

SM1

5

SM2

多机通信控制位(极少用),模式1直接清零。

4

REN

使能串行接收。由软件置位使能接收,软件清零则禁止接收。

3

TB8

模式2和3中要发送的第9位数据(很少用)。

2

RB8

模式2和3中接收到的第9位数据(很少用),模式1用来接收停止位。

1

TI

发送中断标志位,当发送电路发送到停止位的中间位置时,TI由硬件置1,必须通过软件清零。

0

RI

接收中断标志位,当接收电路接收到停止位的中间位置时,RI由硬件置1,必须通过软件清零。

  

      前边学了那么多寄存器的配置,相信SCON这个地方,对于大多数同学来说已经不是难点了,应该能看懂并且可以自己配置了。对于串口的四种模式,模式1是最常用的,就是我们前边提到的1位起始位,8位数据位和1位停止位。下面我们就详细介绍模式1的工作细节和使用方法,至于其它3种模式与此也是大同小异,真正遇到需要使用的时候大家再去查阅相关资料就行了。

      在我们使用IO口模拟串口通信的时候,串口的波特率是使用定时器T0的中断体现出来的。在硬件串口模块中,有一个专门的波特率发生器用来控制发送和接收数据的速度。对于STC89C52单片机来讲,这个波特率发生器只能由定时器T1或定时器T2产生,而不能由定时器T0产生,这和我们模拟的通信是完全不同的概念。

      如果用定时器2,需要配置额外的寄存器,默认是使用定时器1的,我们本章内容主要就使用定时器T1作为波特率发生器来讲解,方式1下的波特率发生器必须使用定时器T1的模式2,也就是自动重装载模式,定时器的重载值计算公式为:

      TH1 = TL1 = 256 - 晶振值/12 /2/16 /波特率 

     和波特率有关的还有一个寄存器,是一个电源管理寄存器PCON,他的最高位可以把波特率提高一倍,也就是如果写PCON |= 0x80以后,计算公式就成了:

    TH1 = TL1 = 256 - 晶振值/12 /16 /波特率

      公式中数字的含义这里解释一下,256是8位定时器的溢出值,也就是TL1的溢出值,晶振值在我们的开发板上就是11059200,12是说1个机器周期等于12个时钟周期,值得关注的是这个16,我们来重点说明。在IO口模拟串口通信接收数据的时候,采集的是这一位数据的中间位置,而实际上串口模块比我们模拟的要复杂和精确一些。他采取的方式是把一位信号采集16次,其中第7、8、9次取出来,这三次中其中两次如果是高电平,那么就认定这一位数据是1,如果两次是低电平,那么就认定这一位是0,这样一旦受到意外干扰读错一次数据,也依然可以保证最终数据的正确性。

      了解了串口采集模式,在这里要给大家留一个思考题。“晶振值/12/2/16/波特率”这个地方计算的时候,出现不能除尽,或者出现小数怎么办,允许出现多大的偏差?把这部分理解了,也就理解了我们的晶振为何使用11.0592M了。

      串口通信的发送和接收电路在物理上有2个名字相同的SBUF寄存器,它们的地址也都是0x99,但是一个用来做发送缓冲,一个用来做接收缓冲。意思就是说,有2个房间,两个房间的门牌号是一样的,其中一个只出人不进人,另外一个只进人不出人,这样的话,我们就可以实现UART的全双工通信,相互之间不会产生干扰。但是在逻辑上呢,我们每次只操作SBUF,单片机会自动根据对它执行的是“读”还是“写”操作来选择是接收SBUF还是发送SBUF,后边通过程序,我们就会彻底了解这个问题。

#循循渐进学51单片机#UART串口通信#not.10_第2张图片

#循循渐进学51单片机#UART串口通信#not.10_第3张图片

#include 
void ConfigUART(unsigned int baud);
void main()
{
		EA =1;
ConfigUART(9600);
	while(1);
	
}
void ConfigUART(unsigned int baud)
{
   SCON  = 0x50;
	TMOD &= 0x0F;
	TMOD |= 0x02;
	TH1 = 256 - (11059200/12/32)/baud;
	TH1 =TL0;
	ET1 = 0;
	TR1 = 1;
}
void InterruptUART() interrupt 4
{
   if(RI)
	{
	RI = 0;
		SBUF++;
	}
	if(TI)
	{
	TI = 0;
	}
}

4、了解字符和数据之间的转换依据和方法。

  我们用字符格式发送一个小写的a,返回一个十六进制的0x61,数码管上显示的也是61,ASCII码表里字符a对应十进制是97,等于十六进制的0x61;我们再用字符格式发送一个数字1,返回一个十六进制的0x31,数码管上显示的也是31,ASCII表里字符1对应的十进制是49,等于十六进制的0x31。这下大家就该清楚了:所谓的十六进制发送和十六进制接收,都是按字节数据的真实值进行的;而字符格式发送和字符格式接收,是按ASCII码表中字符形式进行的,但它实际上最终传输的还是一个字节数据。这个表格,当然不需要大家去记住,理解它,用的时候过来查就行了。

5、完成通过串口控制流水灯流动和停止的程序。

#include 

sbit ADDR3 = P1^3;
sbit ENLED = P1^4;

unsigned char code LedChar[] = { 
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[7] = {  
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
unsigned char T0RH = 0;  
unsigned char T0RL = 0; 
unsigned char RxdByte = 0;  
unsigned char flag200ms = 0; 
unsigned char flagLight = 1;

void ConfigTimer0(unsigned int ms);
void ConfigUART(unsigned int baud);
void FlowingLight();

void main()
{
	EA = 1;
	ADDR3 = 1;
	ENLED = 0;
  ConfigTimer0(1);
	ConfigUART(9600);
	while(1)
	{
	if(flagLight == 0)
	{
		LedBuff[6]= 0XFF;
	}
	else
	{
	if (flag200ms != 0)  
            {
                flag200ms = 0;
                FlowingLight();
            }
	}
	      LedBuff[0] = LedChar[RxdByte & 0x0F];
        LedBuff[1] = LedChar[RxdByte >> 4];
	}
	
}
void FlowingLight()
{
 static unsigned char dir = 0;
 static unsigned char shift = 0x01; 
	
	LedBuff[6] = ~shift;
	if(dir == 0)
	{
	  shift = shift << 1; 
		if(shift == 0x80)
		{
		dir = 1;
		}
	}
	else
	{
	 shift = shift >> 1;
		if(shift == 0x01)
		{
		dir = 0;
		}
	}
}
void ConfigTimer0(unsigned int ms)
{
    unsigned long tmp;  
    
    tmp = 11059200 / 12;     
    tmp = (tmp * ms) / 1000;  
    tmp = 65536 - tmp;        
    tmp = tmp + 13;       
    T0RH = (unsigned char)(tmp>>8);  
    T0RL = (unsigned char)tmp;
    TMOD &= 0xF0;   
    TMOD |= 0x01;  
    TH0 = T0RH;    
    TL0 = T0RL;
    ET0 = 1;        
    TR0 = 1;        
}
void ConfigUART(unsigned int baud)
{
	SCON = 0X50;
	TMOD &=  0X0F;
	TMOD |= 0X02;
	TH1 = (11059200/12/32)/baud;
	TL1 = TH1;
	ET1 = 0;
	ES = 1;
	TR1 = 1;
}
void LedScan()
{
    static unsigned char i = 0; 
    
    P0 = 0xFF;            
    P1 = (P1 & 0xF8) | i;  
    P0 = LedBuff[i];       
    if (i < 6)            
        i++;
    else
        i = 0;
}
void InterruptTimer0() interrupt 1
{
	static unsigned char tmr200ms = 0;

		TH0 = T0RH;
	  TL0 = T0RL;
	  LedScan();
	tmr200ms++;
	if(tmr200ms >= 200)
	{
	tmr200ms = 0;
  flag200ms = 1;
	}
}
void InterruptUART() interrupt 4
{
   if(RI)
	 {
	 RxdByte = SBUF;
	 SBUF = RxdByte;
		 RI = 0;
	
	flagLight = !flagLight;	 
	 }
	 if(TI)
	 {
	 TI = 0;
	 }
}


6、完成通过串口实现蜂鸣器鸣叫的程序。

#include 

sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
sbit BUZZ  = P1^6;

unsigned char code LedChar[] = { 
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[7] = {  
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
unsigned char T0RH = 0;  
unsigned char T0RL = 0; 
unsigned char RxdByte = 0;  
unsigned char flagBuzz = 0;

void ConfigTimer0(unsigned int ms);
void ConfigUART(unsigned int baud);

void main()
{
	EA = 1;
	ADDR3 = 1;
	ENLED = 0;
  ConfigTimer0(1);
	ConfigUART(9600);
	while(1)
	{
	      LedBuff[0] = LedChar[RxdByte & 0x0F];
        LedBuff[1] = LedChar[RxdByte >> 4];
	}
	
}

void ConfigTimer0(unsigned int ms)
{
    unsigned long tmp;  
    
    tmp = 11059200 / 12;     
    tmp = (tmp * ms) / 1000;  
    tmp = 65536 - tmp;        
    tmp = tmp + 13;       
    T0RH = (unsigned char)(tmp>>8);  
    T0RL = (unsigned char)tmp;
    TMOD &= 0xF0;   
    TMOD |= 0x01;  
    TH0 = T0RH;    
    TL0 = T0RL;
    ET0 = 1;        
    TR0 = 1;        
}
void ConfigUART(unsigned int baud)
{
	SCON = 0X50;
	TMOD &=  0X0F;
	TMOD |= 0X20;
	TH1 = (11059200/12/32)/baud;
	TL1 = TH1;
	ET1 = 0;
	ES = 1;
	TR1 = 1;
}
void LedScan()
{
    static unsigned char i = 0; 
    
    P0 = 0xFF;            
    P1 = (P1 & 0xF8) | i;  
    P0 = LedBuff[i];       
    if (i < 6)            
        i++;
    else
        i = 0;
}
void InterruptTimer0() interrupt 1
{
		TH0 = T0RH;
	  TL0 = T0RL;
	  LedScan();
	if(flagBuzz == 0)
	    BUZZ = 1;    
	else
			BUZZ = ~BUZZ;    
}
void InterruptUART() interrupt 4
{
   if(RI)
	 {
	 RxdByte = SBUF;
	 SBUF = RxdByte;
		 RI = 0;
	if(RxdByte == 'B')
	{
	 flagBuzz = !flagBuzz;
	}
	 }
	 if(TI)
	 {
	 TI = 0;
	 }
}

你可能感兴趣的:(51学习记录,51单片机,单片机,嵌入式硬件,笔记,学习方法)