STM32 SPI 硬件和严格别名警告

5
我看到这个主题在许多其他问题中已经被讨论,但我无法找到适用于我的特定情况的答案。
我正在使用STM32F0微控制器。 SPI接收/传输FIFO的顶部可通过内存访问进行访问。 这种特殊的微控制器允许我从FIFO的顶部读取/写入8位或16位。 更准确地说,当执行LDRB / STRB指令时,将从FIFO弹出/推送8位,当执行LDRH / STRH指令时,将从FIFO弹出/推送16位。
STMicroelectronic提供的硬件抽象层提供了这种语法来读取SPI FIFO。
return *(volatile uint8_t*)&_handle->Instance->DR; // Pop 1 byte
return *(volatile uint16_t*)&_handle->Instance->DR; // Pop 2 byte

*(volatile uint8_t*)&_handle->Instance->DR = val; // Push 1 byte
*(volatile uint16_t*)&_handle->Instance->DR = val; // Push 2 bytes

在这里,DR 是一个指向 SPI FIFO 顶部的 uint32_t*

我使用这种语法构建我的软件,它运行良好。唯一的问题是,g++ 抛出了许多有关类型转换的警告。更具体地说:

Inc/drivers/SPI.h:70:50: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing] return *(volatile uint16_t*)&_handle->Instance->DR;

经过一些阅读,似乎在 C++ 中使用联合并不是一个好主意。我仍然尝试了一下,但遇到了一些问题。实际上,在联合中通过指针访问内存会导致我的微控制器崩溃,就像未对齐的内存访问一样。

static_castreinterpret_cast 抛出与 C 风格转换相同的警告

由于我的最终目标是使编译器使用 LDRB/STRB 和 LDRH/STRH 指令,因此我无法使用带有 void*memcpy

我在 Stack Overflow 上找到的其他解决方案都取决于用例。

有什么建议吗?

3个回答

2

我建议为这项工作创建两个特定的指针。您可以在初始化期间或静态地创建它们,以便无需每次都创建它们。

static uint8_t * const DR_Byte = (uint8_t * const)&_handle->Instance->DR;
static uint16_t * const DR_Word = (uint16_t * const)&_handle->Instance->DR;

那么只需这样读取:
uint8_t read_byte = *DR_Byte;
uint16_t read_word = *DR_Word;

并编写者:

*DR_Byte = byte_to_write;
*DR_Word = word_to_write;

或类似的内容。

1

看起来有一种方法可以让GCC吃掉类型转换而不会抱怨。就像Realtime Rik提到的那样。我也成功地通过以下方式抑制了警告:

void* p = &_handle->Instance->DR;
(uint8_t*) p = val;

我退一步,重新考虑我试图做的事情,最终决定简单地使用fno-strict-aliasing禁用严格别名。为什么?据我所知,严格别名是一种优化而不是功能要求。我的软件旨在进行类型转换,因此严格别名只是我无法承受的一种优化。或者至少,我认为禁用它比尝试欺骗编译器相信我不进行类型转换更好。

我不确定我会相信上面的代码片段。我建议查看汇编代码以确保它没问题。另外,关闭警告意味着你可能会错过其他问题。我认为我的解决方案不是“压制”警告,而是以正确且可移植定义的方式工作。个人认为最好避免使用空指针。 - Realtime Rik

1

我使用STM的 LL API而不是HAL。下面是/STM32F0xx_LL_Driver/inc/stm32f0xx_ll_spi.h文件的一部分:

/**
  * @brief  Read 8-Bits in the data register
  * @rmtoll DR           DR            LL_SPI_ReceiveData8
  * @param  SPIx SPI Instance
  * @retval RxData Value between Min_Data=0x00 and Max_Data=0xFF
  */
__STATIC_INLINE uint8_t LL_SPI_ReceiveData8(SPI_TypeDef *SPIx)
{
  return (uint8_t)(READ_REG(SPIx->DR));
}

/**
  * @brief  Read 16-Bits in the data register
  * @rmtoll DR           DR            LL_SPI_ReceiveData16
  * @param  SPIx SPI Instance
  * @retval RxData Value between Min_Data=0x00 and Max_Data=0xFFFF
  */
__STATIC_INLINE uint16_t LL_SPI_ReceiveData16(SPI_TypeDef *SPIx)
{
  return (uint16_t)(READ_REG(SPIx->DR));
}

/**
  * @brief  Write 8-Bits in the data register
  * @rmtoll DR           DR            LL_SPI_TransmitData8
  * @param  SPIx SPI Instance
  * @param  TxData Value between Min_Data=0x00 and Max_Data=0xFF
  * @retval None
  */
__STATIC_INLINE void LL_SPI_TransmitData8(SPI_TypeDef *SPIx, uint8_t TxData)
{
  *((__IO uint8_t *)&SPIx->DR) = TxData;
}

/**
  * @brief  Write 16-Bits in the data register
  * @rmtoll DR           DR            LL_SPI_TransmitData16
  * @param  SPIx SPI Instance
  * @param  TxData Value between Min_Data=0x00 and Max_Data=0xFFFF
  * @retval None
  */
__STATIC_INLINE void LL_SPI_TransmitData16(SPI_TypeDef *SPIx, uint16_t TxData)
{
  *((__IO uint16_t *)&SPIx->DR) = TxData;
}

READ_REG 是来自于 /STM32F0xx_LL_Driver/inc/stm32f0xx.h 文件中的宏定义,其定义如下:

#define READ_REG(REG)         ((REG))

那么对于你的问题,当你通过这个_handle->Instance->DR结构访问spi数据寄存器时,你已经解除了指针Instance的引用,而在那里DRvolatile uint32_t。所以你只需要进行强制转换,这样应该就可以工作了:

return (uint8_t)_handle->Instance->DR;
return (uint16_t)_handle->Instance->DR;

最后谈到不对齐访问:我不知道如何保证它,但它应该用于与ARM微控制器的工作。我的自动生成的链接脚本在每个部分中都有指令. = ALIGN(4);
.rodata :
{
  . = ALIGN(4);
  *(.rodata)         /* .rodata sections (constants, strings, etc.) */
  *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
  . = ALIGN(4);
} >FLASH

我希望这对你有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接