2023-10-29 09:22 PM
Hi,
I wish to produce a single pulse; lo-Hi delay Hi to Low.
I am using Timer 1 PB15 which is the complementary output timer Channel 3
I am using the timer in the following sequence
The correct width pulse is produced.
However if I don't use the interrupt to stop the timer the timer does not set the
output to reset state when the interrupt is initiated.
Thus the duration of the pulse produced is contingent upon the interrupt "clearing" the timer output PB15.
I do not want to use interrupts to produce the pulse as I want the pulse width to be accurate with very little jitter.
I have tried a heap of different configs to solve the problem and have been on stack exchange and this forum, but I can't figure out what I'm doing wrong.
Here is my code for the timer initialisation:
TIM_HandleTypeDef htim1;
/** IMPULSE TIMER
*
* @brief TIM1 Initialization Function
* @PAram None
* @retval None
*/
void MX_TIM1_Init(void)
{
/* TIM1 clock enable */
__HAL_RCC_TIM1_CLK_ENABLE();
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
htim1.Instance = TIM1;
htim1.Init.Prescaler = TIM1_PRESCALER - 1;
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 2;
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
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_UPDATE;
sMasterConfig.MasterOutputTrigger2 = TIM_TRGO_UPDATE;
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_HIGH;
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.Break2State = TIM_BREAK2_DISABLE;
sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_HIGH;
sBreakDeadTimeConfig.Break2Filter = 0;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
{
Error_Handler();
}
/* Set the complementary output for Ch 3 active Polarity High*/
htim1.Instance->CCER = 0xd00;
HAL_TIM_MspPostInit(&htim1);
HAL_NVIC_SetPriority(TIM1_UP_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(TIM1_UP_IRQn);
}
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef* tim_pwmHandle)
{
if(tim_pwmHandle->Instance==TIM1)
{
/* USER CODE BEGIN TIM1_MspInit 0 */
/* USER CODE END TIM1_MspInit 0 */
/* TIM1 clock enable */
__HAL_RCC_TIM1_CLK_ENABLE();
/* TIM1 interrupt Init */
HAL_NVIC_SetPriority(TIM1_UP_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(TIM1_UP_IRQn);
/* USER CODE BEGIN TIM1_MspInit 1 */
/* USER CODE END TIM1_MspInit 1 */
}
}
void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(timHandle->Instance==TIM1)
{
__HAL_RCC_GPIOB_CLK_ENABLE();
/**TIM1 GPIO Configuration
PB15 ------> TIM1_CH3N
*/
GPIO_InitStruct.Pin = PB15_ImpulseOut_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF1_TIM1;
HAL_GPIO_Init(PB15_ImpulseOut_GPIO_Port, &GPIO_InitStruct);
}
}
Here is the code to initiate a pulse:
__inline void impulseGen(const float pulseWidth_ms)
{
uint16_t pulseWidthCount;
pulseWidthCount = (uint16_t)(ms_to_count_tim1 * pulseWidth_ms);
htim1.Instance->ARR = (uint32_t)(pulseWidthCount + 1);
htim1.Instance->CCR3 = pulseWidthCount;
HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_3);
/* Enable the TIM Update interrupt */
__HAL_TIM_ENABLE_IT(&htim1, TIM_IT_UPDATE);
triggerLED_Start(200, IMP_LED_BLUE, false);
}
In the RM0455 Rev 10 pp1469/2967 it says:
"Let’s say one want to build a waveform with a transition from ‘0’ to ‘1’ when a compare
match occurs and a transition from ‘1’ to ‘0’ when the counter reaches the auto-reload
value. To do this PWM mode 2 must be enabled by writing OC1M=111 in the
TIMx_CCMR1 register. Optionally the preload registers can be enabled by writing
OC1PE=’1’ in the TIMx_CCMR1 register and ARPE in the TIMx_CR1 register. In this
case one has to write the compare value in the TIMx_CCR1 register, the auto-reload
value in the TIMx_ARR register, generate an update by setting the UG bit and wait for
external trigger event on TI2. CC1P is written to ‘0’ in this example."
I states that the output should transition from 1 to 0 when the counter reaches the ARR.
I tried PWM2 with various combinations of output polarity, but I cannot get it to do the end of pulse transition without the interrupt initiated time stop function.
What am I doing wrong?
Solved! Go to Solution.
2023-11-03 06:44 PM - edited 2023-11-03 09:36 PM
Hi Jan,
I have already tried you idea, but I gave it another go. Like the alternative using PWM1 it did produce one pulse, but when I did another StartPWM it did not produce a pulse it just stayed low.
I then took a snap shot of the Timer registers pre the start function both on the first time thru' then the second and compared them.
They were identical with the exception of the BTDR MOE bit Master output enable, being set. Resetting this bit prior to the second run made no difference.
So we had the timer in an identical state as read by STM32CubeIDE SFR monitor yet behaving differently from an intial run to repeated runs. Hmmm!! I thought to myself. What to do?
I made a copy of the timer initialisation function and then placed it before the StartPWM function and voila it works. I then used a process of elimination to determine what part of the initialisation function "did the job."
Turns out the code I added to reverse the polarity of the complementary output must be executed before each StartPWM:
FRAGMENT OF TIM 1 INIT CODE
sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_HIGH;
sBreakDeadTimeConfig.Break2Filter = 0;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
{
Error_Handler();
}
/* Set the complementary output for Ch 3 active Polarity High*/
htim1.Instance->CCER = 0xd00;
HAL_TIM_MspPostInit(&htim1);
HAL_NVIC_SetPriority(TIM1_UP_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(TIM1_UP_IRQn);
So I placed this before the StartPWM function and voila, it works.
PULSE GENERATION FUNCTION
/*
*****************************************************************************
* PENDULUM IMPULSE GENERATOR *
*****************************************************************************
*/
__inline void impulseGen(const float pulseWidth_ms)
{
uint16_t pulseWidthCount;
pulseWidthCount = (uint16_t)(ms_to_count_tim1 * pulseWidth_ms);
MX_TIM1_Init();
htim1.Instance->ARR = (uint32_t)(pulseWidthCount + 1);
htim1.Instance->CCR3 = 1; /* pulseWidthCount; */
/* Set the complementary OP polarity for ch 3
* will not do repeat runs correctly without this */
htim1.Instance->CCER = 0xd00;
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);
triggerLED_Start(200, IMP_LED_BLUE, false);
}
The observant programmer looking at the Timer registers would have noted that this value 0xd00 was set in the register before each StartPWM command, so why do I have to set it before every start as it appears to be already set from initialisation. I also inspected the memory location with the memory browser and found the value 0xd00 was set prior to each start command.
Could this be a silicon glitch? The register has the correct value, but somehow it ignores it.
Regards
Rob
Timer 1 SFR's
Initial Run
Repeat Runs
2023-10-30 06:31 AM
Your code doesn't touch CCR4 anywhere. I'd suggest consulting the reference manual. It has some very explicit instructions for PWM mode 2. You should also be able to do one pulse mode to avoid having to stop the timer when it reaches ARR.
2023-10-31 09:45 AM
Hello @Garnett.Robert,
Why didn't you try to use the function HAL_TIM_OnePulse_Start_IT() instead?
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.
2023-10-31 09:58 PM
Hi Sarra,
I tried that but it did not generate even the rising edge. The HAL document says that:
(++) HAL_TIM_OnePulse_Init and HAL_TIM_OnePulse_ConfigChannel: to use the Timer
in One Pulse Mode.
HAL_TIM_OnePulse_ConfigChannel only works with channels 1 and 2 viz:
* @brief Initializes the TIM One Pulse Channels according to the specified
* parameters in the TIM_OnePulse_InitTypeDef.
* @PAram htim TIM One Pulse handle
* @PAram sConfig TIM One Pulse configuration structure
* @PAram OutputChannel TIM output channel to configure
* This parameter can be one of the following values:
* @arg TIM_CHANNEL_1: TIM Channel 1 selected
* @arg TIM_CHANNEL_2: TIM Channel 2 selected
* @PAram InputChannel TIM input Channel to configure
* This parameter can be one of the following values:
* @arg TIM_CHANNEL_1: TIM Channel 1 selected
* @arg TIM_CHANNEL_2: TIM Channel 2 selected
* @note To output a waveform with a minimum delay user can enable the fast
* mode by calling the @ref __HAL_TIM_ENABLE_OCxFAST macro. Then CCx
* output is forced in response to the edge detection on TIx input,
* without taking in account the comparison.
* @retval HAL status
*/
HAL_StatusTypeDef HAL_TIM_OnePulse_ConfigChannel(TIM_HandleTypeDef *htim, TIM_OnePulse_InitTypeDef *sConfig,
uint32_t OutputChannel, uint32_t InputChannel)
It is almost as if you can't use channels 3 and 4 for one-pulse.
I have read and re-read the Ref Man, and I have tried all sorts of combinations PWM2 with different counter settings
etc, but no good. I always have to clear the output on interrupt.
If I don't the timer output stays high.
What I don't understand is that the interrupt is initiated on timer update which is when the ARR reload
occurs ad the counter is set to zero. So why doesn't the output reset?
I have checked the timer registers before executing the start function and all the registers are set
as you would expect wit the OPM bit set to one.
Regards
Rob
2023-11-01 03:49 AM
Instead of
htim1.Instance->ARR = (uint32_t)(pulseWidthCount + 1);
htim1.Instance->CCR3 = pulseWidthCount;
You want
htim1.Instance->ARR = (uint32_t)(pulseWidthCount + 1);
htim1.Instance->CCR3 = 1;
You may want to change polarity, too.
JW
2023-11-03 06:44 PM - edited 2023-11-03 09:36 PM
Hi Jan,
I have already tried you idea, but I gave it another go. Like the alternative using PWM1 it did produce one pulse, but when I did another StartPWM it did not produce a pulse it just stayed low.
I then took a snap shot of the Timer registers pre the start function both on the first time thru' then the second and compared them.
They were identical with the exception of the BTDR MOE bit Master output enable, being set. Resetting this bit prior to the second run made no difference.
So we had the timer in an identical state as read by STM32CubeIDE SFR monitor yet behaving differently from an intial run to repeated runs. Hmmm!! I thought to myself. What to do?
I made a copy of the timer initialisation function and then placed it before the StartPWM function and voila it works. I then used a process of elimination to determine what part of the initialisation function "did the job."
Turns out the code I added to reverse the polarity of the complementary output must be executed before each StartPWM:
FRAGMENT OF TIM 1 INIT CODE
sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_HIGH;
sBreakDeadTimeConfig.Break2Filter = 0;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
{
Error_Handler();
}
/* Set the complementary output for Ch 3 active Polarity High*/
htim1.Instance->CCER = 0xd00;
HAL_TIM_MspPostInit(&htim1);
HAL_NVIC_SetPriority(TIM1_UP_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(TIM1_UP_IRQn);
So I placed this before the StartPWM function and voila, it works.
PULSE GENERATION FUNCTION
/*
*****************************************************************************
* PENDULUM IMPULSE GENERATOR *
*****************************************************************************
*/
__inline void impulseGen(const float pulseWidth_ms)
{
uint16_t pulseWidthCount;
pulseWidthCount = (uint16_t)(ms_to_count_tim1 * pulseWidth_ms);
MX_TIM1_Init();
htim1.Instance->ARR = (uint32_t)(pulseWidthCount + 1);
htim1.Instance->CCR3 = 1; /* pulseWidthCount; */
/* Set the complementary OP polarity for ch 3
* will not do repeat runs correctly without this */
htim1.Instance->CCER = 0xd00;
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);
triggerLED_Start(200, IMP_LED_BLUE, false);
}
The observant programmer looking at the Timer registers would have noted that this value 0xd00 was set in the register before each StartPWM command, so why do I have to set it before every start as it appears to be already set from initialisation. I also inspected the memory location with the memory browser and found the value 0xd00 was set prior to each start command.
Could this be a silicon glitch? The register has the correct value, but somehow it ignores it.
Regards
Rob
Timer 1 SFR's
Initial Run
Repeat Runs
2023-11-04 02:47 AM - edited 2023-11-04 02:47 AM
I don't use Cube/HAL and recommend anybody who wishes to do anything beyond what can be easily clicked in CubeMX to abstain from it too, as then it tends to get into way more than help.
Sure, the Advanced timers are complex and have their idiosyncrasies such as this, but mixing them with Cube/HAL's idiosyncrasies just adds up to the mess.
JW
2023-11-14 09:19 PM
Hi
I did some more checking and found that I was getting a "runt" pulse at the start of the sequence viz:
The cause of this pulse is the TIM_CCxChannelCmd which sets the CCER register at line 12.
void TIM_CCxChannelCmd(TIM_TypeDef *TIMx, uint32_t Channel, uint32_t ChannelState)
{
uint32_t tmp;
/* Check the parameters */
assert_param(IS_TIM_CC1_INSTANCE(TIMx));
assert_param(IS_TIM_CHANNELS(Channel));
tmp = TIM_CCER_CC1E << (Channel & 0x1FU); /* 0x1FU = 31 bits max shift */
/* Reset the CCxE Bit */
// TIMx->CCER &= ~tmp; /* debug rjg */
/* Set or reset the CCxE Bit */
TIMx->CCER |= (uint32_t)(ChannelState << (Channel & 0x1FU)); /* 0x1FU = 31 bits max shift */
}
Commenting out this line removed the runt pulse:
The final code I ended up with is attached.
Of course modifying HAL might create some nasty side effects for other timers and configs, which I will have to test for in the full system.