现在来详细看一下寄存器,我们直接查看单片机手册。
SCON寄存器
先来说说SCON寄存器。
前一节我们提过,我们一般使用串口用的是模式1,即8位UART,这样我们就用不到校验位。从手册中可以看到,寄存器SCON中的SM0和SM1配置成01即可。
SM2寄存器明显用不到,因为我们没有用模式2和3.
REN寄存器控制接收串行,发送数据时候置0,接收数据时置1。
TB8和RB8同SM2,一样用不到。
TI就比较关键了。我们肯定会用到。从串口结构图中可以看到,TI是一个标志位,来判断发送是否结束。举个例子,发送数据就是全自动步枪,TI寄存器就是我们的枪栓。我们发送结束后,TI的值会自动置1,我们需要手动写程序在软件层面给TI置0,让它能够再次使用。
这里插一句,软件层面指的就是我们的代码工程,硬件层面就是我们的板子和电路。
那么RI和TI的原理是类似的,就不详细说了。我们在初始化配置的时候,一般两位都置0,让它能够直接使用。
发送数据时,代码如下:
void UART_Init()
{
SCON = 0x40;//0100 0000
}
接收数据时,代码如下:
void UART_Init()
{
SCON = 0x50;//010 0000
}
PCON寄存器
再来说说PCON寄存器。
SMOD选择波特率是否加倍。我们对晶振频率进行过分频,所以波特率是需要加倍的。
定时器寄存器
除了串口寄存器,我们还需要用到定时器,打开我们上一章定时器的.c文件(不用打开工程,用记事本方式以文本文件方式打开即可)
复制出定时器的初始化代码。
void Timer0Init(void) //1毫秒@12.000MHz
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1;
EA = 1;
PT0 = 0;
}
然而串口所用到的定时器是Timer1,和我们之前用的Timer0是不同的寄存器,所以我们需要修改定时器寄存器的代码。串口用的定时器模式也不是我们之前的16位定时器模式,而是8位自动重装载模式。(相比16位定时器模式,这个模式在计数满后,高八位的初值数据会自动转入低八位)
这里还是详细说一下吧,想简单说两句发现说不清楚16位定时器高八位和低八位都用来计时,所以计数范围会比较大,每次计数高八位和低八位都置初始值,然后开始计数,计数满了我们再手动赋初值。八位重装载模式只用低八位寄存器来计时,高八位里面存放的是我们的定时器初值,在低八位加满之后高八位存的数据会流入低八位,这样就不需要我们手动给计时器置初值了
首先在TMOD中我们需要打开定时器1,我们使用逻辑语言 &= 给TMOD赋值。这样就不会影响我们上一次对TMOD的操作了。
代码如下:
void Timer0Init(void) //1毫秒@12.000MHz
{
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x20; //设置定时器模式
TL0 = ; //设置定时初值
TH0 = ; //设置定时初值
}
软件生成初始化代码
初始值的计算太复杂了,我们还是用STC-ISP软件生成代码,不用再用手算了。 别的寄存器也通过软件来生成吧。
这里提一嘴,波特率倍速得勾选,上面讲PCON的时候说了原因了。不过实在懒得重新截图了。具体分析看下面小标题里的内容!!!
那么初始化函数里面的内容如下
void UartInit(void) //4800bps@12.000MHz
{
PCON &= 0x80; //波特率不倍速
SCON = 0x40; //8位数据,可变波特率
// AUXR &= 0xBF; //定时器1时钟为Fosc/12,即12T
// AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xF3; //设定定时初值
TH1 = 0xF3; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
波特率计算
现在来说一下如何计算TL和TH。
0xF3就是十进制的243,而定时器每隔256溢出一次。我们定时器重装值设为243,那么计数13次后定时器就溢出了(定时器那里讲过,忘记了可以翻看以前的博客)。
12M的晶振,在12T模式下,每隔1us计数一次(因为12分频了嘛,很好理解的对不对)。
那么定时器Timer1的溢出率就是1/(13us),即0.07692MHz,假如我们倍速波特率,那么SMOD的开关会走上面的路,上面的频率需要再除以16,进入接收控制器,即0.07692MHz/16=4807.69Hz,这就是我们所设置的波特率4800。、
同理,假如我们不勾选倍速波特率,SMOD开关会走下面除以2的路,相当于频率除以32。软件帮我们计算出来的TH和TL值是F9,即十进制249,即7us溢出一次,溢出率1/(7us)=0.14285MHz,再除以32,进入接收控制器,即0.14285MHz/32=4464.28Hz,此时误差就比较大了。
发送数据
我们需要将数据写进SBUF寄存器里,这样在配置好寄存器后,数据会直接被发送出。
void UART_SendByte(unsigned char Byte)
{
SBUF = Byte;//将数据写进SUBF中
while(TI==0);//发送完成标志位一旦变为1,说明数据发送成功了。接下来软件复位。
TI = 0;
}
主文件里面的内容如下(其实Delay和Timer0没必要导入,因为没用到):
#include <REGX52.H>
#include "Timer0.h"
#include "Delay.h"
void UartInit(void) //4800bps@12.000MHz
{
PCON |= 0x80; //使能波特率倍速位SMOD
SCON = 0x40; //8位数据,可变波特率
// AUXR &= 0xBF; //定时器1时钟为Fosc/12,即12T
// AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xF3; //设定定时初值
TH1 = 0xF3; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
void UART_SendByte(unsigned char Byte)
{
SBUF = Byte;//将数据写进SUBF中
while(TI==0);//发送完成标志位一旦变为1,说明数据发送成功了。接下来软件复位。
TI = 0;
}
void main()
{
UartInit();
UART_SendByte(0x11);
while(1)
{
}
}
烧录程序后,连续按下复位键,可以看到,串口不断接收到字符11。
当然,我们也可以把UART_SendByte函数写在while(1)里面,这样就能通过串口持续收到11。但是此时我们需要再发送数据之后加上一定的延时,不然因为晶振频率和波特率的误差,会导致接受数据发生错误。
串口模块化
现在我们通过实验验证了串口发送接收数据的可行性,那么按照前面学习的内容,就可以对串口进行模块化了。具体操作不细说,直接上内容(别忘了加上注释):
//UART.c
#include <REGX52.H>
/**
* @brief 串口初始化4800bps,@12.000MHz
* @param 无
* @retval 无
*/
void UartInit(void)
{
PCON |= 0x80; //使能波特率倍速位SMOD
SCON = 0x40; //8位数据,可变波特率
// AUXR &= 0xBF; //定时器1时钟为Fosc/12,即12T
// AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xF3; //设定定时初值
TH1 = 0xF3; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
/**
* @brief 串口发送一个字节数据
* @param Byte 要发送的一个字节数据
* @retval 无
*/
void UART_SendByte(unsigned char Byte)
{
SBUF = Byte;//将数据写进SUBF中
while(TI==0);//发送完成标志位一旦变为1,说明数据发送成功了。接下来软件复位。
TI = 0;
}
#ifndef __UART_H__
#define __UART_H__
void UartInit(void);
void UART_SendByte(unsigned char Byte);
#endif
此时在主文件内直接引用函数就可以正常使用了。
串口接收数据
刚才所讲的内容是关于串口向电脑发送数据。现在再来说一说串口如何接收电脑数据。电脑发送数据被串口收到时,我们不能直接处理,必须进入中断函数进行处理。否则会影响单片机正常工作。
先打开中断使能EA,然后ES置1,相当于打开中断。
只需要在最下面加两行代码。同时修改一下SCON,因为要接收数据,REN需要置1,上面讲SCON的时候提到过。
void UartInit(void) //4800bps@12.000MHz
{
PCON &= 0x80; //波特率不倍速
SCON = 0x50; //8位数据,可变波特率
// AUXR &= 0xBF; //定时器1时钟为Fosc/12,即12T
// AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xF3; //设定定时初值
TH1 = 0xF3; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
EA = 1;
ES = 1;
}
查询串口的中断次序号
我们可以这样写,当串口收到电脑发送的数据时,LED灯亮。那么中断服务子函数写法如下:
void UART_Routine() interrupt 4
{
P2 = 0x00;
}
写好之后,烧录程序,在STC-ISP上发送任意一个数据,可以看到,单片机上的所有LED都亮了。
#include <REGX52.H>
#include "Timer0.h"
#include "Delay.h"
#include "UART.h"
void main()
{
UartInit();
while(1)
{
}
}
void UART_Routine() interrupt 4
{
P2 = 0x00;
}
说明串口接收数据可行,现在我们可以发挥想象力,大展拳脚了。
电脑发送数据控制LED灯
这样写:
void UART_Routine() interrupt 4
{
if(RI == 1)
{
P2 = SBUF;
RI = 0;
}
}
因为TI和RI都有可能使程序进入中断,所以我们要用判断语句来确认是串口收到了数据。
SUBF中保存的是接收和发送的数据,我们可以直接提取出来进行处理。
同时类似于TI,我们在接收时也需要进行软件复位,在确认每次收到数据(即RI=1)后,让RI归零(RI=0)。
那么上面程序的效果,就是前四个LED灭,后四个LED亮。
注意,在中断函数里面的调用的函数,不能在主函数里面使用。因为假如主函数正在调用函数,进入中断后你再调用一次,主函数中的调用就会被打断,那么程序就会出错。
我们可以让单片机再通过串口,把接收到的数据返回给电脑,只需要用之前写好的语句,很简单。
void UART_Routine() interrupt 4
{
if(RI == 1)
{
P2 = SBUF;
UART_SendByte(SBUF);
RI = 0;
}
}
将接收中断模块化
#include <REGX52.H>
/**
* @brief 串口初始化4800bps,@12.000MHz
* @param 无
* @retval 无
*/
void UartInit(void)
{
PCON |= 0x80; //使能波特率倍速位SMOD
SCON = 0x50; //8位数据,可变波特率
// AUXR &= 0xBF; //定时器1时钟为Fosc/12,即12T
// AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xF3; //设定定时初值
TH1 = 0xF3; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
EA = 1;
ES = 1;
}
/**
* @brief 串口发送一个字节数据
* @param Byte 要发送的一个字节数据
* @retval 无
*/
void UART_SendByte(unsigned char Byte)
{
SBUF = Byte;//将数据写进SUBF中
while(TI==0);//发送完成标志位一旦变为1,说明数据发送成功了。接下来软件复位。
TI = 0;
}
/*串口中断函数模板
void UART_Routine() interrupt 4
{
if(RI == 1)
{
RI = 0;
}
}
*/
以模板的形式加入到我们的模块里,需要使用的时候,直接挪到主函数下面就可以了。它和主函数耦合性还是比较高的,可以直接使用。
数据显示模式
HEX模式/十六进制模式/二进制模式
以原始数据的形式显示
文本模式/字符模式
以原始数据编码后的形式显示
说人话
HEX模式就是进行ASCI编码后的数据,而文本模式,就是ASCI译码之后的结果,C语言大家应该了解过,可以对照图来验证一下。
同样也需要留意,SendByte函数里面的参数,如果是0x开头的十六进制,用文本模式和HEX模式发送和接收,结果是不一样的。挺简单的,各位自行验证。文章来源:https://www.toymoban.com/news/detail-471158.html
随便扯两句
其实写完上一篇笔记,就已经马不停蹄地开始写这一篇笔记。中间鸽了近两个月,当时以为处理完手头的事情,就可以继续用空闲时间学点东西,结果学了一半的内容就被课内的任务缠身,忙的不可开交,而且串口这一块知识点环环相扣,时隔两个月,很多东西都忘记了。处理完课内大作业,删无用文件时太激动了小手一抖把之前的keil工程文件都给删了,还好有之前的笔记,让我不至于从头开始去写以前的模块内容。这两天还发着低烧,不过还是硬挺着完成中断了这么久的内容。不能再拖了,越拖越难重新拾起来。文章来源地址https://www.toymoban.com/news/detail-471158.html
到了这里,关于51单片机串口的应用(单片机和电脑互发数据)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!