简体   繁体   中英

STM32 SPI hardware and strict aliasing warnings

I've seen that the subject has been discussed in many other questions, but I can't quite find the answer for my particular case.

I am working with a STM32F0 microcontroller. The top of the SPI reception/transmit FIFO are accessible by a memory access. This particular microcontroller allows me to read/write 8bits or 16bits from the top of the FIFO. More precisely, when a LDRB/STRB instruction is executed, 8bits are popped/pushed from/to the FIFO and when a LDRH/STRH instruction is executed, 16 bits are popped/pushed from/to the FIFO.

The Hardware Abstraction Layer provided by STMicroelectronic proposes this syntax to read the 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

Where DR is a uint32_t* pointing on the top of the SPI FIFO

I've built my software using this syntax and it does work fine. The only problem, is that g++ throws a lot of warning about type punning. More precisely:

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;

After some readings it looks like using union is not a good idea in C++. I did try it anyway but got some problems. Actually accessing the memory through a pointer in a union makes my microcontroller crashes just like an unaligned memory access.

static_cast and reinterpret_cast throws the sames warning as the C-style cast

I cannot use memcpy with void* since my final goal is to make the compiler uses a LDRB/STRB and LDRH/STRH instruction.

Others proposed solutions that I found on Stack Overflow were dependent on the use-case.

Any suggestion?

I would suggest creating two specific pointers for the job. You can create them during initialisation or statically so you don't have to create them each time.

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

then simply read by:

uint8_t read_byte = *DR_Byte;
uint16_t read_word = *DR_Word;

and write by:

*DR_Byte = byte_to_write;
*DR_Word = word_to_write;

or something similar.

So it looks like there is a way to make GCC eat the type-punning without complaining. Just like Realtime Rik mentionned. I also succeeded in suppressing the warning doing

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

I took a step back and reconsidered what I was trying to do to finally decide to simply disable strict aliasing with fno-strict-aliasing

Why? From my understanding, strict aliasing is an optimisation, not a functionnal requirement. My software is designed to do type-punning so strict aliasing is simply an optimisation that I cannot afford. Or at least, I consider better to disable it instead of trying to trick the compiler in believing that I don't do type-punning while I actually do.

I use LL API by STM instead of HAL. Part of /STM32F0xx_LL_Driver/inc/stm32f0xx_ll_spi.h file bellow:

/**
  * @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;
}

There READ_REG is macros from /STM32F0xx_LL_Driver/inc/stm32f0xx.h file and is defined as:

#define READ_REG(REG)         ((REG))

And what about your problem, when you access to spi data register by this _handle->Instance->DR construction you already dereference pointer Instance and there DR is volatile uint32_t . So you just need to cast, and this should work:

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

Finally about unaligned access: I don't know how it may be guaranteed, but it is should be done for work with ARM microcontrollers. My automatically generated linker script have instructions . = ALIGN(4); . = ALIGN(4); in every section:

.rodata :
{
  . = ALIGN(4);
  *(.rodata)         /* .rodata sections (constants, strings, etc.) */
  *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
  . = ALIGN(4);
} >FLASH

I hope it will be helpful for you.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM