当前位置:首页 > 编程笔记 > 正文
已解决

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

来自网友在路上 184884提问 提问时间:2023-09-23 19:47:23阅读次数: 84

最佳答案 问答题库848位专家为你答疑解惑

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

1)串行通信的初步认识

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

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


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 <reg52.h>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);  //ÅäÖò¨ÌØÂÊΪ9600while (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,后边通过程序,我们就会彻底了解这个问题。

#include <REGX52.H>
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 <reg52.h>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++;elsei = 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 <reg52.h>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++;elsei = 0;
}
void InterruptTimer0() interrupt 1
{TH0 = T0RH;TL0 = T0RL;LedScan();if(flagBuzz == 0)BUZZ = 1;    elseBUZZ = ~BUZZ;    
}
void InterruptUART() interrupt 4
{if(RI){RxdByte = SBUF;SBUF = RxdByte;RI = 0;if(RxdByte == 'B'){flagBuzz = !flagBuzz;}}if(TI){TI = 0;}
}

查看全文

99%的人还看了

猜你感兴趣

版权申明

本文"#循循渐进学51单片机#UART串口通信#not.10":http://eshow365.cn/6-12276-0.html 内容来自互联网,请自行判断内容的正确性。如有侵权请联系我们,立即删除!