p8串口
前言
上一篇介绍了定时器的使用,这篇我们来学习串口的知识,使用单片机和电脑作为串口通信的两个设备进行相互通信。
串口介绍
- TXD:transmit exchange data
- 单片机使用 TTL电平
常见通信接口及相关术语
51单片机的UART
- 四种工作模式
- 如果操作的是P3.1端口的寄存器,用的就是I/O口;如果操作的是串口的寄存器,就通过I/O口发送数据
- 检验位:9位相比于8位多了一位,多出来的一位可以用于校验,验证前面的数据是否正确
- 双方约定都使用奇校验,发0000 0011 1(奇校验就是数一下数据位中有几个1,现在是2个,后面补一个1,保证9位数据中1的个数是奇数);接收到0000 0011 1,也发现1的个数是奇数,这样的数据就是正确的;但是如果接收的是0000 0101 1,这样的错误是检测不出来的。
- 由上图,T1溢出率控制收发数据的速率
- SBUF在等号左边是发送,在等号右边是接收。
串口和中断系统及串口相关寄存器
8-1 串口向电脑发送数据
了解完串口的硬件知识,现在我们来配置串口相关的寄存器来实现串口向电脑发送数据的功能。
一定要结合手册和串口模式图来配置
- 首先,根据手册我们先配置控制寄存器SCON和PCON
- 串口工作模式为方式1,所以SM0=0、SM1=1、SM2=0
- REN是控制单片机接收数据,这里我们先暂时让REN=0
- TB8、RB8用不到给0
- TI、RI必须用软件复位
所以SCON=0100 0000=0x40
-
PCON我们暂时给0,后面计算波特率再配置。
-
IE,IPH,IP,不需要开启中断,与默认相同,不用配置
-
配置定时器时只能用定时器1,
TMOD &= 0x0F
;为高四位清零;串口需要用8位自动重装模式。
-
之前讲的是16位,用两个8位表示一个大的计数器:0-65535;缺点是进入中断时需要赋初值,会占用一定时间,所以精度不是特别高、
-
串口中需要更精准的,分成两个8位来实现自动重装,可以使用工具完成初始化。
-
所以TMOD &= 0x0F;TMOD |= 0x20;
-
定时器初值和波特率有关
STC-ISP软件也提供了波特率计算器功能,我们选择好对应的选项,就能自动生成代码配置寄存器
-
生成的代码SCON=0x50,因为软件默认REN=1,这里我们还是按一开始设置的SCON=0x40。
-
'AUXR’编译出错,因为这是高级单片机有的,我们单片机没有,所以删除这两行代码。
void Uart_Init(void) //4800bps@11.0592MHz
{
PCON |= 0x80; //使能波特率倍速位SMOD
SCON = 0x40; //8位数据,可变波特率
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xF4; //设定定时初值
TH1 = 0xF4; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
现在开始编写给电脑发送数据的代码,模块化代码生成UART.c和UART.h文件。
发送内部过程比较复杂,但是操作简单,只需要把数据写到SBUF即可。
UART.c
#include <REGX52.H>
#include "Delay.h"
/**
* @brief 串口初始化 4800bps@11.0592MHz
* @param 无
* @retval 无
*/
void UART_Init() //4800bps@11.0592MHz
{
SCON=0x40;
PCON|= 0x80;
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x20; //设置定时器模式
TL1 = 0xF4; //设定定时初值
TH1 = 0xF4; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
/**
* @brief 串口发送一个字节数据
* @param Byte 要发送的一个数据
* @retval 无
*/
void UART_SendByte(unsigned char Byte)
{
SBUF=Byte; //写入Byte
while(TI==0); //检测发送是否完成
TI=0; // 软件复位
}
UART.h
#ifndef __UART_H__
#define __UART_H__
void UART_Init();
void UART_SendByte(unsigned char Byte);
#endif
main.c
#include <REGX52.H>
#include "Delay.h"
#include "UART.h"
unsigned char Sec;
void main()
{
UART_Init();
while(1)
{
UART_SendByte(Sec);
Sec++;
Delay(1000);
}
}
编译测试下功能
打开串口助手,单片机向电脑发送数据以十六进制显示,复位后从0开始。
8-2 电脑通过串口控制LED
- 实现这部分功能,需要改造一下程序,接收电脑端的数据需要一个中断系统;因为不知道电脑什么时候发过来,也不能一直检测,所以我们利用中断,在电脑发过来的时候触发中断,在中断函数里面进行数据处理,把数据“拿”出来。
- 所以我们需要重新配置SCON=0x50,REN=1
- 并且启动中断,EA=1、ES=1
- 重新编写UART.c:
#include <REGX52.H>
/**
* @brief 串口初始化 4800bps@11.0592MHz
* @param 无
* @retval 无
*/
void UART_Init() //4800bps@11.0592MHz
{
PCON |= 0x80; //使能波特率倍速位SMOD
SCON = 0x50; //允许接受
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xF4; //设定定时初值
TH1 = 0xF4; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
EA=1; //使能中断
ES=1;
}
/**
* @brief 串口发送一个字节数据
* @param Byte 要发送的一个数据
* @retval 无
*/
void UART_SendByte(unsigned char Byte)
{
SBUF=Byte;
while(TI==0);
TI=0;
}
在主函数中编写中断函数,然后验证是否产生中断:
#include <REGX52.H>
#include "Delay.h"
#include "UART.h"
void main()
{
UART_Init();
while(1)
{
}
}
void UART_Routine() interrupt 4
{
P2=0x00; // 中断则会点亮LED
}
因为写的中断函数没有主函数,也没有其他子函数调用,如果没有中断进来,这个函数就不会被执行。
- 在串口助手发送区发送00,单片机LED全部点亮,验证进入了中断;但是不确定是发送中断还是接收中断,当然电脑发送数据,应该是接收中断,我们还需要判断一下。
因为接收数据的时候,也在发送数据,为了防止发送也触发中断,所以我们需要判断区分一下。
void UART_Routine() interrupt 4
{
if(RI==1) // 一旦进入中断就检测;如果是接收中断
{
P2=~SBUF;
RI=0;
}
}
- 如果电脑发送了数据,接收完成后会产生中断,在中断中,如果是接收中断,就把数据读出来放在P2口上并且把中断标志位清零。
注意:一个函数不能既在主函数中出现,又在中断函数中出现,会破坏原来的函数。
main.c代码
#include <REGX52.H>
#include "Delay.h"
#include "UART.h"
unsigned char Sec;
void main()
{
UART_Init();
while(1)
{
}
}
void UART_Routine() interrupt 4
{
if(RI==1)
{
P2=~SBUF;
UART_SendByte(SBUF);
RI=0;
}
}
加入串口中断函数的模板,用的时候加入主函数中。
/*串口中断函数模板
void UART_Routine() interrupt 4
{
if(RI==1)
{
RI=0;
}
}
*/
波特率的计算
8-1部分我们直接使用软件计算波特率:
TL1 = 0xF4; //设定定时初值
TH1 = 0xF4; //设定定时器重装值
若要设置波特率为4800,晶振频率为11.0592MHz,SMOD = 1,实际是怎么得到软件计算结果的呢?
-
波特率计算公式:
波特率 = 2^SMOD / (32 × T1溢出率)
-
T1溢出率计算公式:
T1溢出率 = 晶振频率 / (12 × (256 - TH1))
-
用到T1溢出率计算,自动重装配置TH1=0xF4,对应十进制是244,T1的溢出率= 11.0592MHZ/ (12 × (256 - 244))=0.0768,设置的波特率倍数(SMOD=1),波特率就是0.0768MHZ/16=0.0048MHz=4800HZ,误差=0.
数据显示模式
文本模式编码是看ASCII码表,也可以用单引号发字符
例如:UART_SendByte(0x41)和UART_SendByte(‘A’)是一样的。