5

I'm using an STM32H743. I have an external clock signal coming in on a GPIO pin, and I want to very accurately measure elapsed time between each rising (or falling) edge in the external clock signal. So I set things up so that TIM4 is triggered by the external clock, and TIM5 is triggered by the internal oscillator.

I wrote an IRQ so that whenever TIM4 triggers, an interrupt runs that captures TIM5's value. It seems to work OK, but I'm wondering if I can do it through DMA to avoid all the context switching and free up the CPU. Basically I want to set up a DMA so that each TIM4 event initiates a DMA transfer that copies the TIM5 counter value to a circular buffer somewhere.

I've searched through forums and the DMA documentation but I'm hazy on whether a timer register can be a valid DMA source. I was thinking maybe I could do something like this:

hDma->PAR = (uint32_t) &htim5.Instance->CNT;
hDma->M0AR = (uint32_t) myBufferPtr;
hDma->NDTR = myBufferSize;
hDma->CR |= (uint32_t)DMA_SxCR_EN;

But I'm not sure if this can work.

Short version: Can I use the timer's CNT register as a DMA transfer source? Would it be a peripheral-to-memory transfer? Or a memory-to-memory transfer? Are there other flags I need to make this work? Or is it not possible? Or is there another STM32 feature that would make it easier to count time between pulses?

Kevin Holt
  • 775
  • 9
  • 15

2 Answers2

7

Disclaimer

I must confess that my long practical experience with STM32 by now stayed with mainstream controller families like STM32F0, STM32F3, STM32F4 and STM32L4. Therefore I'm answering based on what those controllers would offer you in your situation. The STM32H7 series is much stronger, let alone it offers several additional DMA technologies like DMA2D, MDMA and lots of other stuff that I'm not sure about. But I think a simplified answer might also help you for now, so I'm daring to write it.


Can I use the timer's CNT register as a DMA transfer source? Would it be a peripheral-to-memory transfer? Or a memory-to-memory transfer? Are there other flags I need to make this work? Or is it not possible?

I would expect this to work. I don't see a reason not to read the TIMx_CNT register in a DMA transfer.

The CNT register is definitely a peripheral address so you have to configure it as a peripheral-to-memory transfer. I believe that the peripheral/memory separation refers to the bus from which the DMA controller fetches the data (or to which bus one it delivers them) in the bus matrix implemented in every STM32.

Or is there another STM32 feature that would make it easier to count time between pulses?

Yes, there is: Many of the TIM peripherals (not all are the same) offer you a feature called "Input Capture" that connects the channel (sub-)peripheral of the TIM instance to the input and has the main part of the (same!) TIM peripheral do the internal clocking. A prerequisite of this is, that the pin you'd like to measure has a TIMx_CHy alternate function, not "only" a TIMx_ETR one.

The TIM peripherals offer a wealthy range of different configuration options - and a complicated mess as long as you haven't got used to it. As an introduction and a good overview, I recommend two application notes from ST:

Looking up those two, I found a third one you might want to check out for better precision, related to HRTIM timers:

HelpingHand
  • 1,294
  • 11
  • 27
  • Thanks again, I wanted to try it before accepting -- it works! I did the capture on TIM5 and it seemed to give a much more reliable measurement than my original code with interrupts, and it was pretty straightforward to get those capture measurements stashed away with DMA. I'll be trying HRTIM next to see if that can further improve the precision. – Kevin Holt May 01 '20 at 01:48
  • @KevinHolt - That's great! Have a lot of fun and success with the setup then! – HelpingHand May 01 '20 at 05:20
1

It is easily done using STM32CubeIDE configurator:

  1. configure timer, enable input capture channel, enable DMA (mode circular, peripheral to memory,data width word/word). Enable interrupts.
  2. Prepare buffer for storing captured counter values
  3. Start IC in DMA mode before main loop
  4. For high speed operation you may copy data from timerCaptureBuffer to timerCaptureBufferSafe inside these callbacks. For example, DMA memory to memory transfer to minimize time spent in HAL_TIM_IC_CaptureHalfCpltCallback and HAL_TIM_IC_CaptureCallback interrupts. Process adjacent captured values stored in timerCaptureBufferSafe after DMA memory to memory callback signals data is ready. You may use signaling flags so timerCaptureBufferSafe will not be overwritten.

Here is an example:

#define TIM_BUFFER_SIZE 128
uint32_t timerCaptureBuffer[TIM_BUFFER_SIZE];
uint32_t timerCaptureBufferSafe[TIM_BUFFER_SIZE];
// ...

HAL_DMA_RegisterCallback(&hdma_memtomem_dma2_stream2,
HAL_DMA_XFER_CPLT_CB_ID,
myDMA_Callback22);
// ...

HAL_TIM_IC_Start_DMA(&htim2, TIM_CHANNEL_1, uint32_t*)timerCaptureBuffer,TIM_BUFFER_SIZE);
// ...

void HAL_TIM_IC_CaptureHalfCpltCallback(TIM_HandleTypeDef *htim)
{
    HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream2,
    (uint32_t)&timerCaptureBuffer[0],
    (uint32_t)&timerCaptureBufferSafe[0],
    sizeof(timerCaptureBuffer)/2/4);
    // ...
}

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream2,
    (uint32_t)&timerCaptureBuffer[TIM_BUFFER_SIZE/2],
    (uint32_t)&timerCaptureBufferSafe[TIM_BUFFER_SIZE/2],
    sizeof(timerCaptureBuffer)/2/4);
    // ...
}
void myDMA_Callback22(DMA_HandleTypeDef *_hdma)
{
    //...
}