实验八 UART—串口通信

一.实验目标

1.编程使用STM32F407的串口来发送和接收数据;

2.STM32F407通过串口和上位机的对话,STM32F429在收到上位机发过来的字符串后,原原本本的返回.给上位机;

3.通过本实验掌握STM32的串口收发数据。

二.知识储备及设计思路

STM32F407串口简介

串口作为MCU的重要外部接口,同时也是软件开发重要的调试手段,其重要性不言而喻。现在基本上所有的MCU都会带有串口,STM32自然也不例外。

STM32F407的串口资源相当丰富的,功能也相当强劲。STM32F407开发板所使用的STM32F407IGT6芯片最多可提供6路串口,有分数波特率发生器、支持同步单线通信和半双工单线通讯、支持LIN、支持调制解调器操作、智能卡协议和IrDA
SIR ENDEC规范、具有DMA等。

接下来我们先从寄存器层面,告诉你如何设置串口,以达到我们最基本的通信功能。本章,我们将实现开机后利用串口3打印信息到电脑上,同时接收从串口发过来的数据,把发送过来的数据直接送回给电脑。开发板板载了1个USB串口,该串口和下载器usb口共用。本章介绍通过USB串口和电脑通信。

串口最基本的设置,就是波特率的设置。STM32F407的串口使用起来还是蛮简单的,只要你开启了串口时钟,并设置相应IO口的模式,然后配置一下波特率,数据位长度,奇偶校验位等信息,就可以使用了。下面,我们就简单介绍下这几个与串口基本配置直接相关的寄存器。

USART寄存器介绍

1,串口时钟使能。串口作为STM32F407的一个外设,其时钟由外设时钟使能寄存器控制,这里我们使用的串口3是在APB1ENR寄存器的第18位。APB1ENR寄存器功能如下面所示。只是说明一点,就是除了串口1和串口6的时钟使能在APB2ENR寄存器,其他串口的时钟使能位都在APB1ENR寄存器。

2,串口波特率设置。每个串口都有一个自己独立的波特率寄存器USART_BRR,通过设置该寄存器就可以达到配置不同波特率的目的。具体实现方法,请参考5.3.2节。

波特率计算公式:

位 15:4 DIV_Mantissa[11:0]:USARTDIV 的尾数,这 12 个位用于定义 USART 除数
(USARTDIV) 的尾数。

位 3:0 DIV_Fraction[3:0]:USARTDIV 的小数,这 4 个位用于定义 USART 除数
(USARTDIV) 的小数。当 OVER8 = 1 时,不考虑
DIV_Fraction3位,且必须将该位保持清零。

计算USARTDIV示例:

3,串口控制。STM32F407的每个串口都有3个控制寄存器USART_CR1~3,串口的很多配置都是通过这3个寄存器来设置的。这里我们只要用到USART_CR1就可以实现我们的功能了,该寄存器的各位描述如图

该寄存器的高16位没有用到,低16位用于串口的功能设置。OVER8为过采样模式设置位,我们一般设置位0,即16倍过采样已获得更好的容错性;UE为串口使能位,通过该位置1,以使能串口;M为字长选择位,当该位为0的时候设置串口为8个字长外加n个停止位,停止位的个数(n)是根据USART_CR2的[13:12]位设置来决定的,默认为0;PCE为校验使能位,设置为0,则禁止校验,否则使能校验;PS为校验位选择位,设置为0则为偶校验,否则为奇校验;TXIE为发送缓冲区空中断使能位,设置该位为1,当USART_SR中的TXE位为1时,将产生串口中断;TCIE为发送完成中断使能位,设置该位为1,当USART_SR中的TC位为1时,将产生串口中断;RXNEIE为接收缓冲区非空中断使能,设置该位为1,当USART_SR中的ORE或者RXNE位为1时,将产生串口中断;TE为发送使能位,设置为1,将开启串口的发送功能;RE为接收使能位,用法同TE。

其他位的设置,这里就不一一列出来了,大家可以参考《STM32F4xx中文参考手册》第714页有详细介绍,在这里我们就不列出来了。

4,数据发送与接收。STM32F407的发送与接收是通过数据寄存器USART_DR来实现的,这是一个双寄存器,包含了TDR和RDR。当向DR寄存器写数据的时候,实际是写入TDR,串口就会自动发送数据;当收到数据,读DR寄存器的时候,实际读取的是RDR。TDR和RDR对外是不可见的,所以我们操作的就只有DR寄存器,该寄存器的各位描述如图

可以看出,虽然是一个32位寄存器,但是只用了低9位(DR[8:0]),其他都是保留。

DR[8:0]为串口数据,包含了发送或接收的数据。由于它是由两个寄存器(TDR和RDR)组成的,一个给发送用(TDR),一个给接收用(RDR),该寄存器兼具读和写的功能。TDR寄存器提供了内部总线和输出移位寄存器之间的并行接口。RDR寄存器提供了输入移位寄存器和内部总线之间的并行接口。

当使能校验位(USART_CR1中PCE位被置位)进行发送时,写到MSB的值(根据数据的长度不同,MSB是第7位或者第8位)会被后来的校验位取代。

当使能校验位进行接收时,读到的MSB位是接收到的校验位。

5,串口状态。串口的状态可以通过状态寄存器USART_SR读取。USART_SR的位描述。

这里我们关注一下两个位,第5、6位RXNE和TC。

RXNE(读数据寄存器非空),当该位被置1的时候,就是提示已经有数据被接收到了,并且可以读出来了。这时候我们要做的就是尽快去读取USART_DR,通过读USART_DR可以将该位清零,也可以向该位写0,直接清除。

TC(发送完成),当该位被置位的时候,表示USART_DR内的数据已经被发送完成了。如果设置了这个位的中断,则会产生中断。该位也有两种清零方式:1)读USART_SR,写USART_DR。2)直接向该位写0。

HAL库函数和cube生成函数介绍

通过以上一些寄存器的操作外加一下IO口的配置,我们就可以达到串口最基本的配置了,关于串口更详细的介绍,请参考《STM32F4xx中文参考手册》第676页至720页,通用同步异步收发器这一章节。接下来我们将着重讲解使用HAL库实现串口配置和使用的方法。在HAL库中,串口相关的函数和定义主要在文件stm32f4xx_hal_uart.c和stm32f4xx_hal_uart.h中。接下来我们看看HAL库提供的串口相关操作函数。

  1. 串口参数初始化(波特率/停止位等),并使能串口。

串口作为STM32的一个外设,HAL库为其配置了串口初始化函数。接下来我们看看使用CUBE生成的串口3初始化函数MX_USART3_UART_Init

1
void MX_USART3_UART_Init(void);

其中用到了初始化函数HAL_UART_Init相关知识,定义如下:

1
HAL_StatusTypeDef  HAL_UART_Init(UART_HandleTypeDef *huart);

该函数只有一个入口参数huart,为UART_HandleTypeDef结构体指针类型,我们俗称其为串口句柄,它的使用会贯穿整个串口程序。一般情况下,我们会定义一个UART_HandleTypeDef结构体类型全局变量,然后初始化各个成员变量。接下来我们看看结构体UART_HandleTypeDef的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typedef struct __UART_HandleTypeDef
{
USART_TypeDef *Instance; /*!< UART registers base address */
UART_InitTypeDef Init; /*!< UART communication parameters */
uint8_t *pTxBuffPtr; /*!< Pointer to UART Tx transfer Buffer */
uint16_t TxXferSize; /*!< UART Tx Transfer size */
__IO uint16_t TxXferCount; /*!< UART Tx Transfer Counter */
uint8_t *pRxBuffPtr; /*!< Pointer to UART Rx transfer Buffer */
uint16_t RxXferSize; /*!< UART Rx Transfer size */
__IO uint16_t RxXferCount; /*!< UART Rx Transfer Counter */
DMA_HandleTypeDef *hdmatx; /*!< UART Tx DMA Handle parameters */
DMA_HandleTypeDef *hdmarx; /*!< UART Rx DMA Handle parameters */
HAL_LockTypeDef Lock; /*!< Locking object */
__IO HAL_UART_StateTypeDef gState; /*!< UART state information related to
__IO HAL_UART_StateTypeDef RxState; /*!< UART state information related to Rx
__IO uint32_t ErrorCode; /*!< UART Error code */
} UART_HandleTypeDef;

该结构体成员变量非常多,一般情况下载调用函数HAL_UART_Init对串口进行初始化的时候,我们只需要先设置Instance和Init两个成员变量的值。接下来我们依次解释一下各个成员变量的含义。

Instance是USART_TypeDef结构体指针类型变量,它是执行寄存器基地址,实际上这个基地址HAL库已经定义好了,如果是串口3,取值为USART3即可。

Init是UART_InitTypeDef结构体类型变量,它是用来设置串口的各个参数,包括波特率,停止位等,它的使用方法非常简单。UART_InitTypeDef结构体定义如下:

1
2
3
4
5
6
7
8
9
10
11
typedef struct
{
uint32_t BaudRate;
uint32_t WordLength;
uint32_t StopBits;
uint32_t Parity;
uint32_t Mode;
uint32_t HwFlowCtl;
uint32_t OverSampling;
} UART_InitTypeDef;

该结构体第一个参数BaudRate为串口波特率,波特率可以说是串口最重要的参数了,它用来确定串口通信的速率。第二个参数WordLength为字长,可以设置为8位字长或者9位字长,这里我们设置为8位字长数据格式UART_WORDLENGTH_8B。第三个参数StopBits为停止位设置,可以设置为1个停止位或者2个停止位,这里我们设置为1位停止位UART_STOPBITS_1。第四个参数Parity设定是否需要奇偶校验,我们设定为无奇偶校验位。第五个参数Mode为串口模式,可以设置为只收模式,只发模式,或者收发模式。这里我们设置为全双工收发模式。第六个参数HwFlowCtl为是否支持硬件流控制,我们设置为无硬件流控制。第七个参数OverSampling用来设置过采样为16倍还是8倍。

pTxBuffPtr,TxXferSize和TxXferCount三个变量分别用来设置串口发送的数据缓存指针,发送的数据量和还剩余的要发送的数据量。而接下来的三个变量pRxBuffPtr,RxXferSize和RxXferCount则是用来设置接收的数据缓存指针,接收的最大数据量以及还剩余的要接收的数据量。这六个变量是HAL库处理中间变量,详细使用方法在我们讲解中断服务函数的时候给大家讲解。

hdmatx和hdmarx是串口DMA相关的变量,指向DMA句柄,这里我们先不讲解。其他的三个变量就是一些HAL库处理过程状态标志位和串口通信的错误码。

函数MX_USART3_UART_Init使用的一般格式为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void MX_USART3_UART_Init(void)
{

huart3.Instance = USART3;
huart3.Init.BaudRate = 115200;
huart3.Init.WordLength = UART_WORDLENGTH_8B;
huart3.Init.StopBits = UART_STOPBITS_1;
huart3.Init.Parity = UART_PARITY_NONE;
huart3.Init.Mode = UART_MODE_TX_RX;
huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart3.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart3) != HAL_OK)
{ Error_Handler(); }
}

这里我们需要说明的是,函数HAL_UART_Init内部会调用串口使能函数使能相应串口,所以调用了该函数之后我们就不需要重复使能串口了。当然,HAL库也提供了具体的串口使能和关闭方法,具体使用方法如下:

1
2
__HAL_UART_ENABLE(__HANDLE__)
__HAL_UART_DISABLE(__HANDLE__)

这里还需要提醒大家,串口作为一个重要外设,在调用的初始化函数HAL_UART_Init内

部,会先调用MSP初始化回调函数进行MCU相关的初始化,函数为:

1
void HAL_UART_MspInit(UART_HandleTypeDef *huart);

我们在程序中,只需要重写该函数即可。一般情况下,该函数内部用来编写IO口初始化,时钟使能以及NVIC配置。

2)使能串口和配置GPIO口,中断配置

我们要使用串口,所以我们必须使能串口时钟和使用到的GPIO口时钟。例如我们要使用串口3,所以我们必须使能串口3时钟和GPIOB时钟(串口3使用的是PB10和PB11)。然后还需要配置GPIO口,具体方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(uartHandle->Instance==USART3)
{
__HAL_RCC_USART3_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/**USART3 GPIO Configuration
PB10 ------> USART3_TX
PB11 ------> USART3_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART3;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* USART3 interrupt Init */
HAL_NVIC_SetPriority(USART3_IRQn, 3, 3);
HAL_NVIC_EnableIRQ(USART3_IRQn);
}
}

3)串口中断开启/关闭

HAL库中定义了一个使能串口中断的标识符__HAL_UART_ENABLE_IT,大家可以把它当一个函数来使用,具体定义请参考HAL库文件stm32f4xx_hal_uart.h中该标识符定义。例如我们要使能接收完成中断,方法如下:

1
__HAL_UART_ENABLE_IT(huart,UART_IT_RXNE); //开启接收完成中断

关闭接收中断,方法如下:

1
__HAL_UART_DISABLE_IT(huart,UART_IT_RXNE); //关闭接收完成中断

4) 编写中断服务函数

串口3中断服务函数为:

1
void USART3_IRQHandler(void)

当发生中断的时候,程序就会执行中断服务函数。然后我们在中断服务函数中编写们相应的逻辑代码即可。

5) 串口数据接收和发送

STM32F4的发送与接收是通过数据寄存器USART_DR来实现的,这是一个双寄存器,包含了TDR和RDR。当向该寄存器写数据的时候,串口就会自动发送,当收到数据的时候,也是存在该寄存器内。HAL库操作USART_DR寄存器发送数据的函数是:

1
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);

HAL库通过中断操作USART_DR寄存器读取串口接收到的数据的函数是:

1
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

三.引脚说明与硬件连接

板载一个串口(USART3)连接到单片机stm32f103c8t6(JTAG功能)的串口,STM32F407可以通过USART3和上位机通信。

图9.1为STM32F407的USART3(PB10,PB11)和stm32f103c8t6的串口硬件连接图。表9.1为其引脚说明。

9.1 UART硬件连接图

9.1 UART引脚说明

设备名 引脚号
USART3_RX PB11
USART3_TX PB10

四.程序设计

本节,我们首先讲解使用HAL库配置串口的一般步骤。然后我们会具体讲解我们串口实验程序实现。

和其他外设一样,HAL库为串口的使用开放了MSP函数。在串口初始化函数HAL_UART_Init内部,会调用串口MSP函数HAL_UART_MspInit来设置与MCU相关的配置。

根据前面的讲解,函数HAL_UART_Init主要用来初始化与串口相关的参数(这些参数与MCU无关),包括波特率,停止位等。而串口MSP函数HAL_UART_MspInit用来设置GPIO初始化,NVIC配置等于MCU相关的配置。

这里我们在main函数调用cube生成的初始化函数MX_USART3_UART_Init初始化串口参数配置,具体函数如下:

1
void MX_USART3_UART_Init(void);

串口MSP函数HAL_UART_MspInit用来设置GPIO初始化,NVIC配置等于MCU相关的配置。

1
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle);

通过上面两个函数,我们就配置了串口相关设置。接下来就是编写中断服务函数USART3_IRQHandler。我们把中断服务函数USART3_IRQHandler从写到debug.c文件中,需要将stm32f4xx_it.h的中断服务函数USART3_IRQHandler注释掉。在HAL库中,对中断服务函数的编写有非常严格的讲究。

首先HAL库定义了一个串口中断处理通用函数HAL_UART_IRQHandler,该函数声明如下:

1
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart);

该函数只有一个入口参数就是UART_HandleTypeDef结构体指针类型的串口句柄huart,我们在调用HAL_UART_Init函数时设置的同一个变量即可。该函数一般在中断服务函数中调用,作为串口中断处理的通用入口。一般调用方法为:

1
2
3
4
void USART3_IRQHandler(void){
HAL_UART_IRQHandler(&usart3); //调用HAL库中断处理公用函数
//中断处理完成后的结束工作
}

也就是说,真正的串口中断处理逻辑我们会最终在函数HAL_UART_IRQHandler内部执行。而该函数是HAL库已经定义好,而且用户一般不能随意修改。这个时候大家会问,那么我们的中断控制逻辑编写在哪里呢?为了把这个问题讲解清楚,我们要来看看函数HAL_UART_IRQHandler内部具体实现过程。因为本章实验,我们主要实现的是串口中断接收,也就是每次接收到一个字符后进入中断服务函数来处理。所以我们就以中断接收为例给大家讲解。这里为了篇幅考虑,我们仅仅列出串口中断执行流程中与接收相关的源码。函数HAL_UART_IRQHandler关于串口接收相关源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart){
uint32_t isrflags = READ_REG(huart->Instance->SR);
uint32_t cr1its = READ_REG(huart->Instance->CR1);
/* UART in mode Receiver -------------------------------------------------*/
if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
{
UART_Receive_IT(huart);
return;
}
//省略部分代码
}

从代码逻辑可以看出,在函数HAL_UART_IRQHandler内部通过判断中断类型是否为接收完成中断,确定是否调用HAL另外一个函数UART_Receive_IT()。函数UART_Receive_IT()的作用是把每次中断接收到的字符保存在串口句柄的缓存指针pRxBuffPtr中,同时每次接收一个字符,其计数器RxXferCount减1,直到接收完成RxXferSize个字符之后RxXferCount设置为0,同时调用接收完成回调函数HAL_UART_RxCpltCallback进行处理。为了篇幅考虑,这里我们仅列出UART_Receive_IT()函数调用回调函数HAL_UART_RxCpltCallback的处理逻辑,代码如下:

1
2
3
4
5
6
7
8
9
static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)
{
//省略部分代码
if (--huart->RxXferCount == 0U)
{
HAL_UART_RxCpltCallback(huart);
}
}

最后我们列出串口接收中断的一般流程,如图所示:

这里,我们再把串口接收中断的一般流程进行概括:当接收到一个字符之后,在函数UART_Receive_IT中会把数据保存在串口句柄的成员变量pRxBuffPtr缓存中,同时RxXferCount计数器减1。如果我们设置RxXferSize=10,那么当接收到10个字符之后,RxXferCount会由10减到0(RxXferCount初始值等于RxXferSize),这个时候再调用接收完成回调函数HAL_UART_RxCpltCallback进行处理。接下来我们看看我们的配置。

在main函数中我们再开启中断接收使能。

1
__HAL_UART_ENABLE_IT(&huart3, UART_IT_RXNE);

在进入接收中断中,当接收到一个字符处理完成功,我们需要再开启中断,调用HAL_UART_Receive_IT函数后,除了开启接收中断外还确定了每次接收RXBUFFERSIZE个字符后标示接收结束从而进入回调函数HAL_UART_RxCpltCallback进行相应处理。aRxBuffer是我们定义的一个全局数组变量,RXBUFFERSIZE是我们定义的一个标识符:

1
2
3
#define RXBUFFERSIZE   1 
uint8_t aRxBuffer[RXBUFFERSIZE];

最后我们看看HAL_UART_RxCpltCallback函数定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{

if(huart->Instance==USART3)//如果是串口3
{
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了0x0d
{
if(aRxBuffer[0]!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else
{
USART_RX_STA|=0x8000; //接收完成了
}
}
else //还没收到0X0D
{
if(aRxBuffer[0]==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=aRxBuffer[0] ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
}
}

因为我们设置了串口句柄成员变量RxXferSize为1,也就是每当串口1发生了接收完成中断后(接收到一个字符),就会跳到该函数执行。当串口接受到一个字符后,它会保存在缓存aRxBuffer中,由于我们设置了缓存大小为1,而且RxXferSize=1,所以每次接受一个字符,回直接保存到RxXferSize[0]中,我们直接通过读取RxXferSize[0]的值就是本次接收到的字符。这里我们设计了一个小小的接收协议:通过这个函数,配合一个数组USART_RX_BUF[],一个接收状态寄存器USART_RX_STA(此寄存器其实就是一个全局变量,由作者自行添加。由于它起到类似寄存器的功能,这里暂且称之为寄存器)实现对串口数据的接收管理。USART_RX_BUF的大小由USART_REC_LEN定义,也就是一次接收的数据最大不能超过USART_REC_LEN个字节。USART_RX_STA是一个接收状态寄存器其各的定义如表所示:

设计思路如下:

当接收到从电脑发过来的数据,把接收到的数据保存在USART_RX_BUF中,同时在接收状态寄存器(USART_RX_STA)中计数接收到的有效数据个数,当收到回车(回车的表示由2个字节组成:0X0D和0X0A)的第一个字节0X0D时,计数器将不再增加,等待0X0A的到来,而如果0X0A没有来到,则认为这次接收失败,重新开始下一次接收。如果顺利接收到0X0A,则标记USART_RX_STA的第15位,这样完成一次接收,并等待该位被其他程序清除,从而开始下一次的接收,而如果迟迟没有收到0X0D,那么在接收数据超过USART_REC_LEN的时候,则会丢弃前面的数据,重新接收。

在函数USART3_IRQHandler的结尾还有几行行代码,其中部分代码是超时退出逻辑,关键逻辑代码如下:

1
2
while (HAL_UART_GetState(huart_debug) != HAL_UART_STATE_READY)//等待就绪 
while(HAL_UART_Receive_IT(huart_debug, (uint8_t *)aRxBuffer, RXBUFFERSIZE) != HAL_OK)//一次处理完成之后,重新开启中断并设置RxXferCount为1

第一行代码是判断串口是否就绪,如果没有就绪就等待就绪。第二行代码是继续调用HAL_UART_Receive_IT函数来开启中断和重新设置RxXferSize和RxXferCount的初始值为1,也就是开启新的接收中断。

最后我们来看一下主函数main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int main(void)
{
uint16_t len=0; //串口接收字符长度;
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C2_Init();
MX_SPI5_Init();
MX_USART3_UART_Init();

//开起串口3接收中断
__HAL_UART_ENABLE_IT(&huart3, UART_IT_RXNE);
HAL_UART_Transmit(&huart3,uartSendTitle,sizeof(uartSendTitle),1000); //发送接收到的数据
while(__HAL_UART_GET_FLAG(&huart3,UART_FLAG_TC)!=SET); //等待发送结束
while (1)
{
if(USART_RX_STA&0x8000)
{
len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
printf("\r\nsend data:\r\n");
HAL_UART_Transmit(&huart3,(uint8_t*)USART_RX_BUF,len,1000);//发送接收到的数据
while(__HAL_UART_GET_FLAG(&huart3,UART_FLAG_TC)!=SET);
USART_RX_STA=0;
}
}
}

这段代码逻辑比较简单,首先判断全局变量USART_RX_STA的最高位是否为1,如果为1的话,那么代表前一次数据接收已经完成,接下来就是把我们自定义接收缓冲的数据发送到串口。接下来我们重点以下两句:

1
2
3
HAL_UART_Transmit(&huart3,uartSendTitle,sizeof(uartSendTitle),1000);	//发送接收到的数据
while(__HAL_UART_GET_FLAG(&huart3,UART_FLAG_TC)!=SET); //等待发送结束

第一句,其实就是调用HAL串口发送函数HAL_UART_Transmit来发送一个字符到串口。第二句呢,就是我们发送一个字节之后之后,要检测这个数据是否已经被发送完成了。

串口还可以通过中断的方式发送,再while循环中,我们使用中断方式发送:

1
HAL_UART_Transmit_IT(&huart3,(uint8_t*)USART_RX_BUF,len);	//发送接收到的数据

在实验中,我们经常需要需要用到printf函数,将调试信息打印到串口上,我们只需要从定义fputc函数,从定义如下:

1
2
3
4
5
6
7
8
9
#define PUTCHAR_PROTOTYPE  int  fputc(int ch, FILE *f)
static uint8_t s_usart_tmp;
int fputc(int ch,FILE *f)
{
s_usart_tmp=(uint8_t)(ch);
HAL_UART_Transmit(huart_debug,&s_usart_tmp,1,10);
return ch;
}

这段代码我们添加在debug.c文件中,实现printf函数从映射。

五.实验结果

我们打开上位机串口软件,串口连接为开发板的DAP-LINK接口(我的电脑是COM4,另外,请注意:波特率是115200),我们把程序下载到STM32F407开发板,可以在上位机看到串口打印的提示信息。

从图中可以看出串口发送数据没有问题,单片机能够发送数据到上位机。

在单片机接收程序中,因为我们在程序上面设置了必须输入回车,串口才认可接收到的数据,所以必须在发送数据后再发送一个回车符,上位机提供的发送方法是通过勾选发送新行实现,只要勾选了这个选项,每次发送数据后,都会自动多发一个回车(0X0D+0X0A)。设置好了发送新行,我们再在发送区输入你想要发送的文字,然后单击发送,可以得到如图所示结果:

六.STM32CubeMx配置串口

本小节讲解使用STM32CubeMX配置串口方法。我们仅讲解串口的配置,其他部分不讲解

我们配置串口3,所以首先我们要使能串口3,然后设置相应通信模式。我们开启串口3的异步模式,并且不使用硬件流控制,我们还需要配置USART3外设相关的参数,包括波特率,停止位等。

在STM32CubeMX中,当我们选择好外设的工作模式之后,软件会自动配置GPIO口的相关模式和参数。在Pinout界面我们看看芯片引脚图会发现,PB10和PB11端口的模式会自动复用为发送和接收模式

NVIC Setting选项卡用来使能USART3中断。这里我们勾上Enabled选项。

NVIC
Configuration界面,我们首先设置中断优先级分组级别,我们系统初始化设置为2,为2位抢占优先级。所以这里的参数我们选择“2
bits for pre-emption priority”,也就是2位抢占优先级。

关于使用STM32CubeMX配置串口的方法就给大家介绍到这里。