蓝桥杯单片机学习笔记:串口通信原理与应用详解
目录
一、原理部分
1、什么是串行通信
(1)并行通信与串行通信
(2)串行通信的制式
(3)串行通信的主要方式
2、配置串口
(1)SCON和PCON :串行口1的控制寄存器
(2)SBUF:串行口数据缓冲寄存器
(3)AUXR:辅助寄存器编辑
(4)ES、PS :与串行口1中断相关的寄存器
(5)波特率设置
3、串口框架编写
二、程序案例
一、原理部分
1、什么是串行通信
(1)并行通信与串行通信
微控制器与外部设备的数据通信,根据连线结构和传送方式的不同,可以分为两种:
并行通信和串行通信。
并行通信:数据的各位同时发送与接收,每个数据位使用一条导线,这种方式传输快,但是需要多条导线进行信号传输。
串行通信:数据一位一位地按照顺序发送与接收。这种方式仅需要一根导线,但是传输较慢
串行通信有SPI、IIC、UART等多种,最常见最通用的是UART,大多数情况下,串口通信指的是UART。
(2)串行通信的制式
串行通信的制式有:单工、半双工、全双工三种
单工: 单工制式下,通信线的一端为发送器,一端为接收器,数据只能按照一个固定的方向传送;半双工:半双工制式下,系统的每个通信设备都由一个发送器和一个接收器组成,因而数据能从A站传送到B站,也可以从B站传送到A站,但是不能同时在两个方向上传送;
全双工:全双工方式下,系统的每端都有发送器和接收器,可以同时发送和接收,即数据可以在两个方向上同时传送。
(3)串行通信的主要方式
串行通信的主要方式有两种:同步和异步
同步串行通信:使用同一个时钟,以数据块为单位传输数据
异步串行通信:每个设备有自己的时钟信号,通信双方的波特率(串口每秒钟传输的位数)需要保持一致,以字符为单位进行数据帧传送,依次传输一个帧。
2、配置串口
配置前需要了解一下相关寄存器
(1)SCON和PCON :串行口1的控制寄存器
SCON: 串行控制寄存器 (可位寻址)
SM0/FE:
当PCON寄存器中的SMODO/PCON.6位为1时,该位用于顿错误检测。当检测到一个无效停止位时,通过UART接收器设置该位。它必须由软件清零。
当PCON寄存器中的SMODO/PCON.6位为0时,该位和SM1一起指定串行通信的工作方式,如下表所示。
一般使用方式1,即SM0=0,SM1=1
SM2: 允许方式2或方式3多机通信控制位。暂时不用理,置0即可。
REN:介许/禁止串行接收控制位。
由软件置位REN,即REN=1为允许串行接收状态,可启动串行接收器RxD,开始接收信息。
软件复位REN,即REN=0,则禁止接收。
TB8、RB8:奇偶校验位,方式1不用用到,置0即可。
TI:发送中断请求标志位。
在方式0,当串行发送数据第8位结束时,由内部硬件自动置位.即TI=1,向主机请求中断,响应中断后TI必须用软件清零,即TI=0。
在其他方式中,则在停止位开始发送时由内部硬件置位,即TI=1,响应中断后TI必须用软件清零。
RI:接收中断请求标志位。在方式0,当串行接收到第8位结束时由内部硬件自动置位RI=1,向主机请求中断,响应中断后RI必须用软件清零,即RI=0。
在其他方式中,串行接收到停止位的中间时刻由内部硬件置位,即RI=1,向CPU发中断申请,响应中断后RI必须由软件清零
使用方式1,则需要REN置1,剩余置零即可,则加上前面的SM0=0,SM1=1,SCON应该设置为:SCON=0x50;
至于TI以及RI:
当一帧发送完成,内部硬件自动置位TI,即TI=1,请求中断处理;
当接收完一帧信息时,内部硬件自动置位RI,即RI=1,请求中断处理。
由于TI和RI以“或逻辑”关系向主机请求中断,所以主机响应中断时事先并不知道是TI还是RI请求的中断,必须在中断服务程序中查询TI和RI进行判别,然后分别处理。因此,两个中断请求标志位均不能由硬件自动置位,必须通过软件清0,否则将出现一次请求多次响应的错误。
PCON:电源控制寄存器 (不可位寻址)
SMOD:波特率选择位。
当用软件置位SMOD,即SMOD=1,则使串行通信方式1、2、3的波特率加倍;
SMOD=0,则各工作方式的波特率加倍。复位时SMOD=0。
这个寄存器只需要用到SMOD一位,默认置0;可以不用理。
(2)SBUF:串行口数据缓冲寄存器
STC15系列单片机的串行口1缓冲寄存器(SBUF)的地址是99H,实际是2个缓冲器,一个是
发送寄存器,一个是接收寄存器,在物理结构上是完全独立的,字节地址均为99H,通过读/写指令区区分:
串行发送时,CPU向SBUF写入数据,此时99H表示发送缓存SBUF。
串行接收时:CPU从SBUF读取数据,此时99H表示接收缓存SBUF。
数据发送:把数据扔进SBUF后,内核会自动将数据发送,内容发送完毕,TI标志位置1。数据接收:内核从串口接收到一个完整数据后,会将RI标志置1,用户用SBUF读取即可。
由于接收通道内设有输入移位寄存器和SBUF缓冲器,从而能使一帧接收完将数据由移位寄存器装入SBUF后,可立即开始接收下一帧信息,主机应在该顿接收结束前从SBUF缓冲器中将数据取走,否则前一帧数据将丢失。SBUF以并行方式送往内部数据总线。
(3)AUXR:辅助寄存器
T1x12: 定时器1速度控制位
0,定时器1是传统8051速度,12分频;
1,定时器1的速度是传统8051的12倍,不分频如果UART1/串口1用T1作为波特率发生器,则由T1x12决定UART1/串口是12T还是1T
S1ST2: 串口1(UART1) 选择定时器2作波特率发生器的控制位
0,选择定时器1作为串口1(UART1)的波特率发生器
1,选择定时器2作为串口1(UART1)的波特率发生器,此时定时器1得到释放,可以作独立定时器使用
AUXR需要用到的为以上两位,这里选择12分频,以及定时器1作为波特率发生器
则AUXR设置为:
AUXR=0x00;
头文件“reg52.h”没有定义AUXR,因此需要添加:
sfr AUXR=0x8e;
才可以使用AUXR寄存器。
(4)ES、PS :与串行口1中断相关的寄存器
IE: 中断允许寄存器 (可位寻址)
EA: CPU的总中断允许控制位
EA=1,CPU开放中断,
EA=0,CPU屏蔽所有的中断申请。EA的作用是使中断允许形成多级控制。即各中断源首先受EA控制:其次还受各中断源自己的中断允许控制位控制。
ES: 串行口中断允许位ES=1,允许串行口中断
ES=0,禁止串行口中断
则IE应设置为:
EA=1;
ES=1;
IP:中断优先级控制寄存器低 (可位寻址)
PS :串行口1中断优先级控制位。
当PS=0时,串行口1中断为最低优先级中断(优先级0)
当PS=1时,串行口1中断为最高优先级中断(优先级1)
(5)波特率设置
此外,还需要配置一下波特率,我们使用的是定时器1做串口波特率发生器,因而需要设置定时器1的TH1以及TL1(设置值可参考下标),以及定时器的工作模式(8位自动重装载模式),观看我前面写的文章——定时器原理及其应用,则定时器的配置形式为:
TMOD=0x20;
TH1=0xfd;TL1=0xfd;
TR1=1;
3、串口框架编写
串口设计使用中,一般需要编写三个函数:串口初始化函数、字节发送函数及串口中断服务函数
(1)初始化函数:
①配置工作模式,对TMOD寄存器编程,TMOD=0x20(8位自动重装载模式)
②计算计数初值,对THx和TLx寄存器进行赋值(波特率9600,设置THx=0xfd,TLx=0xfd)③启动定时器,即TR0或TR1置1
④设置SCON,为0x50⑤设置AUXR,为0x00
⑥使能串口中断,ES=1⑦使能总中断,即EA =1
(2)中断服务函数(串口1中断号为4):
①对接收还是发送进行判断,即RI以及TI是否为1
②若是接收,则将SBUF中的数据取走(定义变量接收),将RI清零
③相关逻辑处理
(3)字节发送函数:
①将数据赋给SBUF
②等待数据发送,此时TI=0,使用while等待至TI=1
③将TI清零
另外,特别注意需要在前面加上:
sfr AUXR=0x8e;
才可以使用AUXR。
二、程序案例
例子:采用8位的UART模式,波特率为9600BPS,建立数据传输通道,完成以下任务:
1、系统上电后关闭蜂鸣器继电器灯设备
2、初始化发送字符串“Welcom to FSZ system!”
3、上位机发送以下命令并产生相应响应
#include "reg52.h"
sfr AUXR=0x8e;//定义寄存器
/*****************************************************************
*@Function: SelectHc573 //
*@Description:锁存器选择 //
*@Input: channel:通道选择//
*@Output:无 //
*@Return: 无 //
*@Others: 无 //
/*****************************************************************/
void SelectHc573(unsigned char channel)
{
switch(channel)
{
case 4:
P2=P2&0x1f|0x80;
break;
case 5:
P2=P2&0x1f|0xa0;
break;
case 6:
P2=P2&0x1f|0xc0;
break;
case 7:
P2=P2&0x1f|0xe0;
break;
case 0:
P2=P2&0x1f|0x00;
break;
}
}
/*****************************************************************
*@Function:InitUart //
*@Description: 串口初始化 //
*@Input: 无//
*@Output:无 //
*@Return: 无 //
*@Others: 无 //
/*****************************************************************/
void InitUart()
{
TMOD=0x20;//设置定时器模式
TH1=0xfd;//设置定时器初始值
TL1=0xfd;
TR1=1;//使能时钟
SCON=0x50;//设置串口模式
AUXR=0x00;//配置AUXR
ES=1;//使能串口中断
EA=1;//使能总中断
}
unsigned char command=0x00;//用于接收串口的数据
/*****************************************************************
*@Function: ServiceUart //
*@Description:中断服务函数 //
*@Input:无 //
*@Output: 无//
*@Return: 无 //
*@Others: 无 //
/*****************************************************************/
void ServiceUart () interrupt 4
{
if(RI==1)//如果RI=1(接收到数据)
{
command=SBUF;//将接收的数据赋值给command
RI=0;//将接收标志位RI清零
}
}
/*****************************************************************
*@Function: SendByte //
*@Description:通过串口发送一个字节 //
*@Input:dat:发送的字节 //
*@Output:无 //
*@Return:无 //
*@Others:无 //
/*****************************************************************/
void SendByte(unsigned char dat)
{
SBUF=dat;//将dat数据赋给SBUF,将数据发送出去
while(TI==0);//等待数据发送
TI=0;//将发送标志位清零
}
/*****************************************************************
*@Function: SendString //
*@Description: 字符串发送函数 //
*@Input: *str:发送的字符串首地址//
*@Output:无 //
*@Return: 无 //
*@Others:无 //
/*****************************************************************/
void SendString(unsigned char *str)
{
while(*str!='\0')//当字符不为空时,继续发送
{
SendByte(*str++);//发送后指针str加1,指向下一个字节
}
}
/*****************************************************************
*@Function:Working //
*@Description: 对串口接收的数据进行处理 //
*@Input: 无//
*@Output:无 //
*@Return: 无 //
*@Others: 无 //
/*****************************************************************/
void Working()
{
if(command!=0x00)//如果command不为0x00(即接收到数据)
{
switch(command&0xf0)//对数据进行判断
{
case 0xa0://若为0xa0
P0=(P0|0x0f)&(~command|0xf0);//将command取反后取出后四位(将前四位置1),P0的低四位置1,两者与上,即保持P0的高四位,将command的低四位覆盖P0低四位
SelectHc573(4);//转换锁存器
command=0x00;//将command清零
break;
case 0xb0://若为0xb0
P0=(P0|0xf0)&(~command<<4|0x0f);//将command取反后左移四位,再取出前四位(将后四位置1),P0的前四位置1,两者与上,即保持P0的低四位,将command的高四位覆盖P0高四位
SelectHc573(4);//转换锁存器
command=0x00;//将command清零
break;
case 0xc0://若是0xc0
SendString("The system is runing\r\n");//发送字符串The system is runing\r\n
command=0x00;//将command清零
break;
}
}
}
/*****************************************************************
*@Function: InitSystem //
*@Description:初始化,关闭蜂鸣器,LED灯以及继电器 //
*@Input:无 //
*@Output:无 //
*@Return: 无 //
*@Others:无 //
/*****************************************************************/
void InitSystem()
{
SelectHc573(5);
P0=0x00;
SelectHc573(4);
P0=0xff;
}
void main()
{
InitSystem();
InitUart();
SendString("Welcom to FSZ system!\r\n");//发送字符串“Welcom to FSZ system!\r\n”
while(1)
{
Working();
}
}
蓝桥杯的学习笔记持续更新中~
要是文章有帮助的话,就点赞收藏关注一下啦!
感谢大家的观看
欢迎大家提出问题并指正~