STM32 USART串口发送+接收

单片机学习!


目录

前言

一、串口发送配置步骤

二、详细步骤

[2.1 RCC开启USART和GPIO时钟](#2.1 RCC开启USART和GPIO时钟)

[2.2 GPIO初始化](#2.2 GPIO初始化)

[2.3 配置USART](#2.3 配置USART)

[2.4 开启USART](#2.4 开启USART)

[2.5 总初始化代码](#2.5 总初始化代码)

三、接收数据

[3.1 查询方法](#3.1 查询方法)

[3.2 中断方法](#3.2 中断方法)

[3.2.1 中断配置](#3.2.1 中断配置)

[3.2.2 接收函数](#3.2.2 接收函数)

总结


前言

上篇博文介绍了串口发送的代码设计。本文主要介绍USART初始化配置、USART串口发送和接收的基础内容。


一、串口发送配置步骤

初始化流程,可以从基本结构图来梳理:

第一步,开启时钟,把需要用的USART和GPIO的时钟打开。

第二步,GPIO初始化,把TX配置成复用输出,RX配置成输入。

第三步,配置USART,直接使用一个结构体就可以把所有需要的参数都配置好。

第四步 ,如果只需要发送的功能,就直接开启USART,初始化就结束了。如果需要接收的功能,可能还需要配置中断,那就在开启USART之前,再加上ITCongfig和NVIC的代码就行了。

得益于库函数的封装,内部各种细节问题就不需要再关心了。

初始化完成之后

  • 如果要发送数据,调用一个发送的函数就行了;
  • 如果要接收数据,就调用接收的函数;
  • 如果要获取发送和接收的状态,就调用获取标志位的函数。

以上就是USART外设的使用思路。

二、详细步骤

2.1 RCC开启USART和GPIO时钟

第一步开启时钟USART和GPIO的时钟。

代码示例:

cpp 复制代码
	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

代码示例:

cpp 复制代码
	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

引脚模式的配置选择:

  • TX引脚是USART外设控制的输出引脚,所以要选复用推挽输出。
  • RX引脚是USART外设数据的输入引脚,所以要选择输入模式。

输入模式 并不分什么普通输入,复用输入。一根线只能有一个输出,但可以有多个输入。所以输入脚、外设和GPIO都可以同时用。一般RX配置是浮空输入 或者上拉输入,因为串口波形空闲状态是高电平,所以不使用下拉输入。这里引脚模式的配置可以参考手册GPIO那一节推荐的配置表。

本章程序需要实现数据发送和数据接收,所以

  • TX引脚初始化模式选择GPIO_Mode_AF_PP复用推挽输出模式;
  • RX引脚初始化模式选择GPIO_Mode_IPU上拉输入模式。

2.3 配置USART

第三步初始化USART

代码示例:

cpp 复制代码
	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_HardwareFlowControl_CTS只用CTS;
  • USART_HardwareFlowControl_RTS只用RTS;
  • USART_HardwareFlowControl_RTS_CTS是CTS和RTS都使用。

这里不使用流控,所以选择USART_HardwareFlowControl_None这个参数。


USART_Mode串口模式,参数有

  • USART_Mode_Tx是Tx发送模式;
  • USART_Mode_Rx是Rx接收模式。
  • 如果既需要接收又需要发送,那就用或符号把Tx和Rx或起来。

代码示例:

cpp 复制代码
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;

这里程序需要发送和接收功能,所以选择USART_Mode_Tx | USART_Mode_Rx,就是同时开启发送和接收的部分。


USART_Parity 校验位,参数有

  • USART_Parity_No无校验;
  • USART_Parity_Odd奇校验;
  • USART_Parity_Even偶校验。

这里不需要校验,所以选择USART_Parity_No无校验。


USART_StopBits停止位,参数可以选择

  • USART_StopBits_0_5 是0.5位停止位;
  • USART_StopBits_1 是1位停止位;
  • USART_StopBits_1_5 是1.5位停止位;
  • USART_StopBits_2 是2位停止位。

这里选择USART_StopBits_1参数,就是1位停止位。


USART_WordLength 字长,参数可以选择

  • USART_WordLength_8b八位字长;
  • USART_WordLength_9b九位字长。

因为不需要校验,前面设置了无校验参数,这里就选择USART_WordLength_8b字长为8位。

以上结构体参数的初始化就完成了,对串口的配置是9600波特率、无流控、发送+接收模式、无校验位、1位停止位、八位字长。

2.4 开启USART

第四步,开启USART,调用USART_Cmd函数,给指定的通道USART1使能。

代码示例:

cpp 复制代码
    USART_Cmd(USART1,ENABLE);

2.5 总初始化代码

代码示例:

cpp 复制代码
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寄存器就可以了。

代码示例:

cpp 复制代码
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 中断配置

使用中断方法首先需要在初始化里加上开启中断的代码。

代码示例:

cpp 复制代码
    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了。

cpp 复制代码
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);

配置中断分组:

cpp 复制代码
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

初始化NVIC的USART1通道:

cpp 复制代码
    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申请中断,之后就可以在中断函数里接收数据。

中断接收初始化配置总代码:

cpp 复制代码
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 接收函数

代码示例:

cpp 复制代码
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); 
	}
}
cpp 复制代码
    if(Serial_GetRxFlag()==1)
    {
        RxData = Serial_GetRxData();
        Serial_SendByte(RxData);
	}				

Serial_GetRxFlag函数实现功能为变量Serial_RxData读后自动清除标志位Serial_RxFlag。

cpp 复制代码
uint8_t Serial_GetRxFlag(void)
{
    if(Serial_RxFlag == 1)
    {
        Serial_RxFlag = 0;
        return 1;
    }
    return 0;
}

Serial_GetRxData函数的功能为返回接收到的数据。

cpp 复制代码
uint8_t Serial_GetRxData(void)
{
	return Serial_RxData;
}	

USART1_IRQHandler函数是把数据进行了一次转存,最终还是要扫描查询Serial_RxFlag来接收数据。

cpp 复制代码
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。

cpp 复制代码
    if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)

将接收到的数据读取给自定义变量Serial_RxData,读完后置一个自定义的标志位Serial_RxFlag。

cpp 复制代码
        Serial_RxData = USART_ReceiveData(USART1);
        Serial_RxFlag = 1;

在if里可以直接读取DR,执行一些操作。if里是否需要清除标志位可以看有没有读取DR:

  • 如果读取了DR,就可以自动清除标志位;
  • 如果没读取DR,就需要手动清除标志位。

示例里直接清除一下标志位

cpp 复制代码
        USART_ClearITPendingBit(USART1,USART_IT_RXNE); 

以上中断接收和变量的封装就完成了!想中断接收数据就可以直接调用函数来使用:

cpp 复制代码
if(Serial_GetRxFlag()==1)
{
	RxData = Serial_GetRxData();//目前接收到的一个字节就已经在RxData里了
	Serial_SendByte(RxData);//把接收到的数据回传到电脑
}	

目前这个程序只支持一个字节的接收。


总结

以上就是今天要讲的内容,本文仅仅简单介绍了USART初始化配置以及一些配置代码的细节。

相关推荐
赵谨言1 小时前
基于单片机的防盗报警器设计与实现
经验分享·单片机·毕业设计
aloneboyooo1 小时前
STM32H7时钟树
stm32·单片机·嵌入式硬件
技术流浪者1 小时前
PCB设计(十九)PCB设计中NPN/PNP选型策略
单片机·嵌入式硬件·硬件工程·pcb工艺
长流小哥2 小时前
STM32实战指南:SG90舵机控制原理与代码详解
stm32·单片机·嵌入式硬件·keil5
xduryan5 小时前
16.1 - VDMA视频转发实验之TPG
嵌入式硬件
逼子格9 小时前
硬件工程师笔记——二极管Multisim电路仿真实验汇总
笔记·嵌入式硬件·硬件工程师·multisim·硬件工程师学习·电子器件·电路图
「QT(C++)开发工程师」9 小时前
STM32 | FreeRTOS 递归信号量
python·stm32·嵌入式硬件
芯眼10 小时前
ALIENTEK精英STM32F103开发板 实验0测试程序详解
开发语言·c++·stm32·单片机·嵌入式硬件·社交电子
百里东风10 小时前
STM32IIC协议基础及Cube配置
stm32·单片机·嵌入式硬件
2301_8003997211 小时前
stm32 DMA
stm32·单片机·嵌入式硬件