STM32 USART串口接收
单片机学习!
目录
前言
一、串口发送配置步骤
二、详细步骤
2.1 RCC开启USART和GPIO时钟
2.2 GPIO初始化
2.3 配置USART
2.4 开启USART
2.5 总初始化代码
三、接收数据
3.1 查询方法
3.2 中断方法
3.2.1 中断配置
3.2.2 接收函数
3.3 发送+接收函数总代码
总结
前言
上篇博文介绍了串口发送的代码设计。本文主要介绍USART初始化配置、USART串口发送和接收的基础内容。
一、串口发送配置步骤
初始化流程,可以从基本结构图来梳理:
第一步,开启时钟,把需要用的USART和GPIO的时钟打开。
第二步,GPIO初始化,把TX配置成复用输出,RX配置成输入。
第三步,配置USART,直接使用一个结构体就可以把所有需要的参数都配置好。
第四步,如果只需要发送的功能,就直接开启USART,初始化就结束了。如果需要接收的功能,可能还需要配置中断,那就在开启USART之前,再加上ITCongfig和NVIC的代码就行了。
得益于库函数的封装,内部各种细节问题就不需要再关心了。
初始化完成之后
以上就是USART外设的使用思路。
二、详细步骤
2.1 RCC开启USART和GPIO时钟
第一步开启时钟USART和GPIO的时钟。
代码示例:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIOA时钟
开启USART1的时钟,这里USART1是APB2的外设,其他都是APB1的外设。然后还需要开启GPIO的时钟,看引脚定义表,USART1的TX是PA9,RX是PA10.
2.2 GPIO初始化
第二步初始化GPIO引脚。在引脚定义表里可以找到USART1的TX复用在了PA9;USART1的RX复用在了PA10,所以这里初始化GPIOA的Pin_9和Pin_10
代码示例:
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode= GPIO_Mode_AF_PP;//引脚模式选复用推挽输出
GPIO_InitStruct.GPIO_Pin= GPIO_Pin_9;//引脚选择Pin_9
GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIOA
GPIO_InitStruct.GPIO_Mode= GPIO_Mode_IPU;//引脚模式,上拉输入
GPIO_InitStruct.GPIO_Pin= GPIO_Pin_10;//引脚选择Pin_10
GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIOA
引脚模式的配置选择:
输入模式并不分什么普通输入,复用输入。一根线只能有一个输出,但可以有多个输入。所以输入脚、外设和GPIO都可以同时用。一般RX配置是浮空输入或者上拉输入,因为串口波形空闲状态是高电平,所以不使用下拉输入。这里引脚模式的配置可以参考手册GPIO那一节推荐的配置表。
本章程序需要实现数据发送和数据接收,所以
2.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;//停止位
USART_InitStructure.USART_WordLength =USART_WordLength_8b; //字长
USART_Init(USART1,&USART_InitStructure);
USART_BaudRate 波特率,可以直接写一个波特率的数值。这里给9600,写完数值之后USART_Init函数内部会自动算好9600对应的分频系数,然后写到BRR寄存器。
USART_HardwareFlowControl 硬件流控制,这个参数的取值可以是
这里不使用流控,所以选择USART_HardwareFlowControl_None这个参数。
USART_Mode串口模式,参数有
代码示例:
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
这里程序需要发送和接收功能,所以选择USART_Mode_Tx | USART_Mode_Rx,就是同时开启发送和接收的部分。
USART_Parity 校验位,参数有
这里不需要校验,所以选择USART_Parity_No无校验。
USART_StopBits停止位,参数可以选择
这里选择USART_StopBits_1参数,就是1位停止位。
USART_WordLength 字长,参数可以选择
因为不需要校验,前面设置了无校验参数,这里就选择USART_WordLength_8b字长为8位。
以上结构体参数的初始化就完成了,对串口的配置是9600波特率、无流控、发送+接收模式、无校验位、1位停止位、八位字长。
2.4 开启USART
第四步,开启USART,调用USART_Cmd函数,给指定的通道USART1使能。
代码示例:
USART_Cmd(USART1,ENABLE);
2.5 总初始化代码
代码示例:
void Serial_Init(void)
{
//第一步开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIO的时钟
//第二步初始化GPIO引脚
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode= GPIO_Mode_AF_PP;//引脚模式
GPIO_InitStruct.GPIO_Pin= GPIO_Pin_9;//引脚选择Pin_9
GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIOA
GPIO_InitStruct.GPIO_Mode= GPIO_Mode_IPU;//引脚模式
GPIO_InitStruct.GPIO_Pin= GPIO_Pin_10;//引脚选择Pin_10
GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIOA
//第三步初始化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;//停止位
USART_InitStructure.USART_WordLength =USART_WordLength_8b; //字长
USART_Init(USART1,&USART_InitStructure);
USART_Cmd(USART1,ENABLE);
}
三、接收数据
以上串口接收的代码已经配置差不多了。对于串口接收来说,可以使用查询和中断两种方法,如果使用查询,那初始化就结束了。如果使用中断,那还需要开启中断,配置NVIC。
下文将对查询和中断的方法分别举例。
3.1 查询方法
查询的流程是在主函数里不断判断RXNE标志位,如果RXNE标志位置1了,就说明收到数据了,那再调用ReceiveData函数读取DR寄存器就可以了。
代码示例:
if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == SET)
{
RxData = USART_ReceiveData(USART1);
}
目前接收到的一个字节就已经在RxData里了。还需要分析一下清除标志位的问题,查看手册:
当RDR移位寄存器中的数据被转移到USART_DR寄存器中,该位被硬件置位。如果 USART_CR1寄存器中的RXNEIE为1,则产生中断。对USART_DR的读操作可以将该位清零。RXNE位也可以通过写入0来清除,只有在多缓存通讯中才推荐这种清除程序。
手册中表示读USART_DR可以自动清零标志位,所以上述代码读完USART_DR就不需要再清除标志位了。
3.2 中断方法
3.2.1 中断配置
使用中断方法首先需要在初始化里加上开启中断的代码。
代码示例:
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//中断通道
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority =1;
NVIC_Init(&NVIC_InitStructure);
第一步,开启RXNE标志位到NVIC的输出,也就是配置USART1的接收中断使能,之后就可以配置NVIC了。
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
配置中断分组:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
初始化NVIC的USART1通道:
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//中断通道
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority =1;
NVIC_Init(&NVIC_InitStructure);
代码配置:使能中断通道为USART1全局中断,先占优先级为1,从优先级也为1,最后将结构体地址放入NVIC_Init函数。
以上配置使RXNE标志位一但置1了,就会向NVIC申请中断,之后就可以在中断函数里接收数据。
中断接收初始化配置总代码:
void Serial_Init(void)
{
//第一步开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIO的时钟
//第二步初始化GPIO引脚
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode= GPIO_Mode_AF_PP;//引脚模式
GPIO_InitStruct.GPIO_Pin= GPIO_Pin_9;//引脚选择Pin_9
GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIOA
GPIO_InitStruct.GPIO_Mode= GPIO_Mode_IPU;//引脚模式
GPIO_InitStruct.GPIO_Pin= GPIO_Pin_10;//引脚选择Pin_10
GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIOA
//第三步初始化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;//停止位
USART_InitStructure.USART_WordLength =USART_WordLength_8b; //字长
USART_Init(USART1,&USART_InitStructure);
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//配置USART中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断分组
NVIC_InitTypeDef NVIC_InitStructure;//初始化NVIC的USART1通道
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//中断通道
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority =1;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART1,ENABLE);
}
3.2.2 接收函数
代码示例:
uint8_t Serial_RxData;//接收到的数据
uint8_t Serial_RxFlag;//接收到数据的标志位
uint8_t Serial_GetRxFlag(void)
{
if(Serial_RxFlag == 1)
{
Serial_RxFlag = 0;
return 1;
}
return 0;
}
uint8_t Serial_GetRxData(void)
{
return Serial_RxData;
}
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)
{
Serial_RxData = USART_ReceiveData(USART1);
Serial_RxFlag = 1;
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}
if(Serial_GetRxFlag()==1)
{
RxData = Serial_GetRxData();
Serial_SendByte(RxData);
}
Serial_GetRxFlag函数实现功能为变量Serial_RxData读后自动清除标志位Serial_RxFlag。
uint8_t Serial_GetRxFlag(void)
{
if(Serial_RxFlag == 1)
{
Serial_RxFlag = 0;
return 1;
}
return 0;
}
Serial_GetRxData函数的功能为返回接收到的数据。
uint8_t Serial_GetRxData(void)
{
return Serial_RxData;
}
USART1_IRQHandler函数是把数据进行了一次转存,最终还是要扫描查询Serial_RxFlag来接收数据。
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)
{
Serial_RxData = USART_ReceiveData(USART1);
Serial_RxFlag = 1;/
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}
先判断标志位:如果RXNE确实置1了,就进入if。
if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)
将接收到的数据读取给自定义变量Serial_RxData,读完后置一个自定义的标志位Serial_RxFlag。
Serial_RxData = USART_ReceiveData(USART1);
Serial_RxFlag = 1;
在if里可以直接读取DR,执行一些操作。if里是否需要清除标志位可以看有没有读取DR:
示例里直接清除一下标志位
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
以上中断接收和变量的封装就完成了!想中断接收数据就可以直接调用函数来使用:
if(Serial_GetRxFlag()==1)
{
RxData = Serial_GetRxData();//目前接收到的一个字节就已经在RxData里了
Serial_SendByte(RxData);//把接收到的数据回传到电脑
}
目前这个程序只支持一个字节的接收。
3.3 发送+接收函数总代码
发送接收总代码示例:
//发送数据的函数
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1,Byte);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
}
//发送一个数组的函数
void Serial_SendArray(uint8_t *Array,uint16_t Length)
{
uint16_t i;
for(i = 0 ; i < Length ; i++)
{
Serial_SendByte(Array[i]);
}
}
//发送字符串的函数
void Serial_SendString(char *String)
uint8_t i;
for(i = 0;String[i] != '\0';i++)
{
Serial_SendByte(String[i]);
}
}
//这个函数的返回值是X的Y次方
uint32_t Serial_Pow(uint32_t X,uint32_t Y)
uint32_t Result = 1;
while(Y--)
{
Result *= X;
}
return Result;
}
//发送一个数字,能显示字符串形式数字的函数。
void Serial_SendNumber(uint32_t Number,uint8_t Length)
{
uint8_t i;
for(i = 0;i < Length;i++)
{
Serial_SendByte(Number / Serial_Pow(10,Length - i - 1) %10 + '0');
}
}
//printf函数重定向到串口
int fputc(int ch,FILE *f)
{
Serial_SendByte(ch);
return ch;
}
//函数实现Serial_RxData变量读后自动清除标志位Serial_RxFlag的功能
uint8_t Serial_GetRxFlag(void)
{
if(Serial_RxFlag == 1)
{
Serial_RxFlag = 0;
return 1;
}
return 0;
}
uint8_t Serial_GetRxData(void)
{
return Serial_RxData;
}
//函数用于数据转存,最终还是要扫描查询Serial_RxFlag来接收数据。
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)
{
Serial_RxData = USART_ReceiveData(USART1);
Serial_RxFlag = 1;
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}
发送部分的函数设计详解可以看上一篇博文STM32 USART串口发送_stm32 uart初始化是怎么设置cr3-CSDN博客https://blog.csdn.net/Echo_cy_/article/details/142794600?spm=1001.2014.3001.5501
总结
以上就是今天要讲的内容,本文仅仅简单介绍了USART初始化配置以及一些配置代码的细节。
作者:Echo_cy_