9

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.

Bence Kaulics
  • 7,066
  • 7
  • 33
  • 63
Nadim
  • 103
  • 1
  • 2
  • 6
  • 3
    The best way is to get rid of the ST "HAL" bloatware and directly program the registers. This saves half the code at actually less effort. – too honest for this site Apr 19 '17 at 00:20
  • @Olaf directly program the registers? can you elaborate for someone who is more hardware oriented? – Nadim Apr 19 '17 at 01:54
  • 1
    Read the reference manual (you have to anyway), only include the CMSIS and register definition headers from ST and directly write/read the registers of the peripheral modules. As a hardware-oriented person, this also should suit you much better. This way you don't have to fiddle with this bloatware **and** the hardware, but only the hardware. – too honest for this site Apr 19 '17 at 02:23
  • 1
    I couldn't agree more with @Olaf 's statement about dropping the library code. It is at best a mess, and at worst a liability. Working from the datasheet and setting the peripherals up your self, you know exactly what's going on. If it's a platform you're going to be working with lots you will eventually end up with your own HAL library anyway. – Colin Apr 19 '17 at 07:38
  • Instead of calling MX_TIM1_Init() repeatedly, have you tried calling HAL_TIM_PWM_ConfigChannel() with the new `pulse_width` value? – kkrambo Apr 19 '17 at 14:25
  • @Colin__s: That ting you will end up with is commonly called "drivers" :-) And it is a habit I strongly recommend to use. No hardware-access at higher abstraction layers, of course. But the drivers should be tailored for the actual use-case. Something the STlib defies. – too honest for this site Apr 19 '17 at 16:15
  • "HAL" or no "HAL" goes either ways. Better to master both. LOL – Paul Oct 07 '18 at 04:45

3 Answers3

35

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);
Bence Kaulics
  • 7,066
  • 7
  • 33
  • 63
1

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.*/ 
}
Flip
  • 881
  • 7
  • 13
  • While the M4F and M7 support floating point, they are a burden for embedded code nevertheless. Don't use them, at least not in such code. Typically only the top-level application code should deal with floats and only **iff** necessary.. – too honest for this site Apr 19 '17 at 16:18
  • @Olaf There are cases where floating points are hazardous and ought to be avoided. Though they are much simpler to understand and serve their purpose well in this piece of illustrative code. If soft float is an issue, then the [0;100] DC can be mapped to [0;0xFFFFU] or fixed point etc. This though will be slower than hardware floating point the latter also preserving more precision. – Flip Apr 20 '17 at 08:58
  • Speed of float processing is not the only issue and I did not talk about hazard (which are two issues). This is not the place for tutoring, but an experienced embedded programmer will avoid floats where reasonable, which is very ofteen the problem. – too honest for this site Apr 20 '17 at 17:35
  • One other problem is RAM/stack space. For CM4F, FPU registers e.g. need 64 additional bytes of stack space. This memory can be a major problem for highly parallelised MT code on small devices (which are prefered for cost reasons). But also a time-problem for the extra clocks. As I wrote: most usages of gloats in embedded are by unexperienced or lazy programmers. With all due respect, but how much experience do you have on such platforms? The STM32F7 is definitively not one of them (although here the FPU-registers take 128 bytes (`double`). F4 is borderline. – too honest for this site Apr 20 '17 at 17:36
  • Btw: the precision of fixed-point is constant, the one of float is relative, so problematic to compare. 32 bit fixed-point has higher precision than IEEE754 `single` around `+/-1E0`. Typical ADCs and DACs use integer or fixed-point anyway. – too honest for this site Apr 20 '17 at 17:39
  • @Olaf Unfortunately I have to work with mostly inexperienced programmers, thus using floats tends to be less error prone, faster (even without hard float support at times) simpler and more portable. Granted for the reasons you mentioned the processors are over specified (F7s). – Flip Apr 21 '17 at 07:47
1

I put the answers together:

`

void adjust_PWM_dutyCycle(TIM_HandleTypeDef* pwmHandle, uint32_t pwmChannel, float dutyCycle)
{
    assert(pwmHandle != NULL);
    assert((dutyCycle >= 0.0f) && (dutyCycle <= 100.0f));

    /* The duty cycle value is a percentage of the reload register value (ARR). Rounding is used.*/
    uint32_t newRegVal = (uint32_t)roundf((float)(pwmHandle->Instance->ARR) * (dutyCycle * 0.01f));

    /*In case of the dutyCycle 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 dutyCycle count to the capture compare register.*/
    __HAL_TIM_SET_COMPARE(pwmHandle, pwmChannel, (uint32_t)(roundf(newRegVal))); 

}

`

tested ok to me

Simo S
  • 55
  • 6