I am looking for a bit of help with SPI and DMA on an STM32F103C8 board.
I have it working as an SPI slave using interrupts and am have trouble getting DMA to work. I'm aiming to get full duplex 16 bit transactions, with a DMA interrupt being generated on the reception of data. My thoughts behind this are if I populate the tx buffer and enable DMA for both tx and rx, then when the slave select is pulled low (configured as hardware slave) rx data will be received as the master clocks out tx data. When it is done, the rx interrupt will occur, I can check flags etc for tx in the DMA interrupt and re enable DMA to repeat.
I've managed to get DMA and SPI working to receive data, but I just can't get it to transmit anything. I'm watching on a logic analyser but all is quiet on the SDO front. I've tried crying, swearing and beer, but none seem to work. I am fairly convinced my problem is in my DMA setup. Can anyone please take a look at my code to see if I'm on the right track?
Setup and enable:
// Setup Code
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
volatile uint16_t dummyRX;
volatile uint16_t dummyTX;
// Host SPI Interface
RCC_APB2PeriphClockCmd(SPI_GPIO_CLK | SPI_CLK, ENABLE); // Enable the clock for SPI1
// Configure GPIO for SPI slave: SCK, MOSI, SS as inputs
GPIO_InitStructure.GPIO_Pin = MOSI_PIN | CS_PIN | SCK_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // Configure SCK and MOSI pins as Input Floating
GPIO_Init(SCK_PORT, &GPIO_InitStructure);
// Configure GPIO for SPI slave: MISO as AF output
GPIO_InitStructure.GPIO_Pin = MISO_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(MISO_PORT, &GPIO_InitStructure);
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // CHECK! pg682 - Should be full duplex as we need to xmit all F's for 485 drivers when recieving
SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; // SCK idle high
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; // second transition -> SCK Idle high => capture on rising edge of clock
SPI_InitStructure.SPI_NSS = SPI_NSS_Hard;
//SPI_BaudRatePrescaler
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
//SPI_CRCPolynomial
SPI_Init(SPI1, &SPI_InitStructure);
//Enable DMA1 channel IRQ Channel
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_DeInit(DMA1_Channel2);
DMA_DeInit(DMA1_Channel3);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SPI_DR_Base;
DMA_InitStructure.DMA_BufferSize = 1;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&dummyRX;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
DMA_Init(DMA1_Channel2, &DMA_InitStructure);
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&dummyTX;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
DMA_Init(DMA1_Channel3, &DMA_InitStructure);
dummyTX = 0xAFFA; // Some data to send
dummyRX = 0x0000;
// Start the devices
DMA_ITConfig(DMA1_Channel2, DMA_IT_TC, ENABLE); // Enable DMA1 Channel Transfer Complete interrupt
SPI_Cmd(SPI1, ENABLE); // Enable SPI device
SPI_CalculateCRC(SPI1, DISABLE);
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx | SPI_I2S_DMAReq_Tx, ENABLE);
DMA_Cmd(DMA1_Channel3, ENABLE);
DMA_Cmd(DMA1_Channel2, ENABLE); // Enable DMA channels
DMA rx (channel 2) interrupt
//Test on DMA1 Channel1 Transfer Complete interrupt
if(DMA_GetITStatus(DMA1_IT_TC2)) {
//Clear DMA1 Channel1 Half Transfer, Transfer Complete and Global interrupt pending bits
DMA_ClearITPendingBit(DMA1_IT_GL2);
LED_1_PORT->ODR ^= LED_1_PIN; // Toggle test LED
while (DMA_GetFlagStatus(DMA1_FLAG_TC3) == RESET) {}
// wait for tx to complete - page 692
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) {}
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET) {}
DMA_ClearFlag(DMA1_FLAG_GL3); // Clear the global flag
spi_buffer_rx = dummyRX;
// Disable DMA
DMA_ITConfig(DMA1_Channel2, DMA_IT_TC, DISABLE);
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx | SPI_I2S_DMAReq_Tx, DISABLE);
DMA_Cmd(DMA1_Channel2, DISABLE);
DMA_Cmd(DMA1_Channel3, DISABLE);
while (DMA1_Channel2->CCR & DMA_CCR2_EN); // wait until DMA is actually off
while (DMA1_Channel3->CCR & DMA_CCR3_EN);
DMA_SetCurrDataCounter(DMA1_Channel2, 1); // Set the number of transfers
DMA_SetCurrDataCounter(DMA1_Channel3, 1);
DMA_ClearITPendingBit(DMA1_IT_GL2); // clear again
DMA_ClearFlag(DMA1_FLAG_GL3); // Clear the global flag
// Start DMA controller again
DMA_ITConfig(DMA1_Channel2, DMA_IT_TC, ENABLE);
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx | SPI_I2S_DMAReq_Tx, ENABLE);
DMA_Cmd(DMA1_Channel2, ENABLE);
DMA_Cmd(DMA1_Channel3, ENABLE);
}
Best Answer
Below a code that works for me. DMA channel assignment is critical. DMA1 channel 4 for SPI2 RX request and DMA1 channel 5 for SPI2 TX request. See here, page 147: