2024-01-30 04:00 PM - edited 2024-01-30 04:01 PM
I've been banging my head against this one for a while. I have two pins I'm trying to generate a peripheral-timed pulse on, which sounds like a perfect fit for One Pulse Mode.
"Trigger 1" is TIM8 CH1; that is, PB6.
"Trigger 2" is TIM1 CH3; that is, PA10.
The two timers are configured identically, produce different results, neither of which are correct.
I have simplified my project to run a simple loop, theoretically continually triggering single pulses every two seconds. TIM8 CH1 generates a runt followed by a permanently-high state; TIM1 CH3 never goes high.
I found this post which sounded just like the problem I was having, but that person's solutions did not work for me. That post does make the important observation (that I also noticed) that HAL_TIM_OnePulse_Start is hard-coded to only work on TIM_CHANNEL_1 and TIM_CHANNEL_2, for no readily-apparent reason.
In any case, here is my init code and the loop. APB clock is 16 MHz. Init code: (CubeMX-generated)
static void MX_TIM1_Init(void)
{
/* USER CODE BEGIN TIM1_Init 0 */
/* USER CODE END TIM1_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
/* USER CODE BEGIN TIM1_Init 1 */
/* USER CODE END TIM1_Init 1 */
htim1.Instance = TIM1;
htim1.Init.Prescaler = 3999;
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 249;
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV4;
htim1.Init.RepetitionCounter = 0;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_OnePulse_Init(&htim1, TIM_OPMODE_SINGLE) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 1;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_LOW;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)
{
Error_Handler();
}
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
sBreakDeadTimeConfig.DeadTime = 0;
sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
sBreakDeadTimeConfig.BreakFilter = 0;
sBreakDeadTimeConfig.BreakAFMode = TIM_BREAK_AFMODE_INPUT;
sBreakDeadTimeConfig.Break2State = TIM_BREAK2_DISABLE;
sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_HIGH;
sBreakDeadTimeConfig.Break2Filter = 0;
sBreakDeadTimeConfig.Break2AFMode = TIM_BREAK_AFMODE_INPUT;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM1_Init 2 */
/* USER CODE END TIM1_Init 2 */
HAL_TIM_MspPostInit(&htim1);
}
static void MX_TIM8_Init(void)
{
/* USER CODE BEGIN TIM8_Init 0 */
/* USER CODE END TIM8_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
/* USER CODE BEGIN TIM8_Init 1 */
/* USER CODE END TIM8_Init 1 */
htim8.Instance = TIM8;
htim8.Init.Prescaler = 3999;
htim8.Init.CounterMode = TIM_COUNTERMODE_UP;
htim8.Init.Period = 249;
htim8.Init.ClockDivision = TIM_CLOCKDIVISION_DIV4;
htim8.Init.RepetitionCounter = 0;
htim8.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim8) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim8, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim8) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_OnePulse_Init(&htim8, TIM_OPMODE_SINGLE) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim8, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 1;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_LOW;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
if (HAL_TIM_PWM_ConfigChannel(&htim8, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
sBreakDeadTimeConfig.DeadTime = 0;
sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
sBreakDeadTimeConfig.BreakFilter = 0;
sBreakDeadTimeConfig.BreakAFMode = TIM_BREAK_AFMODE_INPUT;
sBreakDeadTimeConfig.Break2State = TIM_BREAK2_DISABLE;
sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_HIGH;
sBreakDeadTimeConfig.Break2Filter = 0;
sBreakDeadTimeConfig.Break2AFMode = TIM_BREAK_AFMODE_INPUT;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
if (HAL_TIMEx_ConfigBreakDeadTime(&htim8, &sBreakDeadTimeConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM8_Init 2 */
/* USER CODE END TIM8_Init 2 */
HAL_TIM_MspPostInit(&htim8);
}
My main code:
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM1_Init();
MX_TIM8_Init();
HAL_Delay(2000);
while(1) {
HAL_StatusTypeDef rc = 0;
rc = HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_1);
HAL_Delay(1000);
rc = HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);
HAL_Delay(1000);
}
}
Here's a scope plot of the outputs. The top trace is PB6; the bottom, PA10. Note the runt at the start of the PB6 trace, another issue mentioned in the thread I linked (the solution of which did not work for me).
What am I missing?
2024-01-31 03:04 AM
Read out and check/post content of TIM and relevant GPIO registers.
sConfigOC.Pulse = 1;
Is your hardware capable of capturing/displaying such short pulses?
JW
2024-01-31 06:43 AM
Initialize PB6 and PA10 as GPIO outputs and toggle them. Verify on the scope you are connected to these pins.
> HAL_TIM_PWM_ConfigChannel
> HAL_TIM_PWM_Start
Feels like these should be HAL_TIM_OnePulse_ConfigChannel and HAL_TIM_OnePulse_Start instead. Although the example uses the functions you're calling.
Register values would probably clear things up.
Can you get PWM outputs if you set it to normal mode? (Instead of one pulse mode).
2024-01-31 10:22 AM
Note: The complete lack of output on PB6 was due to an unnoticed solder bridge. Now both channels exhibit identical, incorrect behavior (a runt followed by a step as shown in the original picture).
@waclawek.jan wrote:
Read out and check/post content of TIM and relevant GPIO registers
Since both outputs are now behaving identically, I believe it is safe to focus on one setup, TIM8/PA10
sConfigOC.Pulse = 1;Is your hardware capable of capturing/displaying such short pulses?
Yes, and did you look at the trace I showed? The outputs go high and stay high after the runt pulse. It should never do that. One "pulse" should be idle low, active high, back to idle low.
Further tinkering has revealed:
This dependence on the "Pulse" value does not track with this excerpt from the Reference Manual:
A pulse can be correctly generated only if the compare value is different from the counter
initial value. Before starting (when the timer is waiting for the trigger), the configuration must
be:
• In upcounting: CNT < CCRx ≤ ARR (in particular, 0 < CCRx)
Further, from the Reference Manual:
The OPM waveform is defined by writing the compare registers (taking into account the
clock frequency and the counter prescaler).
• The tDELAY is defined by the value written in the TIMx_CCR1 register.
• The tPULSE is defined by the difference between the auto-reload value and the compare
value (TIMx_ARR - TIMx_CCR1).
This suggests (strongly) that a Pulse value of 1 (the minimum value that satisfies the inequality from the previous quote) is the ideal configuration for a single pulse of practically no initial delay and specified period.
The observed behavior does not seem to track with the description available in the Reference Manual.
Responses to TDK in the next reply, since this forum doesn't seem to let me quote from more than one message.
2024-01-31 10:27 AM
@TDK wrote:Initialize PB6 and PA10 as GPIO outputs and toggle them. Verify on the scope you are connected to these pins.
Yep, did that, and that exposed the solder bridge flaw I mentioned in my reply to jan.
@TDK wrote:> HAL_TIM_PWM_ConfigChannel
> HAL_TIM_PWM_Start
Feels like these should be HAL_TIM_OnePulse_ConfigChannel and HAL_TIM_OnePulse_Start instead. Although the example uses the functions you're calling.
You'd think, but HAL_TIM_OnePulse_Start is hard-coded to only operate on TIM_CHANNEL_1 and TIM_CHANNEL_2. This would be fine for the TIM8/CH1 output, but not the TIM1/CH3 output.
@TDK wrote:Can you get PWM outputs if you set it to normal mode? (Instead of one pulse mode).
Mentioned in my reply to jan, but yes; simply leaving the OPM bit unset yields a PWM pulse train that follows the expected timing set in Pulse (CCRx) and Period (ARR).
The critical problem here is the return to high output state after the pulse. This pulse serves as a trigger to an optical sensing device, which is level sensitive rather than edge sensitive; the line must idle low for proper operation.
2024-02-01 02:09 AM
You have TIMx_CCMR1=0x68, which means OC1M=0b0110 i.e. PWM mode 1. From RM:
PWM mode 1 - In upcounting, channel 1 is active as long as TIMx_CNT<TIMx_CCR1
else inactive.
This means, that in idle, when TIMx_CNT=0, it's <TIMx_CCR i.e. output is active, i.e. 1.
JW
2024-02-01 10:12 AM
@waclawek.jan wrote:This means, that in idle, when TIMx_CNT=0, it's <TIMx_CCR i.e. output is active, i.e. 1.
JW
Okay, that sort of makes sense, but then why does the line start out low before the timer is started for the first time?
Switching to PWM Mode 2 gives me a pulse like I expect, but only once. If the CNT is being reset to 0 (and it must be, because that's what's making the line idle high in PWM1), what else am I missing to get it to generate another pulse when setting CEN = 1 again?
Still using the main() code as quoted in the original post; I would expect to see a pulse come out each line every two seconds, staggered by a second. But there's only one.
... oh. It's what I would call a design flaw of the HAL code. The software knowledge of whether the channel is busy doesn't get set back to "ready" after the pulse, so the next call to HAL_TIM_PWM_Start returns an error. If, instead of calling that method, I do the things that method does (namely, set CCER enable bit for the proper channel, set MOE bit in BDTR, and set CR1.CEN), then I get another pulse.
So I have something that works now. Hearty thank you for everyone's input; it all has been helpful in getting me to the right answer.