cancel
Showing results for 
Search instead for 
Did you mean: 

Possible BUG: CubeIDE TIM1 CH1N PWM F411 Low Layer

JSzem.1
Associate II

There is strange behaviour with CH1 and CH1N in TIM1 (STM32F411) when using CubeIDE and Low Layer.

If you set CH1 in PWM mode 1 it works after:

LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH1);

LL_TIM_EnableCounter(TIM1);

If you set CH1N in PWM mode 1 you have to additionally Enable Output by:

LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH1N);

LL_TIM_EnableCounter(TIM1);

LL_TIM_EnableAllOutputs(TIM1); 

You can also Enable CH1N output by:

TIM_BDTRInitStruct.AutomaticOutput = LL_TIM_AUTOMATICOUTPUT_ENABLE;

TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM2;

It works only for PWM2 not for PWM1.

This is only an output problem. Timer and interrupts work normally.

6 REPLIES 6
berendi
Principal

What does the documentation say, how should these functions work?

JSzem.1
Associate II

These functions enable particular channel, counter and outputs. But there is the difference between Channel 1 and Channel 1N.

Timer TIM1 works the same for Channel 1 and Channel 1N (interrups, events) but the output (physical signal on pin) doesn't work in the same way.

There is also the difference between PWM mode 1 and PWM mode 2 in according to output.

If you do not use: LL_TIM_EnableAllOutputs(TIM1); the Channel 1 works but the Channel 1N doesn't work.

But if you use PWM mode 2 Channel 1N works without LL_TIM_EnableAllOutputs(TIM1).

Additionally, the subject of configuration Chanel 1N is not so easy. You have to use Channel 1 instead of Channel 1N as an argument for ALMOST all functions!

There is one exception - LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH1N).

It looks like a bug. Channel 1 and Channel 1N work in a different manner.

Was that the intention of the creators?

For me it is confusing.

berendi
Principal

If it does not work according to the documentation, then it is a bug.

If you have read the documentation, and can't figure out how they work, then the documentation is inadequate.

That a function should rather work that way because some other function works that way is not a valid argument.

> Was that the intention of the creators?

To provide a wrapper function for setting every bitfield in every hardware register, because the HAL functions are inadequate, and some users have the notion that register names should not appear in the application code.

> For me it is confusing.

Me too. I could not figure it out yet why do these wrapper functions exist at all.

Some people argue that LL functions have names that are easier to remember and more descriptive as register names. You have provided a perfect counterexample.

Read the TIM1/TIM8 functional description chapter of the reference manual, I am sure there is an illustrated description of a use case similar to yours which explains what you should set in what registers, and why.

Then replace the LL functions in your code with direct register reads, writes and bitwise operations. This will make future maintenance easier, because register and bitfield names can be directly looked up in the reference manual.

If it still does not work, post the contents of the registers, or the initialization code with proper register and bitfield names that can be found in the reference manual, so that other developers like me could look up what is going on, without having to work through multiple levels of definitions in the LL headers.

JSzem.1
Associate II

Thank you berendi for you answer.

Maybe it is bad idea but I want to learn LL. I think this library is quite simple and quite fast.

It could be helpfull.

For now I see two problems:

  1. Bugs - time consuming for developer.
  2. Changes in LL - very dangerous for the project.

But I believe that if we will report the bugs and STM will eliminate the bugs, this library will be useful.

berendi
Principal

What bugs? Most LL functions just set a single bit or a bitfield in a register. There are even some macros provided for this purpose, to make sure that even ST developers won't mess up a binary & followed by a | operator.

The exact effect of setting those register bits, and the complex interactions between them is documented in detail in the reference manual. Most of these details are omitted from the LL documentation.

To find out what a particular LL function call e.g. LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH1N) does, you have to look up both the source of LL_TIM_CC_EnableChannel() and the definition of LL_TIM_CHANNEL_CH1N in the headers, before you can look up their documentation in the reference manual. Three times the work. It becomes even more complicated the other way round, to find the LL functions that do exactly what the reference manual suggests. Accessing the registers directly instead comes straightforward from the reference manual, and makes troubleshooting and future maintenance easier, because the documentation can be readily searched.

However if you think that some LL function should do more than setting a single bitfield, perhaps checking some other register first, and somehow managed to convince ST to change it, then it would become not so simple and no longer so fast. Moreover, just as you have pointed it out, existing LL users would be quite upset if some functions changed. It has happened before though.

The STM32F1 series came out with the SPL (Standard Peripheral Library) back in 2007. Seven years later, SPL was silently dropped, replaced by HAL and LL. Customers who were using SPL in order to have an upgrade path to later STM32 series were not impressed. HAL and LL are 6 years old now.

On the other hand, the register interface does not change, it is guaranteed to remain stable as long as the MCU is manufactured. Timer register compatibility is even preserved across the whole STM32 product family.

JSzem.1
Associate II

Thank you berendi for your encouragement to read the documentatio.

I read again the section "Advanced Control Timer (TIM1)" in Reference Manual (RM0383). There is a note:

"When only OCxN is enabled (CCxE=0, CCxNE=1), it is not complemented and becomes 

active as soon as OCxREF is high. For example, if CCxNP=0 then OCxN=OCxRef. On the 

other hand, when both OCx and OCxN are enabled (CCxE=CCxNE=1) OCx becomes 

active when OCxREF is high whereas OCxN is complemented and becomes active when 

OCxREF is low."

I didn't notice it. The OC1N changes behavior according to OC1 configuration.

In addition, I did not understand AOE and MOE bits.

There is initialization function LL_TIM_BDTR_Init where is:

MODIFY_REG(tmpbdtr, TIM_BDTR_AOE, TIM_BDTRInitStruct->AutomaticOutput);

MODIFY_REG(tmpbdtr, TIM_BDTR_MOE, TIM_BDTRInitStruct->AutomaticOutput);

It was (and is) not clear for me, why there is MOE bit reset here?

Except that TIM1 works as is described in Reference Manual.