C++14 及以上版本的 stm32 hal 库警告

Posted

技术标签:

【中文标题】C++14 及以上版本的 stm32 hal 库警告【英文标题】:stm32 hal library warning with C++14 & above 【发布时间】:2018-08-10 04:49:49 【问题描述】:

我也在 STM32 社区论坛上发布了相同的question,但没有收到回复。

我在启用 C++14 的项目中使用 stm32 HAL 库。它向我发出以下警告,我无法摆脱。

../platform/stm32/l4/STM32L4xx_HAL_Driver/Inc/stm32l4xx_hal_rcc.h:735:57:

警告:转换为 void 不会访问 'volatile 类型的对象 uint32_t aka volatile long unsigned int' UNUSED(tmpreg); \

当调用 __GPIOX_CLK_ENABLE() 或 __HAL_RCC_GPIOX_CLK_ENABLE 时会发生这种情况。

有没有人能够摆脱上述警告而保持 HAL 源代码完好无损。

或者任何可以做的想法。

当前警告级别为 -Wall。

我在 l4 和 f4 系列代码中都遇到过上述问题。

示例代码:

int main(void)

    HAL_Init();

    __GPIOB_CLK_ENABLE();
    GPIO_InitTypeDef GPIO_InitStructure;

    GPIO_InitStructure.Pin = GPIO_PIN_7;

    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_InitStructure.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);

    for (;;)
    
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);
        HAL_Delay(500);
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);
        HAL_Delay(500);
    

罪魁祸首是__GPIOB_CLK_ENABLE(),它被扩展为以下内容(在 ST 驱动程序中)。

#define __HAL_RCC_GPIOB_CLK_ENABLE()           do  \
                                                 __IO uint32_t tmpreg; \
                                                 SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOBEN); \
                                                 /* Delay after an RCC peripheral clock enabling */ \
                                                 tmpreg = READ_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOBEN); \
                                                 UNUSED(tmpreg); \
                                                while(0)

我最初的问题旨在找出解决方案,使底层 ST 驱动程序保持不变。 一种可能的解决方案是使用直接寄存器访问而不通过库提供的方便宏。

提前谢谢你。

【问题讨论】:

我做了一些研究,这个警告的原因是 UNUSED 宏,它是上述宏的一部分,对 void 进行了不稳定的引用。它与 C++14 和 -Wall 无关,但所有 g++ 版本都提供相同的诊断。原因可以在链接的副本中找到。解决方案是不使用 volatile 引用,这在编写与硬件相关的代码时是一种可疑的做法——改用 volatile 指针。也许您不小心使用了引用? C++11 中不会发出警告。我可以使用 C++11 成功编译相同的代码,而不会收到 -Wall 的任何警告。绝对不是all g++ 编译器版本。这就是这个问题背后的原因。 绝对不是duplicate。我强烈建议您下载 STM32 CubeMX HAL 源代码并在 C++11 和 C++14 中编译它。该警告在 C++14 中变得明显,但在 C++11 中从未出现。 我能够通过简单地将任何 volatile 引用转换为 void 来将其复制到 C++03。所以这与编译器版本无关。你的调用者代码中一定有一些在 C++14 中表现不同的东西。请使用包含发出警告的调用者代码的MCVE 编辑您的问题。 我现在重新打开这个问题,但我不相信没有例子就可以回答。很可能问题出在 ST 驱动程序上,尽管据我了解,这些驱动程序是用纯 C 编写的?参考来自哪里? 【参考方案1】:

问题在于-std=c++14volatile 表达式的语义转换为(void),并为其引入明显* 无条件警告,ST 的编码人员试图“三重确定”寄存器读取会发生。

UNUSED()宏的定义是

#define UNUSED(x) ((void)(x))

__IO 被定义为

#define     __IO    volatile

那么__HAL_RCC_GPIOB_CLK_ENABLE()的扩展就是

do 
    volatile uint32_t tmpreg;
    RCC->AHB2ENR |= RCC_AHB2ENR_GPIOBEN;
    /* Delay after an RCC peripheral clock enabling */
    tmpreg = RCC->AHB2ENR & RCC_AHB2ENR_GPIOBEN;
    ((void)(tmpreg));
 while(0)

各种STM32勘误说法推荐寄存器的延迟和回读

为了管理外设对寄存器的读/写,应考虑 RCC 外设时钟启用和有效外设启用之间的延迟。

[...]

从相应的寄存器中插入一个伪读操作 启用外设时钟。

由于所有外设寄存器当然都声明为volatile,因此仅包含相关寄存器的简单表达式将通过相同的外设总线强制回读具有必要的等待状态,因此这就足够了:

do 
    RCC->AHB2ENR |= RCC_AHB2ENR_GPIOBEN;
    /* Delay after an RCC peripheral clock enabling */
    RCC->AHB2ENR;
 while(0)

对于一些有缺陷的编译器来说,其余的可能是一种过度设计的解决方法,但我还没有看到一个如此糟糕的解决方法,以至于一个具有 volatile 类型的表达式会被优化掉。

然而,有一个极端情况,将 volatile 变量强制转换为 (void),其语义在 C++14 中显然发生了变化。

让我们举这个简单的例子

void x() 
    volatile int t;
    t=1;
    ((void)(t));

Arm gcc 7.2.1 invoked with -O3 -mcpu=cortex-m4 -mthumb -Wall -x c++ -std=c++11 会将其编译为

x():
  sub sp, sp, #8
  movs r3, #1
  str r3, [sp, #4]
  ldr r3, [sp, #4]
  add sp, sp, #8
  bx lr

而the same code compiled with -std=c++14

x():
  sub sp, sp, #8
  movs r3, #1
  str r3, [sp, #4]
  add sp, sp, #8
  bx lr

...和一个警告:

<source>: In function 'void x()':
<source>:5:13: warning: conversion to void will not access object of type 'volatile int'
     ((void)(t));
            ~^~

还要注意第二种情况下缺少的ldr 指令。用 C++14 写入后变量不被访问。

我最初的问题旨在找出解决方案,使底层 ST 驱动程序保持不变。一种可能的解决方案是使用直接寄存器访问而不通过库提供的方便宏。

我建议继续并避免使用该库,恕我直言,HAL 最好被视为示例或实施建议的集合。

*我找不到禁用它的方法。这并不意味着没有。

【讨论】:

干得好!当然,禁用它的(实用的)方法正是-std=c++11? OP 必须询问他是否比 HAL 和无警告构建更需要 C++14。我想如果你倾向于使用 HAL,它对你来说比 C++14 功能更有用。我同意 HAL 本身有很多要批评的地方,但是如果您使用它是为了利用依赖于它的更高级别中间件(USB 堆栈、文件系统、RTOS 等)的 STM32Cube 生态系统,那么它就是路径对许多人来说阻力最小。 感谢@Clifford 的详细解释。我想,在这个阶段,我将使用寄存器访问来启用您提到的时钟,并在其余工作中继续使用 STM32 HAL。谢谢。 @aep :我认为您错误地向我发表了评论 - 这不是我的答案。如果您发现 HAL 的 C++14 可比性存在一个问题,我敢打赌您会再次遇到它;使用某种混合方法不太可能避免这种情况。 @Clifford 是的。这是我的错。我应该把它写给 berendi。顺便说一句,我也认为我会遇到类似的情况。看看情况如何。 这不是 C++14 特性,而是 GCC 错误!我填了一个错误,gcc 不计算括号中的丢弃值表达式,而 [expr]/11.1 明确指定应计算表达式。【参考方案2】:

您可以将代码提交到自己的存储库以解决该问题,并且仍然使用 c++14 编译代码。

/* Workaround for the broken UNUSED macro */
#include "stm32f3xx_hal_def.h"
#undef UNUSED
#define UNUSED(x) ((void)((uint32_t)(x)))

这需要在包含任何 HAL 标头之前添加。对我来说,在模块启用宏之后(即 #define HAL_WWDG_MODULE_ENABLED 行)但在包含实际 HAL 标头之前放入 stm32f3xx_hal_conf.h 文件很方便。 我将所有来源更新为 #include "stm32f3xx_hal_conf.h",而不是单独的 HAL 标头。

这是有效的,因为基于@berendi 的出色研究,警告来自volatile 名称。通过首先将值转换为非易失性的值,C++14 标准中的新子句就被避开了。

【讨论】:

我相信这个宏对x的类型会更友好:#define UNUSED(x) ((void)(__typeof__(x))(x)) @Glenn 我看不出这是如何消除不稳定的限定条件的。 它没有。在我的宏中使用__typeof__ 仅尊重传递给UNUSED 的表达式x 的类型。您建议的宏强制转换为uint32_t。根据您的编译器标志(例如,对某些类型转换发出警告的标志),编译器可能会在此强制转换时阻塞。 @Glenn 如果您不删除 volatile 限定条件,您将始终收到 OP 中显示的编译器警告。所以你建议的宏不能解决问题。 我的理解是 C 风格转换为 void 删除了 volatile 限定。也许我偶然发现了UB领域?我们不需要使用const_cast 之类的东西。我在 C++14 的 STM HAL 中使用我在代码库中描述的宏没有问题。【参考方案3】:

正如@oliv 在回复@berendi 的回答时提到的,根本原因似乎是 GCC 中的一个错误,该错误已在更新的版本中得到修复。当我升级到“版本 9-2019-q4-major”工具链 (GCC 9.2) 后,警告就消失了。

另外,对于STM32G0系列,ST将UNUSED的定义改为:

#define UNUSED(X) (void)X      /* To avoid gcc/g++ warnings */

这会使早期版本的编译器的警告消失。

【讨论】:

以上是关于C++14 及以上版本的 stm32 hal 库警告的主要内容,如果未能解决你的问题,请参考以下文章

第十届蓝桥杯嵌入式国赛(STM32G4及HAL库)

第十届蓝桥杯嵌入式国赛(STM32G4及HAL库)

STM32的HAL库分析及使用

正点原子STM32(基于HAL库)0

STM32实现薄膜压力传感器数据采集(标准库和HAL库实现)

STM32实现薄膜压力传感器数据采集(标准库和HAL库实现)