Skip to main content
de_koepi
Associate II
May 3, 2014
Question

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

  • May 3, 2014
  • 9 replies
  • 2427 views
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.
    This topic has been closed for replies.

    9 replies

    de_koepi
    de_koepiAuthor
    Associate II
    May 5, 2014
    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.
    Tesla DeLorean
    Guru
    May 5, 2014
    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 (See Profile) Up vote any posts that you find helpful, it shows what's working..
    de_koepi
    de_koepiAuthor
    Associate II
    May 6, 2014
    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
    de_koepiAuthor
    Associate II
    May 8, 2014
    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
    de_koepiAuthor
    Associate II
    May 9, 2014
    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
    de_koepiAuthor
    Associate II
    May 11, 2014
    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
    de_koepiAuthor
    Associate II
    May 13, 2014
    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
    de_koepiAuthor
    Associate II
    May 14, 2014
    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
    de_koepiAuthor
    Associate II
    May 14, 2014
    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!