Reputation:
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?
Upvotes: 5
Views: 1409
Reputation: 1019
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);
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.
Upvotes: 1
Reputation:
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.
Upvotes: 1
Reputation: 1704
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.
Upvotes: 2