最近学习串口通信确实有点复杂,设计到定时器/计数器、中断、通信模式等,我花了3天时间,终于理解了2个小时的视频内容。。。。但好在理解了。下面来看看单片机中的AURT串口通信部分。
如果看详解,直接跳到串口模式图开始看。
基础介绍
硬件电路
电平标准
UART串口通信模式
串口参数及时序图
串口模式图
建议开两个流量器窗口,跟着配置看。
第一步:配置串口、定时器、中断
- 配置SCON串口工作模式及PCON中的SMOD波特率加倍
- 配置TMOD定时器/计数器8位重装载模式,此时需要配置TL1和TH1,及定时器的低8位TL1定时器初值和高8位TH1重装值。按道理讲应该一样。
- 配置禁止定时器中断(ET1=0; 我测试了1和0都没关系。。)。设置TR1=1,允许开始计数。
- 允许串口中断和所有中断ES=1;及EA=1;
第二步:单片机通过串口发送信息
1 将数据写入SBUF
- 检测TI请求发送中断标志位,当标志位由非中断状态0变为中断状态1时,将TI重置为0
第三步:通过串口向单片机发送消息
- 这里是在中断中收到串口数据,直接编写中断服务函数,处理数据即可
c
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
void UART_Init();
void UART_SendByte(unsigned char byte);
/**
目的:
在串口中向电脑发送数据
原理:
查看串口模式图中所示,数据总线上接有两个独立的串口数据缓存寄存器,物理上是两个独立的寄存器,占用相同的地址。
写入的是发送寄存器,读操作时,读出的是接受寄存器。
在发送数据时,数据写入到SBUF中,数据流过控制门,由波特率相关元件控制传输速率。
T1溢出率指的是
采用STC89C52单片机的UART串口,使用模式1(8位UART,波特率可变(常用))向电脑发送数据
电脑端采用STC-ISP程序中的串口工具,接受单片机传输的数据即可。
步骤:
*/
void main()
{
unsigned char Sec;
UART_Init();
while(1) {
// UART_SendByte(Sec);
// Sec++;
// Delay(1000);
}
}
/**
* @brief 串口消息发送
* @param 1字节
* @retval 无
*/
void UART_SendByte(unsigned char byte)
{
// 发送数据到SBUF, 由硬件控制发送. 这里如果SBUF在左边,表示数据送入SBUF,向外发送数据;SBUF在右边,表示从SBUF读取数据。
SBUF = byte;
/*
通过不停检测TI == 0, 来判断硬件是否已经将TI置为1,即发送请求中断,如果跳出while循环,表示TI=1,此时,将TI重新设置为0(我理解发送请求中断处理结束)即可。
当向外部串口写入数据时, 在发送数据结束时, 硬件会将TI自动置位为1,表示请求中断, 需要手动设置TI = 0;
*/
while(TI == 0);
TI = 0;
}
// 串口初始化
void UART_Init()
{
// 配置串口
/*
配置SCON串行控制寄存器(可位寻址).
B7 B6 B5 B4 B3 B2 B1 B0
SM0/FE SM1 SM2 REN TB8 RB8 TI RI
0 1 0 0 0 0 0 0
当PCON寄存器中的SMOD0/PCON.6位为1, 即SMOD0为1, 代表该位用于错误检测. 为0时, 和SM1一起指定串行通信的工作方式
SM0和SM1用于确定串口工作方式. 直接查手册即可.
REN: 允许/禁止串行接受控制位. REN=1, 表示允许串行接受状态, 可启动串行接收器RxD,开始接受信息. 软件复位REN, 即REN=0,则禁止接受。
T1: 发送中断请求标志位. 数据发送结束后, 会向主机请求中断, 硬件自动将T1置位为T1=1;请求中断, 然后由软件复位TF=0;即该帧数据发送完毕.
RI: 接收中断请求标志位. 类似T1, 只是发送换成了接收
这里配置为0x40, 即0100 0000, 意思如下:
SM0 SM1分别为01:即串口工作方式使用01工作方式即方式1,8位UART,波特率可变。波特率=(2^SMOD/32)*(定时器1的溢出率)。
REN=0:禁止接收串行数据.
TB8、RB8分别为00:不考虑这两个. 具体可以参考文档. 其实文档中说的RB8在方式1时, 若SM2=0,则RB8时接收到的停止位。
T1=0:即初始化时不向主机请求中断。这个通常在定时时间到了以后,由硬件置1,即向主机请求中断,而响应中断以后(即检测到TI从0到1以后while(TI==0);,要自己手动将TI复位,即TI=0;)
*/
SCON = 0x50;
// PCON = 10 其他位不变
/*
配置PCON电源控制寄存器(不可位寻址).
B7 B6 B5 B4 B3 B2 B1 B0
SMOD SMOD0 - POF GF1 GF0 PD IDL
SMOD: 波特率选择位. 当SMOD=1时, 则使串行通信方式1、2、3的波特率加倍;SMOD=0时各工作方式的波特率不加倍。
SMOD0: 帧错误检测有效控制位。SMOD0=1时,SCON寄存器中的SM0/FE位用于FE(帧错误检测)功能;SMOD0=0时,SCON
寄存器中SM0/FE位用于SM0功能,并和SM1一起指定串口的工作方式。
SMOD=1:波特率加倍;
SMOD0=0:配合SCON中的SM1一起指定串口工作方式。注意,这里SMOD0似乎默认就是0,文档中说复位时SMOD0=0;
其他位不予改变, 由于是不可位寻址, 需要使用位运算处理
PCON = PCON | 1000 0000;即其他位不变,仅设置SMOD=1
PCON = PCON | 0x80;
*/
PCON = PCON | 0x80;
// 配置定时器/计数器工作模式寄存器. 因为使用到了波特率发生器, 即T1定时器/计数器
/*
TMOD各位描述如下:
7 6 5 4 3 2 1 0
GATE C/T拔 M1 M0 GATE C/T拔 M1 M0
0 0 1 0 低四位不变即可.
GATE:控制定时器,置1时只有在外部中断(INT1拔或者INT0拔)脚为高及(TR1或TR0)控制位置1时才可打开定时器/计数器0或定时器/计数器1
也就是GATE为1且控制位为1同时生效才能打开定时器/计数器,详情查看图。当GATE=0时,只需要TR0或者TR1一个位就能打开定时器/计数器。
C/T拔:高电平为C,即计数器;低电平为T拔,即定时器;
M1、M0:用于定时器/计数器模式选中,共4种模式:
00:13位定时器/计数器
01:16位定时器/计数器. TL1、TH1全用
10:8位自动重装载定时器,当TL1溢出时,TH1自动将自己的初始值装入TL1。
11:定时器/计数器1此时无效。(停止计数)
由此:使用GATE=0仅需TR0或者TR1就能打开定时器就可;C/T拔使用定时器T拔即可;选择8位重装载模式,因为16位定时器/计数器要求在中断中手动
重置TH1和TL1,比较耗费时间,因此采用10模式,即8位自动重装载模式;
*/
// 配置TMOD. 0010 0000
// 低位不变, 高位清零
TMOD = TMOD & 0x0F;
// 低位不变, 高位设为0010
TMOD = TMOD | 0x20;
/*
配置TMOD为8位自动重装载后要配置TL1和TH1的初始值,并且应该是一样的初值~
这里设置TL1和TH1为0xF3的原因是:
0xF3=243=256-13
也就是13us(13微秒)溢出一次,前面已经讲过12MHZ的晶振在12T模式下,每隔1us(1微秒)记一次数,这里相当于
每隔13微秒计数器/定时器溢出一次,
1/13us=0.07692307MHz // 相当于频率为0.07692307MHz
0.07692307MHZ/16=0.0048076923MHZ≈4807.69Hz // 经过16分频以后,频率变为4807Hz. 这里SMOD=1,波特率加倍了。
因此波特率使用4800Hz,误差率在7.69/4800≈0.0016025641, 即误差0.16%
如果使用SMOD=0,即不对波特率加倍的情况下,0.07692307MHZ/2/16=0.002403846MHZ≈2403.846Hz
因此如果给定波特率为BAUD=4800Hz,1/(256 - TL1)/2^SMOD/16*10^6=BAUD; 10^6/(256-TL1)/2^SMOD/16=BAUD; 256-TL1 = 10^6/2^SMOD/16/BAUD; TL1 = 256 - [10^6/2^SMOD/16/BAUD];
结论:TL1 = 256 - [10^6/2^SMOD/16/BAUD];
*/
// 设定定时器初值
TL1 = 0xF3;
// 设定定时器重装值
TH1 = 0xF3;
/*
这里ET1=0表示禁止定时器1中断。这里其实禁止不禁止似乎没关系.
*/
// 禁止定时器1中断
ET1 = 0;
/*
配置定时器/计数器控制寄存器TCON
在配置TMOD时已经说明,在GATE=0时,仅需TR1或者TR0定时器1或者定时器0的运行控制位位1即代表允许开始技术~
*/
TR1 = 1;
// 允许所有Enable All中断
EA = 1;
// 允许串口中断
ES = 1;
}
/**
* @brief UART串口中断服务函数
* @param 无
* @retval 无
*/
void UART_Routine() interrupt 4
{
if(TI == 1)
{
// 发送请求中断
// P2_7 = ~P2_7;
}
if(RI == 1)
{
// 接收请求中断
// P2_7 = ~P2_7;
UART_SendByte(SBUF);
P2 = SBUF;
RI = 0;
// LCD_ShowNum(1, 1, P2_7, 2);
}
}