1)资料下载:点击资料即可下载

2)对正点原子Linux感兴趣的同学可以加群讨论:935446741

3)关注正点原子公众号,获取最新资料更新

http://weixin.qq.com/r/hEhUTLbEdesKrfIv9x2W (二维码自动识别)

前面三章介绍了 STM32F4 的 IO 口操作。这一章我们将学习 STM32F4 的串口,教大家如

何使用 STM32F4 的串口来发送和接收数据。本章将实现如下功能:STM32F4 通过串口和上位

机的对话,STM32F4 在收到上位机发过来的字符串后,原原本本的返回给上位机。本章分为如

下几个小节:

9.1 STM32F4 串口简介

9.2 硬件设计

9.3 软件设计

9.4 下载验证

9.1 STM32F4 串口简介

串口作为 MCU 的重要外部接口,同时也是软件开发重要的调试手段,其重要性不言而喻。

现在基本上所有的 MCU 都会带有串口,STM32 自然也不例外。

STM32F4 的串口资源相当丰富的,功能也相当强劲。ALIENTEK 探索者 STM32F4 开发板

所使用的 STM32F407ZGT6 最多可提供 6 路串口,有分数波特率发生器、支持同步单线通信和

半双工单线通讯、支持 LIN、支持调制解调器操作、智能卡协议和 IrDA SIR ENDEC 规范、具

有 DMA 等。

5.3 节对串口有过简单的介绍,大家看这个实验的时候记得翻过去看看。接下来我们将主要

从库函数操作层面结合寄存器的描述,告诉你如何设置串口,以达到我们最基本的通信功能。

本章,我们将实现利用串口 1 不停的打印信息到电脑上,同时接收从串口发过来的数据,把发

送过来的数据直接送回给电脑。探索者 STM32F4 开发板板载了 1 个 USB 串口和 2 个 RS232 串

口,我们本章介绍的是通过 USB 串口和电脑通信。

在 4.4.章节端口复用功能已经讲解过,对于复用功能的 IO,我们首先要使能 GPIO 时钟,

然后使能相应的外设时钟,同时要把 GPIO 模式设置为复用。这些准备工作做完之后,剩下的

当然是串口参数的初始化设置,包括波特率,停止位等等参数。在设置完成只能接下来就是使

能串口,这很容易理解。同时,如果我们开启了串口的中断,当然要初始化 NVIC 设置中断优先

级别,最后编写中断服务函数。

串口设置的一般步骤可以总结为如下几个步骤:

1) 串口时钟使能,GPIO 时钟使能。

2) 设置引脚复用器映射:调用 GPIO_PinAFConfig 函数。

3) GPIO 初始化设置:要设置模式为复用功能。

4) 串口参数初始化:设置波特率,字长,奇偶校验等参数。

5) 开启中断并且初始化 NVIC,使能中断(如果需要开启中断才需要这个步骤)。

6) 使能串口。

7) 编写中断处理函数:函数名格式为 USARTxIRQHandler(x 对应串口号)。

下面,我们就简单介绍下这几个与串口基本配置直接相关的几个 HAL 库函数。这些函数

和定义主要分布在 stm32f4xx_hal_usart.h 和 stm32f4xx_hal_usart.c 文件中。

1) 串口时钟和 GPIO 时钟使能。

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

初始化函数 HAL_UART_Init 相关知识,定义如下:

HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart);

该函数只有一个入口参数 huart,为 UART_HandleTypeDef 结构体指针类型,我们俗称其为

串口句柄,它的使用会贯穿整个串口程序。一般情况下,我们会定义一个 UART_HandleTypeDef

结构体类型全局变量,然后初始化各个成员变量。接下来我们看看结构体 UART_HandleTypeDef

的定义:

typedef struct

{

USART_TypeDef *Instance;

UART_InitTypeDef Init;

uint8_t *pTxBuffPtr;

uint16_t TxXferSize;

uint16_t TxXferCount;

uint8_t *pRxBuffPtr;

uint16_t RxXferSize;

uint16_t RxXferCount;

DMA_HandleTypeDef *hdmatx;

DMA_HandleTypeDef *hdmarx;

HAL_LockTypeDef Lock;

__IO HAL_UART_StateTypeDef State;

__IO uint32_t ErrorCode;

}UART_HandleTypeDef;

该结构体成员变量非常多,一般情况下载调用函数 HAL_UART_Init 对串口进行初始化的

时候,我们只需要先设置 Instance 和 Init 两个成员变量的值。接下来我们依次解释一下各个成

员变量的含义。

Instance 是 USART_TypeDef 结构体指针类型变量,它是执行寄存器基地址,实际上这个基

地址 HAL 库已经定义好了,如果是串口 1,取值为 USART1 即可。

Init 是 UART_InitTypeDef 结构体类型变量,它是用来设置串口的各个参数,包括波特率,

停止位等,它的使用方法非常简单。UART_InitTypeDef 结构体定义如下:

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 库处理过程状态标志位和串口通信的错误码。

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

UART_HandleTypeDef UART1_Handler; //UART 句柄

UART1_Handler.Instance=USART1; //USART1

UART1_Handler.Init.BaudRate=115200; //波特率

UART1_Handler.Init.WordLength=UART_WORDLENGTH_8B; //字长为 8 位格式

UART1_Handler.Init.StopBits=UART_STOPBITS_1; //一个停止位

UART1_Handler.Init.Parity=UART_PARITY_NONE; //无奇偶校验位

UART1_Handler.Init.HwFlowCtl=UART_HWCONTROL_NONE; //无硬件流控

UART1_Handler.Init.Mode=UART_MODE_TX_RX; //收发模式

HAL_UART_Init(&UART1_Handler); //HAL_UART_Init()会使能 UART1

这里我们需要说明的是,函数 HAL_UART_Init 内部会调用串口使能函数使能相应串口,

所以调用了该函数之后我们就不需要重复使能串口了。当然,HAL 库也提供了具体的串口使能

和关闭方法,具体使用方法如下:

__HAL_UART_ENABLE(handler); //使能句柄 handler 指定的串口

__HAL_UART_DISABLE(handler); //关闭句柄 handler 指定的串口

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

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

void HAL_UART_MspInit(UART_HandleTypeDef *huart);

我们在程序中,只需要重写该函数即可。一般情况下,该函数内部用来编写 IO 口初始化,

时钟使能以及 NVIC 配置。

2)GPIO 口初始化设置(速度,上下拉等)以及复用映射配置

我们在跑马灯实验中讲解过,在 HAL 库中 IO 口初始化参数设置和复用映射配置是在函数

HAL_GPIO_Init 中一次性完成的。这里大家只需要注意,我们要复用 PA9 和 PA10 为串口发送

接收相关引脚,我们需要配置 IO 口为复用,同时复用映射到串口 1。配置源码如下:

GPIO_InitTypeDef GPIO_Initure;

GPIO_Initure.Pin=GPIO_PIN_9|GPIO_PIN_10; //PA9/PA10

GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出

GPIO_Initure.Pull=GPIO_PULLUP; //上拉

GPIO_Initure.Speed=GPIO_SPEED_FAST; //高速

GPIO_Initure.Alternate=GPIO_AF7_USART1; //复用为 USART1

HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化 PA9/PA10

3)开启串口相关中断,配置串口中断优先级

HAL 库中定义了一个使能串口中断的标识符__HAL_UART_ENABLE_IT,大家可以把它当

一个函数来使用,具体定义请参考 HAL 库文件 stm32f4xx_hal_uart.h 中该标识符定义。例如我

们要使能接收完成中断,方法如下:

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

第一个参数为我们步骤 1 讲解的串口句柄,类型为 UART_HandleTypeDef 结构体类型。第

二个参数为我们要开启的中断类型值,可选值在头文件 stm32f4xx_hal_uart.h 中有宏定义。

有开启中断就有关闭中断,操作方法为:

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

对于中断优先级配置,方法就非常简单,详细知识情参考 4.5 小节相关知识。参考方法为:

HAL_NVIC_EnableIRQ(USART1_IRQn); //使能 USART1 中断通道

HAL_NVIC_SetPriority(USART1_IRQn,3,3); //抢占优先级 3,子优先级 3

4) 编写中断服务函数

串口 1 中断服务函数为:

void USART1_IRQHandler(void) ;

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

的逻辑代码即可。HAL 库实际上对中断处理过程进行了完整的封装,具体内容我们在 8.3 小节

通过结合实验源码给大家详细讲解。

5) 串口数据接收和发送

STM32F4 的发送与接收是通过数据寄存器 USART_DR 来实现的,这是一个双寄存器,包

含了 TDR 和 RDR。当向该寄存器写数据的时候,串口就会自动发送,当收到数据的时候,也

是存在该寄存器内。HAL 库操作 USART_DR 寄存器发送数据的函数是:

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart,

uint8_t *pData, uint16_t Size, uint32_t Timeout);

通过该函数向串口寄存器 USART_DR 写入一个数据。

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

HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart,

uint8_t *pData, uint16_t Size, uint32_t Timeout);

通过该函数可以读取串口接受到的数据。

通过以上一些寄存器的操作外加一下 IO 口的配置,我们就可以达到串口最基本的配置了,

关于串口更详细的介绍,请参考《STM32F4XX 中文参考手册》第 676 页至 720 页,通用同步

异步收发器这一章节。

9.2 硬件设计

本实验需要用到的硬件资源有:

1) 指示灯 DS0

2) 串口 1

串口 1 之前还没有介绍过,本实验用到的串口 1 与 USB 串口并没有在 PCB 上连接在一起,

需要通过跳线帽来连接一下。这里我们把 P6 的 RXD 和 TXD 用跳线帽与 PA9 和 PA10 连接起

来。如图 9.2.1 所示:

图 9.2.1 硬件连接图示意图

连接上这里之后,我们在硬件上就设置完成了,可以开始软件设计了。

9.3 软件设计

本章的代码设计,比前两章简单很多,因为我们的串口初始化代码和接收代码就是用我们

之前介绍的 SYSTEM 文件夹下的串口部分的内容。这里我们对代码部分稍作讲解。

打开串口实验工程,然后在SYSTEM组下双击usart.c,我们就可以看到该文件里面的代码,

先介绍 uart_init 函数,该函数代码如下:

//初始化 IO 串口 1

//bound:波特率

void uart_init(u32 bound)

{

//UART 初始化设置

UART1_Handler.Instance=USART1; //USART1

UART1_Handler.Init.BaudRate=bound; //波特率

UART1_Handler.Init.WordLength=UART_WORDLENGTH_8B; //字长为 8 位数据格式

UART1_Handler.Init.StopBits=UART_STOPBITS_1; //一个停止位

UART1_Handler.Init.Parity=UART_PARITY_NONE; //无奇偶校验位

UART1_Handler.Init.HwFlowCtl=UART_HWCONTROL_NONE; //无硬件流控

UART1_Handler.Init.Mode=UART_MODE_TX_RX; //收发模式

HAL_UART_Init(&UART1_Handler); //HAL_UART_Init()会使能 UART1

HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE);

//该函数会开启接收中断:标志位 UART_IT_RXNE,并且设置接收缓冲以

//及接收缓冲接收最大数据量

}

该函数实现的是我们 8.1 小节讲解的步骤 1 的内容。同时这里大家需要注意,最后一行代码调

用函数 HAL_UART_Receive_IT,作用是开启接收中断,同时设置接收的缓存区以及接收的数

据量,对于这个缓冲我们在后面会给大家讲解它的作用。

串口 MSP 函数 HAL_UART_MspInit 函数我们自定义了其内容,代码如下:

void HAL_UART_MspInit(UART_HandleTypeDef *huart)

{

//GPIO 端口设置

GPIO_InitTypeDef GPIO_Initure;

if(huart->Instance==USART1) //如果是串口 1,进行串口 1 MSP 初始化

{

__HAL_RCC_GPIOA_CLK_ENABLE(); //使能 GPIOA 时钟

__HAL_RCC_USART1_CLK_ENABLE(); //使能 USART1 时钟

GPIO_Initure.Pin=GPIO_PIN_9; //PA9

GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出

GPIO_Initure.Pull=GPIO_PULLUP; //上拉

GPIO_Initure.Speed=GPIO_SPEED_FAST; //高速

GPIO_Initure.Alternate=GPIO_AF7_USART1; //复用为 USART1

HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化 PA9

GPIO_Initure.Pin=GPIO_PIN_10; //PA10

HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化 PA10

#if EN_USART1_RX

HAL_NVIC_EnableIRQ(USART1_IRQn); //使能 USART1 中断通道

HAL_NVIC_SetPriority(USART1_IRQn,3,3); //抢占优先级 3,子优先级 3

#endif

}

}

该函数代码实现的是我们 8.1 小节讲解的步骤 2 到 4 的内容。这里大家需要注意,在该段

代码中,通过判断宏定义标识符 EN_USART1_RX 的值来确定是否开启串口中断通道和设置串

口 1 中断优先级。标识符 EN_USART1_RX 在头文件 usart.h 中有定义,默认情况下我们设置为

1。

#define EN_USART1_RX 1 //使能(1)/禁止(0)串口 1 接收

通过上面两个函数,我们就配置了串口相关设置。接下来就是编写中断服务函数

USART1_IRQHandler。而 HAL 库中,对中断服务函数的编写有非常严格的讲究。

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

void HAL_UART_IRQHandler(UART_HandleTypeDef *huart);

该函数只有一个入口参数就是 UART_HandleTypeDef 结构体指针类型的串口句柄 huart,使

用我们在调用 HAL_UART_Init 函数时设置的同一个变量即可。该函数一般在中断服务函数中

调用,作为串口中断处理的通用入口。一般调用方法为:

void USART1_IRQHandler(void)

{

HAL_UART_IRQHandler(&UART1_Handler); //调用 HAL 库中断处理公用函数

…//中断处理完成后的结束工作

}

也就是说,真正的串口中断处理逻辑我们会最终在函数 HAL_UART_IRQHandler 内部执行。

而该函数是 HAL 库已经定义好,而且用户一般不能随意修改。这个时候大家会问,那么我们

的 中 断 控 制 逻 辑 编 写 在 哪 里 呢 ? 为 了 把 这 个 问 题 讲 解 清 楚 , 我 们 要 来 看 看 函 数

HAL_UART_IRQHandler 内部具体实现过程。因为本章实验,我们主要实现的是串口中断接收,

也就是每次接收到一个字符后进入中断服务函数来处理。所以我们就以中断接收为例给大家讲

解。这里为了篇幅考虑,我们仅仅列出串口中断执行流程中与接收相关的源码。

函数 HAL_UART_IRQHandler 关于串口接收相关源码如下:

void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)

{

uint32_t tmp1 = 0, tmp2 = 0;

…//此处省略部分代码

tmp1 = __HAL_UART_GET_FLAG(huart, UART_FLAG_RXNE);

tmp2 = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_RXNE);

if((tmp1 != RESET) && (tmp2 != RESET))

{

UART_Receive_IT(huart);

}

…//此处省略部分代码

}

从代码逻辑可以看出,在函数 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 的处理逻辑,代码

如下:

static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)

{

...//此处省略部分代码

if(--huart->RxXferCount == 0)

{

HAL_UART_RxCpltCallback(huart);

}

...//此处省略部分代码

}

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

图 8.3.1 串口接收中断执行流程图

这里,我们再把串口接收中断的一般流程进行概括:当接收到一个字符之后,在函数

UART_Receive_IT中会把数据保存在串口句柄的成员变量pRxBuffPtr缓存中,同时RxXferCount

计数器减 1。如果我们设置 RxXferSize=10,那么当接收到 10 个字符之后,RxXferCount 会由 10

减到 0(RxXferCount 初始值等于 RxXferSize),这个时候再调用接收完成回调函数

HAL_UART_RxCpltCallback 进行处理。接下来我们看看我们的配置。

首先,我们回到用户函数 uart_init 定义可以看到,在 uart_init 函数中调用完 HAL_UART_Init

后我们还调用了 HAL_UART_Receive_IT 开启接收中断,并且初始化串口句柄的缓存相关参数。

代码如下:

HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE);

而 aRxBuffer 是我们定义的一个全局数组变量,RXBUFFERSIZE 是我们定义的一个标识符:

#define RXBUFFERSIZE 1

u8 aRxBuffer[RXBUFFERSIZE];

所以,调用 HAL_UART_Receive_IT 函数后,除了开启接收中断外还确定了每次接收

RXBUFFERSIZE 个字符后标示接收结束从而进入回调函数 HAL_UART_RxCpltCallback 进行相

应处理。最后我们看看 HAL_UART_RxCpltCallback 函数定义:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)

{

if(huart->Instance==USART1)//如果是串口 1

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 是一个接收状态寄存器其各的定义如表 8.3.2 所

示:

表 8.3.2 接收状态寄存器位定义表

设计思路如下:

当接收到从电脑发过来的数据,把接收到的数据保存在 USART_RX_BUF 中,同时在接收状态

寄存器(USART_RX_STA)中计数接收到的有效数据个数,当收到回车(回车的表示由 2 个字

节组成:0X0D 和 0X0A)的第一个字节 0X0D 时,计数器将不再增加,等待 0X0A 的到来,而

如果 0X0A 没有来到,则认为这次接收失败,重新开始下一次接收。如果顺利接收到 0X0A,

则标记 USART_RX_STA 的第 15 位,这样完成一次接收,并等待该位被其他程序清除,从而开

始下一次的接收,而如果迟迟没有收到0X0D,那么在接收数据超过USART_REC_LEN的时候,

则会丢弃前面的数据,重新接收。

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

键逻辑代码如下:

while (HAL_UART_GetState(&UART1_Handler) != HAL_UART_STATE_READY);

while(HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, 1) != HAL_OK);

这两行代码作用非常简单。第一行代码是判断串口是否就绪,如果没有就绪就等待就绪。第二

行代码是继续调用 HAL_UART_Receive_IT 函数来开启中断和重新设置 RxXferSize 和

RxXferCount 的初始值为 1,也就是开启新的接收中断。

学到这里大家会发现,HAL 库定义的串口中断逻辑确实非常复杂,并且因为处理过程繁

琐所以效率不高。这里我们需要说明的是,在中断服务函数中,大家也可以不用调用

HAL_UART_IRQHandler 函数,而是直接编写自己的中断服务函数。串口实验我们之所以遵

循 HAL 库写法, 是为了让大家对 HAL 库有一个更清晰的理解。

如果我们不用中断处理回调函数,那么就不用初始化串口句柄的中断接收缓存,所以我们

HAL_UART_Receive_IT 函数就不用出现在初始化函数 uart_init 中,而是直接在要开启中断的

地方通过调用__HAL_UART_ENABLE_IT 单独开启中断即可。如果不用中断回调函数处理,中

断服务函数内容为:

//串口 1 中断服务程序

void USART1_IRQHandler(void)

{

u8 Res;

#if SYSTEM_SUPPORT_OS //使用 OS

OSIntEnter();

#endif

if((__HAL_UART_GET_FLAG(&UART1_Handler,UART_FLAG_RXNE)!=RESET))

//接收中断(接收到的数据必须是 0x0d 0x0a 结尾)

{

HAL_UART_Receive(&UART1_Handler,&Res,1,1000);

if((USART_RX_STA&0x8000)==0)//接收未完成

{

if(USART_RX_STA&0x4000)//接收到了 0x0d

{

if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始

else USART_RX_STA|=0x8000; //接收完成了

}

else //还没收到 0X0D

{

if(Res==0x0d)USART_RX_STA|=0x4000;

else

{

USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;

USART_RX_STA++;

if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;

//接收数据错误,重新开始接收

}

}

}

}

HAL_UART_IRQHandler(&UART1_Handler);

#if SYSTEM_SUPPORT_OS //使用 OS

OSIntExit();

#endif

}

这段代码逻辑跟上面的中断回调函数类似,只不过这里还需要通过 HAL 库串口接收函数

HAL_UART_Receive 来获取接收到的字符进行相应的处理,这里我们就不做过多讲解。在我们

后面很多实验,为了效率和处理逻辑方便,我们会选择将接收控制逻辑直接编写在中断服务函

数内部。

HAL 库一共提供了 5 个中断处理回调函数:

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);//发送完成回调函数

void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);//发送完成过半

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);//接收完成回调函数

void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);//接收完成过半

void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);//错误处理回调函数

介绍完了这两个函数,我们回到 main.c,对于 main.c 前面引入的头文件为了篇幅考虑,我

们后面的实验不再列出,详情请参考我们实验代码即可。主函数代码如下:

int main(void)

{

u8 len; u16 times=0;

HAL_Init(); //初始化 HAL 库

Stm32_Clock_Init(336,8,2,7); //设置时钟,168Mhz

delay_init(168); //初始化延时函数

uart_init(115200); //初始化 USART

LED_Init(); //初始化 LED

KEY_Init(); //初始化按键

LED_Init(); //初始化与 LED 连接的硬件接口

LED0=0; //先点亮红灯

while(1)

{

if(USART_RX_STA&0x8000)

{

len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度

printf("rn 您发送的消息为:rn");

for(t=0;t<len;t++)

{

USART1->DR=USART_RX_BUF[t];

while((USART1->SR&0X40)==0);//等待发送结束

}

printf("rnrn");//插入换行

USART_RX_STA=0;

}else

{

times++;

if(times%5000==0)

{ printf("rnALIENTEK 探索者 STM32F407 开发板 串口实验rn");

printf("正点原子@ALIENTEKrnrnrn");

}

if(times%200==0)printf("请输入数据,以回车键结束rn");

if(times%30==0)LED0=!LED0;//闪烁 LED,提示系统正在运行.

delay_ms(10);

}

}

}

这段代码比较简单,调用 uart_init 函数,设置波特率为 115200。接下来我们重点看下以下

两句:

USART1->DR=USART_RX_BUF[t];

while((USART1->SR&0X40)==0);//等待发送结束

第一句,其实就是发送一个字节到串口。第二句呢,就是我们在我们发送一个数据到串口

之后,要检测这个数据是否已经被发送完成了。

其他的代码比较简单,我们执行编译之后可以看到,没有任何错误和警告,下面我们可以

开始下载验证了。

9.4 下载验证

我们把程序下载到探索者 STM32F4 开发板,可以看到板子上的 DS0 开始闪烁,说明程序

已经在跑了。串口调试助手,串口调试助手,我们用 XCOM V2.0,该软件在光盘有提供,且无

需安装,直接可以运行,但是需要你的电脑安装有.NET Framework 4.0(WIN7 直接自带了)或以

上版本的环境才可以,该软件的详细介绍请看:ALIENTEK 发布官方串口调试助手ATK-XCOM V2.0,支持协议传输(可IAP串口升级PK MCUISP) 功能强大,使用方便,欢迎大家下载测试! 这

个帖子。

接着我们打开 XCOM V2.0,设置串口为开发板的 USB 转串口(CH340 虚拟串口,得根据

你自己的电脑选择,我的电脑是 COM3,另外,请注意:波特率是 115200),可以看到如图 9.4.1

所示信息:

图 9.4.1 串口调试助手收到的信息

从图 9.4.1 可以看出,STM32F4 的串口数据发送是没问题的了。但是,因为我们在程序上

面设置了必须输入回车,串口才认可接收到的数据,所以必须在发送数据后再发送一个回车符,

这里 XCOM 提供的发送方法是通过勾选发送新行实现,如图 9.4.1,只要勾选了这个选项,每

次发送数据后,XCOM 都会自动多发一个回车(0X0D+0X0A)。设置好了发送新行,我们再在发

送区输入你想要发送的文字,然后单击发送,可以得到如图 9.4.2 所示结果:

图 9.4.2 发送数据后收到的数据

可以看到,我们发送的消息被发送回来了(图中圈圈内)。大家可以试试,如果不发送回车 (取消发送新行),在输入内容之后,直接按发送是什么结果。

FPGA 串口中断_正点原子【STM32-F407探索者】第九章 串口通信实验相关推荐

  1. 【正点原子STM32连载】第二十章 基本定时器实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1

    1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频 ...

  2. 【正点原子STM32连载】第四十一章 无线通信实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1

    1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频 ...

  3. 【正点原子STM32连载】 第三十章 DMA实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1

    1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频 ...

  4. stm32 adc输入电压范围_正点原子【STM32-F407探索者】第二十五章 光敏传感器实验...

    1)资料下载:点击资料即可下载 2)对正点原子Linux感兴趣的同学可以加群讨论:935446741 3)关注正点原子公众号,获取最新资料更新 本章我们将向大家介绍探索者 STM32F4 开发板自带的 ...

  5. 25 linux ndk 头文件_正点原子Linux第二十五章RTC实时时钟实验

    1)资料下载:点击资料即可下载 2)对正点原子Linux感兴趣的同学可以加群讨论:935446741 3)关注正点原子公众号,获取最新资料更新 第二十五章RTC实时时钟实验 实时时钟是很常用的一个外设 ...

  6. 正点原子探索者原理图_正点原子【STM32-F407探索者】第六章 跑马灯实验

    1)资料下载:点击资料即可下载 2)对正点原子Linux感兴趣的同学可以加群讨论:935446741 3)关注正点原子公众号,获取最新资料更新 http://weixin.qq.com/r/hEhUT ...

  7. 基于stm32f429的手写识别_正点原子【STM32-F407探索者】第五十三章 手写识别实验...

    1)资料下载:点击资料即可下载 2)对正点原子Linux感兴趣的同学可以加群讨论:935446741 3)关注正点原子公众号,获取最新资料更新 现在几乎所有带触摸屏的手机都能实现手写识别.本章,我们将 ...

  8. 正点原子探索者原理图_正点原子【STM32-F407探索者】第二十六章 DAC 实验

    1)资料下载:点击资料即可下载 2)对正点原子Linux感兴趣的同学可以加群讨论:935446741 3)关注正点原子公众号,获取最新资料更新 http://weixin.qq.com/r/hEhUT ...

  9. 正点原子的内存管理_正点原子【STM32-F407探索者】第四十二章 内存管理实验

    1)资料下载:点击资料即可下载 2)对正点原子Linux感兴趣的同学可以加群讨论:935446741 3)关注正点原子公众号,获取最新资料更新 上一章,我们学会了使用 STM32F4 驱动外部 SRA ...

  10. 光盘显示0字节可用_正点原子【STM32-F407探索者】第四十五章 汉字显示实验

    1)资料下载:点击资料即可下载 2)对正点原子Linux感兴趣的同学可以加群讨论:935446741 3)关注正点原子公众号,获取最新资料更新 汉字显示在很多单片机系统都需要用到,少则几个字,多则整个 ...

最新文章

  1. docker tomcat 多开 实例_给妈妈讲什么是docker
  2. 改变软件开发者的形象,增加多样性
  3. 深入理解计算机系统:计算机系统概述
  4. php简单网站源码包含数组_PHP无限分级阶梯递归数组排列 以及多层嵌套数组在网页中的解析源码...
  5. 浅谈Flutter的状态State
  6. 利用os模块生成 文件夹和文件
  7. 理解typedef(转)
  8. 8086 按开关灯亮 c语言程序,基于MCS-51的交通灯程序设计(c语言控制直行左转)...
  9. 唐诗辑注 —— 逢雪宿芙蓉山主人、十五夜望月、小儿垂钓
  10. CSS之after与before的content 和 attr 配合使用
  11. mysql 查看数据表大小_关于MySQL 查询表数据大小的总结
  12. HTML5游戏开发5条建议及开发工具分享
  13. PTA-查询水果价格
  14. 清明祭曾祖@20130402
  15. VUCA时代,敏捷团队如何提升效能?
  16. 还在为520礼物发愁吗?教你用python撩女朋友
  17. hadoop HA 架构
  18. 对大脑有益的16种食物_16种食物为大脑添能量 让你工作干劲满满
  19. 模糊神经网络2--基于ANFIS的混沌时间序列预测
  20. 2020-助你直通大厂前端中高级面试题

热门文章

  1. 实战 Nginx 与 PHP(FastCGI)的安装、配置与优化
  2. 【CSON原创】CSS的障眼法:利用border实现图片的翻转
  3. 匆匆的一瞥,错过了一份正确的BIOS……,安装X64系统时错刷BIOS的彻底死机过程以及解决方法...
  4. Java——可能的文本题
  5. tolua中使用protobuf3—集成lua-protobuf
  6. fastjson SerializerFeature 详解
  7. VUE ---- Windows7环境下安装
  8. mysql查询忽略字符编码是什么_MySQL 查询不区分大小写的问题以及编码格式问题...
  9. mysql中的sql在添加的操作中,id或者是某些字段,需要它添加自带的默认值,或者我们并不想对id或者某个字段赋值
  10. python常见报错异常大全,根据异常找出根本问题,一键解决,建议收藏