2021-10-17 05:14 AM
General objective: Generate 2 PWM's on Timer 3 and Timer 4 respectively at 50% duty cycle, both with period of 2 seconds and phase shift of 1second between them (Timer4 will start 1 sec later).
Because I want it to be precise, I am not considering using a simple HAL_Delay(1000) between the 2 HAL_TIM_PWM_Start(). In fact, I would like to internally trigger/enable Timer4, using Timer3's Output compare match (active level on match).
The motivation of what I'm trying to do came from this STM32 video showing the internal triggering system, where I tried to replicate as far as possible, albeit just using the 2 LEDS from Tim3(Ch 3) and Tim4(Ch 2).
I already have success creating this delay between the 2 PWMs. My issue is that for the very first cycle of the PWMs, both PWMs (well LED in this case) are lit up at the same time.
Thereafter (after the first cycle), when PWM (Tim3) is on, PWM(Tim4) will be off, and they will lit up alternately in the correct fashion.
Timer3 Channel3: PWM output (period 20000 [2 secs], pulse 10000 [1 sec])
Timer3 Channel1: Output compare (no output) pulse 10000 to get 1 sec delay, Active Leve
Timer4 Slaved Mode: (Tried both Gated and Trigger mode, my examples below shows Gated mode)
Timer4 Channel2: PWM output (period 20000 [2 secs], pulse 10000 [1 sec])
Using Timer 3, channel 3, I have no issues starting a PWM using HAL_TIM_PWM_START().
Immediately right after, I start my Timer 3, Channel 1 (Output compare, no output) so that once it reaches it's appropriate count, it will trigger (Active Level on Match) for Timer 4 to start (1 second delay as compared to Timer 3).
2021-10-17 05:22 AM
#include "main.h"
#include <stdio.h>
TIM_HandleTypeDef htim3;
TIM_HandleTypeDef htim4;
UART_HandleTypeDef huart3;
PCD_HandleTypeDef hpcd_USB_OTG_FS;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART3_UART_Init(void);
static void MX_USB_OTG_FS_PCD_Init(void);
static void MX_TIM3_Init(void);
static void MX_TIM4_Init(void);
uint8_t toggle = 0;
uint8_t TxData[15];
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART3_UART_Init();
MX_USB_OTG_FS_PCD_Init();
MX_TIM3_Init();
MX_TIM4_Init();
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);
HAL_TIM_OC_Start(&htim3, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_2); //the led immediately switches on once the CCxE Bit for the PWM is set, even if the Tim4 Enable bit is not triggered on yet.
while (1)
{
uint16_t tim3counter = __HAL_TIM_GET_COUNTER(&htim3);
uint16_t tim4counter = __HAL_TIM_GET_COUNTER(&htim4);
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_BYPASS;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 8;
RCC_OscInitStruct.PLL.PLLN = 384;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4;
RCC_OscInitStruct.PLL.PLLQ = 8;
RCC_OscInitStruct.PLL.PLLR = 2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_3) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief TIM3 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM3_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
htim3.Instance = TIM3;
htim3.Init.Prescaler = 9600-1;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 20000-1;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_OC_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC1REF;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_ACTIVE;
sConfigOC.Pulse = 10000;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_OC_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)
{
Error_Handler();
}
HAL_TIM_MspPostInit(&htim3);
}
static void MX_TIM4_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_SlaveConfigTypeDef sSlaveConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
htim4.Instance = TIM4;
htim4.Init.Prescaler = 9600-1;
htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
htim4.Init.Period = 20000-1;
htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim4) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim4) != HAL_OK)
{
Error_Handler();
}
sSlaveConfig.SlaveMode = TIM_SLAVEMODE_GATED;
sSlaveConfig.InputTrigger = TIM_TS_ITR2;
if (HAL_TIM_SlaveConfigSynchro(&htim4, &sSlaveConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 10000;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim4, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
{
Error_Handler();
}
HAL_TIM_MspPostInit(&htim4);
}
static void MX_USART3_UART_Init(void)
{
huart3.Instance = USART3;
huart3.Init.BaudRate = 115200;
huart3.Init.WordLength = UART_WORDLENGTH_8B;
huart3.Init.StopBits = UART_STOPBITS_1;
huart3.Init.Parity = UART_PARITY_NONE;
huart3.Init.Mode = UART_MODE_TX_RX;
huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart3.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart3) != HAL_OK)
{
Error_Handler();
}
}
static void MX_USB_OTG_FS_PCD_Init(void)
{
hpcd_USB_OTG_FS.Instance = USB_OTG_FS;
hpcd_USB_OTG_FS.Init.dev_endpoints = 6;
hpcd_USB_OTG_FS.Init.speed = PCD_SPEED_FULL;
hpcd_USB_OTG_FS.Init.dma_enable = DISABLE;
hpcd_USB_OTG_FS.Init.phy_itface = PCD_PHY_EMBEDDED;
hpcd_USB_OTG_FS.Init.Sof_enable = ENABLE;
hpcd_USB_OTG_FS.Init.low_power_enable = DISABLE;
hpcd_USB_OTG_FS.Init.lpm_enable = DISABLE;
hpcd_USB_OTG_FS.Init.battery_charging_enable = ENABLE;
hpcd_USB_OTG_FS.Init.vbus_sensing_enable = ENABLE;
hpcd_USB_OTG_FS.Init.use_dedicated_ep1 = DISABLE;
if (HAL_PCD_Init(&hpcd_USB_OTG_FS) != HAL_OK)
{
Error_Handler();
}
}
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(USB_PowerSwitchOn_GPIO_Port, USB_PowerSwitchOn_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(DIR_GPIO_Port, DIR_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin : USER_Btn_Pin */
GPIO_InitStruct.Pin = USER_Btn_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(USER_Btn_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : USB_PowerSwitchOn_Pin */
GPIO_InitStruct.Pin = USB_PowerSwitchOn_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(USB_PowerSwitchOn_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : USB_OverCurrent_Pin */
GPIO_InitStruct.Pin = USB_OverCurrent_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(USB_OverCurrent_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : DIR_Pin */
GPIO_InitStruct.Pin = DIR_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(DIR_GPIO_Port, &GPIO_InitStruct);
/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
I'm showing my code above to show u what I have done so far. The offending part seems to be HAL_TIM_PWM_Start(&htim4, TIM_Channel_2). The moment this line (specifically when the PWM CCxE bit is set) is run, the LED turns on even though it is still too early (Timer4 has not been triggered to run yet).
But if I were to not run this line, the LEDs/PWM for Timer4 Channel 2 will never ever be lit up.
2021-10-17 05:28 AM
As part of my debugging process, I have verified that the Timer4 is indeed correctly lagging behind Timer3 by 10000 counts (continuously verified using HAL_TIM_GetCounter()), proving that the Timer4 delay is ok.
The issue is purely that Timer 4 PWM turns on once the CCxE Bit is set (using HAL_TIM_PWM_Start). Why is it on? The reference manual suggests that this is because the Timer4 counter (although not running/started, has a value of 0), which since it is lesser than the Tim4, Channel 2 CCR value of 10000, indicates that it should be Active (I did not switch channel polarity, nor use PWM Mode 2). Therefore the PWM turns on immediately, even Timer4 has not been triggered to start yet.
2021-10-17 05:53 AM
Possible solutions, albeit seems very unclean.
Option 1) After PWM_Start and OC_Start for Timer 3, use a HAL_Delay(1000) before running the PWM_start for TIM4, Chan2. By toggling the CCxE bit 1 second later, the first inappropriate LED activation will be manually delayed. The timers will still be accurately spaced exactly 10000 counts apart. Looks untidy, but seems to work in the long run because the timers will be accurately spaced 10000 counts apart. Down side is that for the very first blink, there is no guarantee that the TIM4 PWM will be activated exactly on time since we are just using software delay.
Option 2) Modify Tim4->CNT value to 19999 before running PWM_start for TIM4. In this manner, even though the CCxE bit for PWM is set, because the CNT value is higher than the CCR, the PWM will be InActive for that undesired first part. For subsequent cycles, behaviour will be just as desired. Down side is that the timers are not exactly 10000 counts apart, but rather 9999 counts apart forever, since my Timer4 was only 1 count from overflowing at the start. Considering the timespan of mankind on earth, that 1 count is nothing, but it's just very irritating to have a defect of 1 count when I desire 10000 counts apart.
Option 3) Initialise Timer4 Channel 2 PWM CCR as 0 (rather than 10000), to ensure that the LED/PWM does not lit for the first time. Since CCR (0 value) would equals the starting value of Tim4 CNT (also 0), the PWM will be inactive. Right after running PWM_start for TIM4, change the TIM4->CCR to the correct 10000. Here comes the catch, if Preload is enabled, the Tim4 PWM will only light up AFTER completing this cycle. (meaning TIM4 will not switch on right after TIM3 PWM switches off).
If Preload is disabled, then TIM4 PWM will immediately go to Active, even if our TIM4 timer has not been triggered to start yet.
Option 4) At this point, I'm really out of ideas and would really be keen to see what it is I am doing wrongly and how it is u guys are doing it properly. What puzzles me is that the youtube video link above shows it working perfectly while mine has this initial defect. Please help...
2021-10-17 06:31 AM
Which STM32?
> What puzzles me is that the youtube video link above shows it working perfectly
Why do you think so? IMO what they present is taken from a running system, not the "start".
As yourself said, there are many ways how to achieve what you want. I personally would recommend something similar to your option 2. Set the slave timer's CNT to the same value as ARR. Set slave timer's slave-mode controller to Trigger mode (not Gated) and *don't* set slave timer's TIMx_CR1.CEN. Compensate for delay by triggering it from a different channel of the master timer, you can compensate any delay with that. Note, that there's an inherent delay of several clocks in the master-slave process, which is undocumented and possibly depends on STM32 model, particular pair of timers and their clock source.
I recommend against using Cube/HAL in anything beyond "usual", i.e. anything which cannot be set up simply by clicking in CubeMX.
JW
2021-10-17 08:44 AM
Thanks for your answer feedback. The board i'm using is STM32 Nucleo-F413ZH, at this point, I'm still pretty new to STM32 so am still quite dependent on the clicking of checkboxes within the CubeMx (I understand they could occasionally be buggy when generating codes). I suppose that over time, I would be less dependent on the CubeMx and use those HAL functions directly more frequently. But that is another matter altogether.
I guess one possible resolution in this thread is if someone else can confirm that this particular chip behaviour is not just me using it wrongly, but that it is a systemic issue. i.e. Defined behaviour, just undesirable.
Could someone let me know if you are able to replicate the same outcome on your boards too please?
2021-10-17 08:45 AM
Of course the best outcome would still be if someone can replicate the problem, and have a neat solution (i.e. option 4) to it?