STM32 HAL库的中断(一)外部中断&&UART中断

1次阅读

STM32里的中断

可以说大型任务里面逃不掉中断的使用,在从标准库开发转向HAL库开发的过程中,HAL库函数层层调用的函数关系着实令人头皮发麻,在此记录一下自己一下午的摸索,本文将持续更新。

外部中断

在使用一些按键触发操作时,我们可能需要使用外部中断来实现要求,HAL库使用外部中断的一个重要之处在于实现外部中断回调函数。

为了方便配置以及开发,这里全部使用STM32CubeMX完成初始化操作。

cubeMx配置

加入现在我使用了PE4引脚来外接按键,按键按下拉低引脚电平实现触发,在CubeMX中的配置如下:

  • 找到PE4引脚,设置模式为GPIO_EXTI4
  • System Core一栏中找到GPIO,找到PE4,设置GPIO modeExternal Interrupt Mode with Falling edge trigger detection
    • 你也可以按照你的开发板配置为上升沿或者变化沿触发(上升沿和下降沿都触发)
  • 按照需求配置上拉/下拉
    • 你也可以给这个按键起个名字,比如KEY0
  • 在NVIC中开启EXTI4中断,并设置中断的响应优先级。
  • 为了方便看到效果,可以额外添加一个LED灯指示按键效果
    • 确定你的LED灯连接在哪个引脚
    • 设置模式为GPIO_Output
    • 以同样的方式找到GPIO选项并对LED进行配置,配置输出模式,输出速度,上下拉电阻
  • Project Manager中配置CubeMX选项,注意在Code Generator中选中为每个外设都独立生成.c/.h文件
  • 生成代码并用uVision5打开
  • 可以先编译一下
  • 打开stm32f1xx_it.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中断

UART共有三种工作模式

  • 阻塞式
  • 非阻塞(中断)
  • DMA

一般常用的是中断方式。

利用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;
  }
}

进入中断处理函数时,函数会关掉所有相关中断来处理数据,处理完数据之后需要重新调用,开启中断。

未完待续。。。

liubobo
版权声明:本站原创文章,由 liubobo2022-03-17发表,共计8387字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。