I have a very intermittent issue, which happens only on one of two "identical" systems.
I am using DMA to feed SPI3 TX. Very simple!
This is the code
/*
*
* DMA-only version of HAL_SPI_TransmitReceive() but fixed for SPI3 and xx-only options added.
*
* For use where fast transfers are needed, on the limit of SPI3 speed so with zero gaps. This is impossible
* to do by polling at 10.5mbps or 21mbps and is probably marginal at 5.25mbps. The 16 bit SPI mode just
* manages gap-free with polling but works only with even block sizes, and has the "first byte problem" which
* DMA gets around.
*
* ** DMA ONLY SO NO CCM ACCESS SO THE TWO BUFFERS HAVE TO BE "STATIC" **
*
* This function is blocking so the caller can set CS=1 right away (check device data sheet!). A non-blocking
* version would make sense only if transmitting only (txonly=true) but the caller would have to tidy up
* the DMA and SPI3->CR2.
*
* spi3_set_mode() can still be used to set up the clock speed, phase, etc; this function does nothing
* to SPI3 other than to enable/disable DMA. It does however make sense only if SPI3 is a Master.
*
* Two modes, obviously mutually exclusive, for tx-only and rx-only:
* If txonly, dumps rx data so you don't need to allocate a buffer for it
* If rxonly, transmits all-0x00 so you don't need to feed SPI with some "known garbage"
*
* The rx-only mode is superfluous in most cases but it does avoid shifting non-0x00 data to the device
* while we are reading data out of it. With some devices this can matter. The ADS1118 ADC is one such.
*
* Because this function needs to work with KDE_spi3_set_mode(), care is taken to not modify the SPI config.
*
* NULL pointers are allowed if using the txonly or rxonly modes; then the unused one can be NULL.
*
*
*/
bool SPI3_DMA_TransmitReceive(uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, bool txonly, bool rxonly, bool yield)
{
// Check for invalid inputs
if ( Size==0 ) return (false);
if ( (pTxData==NULL) && !rxonly ) return (false);
if ( (pRxData==NULL) && !txonly ) return (false);
uint32_t txadd = (uint32_t) pTxData;
uint32_t rxadd = (uint32_t) pRxData;
static uint8_t txonly_target=0; // must not be in CCM
static uint8_t rxonly_source=0; // must not be in CCM
// DMA ch 1 clock enable already done in b_main.c
//RCC->AHB1ENR |= (1u << 21); // DMA1EN=1 - DMA1 clock enable
//hang_around_us(1); // give it a chance to wake up
// DMA1 Ch 0 Stream 0 is SPI3 RX
DMA1_Stream0->CR = 0; // disable DMA so all regs can be written
DMA1->LIFCR = (0x03d << 0); // clear int flags & transfer complete - 111101 stream 0
DMA1_Stream0->NDTR = Size;
if (txonly)
{
DMA1_Stream0->M0AR = (uint32_t) &txonly_target; // memory address to dump rx data to
}
else
{
DMA1_Stream0->M0AR = rxadd; // memory address in normal mode
}
DMA1_Stream0->PAR = (uint32_t) &(SPI3->DR); // peripheral address
DMA1_Stream0->FCR = 0; // direct mode
if (txonly)
{
DMA1_Stream0->CR = 0 << 25 // CHSEL: ch 0
| 0 << 23 // MBURST: memory burst - single transfer
| 0 << 21 // PBURST: peripheral burst - single transfer
| 3 << 16 // PL: highest priority
| 0 << 15 // PINCOS: no peripheral address increment offset
| 0 << 13 // MSIZE: memory data size: byte
| 0 << 11 // PSIZE: peripheral data size: byte
| 0 << 10 // MINC: memory address increment: 0
| 0 << 9 // PINC: peripheral address increment: 0
| 0 << 8 // CIRC: no circular mode
| 0 << 6 // DIR: peripheral to memory
| 0 << 5 // PFCTRL: DMA is flow controller
| 1 << 0; // EN: enable stream
}
else
{
DMA1_Stream0->CR = 0 << 25 // CHSEL: ch 0
| 0 << 23 // MBURST: memory burst - single transfer
| 0 << 21 // PBURST: peripheral burst - single transfer
| 3 << 16 // PL: highest priority
| 0 << 15 // PINCOS: no peripheral address increment offset
| 0 << 13 // MSIZE: memory data size: byte
| 0 << 11 // PSIZE: peripheral data size: byte
| 1 << 10 // MINC: memory address increment: 1
| 0 << 9 // PINC: peripheral address increment: 0
| 0 << 8 // CIRC: no circular mode
| 0 << 6 // DIR: peripheral to memory
| 0 << 5 // PFCTRL: DMA is flow controller
| 1 << 0; // EN: enable stream
}
// DMA1 Ch 0 Stream 7 is SPI3 TX
DMA1_Stream7->CR = 0; // disable DMA so all regs can be written
DMA1->HIFCR = (0x03d << 22); // clear int flags & transfer complete - 111101 stream 7
DMA1_Stream7->NDTR = Size;
if (rxonly)
{
DMA1_Stream7->M0AR = (uint32_t) &rxonly_source; // memory address to fetch dummy tx data from
}
else
{
DMA1_Stream7->M0AR = txadd; // memory address in normal mode
}
DMA1_Stream7->PAR = (uint32_t) &(SPI3->DR); // peripheral address
DMA1_Stream7->FCR = 0; // direct mode
if (rxonly)
{
DMA1_Stream7->CR = 0 << 25 // CHSEL: ch 0
| 0 << 23 // MBURST: memory burst - single transfer
| 0 << 21 // PBURST: peripheral burst - single transfer
| 0 << 16 // PL: priority low
| 0 << 15 // PINCOS: no peripheral address increment offset
| 0 << 13 // MSIZE: memory data size: byte
| 0 << 11 // PSIZE: peripheral data size: byte
| 0 << 10 // MINC: memory address increment: 0
| 0 << 9 // PINC: peripheral address increment: 0
| 0 << 8 // CIRC: no circular mode
| 1 << 6 // DIR: memory to peripheral
| 0 << 5 // PFCTRL: DMA is flow controller
| 1 << 0; // EN: enable stream
}
else
{
DMA1_Stream7->CR = 0 << 25 // CHSEL: ch 0
| 0 << 23 // MBURST: memory burst - single transfer
| 0 << 21 // PBURST: peripheral burst - single transfer
| 0 << 16 // PL: priority low
| 0 << 15 // PINCOS: no peripheral address increment offset
| 0 << 13 // MSIZE: memory data size: byte
| 0 << 11 // PSIZE: peripheral data size: byte
| 1 << 10 // MINC: memory address increment: 1
| 0 << 9 // PINC: peripheral address increment: 0
| 0 << 8 // CIRC: no circular mode
| 1 << 6 // DIR: memory to peripheral
| 0 << 5 // PFCTRL: DMA is flow controller
| 1 << 0; // EN: enable stream
}
// Config SPI3 to let DMA handle the data. These need to be cleared when transfer complete!
// This starts the transfer
SPI3->CR2 |= 3; // TXDMAEN, RXDMAEN: 11 - both set in one go
SPI3->CR1 |= (1<<6); // SPE=1 enable SPI
// Wait for DMA to finish. Blocking is necessary to prevent device CS=1 too early.
// There could be a timeout here but a failure is impossible short of duff silicon, because
// we are a Master and generating the SPI clock.
while(true)
{
// Either end-transfer detection method below works but the NDTR one may be more reliable
#if 1
uint16_t temp1;
temp1 = DMA1_Stream0->NDTR;
if ( temp1 == 0 ) break; // transfer count = 0
#else
uint32_t temp2;
temp2 = DMA1->LISR;
if ( (temp2 & (1<<5)) !=0 ) break; // TCIF0
#endif
}
SPI3->CR2 &= ~3; // TXDMAEN, RXDMAEN: 00 - both cleared in one go
// Clear int pending flags. They get cleared at the top of this function anyway, but...
DMA1->LIFCR = (0x03d << 0); // clear int flags & transfer complete - 111101 stream 0
DMA1->HIFCR = (0x03d << 22); // clear int flags & transfer complete - 111101 stream 7
// Clear any rx data and the overrun flag in case not all received data was read
SPI3->CR1 &= ~(1<<6); // SPE=0 disable SPI
//hang_around_us(1);
SPI3->DR;
SPI3->DR;
SPI3->SR;
return (true);
}
When it hangs, it hangs in that forever loop near the end. Breaking the code there shows NDTR=6; init value was 7 so it looks like it moved 1 byte. In
SPI3->CR2 |= 3; // TXDMAEN, RXDMAEN: 11 - both set in one go
SPI3->CR1 |= (1<<6); // SPE=1 enable SPI
the relevant bits are correctly set ie both DMA and SPI are enabled.
rxonly=false (txonly mode is used)
CR=10001000000
CR2=3
CR1=1111101100
NDTR=6
Notably, CR has bit 0 = 0 so
1 << 0; // EN: enable stream
has got cleared, but how? What else can clear DMA1_Stream7->CR bit 0???
SPI3 access is mutexed. DMA access isn't but nothing else should be using this DMA channel. Is bit 0 of CR somehow shared between multiple DMA "processes"?
This code has been running for many months, 24/7, solidly. What has changed? Well, I added an RTOS task which reads an ADS1118 ADC. This could use the above DMA like everything else does (for SPI3) but instead it uses the HAL_SPI_TransmitReceive() function, because the ADS1118 uses the SPI in 16 bit mode, and I didn't want to re-hack the DMA code to do that. The ADS1118 code was written and very carefully verified 2 years ago.
I am tempted to replace HAL_SPI_TransmitReceive() with a 16 bit DMA version. I also tried to use the above bytewide DMA code but for some reason it didn't work (I didn't forget the fact that in 16 bit mode, SPI transmits the MS byte first).
But the basic thing is CR bit 0 getting cleared, before NDTR has reached 0. How is that possible? Master SPI cannot just be stopped so DMA requests from it cannot just stop.
The calling code of that DMA function is a bit of code which feeds a 6 digit LED display controller, hence the 7 bytes getting transmitted. This has been working for years too. It runs at 10Hz. Every few minutes or hours, it gets stuck.
FWIW, this is the SPI3 function the ADS1118 uses. It is bloated with loads of junk. We had a thread on this before... it deals with the special condition of transfer count = 1. But I don't think this is the problem.
/**
* @brief Transmit and Receive an amount of data in blocking mode.
* @param hspi pointer to a SPI_HandleTypeDef structure that contains
* the configuration information for SPI module.
* @param pTxData pointer to transmission data buffer
* @param pRxData pointer to reception data buffer
* @param Size amount of data to be sent and received
* @param Timeout Timeout duration
* @retval HAL status
*/
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size,
uint32_t Timeout)
{
uint16_t initial_TxXferCount;
uint32_t tmp_mode;
HAL_SPI_StateTypeDef tmp_state;
uint32_t tickstart;
/* Variable used to alternate Rx and Tx during transfer */
uint32_t txallowed = 1U;
HAL_StatusTypeDef errorcode = HAL_OK;
/* Check Direction parameter */
assert_param(IS_SPI_DIRECTION_2LINES(hspi->Init.Direction));
/* Process Locked */
__HAL_LOCK(hspi);
/* Init tickstart for timeout management*/
tickstart = HAL_GetTick();
/* Init temporary variables */
tmp_state = hspi->State;
tmp_mode = hspi->Init.Mode;
initial_TxXferCount = Size;
if (!((tmp_state == HAL_SPI_STATE_READY) || \
((tmp_mode == SPI_MODE_MASTER) && (hspi->Init.Direction == SPI_DIRECTION_2LINES) && (tmp_state == HAL_SPI_STATE_BUSY_RX))))
{
errorcode = HAL_BUSY;
goto error;
}
if ((pTxData == NULL) || (pRxData == NULL) || (Size == 0U))
{
errorcode = HAL_ERROR;
goto error;
}
/* Don't overwrite in case of HAL_SPI_STATE_BUSY_RX */
if (hspi->State != HAL_SPI_STATE_BUSY_RX)
{
hspi->State = HAL_SPI_STATE_BUSY_TX_RX;
}
/* Set the transaction information */
hspi->ErrorCode = HAL_SPI_ERROR_NONE;
hspi->pRxBuffPtr = (uint8_t *)pRxData;
hspi->RxXferCount = Size;
hspi->RxXferSize = Size;
hspi->pTxBuffPtr = (uint8_t *)pTxData;
hspi->TxXferCount = Size;
hspi->TxXferSize = Size;
/*Init field not used in handle to zero */
hspi->RxISR = NULL;
hspi->TxISR = NULL;
#if (USE_SPI_CRC != 0U)
/* Reset CRC Calculation */
if (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE)
{
SPI_RESET_CRC(hspi);
}
#endif /* USE_SPI_CRC */
/* Check if the SPI is already enabled */
if ((hspi->Instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE)
{
/* Enable SPI peripheral */
__HAL_SPI_ENABLE(hspi);
}
/* Transmit and Receive data in 16 Bit mode */
if (hspi->Init.DataSize == SPI_DATASIZE_16BIT)
{
if ((hspi->Init.Mode == SPI_MODE_SLAVE) || (initial_TxXferCount == 0x01U))
{
hspi->Instance->DR = *((uint16_t *)hspi->pTxBuffPtr);
hspi->pTxBuffPtr += sizeof(uint16_t);
hspi->TxXferCount--;
}
while ((hspi->TxXferCount > 0U) || (hspi->RxXferCount > 0U))
{
/* Check TXE flag */
if ((__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXE)) && (hspi->TxXferCount > 0U) && (txallowed == 1U))
{
hspi->Instance->DR = *((uint16_t *)hspi->pTxBuffPtr);
hspi->pTxBuffPtr += sizeof(uint16_t);
hspi->TxXferCount--;
/* Next Data is a reception (Rx). Tx not allowed */
txallowed = 0U;
#if (USE_SPI_CRC != 0U)
/* Enable CRC Transmission */
if ((hspi->TxXferCount == 0U) && (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE))
{
SET_BIT(hspi->Instance->CR1, SPI_CR1_CRCNEXT);
}
#endif /* USE_SPI_CRC */
}
/* Check RXNE flag */
if ((__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_RXNE)) && (hspi->RxXferCount > 0U))
{
*((uint16_t *)hspi->pRxBuffPtr) = (uint16_t)hspi->Instance->DR;
hspi->pRxBuffPtr += sizeof(uint16_t);
hspi->RxXferCount--;
/* Next Data is a Transmission (Tx). Tx is allowed */
txallowed = 1U;
}
if (((HAL_GetTick() - tickstart) >= Timeout) && (Timeout != HAL_MAX_DELAY))
{
errorcode = HAL_TIMEOUT;
goto error;
}
}
}
/* Transmit and Receive data in 8 Bit mode */
else
{
if ((hspi->Init.Mode == SPI_MODE_SLAVE) || (initial_TxXferCount == 0x01U))
{
*((__IO uint8_t *)&hspi->Instance->DR) = (*hspi->pTxBuffPtr);
hspi->pTxBuffPtr += sizeof(uint8_t);
hspi->TxXferCount--;
}
while ((hspi->TxXferCount > 0U) || (hspi->RxXferCount > 0U))
{
/* Check TXE flag */
if ((__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXE)) && (hspi->TxXferCount > 0U) && (txallowed == 1U))
{
*(__IO uint8_t *)&hspi->Instance->DR = (*hspi->pTxBuffPtr);
hspi->pTxBuffPtr++;
hspi->TxXferCount--;
/* Next Data is a reception (Rx). Tx not allowed */
txallowed = 0U;
#if (USE_SPI_CRC != 0U)
/* Enable CRC Transmission */
if ((hspi->TxXferCount == 0U) && (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE))
{
SET_BIT(hspi->Instance->CR1, SPI_CR1_CRCNEXT);
}
#endif /* USE_SPI_CRC */
}
/* Wait until RXNE flag is reset */
if ((__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_RXNE)) && (hspi->RxXferCount > 0U))
{
(*(uint8_t *)hspi->pRxBuffPtr) = hspi->Instance->DR;
hspi->pRxBuffPtr++;
hspi->RxXferCount--;
/* Next Data is a Transmission (Tx). Tx is allowed */
txallowed = 1U;
}
if ((((HAL_GetTick() - tickstart) >= Timeout) && ((Timeout != HAL_MAX_DELAY))) || (Timeout == 0U))
{
errorcode = HAL_TIMEOUT;
goto error;
}
}
}
#if (USE_SPI_CRC != 0U)
/* Read CRC from DR to close CRC calculation process */
if (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE)
{
/* Wait until TXE flag */
if (SPI_WaitFlagStateUntilTimeout(hspi, SPI_FLAG_RXNE, SET, Timeout, tickstart) != HAL_OK)
{
/* Error on the CRC reception */
SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_CRC);
errorcode = HAL_TIMEOUT;
goto error;
}
/* Read CRC */
READ_REG(hspi->Instance->DR);
}
/* Check if CRC error occurred */
if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_CRCERR))
{
SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_CRC);
/* Clear CRC Flag */
__HAL_SPI_CLEAR_CRCERRFLAG(hspi);
errorcode = HAL_ERROR;
}
#endif /* USE_SPI_CRC */
/* Check the end of the transaction */
if (SPI_EndRxTxTransaction(hspi, Timeout, tickstart) != HAL_OK)
{
errorcode = HAL_ERROR;
hspi->ErrorCode = HAL_SPI_ERROR_FLAG;
goto error;
}
/* Clear overrun flag in 2 Lines communication mode because received is not read */
if (hspi->Init.Direction == SPI_DIRECTION_2LINES)
{
__HAL_SPI_CLEAR_OVRFLAG(hspi);
}
error :
hspi->State = HAL_SPI_STATE_READY;
__HAL_UNLOCK(hspi);
return errorcode;
}