STM32 - using SPI with circular DMA - Page 1
Forum section: electronics / general

author: ebastler
author_rank: Super Contributor
timestamp: April 18, 2021, 07:23:49 am
content: I would like to use an STM32F303 to receive a stream of synchronous serial data from an existing device. The source device provides- a 5 MHz clock which is always active,- a gating signal which periodically goes high, indicating a burst of valid data (burst length is always constant), and- a binary data stream, which is valid/relevant during the gating signal.So I thought I'd set up a "slave, RX only" SPI channel, with its hardware NSS input connected to my gating signal. Then configure circular DMA for this SPI channel, with a buffer length which equals the length of the data burst. I would like to only need to set this up once and then keep it running, without any interrupts or need to re-start SPI reception.(The software does not need to know the exact point in time when a reception cycle is completed. It can simply assume that the DMA memory buffer contains the most recently received data burst at any time. Reading the data buffer in the middle of a new burst reception is also OK.)First question: Can this work as sketched out above, or am I overlooking/misunderstanding anything?My second question refers to the proper DMA configuration. I am using the latest CubeIDE version (1.6.1) with the graphical device configuration tool. Have configured SPI1 as sketched out above, assigned the SPI1_RX DMA request to DMA1, channel 2, and asked for a circular buffer with auto-incremented memory address. I have the code generation set up to use HAL drivers.The generated initialization code for the DMA is shown below. It sets up an interrupt which I don't need (and can probably simply remove, assuming my general concept above makes sense). But where does it set up the DMA for circular buffer operation, and what else needs to be done to actually enable the DMA? It seems strange that information the graphical config tool asks for seems to go unused (circular buffer, auto-increment step size). Should more code be generated automatically?Or, if I need to add this manually: How exactly?HAL_SPI_Receive_DMA(spiHandle, spiBuffer, SPI_BUFFER_SIZE)is probably what I want -- but where do I specifiy the circular buffer (and hence automated, continuous operation)? Some other call(s) must be required to set up the DMA?Thanks for reading through a long post, and thanks for any helpful hints!Code:static void MX_DMA_Init(void){/* DMA controller clock enable */__HAL_RCC_DMA1_CLK_ENABLE();/* DMA interrupt init *//* DMA1_Channel2_IRQn interrupt configuration */HAL_NVIC_SetPriority(DMA1_Channel2_IRQn, 0, 0);HAL_NVIC_EnableIRQ(DMA1_Channel2_IRQn);}

author: voltsandjolts
author_rank: Unknown
timestamp: April 18, 2021, 08:10:34 am
content: Is the data packet length a power of two? If so, this might help.https://www.eevblog.com/forum/microcontrollers/_best_-way-to-load-uart-data-to-ring-buffer-with-stm32hal/msg1071292/#msg1071292https://www.eevblog.com/forum/microcontrollers/implementing-uart-data-packets-with-consistent-overhead-byte-stuffing-(cobs)/msg3175316/#msg3175316

author: Siwastaja
author_rank: Super Contributor
timestamp: April 18, 2021, 08:43:53 am
content: Your approach sounds right at the first glance. The problem seemingly is libraries and autogeneration which claim to make things easier but apparently makes everything harder because without them, you'd have tested this out in less time than it took to write the forum post.So open up the reference manual. As for DMA, assuming the F303 has the same DMA engine as the F3, F4, F7, H7 MCUs I have worked with (F0 devices have a different, even simpler DMA), there is not much to do to configure it: set M0AR, PAR, NDTR, CR, clear interrupt flags through LIFCR, and enable the channel through CR.Code:DMA2_Stream1->M0AR = (uint32_t)&databuf;DMA2_Stream1->PAR = (uint32_t)&SPIwhatever->RXDR;DMA2_Stream1->NDTR = 1234;DMA2_Stream1->CR = 0UL<<25 /* the channel number, see the table in refman*/ |0b01UL<<16 /*med prio*/ | 0b01UL<<13 /*16-bit mem*/ | 0b01UL<<11 /*16-bit periph*/ |1UL<<10 /*mem increment*/ | 0b00UL<<6 /*periph-to-mem*/ | 0b110 /*err interrupts*/ | 1UL<<8 /*circular*/;DMA_CLEAR_INTFLAGS(DMA2, 1);DMA2_Stream1->CR |= 1UL; // enable it!where DMA_CLEAR_INTFLAGS() is a helper I created once:Code:#define DMA_CLEAR_INTFLAGS(_dma_, _stream_) do{ \if ((_stream_) == 0) (_dma_)->LIFCR = 0b111101UL<<0; \else if((_stream_) == 1) (_dma_)->LIFCR = 0b111101UL<<6; \else if((_stream_) == 2) (_dma_)->LIFCR = 0b111101UL<<16; \else if((_stream_) == 3) (_dma_)->LIFCR = 0b111101UL<<22; \else if((_stream_) == 4) (_dma_)->HIFCR = 0b111101UL<<0; \else if((_stream_) == 5) (_dma_)->HIFCR = 0b111101UL<<6; \else if((_stream_) == 6) (_dma_)->HIFCR = 0b111101UL<<16; \else if((_stream_) == 7) (_dma_)->HIFCR = 0b111101UL<<22; \}while(0)Don't forget to* Enable clock to DMA* Enable clock to SPI* Set SPI pins in Alternative Function mode with the right AF number* Look up the correct DMA channel number for DMA CR register, to map the right SPI to the DMA* Enable the DMA mode bit in the peripheral (in this case, SPI) configAs for SPI, most STM32 devices are still broken beyond laughable in how they handle hardware nCS. But if you have truly constant length communication and are not expecting any communication errors like a missed clock, it will work. For variable-length messaging (or a robust system), to make the rising nCS reset the SPI state machine, which is the basics of the basics, you need to write an interrupt handler triggered by rising nCS through EXTI, then on that handler, reset the whole SPI peripheral through the RCC reset register! On the newest STM32 models, they have semi-fixed this issue so that you can now reset the SPI by using the peripheral's own enable bit, still a long way to go to do it right.Finally, STM32F303 has SPI FIFOs. It may be worth considering not using DMA, especially if your communication packet is short enough to fit in SPI FIFOs completely. In this case, you can enable the SPI peripheral in nCS falling ISR, and read out everything in 32-bit bus reads in nCS rising ISR; especially if you would process the data anyway after full reception. Though, in your pattern, make the SPI automagically update memory region with no sync/atomicity concerns, this would be extra overhead.

author: ebastler
author_rank: Super Contributor
timestamp: April 18, 2021, 08:45:29 am
content: Thank you! My data packets are 16 words @ 20 bits, which I intend to read as 20 words @ 16 bits, so not a power of two from the STM32's perspective. But that thread confirms that my general idea for an "autonomous" reception via DMA and circular buffer should work.I just found the solution to the "mystery of the missing initialization code", I believe: There is an automatically generated file stm32f3xx_hal_msp.c which seems to have all the DMA initialization code (and more). Hadn't noticed that at all; duh...I still need to figure out whether and when all of it is called automatically, or what I still need to add in my user code. Will need to wait a couple of days -- I managed to kill my ST-Link clone yesterday by shorting something out on the target. So it's gardening today instead of microcontrollers. ;-)

author: Siwastaja
author_rank: Super Contributor
timestamp: April 18, 2021, 08:48:08 am
content: Note that DMA initialization code is so simple and small that you should really do it Just-In-Time, where needed, IMHO. It also takes just a few clock cycles so you can easily "initialize" DMA in interrupts, or whatever you need.Limiting yourself to initialize DMA once in a big "MCU initialization" step is really limiting.

author: ebastler
author_rank: Super Contributor
timestamp: April 18, 2021, 09:35:07 am
content: Thanks, Siwastaja!Agree, it would be more robust to re-initialize a DMA transfer for a 16*20 bit packet via an ISR every time the gate signal goes high. I am torn between the "auto-magical" background approach and the increased robustness of re-syncing on every transfer cycle...The source is a hard-wired (TTL), scaled-down model of Turing's Pilot ACE computer, by the way. The ACE design fully relies on never missing a beat while the serial data stream is cycling through its delay line memories: All addressing is relative to the prior instruction time, and all delay lines need to stay in sync at all times. So I can trust the source to stay in sync and send constant-length data packets. As long as the STM32 SPI does not miss a beat either, the auto-magical approach should be fine.

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 18, 2021, 11:36:51 am
content: HAL_SPI_Receive_DMA(spiHandle, spiBuffer, SPI_BUFFER_SIZE)Does everything for you. Enables the DMA, sets spiBuffer address and SPI_BUFFER_SIZE.Easy as that.

author: ebastler
author_rank: Super Contributor
timestamp: April 18, 2021, 11:46:27 am
content: Quote from: DavidAlfa on April 18, 2021, 11:36:51 amThanks! As mentioned in my second post, I feel better about it now that I found the "missing" initialization code instm32f3xx_hal_msp.c. See below -- that's all the stuff I "told" the graphical configuration tool and couldn't find anywhere in the generated code.With those details being included in the initialization code, I can see howHAL_SPI_Receive_DMAcan do its thing...Code:void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)[...]/* SPI1 DMA Init *//* SPI1_RX Init */hdma_spi1_rx.Instance = DMA1_Channel2;hdma_spi1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;hdma_spi1_rx.Init.PeriphInc = DMA_PINC_DISABLE;hdma_spi1_rx.Init.MemInc = DMA_MINC_ENABLE;hdma_spi1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;hdma_spi1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;hdma_spi1_rx.Init.Mode = DMA_CIRCULAR;hdma_spi1_rx.Init.Priority = DMA_PRIORITY_MEDIUM;if (HAL_DMA_Init(&hdma_spi1_rx) != HAL_OK){Error_Handler();}

author: ebastler
author_rank: Super Contributor
timestamp: April 21, 2021, 05:32:17 am
content: I am "back in business" now, with a working ST-Link V2 dongle -- but can't seem to get this SPI reception via circular DMA to work.The goal is to let the µC continuously receive bursts of sixteen 20-bit words via SPI and put them into a memory buffer via DMA. The duration of a burst is indicated by an external select signal which goes low periodically. Here's what I set up in CubeMX:SPI1 is a receive-only slave, hardware NSS, data size 10 bits.SPI1_RX DMA is enabled: circular mode, auto-increment the memory address by half-words.SPI and DMA interrupts are disabled.Beyond the automatically generated code, I start the DMA via HAL_SPI_Receive_DMA (&hspi1, DMA_buf, sizeof(DMA_buf) in my main.c. The main loop then periodically displays the content of the DMA_buf on an attached display.What I get is the following: After startup, the firstbyteof the reception buffer is changedonce. The byte that gets written there does somehow depend on the MOSI data I feed in, although it is not what I expect. If the SPI clock and NSS signal are not present at startup, the buffer change will only happen once I switch those signals on. (They come from an external function generator for testing.)But afterwards nothing else changes. No further byte in the buffer is ever affected, and the first byte does not change any further if I change the MOSI data and/or turn the SPI clock and NSS off and back on. It looks like the SPI interface or the DMA have turned themselves off or locked up in some way.Can there be some error condition (framing?) causing this? I don't need to re-enable anything periodically in an interrupt handler, right? And should hence be OK to keep the SPI and DMA interrupts disabled? What else could I be overlooking?I am stumped... Many thanks for your ideas!

author: Siwastaja
author_rank: Super Contributor
timestamp: April 21, 2021, 08:40:06 am
content: The usual:Reduce the problem to minimal code reproducing the problem, then post it here.Obviously this is impossible because the Cube autogeneration bloats the 10 lines of actual code into 100 or who knows how much it is, possibly 1000 total. No one can look at it, so we are back at guessing.Remove, remove, remove until you have the minimum case. Basically you are writing 2-3 registers in SPI peripheral and 4-6 in DMA. SPI peripheral have a few options one could call "trap bits" like when youdon'tuse hardware nCS (as you often don't because it doesn't work) you need toknowto set "SSI" bit to 1 otherwise the SPI peripheral turns itself off automatically making it pretty useless because they thought some weird-ass never-seen "multi-master SPI" is thedefault configurationand it's normal to "lose arbitration" in SPI, go figure. When we don't know your actual register configuration, it's hard to see where the problem might be.

author: tru
author_rank: Regular Contributor
timestamp: April 21, 2021, 09:26:26 am
content: Not sure if this helps.I'm using CubeIDE v1.5.0 and I'm pretty sure that in the CubeMX (device config tool), under the "Parameter Settings" tab for a SPI, there is only 8 bits and 16 bits option for SPI data size.How were you able to set 10bits for data size?I think the HAL library only supports 8 and 16bits anyway.

author: ebastler
author_rank: Super Contributor
timestamp: April 21, 2021, 09:33:00 am
content: Quote from: tru on April 21, 2021, 09:26:26 amThanks for the idea, but I don't think that is the issue. The SPI capabilities of different STM32 chips do differ in that respect. Some support only 8 or 16 bit words, others allow any value between 4..16 bit. The STM32F303 which I use does offer the flexible setting, and CubeMX and the HAL library seem to support that nicely -- the created register settings look plausible.(Actually I did not trust the odd word length and did try 8 bits yesterday night; didn't help...)

author: ebastler
author_rank: Super Contributor
timestamp: April 21, 2021, 09:38:05 am
content: Quote from: Siwastaja on April 21, 2021, 08:40:06 amThat would mean starting from scratch without CubeMX, I guess -- which seems like a great way for me to introduce many new errors.I was hoping to avoid that, but if I don't make progress in another round of staring at the code and the debugger tonight, this would probably be the last resort...

author: tru
author_rank: Regular Contributor
timestamp: April 21, 2021, 09:53:10 am
content: Quote from: ebastler on April 21, 2021, 09:33:00 amAh, I see so other chips do support it, I was wrong, please ignore my comment about HAL support. I've only used HAL library with DMA set to circular buffer and SPI data size of 16-bits, which does continuously receive after sending a call to the HAL_SPI_Receive_DMA function. The SPI interrupt is disabled but the global DMA interrupt is enabled (this is locked and I cannot turn it off in CubeMX). Note, I was not using hardware NSS so can't help in this part.

author: ebastler
author_rank: Super Contributor
timestamp: April 21, 2021, 10:05:18 am
content: Quote from: tru on April 21, 2021, 09:53:10 amHmm, two more things to try and change, thank you!DMA interrupts are indeed "forced on" by CubeMX by default. In the NVIC section, you can uncheck the "Force DMA channels interrupt" checkbox near the top of the dialog; then you can activate or deactivate these interrupts individually. I thought I should disable the DMA IRQ since I would not be using it; but maybe that was a bad idea and something useful is happening in a default IRQ handler? I will try reverting to the default tonight. -- Did you provide an IRQ handler in your code?I had tried to always pull NSS low to avoid an impact from that side, but will try to disable hardware NSS entirely tonight.

author: tru
author_rank: Regular Contributor
timestamp: April 21, 2021, 10:51:52 am
content: Quote from: ebastler on April 21, 2021, 10:05:18 amNo, I didn't add in my own IRQ handler, but added in the HAL predefined callback function to process each SPI data reception:void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef* hspi){...}

author: aandrew
author_rank: Frequent Contributor
timestamp: April 21, 2021, 05:50:10 pm
content: Quote from: Siwastaja on April 21, 2021, 08:40:06 amDon't be daft. I'm no fan of Cube or the HAL library either but this isn't rocket science. All they need to do is paste the code that calls Init(), paste MspInit() and paste the loop that calls the HAL_SPI_Receive_DMA() function. If they haven't got callbacks enabled they'll also want to paste their HAL_SPI_RxCpltCallback(). The HAL code doesn't need to be pasted, but knowing WHICH version of the HAL they're using would help.

author: ebastler
author_rank: Super Contributor
timestamp: April 21, 2021, 06:08:00 pm
content: Thank you for your comments, aandrew. I have a couple of questions if I may:Quote from: aandrew on April 21, 2021, 05:50:10 pmHang on -- I am supposed to call HAL_SPI_Receive_DMA() in aloop?? If that is indeed required, it would explain why I get no further action. But as mentioned above, I am using DMA in circular mode, in an SPI slave device. Isn't the whole point that this keeps going on and on, after just one call to HAL_SPI_Receive_DMA()?QuoteSorry, you lost me there. Did you mean do write "If theyhavegot callbacks, paste HAL_SPI_RxCpltCallback()"?QuoteAs mentioned in the OP, I am using STMCubeIDE 1.6.1, the most recent release version for Windows. If there is a separate version number for the HAL, where would I find this? The HAL source files I checked do not have any version numbers in their header.Thanks!

author: aandrew
author_rank: Frequent Contributor
timestamp: April 21, 2021, 08:04:17 pm
content: Generally speaking, with circular DMA it just means it can re-use the buffer over and over again but the DMA requests still need to be triggered, either manually or via a timer or some other means. I don't know exactly how you're setting things up because I haven't seen your code, most importantly the code leading up to the SPI Init() and then the MspInit() that actually configures the peripheral.I know it sounds counter-intuitive, but no, if you're *not* using callbacks then you must override the weakly-linked HAL function HAL_SPI_RxCpltCallback(), which by default does absolutely nothing. If you are using callbacks then you must register your RxCallback function with the driver. HAL used to not have callback functionality so your only choice was to override the weak functions, but part of their changes in the more recent versions of HAL is that they've introduced the ability to change the callback functions at runtime. It's always been callbacks internally, now they just give you a little more visibility into it and configurability. Personally I don't see the benefit, but it's there.As for HAL version, you usually see that in the "Manage Embedded Software Packages" window under Cube's Help menu item.

author: tru
author_rank: Regular Contributor
timestamp: April 21, 2021, 09:09:08 pm
content: Hmm, that wouldn't explain why there is a Normal and Circular mode for HAL driver because both use the same HAL_SPI_Receive_DMA(spiHandle, spiBuffer, SPI_BUFFER_SIZE) and spiBuffer needs to be passed.Ok, just tried a few tests, and I think I've found a bug in CubeMX!I confirm that with Circular buffer mode you do not call the HAL_SPI_Receive_DMA() repeatedly in a loop, it is to be called only once. In contrast with Normal buffer mode you must call it repeatedly in a loop.However, there is a bug that seems to stop it from working. If I initially set the SPI to Full-Duplex Master mode and add 2x DMA streams:1. DMA stream for SPI receive and set to circular2. DMA stream for SPI transmit and set to normalThe HAL_SPI_Receive_DMA() function receives once but then hangs (I mean HAL_SPI_RxCpltCallback() is never called again)! Like what ebastler said.If I go back into CubeMX and set the DMA stream for transmit to circular, now the HAL_SPI_Receive_DMA() function works and continuously receives - the bug is gone.Now, next problem is, when I go back into CubeMX and change the SPI to Receive only Master mode, it automagically removes the DMA stream for transmit (as mode is receive only) so leaving the DMA receive intact.However, the HAL_SPI_Receive_DMA() function receives only once and then hangs!But if I go back into CubeMX and toggle the Circular to Normal then back to Circular. Then The HAL_SPI_Receive_DMA() function works!Man!!

author: aandrew
author_rank: Frequent Contributor
timestamp: April 21, 2021, 09:26:59 pm
content: Not discounting the rest of your message, but if you're using full-duplex wouldn't you use HAL_SPI_RxCpltCallback() ?

author: aandrew
author_rank: Frequent Contributor
timestamp: April 21, 2021, 09:51:24 pm
content: Just looking at the SPI driver, I see this in the top documentation:Circular mode restriction:(#) The DMA circular mode cannot be used when the SPI is configured in these modes:(##) Master 2Lines RxOnly(##) Master 1Line RxIs what you're after perhaps not supported?

author: tru
author_rank: Regular Contributor
timestamp: April 21, 2021, 10:02:19 pm
content: Quote from: aandrew on April 21, 2021, 09:26:59 pmYes, I am using the HAL_SPI_RxCpltCallback() in both cases which contains only code to flash a LED, and I'm not sending HAL_SPI_Receive_DMA() in there. I didn't intend to test with full-duplex in circular mode, but because I started off by modifying an existing project which is using DMA with full-duplex in normal mode, I forgot to change it.Interesting, I see so it has limitations - I think yes, not supported for certain config.From the "STM32F446xx advanced Arm®-based 32-bit MCUsV6.0" reference manual I confirm DMA requests are continuous in circular mode (mentioned by the last sentence).Quote

author: aandrew
author_rank: Frequent Contributor
timestamp: April 21, 2021, 10:07:45 pm
content: maybe just use bidir mode (it eats another DMA channel but that's probably not an issue), set your SPI MOSI GPIO so it's *not* using the correct AF (so it doesn't actually transmit) and point the TX buffer to a zeroed bit of memory. That's probably a good workaround which'll give you circular rx transfers.

author: ebastler
author_rank: Super Contributor
timestamp: April 22, 2021, 05:54:43 am
content: Thank you very much for digging into this, tru and aandrew! Since I have been trying to use Rx-only mode with circular DMA, the restrictions you found might be relevant -- although the driver comments only seem to indicate restrictions in master mode, while I use slave mode.Unfortunately, switching to full-duplex slave mode and a adding circular Tx DMA has not changed the behavior of my setup. I am still stuck with receiving one (mangled?) byte only.More likely than not, I am making a totally stupid mistake somewhere... I will put this project aside for a few days, re-read the µC and HAL documentation, and get back to the debugger then. I will update this thread when I figure out what's going on (or when I give up). Thank you for your help so far!

author: tru
author_rank: Regular Contributor
timestamp: April 22, 2021, 09:30:11 am
content: I think you guys have misunderstood my tests, SPI DMA circular mode works in both full-duplex and receive only in master mode.The restrictions are probably old notes for older MCU - well definitely it doesn't apply to the MCU I'm using STM32F446 (Nucleo64 STM32F446RE).Ah ha, I think I may have found your problem. I've noticed in earlier post, you said the DMA is set to half-words (16-bits) and then start this once: HAL_SPI_Receive_DMA (&hspi1, DMA_buf, sizeof(DMA_buf))The HAL driver mislabelled the size parameter - a better name for it is transfer count. The sizeof works when the data size is byes but not for other sizes. An example of correct usage for transferring 48 * 16-bits is:Code:uint16_t DMA_buf;HAL_SPI_Receive_DMA(&hspi1, (uint8_t*)DMA_buf, 48);sizeof(DMA_buf) will return 96, that will ask DMA to transfer 96 * half-words = 96 * 16-bits.

author: ebastler
author_rank: Super Contributor
timestamp: April 22, 2021, 10:16:40 am
content: Quote from: tru on April 22, 2021, 09:30:11 amGreat minds think alike!That same idea occured to me while I was in the shower yesterday morning. I was totally confident that this would be the problem -- I must have been barking up the wrong tree for a few days, SPI and DMA were actually fine, but I was asking the RX DMA to exceed its buffer limits and hence shoot my program down. Headed over to the bench right away (well, I did get dried and dressed, but certainly skipped breakfast) -- but alas, no change.At which point I began to wonder whether I am committing some totally stupid mistake elsewhere, maybe even less related to SPI and DMA. I will take a step back and revisit my code and CubeMX settings in a few days. Might be best to start over with a minimum solution without the display attached. (Which uses another SPI port and might have caused some crossed mental wires for me...) I need a cooling-off period now!

author: aandrew
author_rank: Frequent Contributor
timestamp: April 22, 2021, 01:40:29 pm
content: This post is several days old now and still no Init(), MspInit(), DMA_Start() and Callback() code posted... why not do this so we aren't shooting in the dark?

author: ebastler
author_rank: Super Contributor
timestamp: April 22, 2021, 01:50:17 pm
content: Quote from: aandrew on April 22, 2021, 01:40:29 pmHappy to oblige! I am working at the moment but will post it later today. I was reluctant so far, because Siwastaja's initial comment was that the HAL overhead would make this too messy to go through, and since I also have that nagging feeling that my problem lies elsewhere...Thank you in advance for your willingness to look at the code!

author: Siwastaja
author_rank: Super Contributor
timestamp: April 22, 2021, 03:44:58 pm
content: Elsewhere?The point is, if you post all the code, the problemhasto be there, in the code.Very widely accepted forum protocol is to provideminimum example reproducing the problembut this is not possible with the Cube crap. Still, if you don't feel like getting rid of the Cube and writing the minimum case reproducing the problem - this should be about 10 lines and I already posted major part of it earlier -, better to post all the Cube code than nothing at all. Maybe someone can figure something out looking at it, even if long.Without the code, we can only guess. It's like trying to troubleshoot an analog electronic problem without a schematic.Quite frankly, you would have written that DMA and SPI initialiazation with the reference manual from scratch many times by now. As a side effect, you would understand how those peripherals work (by reading the manual).

author: aandrew
author_rank: Frequent Contributor
timestamp: April 22, 2021, 03:48:46 pm
content: His code (not the Cube code) will be small. In fact, since he's using the HAL I would argue that his code would be smaller than yours, from a paste-here perspective. He doesn't have to post the Cube code, but telling us which VERSION of the HAL he's using would be helpful.

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 22, 2021, 04:41:50 pm
content: Try with slave receive only mode, and disable NSS.Then send few bytes.... My bet is on the NSS management

author: ebastler
author_rank: Super Contributor
timestamp: April 22, 2021, 06:21:14 pm
content: Alright, here goes...I have removed the other SPI (which drove the display), and instead only toggle a GPIO in the HAL_SPI_RxCpltCallback function to see whether the SPI reception is still alive. The output only gets toggled once, while I would expect it to toggle periodically, after every 32*10 bits.Hope I didn't introduce any errors during the strip-down. Is this the right way to introduce the callback? My original code did not have any RxCpltCallback at all, but just read data from the DMA_BUF asynchronously. (Edit: That test output toggle only occurs once I turn on the SPI clock from the external signal generator, so I do indeed seem to observe one and only one SPI/DMA cycle.)Please let me know if anything is missing. Many thanks for your comments!main.c excerpt (removed the init code for system clock and GPIO)Code:#include "main.h"SPI_HandleTypeDef hspi1;DMA_HandleTypeDef hdma_spi1_rx;// DMA data buffer. 16*2 16-bit values, each used for 10 bits of data, low half-word first#define DMA_BUF_LEN 32uint16_t DMA_buf ;/* Private function prototypes -----------------------------------------------*/void SystemClock_Config(void);static void MX_GPIO_Init(void);static void MX_SPI1_Init(void);static void MX_DMA_Init(void);/* Private user code ---------------------------------------------------------*/void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef* hspi){HAL_GPIO_TogglePin (TESTOUT_GPIO_Port, TESTOUT_Pin);}int main(void){HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_SPI1_Init();MX_DMA_Init();// Initialize SPI1 receive DMA with circular bufferHAL_SPI_Receive_DMA (&hspi1, (uint8_t *)DMA_buf, DMA_BUF_LEN);while (1){}}static void MX_SPI1_Init(void){/* SPI1 parameter configuration*/hspi1.Instance = SPI1;hspi1.Init.Mode = SPI_MODE_SLAVE;hspi1.Init.Direction = SPI_DIRECTION_2LINES_RXONLY;hspi1.Init.DataSize = SPI_DATASIZE_10BIT;hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH;hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;hspi1.Init.NSS = SPI_NSS_SOFT;hspi1.Init.FirstBit = SPI_FIRSTBIT_LSB;hspi1.Init.TIMode = SPI_TIMODE_DISABLE;hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;hspi1.Init.CRCPolynomial = 7;hspi1.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;hspi1.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;if (HAL_SPI_Init(&hspi1) != HAL_OK){Error_Handler();}}/*** Enable DMA controller clock*/static void MX_DMA_Init(void){__HAL_RCC_DMA1_CLK_ENABLE();/* DMA interrupt init *//* DMA1_Channel2_IRQn interrupt configuration */HAL_NVIC_SetPriority(DMA1_Channel2_IRQn, 0, 0);HAL_NVIC_EnableIRQ(DMA1_Channel2_IRQn);}HAL_SPI_MspInitCode:void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi){GPIO_InitTypeDef GPIO_InitStruct = {0};if(hspi->Instance==SPI1){/* Peripheral clock enable */__HAL_RCC_SPI1_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();/**SPI1 GPIO ConfigurationPB3 ------> SPI1_SCKPB4 ------> SPI1_MISOPB5 ------> SPI1_MOSI*/GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);/* SPI1 DMA Init *//* SPI1_RX Init */hdma_spi1_rx.Instance = DMA1_Channel2;hdma_spi1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;hdma_spi1_rx.Init.PeriphInc = DMA_PINC_DISABLE;hdma_spi1_rx.Init.MemInc = DMA_MINC_ENABLE;hdma_spi1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;hdma_spi1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;hdma_spi1_rx.Init.Mode = DMA_CIRCULAR;hdma_spi1_rx.Init.Priority = DMA_PRIORITY_MEDIUM;if (HAL_DMA_Init(&hdma_spi1_rx) != HAL_OK){Error_Handler();}__HAL_LINKDMA(hspi,hdmarx,hdma_spi1_rx);}}

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 22, 2021, 09:12:15 pm
content: Make a test with 8 bit data. Could be a bug or whatever. Did you check the stm32 errata?I tried myself a moment ago, it worked perfectly in slave only receive mode, and HAL_SPI_Receive_DMA.Both 8 and 16 bit sizes. None of my stm32 have fine SPI frame size adjustment.

author: tru
author_rank: Regular Contributor
timestamp: April 22, 2021, 11:03:58 pm
content: Here's my working code for comparison - maybe you can spot something. It is working on Nucleo64 F446RE dev board with SPI set to receive master mode, 8 bits, DMA circular mode (I tried to strip out most CubeMX comments etc):Code:#include "main.h"#define DMA_BUF_LEN 32uint8_t* m_dma_buf;uint32_t m_toggle_counter = 0;/* Private function prototypes -----------------------------------------------*/void SystemClock_Config(void);static void MX_GPIO_Init(void);static void MX_DMA_Init(void);static void MX_SPI1_Init(void);int main(void){/* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* Configure the system clock */SystemClock_Config();/* Initialize all configured peripherals */MX_GPIO_Init();MX_DMA_Init();MX_SPI1_Init();HAL_SPI_Receive_DMA(&hspi1, (uint8_t*)m_dma_buf, DMA_BUF_LEN);while (1){}}void SystemClock_Config(void){RCC_OscInitTypeDef RCC_OscInitStruct = {0};RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};/** Configure the main internal regulator output voltage*/__HAL_RCC_PWR_CLK_ENABLE();__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);/** Initializes the RCC Oscillators according to the specified parameters* in the RCC_OscInitTypeDef structure.*/RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;RCC_OscInitStruct.HSEState = RCC_HSE_ON;RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;RCC_OscInitStruct.PLL.PLLM = 4;RCC_OscInitStruct.PLL.PLLN = 180;RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;RCC_OscInitStruct.PLL.PLLQ = 15;RCC_OscInitStruct.PLL.PLLR = 2;if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK){Error_Handler();}/** Activate the Over-Drive mode*/if (HAL_PWREx_EnableOverDrive() != HAL_OK){Error_Handler();}/** Initializes the CPU, AHB and APB buses clocks*/RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK){Error_Handler();}}static void MX_SPI1_Init(void){/* USER CODE BEGIN SPI1_Init 0 *//* USER CODE END SPI1_Init 0 *//* USER CODE BEGIN SPI1_Init 1 *//* USER CODE END SPI1_Init 1 *//* SPI1 parameter configuration*/hspi1.Instance = SPI1;hspi1.Init.Mode = SPI_MODE_MASTER;hspi1.Init.Direction = SPI_DIRECTION_2LINES_RXONLY;hspi1.Init.DataSize = SPI_DATASIZE_8BIT;hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;hspi1.Init.NSS = SPI_NSS_SOFT;hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;hspi1.Init.TIMode = SPI_TIMODE_DISABLE;hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;hspi1.Init.CRCPolynomial = 10;if (HAL_SPI_Init(&hspi1) != HAL_OK){Error_Handler();}/* USER CODE BEGIN SPI1_Init 2 *//* USER CODE END SPI1_Init 2 */}/*** Enable DMA controller clock*/static void MX_DMA_Init(void){/* DMA controller clock enable */__HAL_RCC_DMA2_CLK_ENABLE();/* DMA interrupt init *//* DMA2_Stream0_IRQn interrupt configuration */HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);}static void MX_GPIO_Init(void){GPIO_InitTypeDef GPIO_InitStruct = {0};/* GPIO Ports Clock Enable */__HAL_RCC_GPIOC_CLK_ENABLE();__HAL_RCC_GPIOH_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();/*Configure GPIO pin Output Level */HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);/*Configure GPIO pin : PC13 */GPIO_InitStruct.Pin = GPIO_PIN_13;GPIO_InitStruct.Mode = GPIO_MODE_INPUT;GPIO_InitStruct.Pull = GPIO_NOPULL;HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);/*Configure GPIO pin : PA5 */GPIO_InitStruct.Pin = GPIO_PIN_5;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);}void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef* hspi){if(hspi->Instance == SPI1){if((m_toggle_counter / 1000) % 2)HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);elseHAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);m_toggle_counter++;}}The call back procedure HAL_SPI_RxCpltCallback() flashes a test LED GPIO PA5, which confirms the DMA request is continuous since it keeps getting called. I've used a counter with a large division to slow down the flashing. Sorry, CubeMX or my board doesn't support DMA in half-words and SPI 10-bits so I cannot make it as close to yours, and also I don't have a clocking source to test the SPI receive slave mode, but DavidAlfa confirms he got slave mode working.

author: tru
author_rank: Regular Contributor
timestamp: April 22, 2021, 11:09:26 pm
content: Hmm, just noticed your CubeMX code doesn't have a SPI baud rate prescaler setting, or perhaps your chip only has a default?

author: ebastler
author_rank: Super Contributor
timestamp: April 23, 2021, 05:01:13 am
content: Quote from: tru on April 22, 2021, 11:09:26 pmThe baud rate prescaler is only active in SPI master mode, I think, as the slave does not generate a clock. If I switch to master mode in CubeMX, it offers the prescaler setting, in slave mode the option is hidden.

author: ebastler
author_rank: Super Contributor
timestamp: April 23, 2021, 05:14:09 am
content: Quote from: DavidAlfa on April 22, 2021, 09:12:15 pmWhile debugging my full code I also tried to fall back on 8-bit frames, which did not resolve the problem. I will try that again with the minimal code just to make sure.I had not checked the errata for the 'F303 so far, but just did so. Unfortunately can't find any relevant SPI or DMA issues.

author: tru
author_rank: Regular Contributor
timestamp: April 23, 2021, 06:35:39 am
content: Quote from: ebastler on April 23, 2021, 05:01:13 amAh yes, silly me forgot about slave mode!Also forgot to add the MSP init section from other file:Code:void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi){GPIO_InitTypeDef GPIO_InitStruct = {0};if(hspi->Instance==SPI1){/* USER CODE BEGIN SPI1_MspInit 0 *//* USER CODE END SPI1_MspInit 0 *//* Peripheral clock enable */__HAL_RCC_SPI1_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();/**SPI1 GPIO ConfigurationPA6 ------> SPI1_MISOPB3 ------> SPI1_SCK*/GPIO_InitStruct.Pin = GPIO_PIN_6;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);GPIO_InitStruct.Pin = GPIO_PIN_3;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);/* SPI1 DMA Init *//* SPI1_RX Init */hdma_spi1_rx.Instance = DMA2_Stream0;hdma_spi1_rx.Init.Channel = DMA_CHANNEL_3;hdma_spi1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;hdma_spi1_rx.Init.PeriphInc = DMA_PINC_DISABLE;hdma_spi1_rx.Init.MemInc = DMA_MINC_ENABLE;hdma_spi1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;hdma_spi1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;hdma_spi1_rx.Init.Mode = DMA_CIRCULAR;hdma_spi1_rx.Init.Priority = DMA_PRIORITY_LOW;hdma_spi1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;if (HAL_DMA_Init(&hdma_spi1_rx) != HAL_OK){Error_Handler();}__HAL_LINKDMA(hspi,hdmarx,hdma_spi1_rx);/* USER CODE BEGIN SPI1_MspInit 1 *//* USER CODE END SPI1_MspInit 1 */}}

author: ebastler
author_rank: Super Contributor
timestamp: April 23, 2021, 07:48:38 am
content: Quote from: Siwastaja on April 22, 2021, 03:44:58 pmI can think of many things that could go wrong outside of the source code: A missing or bad connection on the breadboard, incorrect jumper settings for the boot selection, some untoward debug setting in the IDE, a counterfeit and buggy CPU... I checked and double-checked the aspects which I thought of, but obviously can't rule out that something else might be wrong.QuoteMaybe coding from scratch would have been quicker, or maybe I would still be trying to figure out how to set up the system clocks...Most likely, I would be exactly where I am now, since setting the registers correctly does not seem to be the problem.No need to reprimand me for using HAL and CubeMX, I think. I did make that choice consciously (and not without doubts -- a HAL manual which is actuallylongerthan the processor's reference manual seems to defeat the purpose of a HAL to some extent...) I have programmed various microcontrollers on the bare metal level over the years, starting with the 8051, so I am not coming at this straight from Arduinos. But did find the STM32 family a bit overwhelming and thought I'd give the "modern" approach a try; and it certainly has helped me to get my bearings quicker in several cases. Still undecided what approach I will take on my next project.

author: ebastler
author_rank: Super Contributor
timestamp: May 06, 2021, 05:57:49 pm
content: Quick update: I have revisited this project now, using a different STM32 processor instead of the STM32F303 I had tried without success.Using an STM32F401 (on a small "black pill" protoboard made by WeAct in China) everything works like a charm, with the exact same source code and the same peripheral settings! So the simple concept -- just a circular DMA continuously updating a buffer which gets read asynchronously, without using any interrupts -- works nicely in this application.I still have no idea what is wrong with my implementation on the 'F303: Is it an undocumented CPU erratum, or one that I could not spot and relate to my situation, or do I have a conterfeit CPU after all? But I have limited interest in finding that out; the 'F303 boards are unobtainable anyway at the moment, while the 'F401 are easily available.In any case, thanks again to those of you who have provided ideas for this wild goose chase!

author: voltsandjolts
author_rank: Unknown
timestamp: May 07, 2021, 11:10:20 am
content: Could you post a copy of the complete buildable and working project?

author: ebastler
author_rank: Super Contributor
timestamp: May 07, 2021, 06:52:28 pm
content: Quote from: voltsandjolts on May 07, 2021, 11:10:20 amSure, if it is useful -- attached here. This should be the complete project for use with STMCubeIDE. To help you get your bearings:This is meant as an X/Y "oscilloscope" display for a functional model of a vintage computer -- the Pilot ACE, based on a 1945 design by Alan Turing and realized in 1950. The ACE operates bit-serially, and the oscilloscope displays the contents of one (selectable) ultrasonic delay line, i.e. the main memory. Each delay line comprises 16 words @ 20 bits in my scaled-down functional model. This gets displayed as a simple array of bright and dim spots, one spot per bit. The original Pilot ACE did this on a CRT, of course; I am using a small TFT display driven by an ST7735.TIM1 receives a word sync signal from the ACE (high pulse at the beginning of each word) and provides a PWM output which is high for 16 words, then low for 16 words. Used as the NSS signal for SPI1.SPI1 operates in receive-only slave mode with hardware NSS, driven by the clock and data stream from the ACE. Its circular DMA continuously dumps the received data into a 40-byte buffer. The buffer is read (asynchronously) in the main loop of main.c and displayed on the "oscilloscope" screen.SPI2 operates in master mode to drive the ST7735 mini TFT display.