跳到主要内容

USART外设

1. USART外设工作流程

Image

发送数据寄存器(TDR)和接收数据寄存器(RDR)是USART外设的两个寄存器,用于存储发送和接收的数据。会视为一个寄存器,实际是两个寄存器。TDR只写,RDR只读。写操作时,数据写入TDR,读操作时,从RDR中读取数据。

两个移位寄存器,一个用于发送一个用于接收。它的作用是把一个字节的数据一位一位地移出去。

在某时刻,若给TDR写入了0x55这个数据,在寄存器里就是二进制存储。此时会检查:当前移位寄存器有没有数据正在移位,如果没有,二进制数据就会立刻全部移动到发送移位寄存器,准备发送。当数据从TDR移动到移位寄存器时,会置一个标志位,叫TXE(TX Empty),发送寄存器空,检查该标志位,如果置1了,就可以在TDR写入下一个数据了。此时数据其实还没有发送出去,只要数据从TDR转移到发送移位寄存器了,TXE就会置1,就可以写入新的数据,然后发送移位寄存器就会在发生器的控制的驱动下,向右移位,然后一位一位地把数据传输到TX引脚;这里是向右移位,所以正好和串口协议规定的低位先行是一致的。当数据移位完成后,新的数据就会再次自动地从TDR转移到发送移位寄存器里来,如果当前移位还没有完成,TDR的数据就会进行等待,一旦完成,就会立刻转移过来。有了TDR和移位寄存器的双重缓存,可以保证连续发送数据的时候,数据帧之间不会有空闲,提高效率。

移位完成后,数据就会转移到数据寄存器RDR里。然后可以直接移位接收下一帧数据了。

转移过程中,也会置一个标志位,叫RXNE(RX Not Empty),接收寄存器非空,检查该标志位,如果置1了,就可以从RDR读取数据了。此时数据其实还没有接收到,只要数据从移位寄存器转移到RDR了,RXNE就会置1,就可以读取新的数据了.

2. 发送器控制

发送器就是用来控制发送移位寄存器的工作。

硬件数据流控(流控,硬件流控制)主要解决发送设备频率太高,接收设备来不及处理,造成数据丢失、覆盖的问题。流控有2个引脚,一个是nRTS,一个nCTS。nRTS(Request to Send)是请求发送,是输出脚。nCTS(Clear to Send)是清除发送,是输入脚。"n"意思是低电平有效。

找到另一个支持流控的串口,其TX接收到RX,RTS输出一个能不能接收的反馈信号,接到对方的CTS,当我能接收的时候,RTS就置低电平,请求对方发送,对方的CTS接收到之后,就可以一直发。当处理不过来时,比如接收数据寄存器一直没读,又有新数据过来了,就代表没有及时处理;反过来,TX给对方发送数据时,CTS就要接到对方的RTS,用于判断对方能不能接收。TX和CTS是一对的,RX和RTS是一对的。CTS和RTS也要交叉连接。

SCLK控制电路用于产生同步的时钟信号,是配合发送移位寄存器输出的,发送寄存器每移位一次,同步时钟电平就跳变一个周期,时钟告诉对方,移出了一位数据,是否需要时钟信号来指导接收。时钟只支持输出,不支持输入。它可以兼容协议,自适应波特率(测量时钟周期,计算得出波特率)。

唤醒单元的作用是实现串口挂载多设备,串口一般是点对点的通信,多设备可以在一条总线上挂载多设备。当发送唤醒单元以指定地址时,就唤醒该设备进行工作。当没有收到指定地址时,设备保持沉默。

USART中断输出控制,中断申请位就是状态寄存器的各种标志位,状态寄存器有两个标志位比较重要,一个是TXE发送寄存器空,另一个是RXNE接收寄存器非空。是判断发送/接收状态的必要标志位。

波特率发生器其实就是分频器,APB时钟进行分配,得到发送和接收移位的时钟。时钟输入是fPCLKx(x = 1 or 2),USART1挂载在APB2,所以就是PCLK2的时钟,一般是72M,其他的USART都挂载在APB1,所以是PCLK1的时钟,一般是36M。之后时钟进行分频,除一个USARTDIV的分频系数,USARTDIV分为了整数和小数部分(波特率用72M除一个整数,可能除不尽有误差),支持小数点后4位。分频后再除16,得到发送器时钟和接收器时钟,通向控制部分。如果TE(TX Enable)为1,就是发送器使能,发送部分的波特率就有效。如果RE(RX Enable)wei1,就是接收器使能,接收部分的波特率就有效。

Image

3. 波特率发生器

发送器和接收器的波特率由波特率寄存器BRR里的DIV确定。

计算公式:波特率 =fPCLK2/1/(16DIV) = f_{PCLK2/1} / (16 * DIV)

Image

波特率发生器就是分频器。发生器和接收器的波特率由波特率寄存器BRR里面的DIV(分频系数)确定。

提示

为什么除以16?

因为内部还有一个16倍波特率的采样时钟。输入时钟/DIV等于16倍的波特率。

在标准库的帮助下,需要多少波特率直接写就可以了。

4. 基本的串口初始化、收发数据的代码

我这里提供标准库和HAL库的代码。

4.1 串口初始化(查询方式)

标准库

首先我们回顾一下USART_InitTypeDef结构体。

typedef struct
{
uint32_t USART_BaudRate; // 波特率
uint16_t USART_WordLength; // 字长
uint16_t USART_StopBits; // 停止位
uint16_t USART_Parity; // 校验位
uint16_t USART_Mode; // 模式
uint16_t USART_HardwareFlowControl; // 硬件流控制
} USART_InitTypeDef;

标准库的串口初始化代码:

void usart1_init(uint32_t baudrate){
GPIO_InitTypeDef GPIO_InitStruct;
USART_InitTypeDef USART_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);// 使能USART1, GPIOA的时钟
USART_DeInit(USART1); // 复位串口1


// 记住GPIO配置需要的三大参数是引脚、模式、速度
GPIO_InitSturcture.GPIO_Pin = GPIO_Pin_9; // 串口1的发送TX,PA9,这个是原厂规定的哦!
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;// 推挽复用模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); // GPIO_Init()这个也是标准库提供的函数

// 接收引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // 串口1的RX,PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入模式
GPIO_Init(GPIOA, &GPIO_InitStructure);

USART_InitStructure.USART_BaudRate = baudrate;
USART_InitSructure.USART_WordLength = USART_WordLength_8b; // 字长为8位数据格式
USART_InitStructure.StopBits = USART_StopBits_1; // 1个停止位
USART_InitStructure.Parity = USART_Parity_No; // 无校验位,就简单的收发环境,没有别的通信干扰不需要校验
USART_InitStructure.Mode = USART_Mode_Rx | USART_Mode_Tx; // 允许接收和发送

USART_Init(USART1, &USART_InitStructure); // 初始化USART1
USART_Cmd(USART1,ENABLE); // 使能USART1
}
注意
Details

4.2 串口初始化(中断方式)

多了一个中断的配置。

void usart1_init(uint32_t baudrate){
GPIO_InitTypeDef GPIO_InitStruct;
USART_InitTypeDef USART_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);// 使能USART1, GPIOA的时钟
USART_DeInit(USART1); // 复位串口1


// 记住GPIO配置需要的三大参数是引脚、模式、速度
GPIO_InitSturcture.GPIO_Pin = GPIO_Pin_9; // 串口1的发送TX,PA9,这个是原厂规定的哦!
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;// 推挽复用模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); // GPIO_Init()这个也是标准库提供的函数

// 接收引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // 串口1的RX,PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入模式
GPIO_Init(GPIOA, &GPIO_InitStructure);

// 中断NVIC配置

NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; // USART1中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3; // 抢占优先级3 数值越小,优先级越高
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; // 子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能
NVIC_Init(&NVIC_InitStructure);


USART_InitStructure.USART_BaudRate = baudrate;
USART_InitSructure.USART_WordLength = USART_WordLength_8b; // 字长为8位数据格式
USART_InitStructure.StopBits = USART_StopBits_1; // 1个停止位
USART_InitStructure.Parity = USART_Parity_No; // 无校验位,就简单的收发环境,没有别的通信干扰不需要校验
USART_InitStructure.Mode = USART_Mode_Rx | USART_Mode_Tx; // 允许接收和发送

USART_Init(USART1, &USART_InitStructure);
USART_Cmd(USART1,ENABLE);
}

同时,还有一个对应的中断服务函数:

void usart1_irq_handler(){
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET){
{
Receive_Byte = USART_ReceiveData(USART1);
USART_SendData(USART1, Receive_Byte);
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) != SET)
// 当USART的移位寄存器(负责把数据一位一位发出去)发送完最后一个bit,并且数据寄存器(TDR)里也没有新数据要发送时,TC标志会被硬件自动置 1。
}

}
}