2025-04-16 2:14 AM - last edited on 2025-04-16 6:29 AM by mƎALLEm
Hi,
I'm trying to connect a high speed ADC to an STM32F7. I have chained two timers to generate the necessary convert and read pulses to the ADC. Its output is 16 bits that are all connected to the same gpio block.
However I would also like to setup a DMA (double buffered) transfer from GPIO input register to memory that is timed from the timer.
The timer that generates the samples from the ADC to the gpio input is configured as a one shot timer with an output channel, in this case TIM1_CH1.
Config for the timer is:
m_rd_timer.Instance = TIM1;
m_rd_timer.Init.Prescaler = prescaler;
m_rd_timer.Init.CounterMode = TIM_COUNTERMODE_UP;
m_rd_timer.Init.Period = period_len;
m_rd_timer.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
m_rd_timer.Init.RepetitionCounter = 8-1;
m_rd_timer.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
ocConfig.OCMode = TIM_OCMODE_PWM2;
ocConfig.Pulse = pulse_len;
ocConfig.OCPolarity = TIM_OCPOLARITY_LOW;
ocConfig.OCNPolarity = TIM_OCNPOLARITY_LOW;
ocConfig.OCIdleState = TIM_OCIDLESTATE_SET;
ocConfig.OCNIdleState = TIM_OCNIDLESTATE_SET;
ocConfig.ICPolarity = TIM_ICPOLARITY_FALLING;
ocConfig.ICSelection = TIM_ICSELECTION_DIRECTTI;
So basically every time the timer triggers it will generate a 8 active low pulses of a certain pulse length.
From a timing perspective the falling edge of the timer output will tell the ADC to output data to GPIO block and rising edge would be the time to actually read the GPIO register.
How do I setup the timer + DMA to do this?
DMA_HandleTypeDef dma2_stream0 = {0};
dma2_stream0.Instance = DMA2_Stream0;
dma2_stream0.Init.Channel = DMA_CHANNEL_6; // DMA2 Channel 6, stream 0 => TIM1_TRIG
dma2_stream0.Init.Direction = DMA_PERIPH_TO_MEMORY;
dma2_stream0.Init.PeriphInc = DMA_PINC_DISABLE;
dma2_stream0.Init.MemInc = DMA_MINC_ENABLE;
dma2_stream0.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
dma2_stream0.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
dma2_stream0.Init.Mode = DMA_NORMAL;
dma2_stream0.Init.Priority = DMA_PRIORITY_HIGH;
dma2_stream0.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
dma2_stream0.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_HALFFULL;
dma2_stream0.Init.MemBurst = DMA_MBURST_SINGLE;
dma2_stream0.Init.PeriphBurst = DMA_PBURST_SINGLE;
if (HAL_DMA_Start(&dma2_stream0, (uint32_t)&GPIOC->IDR, (uint32_t)dma_buf0, BUF_LEN) != HAL_OK)
Checking in the DMA docs, DMA2, ch6, stream 0 maps to TIM1_TRIG. Is it correct that if TRGO from TIM1 is configured correctly it will time the transfer from the GPIO register correctly?
And the next question how do I setup correctly?
Is it only to enable TRG0 output from the timer, and set TIM->DIER = TIM_DIER_UDE? And then use the correct DMA channel, in this case for TIM1_CH1_TRIG = DMA2:ch6:stream0.
And is there a simple way to get the TRG0 from the rising flank of each output from the timer TIM1_CH1 or do I need to chain another timer to generate the correct timing for the sync to DMA.
BR,
/Johannes
Solved! Go to Solution.
2025-04-29 1:49 AM
Indeed, I did not think about that.
> But I cannot find any documentation about what this exactly is
If TIMx_CHx is set to Compare (as opposed to Capture), TIMx_CHx event is generated upon a match, i.e. when TIMx_CCRx == TIMx_CNT:
(used 'F4 RM as that's what I have open right now, but in this regard all STM32 are the same).
In your case, that would be at the falling edge, which is what you don't want. As I've hinted above, you can visualize the moment of sampling yourself (okay there might be a few cycles difference between input and output, but you get the point).
What you can do is to set up a different channel TIM1_CHy of the same TIM1 timer to Compare at 0 (which incidentally is the reset setting), enable DMA from that TIM1_CHy in TIM1_DIER, and set up DMA to be triggered from that channel.
JW
2025-04-25 6:45 AM
Hello @JohannesS,
Yes, when the TRGO is set up to occur at the desired timing point (e.g., on the rising edge of a PWM signal or at a specific timer count), it will trigger the DMA to start transferring data from the specified peripheral (in this case, the GPIO input data register) to memory.
For DMA configuration, you can configure it in circular mode without restarting after each buffer is filled
To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.
2025-04-28 6:41 AM
Hi,
I'm not sure how to actually in a correct way trigger the DMA transfer.
Now every time the timer is triggered it will generate 8 active low pulses. This is done with PWM mode 2 and a repetition counter of 8. The DMA should trigger on rising flank. This also means that each time the timer is triggered a total number of 8 DMA transfers should happen.
Setting:
TIMER1: masterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
dma2_stream0.Instance = DMA2_Stream0;
dma2_stream0.Init.Channel = DMA_CHANNEL_6;
will only trigger once for each timer pulse and not 8. The documentation also seems to specify that if repetition count is set the event will only happen once when the rep counter reaches 0. How do I trigger it for each rising flank of the OC output (CH1).
If I instead setup the DMA like this:
dma2_stream0.Instance = DMA2_Stream1; // <= TIM1_CH1 instead of TIM1_TRIG
dma2_stream0.Init.Channel = DMA_CHANNEL_6;
then I get 8 transfers but I have no idea of where this transfer is triggered. It needs to be on the rising flank of the active low OC output.
BR,
/Johannes
2025-04-28 6:51 AM
> Checking in the DMA docs, DMA2, ch6, stream 0 maps to TIM1_TRIG. Is it correct that if TRGO from TIM1 is configured correctly it will time the transfer from the GPIO register correctly?
No. The TIMx_TRIG request to DMA, enabled in timer by setting TIMx_DIER.TDE, is bound to the trigger input signal to the slave-mode controller, i.e. TRGI, and not TRGO. This is unfortunately not documented, or at least I am not aware of any explicit documentation, there are only hints to this.
Nonetheless, I have no idea what do you want to accomplish. Try to draw/sketch a timing diagram.
JW
2025-04-28 7:49 AM
This is the example screenshot from a oscilloscope. Yellow not seen is the trigger signal to ADC chip. Blue is output busy signal from ADC chip. Output from ADC is 16-bit and directly connected to gpio, one full bank so GPIOC->IDR should contain the values. Target is to send 8 pulses to read ADC values from chip. This is the purple signal.
So in this case I have setup timer 1:
Slave trigger on ADC busy signal, this will generate 8 active low signals towards the ADC. This is output through TIM1_CH1 as OC using PWM. It is setup a one pulse to generate 8 output active low pulses with the help of PWM with repetition count of 8.
ADC output will be stable on rising edge of the purple timer output. The goal is to trigger DMA periph (GPIOC->IDR) -> memory in a double buffered way.
So how do I setup that the timer triggers DMA transfers. Each pulse on the timer generates 8 active low signal and I need to trigger the DMA on rising edge of that output.
Using DMA2->Channel 6, Stream0 only generates one transfer per pulse and not 8, one per repitition.
Using DMA2->Channel 6, Stream1 generates 8 transfers per pulse and I can read ADC values on the resulting buffer. However I cannot find information how this is timed since I need the DMA trigger to be at the correct flank.
BR,
/Johannes
2025-04-28 8:52 AM
The rising edge occurs on the Update event, i.e. when the timer's counter rolls over from ARR to 0. That means, that you want to trigger the DMA upon Update (TIM1_UP), i.e. use DMA2 Stream 5 set to Channel 6, and in TIM1 set TIM1_DIER.UDE.
You can visualize the DMA transfers, too: set some GPIO pin to Output, set the DMA to transfer from a buffer in memory to given GPIO_BSRR and fill that buffer in memory with alternating values which will set and clear that pin's output level.
JW
2025-04-28 11:58 PM
Hi,
Thanks for the reply. The main problem is that I cannot see that I will get an update event when the counter rolls over when I use repetitions. From the documentation:
"If the repetition counter is used, the update event (UEV) is generated after upcounting is
repeated for the number of times programmed in the repetition counter register
(TIMx_RCR) + 1. Else the update event is generated at each counter overflow."
I just tested an no I do not seem to get a transfer for each rising edge of the output when using DMA2 Stream5, ch6.
The only setting that I have used that gets me 8 transfers of DMA Stream1, ch6, which is TIM1_CH1. But I cannot find any documentation about what this exactly is and exactly where the trigger will be generated.
BR,
/Johannes
2025-04-29 1:49 AM
Indeed, I did not think about that.
> But I cannot find any documentation about what this exactly is
If TIMx_CHx is set to Compare (as opposed to Capture), TIMx_CHx event is generated upon a match, i.e. when TIMx_CCRx == TIMx_CNT:
(used 'F4 RM as that's what I have open right now, but in this regard all STM32 are the same).
In your case, that would be at the falling edge, which is what you don't want. As I've hinted above, you can visualize the moment of sampling yourself (okay there might be a few cycles difference between input and output, but you get the point).
What you can do is to set up a different channel TIM1_CHy of the same TIM1 timer to Compare at 0 (which incidentally is the reset setting), enable DMA from that TIM1_CHy in TIM1_DIER, and set up DMA to be triggered from that channel.
JW
2025-04-29 2:08 AM - edited 2025-04-29 2:10 AM
Edit (this was posted before the latest reply from JW)
As suggested above I setup the DMA to instead output to a spare gpio to visualize the timing. The buffer is just alternating 0/1.
DMA2 stream1, ch6 = TIM_CH1:
Unfortunately this shows that the timing is not correct and the timing is on falling edge instead of rising edge.
If I instead setup DMA2 stream5, ch6 = TIM_UP:
Here we get the trigger only on the last rising edge and not all. I also get an extra at the start as well.
2025-04-29 6:39 AM
Thanks for the help. After configuring up TIM1_CH3 as OC with Pulse = 0 and configuring the DMA to use Stream6, ch6 it seems to be triggering at the correct flank.
Bottom dark blue is the debug output showing the DMA trigger timing.