2025-12-15 2:15 PM
I have a custom board with an STM32G051. The STM32 is a SPI receive only device. PA1 is SCLK, PA2 is MOSI, PB0 is nSS (which is hardware controlled). The beginnings of the project where created with CubeMX so the clocks should all be set correctly.
The SPI transactions are simple 12-bit data with an active low chip select. Note that the MISO signal is not connected on the board. The master device does not read any data from the STM32G051.
The SPI peripheral is setup as below:
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_SLAVE;
hspi1.Init.Direction = SPI_DIRECTION_2LINES_RXONLY;
hspi1.Init.DataSize = SPI_DATASIZE_12BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;
hspi1.Init.NSS = SPI_NSS_HARD_INPUT;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 7;
hspi1.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
hspi1.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;and
void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(spiHandle->Instance==SPI1)
{
/* USER CODE BEGIN SPI1_MspInit 0 */
/* USER CODE END SPI1_MspInit 0 */
/* SPI1 clock enable */
__HAL_RCC_SPI1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/**SPI1 GPIO Configuration
PA1 ------> SPI1_SCK
PA2 ------> SPI1_MOSI
*/
GPIO_InitStruct.Pin = SCLK_Pin|MOSI_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF0_SPI1;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = nCS_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF0_SPI1;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init( nCS_GPIO_Port, &GPIO_InitStruct);
/* SPI1 interrupt Init */
HAL_NVIC_SetPriority(SPI1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(SPI1_IRQn);
/* USER CODE BEGIN SPI1_MspInit 1 */
/* USER CODE END SPI1_MspInit 1 */
}
}
There is a lot of logic to handle all the many different ways SPI can be configured in the HAL_SPI_Receive() routine so I made a copy of it and stripped out sections that are not relevant to this project. Note that I did this step as part of debugging when I couldn't get the SPI peripheral to receive anything. My thought was to reduce the amount of code to review/debug.
//
// based on HAL_SPI_Receive() with a lot of changes that are specific to this project
//
HAL_StatusTypeDef nonHAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
uint32_t tickstart;
uint32_t statusReg;
register HAL_StatusTypeDef errorcode = HAL_OK;
if (hspi->State != HAL_SPI_STATE_READY)
{
errorcode = HAL_BUSY;
return( errorcode );
}
/* Process Locked */
__HAL_LOCK(hspi);
HAL_SPI_Abort( hspi );
/* Init tickstart for timeout management*/
tickstart = HAL_GetTick();
if ((pData == NULL) || (Size == 0U))
{
errorcode = HAL_ERROR;
Error_Handler();
}
__HAL_SPI_DISABLE(hspi);
/* Set the transaction information */
hspi->State = HAL_SPI_STATE_BUSY_RX;
hspi->ErrorCode = HAL_SPI_ERROR_NONE;
hspi->pRxBuffPtr = (uint8_t *)pData;
hspi->RxXferSize = Size;
hspi->RxXferCount = Size;
/*Init field not used in handle to zero */
hspi->pTxBuffPtr = (uint8_t *)NULL;
hspi->TxXferSize = 0U;
hspi->TxXferCount = 0U;
hspi->RxISR = NULL;
hspi->TxISR = NULL;
// Set FIFO for 8-bit length since it will never get all 16 bits
// with a 12-bit transaction
SET_BIT(hspi->Instance->CR2, SPI_RXFIFO_THRESHOLD);
/* Enable SPI peripheral */
__HAL_SPI_ENABLE(hspi);
/* Receive 1 piece of data in 12 Bit mode */
statusReg = hspi->Instance->SR & SPI_FLAG_RXNE;
while( statusReg == 0 )
{
hspi->Instance->DR = 0x7E;
// wait for SPI_FLAG_RXNE to be set
if( hspi->Instance->SR & SPI_FLAG_FRLVL )
{
// Wow, actually might have gotten something
asm("nop");
}
/* Timeout management */
if ((((HAL_GetTick() - tickstart) >= Timeout) && (Timeout != HAL_MAX_DELAY)) || (Timeout == 0U))
{
errorcode = HAL_TIMEOUT;
hspi->State = HAL_SPI_STATE_READY;
break;
}
statusReg = hspi->Instance->SR & SPI_FLAG_RXNE;
}
// To get here, Receiver Not Empty must be true or the transaction timed out
if( errorcode != HAL_TIMEOUT )
{
*((uint16_t *)hspi->pRxBuffPtr) = (uint16_t)hspi->Instance->DR;
hspi->pRxBuffPtr += sizeof(uint16_t);
hspi->RxXferCount--;
}
hspi->State = HAL_SPI_STATE_READY;
__HAL_UNLOCK(hspi);
return errorcode;
}
This is really a minimal project as the pin connections below show:
If anyone has suggestions (other than switching to a bit-bang solution) I would be happy to try it. I have tried using the SSM (software slave management) and the EXTI0 falling edge interrupt but that didn't work either.
I checked the errata sheet but didn't see anything listed about SPI1 not working in receive-only mode.
As a debug attempt I did, at one point, use the debugger to write a value to the DR register. At that point the TXE status bit went active and then was cleared by the hardware. That seems to indicate that the nSS signal is being seen but I don't remember if that was with the EXTI0 based SSM mode or with the hardware mode.
I did try to use the example projects of SPI_FullDuplex_ComPolling_Slave and SPI_FullDuplex_ComPolling_Master. I was able to get the Master version to run but the Slave version also did not receive any data; however, I did have to make a number of changes to get both versions to compile and run.
2025-12-15 3:03 PM
You haven't just pared down the HAL code, you've materially changed it by adding additional functions and changing how it does things. For example, the call to HAL_SPI_Abort and the write to DR.
I was able to load the SPI_FullDuplex_ComPolling_Master example from STM32CubeMX and compile it without any changes. I'd suggest starting from there and from using the unmodified HAL functions.
You mention the debugger. Reading DR in the debugger will clear the RXNE flag which can cause side effects in the code.
> The master device does not read any data from the STM32G051.
How exactly was this determined? Did code time out and return HAL_TIMEOUT? Or was the data not as expected? Were the CLK or NSS signals active?
RXONLY mode is generally not great. Clocks continue to be sent until the peripheral is disabled. Standard two-line mode is recommended. Leave the MOSI pin uninitialized if it's not needed. You can use the same buffer to send and receive.