目录
[一. USART串口协议](#一. USART串口协议)
[二. USART串口外设](#二. USART串口外设)
[三. 串口发送+接收](#三. 串口发送+接收)
[四. 效果展示](#四. 效果展示)
一. USART串口协议
USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器。
通信的目的:将一个设备的数据传送到另一个设备,扩展硬件系统。
通信的协议:制定通信的规则,通信双方按照协议规则进行数据收发。
单片机有了通信功能,就能与众多别的模块互联。总的来说,通信的目的就是进行信息传递,双方约定的规则就是通信协议。通信协议不止一种,在STM32中,就有诸如USART,I2C,SPI,CAN,USB等通信。今天我们主要介绍USART串口通信。
单片机的串口可以使单片机与单片机,单片机与电脑,单片机与各种各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力。
其中,单片机和电脑通信,是串口的一大优势,可以接电脑屏幕,非常适合调试程序,打印信息。
串口的硬件电路,需要其中一个设备的发送端TX连接另外一个设备的接收端RX,接收端RX连接另一个设备的发送端TX。当只需单向的数据传输时,可以只接一根通信线。
TX和RX都是单端信号,它们的高低电平都是相对于GND的。所以串口通信的TX,RX,GND都是必须要连接的。
串口的参数及时序:
**1)波特率:**串口通信的速率。由于串口一般使用异步通信,所以需要双方约定一个通信速率。它决定了每隔多久发送一位。
**2)起始位:**它是标志一个数据帧的开始,固定为低电平。首先串口的空闲状态是高电平,也就是没有数据传输的时候,引脚必须要置高电平,作为空闲状态。然后需要传输的时候,就必须要先发送一个起始位,这个起始位必须是低电平,来打破空闲状态的高电平,产生一个下降沿。这个下降沿就告诉接收设备,这一帧数据要开始了。
**3)停止位:**在一个字节数据发送完成后,必须要有一个停止位。这个停止位的作用就是用于数据帧间隔,固定为高电平。同时,这个停止位,也是为下一个起始位做准备的。
**4)数据位:**表示数据帧的有效载荷,1为高电平,0为低电平,低位先行。低位先行的意思就是数据先从低位发送。
**5)校验位:**用于数据验证,根据数据位计算得来。校验可以选择3种方式。无校验,奇校验和偶校验。
二. USART串口外设
一般我们串口很少使用同步功能,所以USART和UART使用起来,并没有太大的区别。所以我们学习串口,主要还是异步通信。
USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里。
USART外设就是串口通信的硬件支持电路。当我们配置好了USART电路,直接读写数据寄存器,就能自动发送和接收数据了。
举个案例,我们STM32F103C8T6的USART资源就有如下:USART1,USART2,USART3.总共3个独立的USART外设,可以挂载很多串口设备。其中,USART1是APB2总线上的设备,剩下的两个都是APB1总线的设备。
下面我们来了解一下整个USART的框图和结构:
如上所示, 串口的数据寄存器就包括上面的右边部分,发送或接收的字节数据就存在这里。一个是发送数据寄存器TDR(Transmit DR)和接收数据寄存器RDR(Receive DR)。
但其实,这两个数据寄存器都占用一个地址,在程序上只表现为一个寄存器,那就是数据寄存器DR(Data Register)。
当数据从TDR移动到移位寄存器时,会置一个标志位,**叫TXE(TX Empty),发送寄存器空。**我们检查这个标志位,如果置1了,我们就可以在TDR写入下一个数据了。然后发送移位器就会在下面的发送器控制的驱动下,向右移位,然后一位一位地,把数据输出到TX引脚。向右移位,正好和串口协议规定的低位先行,是一致的。
同样的,接收数据的时候,也会置一个标志位,**叫RXNE(RX Not Empty),接收数据寄存器非空。**当我们检测到RXNE置1之后,就可以把数据读走了。当数据从移位寄存器转移到RDR时,就可以直接移位接收下一帧数据了。
如上所示的中间位置,有一个唤醒单元,而这个唤醒单元的作用就是用来实现多设备的功能。
串口一般是点对点模式。
上面的时钟输入一般是fPCLKx(x=1或2)。
USART1挂载在APB2,所以就是PCLK2的时钟,一般是72M.其他的USART都挂载在APB1,所以是PCLK1的时钟,一般是32M.
之后这个时钟进行一个分频,除以一个USARTDIV的分频系数。之后分频完后,还要再除个16,得到发送器时钟和接收器时钟,通向控制部分。
然后右边这里,如果TE(TX Enable)为1,就是发送器使能了,发送部分的波特率就有效。
如果RE(RX Enable)为1,就是接收器使能了,接收部分的波特率就有效。
到这里USART的框图就看完了,剩下的就是一些寄存器的指示,比如各个CR控制寄存器的哪一位控制那一部分电路。这些都可以自己看看手册里的寄存器描述。
最后我们看下USART基本结构:
上图中的最左边这里就是波特率发生器,用于产生约定的通信速率。
时钟来源是PCLK2或1.
三. 串口发送+接收
通过观察引脚定义表,我们可以发现USART1的TX是PA9,RX是PA10.
如下所示:
所以根据产品手册和上文中所介绍的USART串口结构,我们可以总结除如下串口的相关代码配置流程:
1)开启时钟,把需要用到的USART和GPIO的时钟打开。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启对应的GPIO口
**2)GPIO初始化,**把TX配置成复用功能(因为只有复用功能才能让片上外设模块的复用功能生效),RX配置成输入模式。
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA9引脚初始化为复用推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //将PA10引脚初始化为上拉输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
**3)配置USART,**可以直接使用一个结构体,就可以把相关的所有参数配置好了。
/*USART初始化*/
USART_InitTypeDef USART_InitStructure; //定义结构体变量
USART_InitStructure.USART_BaudRate = 9600; //波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制,不需要
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //模式,发送模式和接收模式均选择
USART_InitStructure.USART_Parity = USART_Parity_No; //奇偶校验,不需要
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位,选择1位
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长,选择8位
USART_Init(USART1, &USART_InitStructure);
4)如果你只需要发送的功能,就直接开启USART,初始化就完成了。如下所示:
USART_Cmd(USART1, ENABLE);
如果你需要接收的功能,可能还需要配置中断,那就在开启USART之前,再加上ITConfig和NVIC的代码就行了。如下所示:
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //选择配置NVIC的USART1线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure);
在上面进行好了USART相关初始化后,就需要编写串口函数了,在启动文件startup_stm32f10x_md.s中找到串口的名称,如下所示:
void USART1_IRQHandler(void)
{
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) //判断是否是USART1的接收事件触发的中断
{
Serial_RxData = USART_ReceiveData(USART1); //读取数据寄存器,存放在接收的数据变量
Serial_RxFlag = 1; //置接收标志位变量为1
USART_ClearITPendingBit(USART1, USART_IT_RXNE); //清除USART1的RXNE标志位
//读取数据寄存器会自动清除此标志位
//如果已经读取了数据寄存器,也可以不执行此代码
}
}
我们就可以编写程序来测试下了。这里例举两个测试案例:
#include "Serial.h"
uint8_t RxData; //定义用于接收串口数据的变量
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
/*显示静态字符串*/
OLED_ShowString(1, 1, "RxData:");
/*串口初始化*/
Serial_Init(); //串口初始化
while (1)
{
if (Serial_GetRxFlag() == 1) //检查串口接收数据的标志位
{
RxData = Serial_GetRxData(); //获取串口接收的数据
Serial_SendByte(RxData); //串口将收到的数据回传回去,用于测试
OLED_ShowHexNum(1, 8, RxData, 2); //显示串口接收的数据
}
}
}
四. 效果展示
在这里我们使用某一位b站博主自己做的串口助手,并对它设置好波特率为9600,数据位为8,停止位为1等参数,然后我们发送一个数23,如下所示:
然后我们再来看下我们的OLED调试屏幕:
如上所示,屏幕上也显示出了所接收到数据23.
好了,到这里所有的内容差不多已经全部讲完,如果有任何疑问或者问题都可以在下面进行留言或者私信。