‎2019-05-15 06:46 AM
Hey guys,
I'm currently struggling to setup my SPI to work in Full Duplex Mode together with an DMA.
I'm using 2 STM32F4 to communicate via SPI + DMA
The Initialization for both MCUs was created using STM32Cube.
This is the generated Init Code for the master
/* SPI2 init function */
void MX_SPI2_Init(void)
{
hspi2.Instance = SPI2;
hspi2.Init.Mode = SPI_MODE_MASTER;
hspi2.Init.Direction = SPI_DIRECTION_2LINES;
hspi2.Init.DataSize = SPI_DATASIZE_16BIT;
hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi2.Init.NSS = SPI_NSS_SOFT;
hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi2.Init.TIMode = SPI_TIMODE_DISABLE;
hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_ENABLE;
hspi2.Init.CRCPolynomial = 11;
if (HAL_SPI_Init(&hspi2) != HAL_OK)
{
Error_Handler();
}
}
void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(spiHandle->Instance==SPI2)
{
/* USER CODE BEGIN SPI2_MspInit 0 */
/* USER CODE END SPI2_MspInit 0 */
/* SPI2 clock enable */
__HAL_RCC_SPI2_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/**SPI2 GPIO Configuration
PB13 ------> SPI2_SCK
PB14 ------> SPI2_MISO
PB15 ------> SPI2_MOSI
*/
GPIO_InitStruct.Pin = UCE_UCN_SCLK_Pin|UCE_UCN_MOSI_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Pin = UCE_UCN_MISO_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
HAL_GPIO_Init(UCE_UCN_MISO_GPIO_Port, &GPIO_InitStruct);
/* SPI2 DMA Init */
/* SPI2_RX Init */
hdma_spi2_rx.Instance = DMA1_Stream3;
hdma_spi2_rx.Init.Channel = DMA_CHANNEL_0;
hdma_spi2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_spi2_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_spi2_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_spi2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_spi2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_spi2_rx.Init.Mode = DMA_NORMAL;
hdma_spi2_rx.Init.Priority = DMA_PRIORITY_LOW;
hdma_spi2_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_spi2_rx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(spiHandle,hdmarx,hdma_spi2_rx);
/* SPI2_TX Init */
hdma_spi2_tx.Instance = DMA1_Stream4;
hdma_spi2_tx.Init.Channel = DMA_CHANNEL_0;
hdma_spi2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_spi2_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_spi2_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_spi2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_spi2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_spi2_tx.Init.Mode = DMA_NORMAL;
hdma_spi2_tx.Init.Priority = DMA_PRIORITY_LOW;
hdma_spi2_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_spi2_tx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(spiHandle,hdmatx,hdma_spi2_tx);
/* SPI2 interrupt Init */
HAL_NVIC_SetPriority(SPI2_IRQn, 6, 0);
HAL_NVIC_EnableIRQ(SPI2_IRQn);
/* USER CODE BEGIN SPI2_MspInit 1 */
/* USER CODE END SPI2_MspInit 1 */
}
}
There's no magic there =)
Now if i'm using
if(HAL_OK != HAL_SPI_TransmitReceive_DMA(&hspi2,(uint8_t*)(&dummy_tx_packet), (uint8_t*)(&dummy_rx_packet), sizeof(UCUCCom_Single_Packet_T)))
{
hal_errors++;
}
If I record the Clk using an oscilloscope I'm seeing a nice signal.
=> So there is no hardware problem
However I'm using a complex slave selection mechanism which is not supported by the HAL drivers. So I've to start the transmit and receive myself. The problem is that no data are transmitted.
The problem with my Code: There is no transmission.
The the master does not generate a Clk Signal.
The manual states (see attachment) states on page 951 in Chapter 29.3.11 Communication using DMA.
When starting communication using DMA, to prevent DMA channel management raising error events, these steps must be followed in order:
1.Enable DMA Rx buffer in the RXDMAEN bit in the SPI_CR2 register, if DMA Rx is used.
2. Enable DMA streams for Tx and Rx in DMA registers, if the streams are used.
3. Enable DMA Tx buffer in the TXDMAEN bit in the SPI_CR2 register, if DMA Tx is used.
4. Enable the SPI by setting the SPE bit
I believe i'm following this order.
static void Enable_SPI(const uint8_t enable, const SPI_HandleTypeDef* const pSPI)
{
if ((NULL == pSPI) || (NULL == pSPI->Instance))
{
return;
}
if ((NULL == pSPI->hdmarx) || (NULL == pSPI->hdmarx->Instance))
{
return;
}
if ((NULL == pSPI->hdmatx) || (NULL == pSPI->hdmatx->Instance))
{
return;
}
if (1 == enable)
{
//enable RX DMA Buffer
SET_BIT(pSPI->Instance->CR2, SPI_CR2_RXDMAEN);
//setup DMA RX stream
WRITE_REG(pSPI->hdmarx->Instance->NDTR, (sizeof(UCUCCom_Single_Packet_T)) / 2); // 16 bit Mode
WRITE_REG(pSPI->hdmarx->Instance->PAR, (uint32_t ) &(pSPI->Instance->DR)); // from SPI DR ...
WRITE_REG(pSPI->hdmarx->Instance->M0AR, (uint32_t ) &ucuccom_rx_buffer); // ... to RX Memory
//enable DMA RX Stream
SET_BIT(pSPI->hdmarx->Instance->CR, (DMA_SxCR_TCIE | // Transfer Complete Interrupt
DMA_SxCR_TEIE |// Transfer Error Interrupt
DMA_SxCR_EN));// Enable
//setup DMA TX Stream
WRITE_REG(pSPI->hdmatx->Instance->NDTR, (sizeof(UCUCCom_Single_Packet_T)) / 2); // 16 bit mode
WRITE_REG(pSPI->hdmatx->Instance->M0AR, (uint32_t ) pucuccom_tx_buffer); // from TX memory ...
WRITE_REG(pSPI->hdmatx->Instance->PAR, (uint32_t ) &(pSPI->Instance->DR)); // ... to SPI DR.
//enable DMA TX Stream
SET_BIT(pSPI->hdmatx->Instance->CR, ( DMA_SxCR_TEIE | // Transfer Error Interrupt
DMA_SxCR_EN));// Enable
//enable DMA TX Buffer
SET_BIT(pSPI->Instance->CR2, SPI_CR2_TXDMAEN);
//SPI enable
SET_BIT(pSPI->Instance->CR1, SPI_CR1_SPE);
}
else
{
//close DMA RX Stream
CLEAR_BIT(pSPI->hdmarx->Instance->CR, (DMA_SxCR_TCIE | // Transfer Complete Interrupt
DMA_SxCR_TEIE |// Transfer Error Interrupt
DMA_SxCR_EN));// Enable
//close DMA TX Stream
CLEAR_BIT(pSPI->hdmatx->Instance->CR, (DMA_SxCR_TEIE | // Transfer Error Interrupt
DMA_SxCR_EN));// Enable
//disable SPI
//Wait for transmission complete
//Timeout 1ms
const uint32_t ms_timeout = 1;
const uint32_t ticks_at_start = HAL_GetTick();
uint32_t current_ticks = 0;
uint32_t timediff = 0;
while (timediff <= ms_timeout)
{
current_ticks = HAL_GetTick();
timediff = current_ticks - ticks_at_start;
const uint32_t register_value = READ_BIT(pSPI->Instance->SR, (SPI_SR_RXNE | SPI_SR_TXE));
const uint32_t rxne = (SPI_SR_RXNE & register_value);
const uint32_t txe = (SPI_SR_TXE & register_value);
if ((SPI_SR_RXNE == rxne) && (SPI_SR_TXE == txe))
{
//no data left => disable SPI now
break;
}
//short break
osDelay(1);
}
/*
* according to the Reference Manual one should wait for BSY = 0
* however the STM32F4 errata Sheet mentions that this flag is unreliable
* therefore we ignore this flag
*/
//Read Data (there should be no data left to read => RX data was allready read in tome other code part)
READ_REG(pSPI->Instance->DR); // Read DR to clear flags
//disable DMA RX und TX Buffer
SET_BIT(pSPI->Instance->CR2, ( SPI_CR2_RXDMAEN |SPI_CR2_TXDMAEN));
SPI_RESET_CRC(pSPI); // reset CRC
CLEAR_BIT(pSPI->Instance->SR, SPI_SR_CRCERR); // clear Flag
CLEAR_BIT(pSPI->Instance->CR1, SPI_CR1_SPE); // SPI disable
}
}
this code is run in a seperate task which basically does nothing else than
void Some_Task()
{
for (;;)
{
// reset SPI
Enable_SPI(0, &hspi2);
//Clear old flags
xEventGroupClearBits(UCCOM_internalEventBits, UCCOM_ALL_ISR_EVENTS);
//Transfer on
Enable_SPI(1, &hspi2);
// wait for transfer complete
EventBits_t flagsAtReturn = xEventGroupWaitBits(UCCOM_internalEventBits,
UCCOM_EVENT_CRC_COMPLETE | UCCOM_EVENT_DMARX_ERROR | UCCOM_EVENT_DMATX_ERROR,
pdFALSE,
pdFALSE, WAIT_TIME); //3 ms Waittime
//SPI RXNEIE is set in RX DMA TC Interrupt
//do some stuff to use the RX Data....
// 7. Disable Transfer Hardware
Enable_SPI(0, &hspi2);
}
}
‎2019-05-15 08:21 AM
If using interrupt, use DMA RX interrupt for the master side. DMA TX will define the number of bytes transiting on the bus.
As for the slave, their DMA must be setup prior to communication (NSS falling edge EXTI ?) and better use DMA in cyclic mode instead of normal mode.
In this case, end of transmission is NSS rise edge (EXTI) for the slave.
As for the HAL, make sure to read all the header files for both SPI and DMA.
When SPI+DMA is used, all callbacks should come from SPI, none from DMA.
‎2019-05-15 09:01 AM
Read out and check content of relevant DMA and SPI registers.
JW
‎2019-05-16 06:37 AM
Hey guys,
thx for your answer.
After doing some debugging i found out that for some reason my task was faster than expected. So the SPI got diabled before the transmission/reception was complete.
So moving the SPI disable into the SPI interrupt solved my problem :):grinning_face:
thx for your help