2024-06-11 01:39 PM
Hallo everyone,
As engineer who started to explore the world of MCU's years after my retirement, I regularly encounter problems that puzzle me for some time. However, since I'm stuck for several days now, I decided to ask for assistance by this forum.
For my project, I selected a high-performance MCU, (STM32H723VGT6) mounted on a WeAct test-board, because of its price and atractive form-factor.
I want to digitize the analoge output of a linear CCD by an ADC with SPI interface. This ADC (ADS8319) needs a rising edge to start a conversion and >1.6us later it expects a serie of 16 Clock pulses to export its data. I have read an attractive solution for such challenge at the 'StackExchange' site, that sends dummy uint16 data to a SPI with DMA, triggered by a Timer.
A SPI in Full-Duplex mode, will produce 16 Clock-pulses to transmit an U16 word. The Timer must produce PWM pulses with the wanted frequency for the CCD readout. The rising edge of the pulse will trigger the ADC conversion, while the falling edge triggers the DMA transfer of a dummy uint16 to SPI, that produces the Clock-pulses needed by the ADC to export its data. The ADC's uint16 data is sent to the SPI MISO pin, to let it be transferred to the global data-array by another DMA action. The scheme below shows the timing:
Unfortunately, the organization of DMA on my MCU (BDMA, DMA with Mux, MDMA) is different from the STM32F746 on the 'StackExchange' forum, meaning the shown code snippets cannot be copied.
I configured SPI2 as Master in Full-Duplex mode with HW NSS signal handling, set 16bit frame-size, the baudrate and MSB-first. Despite many changes and different approaches, I have not been able to make SPI2 produce Clock-pulses, while the task looks quite simple: define and start a DMA action which, triggered by a Timer-compare event, writes a uint16 value to the SPI2 TXDR register.
I sincerely hope I made myself clear and that some readers are willing to think with me about how to crack this problem. Thank you in advance,
Fred Schimmel
2024-06-16 10:11 AM - edited 2024-06-21 12:55 AM
@waclawek.jan just replied to someone in another thread and he made me realize something I didn't before (thanks for that).
You can indeed trigger a dma directly from an output compare/Input capture event. You do not need the DMAMUX (though you might be able to do it that way too).
Again, you can't do it with CubeMX, but you can get a sense of how to do it by enabling a timer and then setting
a DMA channel with DMA request "TIMx_COM". Generate the code, then look at the implementation
of `HAL_TIM_OC_Start_DMA`. It hardcodes the source/target peripheral address to be one of the timer's registers (*), so it's unusable for general purpose, but other than that it's a complete example of how to trigger DMA from a timer event.
To settle this once and for all, the list of DMA triggers sources is plainly listed in the RM in the documentation of the TIMx_DIER register. I should have checked this, but I did not.
And yes, I still claim this is the wrong way to do what you're trying to do.
(*) In the case of Input Capture, you can HAL_TIM_IC_Start_DMA to have a series of capture values written to memory, which can be quite useful. In the case of Output Compare, I think HAL_TIM_OC_Start_DMA is used for waveform generation - for example you can modify the interval between toggles (from a list in memory), each time the timer expires.
2024-06-16 12:42 PM
> You do not need the DMAMUX
In STM32 families with DMAMUX there is no way you can avoid using it, if you want to use DMA. Triggers (requests) for DMA go through DMAMUX.
JW
2024-06-16 03:19 PM
JW, that's an invisible implementation detail. There is no explicit configuration of DMAMUX facilities, namely the DMA Request generator. Instead, you set the TIMx_DIER->CC1R register bit. This is exactly how it works in the STM32F103 for example. Still, I accept you're correct in terms of architecture.
2024-06-16 03:22 PM
I created a bug report thread for the recurring pain point of triggering DMA from timer event with CubeMX/HAL
Usability: CubeMX/HAL is a footgun if you want to trigger SPI/UART/foo DMA from timer events
2024-06-17 02:48 AM
> that's an invisible implementation detail. There is no explicit configuration of DMAMUX facilities, namely the DMA Request generator.
You still have to set up DMAMUX facilities, namely the DMA request selection.
This may appear to be "invisible" to Cube users, but not everybody uses Cube.
> Instead, you set the TIMx_DIER->CC1R register bit.
You probably meant TIMx_DIER.CCxDE. It's not "instead", it's "in addition".
> This is exactly how it works in the STM32F103 for example.
The way how DMA requests are handled is not irrelevant. In the 'F103 example, DMA requests from particular sources are steered to particular DMA Channels, thus you cannot arbitrarily select a DMA Channel (as you can in models with DMAMUX). Also, you have to take into mind that the requests to that particular DMA Channel are ORed, i.e. if you enable multiple colliding requests in the respective peripherals, you are in for a nasty surprise.
There are other DMA requests schemes implemented in other STM32 families, too; these things evolve and DMAMUX is one of the evolution steps. You may click in Cube to find out the constraints, but also you can simply read the respective manual/datasheet and design the system from a thorough understanding of it.
JW
2024-06-17 05:55 AM
Hallo Barry,
At first I want to express my appreciation for your help and attempts to convince me to be misunderstanding how the ADS8319 ADC must be handled to retrieve its data.
You may have been I misled you by my description of my configuration of the SPI peripheral: "Full-Duplex with HW NSS control". The reality is: I left the pins for SPI MOSI and NSS un-connected and applied the TIM8-CH1 PWM pulses on the CNVST input of the ADC. The timing scheme for '3-Wire CS Mode Without Busy Indicator (SDI = 1)' on Datasheet page 20 shows the following, I added my signals at the left and below:
I may seem a stubborn old ***, but I think this approach should be able to work (assuming I manage to program the SPI-read part). I already had a working situation with such layout, but running on a STM32F401CCU6 with an interrupt on TIM8 by DIER.CC1IE. The ISR was simple:
void Adapted_SPI_TxISR_16BIT(struct __SPI_HandleTypeDef *hspi)
{
/* Transmit data in 16 Bit mode */
hspi->Instance->DR = (uint32_t)(*hspi->pTxBuffPtr);
// don't update Tx-parameters, always transmit same word:
} // end of ISR 'Adapted_SPI_TxISR_16BIT()'
I was not satisfied with this solution because I had the impression the MCU could not handle 125k interrupts per second in a timely manner, causing some ADC conversions to be skipped.
The remaining question is now: is this approach feasible? or are you still convinced, I am thinking in the wrong direction?
As I wrote before: I have a lot to read, to think about and to experiment with, so don't expect any result on short notice.
Have a nice day,
Fred Schimmel
2024-06-17 05:56 AM - edited 2024-06-17 07:36 AM
JW, I appreciate both your attention to detail and your deep knowledge. And your corrections. I also agree that the way to gain a deep understanding of a part is to know it at the register level, not the GUI level. But I'm not there yet.
I've verified what you say is true. When Cube generates for G4 (a Part which includes a DMAMUX),
main->MX_TIMx_Init->HAL_TIM_Base_Init->HAL_TIM_Base_MspInit ->HAL_DMA_Init
does program the DMAMUX registers. Like you said, those who write bare-metal code can't ignore the DMAMUX in this scenario, so it's by no means "transparent" as I suggested.
2024-06-17 06:55 AM
> I had the impression the MCU could not handle 125k interrupts per second in a timely manner
At 84MHz system clock, you had 672 machine cycles per interrupt. While the ISR entry/exit, C function prologue/epilogue, qualifying the interrupt source and clearing it, may take around 50 cycles - maybe a tad bit more, if the ISR does nothing but pulling a variable and storing it into a peripheral register, that may push towards 100 cycles. Out of the 672. That's more than enough, provided you don't use other lengthy interrupt (e.g. USB) with the same or higher priority.
What I guess choked your mcu was the overhead brought in by Cube's method of handling the interrupts, possibly exacerbated by not using compiler optimizations.
JW
2024-06-17 07:09 AM - edited 2024-06-17 07:22 AM
Fred, I wish you nothing but success with your chosen approach. It's possible it will work just fine. I think taking advantage of the hardware support for CSn in the SPI peripheral would be a far simpler (and natural, and perhaps even the manufacturer's recommended) way to do it, and I tried to push you in that direction. But it's your project - only you can decide on the direction you want to take.
You now know you can trigger a DMA directly from an output compare event (I laid out roughly how to go about it with Cube/HAL, and JW gave a detailed explanation at the register level). You said several times that implementing this was the primary obstacle. That means you've made definite progress and therefore all is well.
2024-06-17 08:16 AM - edited 2024-06-17 08:17 AM
Wow, this is interesting. I'm not sure which families it applies to but it seems to suggest the described behavior is by design (i.e. applies to all STM32 families):
STM32 gotcha No. 21: SPI master NSS (CSn) is unusable