DMA for SPI
Posted: Thu Jan 09, 2025 5:04 am
I'm thinking about gradually introducing DMA Into SPI, but for now I too have little bandwidth to work on that.
I did some experiments, by using STM32 Cube IDE to generate SPI codes for various socs, I tried with the following socs
(only code generation not tried actual tests with devices yet):
- stm32f103cb (c8 should be quite similar I'd guess)
- stm32f401cc
- stm32g431cb
- stm32h562rg
- stm32h723vg
and even
- stm32g030f6p6
it turns out that SPI with DMA is possible (at least the codes are generated) for the above socs.
However, I realized big stumbling blocks while examining the codes and various ref manuals.
To do DMA, the following tasks are necessary
- at initialization to configure DMA for SPI
(this is the biggest stumbling block)
the DMA peripherals on every series (possibly within series as well?) is different
and the worse thing is:
the DMA channels and/or streams (and possibly other designs) of every SPI (including within series) is different
e.g. spi1, spi2, spi3 etc are bound to different channels / streams and there are limited DMA resources (i.e limited channels, stream etc)
and a possible channel/stream conflict use is high
in this terms, every chip (every series and every SPI ) would need a different configuration
(i.e. different even on a same chip for different spi)
due to the high risks of DMA usage conflicts, SPI with DMA may not be possible 'everywhere'.
- configuration of SPI (this is the usual SPI settings baud rates, polarity, phase, etc), but that there is at least some differences between the series
some of the SPI peripherals can be driven by clocks other than PCLK e.g. on the H7 series (this is 'lower level' than just SPI in RCC)
e.g. it may cause some troubles with baud rate calcs.
In addition, to enable DMA in the SPI peripheral register settings.
- configuration of interrupts for DMA half_complete, complete interrupts
Apparently the call
HAL_SPI_TransmitReceive_DMA(&hspi1, TX_Buffer, RX_Buffer, SPI_DMA_BUF_SIZE);
is actually similar/same between the series and different soc, but:
- it actually uses the DMA_complete interrupt (it registers a callback) to handle DMA completion
- this means that it is actually *async*, i.e. it returns even before the DMA transfer is complete.
- due to the use of the DMA complete interrupts, the interrupt hooks needs to be configured for the *correct DMA channel/stream* and enabled.
different between series and different spi pheripherial (i.e. different even on a same chip for different spi)
I'm thinking then that the SPI class with DMA would be better implemented in its own separate base class.
Due to very different initializations between different series (even within series) and different between every different SPI pheripherial (e.g. spi1, spi2, etc are different) and different behaviour (e.g. async)
Some ways to do that however, could include that the SPI.transfer() functions may need to be declared virtual in the original base class the .h file.
It is also unlikely that DMA be provided in default (given that 'everything is different' with possible conflicts as above), but that it could be implemented as 'examples' and using the DMA base class as a template, and that individuals take that forward by overriding the methods in the DMA base class and do their own DMA, SPI and interrupts (NVIC) initializations for their own SOC.
I did some experiments, by using STM32 Cube IDE to generate SPI codes for various socs, I tried with the following socs
(only code generation not tried actual tests with devices yet):
- stm32f103cb (c8 should be quite similar I'd guess)
- stm32f401cc
- stm32g431cb
- stm32h562rg
- stm32h723vg
and even
- stm32g030f6p6
it turns out that SPI with DMA is possible (at least the codes are generated) for the above socs.
However, I realized big stumbling blocks while examining the codes and various ref manuals.
To do DMA, the following tasks are necessary
- at initialization to configure DMA for SPI
(this is the biggest stumbling block)
the DMA peripherals on every series (possibly within series as well?) is different
and the worse thing is:
the DMA channels and/or streams (and possibly other designs) of every SPI (including within series) is different
e.g. spi1, spi2, spi3 etc are bound to different channels / streams and there are limited DMA resources (i.e limited channels, stream etc)
and a possible channel/stream conflict use is high
in this terms, every chip (every series and every SPI ) would need a different configuration
(i.e. different even on a same chip for different spi)
due to the high risks of DMA usage conflicts, SPI with DMA may not be possible 'everywhere'.
- configuration of SPI (this is the usual SPI settings baud rates, polarity, phase, etc), but that there is at least some differences between the series
some of the SPI peripherals can be driven by clocks other than PCLK e.g. on the H7 series (this is 'lower level' than just SPI in RCC)
e.g. it may cause some troubles with baud rate calcs.
In addition, to enable DMA in the SPI peripheral register settings.
- configuration of interrupts for DMA half_complete, complete interrupts
Apparently the call
HAL_SPI_TransmitReceive_DMA(&hspi1, TX_Buffer, RX_Buffer, SPI_DMA_BUF_SIZE);
is actually similar/same between the series and different soc, but:
- it actually uses the DMA_complete interrupt (it registers a callback) to handle DMA completion
- this means that it is actually *async*, i.e. it returns even before the DMA transfer is complete.
- due to the use of the DMA complete interrupts, the interrupt hooks needs to be configured for the *correct DMA channel/stream* and enabled.
different between series and different spi pheripherial (i.e. different even on a same chip for different spi)
I'm thinking then that the SPI class with DMA would be better implemented in its own separate base class.
Due to very different initializations between different series (even within series) and different between every different SPI pheripherial (e.g. spi1, spi2, etc are different) and different behaviour (e.g. async)
Some ways to do that however, could include that the SPI.transfer() functions may need to be declared virtual in the original base class the .h file.
It is also unlikely that DMA be provided in default (given that 'everything is different' with possible conflicts as above), but that it could be implemented as 'examples' and using the DMA base class as a template, and that individuals take that forward by overriding the methods in the DMA base class and do their own DMA, SPI and interrupts (NVIC) initializations for their own SOC.