你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32小技巧(1) HAL自定义串口中断回调 模块化代码进行解耦

[复制链接]
STMCU小助手 发布时间:2022-8-23 16:13
前言
用STM32CubeMx生成初始化配置代码是十分方便的,但是在处理多个项目的时候,就会发现,自动生成的中断函数调用的都是同一个回调函数,在这个回调函数中又要根据不同的句柄来处理,不同的项目外设又有所区别,还是要花点时间来进行移植。
比如说,我在项目A中使用到了串口1、串口2、串口3,但是在项目B中,我只使用到了串口2,并且在项目B中,串口2的功能是项目A的串口1的功能,在这种情况下,我就需要再定义一次 HAL_UART_RxCpltCallback() ,然后移植对应的功能。当然也可以重新 define 一个宏,来对应所使用的串口,这样移植上也会快许多。但是假如,后面新来了一个需求,需要实现项目C的串口3的功能,原本只要添加对应的头文件,模块处理这些外设是最简便的,但是这时候会发现,项目C中也定义了HAL_UART_RxCpltCallback(),这就需要人工去移植这部分的代码了。
而我就在想,项目C的串口3是已经实现的模块,且这个模块是单独的.c文件和.h文件,并且不和其他的串口耦合在一起,这样的轮子造起车子来就快了许多了。但是找遍全网,会发现,关于HAL库中串口中断的教程都是基本的实现,没有更深层次的解释了,在学习MEMS库的过程中,看到官方的代码,在使用按钮触发外部中断的时候,竟然可以自定义外部中断的回调函数,进行一番学习之后,终于掌握了自定义中断回调的方法,不仅限于串口中断函数的回调,其他的外设都是相类似的,都是可以自定义中断的回调函数。
高手可以直接跳过第一节的内容,直接跳到第二节看如何进行操作。
一、HAL库的中断实现
中断的原理网上到处都有,也可以参考正点原子这种开源的教程。
我们正常在STM32CubeMX配置好中断后,通过 GENERATE CODE 生成代码,在 stm32f7xx_it.c 中,就会自动生成中断的处理函数,比如 USART2_IRQHandler,可以看到串口2的全局中断仅仅调用了 HAL_UART_IRQHandler 函数,该函数用于处理UART中断请求。

  1. /**
  2.   * @brief This function handles USART2 global interrupt.
  3.   */
  4. void USART2_IRQHandler(void)
  5. {
  6.   /* USER CODE BEGIN USART2_IRQn 0 */

  7.   /* USER CODE END USART2_IRQn 0 */
  8.   HAL_UART_IRQHandler(&huart2);
  9.   /* USER CODE BEGIN USART2_IRQn 1 */

  10.   /* USER CODE END USART2_IRQn 1 */
  11. }
复制代码

我们进入 HAL_UART_IRQHandler 看看里面具体的实现。这个实现虽然很长,但是逻辑比较简单。首先判断是否有错误产生,没有错误就会调用 UART_Receive_IT,用于在非阻塞模式下接收大量数据。如果在接收的过程中有错误发生,那么就会进行标志位的处理,再下面一点就可以看到 USE_HAL_UART_REGISTER_CALLBACKS 这个宏定义,这就是我们自定义中断函数的关键了。如果我们定义了 USE_HAL_UART_REGISTER_CALLBACKS,那么我们通过注册自定义的中断函数后,在这里 huart->ErrorCallback(huart) 就可以直接跳转到我们的自定义的接收异常函数了,而不是 HAL_UART_ErrorCallback(huart) 这个HAL通用的接收异常处理函数。
注:F7的板子在公司,写这个教程的时候手头只有 NUCLEO-L152RE 的开发板,使用的库是 STM32Cube_FW_L1_V1.10.2。 STM32CubeMx生成的代码有所区别,但原理上是一样的。F767ZI 是通过 huart->RxISR(huart) 这个方式来调用中断处理函数的,最终调用的是 UART_RxISR_8BIT 这样子的函数。当然后面推出了新的固件版本,也可能导致具体的实现有所区别。

  1. void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
  2. {
  3.   uint32_t isrflags   = READ_REG(huart->Instance->SR);
  4.   uint32_t cr1its     = READ_REG(huart->Instance->CR1);
  5.   uint32_t cr3its     = READ_REG(huart->Instance->CR3);
  6.   uint32_t errorflags = 0x00U;
  7.   uint32_t dmarequest = 0x00U;

  8.   /* 如果没有错误发生 */
  9.   errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));
  10.   if (errorflags == RESET)
  11.   {
  12.     /* UART处于接收模式 -------------------------------------------------*/
  13.     if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
  14.     {
  15.       UART_Receive_IT(huart);
  16.       return;
  17.     }
  18.   }

  19.   /* 如果发生一些错误 */
  20.   if ((errorflags != RESET) && (((cr3its & USART_CR3_EIE) != RESET) || ((cr1its & (USART_CR1_RXNEIE | USART_CR1_PEIE)) != RESET)))
  21.   {
  22.     /* UART parity error interrupt occurred ----------------------------------*/
  23.     if (((isrflags & USART_SR_PE) != RESET) && ((cr1its & USART_CR1_PEIE) != RESET))
  24.     {
  25.       huart->ErrorCode |= HAL_UART_ERROR_PE;
  26.     }

  27.     /* UART noise error interrupt occurred -----------------------------------*/
  28.     if (((isrflags & USART_SR_NE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
  29.     {
  30.       huart->ErrorCode |= HAL_UART_ERROR_NE;
  31.     }

  32.     /* UART frame error interrupt occurred -----------------------------------*/
  33.     if (((isrflags & USART_SR_FE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
  34.     {
  35.       huart->ErrorCode |= HAL_UART_ERROR_FE;
  36.     }

  37.     /* UART Over-Run interrupt occurred --------------------------------------*/
  38.     if (((isrflags & USART_SR_ORE) != RESET) && (((cr1its & USART_CR1_RXNEIE) != RESET) || ((cr3its & USART_CR3_EIE) != RESET)))
  39.     {
  40.       huart->ErrorCode |= HAL_UART_ERROR_ORE;
  41.     }

  42.     /* Call UART Error Call back function if need be --------------------------*/
  43.     if (huart->ErrorCode != HAL_UART_ERROR_NONE)
  44.     {
  45.       /* UART in mode Receiver -----------------------------------------------*/
  46.       if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
  47.       {
  48.         UART_Receive_IT(huart);
  49.       }

  50.       /* If Overrun error occurs, or if any error occurs in DMA mode reception,
  51.          consider error as blocking */
  52.       dmarequest = HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR);
  53.       if (((huart->ErrorCode & HAL_UART_ERROR_ORE) != RESET) || dmarequest)
  54.       {
  55.         /* Blocking error : transfer is aborted
  56.            Set the UART state ready to be able to start again the process,
  57.            Disable Rx Interrupts, and disable Rx DMA request, if ongoing */
  58.         UART_EndRxTransfer(huart);

  59.         /* Disable the UART DMA Rx request if enabled */
  60.         if (HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR))
  61.         {
  62.           CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);

  63.           /* Abort the UART DMA Rx channel */
  64.           if (huart->hdmarx != NULL)
  65.           {
  66.             /* Set the UART DMA Abort callback :
  67.                will lead to call HAL_UART_ErrorCallback() at end of DMA abort procedure */
  68.             huart->hdmarx->XferAbortCallback = UART_DMAAbortOnError;
  69.             if (HAL_DMA_Abort_IT(huart->hdmarx) != HAL_OK)
  70.             {
  71.               /* Call Directly XferAbortCallback function in case of error */
  72.               huart->hdmarx->XferAbortCallback(huart->hdmarx);
  73.             }
  74.           }
  75.           else
  76.           {
  77.             /* Call user error callback */
  78. #if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
  79.             /*Call registered error callback*/
  80.             huart->ErrorCallback(huart);
  81. #else
  82.             /*Call legacy weak error callback*/
  83.             HAL_UART_ErrorCallback(huart);
  84. #endif /* USE_HAL_UART_REGISTER_CALLBACKS */
  85.           }
  86.         }
  87.         else
  88.         {
  89.           /* Call user error callback */
  90. #if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
  91.           /*Call registered error callback*/
  92.           huart->ErrorCallback(huart);
  93. #else
  94.           /*Call legacy weak error callback*/
  95.           HAL_UART_ErrorCallback(huart);
  96. #endif /* USE_HAL_UART_REGISTER_CALLBACKS */
  97.         }
  98.       }
  99.       else
  100.       {
  101.         /* Non Blocking error : transfer could go on.
  102.            Error is notified to user through user error callback */
  103. #if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
  104.         /*Call registered error callback*/
  105.         huart->ErrorCallback(huart);
  106. #else
  107.         /*Call legacy weak error callback*/
  108.         HAL_UART_ErrorCallback(huart);
  109. #endif /* USE_HAL_UART_REGISTER_CALLBACKS */

  110.         huart->ErrorCode = HAL_UART_ERROR_NONE;
  111.       }
  112.     }
  113.     return;
  114.   } /* 如果发生错误则结束 */

  115.   /* UART 处于发送模式,准备在非阻塞模式下发送大量数据。 ---------------------------*/
  116.   if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
  117.   {
  118.     UART_Transmit_IT(huart);
  119.     return;
  120.   }

  121.   /*  UART 处于发送结束模式,在非阻塞模式下结束传输。 -------------------------------*/
  122.   if (((isrflags & USART_SR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET))
  123.   {
  124.     UART_EndTransmit_IT(huart);
  125.     return;
  126.   }
  127. }
复制代码

然后我们看看没有接收错误发生时,调用的函数 UART_Receive_IT(huart)。这个首先根据数据位的长度来进行接收数据,(9bit和少于9bit的处理是不一样的),串口数据就保存在 huart->pRxBuffPtr,每接收一个数据,那么 huart->RxXferCount 就减一,直至为0,这时候就会触发接收完成的中断了。至于需要接收几个字节来触发中断,就取决于初始化时候的设置了。
huart->pRxBuffPtr变为0后,会先关闭中断,如果使能了USE_HAL_UART_REGISTER_CALLBACKS ,就会调用 huart->RxCpltCallback(huart),我们就可以通过注册中断处理函数来跳转到我们自定义的函数,而不是HAL库本身的接收完成中断函数HAL_UART_RxCpltCallback(huart)。而如果我们使能了USE_HAL_UART_REGISTER_CALLBACKS,却没有去注册自定义函数,最终调用的还是HAL库本身的中断处理函数,这是因为在 HAL_UART_Init 初始化中会调用函数 UART_InitCallbacksToDefault,这个函数会将所有的回调函数初始化为其默认值。
注:接收完成后,这个会自动将关闭中断,接收完指定的字节之后,还需要继续接收的话就需要再次打开中断了。

  1. static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)
  2. {
  3.   uint16_t *tmp;

  4.   /* 检查接收进程是否正在进行 */
  5.   if (huart->RxState == HAL_UART_STATE_BUSY_RX)
  6.   {
  7.     if (huart->Init.WordLength == UART_WORDLENGTH_9B)
  8.     {
  9.       tmp = (uint16_t *) huart->pRxBuffPtr;
  10.       if (huart->Init.Parity == UART_PARITY_NONE)
  11.       {
  12.         *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF);
  13.         huart->pRxBuffPtr += 2U;
  14.       }
  15.       else
  16.       {
  17.         *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x00FF);
  18.         huart->pRxBuffPtr += 1U;
  19.       }
  20.     }
  21.     else
  22.     {
  23.       if (huart->Init.Parity == UART_PARITY_NONE)
  24.       {
  25.         *huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
  26.       }
  27.       else
  28.       {
  29.         *huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F);
  30.       }
  31.     }

  32.     if (--huart->RxXferCount == 0U)
  33.     {
  34.       /* 禁止UART数据寄存器不为空的中断 */
  35.       __HAL_UART_DISABLE_IT(huart, UART_IT_RXNE);

  36.       /* 禁用UART奇偶校验错误中断 */
  37.       __HAL_UART_DISABLE_IT(huart, UART_IT_PE);

  38.       /* 禁用UART错误中断:(帧错误,噪声错误,溢出错误) */
  39.       __HAL_UART_DISABLE_IT(huart, UART_IT_ERR);

  40.       /* Rx进程完成,将huart-> RxState还原为Ready */
  41.       huart->RxState = HAL_UART_STATE_READY;

  42. #if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
  43.       /* 调用注册的Rx完成回调函数 */
  44.       huart->RxCpltCallback(huart);
  45. #else
  46.       /*调用旧版弱Rx完成回调函数*/
  47.       HAL_UART_RxCpltCallback(huart);
  48. #endif /* USE_HAL_UART_REGISTER_CALLBACKS */

  49.       return HAL_OK;
  50.     }
  51.     return HAL_OK;
  52.   }
  53.   else
  54.   {
  55.     return HAL_BUSY;
  56.   }
  57. }
复制代码

二、使用方法
原理性的代码解析已经在前面一章节解析了,这一章节就是讲要如何实现,其他的都不难,重点是要知道 USE_HAL_UART_REGISTER_CALLBACKS 这个宏,其他的不懂的地方就可以根据这个线索来一步一步的追踪了。

20210131202630176.png

图2.1 设置串口参数

2021013120265496.png

图2.2 使能串口中断并设置优先级

前两步都是设置串口中断的设置,按照常规来设置就可以了,重点了如何使能 USE_HAL_UART_REGISTER_CALLBACKS 这个宏,这个宏在 Project Manager 中的 Advanced Settings 右边的 Register Callback 中,选择 UART 为 ENABLE ,注意不是 USART,别选错了。
当然也可以在生成的项目中搜索 USE_HAL_UART_REGISTER_CALLBACKS 手工改成1,但是假如后续又通过STM32CubeMX修改配置的话,再生成代码,会将这里的配置覆盖过去,如果没注意这个点的就很容易出错,建议还是在这里修改比较稳妥一些。

20210131203021383.png

代码生成之后,默认是没有打开串口中断的,通过 HAL_UART_RegisterCallback() 自定义回调函数,再开启中断就完成了我们的目的。
我们可以专门在一个.c文件来自定义接收完成的回调函数并打开中断,在main函数中调用这个初始化函数就可以了。这样就能和其他串口解耦,移植起来就很方便了。比如我需要指定一个串口来使用 Letter Shell( github上的开源项目,我的文章中也有这个教程),那么我就写一个 shell_port.c 文件,仅仅需要通过宏来指定使用shell 的串口句柄,其他的都不需要修改,直接添加这个 c文件,在main函数中使用 userShellInit() 来初始化,就能直接使用这个扩展模块了,相比于其他的方式,这个移植过程可以说是相当简便省事了(其他的形式需要在 usart.c 中定义串口接收的数组,再定义接收的个数,再在中断中移植自己的功能,再在main函数中打开串口中断,每建立一个新的项目都需要这么做,尤其时间长了之后,要配置串口中断的函数的时候就容易忘记函数名称,真的很烦)。

HAL_UART_RegisterCallback()
这个函数的第一个参数是 huart ,要配置的串口句柄。
这个函数的第二个参数是 CallbackID ,不同的外设所支持的回调函数ID不同,对于串口来说,HAL_UART_CallbackIDTypeDef 列出了所支持的各个功能ID,我们要在串口接收完成后调用我们自定义的ID,使用的就是 HAL_UART_RX_COMPLETE_CB_ID 。
这个函数的第三个参数是 pCallback ,我们自定义的回调函数,但是这个函数有要求,参照 pUART_CallbackTypeDef 的定义。返回应该是void,并且需要一个参数 UART_HandleTypeDef *huart。

typedef void (*pUART_CallbackTypeDef)(UART_HandleTypeDef *huart); // pointer to an UART callback function

注:这里设置的是每接收一个字节就触发中断,但是HAL库本身处理串口中断标志位后,会关闭中断,所以我们就需要 HAL_UART_Receive_IT 再次开启中断。

  1. #include "shell_port.h"
  2. #include "usart.h"

  3. Shell shell;
  4. char shellBuffer[512];

  5. #define userShellhuart                             huart2        //shell 使用到的串口句柄
  6. #define SHELL_UART_REC_LEN_ONCE         1           //串口单次接收的个数
  7. uint8_t uartRecBuffer[SHELL_UART_REC_LEN_ONCE];                //串口接收的buffer


  8. /**
  9. * @brief 用户shell写
  10. *
  11. * @param data 数据
  12. */
  13. void userShellWrite(char data)
  14. {
  15.   HAL_UART_Transmit(&userShellhuart,(uint8_t *)&data, 1,1000);
  16. }


  17. /**
  18. * @brief 用户shell读
  19. *
  20. * @param data 数据
  21. * @return char 状态
  22. */
  23. signed char userShellRead(char *data)
  24. {
  25.   if(HAL_UART_Receive(&userShellhuart,(uint8_t *)data, 1, 0) == HAL_OK)
  26.   {
  27.       return 0;
  28.   }
  29.   else
  30.   {
  31.       return -1;
  32.   }
  33. }

  34. /**
  35. * @brief 自定义函数串口接收完成中断 RxCpltCallback
  36. */
  37. void ShellRxCpltCallback(UART_HandleTypeDef *huart)
  38. {
  39.   shellHandler(&shell, uartRecBuffer[0]);   //命令行处理函数
  40.   HAL_UART_Receive_IT(huart, (uint8_t *)uartRecBuffer, SHELL_UART_REC_LEN_ONCE);//使能串口中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量
  41. }

  42. /**
  43. * @brief 注册串口接收中断到自己的自定义函数
  44. */
  45. void ShellReceiveCallbackRemap(void)
  46. {
  47.   HAL_UART_RegisterCallback(&userShellhuart,HAL_UART_RX_COMPLETE_CB_ID,ShellRxCpltCallback);    //注册串口接收中断到自己的自定义函数
  48.   HAL_UART_Receive_IT(&userShellhuart, (uint8_t *)uartRecBuffer, SHELL_UART_REC_LEN_ONCE);      //使能串口中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量
  49. }


  50. /**
  51. * @brief 用户shell初始化
  52. *
  53. */
  54. void userShellInit(void)
  55. {
  56.   shell.write = userShellWrite;         //指定串口写入的函数
  57.   shell.read = userShellRead;           //指定串口读取的函数
  58.   ShellReceiveCallbackRemap();          //注册串口接收中断到自己的自定义函数
  59.   shellInit(&shell, shellBuffer, 512);

  60. }
复制代码

————————————————
转载:fafuwxm


收藏 评论0 发布时间:2022-8-23 16:13

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版