可以说大型任务里面逃不掉中断的使用,在从标准库开发转向HAL库开发的过程中,HAL库函数层层调用的函数关系着实令人头皮发麻,在此记录一下自己一下午的摸索,本文将持续更新。
在使用一些按键触发操作时,我们可能需要使用外部中断来实现要求,HAL库使用外部中断的一个重要之处在于实现外部中断回调函数。
为了方便配置以及开发,这里全部使用STM32CubeMX完成初始化操作。
加入现在我使用了PE4引脚来外接按键,按键按下拉低引脚电平实现触发,在CubeMX中的配置如下:
GPIO_EXTI4
System Core
一栏中找到GPIO
,找到PE4
,设置GPIO mode
为External Interrupt Mode with Falling edge trigger detection
KEY0
GPIO_Output
GPIO
选项并对LED进行配置,配置输出模式,输出速度,上下拉电阻Project Manager
中配置CubeMX选项,注意在Code Generator
中选中为每个外设都独立生成.c/.h
文件void EXTI4_IRQHandler(void);
这行代码就是外部中断Line4的中断处理函数,右键go to Denifition of void EXTI4_IRQHandler(void)
,可以在stm32f1xx_it.c
中看到如下代码
/******************************************************************************/
/* STM32F1xx Peripheral Interrupt Handlers */
/* Add here the Interrupt Handlers for the used peripherals. */
/* For the available peripheral interrupt handler names, */
/* please refer to the startup file (startup_stm32f1xx.s). */
/******************************************************************************/
/**
* @brief This function handles EXTI line4 interrupt.
*/
void EXTI4_IRQHandler(void)
{
/* USER CODE BEGIN EXTI4_IRQn 0 */
/* USER CODE END EXTI4_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4);
/* USER CODE BEGIN EXTI4_IRQn 1 */
/* USER CODE END EXTI4_IRQn 1 */
}
继续右键go to Defnition of HAL_GPIO_EXTI_IRQHandler
/**
* @brief This function handles EXTI interrupt request.
* @param GPIO_Pin: Specifies the pins connected EXTI line
* @retval None
*/
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
/* EXTI line interrupt detected */
if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u)
{
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
HAL_GPIO_EXTI_Callback(GPIO_Pin);
}
}
这段代码就是处理外部中断请求,首先判断并清除中断标志位,然后调用HAL_GPIO_EXTI_Callback(GPIO_Pin);
处理中断,同样的方式找到HAL_GPIO_EXTI_Callback
的定义,你可以看到这个函数的声明前面有一个__weak
声明,这个声明表示这个函数一旦被重新声明,这里的函数就自动失效,其他函数调用的时候就会找到你新定义的同名函数。
关于在哪里重新声明并实现这个函数,我个人的想法是写在每个外设单独的.c/.h
文件里,然后在main.h
文件中引用,不要全写在main.c
文件里。
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == KEY0_Pin)
{
HAL_Delay(20);
if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin)==GPIO_PIN_RESET)
{
HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
}
}
}
添加一些简单的逻辑处理代码,编译烧写,尝试一下效果吧。
如果有多个外部中断线,需要在函数里面添加多个if
代码块,以判断是哪条线产生中断。
总而言之,外部中断的处理就是开启对应的中断,然后实现中断回调(callback
)函数。这个过程不是很复杂,可以看着函数调用顺序捋一下。
UART共有三种工作模式
一般常用的是中断方式。
利用CubeMX配置USART1,设置模式为Asynchronous
异步,下方的参数设置默认即可,也可以修改波特率,收发模式,起始位数据位校验位等等。
注意开启并配置NVIC中断。
按照与第一部分同样的方式生成代码,打开stm32f1xx_it.h
,找到void USART1_IRQHandler(void);
/**
* @brief This function handles USART1 global interrupt.
*/
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 */
/* USER CODE END USART1_IRQn 1 */
}
继续找函数,对代码的解释都写在了注释里
/**
* @brief This function handles UART interrupt request.
* @param huart Pointer to a UART_HandleTypeDef structure that contains
* the configuration information for the specified UART module.
* @retval None
*/
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{
uint32_t isrflags = READ_REG(huart->Instance->SR);
uint32_t cr1its = READ_REG(huart->Instance->CR1);
uint32_t cr3its = READ_REG(huart->Instance->CR3);
uint32_t errorflags = 0x00U;
uint32_t dmarequest = 0x00U;
/* If no error occurs */
//
errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE)); //找到每个变量的定义可以发现这里检测了各种错误
if (errorflags == RESET) //如果没有出现任何错误
{
/* UART in mode Receiver -------------------------------------------------*/
if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET)) //判断接收寄存器非空,并且中断使能
{
UART_Receive_IT(huart); //进入接收处理函数
return;
}
}
//以下代码都是判断并处理一些错误或者DMA模式下的一些处理,省略
//.................
//.................
/
// 如果是发送模式,
/* UART in mode Transmitter ------------------------------------------------*/
if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
{
UART_Transmit_IT(huart);
return;
}
/* UART in mode Transmitter end --------------------------------------------*/
if (((isrflags & USART_SR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET))
{
UART_EndTransmit_IT(huart);
return;
}
}
/**
* @brief Receives an amount of data in non blocking mode
* @param huart Pointer to a UART_HandleTypeDef structure that contains
* the configuration information for the specified UART module.
* @retval HAL status
*/
static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)
{
uint8_t *pdata8bits;
uint16_t *pdata16bits;
//以下代码主要是检查UART的相关配置,比如数据位等等
/* Check that a Rx process is ongoing */
if (huart->RxState == HAL_UART_STATE_BUSY_RX)
{
if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE))
{
pdata8bits = NULL;
pdata16bits = (uint16_t *) huart->pRxBuffPtr;
*pdata16bits = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF);
huart->pRxBuffPtr += 2U;
}
else
{
pdata8bits = (uint8_t *) huart->pRxBuffPtr;
pdata16bits = NULL;
if ((huart->Init.WordLength == UART_WORDLENGTH_9B) || ((huart->Init.WordLength == UART_WORDLENGTH_8B) && (huart->Init.Parity == UART_PARITY_NONE)))
{
*pdata8bits = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
}
else
{
*pdata8bits = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F);
}
huart->pRxBuffPtr += 1U;
}
//这里是在检查接收的数据数量是否完成
if (--huart->RxXferCount == 0U)
{
/* Disable the UART Data Register not empty Interrupt */
__HAL_UART_DISABLE_IT(huart, UART_IT_RXNE);
/* 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;
//检查当前接收模式是事件阻塞式还是中断式
/* Check current reception Mode :
If Reception till IDLE event has been selected : */
if (huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE)
{
/* Set reception type to Standard */
huart->ReceptionType = HAL_UART_RECEPTION_STANDARD;
/* Disable IDLE interrupt */
CLEAR_BIT(huart->Instance->CR1, USART_CR1_IDLEIE);
/* Check if IDLE flag is set */
if (__HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE))
{
/* Clear IDLE flag in ISR */
__HAL_UART_CLEAR_IDLEFLAG(huart);
}
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered Rx Event callback*/
huart->RxEventCallback(huart, huart->RxXferSize);
#else
/*Call legacy weak Rx Event callback*/
HAL_UARTEx_RxEventCallback(huart, huart->RxXferSize);
#endif
}
else
// 中断处理
{
/* Standard reception API called */
#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 */
}
return HAL_OK;
}
return HAL_OK;
}
else
{
return HAL_BUSY;
}
}
HAL_UART_RxCpltCallback(huart);
,我们要重新实现这个虚函数。
/**
* @brief Wraps up transmission in non blocking mode.
* @param huart Pointer to a UART_HandleTypeDef structure that contains
* the configuration information for the specified UART module.
* @retval HAL status
*/
static HAL_StatusTypeDef UART_EndTransmit_IT(UART_HandleTypeDef *huart)
{
/* Disable the UART Transmit Complete Interrupt */
__HAL_UART_DISABLE_IT(huart, UART_IT_TC);
/* Tx process is ended, restore huart->gState to Ready */
huart->gState = HAL_UART_STATE_READY;
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered Tx complete callback*/
huart->TxCpltCallback(huart);
#else
/*Call legacy weak Tx complete callback*/
HAL_UART_TxCpltCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
return HAL_OK;
}
HAL_UART_TxCpltCallback(huart);
我们要重新实现这个虚函数
为了开启接收或者发送中断,我们还要在main.c主程序while前添加
HAL_UART_Transmit_IT(&huart1,(uint8_t *)&aRxBuffer, 1); //开启发送中断
HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1); //开启接收中断
以及在中断回调函数最后添加相同的代码。
至于为何会是这样,我们可以找到这两个函数的实现代码
/**
* @brief Sends an amount of data in non blocking mode.
* @note When UART parity is not enabled (PCE = 0), and Word Length is configured to 9 bits (M1-M0 = 01),
* the sent data is handled as a set of u16. In this case, Size must indicate the number
* of u16 provided through pData.
* @param huart Pointer to a UART_HandleTypeDef structure that contains
* the configuration information for the specified UART module.
* @param pData Pointer to data buffer (u8 or u16 data elements).
* @param Size Amount of data elements (u8 or u16) to be sent
* @retval HAL status
*/
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
/* Check that a Tx process is not already ongoing */
if (huart->gState == HAL_UART_STATE_READY)
{
if ((pData == NULL) || (Size == 0U))
{
return HAL_ERROR;
}
/* Process Locked */
__HAL_LOCK(huart);
huart->pTxBuffPtr = pData;
huart->TxXferSize = Size;
huart->TxXferCount = Size;
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->gState = HAL_UART_STATE_BUSY_TX;
/* Process Unlocked */
__HAL_UNLOCK(huart);
// 使能发送寄存器空中断
/* Enable the UART Transmit data register empty Interrupt */
__HAL_UART_ENABLE_IT(huart, UART_IT_TXE);
return HAL_OK;
}
else
{
return HAL_BUSY;
}
}
进入中断处理函数时,函数会关掉所有相关中断来处理数据,处理完数据之后需要重新调用,开启中断。