I used the STM32Cube initialization code generator to generate an initialized Timer function. To generate a fixed duty cycle PWM signal I added HAL_TIM_Base_Start(&htim1); //Starts the TIM Base generation
and HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1)//Starts the PWM signal generation
to the Timer initialization function as shown below.
/* Private variables ---------------------------------------------------------*/
int pulse_width=0;
/* TIM1 init function */
static void MX_TIM1_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig;
TIM_MasterConfigTypeDef sMasterConfig;
TIM_OC_InitTypeDef sConfigOC;
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig;
htim1.Instance = TIM1;
htim1.Init.Prescaler = 0;//we want a max frequency for timer, so we set prescaller to 0
//And our timer will have tick frequency
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 1066;//max value for timer is 16bit = 65535, TIM_Period = timer_tick_frequency / PWM_frequency - 1
//In our case, for 15Khz PWM_frequency, set Period to TIM_Period = 16MHz / 15KHz - 1 = 1066
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
if (HAL_TIM_Base_Init(&htim1) != HAL_OK)/* to use the Timer to generate a simple time base for TIM1 */
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;//the default clock is the internal clock from the APBx, using this function
if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)//Initializes the TIM PWM Time Base according to the specified
//parameters in the TIM_HandleTypeDef and create the associated handle.
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
//sConfig: TIM PWM configuration structure
//set duty cycle: pulse_length = ((1066 + 1) * duty_cycle) / (100 - 1)
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = pulse_width;/* 50% duty cycle is 538, set to 0 initially*///
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_1) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
{
Error_Handler();
}
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_ENABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_ENABLE;
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_1;
sBreakDeadTimeConfig.DeadTime = 0;
sBreakDeadTimeConfig.BreakState = TIM_BREAK_ENABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_ENABLE;
if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
{
Error_Handler();
}
HAL_TIM_MspPostInit(&htim1);//output pin assignment
HAL_TIM_Base_Start(&htim1); //Starts the TIM Base generation
if (HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1) != HAL_OK)//Starts the PWM signal generation
{
/* PWM Generation Error */
Error_Handler();
}
/* Start channel 2 */
if (HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2) != HAL_OK)
{
/* PWM Generation Error */
Error_Handler();
}
}
This is enough to run the PWM at a fixed duty cycle specified in the comments above when I hard code the right value to replace pulse_width
value insConfigOC.Pulse = pulse_width
.
In another function, I have an algorithm that would update the pulse_width
global variable. The function is called: adjust_PWM();
. The algorithm calculate values measured from the ADC and stored as global variables. That function is called: Data_Update();
. In main()
, after all functions are initialized. I call these three functions endlessly
Data_Update();
adjust_PWM();
MX_TIM1_Init();
I tried that and obtained weird waveforms on the oscilloscope, but that might be because The ADC pins where floating, causing floating measurements to interfere with the duty cycle by the algorithm. Also recalling the initialization of the timer continuously would interrupt the PWM signal. Is there a better way to change the duty cycle while running the code without using global variables, or without initializing the timer every time I want to update the duty cycle. Any link would be appreciated.
A variable resistor is used to adjust the duty cycle of the PWM produced by the PWM generator. In more complicated circuits, where auto-tuning of the duty cycle is required, PCB designers often turn to microcontrollers.
Duty Cycle = (THIGH / T) * 100 But before seeing how 555 Timer PWM signal is generated, you need to understand the Astable Multivibrator operation of 555 Timer IC. Once you understand this, then by making small modifications, you can effortlessly produce a PWM signal using 555 Timer.
Channel-1 corresponds to PB6 pin of stm32f103 microcontroller. At PB6 an led is connected on which pwm output can be seen. 1 Hz frequency in time domain is T=1/f > T=1/1 Hz > T=1 s. So 1 Hz frequency translates to 1 s and my duty cycle is 50% so the led at PB6 pin will blink at half a second rate.
Do not reinit the timer when you want to change a setting, HAL has a dedicated macro for that purpose called:
/**
* @brief Sets the TIM Capture Compare Register value on runtime without
* calling another time ConfigChannel function.
* @param __HANDLE__: TIM handle.
* @param __CHANNEL__ : TIM Channels to be configured.
* 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
* @arg TIM_CHANNEL_3: TIM Channel 3 selected
* @arg TIM_CHANNEL_4: TIM Channel 4 selected
* @param __COMPARE__: specifies the Capture Compare register new value.
* @retval None
*/
#define __HAL_TIM_SET_COMPARE(__HANDLE__, __CHANNEL__, __COMPARE__) \
(*(__IO uint32_t *)(&((__HANDLE__)->Instance->CCR1) + ((__CHANNEL__) >> 2)) = (__COMPARE__))
For Timer 1 - Channel 1 and Timer 1 - Channel 2 it should look like:
Data_Update();
adjust_PWM();
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, pulse_width);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, pulse_width);
Write your own function to update the register that governs the duty cycle. You will have to manually update the appropriate CCRx register (x is the PWM channel you're using, which is CCR1 in your case).
The ARR register is the the register you will reference when calculating the new value for the CCR register based upon the desired duty cycle.
void adjust_PWM_DC(TIM_HandleTypeDef* const pwmHandle, const float DC)
{
assert(pwmHandle != NULL);
assert((DC >= 0.0F) && (DC <= 100.0F));
/* The duty cycle value is a percentage of the reload register value (ARR). Rounding is used.*/
uint32_t newRegVal = (uint32_t)roundf((float32_t)(pwmHandle->Instance->ARR) * (DC * 0.01F));
/*In case of the DC being calculated as higher than the reload register, cap it to the reload register*/
if(newRegVal > pwmHandle->Instance->ARR){
newRegVal = pwmHandle->Instance->ARR);
}
/*Assign the new DC count to the capture compare register.*/
pwmHandle->Instance->CCR1 = (uint32_t)(roundf(newRegVal)); /*Change CCR1 to appropriate channel, or pass it in with function.*/
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With