Skip to main content
Visitor II
December 9, 2023
Solved

STM32L476 SPI3 Using DMA2 Chan 2. No Transmit.

  • December 9, 2023
  • 11 replies
  • 3967 views

Hello Everyone. My how the forum has changed. Just dropping a recent odd experience here to see if anyone might have any comments. No. I don't use CubeMx and no this isn't another 3 year anniversary item regarding initializing the DMA before any peripherals that use it. We do that by default. However - working on a colleagues project, I have some code written to do some work that IS HAL library style stuff.

Getting a DMA controlled transmission from SPI 3 from the micro.

I have a DMA transmission from SPI 1 working. So I am familiar…….

Second Saturday in a row working on this for SPI3…..    Nothing I try works when using the DMA approach.

I can get all the SPI 3 hardware to work and send data if I call the BLOCKING SPI routine. NOT using the DMA.

Dumped out tons of register settings – All looks as it should.

Finally I give in to what I figure is a LOOOOOOOOOOONNNGGGGGG shot . --------              Try a different board.

It appears to work.  (???????????????)

Mind you --- the first board works with everything EXCEPT a DMA transfer for SPI3. Leads me to believe that – the micro has a BURRIED PERIPHERAL BAD ? One that has NO OUTSIDE WORLD HOOKUPS ?

*THAT*   will definitely be a FIRST for me.  I don’t know if I believe what I see until I try this on several boards.       I have 4. Of course -  I'm spooked about some sort of configuration weirdo that allows some race to occur that will maybe work on some chips and not on others.  My only question would be ---  Suggestions as to Where? I have this set up to test as a 1 pass event then trap it after the call to HAL_SPI_Transmit_DMA(&Spi_3_Handle, source, 33840). So not getting a loop around recall. And yes I always  wait after enabling clocks .

How rare is the bad burried peripheral idea?

Any feedback appreciated!

Thanks.

R

    This topic has been closed for replies.
    Best answer by shingadaddy

    Arg,,,,   Probably 4-5-6 years since I was bit by that one. 

    Okay - Say you need to use 2 SPI devices. And you want to use DMA on BOTH!

    You KNOW you need to init the DMA before any of the other peripheral stuff that uses it gets initialized.

    So "I" did EXACTLY this, one right after the other.:

    /***********************************************************************************************************************/
    /********************** SPI3 using DMA 2 *********************/
    __HAL_RCC_DMA2_CLK_ENABLE(); /*  DMA peripheral clock   */
    HAL_Delay(100);
     
        /*##-3- Configure the DMA ##################################################*/
        /* Configure the DMA handler for Transmission process */
        hdma_tx.Instance                    = DMA2_Channel2;
        hdma_tx.Init.Request              = DMA_REQUEST_3;
        hdma_tx.Init.Direction             = DMA_MEMORY_TO_PERIPH;
        hdma_tx.Init.PeriphInc            = DMA_PINC_DISABLE;
        hdma_tx.Init.MemInc               = DMA_MINC_ENABLE;
        hdma_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
        hdma_tx.Init.MemDataAlignment    = DMA_MDATAALIGN_BYTE;
        hdma_tx.Init.Mode                = DMA_NORMAL;
        hdma_tx.Init.Priority            = DMA_PRIORITY_LOW;
     
        HAL_DMA_Init(&hdma_tx);
     
        /* Associate the initialized DMA handle to the the SPI handle */
        __HAL_LINKDMA(&Spi_3_Handle, hdmatx, hdma_tx);
    /********************************************************************/
     
    //#if 0
    /********************** SPI2 using DMA 1 *********************/
    __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA peripheral clock */
    HAL_Delay(100);
     
        /*##-3- Configure the DMA ##################################################*/
        /* Configure the DMA handler for Transmission process */
        hdma_tx.Instance                 = DMA1_Channel5;
        hdma_tx.Init.Request             = DMA_REQUEST_1;
        hdma_tx.Init.Direction           = DMA_MEMORY_TO_PERIPH;
        hdma_tx.Init.PeriphInc           = DMA_PINC_DISABLE;
        hdma_tx.Init.MemInc              = DMA_MINC_ENABLE;
        hdma_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
        hdma_tx.Init.MemDataAlignment    = DMA_MDATAALIGN_BYTE;
        hdma_tx.Init.Mode                = DMA_NORMAL;
        hdma_tx.Init.Priority            = DMA_PRIORITY_LOW;
     
        HAL_DMA_Init(&hdma_tx);
     
        /* Associate the initialized DMA handle to the the SPI handle */
        __HAL_LINKDMA(&Spi_2_Handle, hdmatx, hdma_tx);
    //#endif
    /***************************************************************************************************************/
     
    Then proceeded onward to init the SPI and GPIO stuff. 
    Problem - 
    Here - That hdma_tx struct isn't used as a generic "fill it out and fling it into a peripheral register" use. 
    Having both of these DMA inits in code caused to the top one , SPI3-DMA2 to fly off in the weeds. when the DMA TX was called. (NOT DURING INIT TIME ...) 
    POUND IFING (Commenting)  the bottom one out - let the top one operate as it should. Creating separate  hdma1_tx and hdma2_tx structs seems to have done the trick. At least with both using those individual structs - the top one works. I'll get to testing both DMA transmits simultaneously in the near future.  
     
    There was no 1 board works and the other doesn't. THAT was *my* mistake where I commented out the second DMA setup after thinking - "Lets deal with one DMA at a time". SO I commented it out.. THEN tried it on - THE BOARD THAT WORKED.    Little did I now that THAT was where the issue was...And around the circle I went.
    Bottom line ------------    DON'T DO THAT.    :\  
     
    AND! - I found a while(1)  (Didn't have any ill effects on the SPI DMA TX however)       
    AND it looked exactly like that. No brackets! 
    And the compiler (complainer) never barked at me for it.   I'm somewhat surprised by that. Thank you all for your feedback / encouragement. 
     

    11 replies

    Super User
    December 9, 2023

    > How rare is the bad burried peripheral idea?

    I wouldn't bet on it, even with 10:1 odds.

    I would suggest not falling into the X is working, Y is not, so what's different must be the reason (in this case, the hardware).

    Rather debug from first principles. What does "not working" mean in particular and drill down from there. HAL function returning something other than HAL_OK? Debug, step through, find out why. Not getting the response you expect? Look at signals on the line to see which side of the transaction is at fault.

    DMA is pretty straightforward, see if NDTR goes to 0 or stays where it was. TXE signal should be making it to DMA causing the transfer.

    Visitor II
    December 10, 2023

    Thanks TDK!. NOTE:  this SPI is ONE WAY. Micro is master and display is slave. SPI speeds slowed to minimum for testing. 300 or so Kbps. Scope on MOSI shows - no TX if triggered by DMA . Before I do a DMA TX on - SPI3 ---  I do a Blocking TX on SPI3!  This is to set a window location/size on a display to transmit data to for display. Then I cut loose the long winded image data DMA transmit. I do the exact same thing - SUCCESSFULLY thank goodness - on SPI 1 DMA 1 chan 3 for different displays. On SPI3 DMA2 chan 2 on the original board, scope shows the SETWINDOWS transmission on MOSI.    After 1mS,  Then I see my chip select go low and no data, no clock,  and it looks like it immediately interrupts as COMPLETE! The Length variable value of 33840 is successfully getting in the called DMA function. Chip select returns high near immediately and follow on code executes as expected. I even put a transfer wait block around the DMA TX code and it clears - presumably because the TX COMPLETE int fired and set it to "ready"

    I see no returns of failures. I ferreted out any error spinlocks, HAL_BUSY conditions for failure but none are tripping. I have a code block I drop in at any location and it will dump registers (via USB - no debugger here) when encountered and spinlock the micro so I know where it got to. No register differences noted from SPI1 to SPI3 other than those expected.

    Honestly -  Seeing the other board work - blew my mind.....

    Graduate II
    December 10, 2023

    The HAL/Cube broken bloatware is so unnecessarily over-complicated that it's almost impossible to follow all of it's inconsistent state changes full with race conditions. Just get rid of it and implement a simple but correct test based on registers. Most likely it will just work.

    Visitor II
    December 10, 2023

    Thank you Piranha. Yes the abstraction level created by all this "CLICK IT TO WRITE CODE" stuff gets users far removed from the real execution level stuff but that's a common talking point here. Always has been! :)

    Its highly possible I'll do the fat trim approach before I get happy enough to call it good.

    It does however get more folks more interested easier and quicker. If it doesn't work due to over complication however - it can scare people away from a good product. Maybe some even stay with it long enough to learn the really clean methods. 

    Graduate II
    December 10, 2023

    The main problem with HAL is the brokenness and unusable API. The abstraction is not the main problem and actually HAL doesn't abstract common things (USART, I2C) high enough and therefore doesn't provide any usable abstraction layer. And on top of that HAL tries and fails to abstract 80% of things, which should not be abstracted at all (TIM, ADC, DAC).

    Super User
    December 10, 2023

    So, without DMA you see activity on SCK and MOSI, and with DMA you don't?

    Read out and check/compare-to-working/post content of SPI3 and relevant DMA data, after you've started the DMA and seen no activity at the wires.

    JW

     

    Super User
    December 10, 2023

    > no debugger here

    Going to be an uphill battle. Good luck.

    This is probably something that could be solved in 5-10 minutes stepping through the code.

    Visitor II
    December 10, 2023

     

    Hi JW!.  Thanks for the time. 

    "So, without DMA you see activity on SCK and MOSI, and with DMA you don't?"

    Yeeeessss....  EXACTLY! Raises an eyebrow right? :)

    I call BLOCKING SPI3 - it works

    I call TXDMA SPI3 - it DOES NOT TX

    I call TXDMA SPI1 - it works! (Uses the same code but the different passed SPI handle struct.)

    Meaning of course the CLocks/ Port-Pins for the GPIO /SPI3 are okay the clocks and general struct content for the SPI 3 are okay. And I've veryified the clock enable bit and all (Both in my case) bits in the register collection for the DMA are set correct. I want to do as you say and kick off the DMA call then loop on the interrupt event register to see if the complete bit sets immediately . Thats what LOOKS like is happening. 

    TDK! Thanks again.

    "This is probably something that could be solved in 5-10 minutes stepping through the code."

    Very possible if a debugger was in play here but it isn't. I COULD move this code to one of my many Nucleo 476 RG dev boards. My fear there would be --- IT WOULD WORK!....   I'm drawn to the one that does not work and find out WHY. 

    Piranha!

    It can be frustrating. I get it believe me. Problem is - ST are *trying* to reach out to all manner of humans who have walked the many paths of life that are available- that MIGHT be interested in this sort of activity. Undoubtedly a DAUNTING task to say the least. SO I will alway cut them some slack :) .

    Some folks that try this stuff out , try -- and leave quick. Some stick it out even though the path isn't perfect.

    And ST are almost certain to hire more code writers that come down the OS level path that have to make a sizable shift to understand the superloop stuff that ground level application writers face / can do. If someone sticks to it long enough to realize that the 30-40-50 lines of code they are writing that crams struct data into a peripheral register actually can be done with a good ground level understanding and - 1 LINE OF CODE ----    well ---  it can be eye opening at the least. 

    I suspect most folks who are ground level capable don't have time to write entry level code toward the OS capable folks since ---- well they are (I am) like mushrooms and kept hidden away working on stuff that uses these devices. 

    Graduate II
    December 14, 2023

    Trying to adapt to all types of people means adapting to dumb people. If you do that, the result is that the product is "useful" only for dumb people. That is what CubeMX code generation is doing. And the idea of the code generation comes from 90ies, when it was over-hyped by university professors, who cannot develop a real software product anyway. It does not work and cannot work, because even to know what to click, one must still understand how the device works and therefore still read the reference manual. 

    The HAL has a different set of problems - it is over-abstracted, under-abstracted and all of the code is broken. 

    I myself started with a desktop PC software development and that knowledge actually helps for embedded development. I can design a real life large and complex modular software with many components and abstraction layers, write proper asynchronous and multi-threaded code etc., none of which ST's "experts" and typical electronics engineers are capable of. You mentioned the superloop architecture... Well, that is a perfect example of how stagnant and stuck at a primitive level the whole software industry is. Any decent software is event triggered, regardless if it's based on a simple single stack cooperative scheduler, RTOS or a huge desktop level OS.

    shingadaddyAuthorAnswer
    Visitor II
    December 12, 2023

    Arg,,,,   Probably 4-5-6 years since I was bit by that one. 

    Okay - Say you need to use 2 SPI devices. And you want to use DMA on BOTH!

    You KNOW you need to init the DMA before any of the other peripheral stuff that uses it gets initialized.

    So "I" did EXACTLY this, one right after the other.:

    /***********************************************************************************************************************/
    /********************** SPI3 using DMA 2 *********************/
    __HAL_RCC_DMA2_CLK_ENABLE(); /*  DMA peripheral clock   */
    HAL_Delay(100);
     
        /*##-3- Configure the DMA ##################################################*/
        /* Configure the DMA handler for Transmission process */
        hdma_tx.Instance                    = DMA2_Channel2;
        hdma_tx.Init.Request              = DMA_REQUEST_3;
        hdma_tx.Init.Direction             = DMA_MEMORY_TO_PERIPH;
        hdma_tx.Init.PeriphInc            = DMA_PINC_DISABLE;
        hdma_tx.Init.MemInc               = DMA_MINC_ENABLE;
        hdma_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
        hdma_tx.Init.MemDataAlignment    = DMA_MDATAALIGN_BYTE;
        hdma_tx.Init.Mode                = DMA_NORMAL;
        hdma_tx.Init.Priority            = DMA_PRIORITY_LOW;
     
        HAL_DMA_Init(&hdma_tx);
     
        /* Associate the initialized DMA handle to the the SPI handle */
        __HAL_LINKDMA(&Spi_3_Handle, hdmatx, hdma_tx);
    /********************************************************************/
     
    //#if 0
    /********************** SPI2 using DMA 1 *********************/
    __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA peripheral clock */
    HAL_Delay(100);
     
        /*##-3- Configure the DMA ##################################################*/
        /* Configure the DMA handler for Transmission process */
        hdma_tx.Instance                 = DMA1_Channel5;
        hdma_tx.Init.Request             = DMA_REQUEST_1;
        hdma_tx.Init.Direction           = DMA_MEMORY_TO_PERIPH;
        hdma_tx.Init.PeriphInc           = DMA_PINC_DISABLE;
        hdma_tx.Init.MemInc              = DMA_MINC_ENABLE;
        hdma_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
        hdma_tx.Init.MemDataAlignment    = DMA_MDATAALIGN_BYTE;
        hdma_tx.Init.Mode                = DMA_NORMAL;
        hdma_tx.Init.Priority            = DMA_PRIORITY_LOW;
     
        HAL_DMA_Init(&hdma_tx);
     
        /* Associate the initialized DMA handle to the the SPI handle */
        __HAL_LINKDMA(&Spi_2_Handle, hdmatx, hdma_tx);
    //#endif
    /***************************************************************************************************************/
     
    Then proceeded onward to init the SPI and GPIO stuff. 
    Problem - 
    Here - That hdma_tx struct isn't used as a generic "fill it out and fling it into a peripheral register" use. 
    Having both of these DMA inits in code caused to the top one , SPI3-DMA2 to fly off in the weeds. when the DMA TX was called. (NOT DURING INIT TIME ...) 
    POUND IFING (Commenting)  the bottom one out - let the top one operate as it should. Creating separate  hdma1_tx and hdma2_tx structs seems to have done the trick. At least with both using those individual structs - the top one works. I'll get to testing both DMA transmits simultaneously in the near future.  
     
    There was no 1 board works and the other doesn't. THAT was *my* mistake where I commented out the second DMA setup after thinking - "Lets deal with one DMA at a time". SO I commented it out.. THEN tried it on - THE BOARD THAT WORKED.    Little did I now that THAT was where the issue was...And around the circle I went.
    Bottom line ------------    DON'T DO THAT.    :\  
     
    AND! - I found a while(1)  (Didn't have any ill effects on the SPI DMA TX however)       
    AND it looked exactly like that. No brackets! 
    And the compiler (complainer) never barked at me for it.   I'm somewhat surprised by that. Thank you all for your feedback / encouragement. 
     
    Super User
    December 13, 2023

    > Creating separate hdma1_tx and hdma2_tx structs seems to have done the trick.

    OK so this is a case of improper use of Cube/HAL, isn't it.

    Yes, this boils down to appropriateness of accompanying documentation, availability and readability of relevant examples, etc. I believe this is a well known thing.

    Please click in post telling the solution the "Accept" button, so that the thread is marked as solved.

    JW

    Super User
    December 14, 2023

    Great. :thumbs_up:

    Visitor II
    December 24, 2023

    Just droppped in to finalize. Thanks everyone for the feedback.  And thanks Pavel A. for the kudo!

    This may serve as an example for the less experienced attendees who might get this far - of what you might run into if you rely on HAL for some quick fix under a short fused project. Don't get too discourage at working at the HAL level but understand that it is not the cleanest or most polished solutions. Time and more experience will help as you go on. Remember HAL is FREE. And - you get what you pay for. 

    Why didn't someone tell me "you don't need brackets dummy"? 

    Health Happiness and positive learning experiences to you all in the coming New Year. !

    R.