TPM behavior on KL05

cancel
Showing results for 
Search instead for 
Did you mean: 

TPM behavior on KL05

851 Views
Contributor V

Hi!

I am trying to generate a precisely timed series of pulses with the FRDM-KL05 board, using the TPM0 timer. This would not be a serious problem, except for the fact that the period is 1.3µs, with duty cycle varying between 350ns and 650ns. Which means I can't use interrupts to load the next pulse's duty cycle data (not enough cycles), and even without interrupts cycles are *really* scarce. I am not even sure if it is possible, but in the meantime I stressed the TPM module quite a bit and was surprised by what I found.

I am using TPM0 with channel 0 set to EPWM (MSB|ELSB are set), and a 20.971MHz TPM clock.

Here are the three main questions:

1. The channel flag (TPM0_C0SC & TPM_CnSC_CHF_MASK) gets set when I reset the counter to 0 by writing to TPM0_CNT, even though TPM0_C0V is set to 0, this happens regardless of whether the timer is enabled (CMOD bits) or not.

Is this a documentation problem or a bug?

2. I have a program that gets the timer into a state where after my pulses have been generated, TPM0_C0V is set to 0, but pulses continue to appear on the output pin. Indefinitely.


Documentation says nothing about the possibility of TPM0_C0V write *failing*. I know about the buffering (I use it, after all), but from what I understand, writing a 0 to TPM0_C0V should stop EPWM on the output pin starting with the next timer overflow.

Is there a scenario where writing a 0 to TPM0_COV can fail?

3. I could not find a procedure to predicably start the timer and generate pulses relying on the CnSC_CHF flag (TPM0_C0SC & TPM_CnSC_CHF_MASK). My first attempt was to try busy-waiting on the TPM_CnSC_CHF flag: "while(!(TPM0_C0SC & TPM_CnSC_CHF_MASK));". The problems with this:

a) resetting the timer sets the CHF flag for channel 0,

b) if I reset the timer and the CHF flag for channel 0 while the timer is disabled, enabling the timer will still set the CHF flag,

c) clearing the CHF flag after the timer starts running means that I'm introducing a delay where I'd rather not,

d) this is compounded by the fact that I have to reset the CHF flag *twice* (if I understand the docs correctly) to make sure it is really reset.

So, what would be the procedure to predictably start the timer in a known state, with CHF cleared, C0V set to 0, so that I can write the new C0V value within the first period and continue on subsequently?

To give you a rough idea of the code we're talking about, here's one of the C versions (I subsequently rewrote this in assembly):

  TPM0_C0SC |= TPM_CnSC_CHF_MASK;

  TPM0_C0SC |= TPM_CnSC_CHF_MASK;

  TPM0_CNT = 0;

  for(i=0; i < count; i++) {

      byte = data[i];

      bit = 8;

      do {

          bit--;

          TPM0_C0V = timings[(byte >> bit) & 1];

          while(!(TPM0_C0SC & TPM_CnSC_CHF_MASK));

          TPM0_C0SC |= TPM_CnSC_CHF_MASK;

      } while(bit);

  }

  TPM0_C0V = 0;

This does not work reliably, for a number of reasons, but I'm posting it to make it easier to understand what I'm trying to do.

Labels (1)
0 Kudos
11 Replies

41 Views
NXP Employee
NXP Employee

Hi Jan,

To answer your questions:

1) I haven't heard of any bugs or documentation issues on this functionality.  Will look into this. 

2) Haven't heard of anything about this either, so I will need to check this as well. 

3) In this scenario, it looks like you are simply trying to gradually decrease the pulse width of your pulses?  If that's the case, I would recommend you use interrupts if you can.  If you cannot, then do you have DMA channels available?  DMA could also possibly meet this need. 

Will get back to you when I have more,

Chris

0 Kudos

41 Views
Contributor V

So, on (3) — I need to generate a signal where 1 and 0 are encoded using different duty cycles with in a signal with a period of 1.3µs. Interrupts aren't really an option here, I think, because given that just entering the ISR costs 12 cycles and 350ns is 7 cycles at 20MHz, I won't manage to keep up. And from what I understand, even if you run the core at 48MHz, that won't necessarily speed up peripheral access. I'm not sure if it's even possible to keep up with a loop that busy-waits on the TOF flag (or CHF). I have code that kind-of works (with a very tight cycle margin), but I don't consider it reliable enough for longer bit strings.

I think it should be doable with DMA, with a ping-pong scheme, one channel linked to another — that is exactly what I plan to try next. But I would still like to understand the TPM peripheral better, hence my questions above.

0 Kudos

41 Views
NXP Employee
NXP Employee

Ok.

So, I have looked at #1, and I haven't been able to duplicate what you describe.  I do see that if my Channel value (C0V) is 0 and the CNT is zero, then the channel flag will be set almost immediately after enabling the clock to the timer (CMOD != 0x00).  Can you check that this is actually what's happening?  Have you seen this behavior on multiple parts?

For #2, can you provide a little more detail on how you get into this state?  For example, what is the sequence of events that gets you into this state?  Does this also happen on multiple parts?  Is it reliably repeatable?

0 Kudos

41 Views
Contributor V

Regarding #1: so, what is the expected behavior of the channel flag when C0V is 0, MSB|ELSB are set, and CNT is reset? I expected the CHF flag to never be set until I write something other than 0 to C0V. In other words, I expected to be able to stop the timer (write 00 to CMOD), clear the CHF, reset CNT, start the timer (write 01 to CMOD) and see the CHF bit as 0.

I will reproduce this again tomorrow if needed, but perhaps my expectations are simply wrong (or I misunderstood the documentation, which doesn't go into much detail on CHF behavior)?

Regarding #2: I'm afraid I can't provide the exact sequence of events, and I'm pretty sure it is timing related, because it happens on only some versions of my code (variations on the code that I pasted in my question above). What I could do though is try to reproduce this on a FRDM-KL25. And I could narrow it down and send you a complete project that does this on my FRDM-KL05. But before I invest more time into it I wanted to confirm that this is not supposed to happen. Basically, I'm trying to understand whether if I have a function where the last instruction is  TPM0_C0V = 0 it is possible for pulses to be generated indefinitely as if the write wasn't there.

0 Kudos

41 Views
NXP Employee
NXP Employee

[You wrote] Regarding #1: so, what is the expected behavior of the channel flag when C0V is 0, MSB|ELSB are set, and CNT is reset? I expected the CHF flag to never be set until I write something other than 0 to C0V. In other words, I expected to be able to stop the timer (write 00 to CMOD), clear the CHF, reset CNT, start the timer (write 01 to CMOD) and see the CHF bit as 0.

[Response] So, the expected behavior is that the CHF is set when the Channel value (CnV) equals the CNT. 

[You wrote] Regarding #2: I'm afraid I can't provide the exact sequence of events, and I'm pretty sure it is timing related, because it happens on only some versions of my code (variations on the code that I pasted in my question above). What I could do though is try to reproduce this on a FRDM-KL25. And I could narrow it down and send you a complete project that does this on my FRDM-KL05. But before I invest more time into it I wanted to confirm that this is not supposed to happen. Basically, I'm trying to understand whether if I have a function where the last instruction is  TPM0_C0V = 0 it is possible for pulses to be generated indefinitely as if the write wasn't there.


[Response] As for this one, this is not expected behavior.  If somehow the MSA bit inadvertently got set as well, then you could end up with a condition where the module will generate a pulse indefiinitely.


41 Views
Contributor V

After investigating problem (2) described above (a write of a 0 to TPM0_C0V not taking effect), I managed to capture the event in a debug session. I am trying to attach screenshots, hopefully this will work.

assembly-1.png

TPM0_C0V-1.png

cpu-regs.png

Note that the breakpoint is set right after the write executes. So we are looking at the state right after I wrote r2 (0) to the location at r3+0x10, which is 0x40038000 + 0x10, or 0x40038010. That's TPM0_C0V, and all the previous writes (I write to it every microsecond or so) have happily succeeded. But this one does not, and TPM0_C0V remains set to 8 (its previous value).

Note that TPM0_CNT at this moment equals TPM0_MOD, we are about to overflow. Could this have something to do with the write failing?

If I insert additional instructions in the code above the breakpoint, the problem disappears. The write succeeds and no pulses are generated after the write.

TPM0 runs at bus speed, 20MHz in this case (you can see the divider is set to 1). So does the core.

This setup reproduces the problem reliably, every time. Without the debugger attached the result is endless pulses (length 8) from the TPM0 channel 0 output pin. Well, endless at least for another 50-200µs, which is the length of the busy-waiting delay loop that sits right after the return from the function displayed in the screenshot.


0 Kudos

41 Views
NXP Employee
NXP Employee

There shouldn't be anything that causes a write to a channel value to fail.  The fact that the counter is about to overflow may cause the timer to not update immediately (which is currently already described in the reference manual in section 31.4.8). 

Are you able to share your project?  If not on this Community post, would you be able to if I provided you with my email address, would you be able to?

Also, can you try to duplicate this scenario with a non-zero value?  How about on another channel (like say TPM0 channel 3)?

0 Kudos

41 Views
Contributor V

There shouldn't be anything that causes a write to a channel value to fail.  The fact that the counter is about to overflow may cause the timer to not update immediately (which is currently already described in the reference manual in section 31.4.8). 

Yes, I'd expect that, and that is fine. One more timer period with the old C0V value is something I expect. What I do not expect is that the write never makes it to C0V. I am out of ideas -- the only remaining idea I have is that if one writes to C0V two times during a single timer period, only the first write makes it?

Are you able to share your project?  If not on this Community post, would you be able to if I provided you with my email address, would you be able to?

Please contact me directly, I have a minimal project that exhibits the issue prepared for you.

0 Kudos

41 Views
NXP Employee
NXP Employee

Hi Jan,


So I finally had a chance to jump into this one.  I believe understand what's going on.  The problem is that when you try to write 0 to the channel value register (C0V), you have already written to C0V once during that period.  In this case, the second write is ignored (most of our peripherals would take the second one but the TPM is different). This is why we always suggest using interrupts or DMA.  Both of these mechanisms would use the module flags to update the channel value register and therefore, your values would always be in sync.  You would only get one write to the channel value register per period.


This is already documented in the KL26 reference manual (which has the same TPM module) but the updated information has not made it into the KL05 reference manual yet.

0 Kudos

41 Views
Contributor V

Thanks, this explains the issue I was seeing. It definitely deserves a mention in the documentation.

0 Kudos

41 Views
Contributor V

[Response] So, the expected behavior is that the CHF is set when the Channel value (CnV) equals the CNT.


Thank you. I guess I'm seeing the expected behavior, then. It isn't useful at all, but at least it is expected :-)


As for the other problem, I will try to narrow it down and report it, then.


thanks,

--J.


0 Kudos