# STM32_HAL库的STOP低功耗模式UART串口唤醒以及解决首字节出错的问题(全网第一解决方案) **Repository Path**: Mike_Zhou_Admin/STM32_HAL_UART_StopMode ## Basic Information - **Project Name**: STM32_HAL库的STOP低功耗模式UART串口唤醒以及解决首字节出错的问题(全网第一解决方案) - **Description**: STM32_HAL库的STOP低功耗模式UART串口唤醒以及解决首字节出错的问题(全网第一解决方案) https://mikezhou.blog.csdn.net/article/details/135553682 目前已解决 并更新了我的gitee库: (https://gitee.com/Mike_Zhou_Admin/STM32L4_HAL_User_LOW_POWER_Lib) - **Primary Language**: C - **License**: AGPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2024-10-12 - **Last Updated**: 2024-11-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: HAL, stm32, mcu, uart, 低功耗 ## README # STM32_HAL库的STOP低功耗模式UART串口唤醒以及解决首字节出错的问题(全网第一解决方案) #### 介绍 STM32_HAL库的STOP低功耗模式UART串口唤醒以及解决首字节出错的问题(全网第一解决方案) https://mikezhou.blog.csdn.net/article/details/135553682 目前已解决 并更新了我的gitee库: (https://gitee.com/Mike_Zhou_Admin/STM32L4_HAL_User_LOW_POWER_Lib) 【STM32】HAL库的STOP低功耗模式UART串口唤醒,解决首字节出错的问题(全网第一解决方案) 前文: [【STM32】HAL库的STOP低功耗模式UART串口唤醒,第一个接收字节出错的问题(疑难杂症)](https://blog.csdn.net/weixin_53403301/article/details/135481249) 目前已解决 并更新了我的gitee库: [基于HAL库建立自己的低功耗模式配置库(STM32L4系列低功耗所有配置汇总)](https://gitee.com/Mike_Zhou_Admin/STM32L4_HAL_User_LOW_POWER_Lib) @[toc] # 先说结论 要调用函数`HAL_UARTEx_EnableClockStopMode`或`__HAL_RCC_HSISTOP_ENABLE()`才能保证首字节在唤醒时不出错 # 最初的串口唤醒配置 在最初的串口唤醒配置中 我是用的如下配置(现已更新): [【STM32】HAL库低功耗STOP停止模式的串口唤醒(解决进入以后立马唤醒、串口唤醒和回调无法一起使用、接收数据不全、首字节错误的问题)](https://blog.csdn.net/weixin_53403301/article/details/129014963) 进入和退出函数为: ```c /*! * @brief 配置串口在停止模式下的唤醒 * * @param [in] huart: UART_HandleTypeDef类型的器件 * [in] EnableNotDisable: 使能或者关闭 * * @return None */ void Ctrl_UART_StopMode_WakeUp(UART_HandleTypeDef *huart,bool EnableNotDisable) { if(EnableNotDisable) { __HAL_RCC_WAKEUPSTOP_CLK_CONFIG(RCC_STOP_WAKEUPCLOCK_HSI); //保留唤醒用的HSI线 串口初始化时钟也必须要配置为HSI UART_WakeUpTypeDef UART_WakeUpStruct={0}; UART_WakeUpStruct.WakeUpEvent=UART_WAKEUP_ON_READDATA_NONEMPTY; //接收数据不为空时唤醒 HAL_UARTEx_StopModeWakeUpSourceConfig(huart,UART_WakeUpStruct); __HAL_UART_ENABLE_IT(huart,UART_IT_WUF); //开启唤醒中断 HAL_UARTEx_EnableStopMode(huart); //开启模式 } else { __HAL_UART_DISABLE_IT(huart,UART_IT_WUF); //关闭唤醒中断 HAL_UARTEx_DisableStopMode(huart); //关闭模式 } } ``` 在唤醒后 我将时钟初始化等 放到了唤醒回调中 导致长数据中间丢数据 于是进行改进 就是把初始化等退出以后的函数放到主线程 低功耗函数之后 就能完美解决这个问题 在进入低功耗模式之前的流程为: 1. 保留HSI线(系统RCC中的宏函数,其他用到HSI时钟唤醒的外设也建议进行此步骤,这里串口也必须要用这条时钟线) 2. 配置串口唤醒方式 3. 开启串口唤醒中断(可以不开) 4. 开启串口唤醒功能 其中 开启串口唤醒功能就是将此寄存器置1 ![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/f2e140bca09aa56e76eea4d6daeab4be.png) 当然 这里的串口唤醒是抄的网上的代码和流程 教程很多 我就不叙述了 ![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/23a0a102e088fdc8de8f26b5e491df32.png) 不过最后我发现在高速UART波特率的情况下 首唤醒字节仍然会丢失 且无论怎么改 都会出现此问题 具体调试和BUG复现过程: [【STM32】HAL库的STOP低功耗模式UART串口唤醒,第一个接收字节出错的问题(疑难杂症)](https://blog.csdn.net/weixin_53403301/article/details/135481249) # 官方文档的说明 官方文档提到 在进行HSI时钟作为唤醒时钟时 有两种可行性方案 第一次在调试时我没看见 后面找到了该文档 ![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/99a172e720ac0696dd459faa8d27dcf0.png) 上文中的函数: ```c __HAL_RCC_WAKEUPSTOP_CLK_CONFIG(RCC_STOP_WAKEUPCLOCK_HSI); ``` 只是将以下寄存器置位 配置的是唤醒时的时钟以及CSS备份时钟 只开启这个寄存器虽然能唤醒 但首字节会丢失 原因是串口唤醒需要时间 这个寄存器只管唤醒后用的是什么时钟 而停止模式下没有这个时钟 位于RCC_CFGR寄存器 ![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/01d0034949213516c9d6c42bc196fd87.png) 而用到STOP模式下HSI能强制工作的寄存器在这: 位于RCC_CR寄存器 ![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/17ff6d68e317b3d851124ee61a554f17.png) 其实这里如果配置了就没事了 对应函数: ```c /** * @brief Macros to enable or disable the force of the Internal High Speed oscillator (HSI) * in STOP mode to be quickly available as kernel clock for USARTs and I2Cs. * @note Keeping the HSI ON in STOP mode allows to avoid slowing down the communication * speed because of the HSI startup time. * @note The enable of this function has not effect on the HSION bit. * This parameter can be: ENABLE or DISABLE. * @retval None */ #define __HAL_RCC_HSISTOP_ENABLE() SET_BIT(RCC->CR, RCC_CR_HSIKERON) #define __HAL_RCC_HSISTOP_DISABLE() CLEAR_BIT(RCC->CR, RCC_CR_HSIKERON) ``` 但是我下文用到的是第二种方法(我也更推荐该方法 因为第二种方法的寄存器位于外设下 而不是RCC下 随便乱动RCC可能会有其他BUG) 那么下文继续调试: # 首字节出错的问题 经过前文的调试 首字节在出错时 是将采样后移了一位 发送AA 接收到55 也就是发送1010 1010 接收到0101 0101 所以我猜想应该是时钟的问题 进入低功耗需要时间 同样 唤醒也需要时间 所以可能就是这么点时间导致CPU延迟采样 所以串口接收不准 同样 在进入低功耗后 可以看到电流也是缓慢下降的 而采用jlink烧录后 实际上功耗下不来(大个1ma左右) 推测应该是jlink的信号复位保持了某些寄存器不清零(毕竟jlink和复位也算中断的一种) 这个问题困扰我好几天 我心中知道一定是时钟的问题 但一直没办法解决 直到我开发STM32L4R系列时 发现一个寄存器如L496不同: 也就是CR3的第23位 ![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/24cc382c1897f6ca6e440bd68008ed88.png) ![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/2a34d6c610efdc64da38d59d0e85bfdc.png) 起初 我以为是芯片手册版本的问题 直到我查看SDK发现真的如此 有个操作函数`HAL_UARTEx_EnableClockStopMode`长这样: ```c #if defined(USART_CR3_UCESM) /** * @brief Keep UART Clock enabled when in Stop Mode. * @note When the USART clock source is configured to be LSE or HSI, it is possible to keep enabled * this clock during STOP mode by setting the UCESM bit in USART_CR3 control register. * @note When LPUART is used to wakeup from stop with LSE is selected as LPUART clock source, * and desired baud rate is 9600 baud, the bit UCESM bit in LPUART_CR3 control register must be set. * @param huart UART handle. * @retval HAL status */ HAL_StatusTypeDef HAL_UARTEx_EnableClockStopMode(UART_HandleTypeDef *huart) { /* Process Locked */ __HAL_LOCK(huart); /* Set UCESM bit */ SET_BIT(huart->Instance->CR3, USART_CR3_UCESM); /* Process Unlocked */ __HAL_UNLOCK(huart); return HAL_OK; } ``` 而部分芯片是没有USART_CR3_UCESM这一位的 ```c #define USART_CR3_UCESM_Pos (23U) #define USART_CR3_UCESM_Msk (0x1UL << USART_CR3_UCESM_Pos) /*!< 0x02000000 */ #define USART_CR3_UCESM USART_CR3_UCESM_Msk /*!< USART Clock enable in Stop mode */ ``` 这个函数位于uart_ex中 注释里面说明了 要保证在低功耗时 保持时钟 所以要用到的这个函数 另外 采用LPUART时 如果波特率在9600之上 这一位需要置1 而函数`HAL_UARTEx_EnableStopMode`只对USART_CR1_UESM进行了操作 此寄存器是通用的 并且SDK中的函数也是通用的 ```c /** * @brief Enable UART Stop Mode. * @note The UART is able to wake up the MCU from Stop 1 mode as long as UART clock is HSI or LSE. * @param huart UART handle. * @retval HAL status */ HAL_StatusTypeDef HAL_UARTEx_EnableStopMode(UART_HandleTypeDef *huart) { /* Process Locked */ __HAL_LOCK(huart); /* Set UESM bit */ SET_BIT(huart->Instance->CR1, USART_CR1_UESM); /* Process Unlocked */ __HAL_UNLOCK(huart); return HAL_OK; } ``` 所以 我就把函数`HAL_UARTEx_EnableClockStopMode`添加到了进入低功耗之前 然后就解决了BUG 停止模式下的BUG再也没出错 ![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/72129d9212d31da72a6744b2f4104456.png) # 解决后的流程及代码 最后的串口唤醒配置流程就改为: 1. 保留HSI线(系统RCC中的宏函数,其他用到HSI时钟唤醒的外设也建议进行此步骤,这里串口也必须要用这条时钟线) 2. 配置串口唤醒方式 3. 开启串口唤醒中断(可以不开) 4. 开启串口STOP模式时钟 开启串口唤醒功能 代码: ```c /*! * @brief 配置串口在停止模式下的唤醒 * * @param [in] huart: UART_HandleTypeDef类型的器件 * [in] EnableNotDisable: 使能或者关闭 * * @return None */ void Ctrl_UART_StopMode_WakeUp(UART_HandleTypeDef *huart,bool EnableNotDisable) { if(EnableNotDisable) { __HAL_RCC_WAKEUPSTOP_CLK_CONFIG(RCC_STOP_WAKEUPCLOCK_HSI); //保留唤醒用的HSI线 串口初始化时钟也必须要配置为HSI UART_WakeUpTypeDef UART_WakeUpStruct={0}; UART_WakeUpStruct.WakeUpEvent=UART_WAKEUP_ON_READDATA_NONEMPTY; //接收数据不为空时唤醒 HAL_UARTEx_StopModeWakeUpSourceConfig(huart,UART_WakeUpStruct); __HAL_UART_ENABLE_IT(huart,UART_IT_WUF); //开启唤醒中断 HAL_UARTEx_EnableClockStopMode(huart); HAL_UARTEx_EnableStopMode(huart); //开启模式 } else { __HAL_UART_DISABLE_IT(huart,UART_IT_WUF); //关闭唤醒中断 HAL_UARTEx_DisableClockStopMode(huart); HAL_UARTEx_DisableStopMode(huart); //关闭模式 } } ``` 其中 第一步的保留HSI时钟线 也就是RCC中的宏函数 这一步我在测试后发现 如果使用了`HAL_UARTEx_EnableClockStopMode`函数 则不需要进行第一步 但是串口所使用的时钟还是要配置为HSI线 # 为什么说是“全网第一” 最后 我在网上搜素`HAL_UARTEx_EnableClockStopMode`函数 发现国内无论是CSDN还是百度都搜不到 ![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/1a0a45f3b2d17b6f581de80695897ec0.png) ![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/d217e40e287c659cba62bf97776532c4.png) 只有GitHub上寥寥无几的几个搜素结果 并且都是与LPUART有关的(虽然这个与UART配置流程和函数基本一样) ![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/49ed6965481a54b17307c5fdb553680c.png)