03:(手撸HAL+CubeMX)串口UART篇一
这里写目录标题
有关GPIO引脚和外部中断的基础知识请参考“寄存器开发入门教程的第9章”
本章使用到的HAL库编程接收解释如下:
/***** RCC_Exported_Functions_Group1 *****/
1.HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart);
•功能:串口异步通信初始化配置。比如配置指定串口USART1,数据帧格式等。
•参数:
•huart:需要配置参数的结构体指针
•UART_HandleTypeDef:参数配置的总的结构体,包含以下的元素:
•USART_TypeDef *Instance:USART_TypeDef 结构体指针变量,USART_TypeDef 里面的元素是USART的寄存器。
所以Instance串口的基地址,而基地址为(USART1_BASE/USART1_BASE/USART3_BASE)。
而宏定义 #define USART1 ((USART_TypeDef *)USART1_BASE)。所以Instance取值如下:
USART1/USART2/USART3
•UART_InitTypeDef Init 参数配置的结构体,包含以下元素:
uint32_t BaudRate :串口波特率
uint32_t WordLength:有效的数据位,可选如下选项:
UART_WORDLENGTH_8B:8位数据位
UART_WORDLENGTH_9B:9位数据位
uint32_t StopBits:有效的停止位,可选如下选项:
UART_STOPBITS_1:1位停止位
UART_STOPBITS_2:2位停止位
uint32_t Parity:奇偶校验位:可选如下选项:
UART_PARITY_NONE:无校验位
UART_PARITY_EVEN:偶校验位
UART_PARITY_ODD :奇校验位
uint32_t Mode:串口的模式,可选如下选项:
UART_MODE_RX :仅接受
UART_MODE_TX :仅发送
UART_MODE_TX_RX:发送接收
uint32_t HwFlowCtl:硬件流控,可选如下选项;
UART_HWCONTROL_NONE :不使用硬件流控
UART_HWCONTROL_RTS :发送方硬件流控
UART_HWCONTROL_CTS :接收方硬件流控
UART_HWCONTROL_RTS_CTS
uint32_t OverSampling:设置采样参数,一般在STM32F100xx中使用
下面参数是HAL库内部设置,我们不需要自己设置:
•uint8_t *pTxBuffPtr:发送的数据缓冲区的指针
•uint16_t TxXferSize:需要被发送的数据的总数
•uint16_t TxXferCount:需要被发送的数据的剩余个数。TxXferSize - TxXferCount:表示已经发送了多少个数据
•uint8_t *pRxBuffPtr:接收的数据缓冲区的指针
•uint16_t RxXferSize:需要被接收的数据总数
•uint16_t RxXferCount:需要被接收的数据的剩余个数。RxXferSize - RxXferCount:表示已经接收了多少个数据
•HAL_LockTypeDef Lock:(串口当前锁定状态),有如下的2个状态
HAL_UNLOCKED = 0x00U:未被锁定(数值0)
HAL_LOCKED = 0x01U:被锁定(数值1)
•HAL_UART_StateTypeDef gState:指示当前全局/发送/接收状态,有如下的状态
HAL_UART_STATE_RESET = 0x00U:尚未初始化
HAL_UART_STATE_READY = 0x20U:初始化并准备使用
HAL_UART_STATE_BUSY = 0x24U:忙
HAL_UART_STATE_BUSY_TX = 0x21U:发送忙
HAL_UART_STATE_BUSY_RX = 0x22U:接收满
HAL_UART_STATE_BUSY_TX_RX= 0x23U:发送接收忙
HAL_UART_STATE_TIMEOUT = 0xA0U:超时
HAL_UART_STATE_ERROR = 0xE0U:错误
•HAL_UART_StateTypeDef RxState:指示当前接收状态,有如下的状态
HAL_UART_STATE_RESET
HAL_UART_STATE_READY
HAL_UART_STATE_BUSY_RX
•uint32_t ErrorCode:指示当编码状态,有如下的状态
•返回值:
HAL_OK = 0x00U,
HAL_ERROR = 0x01U,
HAL_BUSY = 0x02U,
HAL_TIMEOUT = 0x03U
【注意】:此函数中会调用HAL_UART_MspInit(huart)这个函数。用户重写这个函数用于配置串口的引脚端口号
引脚输入/输出模式,等等。
2.HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart);
•功能:串口半双工异步通信配初始化配置。比如配置指定串口USART1,数据帧格式等。
•参数:
同上
3.HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
•功能:发送数据(阻塞方式)。将pData指针指向的缓冲区数据,在设定的Timeout时间内,发送Size字节数据。
•参数:
•huart:调用HAL_UART_Init()配置好的结构体的指针。
•pData:需要被发送的数据的缓冲区空间的头指针
•Size:需要被发送的数据个数
•Timeout:规定超时时间,若还没有发送Size个数据时,数据卡在DR寄存器里面
导致TXE置0,则等待Timeout(ms),若等待超过了Timeout(ms),则停止发送,返回HAL_TIMEOUT
•返回值:
HAL_OK = 0x00U,
HAL_ERROR = 0x01U,
HAL_BUSY = 0x02U,
HAL_TIMEOUT = 0x03U
4.HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
•功能:接收数据(阻塞方式)。在设定的Timeout时间内,接收Size字节数据,保存到pData指针指向的缓冲区
•参数:
•huart:调用HAL_UART_Init()配置好的结构体的指针。
•pData:用于保存接收到数据的缓冲区的头指针
•Size:需要被接收的数据个数
•Timeout:规定超时时间,若还没有接收到Size个数据时,DR中没有数据来了,
则等待Timeout(ms),若等待超过Timeout(ms),则停止接收,返回一个HAL_TIMEOUT
•返回值:
HAL_OK = 0x00U,
HAL_ERROR = 0x01U,
HAL_BUSY = 0x02U,
HAL_TIMEOUT = 0x03U
【注意】一般情况下我们使用串口接收数据不用阻塞方式,使用中断发送接收。
5.HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
•功能:发送数据(中断方式)。将pData指针指向的缓冲区数据,发送Size字节数据
•参数:
•huart:调用HAL_UART_Init()配置好的结构体的指针。
•pData:需要被发送的数据的缓冲区空间的头指针
•Size:需要被接收的数据个数
•返回值:
HAL_OK = 0x00U,
HAL_ERROR = 0x01U,
【注意】中断时发送和中断式接收有很多的细节,之后解释
6.HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
•功能:接收数据(中断方式)。接收Size字节数据,保存到pData指针指向的缓冲区
•参数:
•huart:调用HAL_UART_Init()配置好的结构体的指针。
•pData:用于保存接收到数据的缓冲区的头指针
•Size:需要被接收的数据个数
•返回值:
HAL_OK = 0x00U,
HAL_ERROR = 0x01U,
【注意】中断时发送和中断式接收有很多的细节,之后解释
7.void HAL_UART_IRQHandler(UART_HandleTypeDef *huart);
•功能:串口中断服务器总函数函数,串口的所有中断请求,都会执行这个函数。
这个函数里面有很多的中断分函数
•参数:
•huart:调用HAL_UART_Init()配置好的结构体的指针。
8.void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
•功能:发送数据完成的中断服务函数
•参数:
•huart:调用HAL_UART_Init()配置好的结构体的指针。
9.void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
•功能:接收数据完成的中断服务函数
•参数:
•huart:调用HAL_UART_Init()配置好的结构体的指针。
-
对HAL库串口中断服务函数的详解:
-
接收中断:
①如图: CPU执行HAL_UART_Receive_IT(&huart, Receive_Data, 10)这个函数代表:打开串口接收数据中断仅此而已。如下图所示:
②而CPU将DR数据寄存器中的数据搬运到Receive_Data里面操作是在UART_Receive_IT(huart)函数。
当CPU执行了HAL_UART_Receive_IT(&huart, Receive_Data, 10) 函数,串口的DR寄存器一旦有数据时,串口的RXNE(读数据寄存器非空)会被置1。触发数据接收中断请求。然后CPU就会去执行中断服务总函数,当执行到中断服务分函数UART_Receive_IT(huart)时,CPU将DR中的数据搬运到Receive_Data数组中。如下图所示:
如上图所示:一旦有DR中有1个数据时,CPU就会执行USART1_IRQHandler——>HAL_UART_IRQHandler(&huart1)——>UART_Receive_IT(huart1)函数,将串口中数据搬运到Receive_Data数组里面,然后让RxXferCount减1。所以每搬运1个数据,RxXferCount就会减1。当搬运完最后1个数据时,RxXferCount会减为0,然后CPU就会进入if语句里面去执行关闭串口接收中断。然后执行全部接收完成回调函数。若此时串口的DR还有数据,CPU也不会在进行将DR中的数据搬运到数组里了。
【注意】HAL_UART_Receive_IT(&huart, Receive_Data, 10)会使CPU搬运10个数据后,停止搬运。若想CPU进行进行搬运数据,则需要重新调用此函数,打开串口接收数据中断。而进行第二轮数据搬运时,CPU会将搬运的数据重新从Receive_Data数组的第0位开始存储。 -
发送中断
①如图: CPU执行了HAL_UART_Transmit_IT(&huart, Receive_Data, 10)这个函数代表着:打开串口发送数据中断仅此而已。如下图所示:
②而CPU将Receive_Data中的数据搬运到DR数据寄存器里面操作是在UART_Transmit_IT(huart)函数。
当CPU执行了HAL_UART_Transmit_IT(&huart, Receive_Data, 10) ,串口的DR寄存器一旦为空,即TXE(发送数据寄存器空)会被置1。触发发送中断请求,CPU会执行中断服务总函数,当执行到中断服务分函数UART_Transmit_IT(huart)时,将Receive_Data中的数据搬运到DR数据寄存器。然后串口将数据发送出去。如下图所示:
如上图所示:CPU执行了HAL_UART_Transmit_IT(&huart, Receive_Data, 10),一旦有DR中没有数据时,CPU就会执行USART1_IRQHandler——>HAL_UART_IRQHandler(&huart1)——>UART_Transmit_IT(huart)函数,将Receive_Data数组中数据搬运到串口DR数据寄存器里面,然后让TxXferCount减1。所以每搬运1个数据,TxXferCount就会减1。当搬运完最后1个数据时,TxXferCount会减为0,然后CPU就会进入if语句里面去执行关闭串口发送中断,执行打开串口发生完成中断。然后CPU执行USART1_IRQHandler——>HAL_UART_IRQHandler(&huart1)——>UART_EndTransmit_IT(huart)——>回调函数。若此时串口的DR没有数据了,CPU也不会在进行将数组的数据搬运到DR里面了。
1、阻塞式串口的发送与接收
一般情况下不使用阻塞式的数据接收,因为接收数据是单片机被动操作,只有当DR寄存器只能有数据时,单片机去执行接收函数HAL_UART_Receive()将DR中的数据存储到数组中才有意义。这就是说这里面有个先后顺序,DR寄存器中有数据在先,然后去执行HAL_UART_Receive()将数据搬运到数组中在后。而我们根本不知道上位机何时给单片机发送数据,有从何谈起进行HAL_UART_Receive搬运数据喃?
除非在主函数中while循环中一直执行HAL_UART_Receive()进行搬运,且其他的代码量较少。但是在项目中while循环不可能存在代码量较少的情况。当单片机去执行其他的代码时,上位机向单片机发送数据,而单片机没有即使的执行HAL_UART_Receive()进行数据的搬运,从而造成了数据的丢失。
综上:使用串口进行数据的接收时,一般情况下使用串口的接收中断进行中断。
1. 对HAL_UART_Receive()源码详解
源码如下:
/*下面是阻塞式接收数据的源码*/
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
uint16_t *tmp;//定义一个指针变量tmp,在9位数据位的代码处理里面会将tmp = pData
uint32_t tickstart = 0U;//定义一个变量tickstart
/* Check that a Rx process is not already ongoing */
if (huart->RxState == HAL_UART_STATE_READY)//判断当前接收状态是否初始化并准备使用,串口初始化完毕后,
//会将huart->RxState = HAL_UART_STATE_READY
{
if ((pData == NULL) || (Size == 0U))//如果接收缓冲区没有被定义或者需要被接收的数据个数为0
{
return HAL_ERROR;//跳出函数,不在进行搬运数据,返回一个接收错误
}
/* Process Locked */
__HAL_LOCK(huart);//将串口的配置锁住
huart->ErrorCode = HAL_UART_ERROR_NONE;//给编码状态一个无错误状态
huart->RxState = HAL_UART_STATE_BUSY_RX;//给接收状态一个接收满状态
/* Init tickstart for timeout managment */
tickstart = HAL_GetTick();//获取当前的计数的数值,位于滴答定时器中断,uwTick每隔1ms进行+1
huart->RxXferSize = Size;//将Size给RxXferSize,表示单片机需要Size个数据(即需要被接收到的数据总个数)
huart->RxXferCount = Size;//将Size给RxXferCount,表示单片机还需要Size个数据(即还需要被接收到的数据个数)
/* Check the remain data to be received */
while (huart->RxXferCount > 0U)//还需要被接收到的数据个数>0(即实际接收到的数据个数还没达到需要被接收的总个数),进入while循环
{
huart->RxXferCount--;//还需要被接收的数据个数据减1
if (huart->Init.WordLength == UART_WORDLENGTH_9B)//判断有效数据位是否为9位,一般为8位,所以不成立
{
//这里面是9位数据位的数据处理,我将其删除
}
else//有效数据位不是9位,一般情况下为8位
{
if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK)//超时判断,超时了这个函数返回HAL_TIMEOUT,不等于HAL_OK,调节成立
{
return HAL_TIMEOUT;//退出函数,返回HAL_TIMEOUT
}
if (huart->Init.Parity == UART_PARITY_NONE) //判断是否为无校验位
{
*pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);//将DR中的数据,搬运到pData数组中
}
else
{
*pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F);
}
}
}
/* At end of Rx process, restore huart->RxState to Ready */
huart->RxState = HAL_UART_STATE_READY;//将接收状态变为初始化并准备使用,以便接收第二个数据
/* Process Unlocked */
__HAL_UNLOCK(huart);//对串口配置解锁
return HAL_OK;//返回一个OK,表示在规定的超时时间内接收到需要被接收的数据个数
}
else//如果接收状态不是为HAL_UART_STATE_READY,即串口还没有准备好
{
return HAL_BUSY;//那么就返回一个忙状态
}
}
if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK)//超时判断,超时了这个函数返回HAL_TIMEOUT,不等于HAL_OK,调节成立
{
return HAL_TIMEOUT;//退出函数,返回HAL_TIMEOUT
}
/*下面是接收数据超时的处理*/
static HAL_StatusTypeDef UART_WaitOnFlagUntilTimeout(UART_HandleTypeDef *huart, uint32_t Flag, FlagStatus Status, uint32_t Tickstart, uint32_t Timeout)
{
/* Wait until flag is set */
while ((__HAL_UART_GET_FLAG(huart, Flag) ? SET : RESET) == Status)//通过一个三元运算符进行对UART_FLAG_RXNE的判断。当DR中有数据时__HAL_UART_GET_FLAG(huart, Flag) = 1.
//则执行SET,而SET != Status(RESET)。使用不会进入while循环中,则返回一个HAL_OK
//若DR中一直没有数据,则__HAL_UART_GET_FLAG(huart, Flag) = 0
//则执行RESET,而RESET == Status(RESET),则会进入while循环中
{
/* Check for the Timeout */
if (Timeout != HAL_MAX_DELAY)//判断设定的时间Timeout是否为HAL_MAX_DELAY,假设设定为200ms
{
if ((Timeout == 0U) || ((HAL_GetTick() - Tickstart) > Timeout))//判断设定是超时时间是否为0,若为0
//进入if里面执行下面语句,然后返回一个HAL_TIMEOUT,若不为0则进行超时判断
//HAL_GetTick() - Tickstart表示当前的计数ms减去开始接收数据的计数ms
//若减去的结果没有超过设定的超时时间,则不进入下面的语句,再次在while循环里面判断
//( __HAL_UART_ GET_FLAG(huart, Flag),若DR寄存器中还是没有数据。
//则继续进入if判断,判断当前计数ms减去开始接收数据的计数ms,若大于设置的超时时间。
//即等待的时间已经超过了超时时间了。进入if里面执行下面的语句。最后返回一个HAL_TIMEOUT
{
/* Disable TXE, RXNE, PE and ERR (Frame error, noise error, overrun error) interrupts for the interrupt process */
CLEAR_BIT(huart->Instance->CR1, (USART_CR1_RXNEIE | USART_CR1_PEIE | USART_CR1_TXEIE));
CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);
huart->gState = HAL_UART_STATE_READY;
huart->RxState = HAL_UART_STATE_READY;
/* Process Unlocked */
__HAL_UNLOCK(huart);
return HAL_TIMEOUT;
}
}
}
return HAL_OK;
}
1.1、阻塞式进行数据的收发
实验:使用串口1进行阻塞式的串口数据接收和发送
①UART.c文件的代码如下:
#include "UART.h"
/**
* 串口1的初始化
*/
UART_HandleTypeDef huart1;
void UART1_Init(uint32_t baudrate)
{
/* 1、开启串口1的时钟*/
__HAL_RCC_USART1_CLK_ENABLE();
/* 2、对串口1进行配置*/
huart1.Instance = USART1;
huart1.Init.BaudRate = baudrate; //波特率
huart1.Init.WordLength = UART_WORDLENGTH_8B; //8位数据位
huart1.Init.Parity = UART_PARITY_NONE; //无校验位
huart1.Init.StopBits = UART_STOPBITS_1; //1位停止位
huart1.Init.Mode = UART_MODE_TX_RX; //收+发模式
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; //无硬件流控
HAL_UART_Init(&huart1);
}
/**
* 函数重写,用于配置UASRT引脚
*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)//HAL_UART_Init()里面会调用这个函数
{
GPIO_InitTypeDef GPIO_Init;
if(huart->Instance == USART1)//如果是USART1,则对引脚进行配置
{
/* 1、开启GPIOA的时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE();
/* 2、对PA9进行配置,PA9 = Tx */
GPIO_Init.Pin = GPIO_PIN_9; //选择PA9
GPIO_Init.Mode = GPIO_MODE_AF_PP; //选择复用推挽输出
GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH; //最大输出速度50MHz
HAL_GPIO_Init(GPIOA, &GPIO_Init);
/* 2、对PA10进行配置,PA10 = Rx */
GPIO_Init.Pin = GPIO_PIN_10; //选择PA10
GPIO_Init.Mode = GPIO_MODE_AF_INPUT; //选择输入模式
GPIO_Init.Pull = GPIO_NOPULL; //选择浮空输入
HAL_GPIO_Init(GPIOA, &GPIO_Init);
}
}
②UART.h文件的代码如下:
#ifndef __UART_H
#define __UART_H
#include "stm32f1xx_hal.h"
extern UART_HandleTypeDef huart1;
void UART1_Init(uint32_t baudrate);
#endif
③主函数main.c文件的代码如下:
#include "stm32f1xx_hal.h"
#include "STM32_RCC_Init.h"
#include "UART.h"
uint8_t pData[200];//数组用来保存数据
int main(void){
HAL_Init();
HSE_RCC_Init(); //时钟树的初始化
UART1_Init(115200); //串口1的初始化
while (1)
{
switch(HAL_UART_Receive(&huart1, pData, 200, 200))//阻塞式接受数据,200ms内接受200字节的数据
{
case HAL_OK: //表示在200ms内成功接受到了20个字节的数据
HAL_UART_Transmit(&huart1, pData, 200, 200);//将20个数据发送出去
break;
case HAL_TIMEOUT: //表示200ms内没有接受到200个字节数据.
if(huart1.RxXferCount != 199)//接受到了部分的数据,RxXferCount表示剩余需要被接受的数据
{
HAL_UART_Transmit(&huart1, pData, (199-huart1.RxXferCount), 200);//将接受到的部分数据发送出去
}
else //一个字节的数据都没有接受到
{
//不做任何处理
}
break;
case HAL_BUSY:
break;
case HAL_ERROR:
break;
}
}
}
1.2、串口的重映射
实验:串口2和串口3的数据收发+串口1的重映射
①UART.c文件的代码如下:
#include "UART.h"
/**
* 串口1的初始化
*/
UART_HandleTypeDef huart1;
UART_HandleTypeDef huart2;
UART_HandleTypeDef huart3;
void UART1_Init(uint32_t baudrate)
{
/* 1、开启串口1的时钟*/
__HAL_RCC_USART1_CLK_ENABLE();
/* 2、对串口2进行配置*/
huart1.Instance = USART1;
huart1.Init.BaudRate = baudrate; //波特率
huart1.Init.WordLength = UART_WORDLENGTH_8B; //8位数据位
huart1.Init.Parity = UART_PARITY_NONE; //无校验位
huart1.Init.StopBits = UART_STOPBITS_1; //1位停止位
huart1.Init.Mode = UART_MODE_TX_RX; //收+发模式
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; //无硬件流控
HAL_UART_Init(&huart1);
}
/**
* 串口1的初始化
*/
void UART2_Init(uint32_t baudrate)
{
/* 1、开启串口2的时钟*/
__HAL_RCC_USART2_CLK_ENABLE();
/* 2、对串口2进行配置*/
huart2.Instance = USART2;
huart2.Init.BaudRate = baudrate; //波特率
huart2.Init.WordLength = UART_WORDLENGTH_8B; //8位数据位
huart2.Init.Parity = UART_PARITY_NONE; //无校验位
huart2.Init.StopBits = UART_STOPBITS_1; //1位停止位
huart2.Init.Mode = UART_MODE_TX_RX; //收+发模式
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; //无硬件流控
HAL_UART_Init(&huart2);
}
/**
* 串口1的初始化
*/
void UART3_Init(uint32_t baudrate)
{
/* 1、开启串口3的时钟*/
__HAL_RCC_USART2_CLK_ENABLE();
/* 2、对串口3进行配置*/
huart3.Instance = USART3;
huart3.Init.BaudRate = baudrate; //波特率
huart3.Init.WordLength = UART_WORDLENGTH_8B; //8位数据位
huart3.Init.Parity = UART_PARITY_NONE; //无校验位
huart3.Init.StopBits = UART_STOPBITS_1; //1位停止位
huart3.Init.Mode = UART_MODE_TX_RX; //收+发模式
huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE; //无硬件流控
HAL_UART_Init(&huart3);
}
/**
* 函数重写,用于配置UASRT引脚
*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)//HAL_UART_Init()里面会调用这个函数
{
GPIO_InitTypeDef GPIO_Init;
if(huart->Instance == USART1)//如果是USART1,使用重映射对引脚进行配置
{
/* 1、开启AFIO的时钟 */
__HAL_RCC_AFIO_CLK_ENABLE();
/* 1、开启GPIOB的时钟 */
__HAL_RCC_GPIOB_CLK_ENABLE();
/* 1、开启UART1的引脚重映射 */
__HAL_AFIO_REMAP_USART1_ENABLE();
/* 2、对PB6进行配置,PB6 = Tx */
GPIO_Init.Pin = GPIO_PIN_6; //选择PB6(重映射)
GPIO_Init.Mode = GPIO_MODE_AF_PP; //选择复用推挽输出
GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH; //最大输出速度50MHz
HAL_GPIO_Init(GPIOB, &GPIO_Init);
/* 2、对PB7进行配置,PB7 = Rx */
GPIO_Init.Pin = GPIO_PIN_7; //选择B7(重映射)
GPIO_Init.Mode = GPIO_MODE_AF_INPUT; //选择输入模式
GPIO_Init.Pull = GPIO_NOPULL; //选择浮空输入
HAL_GPIO_Init(GPIOB, &GPIO_Init);
}
else if(huart->Instance == USART2)//如果是USART2,则对引脚进行配置
{
/* 1、开启GPIOA的时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE();
/* 2、对PA2进行配置,PA2 = Tx */
GPIO_Init.Pin = GPIO_PIN_2; //选择PA2
GPIO_Init.Mode = GPIO_MODE_AF_PP; //选择复用推挽输出
GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH; //最大输出速度50MHz
HAL_GPIO_Init(GPIOA, &GPIO_Init);
/* 2、对PA3进行配置,PA3 = Rx */
GPIO_Init.Pin = GPIO_PIN_3; //选择PA10
GPIO_Init.Mode = GPIO_MODE_AF_INPUT; //选择输入模式
GPIO_Init.Pull = GPIO_NOPULL; //选择浮空输入
HAL_GPIO_Init(GPIOA, &GPIO_Init);
}
else if(huart->Instance == USART3)//如果是USART3,则对引脚进行配置
{
/* 1、开启GPIOB的时钟 */
__HAL_RCC_GPIOB_CLK_ENABLE();
/* 2、对PB10进行配置,PB10 = Tx */
GPIO_Init.Pin = GPIO_PIN_10; //选择PB10
GPIO_Init.Mode = GPIO_MODE_AF_PP; //选择复用推挽输出
GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH; //最大输出速度50MHz
HAL_GPIO_Init(GPIOB, &GPIO_Init);
/* 2、对PB11进行配置,PB11 = Rx */
GPIO_Init.Pin = GPIO_PIN_11; //选择PB11
GPIO_Init.Mode = GPIO_MODE_AF_INPUT; //选择输入模式
GPIO_Init.Pull = GPIO_NOPULL; //选择浮空输入
HAL_GPIO_Init(GPIOB, &GPIO_Init);
}
}
②UART.h文件的代码如下:
#ifndef __UART_H
#define __UART_H
#include "stm32f1xx_hal.h"
extern UART_HandleTypeDef huart1;
extern UART_HandleTypeDef huart2;
extern UART_HandleTypeDef huart3;
void UART1_Init(uint32_t baudrate);
void UART2_Init(uint32_t baudrate);
void UART3_Init(uint32_t baudrate);
#endif
③主函数main.c文件的代码如下:
#include "stm32f1xx_hal.h"
#include "STM32_RCC_Init.h"
#include "UART.h"
uint8_t pData[200];
int main(void){
HAL_Init();
HSE_RCC_Init(); //时钟树的初始化
UART2_Init(115200); //串口2的初始化
while (1)
{
switch(HAL_UART_Receive(&huart2, pData, 200, 200))//阻塞式接受数据,200ms内接受200字节的数据
{
case HAL_OK: //表示在200ms内成功接受到了20个字节的数据
HAL_UART_Transmit(&huart2, pData, 200, 200);//将20个数据发送出去
break;
case HAL_TIMEOUT: //表示200ms内没有接受到200个字节数据.
if(huart2.RxXferCount != 199)//接受到了部分的数据,RxXferCount表示剩余需要被接受的数据
{
HAL_UART_Transmit(&huart2, pData, (200-1-huart2.RxXferCount), 200);//将接受到的部分数据发送出去
}
else //一个字节的数据都没有接受到
{
//不做任何处理
}
break;
case HAL_BUSY:
break;
case HAL_ERROR:
break;
}
}
}
2、中断式串口的发送与接收
- HAL_UART_Receive_IT源码详解
若CPU执行HAL_UART_Receive_IT(&huart1, pData, 20);
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
/* Check that a Rx process is not already ongoing */
if (huart->RxState == HAL_UART_STATE_READY)//先判断接收状态是否为READY,一般串口初始化完毕会将RxState状态变为READY
{
if ((pData == NULL) || (Size == 0U))//判断存放数据的数组是否被定义了,或者需要被搬运的是否为0
{
return HAL_ERROR;//是的话就返回一个HAL_ERROR,代表串口接收中断开启失败
}
/* Process Locked */
__HAL_LOCK(huart);//锁住串口的配置
huart->pRxBuffPtr = pData;//存放数据的数组的头指针pData给pRxBuffPtr变量
huart->RxXferSize = Size;//需要搬运的总数20个RxXferSize
huart->RxXferCount = Size;//需要搬运的总数20个RxXferCount
huart->ErrorCode = HAL_UART_ERROR_NONE;//让编码状态为无错误编码
huart->RxState = HAL_UART_STATE_BUSY_RX;//数据接收状态为BUSY_RX
/* Process Unlocked */
__HAL_UNLOCK(huart);//解锁
/* Enable the UART Parity Error Interrupt */
__HAL_UART_ENABLE_IT(huart, UART_IT_PE);
/* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
__HAL_UART_ENABLE_IT(huart, UART_IT_ERR);
/* Enable the UART Data Register not empty Interrupt */
__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);//开启接收中断
return HAL_OK;//返回OK,代表配置完成了
}
else//若判断接收状态不是READY
{
return HAL_BUSY;//返回一个HAL_BUSY,代表串口还没准备好
}
}
程序流程如下:
- UART_Receive_IT源码详解
static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)
{
uint16_t *tmp;
/* Check that a Rx process is ongoing */
if (huart->RxState == HAL_UART_STATE_BUSY_RX)//判断RxState 是否为BUSY_RX,HAL_UART_Receive_IT()会将RxState变为BUSY_RX
{
if (huart->Init.WordLength == UART_WORDLENGTH_9B)//9位数据位,不管他
{
tmp = (uint16_t *) huart->pRxBuffPtr;
if (huart->Init.Parity == UART_PARITY_NONE)
{
*tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF);
huart->pRxBuffPtr += 2U;
}
else
{
*tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x00FF);
huart->pRxBuffPtr += 1U;
}
}
else
{
if (huart->Init.Parity == UART_PARITY_NONE)//判断是否有校验位,一般没有
{
*huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);//若没有,就进行数据搬运
}
else//若有
{
*huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F);//进行这一步数据搬运
}
}
if (--huart->RxXferCount == 0U)//判断搬运的总数20个是否完成
{
/* Disable the UART Data Register not empty Interrupt */
__HAL_UART_DISABLE_IT(huart, UART_IT_RXNE);//总数20个搬运完成了,将关闭接受中断
/* Disable the UART Parity Error Interrupt */
__HAL_UART_DISABLE_IT(huart, UART_IT_PE);
/* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
__HAL_UART_DISABLE_IT(huart, UART_IT_ERR);
/* Rx process is completed, restore huart->RxState to Ready */
huart->RxState = HAL_UART_STATE_READY;//总数20个搬运完成了,将RxState变为READY
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered Rx complete callback*/
huart->RxCpltCallback(huart);
#else
/*Call legacy weak Rx complete callback*/
HAL_UART_RxCpltCallback(huart);//总数20个搬运完成了,将执行搬运完成的回调函数
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
return HAL_OK;
}
return HAL_OK;
}
else
{
return HAL_BUSY;
}
}
执行流程如下:
2.1、中断接受定长数据
实验:中断的方式接收和发送数据,
①UART.c文件的代码如下:
#include "UART.h"
/**
* 串口1的初始化
*/
UART_HandleTypeDef huart1;
uint8_t pData[256];
void UART1_Init(uint32_t baudrate)
{
/* 1、开启串口1的时钟*/
__HAL_RCC_USART1_CLK_ENABLE();
/* 2、对串口2进行配置*/
huart1.Instance = USART1;
huart1.Init.BaudRate = baudrate; //波特率
huart1.Init.WordLength = UART_WORDLENGTH_8B; //8位数据位
huart1.Init.Parity = UART_PARITY_NONE; //无校验位
huart1.Init.StopBits = UART_STOPBITS_1; //1位停止位
huart1.Init.Mode = UART_MODE_TX_RX; //收+发模式
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; //无硬件流控
HAL_UART_Init(&huart1);
HAL_UART_Receive_IT(&huart1, pData, 20); //开启接收中断,搬运了20个字节停止搬运
HAL_NVIC_SetPriority(USART1_IRQn,3,0);//中断优先级
HAL_NVIC_EnableIRQ(USART1_IRQn);
}
/**
* 函数重写,用于配置UASRT引脚
*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)//HAL_UART_Init()里面会调用这个函数
{
GPIO_InitTypeDef GPIO_Init;
if(huart->Instance == USART1)//如果是USART1,使用重映射对引脚进行配置
{
/* 1、开启GPIOA的时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE();
/* 2、对PA9进行配置,PA9 = Tx */
GPIO_Init.Pin = GPIO_PIN_9; //选择A9
GPIO_Init.Mode = GPIO_MODE_AF_PP; //选择复用推挽输出
GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH; //最大输出速度50MHz
HAL_GPIO_Init(GPIOA, &GPIO_Init);
/* 2、对PA10进行配置,PA10 = Rx */
GPIO_Init.Pin = GPIO_PIN_10; //选择PA10
GPIO_Init.Mode = GPIO_MODE_AF_INPUT; //选择输入模式
GPIO_Init.Pull = GPIO_NOPULL; //选择浮空输入
HAL_GPIO_Init(GPIOA, &GPIO_Init);
}
}
/**
* 中断服务函数
*/
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart1);//中断服务总函数,这个函数调用UART_Receive_IT()函数
//HAL_UART_Receive_IT(&huart1, pData, 20)//若在这里再次开启开启接收数据中断,不会成功,因为RxState != READY
}
/**
* 接收数据的回调函数,
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//UART_Receive_IT()会调用此函数
{
if(huart->Instance == USART1)
{
HAL_UART_Transmit_IT(&huart1,pData,20); //启动发送中断,当TEX发送数据寄存器空时,就会触发中断,将数据发送出去
HAL_UART_Receive_IT(&huart1, pData, 20); //再次开启开启接收数据中断
}
}
②UART.h文件的代码如下:
#ifndef __UART_H
#define __UART_H
#include "stm32f1xx_hal.h"
extern UART_HandleTypeDef huart1;
void UART1_Init(uint32_t baudrate);
#endif
③主函数main.c文件的代码如下:
#include "stm32f1xx_hal.h"
#include "STM32_RCC_Init.h"
#include "UART.h"
int main(void)
{
HAL_Init();
HSE_RCC_Init(); //时钟树的初始化
UART1_Init(115200);
while (1)
{
}
}
上面的实验是接受20个定长的数据。存在着一些弊端:
①若上位机给单片机发送50个字节的数据,单片机第一轮接受20个数据存储到pData[0]~pData[19]。第二轮接受20个数据也存储到pData[0] ~pData[19],所以会造成第一轮接受到的数据覆盖丢失。
②那将需要搬运的总个数增大不就解决了这个问题了吗?但是若总个数设定为300 > 数组的范围256时,若上位机给单片机发送300个字节的数据时,单片机搬运前256个数据能正常的存储在数据中,若继续搬运,则后面的数据将会造成数组的越界。
③将总个数设定为20 <数组的范围256,若上位机第一次发送5个数据,第二次发送10个数据,若想对第二次接收到的数据进行处理也是比较困难的。
2.2、中断接受不定长的数据
①UART.c文件的代码如下:
#include "UART.h"
/**
* 串口1的初始化
*/
UART_HandleTypeDef huart1;
uint8_t Receive_Data = 0;
uint8_t pData[256];
uint16_t Index = 0;//定义数组下标
void UART1_Init(uint32_t baudrate)
{
/* 1、开启串口1的时钟*/
__HAL_RCC_USART1_CLK_ENABLE();
/* 2、对串口2进行配置*/
huart1.Instance = USART1;
huart1.Init.BaudRate = baudrate; //波特率
huart1.Init.WordLength = UART_WORDLENGTH_8B; //8位数据位
huart1.Init.Parity = UART_PARITY_NONE; //无校验位
huart1.Init.StopBits = UART_STOPBITS_1; //1位停止位
huart1.Init.Mode = UART_MODE_TX_RX; //收+发模式
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; //无硬件流控
HAL_UART_Init(&huart1);
HAL_UART_Receive_IT(&huart1, &Receive_Data, 1); //开启接收1个字节数据中断
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); //使能空闲中断
HAL_NVIC_SetPriority(USART1_IRQn,3,0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
}
/**
* 函数重写,用于配置UASRT引脚
*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)//HAL_UART_Init()里面会调用这个函数
{
GPIO_InitTypeDef GPIO_Init;
if(huart->Instance == USART1)//如果是USART1,使用重映射对引脚进行配置
{
/* 1、开启GPIOA的时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE();
/* 2、对PA9进行配置,PA9 = Tx */
GPIO_Init.Pin = GPIO_PIN_9; //选择A9
GPIO_Init.Mode = GPIO_MODE_AF_PP; //选择复用推挽输出
GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH; //最大输出速度50MHz
HAL_GPIO_Init(GPIOA, &GPIO_Init);
/* 2、对PA10进行配置,PA10 = Rx */
GPIO_Init.Pin = GPIO_PIN_10; //选择PA10
GPIO_Init.Mode = GPIO_MODE_AF_INPUT; //选择输入模式
GPIO_Init.Pull = GPIO_NOPULL; //选择浮空输入
HAL_GPIO_Init(GPIOA, &GPIO_Init);
}
}
/**
* 中断服务函数
*/
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart1);//这个函数调用UART_Receive_IT()函数
/* 对每一轮的数进行处理*/
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE))//若空闲置位了
{
//清除中断标志位
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
//对数据处理,将第一轮接受到的数据发送出去
HAL_UART_Transmit(&huart1,pData,Index,200);
Index = 0;
}
}
/**
* 搬运数据完成的 的回调函数,
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//UART_Receive_IT()会调用此函数
{
if(huart->Instance == USART1)
{
if(Index > 256)
{
Index = 0;
}
pData[Index++] = Receive_Data;//将Receive_Data中的数据保存到pData数组中
HAL_UART_Receive_IT(&huart1, &Receive_Data, 1); //开启接收数据中断
}
}
②主函数main.c文件的代码如下:
#include "stm32f1xx_hal.h"
#include "STM32_RCC_Init.h"
#include "UART.h"
int main(void){
HAL_Init();
HSE_RCC_Init(); //时钟树的初始化
UART1_Init(115200);
while (1)
{
}
}
上位机向串口每发送一轮数据,这轮数据都是从数组的pData[0]进行存储。这样就方便操作数据
当然我们也可以不使用HAL_UART_Receive_IT(&huart1, &Receive_Data, 1);使能中断,使用__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);直接使能接收中断,在中断服务总函数里面自定义操作搬运数据,如下:
①UART.c文件的代码如下:
#include "UART.h"
/**
* 串口1的初始化
*/
UART_HandleTypeDef huart1;
uint8_t pData[256];
uint16_t Index = 0;//定义数组下标
void UART1_Init(uint32_t baudrate)
{
/* 1、开启串口1的时钟*/
__HAL_RCC_USART1_CLK_ENABLE();
/* 2、对串口2进行配置*/
huart1.Instance = USART1;
huart1.Init.BaudRate = baudrate; //波特率
huart1.Init.WordLength = UART_WORDLENGTH_8B; //8位数据位
huart1.Init.Parity = UART_PARITY_NONE; //无校验位
huart1.Init.StopBits = UART_STOPBITS_1; //1位停止位
huart1.Init.Mode = UART_MODE_TX_RX; //收+发模式
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; //无硬件流控
HAL_UART_Init(&huart1);
__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE); //使能接受中断
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); //使能空闲帧中断
HAL_NVIC_SetPriority(USART1_IRQn,3,0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
}
/**
* 函数重写,用于配置UASRT引脚
*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)//HAL_UART_Init()里面会调用这个函数
{
GPIO_InitTypeDef GPIO_Init;
if(huart->Instance == USART1)//如果是USART1,使用重映射对引脚进行配置
{
/* 1、开启GPIOA的时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE();
/* 2、对PA9进行配置,PA9 = Tx */
GPIO_Init.Pin = GPIO_PIN_9; //选择A9
GPIO_Init.Mode = GPIO_MODE_AF_PP; //选择复用推挽输出
GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH; //最大输出速度50MHz
HAL_GPIO_Init(GPIOA, &GPIO_Init);
/* 2、对PA10进行配置,PA10 = Rx */
GPIO_Init.Pin = GPIO_PIN_10; //选择PA10
GPIO_Init.Mode = GPIO_MODE_AF_INPUT; //选择输入模式
GPIO_Init.Pull = GPIO_NOPULL; //选择浮空输入
HAL_GPIO_Init(GPIOA, &GPIO_Init);
}
}
/**
* 中断服务函数
*/
void USART1_IRQHandler(void)
{
uint8_t Receive_Data = 0;
/*对数据进行搬运*/
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE))//判断是否是RXNE置位
{
if(Index > 256)//接受到的数据个数大于数组范围
{
Index = 0;
}
HAL_UART_Receive(&huart1, &Receive_Data, 1, 100); //将数据搬运到Receive_Data里面,
//注:进行数据的搬运会将RXNE置0,无需清除RXNE标志位
pData[Index++] = Receive_Data; //将Receive_Data中的数据保存到数组里面
}
/* 对每一轮接受到的数据进行处理 */
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE))//判断是否接受到空闲帧,代表一轮的接受结束了
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除空闲帧标志位
HAL_UART_Transmit(&huart1, pData, Index, 200);//将这一轮接受到的数据进行处理,发送出去
Index = 0;
}
}
/* 重构了printf()函数 */
int fputc(int ch,FILE *f)
{
while((USART1->SR & 0x40) == 0);
USART1->DR = (uint8_t)ch;
return ch;
}
3、收发缓冲区方案(环形队列)
①环形缓冲区Queue.c文件的代码如下:
#include "Queue.h"
/**
* 队列的初始化接口函数:用户调用此函数,向里面填值即可
* queue:队列结构体指针
* Buffer:数组
* size:数组的长度
*/
void Queue_Init(QueueType_t *queue,uint8_t *Buffer)
{
queue->head = 0;
queue->tail = 0;
queue->count = 0;
queue->buffer = Buffer;
}
/**
* 判断队列是否为空
*/
bool Queue_IsEmpty(QueueType_t *queue)
{
if(queue->head == queue->tail)//队列为空,返回true
{
return true;
}
return false;
}
/**
* 判断队列是否满
*/
bool Queue_IsFull(QueueType_t *queue)
{
if((queue->tail + 1) % QUEUE_SIZE == queue->head)//对了为满,返回true
{
return true;
}
return false;
}
/**
* 入队的接口函数:向队列里面放入一个字节的数据
* Data:需要放入队列的数据
*/
bool QueuePut(QueueType_t *queue, uint8_t Data)
{
/* 先判断队列是否满,满了就不能放入数据了 */
if(Queue_IsFull(queue) == true)//代表队列满,则不能向队列放入数据
{
return false;
}
//队列没有满,向里面放入数据
queue->buffer[queue->tail] = Data;//将数据Data放入数组buffer中
queue->tail = (queue->tail + 1) % QUEUE_SIZE;;//将放入数据的下标往后移一位
queue->count++;//有效数据加1
return true;//代表放入成功,返回OK
}
/**
* 出队的接口函数:向队列取出一个字节的数据
* pData:将队列中的数据取出来放入pData的数组中
*/
bool QueueGet(QueueType_t *queue, uint8_t *pData)
{
/* 先判断队列是否为空,若为空则不能取数据 */
if(Queue_IsEmpty(queue) == true)//代表队列空,则不能向队列放入数据
{
return false;
}
//队列没有空,代表有数据,则可以取
*pData = queue->buffer[queue->head];
queue->head = (queue->head + 1) % QUEUE_SIZE;//将取数据的下标向后移动一位
queue->count--;//有效数据加1
return true; //表示取出超过,返回OK
}
/**
* 入队的接口函数:向队列里面放入多个字节(一组)的数据
* pArray:需要放入队列的一组数据的数组
* Len:需要放入的数据个数
*/
uint16_t QueuePut_Array(QueueType_t *queue, uint8_t *pArray, uint32_t Len)
{
uint32_t i = 0;
for (i = 0; i < Len; i++)
{
if(Queue_IsFull(queue) == true)//代表队列满,则不能向队列放入数据
{
return i;
}
//队列没有满,向里面放入数据
queue->buffer[queue->tail] = pArray[i];//将数据Data放入数组buffer中
queue->tail = (queue->tail + 1) % QUEUE_SIZE;//将放入数据的下标往后移一位
queue->count++;//有效数据加1
}
return i;//返回实际被放入数据的个数
}
/**
* 出队的接口函数:将队列的取出多个数据
* pArray:将队列中的多个数据取出来放入pArray的数组中
*/
uint16_t QueueGet_Array(QueueType_t *queue, uint8_t *pArray, uint32_t Len)
{
uint16_t i = 0;
for(i = 0; i<Len; i++)
{
if(Queue_IsEmpty(queue) == true)//代表队列空,则不能向队列 取出数据
{
return i;
}
pArray[i] = queue->buffer[queue->head];
queue->head = (queue->head + 1) % QUEUE_SIZE;//将取数据的下标向后移动一位
queue->count--;//有效数据加1
}
return i;//返回实际取出数据的个数
}
/**
* 获取队列中数据的个数
*/
uint16_t Queue_GetCount(QueueType_t *queue)
{
if (queue->head <= queue->tail)
{
return (queue->tail - queue->head);
}
return (QUEUE_SIZE - (queue->head - queue->tail));
}
②Queue.h文件的代码如下:
#ifndef __Queue_H
#define __Queue_H
#include <stdint.h>
#include <stdbool.h>
/**
* 创建一个队列必要元素的结构体
*/
typedef struct
{
uint16_t head; //出队时的数组下标
uint16_t tail; //入队时的数组下标
uint16_t count; //有效数据的个数
uint8_t *buffer; //数组
} QueueType_t;
#define QUEUE_SIZE 256
bool Queue_IsEmpty(QueueType_t *queue);
bool Queue_IsFull(QueueType_t *queue);
void Queue_Init(QueueType_t *queue, uint8_t *Buffer);
bool QueuePut(QueueType_t *queue, uint8_t Data);
bool QueueGet(QueueType_t *queue, uint8_t *pData);
uint16_t QueuePut_Array(QueueType_t *queue, uint8_t *pArray, uint32_t Len);
uint16_t QueueGet_Array(QueueType_t *queue, uint8_t *pArray, uint32_t Len);
uint16_t Queue_GetCount(QueueType_t *queue);
#endif
③UART.c文件的代码如下:
#include "UART.h"
QueueType_t QueueStruct;
uint8_t Buffer[QUEUE_SIZE];
uint8_t Receive_Data;
/**
* 串口1的初始化
*/
UART_HandleTypeDef huart1;
void UART1_Init(uint32_t baudrate)
{
Queue_Init(&QueueStruct,Buffer); //对缓冲区的初始化
/* 1、开启串口1的时钟*/
__HAL_RCC_USART1_CLK_ENABLE();
/* 2、对串口2进行配置*/
huart1.Instance = USART1;
huart1.Init.BaudRate = baudrate; //波特率
huart1.Init.WordLength = UART_WORDLENGTH_8B; //8位数据位
huart1.Init.Parity = UART_PARITY_NONE; //无校验位
huart1.Init.StopBits = UART_STOPBITS_1; //1位停止位
huart1.Init.Mode = UART_MODE_TX_RX; //收+发模式
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; //无硬件流控
HAL_UART_Init(&huart1);
HAL_UART_Receive_IT(&huart1, &Receive_Data, 1); //开启接收1个字节数据中断
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); //使能接收空闲帧中断,通过空闲中断来判断接收是否完成
HAL_NVIC_SetPriority(USART1_IRQn,3,0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
}
/**
* 函数重写,用于配置UASRT引脚
*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)//HAL_UART_Init()里面会调用这个函数
{
GPIO_InitTypeDef GPIO_Init;
if(huart->Instance == USART1)//如果是USART1,使用重映射对引脚进行配置
{
/* 1、开启GPIOA的时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE();
/* 2、对PA9进行配置,PA9 = Tx */
GPIO_Init.Pin = GPIO_PIN_9; //选择A9
GPIO_Init.Mode = GPIO_MODE_AF_PP; //选择复用推挽输出
GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH; //最大输出速度50MHz
HAL_GPIO_Init(GPIOA, &GPIO_Init);
/* 2、对PA10进行配置,PA10 = Rx */
GPIO_Init.Pin = GPIO_PIN_10; //选择PA10
GPIO_Init.Mode = GPIO_MODE_AF_INPUT; //选择输入模式
GPIO_Init.Pull = GPIO_NOPULL; //选择浮空输入
HAL_GPIO_Init(GPIOA, &GPIO_Init);
}
}
/**
* 中断服务函数
*/
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart1);
/*对每一轮发送来的数据进行处理 */
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE))//如果产生了空闲中断,标志位置位
{
__HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_IDLE);//清除中断标志位
UART1_SendData(&QueueStruct);
}
}
/**
* 接收完数据的回调函数,
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//UART_Receive_IT()会调用此函数
{
if(huart->Instance == USART1)
{
if(QueuePut(&QueueStruct,Receive_Data) == true)//将Receive_Data放入环形缓冲区成功,
//没有成功则代表缓冲区满了,不在使能接收中断
{
HAL_UART_Receive_IT(&huart1, &Receive_Data, 1); //开启第2个轮接收中断
}
}
}
/**
* 对环形队列的数据进行处理
*/
bool UART1_SendData(QueueType_t *queue)
{
uint16_t a = queue->count;
if(a == 0)//代表队列中的数据为空
{
return false;
}
uint8_t SendBuffer[a];//创建一个数组
QueueGet_Array(queue, SendBuffer, a);//从环形队列中取出数据存储到数组SendBuffer里面
HAL_UART_Transmit(&huart1, SendBuffer, a, 200);//发送数据
return true;
}
④主函数main.c文件的代码如下:
#include "stm32f1xx_hal.h"
#include "STM32_RCC_Init.h"
#include "UART.h"
int main(void)
{
HAL_Init();
HSE_RCC_Init(); //时钟树的初始化
UART1_Init(115200);
while (1)
{
}
}
原文地址:https://blog.csdn.net/qq_51284092/article/details/143814612
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!