目录
1.串口简介
1.1TXD和RXD
1.2通讯接口
1.3通信方式
1.4 51单片机的UART模式
2.串口配置
2.1寄存器简介
SCON寄存器配置
PCON配置
2.2代码配置串口
2.2.1 配置串口发送数据
2.2.2配置电脑向单片机发送数据点亮LED
1.串口简介
串口是一个应用十分广泛的通讯接口,它可以实现两个设备之间的相互通信,大大提高了单片机的硬件实力。前面讲述中断的时候顺便提过51单片机的串口中断,我们使用的51单片机使用的是UART(Universal Asynchronous Receiver Transmitter,通用异步收发器),可以实现单片机的串口通信。
1.1TXD和RXD
简单的两个设备之间的通信可以简化成下面的图:
VCC与GND的连接就不多说, 主要是为了供电,TXD和RXD需要解释一下:TXD可以展开成Transmit Exchange Data,即传输数据端;RXD可以展开成Receive Exchange Data,即接收数据端。也就是我们使用TXD输出数据,RXD接收数据,实现两个设备之间的相互通信。
1.2通讯接口
就像学校机房老式台式机就有接口这样一个东西:这就是我们所说的接口
我们设备之间的通信也是通过接口进行的,像这里列举出的常见的接口:
UART就是我们单片机上的通讯接口,I2C也是一个常见的接口,未来会接触很多。
此外,设备之间通信还有CAN、USB等.USB就是我们常用的一个简单易用的通讯工具,就像我们使用的U盘,USB数据线,很多都是USB通讯使用的。
1.3通信方式
上面表格里写了通信接口的通信方式,这里介绍一下这个通信方式的意思:
- 全双工:通信双方可以在同一时刻互相传输数据
- 半双工:通信双方可以互相传输数据,但必须分时复用一根数据线
- 单工:通信只能有一方发送到另一方,不能反向传输
1.全双工就类似有两根线 ,像这样:
因为有两个可以相互通信的通道,所以就可以进行两个设备之间的通信,并且可以在同一时刻进行相互通信
2.半双工就是类似只有一根线,但是却又可以进行两端的通信
因为只有一个通信的通道,所以两设备之间的通信不能在同一时间完成,需要一次进行一个单方向的通信,完成后才可以进行下一个通信。
3.单工就比较简单了,就是只能进行单方向的通信
就好比我们的电视机遥控器向电视发送信息,这就是单工。
- 异步:通信双方各自约定通信速率
- 同步:通信双方靠一根时钟线来约定通信速率
1.异步就是通信双方互相约定一个速率,让两边设备虽然运作方式不同,但是可以通过约定的规则进行采样获得数据
比如这里就是对同一个数据不同的采样方式,前一个得到的是1100,后一个得到的是10,再把这个数据变得更加复杂一点,我们使用不同的采样方式,得到的数据会完全不同,所以我们使用异步约定速率还是很重要的,并且一般异步的两个设备是可以不使用线连接的。
2.同步是双方使用一根时钟线直接约定速率,所以一般同步都是使用线连接的。这里就不多讲
- 总线:连接各个设备的数据传输线路(类似于一条马路,把路边各住户连接起来,使住户可以相互交流)
总线就是一种运输方式,这里只是做一个简单介绍,后面会详细介绍 。
就像是一个大马路连接了路边所有住户,所有设备可以通过这条总线进行相互通信。
1.4 51单片机的UART模式
STC89C52有1个UART
STC89C52的UART有四种工作模式:
- 模式0:同步移位寄存器
- 模式1:8位UART,波特率可变(常用)
- 模式2:9位UART,波特率固定
- 模式3:9位UART,波特率可变
-
-
波特率又是一个我们经常听到的东西,那么它是什么呢?其实就是一个约定的速率,就是我们串口通信的速率(发送和接收各数据位的间隔时间)
-
还有检验位是用于数据验证的。打开这个软件
-
打开
-
这里有几个校验位:就是用来检测数据的正确性的。
-
这里主要介绍最常用的两个:奇校验和偶校验
-
奇校验:
-
给定一串二进制数据如1001 1100,设置该数据的1的个数为奇数个,如果原本的数据中1的个数就是奇数,那么设置校验位为0,否则设置为1,这里的1001 1100就可以把校验位设置为1,然后再把传输后的数据进行同样的操作,比较校验位是否相同,如果不相同则说传输数据出错了。
-
-
偶校验:
-
和奇校验类似,控制二进制数据中1的个数为偶数个,设置校验位。然后再在传输数据之后设置校验位。
-
很显而易见的是,奇校验和偶校验是可以一定程度上检验数据的准确性的,但是还是有很多情况是无法检测错误的,比如1111 0011变成1100 0011,这样我们的奇校验和偶校验都无法检验出来。
-
-
-
-
停止位用于数据帧的间隔,即数据之间有间隔,我们使用停止位来占用这部分位。
8位数据和9位数据格式的区别就是9位数据可以用来设置一位为校验位。
2.串口配置
2.1寄存器简介
我们这里配置最常见的串口模式——模式1
这是串口模式配置图,这里有一个很重要的寄存器,就是SBUF寄存器,也叫串口数据缓存寄存器,物理上是两个独立的寄存器,但占用相同的地址。写操作时,写入的是发送寄存器,读操作时,读出的是接收寄存器
最左边的就是总线,两个不同意义上的SBUF寄存器使用一个总线连接,从输入端进入就会从输出端出。
其中的这部分是使用定时器1实现的,主要作用就是控制波特率,也就是我们的数据收发速率。
有了前面配置定时器0的经验,现在我们来配置这个串口的中断也是比较简单的,还是配置一下寄存器和使能还有中断优先级。所以我们来直接看寄存器表格:
首先来介绍一下SCON寄存器:
SCON 还是可位寻址,所以我们还是可以直接一位一位的配置这个寄存器。但是我们一般都是直接配置SCON的,如果想要一个一个配也是可以的。
再来看看PCON寄存器:
这个寄存器本来是用来控制电源的,但是这里多了两个控制模式的位SMOD和SMOD0,原因就是SCON寄存器的位满了,而中国PCON还有几个空闲的位,所以干脆就直接把这两个模式控制的就放在PCON里了。
SCON寄存器配置
我们一般使用的是方式1,所以这里我们要把SM0设为0,SM1设为1。并且,我们需要SM0为方式判断的位,我们还要把PCON中的第六位置为0。
REN即允许/禁止串口接收数据,我们需要使用软件置位,我们需要使用接收数据就置REN为1,否则我们就置REN为0.
TI和RI我们在前面原理图的地方有见到过:
TI和RI任意一个为1,就会导致跳转到中断函数,其中TI为发送中断请求标志位,RI为接收中断请求标志位。我们需要把这两个在初始时都标为0,然后一般由硬件置为1,当中断启用的时候,我们就必须把它们再次在软件中置为0.
这三个我们是不需要管的,这三个是和方式2和方式3相关联,我们使用的是方式1,所以我们不配置,直接置为0都可以。
总的来说,如果我们只需要串口发送数据,我们只要把SCON中的SM1置为1,其他的置为0就可以了,这样我们就可以直接配置SCON = 0x40;或者是分开来。如果我们需要串口接收数据,就还要加一个REN = 1,即SCON = 0x50.
PCON配置
上面讲到说我们的SCON中需要SM0和SM1共同配置我们的串口运作的方式,而不是用来检测错误,我们就需要把PCON中的第六位置为0.
所以我们就要把SMOD0 = 0先设好。
而PCON的第七位SMOD是用来设置方式123的波特率是否加倍:
这一点我们在原理图上也可以看到:
我们让SMOD = 0,就说明我们的波特率需要除以2,也就是我们说的“不加倍”,然后SMOD=1就说明我们的波特率不用除以2,也就是要加倍。
最后还有几个寄存器就简要介绍一下:
SADEN (Slave Address Enable):SADEN是一个特殊功能寄存器,用于启用或禁用I2C总线中从设备的地址匹配。当SADEN为1时,从设备的地址将参与地址匹配过程;当SADEN为0时,该从设备的地址将被忽略。
SADDR (Slave Address):SADDR是用于设置51单片机作为I2C从设备时的从设备地址。通过配置SADDR,可以将从设备的地址与主设备进行匹配,以便进行通信。SADDR的值可以是7位或10位,取决于所使用的I2C工作模式。
SADEN和SADDR都是用于配置51单片机作为I2C从设备时的地址匹配功能。SADEN用于启用或禁用地址匹配,而SADDR用于设置从设备的地址。这些功能使得51单片机能够与其他I2C设备进行通信。
然后就是老生常谈的IE使能位
这里我们只要把EA和ES单独拿出来配置就好了,两个为0就禁止中断,两个为1就开放中断,EA主要是针对CPU的中断允许控制,ES则是针对串口的中断允许控制只要我们需要开放中断系统,我们就要把这两个都设置为1.
最后就是IP/IPH配置中断优先级,我们只有一个中断的时候就不用管,不用配置都可以,需要的时候按照下面配置一下就好了:
2.2代码配置串口
2.2.1 配置串口发送数据
我们先根据上面的介绍,配置一个简单的单片机串口向电脑端发送数据的代码。
首先,我们讲过,我们如果串口只要发送数据,我们就只要配置SCON为0x40,然后我们的PCON中波特率要加倍就把SMOD配置为1,这里我们就不加倍配置为0,然后SMOD0是控制SCON的SM0和SM1对应模式还是检验的前提,我们需要使用SM0和SM1把串口配置为模式1,所以我们的SMOD0必须为0,至于PCON里的其他配置暂时我们先不考虑,我们一会会通过软件生成
就有:
SCON = 0x40;
这里我们只要单片机向电脑发送数据,所以我们不需要使用中断,但是我们发送数据需要一定的波特率即发送的速率,这样的速率需要很精确的时钟计时,否则会发生数据传输出错的严重失误。
我们在这里就使用定时器实现严格的时序控制,从而确保串口通信的稳定性和可靠性。并且我们需要更加精确的定时器,所以我们这里选择定时器1并且使用8位自动重载实现更加精确的计时。
但是波特率的配置时比较复杂的,所以我们使用软件工具:
像这样实现了我们的配置之后,我们就可以把代码复制出来使用了。一般来说,波特率设置越高误差越大,当然这个针对其他频率的51单片机而言的,我们的这个11.0592MHz是非常精确的一个频率,所以我们使用9600就可以了,其他频率的单片机比如12MHz的就需要使用4800在点击波特率加倍来使得这个误差减小,我们的11.0592就直接选9600就好了。
生成代码:
void UartInit(void) //9600bps@11.0592MHz
{
PCON &= 0x7F; //波特率不倍速
SCON = 0x50; //8位数据,可变波特率
AUXR &= 0xBF; //定时器1时钟为Fosc/12,即12T
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xFD; //设定定时初值
TH1 = 0xFD; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
同样的,这里有的东西是需要更改的比如这个AUXR是更高级的单片机寄存器,我们就可以删掉,因为我们单片机没有这个东西,SCON可以配置为0x40,取决于自己的需求。
更改之后代码为:
void UartInit(void) //9600bps@11.0592MHz
{
PCON &= 0x7F; //波特率不倍速
SCON = 0x40; //8位数据,可变波特率
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xFD; //设定定时初值
TH1 = 0xFD; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
这里我们设置ET1 = 0是因为我们实现单片机发送数据只需要定时器1作为一个时钟计算波特率,而不需要定时器1实现中断,所以就把它置为0禁止定时器1中断。
配置好初始化函数,我们就再来配置一个发送数据的子函数,使用这个函数就可以在TI发送标志位被置为1的时候发送数据,并且在函数内把它重新置为0。发送数据要我们把数据发送到SBUF寄存器中。这样一个简单的发送数据的函数就写好了:
void SendByte(unsigned char buffer)
{
SBUF = buffer;
while(TI== 0);//标志位为0时停留在函数内部
TI = 0;//在此处TI变为1,我们需要在函数中将其置为0
}
注意,我们这里需要发送的数据应该是个8位的二进制,又因为我们在单片机中二级制无法直接表示,所以我们转化成十六进制再发送到SBUF中。
#include <REGX52.H>
void UartInit(void)
{
PCON &= 0x7F;
SCON = 0x50;
TMOD &= 0x0F;
TMOD |= 0x20;
TL1 = 0xFD;
TH1 = 0xFD;
ET1 = 0;
TR1 = 1;
}
void SendByte(unsigned char buffer)
{
SBUF = buffer;
while(TI== 0);
TI = 0;
}
void main()
{
UartInit();
SendByte(0x12);
while(1)
{
}
}
这里就是一个发送0x12的程序,我们这里没有把发送数据的子函数放在while循环中是因为我们把它放在while循环中就会导致一次发太多数据,不好观察,而且会出错,所以我们把它放在while循环前面,并且使用按下单片机上的复位按键来重置发送数据。
然后打开串口调试小助手,看一下发送的是不是对的。
STC-ISP在这里,要注意这些需要注意的东西,而且使用前串口是关闭的,需要我们手动打开串口,这个串口就是我们用来下载程序的CH340驱动的接口。
PZ-ISP就注意这里:
大部分的都是一样的,只是两个软件不能同时使用,所以需要注意一下
注意:到这里看一下是不是发送的数据都是想要的值,如果按下复位键还是没有显示,检查一下是不是开启的是字符发送,需要改成HEX才可以看的到,或者是串口没有打开。这里如果显示的和自己的不太一样,检查一下波特率设置是否正确,如果还是不一样,建议重新生成代码,并且检查是不是选择的是定时器1的8位自动重载模式。
如果想要把发送的函数放在while循环中,可能会出现发送的数据不对的情况,这个可能是因为发送的速度太快,可以在函数后面加个Delay函数试试。
2.2.2配置电脑向单片机发送数据点亮LED
还是按照上面的流程,我们直接在工具里生成代码:这里把多余的寄存器AUXR删除,其他的就不要变了,SCON保持0x50
或者是直接把前面的代码复制下来,直接把SCON改成0x50也可以
void UartInit(void) //9600bps@11.0592MHz
{
PCON &= 0x7F; //波特率不倍速
SCON = 0x50; //8位数据,可变波特率
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xFD; //设定定时初值
TH1 = 0xFD; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
然后这里我们还需要配置串口中断,注意:是串口的中断而不是定时器的中断,即我们的电脑向单片机发送数据之后,单片机接收到这个串口数据的那一时刻,就处理这个信息让这个LED点亮,使用串口的中断会更好实现一点。
主要是这里几个寄存器,IE使能中,我们只要设置EA和ES为1允许中断即可。
于是就写好了初始化函数:
void UartInit(void)
{
PCON &= 0x7F;
SCON = 0x50;
TMOD &= 0x0F;
TMOD |= 0x20;
TL1 = 0xFD;
TH1 = 0xFD;
ET1 = 0;
TR1 = 1;
EA = 1;
ES = 1;
}
然后是中断函数:
查询中断号,我们可以知道我们要使用的是4号中断。
开始配置函数:
void UART_Routine() interrupt 4
{
if(RI == 1)
{
P2 = SBUF;
RI = 0;
}
}
然后我们使用电脑端发送数据就可以控制我们的单片机LED了。这里注意我们要控制这个RI的取值在为1的时候才算接收到数据,之后就要把它重新置为0