cancel
Showing results for 
Search instead for 
Did you mean: 

STM32L476 SPI3 Using DMA2 Chan 2. No Transmit.

shingadaddy
Senior

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

1 ACCEPTED SOLUTION

Accepted Solutions
shingadaddy
Senior

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. 
 

View solution in original post

15 REPLIES 15
TDK
Guru

> 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.

If you feel a post has answered your question, please click "Accept as Solution".

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.....

Piranha
Chief II

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.

shingadaddy
Senior

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. 

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

 

TDK
Guru

> 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.

If you feel a post has answered your question, please click "Accept as Solution".

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).

shingadaddy
Senior

 

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. 

shingadaddy
Senior

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.