cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F103 - DMA with SPI, Rx works Tx doesn't

de_koepi
Associate II
Posted on May 03, 2014 at 20:01

 

 

The original post was too long to process during our migration. Please click on the attachment to read the original post.
9 REPLIES 9
de_koepi
Associate II
Posted on May 05, 2014 at 19:20

Really, nobody has a clue?

This behaviour is really strange, because for the Rx to happen the Tx must work. Still, the data I write in the first loop doesn't end up in the SPI RAM. But data must be clocked in via Tx to retrieve the bytes, and I see data I wrote via standard polling to SPI before then.

I tried a lot already but run out of ideas. Using spixbase or SPI1->DR doesn't make a difference; for both Tx and Rx or only the Tx DMA setup I end up with the same result.

Enabling mem2mem bit makes the transfer not working at all (the idea was that this is the DMA software trigger which I need additionally to DMA_ENABLE according to the reference manual.) 

Also switching the spi_dma_tx() routine commands to enable spi_dma, then dma, then a while loop on SPI_BSY flag, then disabling spi_dma and dma doesn't deliver a good result, although the data shown changes to something like 0 0 2 0 0 1 0 0 0 ... randomly. This is also contradicting the idea to use the CPU while the data transfer takes place.

I really want to manage using one of my STM32F103 dev boards with this project, although I already have a STM32F0-Disco and receive a STM32F407-Disco tomorrow.
Posted on May 06, 2014 at 01:49

Really, nobody has a clue?

Forums can be like that, it's not as if you're a huge contributor yourself.

You are mixing polled/dma, not sure how well that will work. Pins look set up Ok as best I can tell. You could check as GPIO.

Not sure how effective a disable/enable of DMA is for resetting address/count

You use BRR BSRR in RMW fashion, really you don't need to do that, a simple write of the bits you're interested in is sufficient.
Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
de_koepi
Associate II
Posted on May 06, 2014 at 07:49

Thank you for the input, Clive.

I didn't mean to sound rude and impolite, I'm sorry. I thought it's a non-offending, non-swearing standard term in english (I'm no native speaker) which means ''Nobody has an idea?'' in more vivid language. Until now I was lurking in this forum, which was considered good style back in the last century on forums. I am active mostly in german forums as it is easier to help out in your native tongue and just started to post here (as I started looking at µCs only 8 weeks ago, first AVR, now Cortex/STM32, and struggle myself with STM32 I currently am not confident enough to be of help myself).

I will rewrite the routines to use different buffer sizes and only use DMA transfer without polling, though I'm not sure this makes a difference - the polled Rx command with DMA after that and writing via polling still works. But maybe that is just a random effect?

I also tried disabling/enabling SPI_I2S_DMA around the DMA enable/disable, that didn't change the result. I will try to write directly to the DMA registers to reset the address/counter. To be honest, I didn't reset the address until now at all; the results in the Rx buffer were pointing out that this seems unnecessary. But trying that is a good idea.

I read that RMW is of course slower and not necessary, but I left it that way because I didn't consider the chip-select/deselect time critical and on other architectures it is necessary, so in a sense of readability it looked like the way to go. Will change that.

Thank you again for giving some pointers where to go on!
de_koepi
Associate II
Posted on May 08, 2014 at 19:47

Ok, tried the suggested changes to completely change to DMA without any polling.

uart_tx(
''Starting program.
''
);
// Check the RAM first
for
(
int
bank=0; bank<5; bank++) {
bool
is_ok=
true
;
chip_select(bank);
// Send WRITE MODE register command to chip
buffer1_tx[0]=WRMR;
buffer1_tx[1]=SEQUENTIAL_MODE;
spi1_dma_tx(2);
chip_deselect();
chip_select(bank);
// Send WRITE command to chip
buffer1_tx[0]=WRITE;
// as default sequential mode, thus address has to be sent only at start
buffer1_tx[1]=0; 
//send even MoreMSByte address first
buffer1_tx[2]=0; 
//send MSByte address first
buffer1_tx[3]=0; 
//send LSByte address
spi1_dma_tx(4);
for
(
int
i=0; i<buffer_size; i++) {
buffer1_tx[i]=i;
buffer1_rx[i]=0;
}
for
(
int
i=0; i < (ram_size/buffer_size); i++) {
spi1_dma_tx(buffer_size);
}
chip_deselect();
chip_select(bank);
// Send WRITE MODE register command to chip
buffer1_tx[0]=WRMR;
buffer1_tx[1]=SEQUENTIAL_MODE;
spi1_dma_tx(2);
chip_deselect();
chip_select(bank);
// Send READ command to chip
buffer1_tx[0]=READ;
// as default sequential mode, thus address has to be sent only at start
buffer1_tx[1]=0; 
//send even MoreMSByte address first
buffer1_tx[2]=0; 
//send MSByte address first
buffer1_tx[3]=0; 
//send LSByte address
spi1_dma_tx(4);
for
(
int
i=0; i<buffer_size; i++) {
buffer1_tx[i]=0xFF;
buffer1_rx[i]=0;
}
for
(
int
i=0; i < (ram_size/buffer_size); i++) {
spi1_dma_tx(buffer_size);
for
(
int
i=0; i<buffer_size; i++) {
if
(buffer1_rx[i] != i) is_ok=
false
;
}
}
sprintf(text,
''RAM chip %i is %s -> ''
, bank, (is_ok? 
''OK.''
: 
''not OK!''
));
uart_tx(text);
for
(
int
i=0; i<10; i++) {
sprintf(text, 
''[%i] %i ''
, i, buffer1_rx[i]);
uart_tx(text);
}
uart_tx(
''
''
);
chip_deselect();
}

and

void
spi1_dma_tx(
int
size) {
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx | SPI_I2S_DMAReq_Tx, DISABLE);
DMA_Cmd(DMA_Channel_SPI1_TX, DISABLE);
DMA_Cmd(DMA_Channel_SPI1_RX, DISABLE);
DMA_SetCurrDataCounter(DMA_Channel_SPI1_RX, size);
DMA_SetCurrDataCounter(DMA_Channel_SPI1_TX, size);
DMA_Channel_SPI1_TX->CMAR=(uint32_t)buffer1_tx;
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx | SPI_I2S_DMAReq_Tx, ENABLE);
DMA_Cmd(DMA_Channel_SPI1_RX, ENABLE);
DMA_Cmd(DMA_Channel_SPI1_TX, ENABLE);
}

also resetting the base address of the transfer buffer; additionally tried to disable and enable SPI->DMA. I also tried using ''transfer size -1'' just in case, which doesn't change anything though. The programm now immediatly stalls after

Starting program.

I even built a LED Cube yesterday just to have a little success with working with these cute µCs. _That_ works at least 😉 I'm still lost. What else could I try? Edit: I just stumbled upon these App notes for STM32F0xx - how to use DMA:http://www.icbase.com/Promotion/download/AN4pdf In that SPI example, they use TIM2 for generating a 4kHz PWM which is used as trigger for DMA. In the examples on the net I saw nothing like this. Is it necessary to use TIM2 to generate somehting like my SPI clock frequency for DMA to start? Or is that just misleading?
de_koepi
Associate II
Posted on May 09, 2014 at 19:59

A little progress - by shifting some lines of code around, the program starts again at least.

// Reset SPI Interface
SPI_I2S_DeInit(SPI1);
// SPI initial setup - 23LC1024 SPI mode 0
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;

// SPI_Mode 0 / High=1

SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;

// SPI_Mode 0 / 2EDGE=1

SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;

// set the NSS management to internal | SPI_NSSInternalSoft_Set and pull internal NSS high

SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_Init(SPI1, &SPI_InitStructure);
// for SPI transfers, get the function address for pointer arithmetics
get_spixbase();
// ----------------------------------------------------------------------------
// DMA RX SPI1 channel
DMA_DeInit(DMA_Channel_SPI1_RX);
// DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
DMA_InitStructure.DMA_PeripheralBaseAddr = spixbase;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)buffer1_rx;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = buffer_size;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA_Channel_SPI1_RX, &DMA_InitStructure);
// DMA_Cmd(DMA_Channel_SPI1_RX, ENABLE);
// DMA TX SPI1 channel
DMA_DeInit(DMA_Channel_SPI1_TX);
// DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
DMA_InitStructure.DMA_PeripheralBaseAddr = spixbase;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)buffer1_tx;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = buffer_size;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_Low;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA_Channel_SPI1_TX, &DMA_InitStructure);
// DMA_Cmd(DMA_Channel_SPI1_TX, ENABLE);
SPI_Cmd(SPI1, ENABLE);
// tell SPI1 to use DMA
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx | SPI_I2S_DMAReq_Tx, ENABLE);
// Interrupt at DMA transfer complete
DMA_ITConfig(DMA_Channel_SPI1_TX, DMA_IT_TC, ENABLE);
DMA_ITConfig(DMA_Channel_SPI1_RX, DMA_IT_TC, ENABLE);

void

spi1_dma_tx(

int

size) {
DMA_Cmd(DMA_Channel_SPI1_TX, DISABLE);
DMA_Cmd(DMA_Channel_SPI1_RX, DISABLE);
DMA_SetCurrDataCounter(DMA_Channel_SPI1_RX, size);
DMA_SetCurrDataCounter(DMA_Channel_SPI1_TX, size);
// DMA_Channel_SPI1_TX->CMAR=(uint32_t)buffer1_tx;
DMA_Cmd(DMA_Channel_SPI1_RX, ENABLE);
DMA_Cmd(DMA_Channel_SPI1_TX, ENABLE);
}
void

DMA1_Channel2_IRQHandler(

void

){
if

(DMA_GetITStatus(DMA1_IT_TC2))
DMA_ClearFlag(DMA1_FLAG_TC2);
// while( !(SPI1->SR & SPI_I2S_FLAG_RXNE) ); // wait until receive complete
}
void

DMA1_Channel3_IRQHandler(

void

){
if

(DMA_GetITStatus(DMA1_IT_TC3))
DMA_ClearFlag(DMA1_FLAG_TC3);
// while( !(SPI1->SR & SPI_I2S_FLAG_RXNE) ); // wait until receive complete
}

When the waiting-for-SPI-transfer-ready loop is removed, the program runs, even with starting the RAM check completely in DMA. The results are wrong though:

Starting program.
RAM chip 0 is not OK! -> [0] 255 [1] 255 [2] 255 [3] 255 [4] 255 [5] 255 [6] 255 [7] 255 [8] 255 [9] 255
RAM chip 1 is not OK! -> [0] 255 [1] 255 [2] 255 [3] 255 [4] 255 [5] 255 [6] 255 [7] 255 [8] 255 [9] 255
RAM chip 2 is not OK! -> [0] 255 [1] 255 [2] 255 [3] 255 [4] 255 [5] 255 [6] 255 [7] 255 [8] 255 [9] 255
RAM chip 3 is not OK! -> [0] 255 [1] 255 [2] 255 [3] 255 [4] 255 [5] 255 [6] 255 [7] 255 [8] 255 [9] 255
RAM chip 4 is not OK! -> [0] 255 [1] 255 [2] 255 [3] 255 [4] 255 [5] 255 [6] 255 [7] 255 [8] 255 [9] 255
[..]
Reading COM11 again from OV7670: [143] -> Prescaler 16, frequency 500 kHz.
Select first RAM chip.
Writing image succeeded! Wrote 614400 bytes, used 5 RAM chips.
Image dimension: 640 x 480 pixel. 10 bytes captured:
[101]: 159 [102]: 207 [103]: 162 [104]: 222 [105]: 64 [106]: 221 [107]: 123 [108]: 217 [109]: 72 [110]: 215

The results at bottom are via SPI polling an correct. Maybe there is some special order I have to adhere to so that the transfers work?
de_koepi
Associate II
Posted on May 11, 2014 at 09:31

A little progress as I see _some_ data, but not the proper result yet:

Starting program.
RAM chip 0 is not OK! -> [0] 254 [1] 126 [2] 255 [3] 127 [4] 128 [5] 0 [6] 129 [7] 1 [8] 130 [9] 2 
RAM chip 1 is not OK! -> [0] 254 [1] 126 [2] 255 [3] 127 [4] 128 [5] 0 [6] 129 [7] 1 [8] 130 [9] 2 
RAM chip 2 is not OK! -> [0] 254 [1] 126 [2] 255 [3] 127 [4] 128 [5] 0 [6] 129 [7] 1 [8] 130 [9] 2 
RAM chip 3 is not OK! -> [0] 254 [1] 126 [2] 255 [3] 127 [4] 128 [5] 0 [6] 129 [7] 1 [8] 130 [9] 2 
RAM chip 4 is not OK! -> [0] 123 [1] 234 [2] 109 [3] 228 [4] 143 [5] 232 [6] 73 [7] 238 [8] 108 [9] 216 

I changed the DMA-Transfer routine to disable and enable SPI completely:

void
spi1_dma_tx(
int
size) {
while
(!(SPI1->SR & SPI_SR_TXE)); 
// Wait for bus free
while
(SPI1->SR & SPI_SR_BSY);
// tell SPI1 not to use DMA
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx | SPI_I2S_DMAReq_Tx, DISABLE);
SPI_Cmd(SPI1, DISABLE);
DMA_Cmd(DMA_Channel_SPI1_TX, DISABLE);
DMA_Cmd(DMA_Channel_SPI1_RX, DISABLE);
DMA_SetCurrDataCounter(DMA_Channel_SPI1_RX, size);
DMA_SetCurrDataCounter(DMA_Channel_SPI1_TX, size);
// DMA_Channel_SPI1_TX->CMAR=(uint32_t)buffer1_tx;
DMA_Cmd(DMA_Channel_SPI1_RX, ENABLE);
DMA_Cmd(DMA_Channel_SPI1_TX, ENABLE);
// tell SPI1 to use DMA
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx | SPI_I2S_DMAReq_Tx, ENABLE);
SPI_Cmd(SPI1, ENABLE);
}

Edit: Will clean up the first posts a little, noone wants to read the whole program code for sure!
de_koepi
Associate II
Posted on May 13, 2014 at 11:55

I added a global variable which is true during DMA transfers and gets set to false on the Transfer Complete interrupts. I use this for example in chip_select() and deselect() so the transfer has a chance to complete before I lift the CS pin. This is also checked before a new transfer gets started.

Interestingly, this doesn't help either; the result stays the same.
de_koepi
Associate II
Posted on May 14, 2014 at 06:25

Logic Analyzer at 16 MHz ...

0690X000006057LQAQ.png

0690X000006057QQAQ.png

The DMA transfer looks alright, this is 0..1..2..3..4..5... and so on. Zooming into the MISO signal shows that the weird results I am seeing from the code are there on the line as well. 

I tried lowering the SPI frequency. With a Prescaler > 8, MISO stays calm all the time (constantly at HIGH level) until the polling code starts at the end.

Maybe someone else had such a behaviour and could solve it?
de_koepi
Associate II
Posted on May 14, 2014 at 22:30

If anybody is interested to the solution - it is quite simple: Wait for the damn transfer to finish before writing to the buffers again.

// Check the RAM first
for
(
int
bank=0; bank<5; bank++) {
bool
is_ok=
true
;
chip_select(bank);
// Send WRITE command to chip
buffer1_tx[0]=WRITE;
// as default sequential mode, thus address has to be sent only at start
buffer1_tx[1]=0; 
//send even MoreMSByte address first
buffer1_tx[2]=0; 
//send MSByte address first
buffer1_tx[3]=0; 
//send LSByte address
spi1_dma_tx(4);
while
(transfer);
for
(
int
i=0; i<buffer_size; i++) {
buffer1_tx[i]=i;
buffer1_rx[i]=0;
}
for
(
int
i=0; i < (ram_size/buffer_size); i++) {
spi1_dma_tx(buffer_size);
}
chip_deselect();
chip_select(bank);
// Send READ command to chip
buffer1_tx[0]=READ;
// as default sequential mode, thus address has to be sent only at start
buffer1_tx[1]=0; 
//send even MoreMSByte address first
buffer1_tx[2]=0; 
//send MSByte address first
buffer1_tx[3]=0; 
//send LSByte address
spi1_dma_tx(4);
while
(transfer);
for
(
int
i=0; i<buffer_size; i++) {
buffer1_tx[i]=0xff;
buffer1_rx[i]=0;
}
for
(
int
i=0; i < ((ram_size/buffer_size)); i++) {
spi1_dma_tx(buffer_size);
while
(transfer);
for
(
int
i=0; i<buffer_size; i++) {
if
(buffer1_rx[i] != i) {
is_ok=
false
;
sprintf(text, 
''[%i]!=%i ''
, i, buffer1_rx[i]);
uart_tx(text);
}
}
}
sprintf(text,
''RAM chip %i is %s!
''
, bank, (is_ok? 
''OK''
: 
''NOT OK''
));
uart_tx(text);
chip_deselect();
}

At SPI_Prescaler_8 this results in 1 wrong byte in the last four chips. Setting prescaler to 4 leads to this:

Starting program.
RAM chip 0 is OK!
RAM chip 1 is OK!
RAM chip 2 is OK!
RAM chip 3 is OK!
RAM chip 4 is OK!