STM32学习笔记-SMT32使用HAL库UART中断方式使用
目录
1、引言
近来需要使用蓝牙模块,再了解到蓝牙模块等无线模块许多使用串口透传,便决定在研究一下串口UART 的使用方法。
由于用的板子种类每次都不一样,有F103 有G0 有G4 还有公司的8位自研芯片,所以每次用起来都需要重头开始复习
每次使用UART的目标都是能够发送不定长数据,接收不定长数据。
使用8位单片机的时候,总体思路是使用定时器定一个时间,在里面对一个时间标志进行加法,在使用串口接收数据时候,每当缓存区的一个字节数据存储到定义好的数组里面,就对这个时间标志进行一次清零,当串口不定长数据接收完成以后就是最后一次清零,此后该事件标志位不再清零将一直进行加法,在随后实用查询方式对该时间标志进行查询,当超过一定时间(要远大于字符串发送时候两个字节之间的时间,一般定义为ms级别)则表明数据接收完成,随后对存储数据的数组进行处理,在对串口各个状态标志和刚才制定的时间标志进行清零。这就完成了一次不定长数据的接收。
在使用STM32的时候,也借鉴了这个思路,同时STM32提供的HAL库大体上有三种UART方式可以使用:
轮询模式(Polling mode IO operation)
使用HAL_UART_Transmit()与HAL_UART_Receive()来配合超时操作来发送与接收数据。
中断模式(Interrupt mode IO operation)
使用HAL_UART_Transmit_IT()与HAL_UART_Receive_IT来发送接收,在发送或接收完之后,再进行函数回调HAL_UART_TxCpltCallback与HAL_UART_RxCpltCallback来进行处理这两个函数都是由用户重新定义的,来实现用户自己的操作。
DMA模式(DMA mode IO operation)
使用HAL_UART_Transmit_DMA()与HAL_UART_Receive_DMA()来发送接收,在发送或接收完之后,也使用HAL_UART_TxCpltCallback与HAL_UART_RxCpltCallback来完成实际操作,同时接收到一半的时候,也可以调用相应的 HAL_UART_TxHalfCpltCallback 与HAL_UART_RxHalfCpltCallback,如果需要用到这个操作的情况下可以添加自己的操作,当然来还用到一关于DMA的API函数,如HAL_UART_DMAPause,HAL_UART_DMAResume, HAL_UART_DMAStop等
其中DMA方式加空闲中断方式效率高,但是由于我没有这么高的需求而且对dma模块还不够熟悉,就是用最常用的中断方式实现功能。
这里主要介绍一下中断方式:
2、配置
这里按照流程讲解一下相关的HAL库函数使用
首先使用cubmax设置所需要的GPIO端口,选定UART功能 ,然后配置基础功能(时钟等)生成文件。
void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart1.Init.ClockPrescaler = UART_PRESCALER_DIV1;
huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetTxFifoThreshold(&huart1, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetRxFifoThreshold(&huart1, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_DisableFifoMode(&huart1) != HAL_OK)
{
Error_Handler();
}
__HAL_UART_CLEAR_IT(&huart1, UART_CLEAR_IDLEF); //此处初始化一下ISR寄存器IDLE位
}
在这里面设置uart的各种参数 ,根据需要调节;
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM1_Init();
MX_USART1_UART_Init();
HAL_UART_Receive_IT(&huart1, (uint8_t *)aRxBuffer, RXBUFFERSIZE);//串口中断接收初始化
/* USER CODE BEGIN 2 */
uart配置就完成了(基本没有变化)
这里注意如果要使用中段方式初始化位置(main.c的开头部分)一定要加入 HAL_UART_Receive_IT(&huart1, (uint8_t *)aRxBuffer, RXBUFFERSIZE);这个函数,函数可以去工程里搜索,aRXBuffer是自己设置的接收缓存数组,RXBUFFERSIZE是一个宏定义,为20,代表着接收20个字节以后就完成接收。
这个函数里面重点注意两部分:
1:hart指向的这几个 :重点理解 huart->pRxBuffPtr = pData;
huart->RxXferSize = Size;
huart->RxXferCount=Size;
这几行代码是开头初始化要添加HAL_UART_Receive_IT(&huart1, (uint8_t *)aRxBuffer, RXBUFFERSIZE);函数的原因之一,对后面要用到的一些参数进行配置。
理论上接收完成一次都要重新调用一次这个函数,但是由于__HAL_LOCK(huart)对函数有一个锁定的过程,所以后续我选择直接处理huart里的那些数据,即上述的三个数据
if ((pData == NULL) || (Size == 0U))
{
return HAL_ERROR;
}
__HAL_LOCK(huart);
huart->pRxBuffPtr = pData;
huart->RxXferSize = Size;
huart->RxXferCount = Size;
huart->RxISR = NULL;
2: 这里掌握住huart->RxISR = UART_RxISR_8BIT;这行代码,至于8BIT是每次八个字节为一个数据,还有7BIT和9BIT,不过8BIT最常用(不包括停止位)这里是把hart中的空函数地址RxISR附上UART_RxISR_8BIT这个函数的地址;以后调用huart->RxISR就是指向UART_RxISR_8BIT(可以理解为“huart->RxISR” = “UART_RxISR_8BIT“,具体原因查看句柄huart的定义)
/* Set the Rx ISR function pointer according to the data word length */
if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE))
{
huart->RxISR = UART_RxISR_16BIT;
}
else
{
huart->RxISR = UART_RxISR_8BIT;
}
此外我还设置了一个RXbuffer[20]数组用于处理接收道德数据
/*定义接收缓冲区块 */
#define RXBUFFERSIZE 20//TXBUFFERSIZE
uint8_t aRxBuffer[RXBUFFERSIZE]={0};
int RXnum=0;
char RXbuffer[20]={0};
之后进入stm32g4xx_it.c文件(中断服务函数入口在这里)找到uart1的中断服务函数
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);//这里是自动生成的
/* USER CODE BEGIN USART1_IRQn 1 */
RXnum++;//每次进终端标志位加一
/* USER CODE END USART1_IRQn 1 */
}
这里添加一个在main.c里面设置好的全局变量RXnum来计算发送的字节数。
3、流程
当发送一串数据,首先进入中断服务函数
USART1_IRQHandler(void)(每个字节进入一次)
这里只有一个HAL_UART_IRQHandler(&huart1)函数 还有刚才设置的RXnum来计算发送的字节数
之后进入HAL_UART_IRQHandler(&huart1),(截取一部分)
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{
uint32_t isrflags = READ_REG(huart->Instance->ISR);
uint32_t cr1its = READ_REG(huart->Instance->CR1);
uint32_t cr3its = READ_REG(huart->Instance->CR3);
uint32_t errorflags;
uint32_t errorcode;
/* 如果没有错误发生 */
errorflags = (isrflags & (uint32_t)(USART_ISR_PE | USART_ISR_FE | USART_ISR_ORE | USART_ISR_NE));
if (errorflags == 0U)
{
/* UART in mode Receiver ---------------------------------------------------*/
if (((isrflags & USART_ISR_RXNE_RXFNE) != 0U)
&& (((cr1its & USART_CR1_RXNEIE_RXFNEIE) != 0U)
|| ((cr3its & USART_CR3_RXFTIE) != 0U)))
{
if (huart->RxISR != NULL)
{
huart->RxISR(huart);///从此处进入接收数据处理函数
}
return;
}
}
......
}
后面代码大部分是一些出错的情况的处理方法和一些不太常用的功能的条件逻辑的定义。
进来以后函数进入到里面的 huart->RxISR(huart);刚才说了可以理解为“huart->RxISR” = “UART_RxISR_8BIT“),因此 huart->RxISR(huart);即为UART_RxISR_8BIT(huart);因此就进入到HAL_UART_Receive_IT(&huart1, (uint8_t *)aRxBuffer, RXBUFFERSIZE);函数中的UART_RxISR_8BIT(huart);函数中:
static void UART_RxISR_8BIT(UART_HandleTypeDef *huart)
{
uint16_t uhMask = huart->Mask;
uint16_t uhdata;
/* Check that a Rx process is ongoing */
if (huart->RxState == HAL_UART_STATE_BUSY_RX)
{
uhdata = (uint16_t) READ_REG(huart->Instance->RDR);
*huart->pRxBuffPtr = (uint8_t)(uhdata & (uint8_t)uhMask);
huart->pRxBuffPtr++;
huart->RxXferCount--;
if (huart->RxXferCount == 0U)
{
/* Disable the UART Parity Error Interrupt and RXNE interrupts */
CLEAR_BIT(huart->Instance->CR1, (USART_CR1_RXNEIE_RXFNEIE | USART_CR1_PEIE));
/* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);
/* Rx process is completed, restore huart->RxState to Ready */
huart->RxState = HAL_UART_STATE_READY;
/* Clear RxISR function pointer */
huart->RxISR = NULL;
#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);//接收完成回调函数
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
}
}
else
{
/* Clear RXNE interrupt flag */
__HAL_UART_SEND_REQ(huart, UART_RXDATA_FLUSH_REQUEST);
}
}
uhdata = (uint16_t) READ_REG(huart->Instance->RDR);//读取当前字节
*huart->pRxBuffPtr = (uint8_t)(uhdata & (uint8_t)uhMask);//掩蔽多余的位数(数据为8位,定义的 读取为16位,无效位掩蔽掉)
huart->pRxBuffPtr++; //pRxBuffPtr为数组aRXBuffer的首地址,pRxBuffPtr=aRXBuffer[0];
huart->RxXferCount–;//此处为刚才定义的RXBUFFERSIZE,为20相当于20个数据
读取完成以即
if (huart->RxXferCount == 0U)
则进入下述回调函数里面:
HAL_UART_RxCpltCallback(huart);
此处依旧为接收定好长度的数据方式,长度为刚才设置好的20,
要想接收不定长,还要引入一个标志,可以表示接收完成的标志,这里使用了ISR寄存器的IDLE位
关于IDLE有两个操作:
__HAL_UART_GET_IT(&huart1,UART_IT_IDLE); //读取IDLE位的值
__HAL_UART_CLEAR_IT(&huart1, UART_CLEAR_IDLEF); //清空IDLE位,即设置为0
当接收数据小于设置好的值20,IDLE依旧可以变为1,而且此位置只有RXNE置为1时候才置为1,因此完全可以用来检测数据完成情况:检测这个IDLE的入口放在主函数while循环里;
当检测到IDLE非零以后进入回调函数HAL_UART_RxCpltCallback(&huart1);
if(RXnum!= 0 && __HAL_UART_GET_IT(&huart1,UART_IT_IDLE)!=0)
{
HAL_UART_RxCpltCallback(&huart1);
__HAL_UART_CLEAR_IT(&huart1, UART_CLEAR_IDLEF); //手动置0清空标志位
}
注意使用完一次IDLE以后一定要手动清0;
之后配置一下回调函数:
__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
// UNUSED(huart);
int a=0;
for(a=0;a<RXnum;a++)
{
RXbuffer[a]=aRxBuffer[a];
}
memset(aRxBuffer,0,20);
RXnum=0;
huart->pRxBuffPtr = aRxBuffer;
huart->RxXferSize = 20;
huart->RxXferCount = 20;
}
将aRxBuffer[];值赋给RXbuffer[],将aRxBuffer[];清空,RXnum清空,huart 的三个标志重置,方便下一次接收数据,此处就设置完成。
下面是使用:
#include <string.h>
if(RXbuffer[0]==0x11)
{
LED_Toggle(GPIOC, GPIO_PIN_8);
HAL_Delay(50);
LED_Toggle(GPIOC, GPIO_PIN_8);
memset(RXbuffer,0,20);
}
当接收到0x11的数据 灯闪烁一下;
最后加入一下串口发送:
1.HAL_UART_Transmit(&huart1, (uint8_t *)aTxBuffer, TXBUFFERSIZE,1000);
定义好发送缓存数组,即可发送数据,缺点是每次都要重新定义数组内容
2.printf的重映射:在uart.c以后
#include <stdio.h>
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
return ch;
}
之后就可以直接调用printf函数了。