介绍
我们软件里自带有串口助手 ,一定要注意串口必须和我们电脑连接单片机的串口相同
硬件电路
电平标准
接口以及引脚定义
RTS、CTS,DRS--->流控制
常见通信接口比较
相关术语
单片机中的UART
模式0:同步移位寄存器
模式1:8位UART,波特率可变(常用)
模式2:9位UART,波特率固定
模式3:9位UART,波特率可变
串口参数及时序图
从低位开始发
串口模式图
•SBUF:串口数据缓存寄存器,物理上是两个独立的寄存器,但占用相同的地址。写操作时,写入的是发送寄存器,读操作时,读出的是接收寄存器
串口和中断系统
串口相关寄存器
PCON的前两个寄存器是和串口有关的
实现通过串口向计算机发送数据
要使用到Delay.c
先了解每一个寄存器所对应的功能(对照着手册),再进行配置:
SM0:我们使用的是八位寄存器,不需要帧错误检测, 不需要管;
SM0、SM1:我们需要用到常用的方式1,则SM0=0,SM1=1;
SM2:不用管;
REN:需要单片机接受数据时给1,不需要就给0;
TB8、RB8:无关,不用管;
TI、RI:中断标志位,给0;
则SM0,SM1,REN,TB8,RB8,Ti,RI=0100 0000=0x40
SCON = 0x40;
PCON = 0;
此过程不需要配置中断,则不管它,默认是不开启
选择波特率
需要参考计时器模块的代码
TMOD &= 0xF0;
TMOD |= 0x01;
TL0 = 0x66;
TH0 = 0xFC;
TF0 = 0;
TR0 = 1;
ET0 = 1;
EA = 1;
PT0 = 0;
串口只能使用定时器1,但是代码是配置的定时器0,所以需要稍加修改
而且串口传输速度很快,不能用先前的16位定时器模式(0~65535),得用更精准的双8位自动重装(0~255)
则M1、M0分别给1、0 ,则:
TMOD &= 0x0F;
TMOD |= 0x20;
给TH0和TL0赋初值需要计算波特率,用到软件STC,设置如下
得到:
void UartInit(void) //4800bps@11.0592MHz
{
PCON |= 0x80; //使能波特率倍速位SMOD
SCON = 0x50; //8位数据,可变波特率
AUXR &= 0xBF; //定时器1时钟为Fosc/12,即12T
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xF4; //设定定时初值
TH1 = 0xF4; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
上下对比: 上(AUXR也需要删掉,因为SCT系列没有这个功能)
void UART_Init(void)
{
SCON = 0x40;
PCON =0; //赋予了0x80
TMOD &= 0xF0; //0x0F
TMOD |= 0x01; //0x20,设置定时器模式
TL0 = 0x66; //设定定时初值,计算波特率得来,0xF3
TH0 = 0xFC; //0xF3
TF0 = 0; //禁止定时器1中断,设为1且这句话以后的都删去(设定中断器的语句)
TR0 = 1;
ET0 = 1;
EA = 1;
PT0 = 0;
}
得最终结果
void UART_Init(void)//4800bps@11.0592MHz
{
SCON = 0x40;
PCON |= 0x80;
TMOD &= 0x0F;
TMOD |= 0x20; //给定时器1设为8位自动重装方式
TL1 = 0xF4; //设定定时初值
TH1 = 0xF4; //设定定时器重装值
ET1 = 0; //禁止计时器1中断
TR1 = 1; //启动定时器1
}
void main(void)
{
UART_Init();//初始化串口和定时器1
while(1)
{
}
}
此时,下载到单片机中是没有任何现象的
需要建立一个发送字节的函数
void UART_SendByte(unsigned char Byte)
{
SBUF = Byte;
while(TI==0);//检测是否完成发送
TI = 0; //发送完成后立即复位
}
整体:
#include <REGX52.H>
#include "Delay.h"
void UART_Init(void)//4800bps@11.0592MHz
{
SCON = 0x40;
PCON |= 0x80;
TMOD &= 0x0F;
TMOD |= 0x20;
TL1 = 0xF4;
TH1 = 0xF4;
ET1 = 0; //½ûÖ¹¶¨Ê±Æ÷1ÖжÏ
TR1 = 1; //Æô¶¯¶¨Ê±Æ÷1
}
void UART_SendByte(unsigned char Byte)
{
SBUF = Byte;
while(TI==0);//¼ì²âÊÇ·ñÍê³É·¢ËÍ
TI = 0; //·¢ËÍÍê³ÉºóÁ¢Âí¸´Î»
}
void main(void)
{
UART_Init();
UART_SendByte(0x11);
while(1)
{
}
}
编译后得:
设置:打开串口,波特率:4800;校验位:无校验;停止位:1位。
然后连接开发板,每按一次复位键都会传输一次Byte给开发板。
注意:
当我们这么写代码时:
void main(void)
{
UART_Init();
while(1)
{
UART_SendByte(0x66);
}
}
然后打开串口,会出现BUG
并不是66,而是96,这是因为晶振的误差,可以通过延时来解决问题
void main(void)
{
UART_Init();
while(1)
{
UART_SendByte(0x66);
Delay(1);
}
}
每隔一秒发送一个递增的数
UART.c
#include <REGX52.H>
/**
* @brief ´®¿Ú³õʼ»¯
* @param ÎÞ
* @retval ÎÞ
*/
void UART_Init(void)//4800bps@11.0592MHz
{
SCON = 0x40;
PCON |= 0x80;
TMOD &= 0x0F;
TMOD |= 0x20;
TL1 = 0xF4;
TH1 = 0xF4;
ET1 = 0; //½ûÖ¹¶¨Ê±Æ÷1ÖжÏ
TR1 = 1; //Æô¶¯¶¨Ê±Æ÷1
}
/**
* @brief ´®¿Ú·¢ËÍÒ»¸ö×Ö½Ú
* @param ×Ö½Ú
* @retval ÎÞ
*/
void UART_SendByte(unsigned char Byte)
{
SBUF = Byte;
while(TI==0);//¼ì²âÊÇ·ñÍê³É·¢ËÍ
TI = 0; //·¢ËÍÍê³ÉºóÁ¢Âí¸´Î»
}
main.c
#include <REGX52.H>
#include "Delay.h"
#include "UART.h"
unsigned char Sec = 0;
void main(void)
{
UART_Init();
while(1)
{
UART_SendByte(Sec);
Sec++;
Delay(1000);
}
}
按下复位键可以从0开始
电脑通过串口控制LED
复制UART的头文件和c文件
接受电脑数据需要中断器判断,当接受到数据后,申请中断,检测出数据,所以要打开串口的中断器,并不是打开定时器1的中断器。
因为要用电脑送给单片机,所以REN要设置为1,
其他不变,则SM0,SM1,REN,TB8,RB8,Ti,RI=0101 0000=0x50
SCON = 0x50;
中断器设置:
EA = 1;ES = 1;
根据中断查询次序号(下图)
串口中断号是4;则可得中断服务子函数:
void UART_Routine() interrupt 4
{
}
最终我们可得修改后的UART.c
#include <REGX52.H>
void UART_Init(void)//4800bps@11.0592MHz
{
SCON = 0x50;
PCON |= 0x80;
TMOD &= 0x0F;
TMOD |= 0x20;
TL1 = 0xF4;
TH1 = 0xF4;
//禁止定时器1中断
ET1 = 0;
//启动定时器1
TR1 = 1;
//启动串口中断器
EA = 1;
ES = 1;
}
void UART_SendByte(unsigned char Byte)
{
SBUF = Byte;
while(TI==0);
TI = 0;
}
main.c
#include <REGX52.H>
#include "Delay.h"
#include "UART.h"
unsigned char Sec = 0;
void main(void)
{
UART_Init();
while(1)
{
}
}
void UART_Rountine() interrupt 4
{
}
我们还可以编写一个建议代码来验证
main.c:
#include <REGX52.H>
#include "Delay.h"
#include "UART.h"
unsigned char Sec = 0;
void main(void)
{
UART_Init();
while(1)
{
}
}
void UART_Rountine() interrupt 4
{
P2 = 0x00;
}
随机发送一个数据,
会使LED灯全部亮起
但是这不能证明是串口接收了数据才使LED亮起,所以要加一个判断(因为也有可能是串口发送数据导致中断,因为发送中断标志位和接收中断标志位是使用同一个通道的)
仅对主函数.c文件中的中断函数做修改
void UART_Rountine() interrupt 4
{
if(RI==1)
{
P2 = SBUF;//SBUF是串口接收到的数据
RI = 0;//让串口还能继续接受数据
}
}
当我们发送0xf0时
可得结果:
因为有些板子LED方向不一样,可以给P2加上取反符号。
再继续实现把数据发送到板子中,这就要运用到之前所编写的UART_SendByte()函数,
#include <REGX52.H>
#include "Delay.h"
#include "UART.h"
unsigned char Sec = 0;
void main(void)
{
UART_Init();
while(1)
{
}
}
void UART_Rountine() interrupt 4
{
if(RI==1)
{
P2 = ~SBUF;
UART_SendByte(SBUF);
RI = 0;
}
}
特别注意:UART_SendByte()函数不能同时出现在中断函数和主函数中(函数的重录),这会导致一些BUG。
正常结果如图
最终结果:
UART.c
#include <REGX52.H>
void UART_Init(void)//4800bps@11.0592MHz
{
SCON = 0x50;
PCON |= 0x80;
TMOD &= 0x0F;
TMOD |= 0x20;
TL1 = 0xF4;
TH1 = 0xF4;
ET1 = 0; //½ûÖ¹¶¨Ê±Æ÷1ÖжÏ
TR1 = 1; //Æô¶¯¶¨Ê±Æ÷1
EA = 1;
ES = 1; //Æô¶¯´®¿ÚÖжÏÆ÷
}
void UART_SendByte(unsigned char Byte)
{
SBUF = Byte;
while(TI==0);//¼ì²âÊÇ·ñÍê³É·¢ËÍ
TI = 0; //·¢ËÍÍê³ÉºóÁ¢Âí¸´Î»
}
/**
* @brief 串口中断函数模板
* @param
* @retval
*/
/*void UART_Rountine() interrupt 4
{
if(RI==1)
{
RI = 0;
}
}*/
main.c
#include <REGX52.H>
#include "Delay.h"
#include "UART.h"
unsigned char Sec = 0;
void main(void)
{
UART_Init();
while(1)
{
}
}
void UART_Rountine() interrupt 4
{
if(RI==1)
{
P2 = SBUF;
UART_SendByte(SBUF);
RI = 0;
}
}
拓展:
波特率的计算(12MHz)
TL1 = 0xF3 = 243(十进制),而计数器(8位重装模式)每到256就溢出一次
256 - 243 = 13,即可得计数器每记13个数就溢出一次
即13us,即T1溢出频率即为1/13us = 0.07692MHz
因为我们设置SMOD=1,
到发送控制器T1只用/16,0.07692 / 16 =0.00480769MHz = 4807.69Hz;
当SMOD=0时,波特率=4807.69/2;
数据显示模式
编码,即ASCII码
当我们HEX模式给串口输入0x30时,HEX模式下接手即为30
HEX模式发送30时,文本模式接收则会为0
这其实是对照ASCII码的