Skip to main content
Ludovic Durand-Texte
Associate II
March 27, 2019
Question

Alternative use of timer output compare (pwm) and input compare on the same channel

  • March 27, 2019
  • 13 replies
  • 3637 views

Hi,

I use a timer channel and a DMA stream to generate a PWM signal.

At Transfer Complete interruption, I swap the channel to input capture mode to get an answer from external device, using the same DMA.

At transfer (reception) complete, I swap again to output compare mode.

It works, except that after having received data and reconfigured the timer channel to output compare, the first two data loaded by the dma into the CCR are not taken in account and the timer channel output stays 1 for two periods.

(It does the same if under debug I manually disable the DMA and force a value into the CCR)

The peripheral don't understand the command and of course do not send any answer. But after that, the timer channel is set again to output compare for next cycle and the output works again normally for one cycle.

So one in two of the cycle works : I send the command, I read the peripheral answer, but I can't send a right command just after having received an answer.

If I configure the external device to do not send answer, it works perfectly, I can send data, turn in input compare, later turn again in output compare and send the data correctly.

So the problem occurs only when the external device send an answer and a TC IT occurs.

To set in output compare mode I use TIM_OCxInit() and TIM_OCxPreloadConfig()

To set in input compare I use TIM_ICInit()

I also change the direction of DMA

I do miss something but do not know what.

Can anybody help?

Thanks in advance.

Ludovic

This topic has been closed for replies.

13 replies

Ludovic Durand-Texte
Associate II
March 27, 2019

I forgot to say that I tried to deinit (TIM_DeInit) and reinit (TIM_TimeBaseInit + TIM_OCxInit) the timer after having received the "answer" but unsuccessfully, the problem stays the same.

Tesla DeLorean
Guru
March 27, 2019

You're probably going to want to get more familiar with the TIM peripheral at a register level, and get the transition down to a minimum without tearing everything down and reconfiguring.

Ideally you might construct the capture side to use a second channel in indirect more ie CH2 using CH1 input indirectly, so you can just disable the CH1 output as necessary.

Perhaps also look at the protocol at a wave form level and determine if there is a better or more effective way of implementing it. Sounds like some 1-wire protocol

Tips, Buy me a coffee, or three.. PayPal Venmo (See Profile) Up vote any posts that you find helpful, it shows what's working..
Ludovic Durand-Texte
Associate II
March 27, 2019

Hi, thanks for your response.

I already use the TIM peripheral at register level inspired from sdtLibrary and of course follow the timing to stay in the needed response time. Unfortunately, I cannot use the CH2 in indirect mode because it is already used (in fact I use the four channels), and I cannot change the protocol to more than one wire...

The thing is I do not understand why the timer output stays locked to 1 for two cycles, and only when data has been received before.

waclawek.jan
Super User
March 27, 2019

As Clive said, a lot depends here on the details, so you need to understand very well what you are doing.

> The thing is I do not understand why the timer output stays locked to 1 for two cycles, and only when data has been received before.

I can't explain 2 cycles without seeing actual code (and maybe not even after that), but for example the CCR preloading sometimes results in effects which may be really hard to understand.

JW

Ludovic Durand-Texte
Associate II
March 29, 2019

Hi,

In fact, I have found my problem... when I was swapping from input to output mode, I was restarting the timer after the dma.

I do not understand exactly why, but starting the dma after the timer has solved my problem.

I was thinking that it could be possible to configure and start the dma and then enable the timer at the time I want it to start while filling the CCR data from dma. If it is possible, I have not found the solution for it to work at first output.

Thank you for your suggestions

Ludo

waclawek.jan
Super User
March 29, 2019

As it's been said, lot depends on the very details, and can't really be discussed without a complete description of the content of relevant registers at the key moments.

JW

S.Ma
Principal
March 30, 2019

True, anyway, if you change from output to input manually using SW interrupt, why using DMA in the first place, because you might need both change GPIO.MODER (unless open drain with pull-up?) AND DMA direction bit. Then just do it purely by SW, in this case you'll have to calculate the worst case interrupt delay jitter due to other's and check the duration: for example when turning from output to input, there will be an ISR dead time for monitorint the incoming edge. A min dead time between edges.

Ludovic Durand-Texte
Associate II
March 30, 2019

I use dma because I have four charnels to manage synchroniously in both ways and I need up to 1,2 Mbit data transfer with less than 80ns of error. Only a timer and dma can do.

DN.4
Visitor II
March 9, 2022

Hi @Ludovic Durand-Texte​ , I know its been a while since your post but I'm having the same issue as you had.

Can you please share the sequence you did for the input and output change? The functions you use and the configuration needed.

It will help me a lot..!

Thanks you!!

Ludovic Durand-Texte
Associate II
March 9, 2022

Here is a quick and dirty code extract

Was a long time ago, I'm sure I could write a better code today :)

Hope it will help

Regards

setMode(mode) function :

//disable DMA IT

dmaStream->CR &= ~DMA_IT_TC;

// stop DMA

dmaStream->CR &= ~DMA_SxCR_EN;

// wait for EOT

while (dmaStream->CR & DMA_SxCR_EN){}

// clear timer DMA IT

tim->DIER &= (uint16_t) ~(dmaDef->timerDMA_IT);

// stop timer

tim->CR1 &= (uint16_t) ~TIM_CR1_CEN;

tim->SR = 0;

if (mode==READ)

TIM_ICInitStructure.TIM_Channel = hw->timerChannel;

TIM_ICInit (timer, &TIM_ICInitStructure);

// set DMA dir

dmaStream->CR &= ~(DMA_SxCR_DIR | DMA_SxCR_MSIZE | DMA_SxCR_PSIZE);dmaStream->CR |= (DMA_DIR_PeripheralToMemory | DMA_MemoryDataSize_HalfWord | DMA_PeripheralDataSize_HalfWord);

dmaStream->NDTR = FRAME_LENGTH;

// clear pending DMA IT

DMAy->HIFCR = (uint32_t)(DMA_IT & RESERVED_MASK);

//or

//DMAy->LIFCR = (uint32_t)(DMA_IT & RESERVED_MASK);

// enable stream

dmaStream->CR |= DMA_SxCR_EN;

// set timer dma interrupt

timer->DIER |= timerDMA_IT;

// start timer(s)

timers[t]->ARR = 0xFFFFFFFF;

timers[t]->CNT = 0;

// enable timer

timers[t]->CR1 |= TIM_CR1_CEN;

else

if (mode==WRITE)

// disable stream

dmaStream->CR &= ~((uint32_t) DMA_SxCR_EN);

while (dmaStream->CR & DMA_SxCR_EN){}

// set DMA dir

uint32_t tempcr = dmaStream->CR;

tempcr &= ~(DMA_SxCR_DIR | DMA_SxCR_MSIZE | DMA_SxCR_PSIZE);

// DIR and SIZE

tempcr |= (DMA_DIR_MemoryToPeripheral | DMA_MemoryDataSize_Word

  | DMA_PeripheralDataSize_Word);

dmaStream->CR = tempcr;

// frame length (NOTE : one data added to set the timer output to zero

dmaStream->NDTR = FRAME_LENGTH + 1;

// init timer

TIM_OCInit (timer, &TIM_OCInitStructure); // see the structure below

// enable preload 

TIM_OCPreloadConfig (timer, TIM_OCPreload_Enable);

// prepare ARR

timers[t]->ARR = ???

// eventually

TIM_CtrlPWMOutputs (timer, ENABLE);

if (mode changed)

// set CNT

timers[t]->CNT = timers[t]->ARR - 1

// finish to prepare DMA (?? done twice ?)

dmaStream->NDTR = FRAME_LENGTH + 1; // +1 because zero sent at last

// connect dma to timer capture compare

tim->DIER |= timerDMA_IT;

DMA_ClearITPendingBit (dmaStream, clearFlags)

// enable DMA IT

DMAStream->CR |= DMA_IT_TC;

END OF SET MODE

to start the out transfer :

// start DMA

dmaStream->CR |= DMA_SxCR_EN;

// start timer

timers[t]->CR1 |= TIM_CR1_CEN;

// in EOT IT

DMAStream_IRQHandler ()

DMA_ClearITPendingBit (DMAStream, DMAStreamClearFlags)

if (mode==WRITE)

// wait for all DMA EOT

while (dma[esc].dmaStream->CR & DMA_SxCR_EN){}

blablabla

setMode(READ)

if (mode==READ)

blablabla

setMode(WRITE) // see upper to start the out transfer

// timer init structure

   TIM_OCStructInit (&TIM_OCInitStructure);

   TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;

   TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;

   TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;

   TIM_OCInitStructure.TIM_OutputNState =

   TIM_OutputNState_Disable;

   TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;

    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low or TIM_OCPolarity_High;

   // init TIM_ICInitStructure

   TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_BothEdge;

   TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;

   TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;        

DN.4
Visitor II
March 10, 2022

Hi @Ludovic Durand-Texte​ , I am very appreciate your quick answer!! :)

It helps me a lot.

I still have a problem with the reading, the write looks OK on Oscilloscope, and I see the report return but the buffer is not update with the data.

Did you use the same buffer for read and write? Which functions did you use to read the buffer?

Another question, did you change the GPIO (You used for the Timer) from input to output respectively?

Thanks again !

Ludovic Durand-Texte
Associate II
March 11, 2022

Yes, I do !

Unfortunately, I cannot use the DMA interrupt because the read frame does not have a fixed length... But I know the time to wait. So I wait long enough before use the data in the buffer.

About GPIO, it is configured as AF (alternate function) mode with the output type set as PullUp or PullDown depending of inverted or not inverted signal. Because AF is used, no need to change GPIO config.

When direction changes, I just change the timer configuration. I use two prepared init structures:with the associated timer init functions:

for output: TIM_OCxInit(TIM_OCInitStructure)

for input: TIM_ICInit (TIM_ICInitStructure)

Hope it will help !

Best regards

Ludovic Durand-Texte
Associate II
March 13, 2022

When I say, "yes I do​", it is for the use of a same buffer, and for the gpio, the answer is no I do not change the config :)

DN.4
Visitor II
March 16, 2022

Hi @Ludovic Durand-Texte​ , I am very grateful for your help and support!!

Following your comment I understand that the GPIO needs to stay in alternate function mode for both cases input and output.

But unfortunately I am still having problem with the input transfer (peripheral to memory).

When you are changing the TIM to IC and change the DMA direction to peripheral to memory what gives the trigger for the DMA buffer to keep the input data?

For the output direction I use an interrupt but for the input I am a little bit confuse.

Thanks again!