2024-09-19 09:01 PM
I just spent several days (luckily not full time) failing to get a simple PWM output working. I could get it working using the HAL drivers, but not able to get it working with the LL drivers. After spending most of my time assuming it was user error I finally resorted to full register dumps of the Timer registers comparing the HAL code vs the LL code and found the discrepancy.
In the LL-code, somehow the MX_TIM17_Init() generated by CubeMX clears the MOE bit in the BDTR register, disabling outputs even though I have break mode disabled in the UI. Generating HAL code with exactly the same settings correctly sets the MOE bit.
This seems to be a bug. After isolating the issue, it was a simple case of adding the call
LL_TIM_EnableAllOutputs(TIM17) ;
to my code to enable outputs and all is well. Double checking all the LL examples, there are no calls to the above routine, so don't think it is expected.
Did I miss something somewhere or is this a CubeMX bug?
will
Solved! Go to Solution.
2024-09-20 07:04 AM
The LL library assumes you're going to take care of the details. For advanced timers, this means you have to set the MOE bit. If you want everything taken care of for you, that's what HAL is for.
> I needed to add that EnableAllOutputs() call to get it to work, something that wasn't in any of the LL example code
It's there when they use an advanced timer. In this case, TIM1:
2024-09-19 09:45 PM
Hello @Will5
Could you please share your product complete name.
Best Regards.
STTwo-32
To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.
2024-09-20 03:37 AM
Hello @Will5,
I cannot see how the MX_TIM17_Init() is clearing the MOE bit, which instruction?
Could you share your code sequence to generate a PWM output?
Otherwise, there are some examples on generating PWM output using LL drivers in STM32CubeG0/Projects/NUCLEO-G071RB/Examples_LL/TIM/TIM_PWMOutput at master · STMicroelectronics/STM32CubeG0 (github.com)
To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.
2024-09-20 05:06 AM
Thanks for replying Sarra.S!
The full part number I'm generating for is the STM32G031J6. It is CubeIDE 1.16.0 (yep, just noticed I'm one revision behind).
Here is the TIM17 code generated by CubeMX:
static void MX_TIM17_Init(void)
{
/* USER CODE BEGIN TIM17_Init 0 */
/* USER CODE END TIM17_Init 0 */
LL_TIM_InitTypeDef TIM_InitStruct = {0};
LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0};
LL_TIM_BDTR_InitTypeDef TIM_BDTRInitStruct = {0};
/* Peripheral clock enable */
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM17);
/* USER CODE BEGIN TIM17_Init 1 */
/* USER CODE END TIM17_Init 1 */
TIM_InitStruct.Prescaler = 0;
TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP;
TIM_InitStruct.Autoreload = 25;
TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;
TIM_InitStruct.RepetitionCounter = 0;
LL_TIM_Init(TIM17, &TIM_InitStruct);
LL_TIM_DisableARRPreload(TIM17);
LL_TIM_OC_EnablePreload(TIM17, LL_TIM_CHANNEL_CH1);
TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1;
TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_DISABLE;
TIM_OC_InitStruct.OCNState = LL_TIM_OCSTATE_DISABLE;
TIM_OC_InitStruct.CompareValue = 12;
TIM_OC_InitStruct.OCPolarity = LL_TIM_OCPOLARITY_HIGH;
TIM_OC_InitStruct.OCNPolarity = LL_TIM_OCPOLARITY_HIGH;
TIM_OC_InitStruct.OCIdleState = LL_TIM_OCIDLESTATE_LOW;
TIM_OC_InitStruct.OCNIdleState = LL_TIM_OCIDLESTATE_LOW;
LL_TIM_OC_Init(TIM17, LL_TIM_CHANNEL_CH1, &TIM_OC_InitStruct);
LL_TIM_OC_DisableFast(TIM17, LL_TIM_CHANNEL_CH1);
TIM_BDTRInitStruct.OSSRState = LL_TIM_OSSR_DISABLE;
TIM_BDTRInitStruct.OSSIState = LL_TIM_OSSI_DISABLE;
TIM_BDTRInitStruct.LockLevel = LL_TIM_LOCKLEVEL_OFF;
TIM_BDTRInitStruct.DeadTime = 0;
TIM_BDTRInitStruct.BreakState = LL_TIM_BREAK_DISABLE;
TIM_BDTRInitStruct.BreakPolarity = LL_TIM_BREAK_POLARITY_HIGH;
TIM_BDTRInitStruct.BreakFilter = LL_TIM_BREAK_FILTER_FDIV1;
TIM_BDTRInitStruct.AutomaticOutput = LL_TIM_AUTOMATICOUTPUT_DISABLE;
LL_TIM_BDTR_Init(TIM17, &TIM_BDTRInitStruct);
/* USER CODE BEGIN TIM17_Init 2 */
/* USER CODE END TIM17_Init 2 */
}
Here is my code: (the call to MX_TIM18_Init() is earlier)
LL_TIM_CC_EnableChannel(TIM17,LL_TIM_CHANNEL_CH1) ;
LL_TIM_EnableAllOutputs(TIM17) ;
LL_TIM_EnableCounter (TIM17) ;
I needed to add that EnableAllOutputs() call to get it to work, something that wasn't in any of the LL example code--and yes, I was looking at the examples for the G071 as you mentioned and they didn't have that call, hence why I was confused for so long.
Again, I found the issue by resorting to using HAL drivers that worked, stopped the code and did a dump of all the Timer registers, went back to the LL drivers and did the same thing and compared. All register values were identical except for BDTR. It had a value of 0xa000 with the HAL drivers and 0x2000 with the LL drivers. The difference was the MOE bit which clearly disabled the outputs! To my knowledge I'm not doing anything strange.
Attached is a screenshot of the CubeMX config for the Timer.
Hope that helps isolate things. I'm still open to it being user error of some kind.
will
2024-09-20 05:38 AM
Sarra.S:
Just in case it matters, I'm using the STM32G0316-DISCO board and used it as the starting point for the project.
will
2024-09-20 07:04 AM
The LL library assumes you're going to take care of the details. For advanced timers, this means you have to set the MOE bit. If you want everything taken care of for you, that's what HAL is for.
> I needed to add that EnableAllOutputs() call to get it to work, something that wasn't in any of the LL example code
It's there when they use an advanced timer. In this case, TIM1:
2024-09-20 07:29 AM
TDK:
Ah! OK, it is kinda user error then. I know LL means you take care of a lot of the details and I rely heavily on the examples to make sure I don't miss anything. I see now that the example code I was using as a reference wasn't for an advanced timer and I (incorrectly) assumed that the default settings for all the break stuff would be benign if disabled in CubeMX. They were for HAL, but not for LL.
In fact, the reason I'm going down the LL path on this small project is I do need to do something weird I don't believe the HAL drivers handle. I'm doing DMA-based PPM modulation. So need PWM mode, but need the DMAing to be to the Period register, not the Compare register. I couldn't see how the HAL could do that natively. I probably could have made some weird macro calls to force it, but going down the LL path to manage things directly seemed a lot easier.
will
2024-09-20 07:52 AM
HAL_TIM_Base_Start_DMA will update the ARR register. But it might not like you calling that along with the PWM.
Also consider using direct register access. It's a lot more straightforward than LL in my opinion.
2024-09-20 09:26 AM
TDK:
Yes, I started down that path, trying to use HAL_TIM_Base_Start_DMA() then some of the macros to enable PWM mode. HAL drivers are pretty complex being very general purpose and I quickly realized that poking at them without understanding them wasn't a great recipe for success or reliability, hence going down the LL path.
Over the years I've progressed from the "Snippets" code (do those even exist anymore?) to LL and then HAL, but for HAL seeing how much overhead it has. It isn't a knock against the HAL code, there are many cases where it is just fine especially for big projects. For small pieces of critical code I've just bypassed it and gone down the LL or register path.
Thanks again (and to Sarra.S) for the responses.
will