Pulse generation using STM32 - Page 1
Forum section: electronics / general

author: VanitarNordic
author_rank: Frequent Contributor
timestamp: July 15, 2018, 08:48:56 pm
content: Hi,I want to generate a pulse something like this and use it as a trigger internally, I mean I don't want to have this pulse on MCU pins and therefore I should use Timers. I want to use it as an ADC trigger. What should I do?

author: mbless
author_rank: Regular Contributor
timestamp: July 15, 2018, 11:37:41 pm
content: One option is a timer in circular dma mode to fill the CCRx register.

author: VanitarNordic
author_rank: Frequent Contributor
timestamp: July 16, 2018, 03:51:47 am
content: Quote from: mbless on July 15, 2018, 11:37:41 pmHow can I check that the configurations work? toggling a PIN inside DMA interrupt?

author: mbless
author_rank: Regular Contributor
timestamp: July 16, 2018, 12:53:26 pm
content: Scratch that. I don't think you can use my method for internal triggering. It is used to drive variable duty cycle PWM, so you'd have to route an output pin to the ADC external interrupt.

author: newbrain
author_rank: Super Contributor
timestamp: July 16, 2018, 01:24:18 pm
content: A way to generate repeating bursts is using two timers: the first one triggers the second, set in one pulse mode with a repeat count equal to the number of pulses in the burst.See thetimer cookbook, at chapter 6.2.The ADC should be triggered by the second timer CCR events, no need to use an actual output pin (using the EXTSEL bits in the ADC configuration registers)Quote from: mbless on July 16, 2018, 12:53:26 pmThe solution proposed looks quite interesting, and would need just one timer (to the expense of a DMA channel, though).Are you sure it would not work? (I'm unable to prototype it at the moment)After all, if a CC event is generated at the right time, why shouldn't it work as a trigger?

author: mbless
author_rank: Regular Contributor
timestamp: July 16, 2018, 01:34:55 pm
content: Quote from: newbrain on July 16, 2018, 01:24:18 pmYour two timer solution looks good.I used my solution for a peak-hold PWM driver where it has to change the duty cycle (CCRx) for each period. The problem I see in the current case is that when you don't want to trigger the ADC, CCRx is 0 but a CC event is still generated which would trigger the ADC.

author: VanitarNordic
author_rank: Frequent Contributor
timestamp: July 16, 2018, 05:36:29 pm
content: QuoteYou mean Master/Slave? I mainly use CubeMX, I'll so appreciate if you explain by that. thank you

author: mbless
author_rank: Regular Contributor
timestamp: July 16, 2018, 10:37:26 pm
content: Quote from: VanitarNordic on July 16, 2018, 05:36:29 pmDid you read Section 6.2 of link newbrain gave? It explains what settings to use, most of which should be in CubeMX.

author: newbrain
author_rank: Super Contributor
timestamp: July 17, 2018, 07:24:06 am
content: Quote from: VanitarNordic on July 16, 2018, 05:36:29 pmYes, you need to use one general purpose timer as Master and one advanced-control timer as Slave.IIRC from other threads you've started, you are using an STM32F4xx so TIM1 as advanced-control and TIM2 as general purpose should be available.The need for an advanced control slave is due to repetition counter (TIM1_RCR) support:Code:If the repetition counter is used, the update event (UEV) is generatedafter upcounting is repeated for the number of times programmed in therepetition counter register plus one (TIMx_RCR+1).that, together with with the one pulse mode (OPM bit in TIM1_CR1):Code:Bit 3 OPM: One pulse mode0: Counter is not stopped at update event1: Counter stops counting at the next update event (clearing the bit CEN)and the use of a CCR will give us exactly what you need.Figure 37 in the link I gave is very clear.For CubeMX, see the attached pictures: ITR1 (trigger from TIM2) has to be selected as trigger source in TIM1, One Pulse Mode set, and update event as TRGO in TIM2.The ADC must be triggered by the CCR1 of TIM1.Up to you to set values/pre-scalers/interrupts etc. etc...Of course other alternative configurations can be used, e.g. with TIM8 you don't need to set a CCR register and can directly use the update event to trigger the ADC...The STM32 timers are a bit unfriendly, but definitely flexible and powerful.HTH

author: VanitarNordic
author_rank: Frequent Contributor
timestamp: July 18, 2018, 12:03:30 pm
content: my intention of doing this is to capture a specific part of the input signal by the ADC and just sample that part. I tried to mach the ADC trigger timing with that specific signal part. I don't now if it is a good method or not

author: VanitarNordic
author_rank: Frequent Contributor
timestamp: July 18, 2018, 01:06:44 pm
content: Besides, I think this is not applicable as a trigger for the ADC in DMA mode and it is just for the ADC regular conversions.

author: newbrain
author_rank: Super Contributor
timestamp: July 18, 2018, 01:19:08 pm
content: Quote from: VanitarNordic on July 18, 2018, 12:03:30 pmDepending on the sampling rate and the burst repetition rate, there are many different ways.For example, one could use DMA in circular mode (or interrupts, if the sampling rate is low enough) tocontinuouslyfill a buffer large enough for your needs (double the needed samples?), then when the "interesting" part of the signal arrives, just DMA them out of the buffer in your working memory.If done right, latencies can be zero or even negative, and the burst trigger can be anything (timer, external pin, analogue watchdog, SW etc.): this is a bit how a DSO work.Quote from: VanitarNordic on July 18, 2018, 01:06:44 pmSetting the DMA bit will generate a DMA request at the end of conversion, regardless of the ADC trigger source.But I might have misinterpreted your statement.

author: VanitarNordic
author_rank: Frequent Contributor
timestamp: July 18, 2018, 01:53:50 pm
content: QuoteActually I want the ADC in the highest sampling rate, 2.4Msps for each channel or around 0.5uS for one conversion. The question with your method is that if we run DMA continuously to fill the buffer (it will fire a DMA interrupt ant the end of all conversions), then I would not know which element of this buffer refers to the start of the interesting part or which element shows the end. for example if we have a buffer with the size of 1000, then start is which one 0..999?I think the ADC of DSO also work continuously except we limit the conversion boundaries manually by knobsQuoteI think triggering for the DMA should be something like a clock, not a rising edge to be used as a switch for conversion start, but in regular conversion, one pulse trigger will handle one conversion. I have tested these many times that the reason I reached to this conclusion that I have to provide bursts in high frequency if I want to trigger DMA conversions

author: newbrain
author_rank: Super Contributor
timestamp: July 18, 2018, 02:49:59 pm
content: Quote from: VanitarNordic on July 18, 2018, 01:53:50 pmWhen the trigger arrives, one can read the DMA_SxNDTR register to know the offset to use in the circular buffer.Of course, as you are running the ADC at the maximum speed timing is critical.Quote from: VanitarNordic on July 18, 2018, 01:53:50 pmThe DMA is actually triggered by the ADC, but yes, you'll get one DMA request per conversion!Since you intend to run the ADC at the maximum rate, if the needed samples are less than or equal to 16, you could simply program a regular sequence with the needed channel repeated n<=16 times, set the SCAN bit in ADC_CR1 and collect the samples with DMA. No need for double timers or other complications, just trigger the scan when it's needed and use either the conversion complete interrupt (with EOSC set to 0) or (better, in this case) the DMA transfer complete interrupt to signal the end of the burst.

author: VanitarNordic
author_rank: Frequent Contributor
timestamp: July 18, 2018, 03:25:46 pm
content: QuoteThank you. this method sounds the best actually no one knew about this and you are the first person who noted that. By this, I can match a timer Interrupt with the starting point of the interesting part and get the array element of the DMA buffer which refers to that. The delay of the interrupt is around 3 to 4uS which is okay for this application.may I ask you how can I read the value of the DMA_SxNDTR register? is there any HAL function for that?

author: newbrain
author_rank: Super Contributor
timestamp: July 18, 2018, 03:42:13 pm
content: Quote from: VanitarNordic on July 18, 2018, 03:25:46 pmIn stm32f4xx_hal_dma.h:Code:#define __HAL_DMA_GET_COUNTER(__HANDLE__) ((__HANDLE__)->Instance->NDTR)Or, in stm32f4xx_ll_dma.h:Code:__STATIC_INLINE uint32_t LL_DMA_GetDataLength(DMA_TypeDef* DMAx, uint32_t Stream)But, really, I would not bother!Doing something as:Code:DMA2_Stream1->NDTR;is even clearer...Don't disregard my last proposal, though: if suitable, it's the one with the simpler code and the lighter MCU load (CPU and DMA).

author: VanitarNordic
author_rank: Frequent Contributor
timestamp: July 18, 2018, 04:22:36 pm
content: May I ask you what should I write in the argument ofCode:__HAL_DMA_GET_COUNTER?The used DMA is DMA2_Stream0Besides, in your short-code, I assigned like this:Code:result = DMA2_Stream0->NDTRwhich result has defined as unit32_t somewhere.is it correct?

author: newbrain
author_rank: Super Contributor
timestamp: July 18, 2018, 06:08:56 pm
content: You're welcome to ask!__HAL_DMA_GET_COUNTER takes a DMA handle as argument.If you let CubeMX generate the code for you, there's probably something either in main.c or dma.c such as:Code:DMA_HandleTypeDef hdma_adc?; // ? is the number of the ADC peripheralFrankly, I always used the direct register access, as it's also faster (and here you need to be careful with timings).As you correctly point out, you need to assign it to an uint_16 (the register is 16 bits wide) or uint_32.Remember that NTDR gives you the number ofremainingDMA transfers.

author: VanitarNordic
author_rank: Frequent Contributor
timestamp: July 18, 2018, 07:18:31 pm
content: Thank you very much.the code that worked was this:Code:result = __HAL_DMA_GET_COUNTER(hadc1.DMA_Handle);which handles the same results as your short-code, but as I analyze the sampled signal part, it is not in harmony with expectations. I check the timings like this: one GPIO PIN state change inside timer interrupt and another PIN state inside DMA interrupt.Code:void TIM3_IRQHandler(void) {HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, 1);HAL_TIM_IRQHandler(&htim3);}void DMA2_Stream0_IRQHandler(void) {HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, 0);HAL_DMA_IRQHandler(&hdma_adc1);}

author: VanitarNordic
author_rank: Frequent Contributor
timestamp: July 18, 2018, 07:29:08 pm
content: another weird thing is that as much as change the buffer size, this DMA counter value is identical (it says 40) which I think it should not be. besides any value for the buffer which is not a coefficient of 100 (100, 200, 300 ...) makes this number and timing pulse unstable. I mean if I put 100, it is okay, but if I put 120, it makes it unstable

author: newbrain
author_rank: Super Contributor
timestamp: July 19, 2018, 09:09:54 am
content: Quote from: VanitarNordic on July 18, 2018, 07:29:08 pmWithout knowing the rest of the setup, it's a bit difficult to diagnose...Given that the start of the sampling window is periodic, I can expect that for some values of the buffer length you'll always get the same results from NDTR, while for others lengths that are not a divisor of the total samples in one full period results will vary.

author: VanitarNordic
author_rank: Frequent Contributor
timestamp: July 19, 2018, 10:29:52 am
content: QuoteThank you for the response.I attached the code. it is simple. a few configurations which DMA fills the buffer and UART sends the average value to the serial in a while loop. as the GPIO pulse confirms (rising in the timer interrupt, falling in the DMA interrupt) it must read the interesting part which starts from 40 in the buffer. but in practice it does not.

author: VanitarNordic
author_rank: Frequent Contributor
timestamp: July 19, 2018, 10:46:11 am
content: if it still did not work, I have to cut the interesting part with an analog switch and run the ADC continuously which still I have to know where is the start point, but in this case, anywhere except signal will have 0 or less than 10 in ADC values, if we neglect noises. But still it is not a good solution you know. it should be easier and straightforward than this. i don't know what is the problem

author: newbrain
author_rank: Super Contributor
timestamp: July 20, 2018, 04:06:59 pm
content: I did not have much time to check your code (you might know about this "real life" thing that tends to interfere), and I'm going to be up in the mountains for a week without internet in a few hours...Some things I noticed:The loop in main() will take longer to execute than interval set on TIM3 (I assume that's your timebase).TIM3 interval = (60000*10)/120 us = 5000 us = 5msUART transmission time (at 9600bps, even ignoring all the rest and the HAL overhead) = (5+2) / 960 s ~= 7.3msThe main loop is completely missing any synchronization with TIM3, and is reading samples in a fixed position.I have some problems in understanding how you intend to synchronize the sampling windows with an external waveform, but this is up to you.For the first point, increasing the UART speed is more or less the only solution: I usually find no problems with 115200bps or even higher, when transmitting. Using the HAL for receiving is an exercise in frustration.For the second point, you could, in the TIM3 ISR (be wary of the possible latency that will introduce some jitter):Read the NDTR register in a volatile variable (you might even use another DMA channel triggered by TIM3 to do this, to reduce/avoid the jitter)Stop the ADCSet a flag to tell the main loop that data is readyIn the main loop:Wait for the flagRead and elaborate the buffer from (buffersize - NDTR) backward for the needed number of samples (modulo buffersize)Restart the ADC: according to the DS it just takes 2-3us, if you bypass the HALReset the flagBut since the number of samples seems to be less than 256, why not use the two timers solution (and DMA) I originally proposed?

author: VanitarNordic
author_rank: Frequent Contributor
timestamp: July 20, 2018, 04:25:52 pm
content: QuoteThe TIM3 interrupt is in harmony with the start point of the input signal (start of the interesting part of the input signal). Therefore it would be the start-point for ADC conversions or ADC DMA counter checkpoint.I have set the buffer length to cover the whole interesting part. therefore when the ADC DMA buffer gets filled, it raises a DMA interrupt. Therefore inside the DMA interrupt, I put another GPIO PIN change to verify the conversions tail. on the oscilloscope, this square pulse should match with the interesting part of the signal, therefore I can be sure timings are correct.QuoteI have to burst the ADC with 30MHz bursts (as datasheet says) to achieve its maximum channel sampling which is not possible with timer triggering and burst method. The timer works at 60MHz itself.

author: newbrain
author_rank: Super Contributor
timestamp: July 20, 2018, 06:03:44 pm
content: Quote from: VanitarNordic on July 20, 2018, 04:25:52 pmI think you are under a misconception: 30MHz is the ADC clock, that you derive from the clock tree.This must be always running, no need to turn it on and off, and is used to keep the internal ADC machinery running.It's not the ADC sampling rate (but of course it's related!).Then, you can trigger the ADC with the TRGO (from update or compare events) of a timer, or a choice of other methods.The maximum frequency this can be done cannot exceed 2MHz, if using a single ADC (see table 67 in the F407 datasheet), less if using an external trigger, more if interleaving two or three ADCs.So the two timer solution is still the best one.

author: VanitarNordic
author_rank: Frequent Contributor
timestamp: July 20, 2018, 06:29:19 pm
content: Oh yes, thank you very much.Do you confirm my method to confirm the timing? I mean GPIO inside TIM ISR and DMA ISR?I will check and fix UART delays as you guided, and maybe I would catch the correct values.Anyway for 100 conversions (buffer), do you think how many pulses would I need to include in each burst?