使用STM32 HAL定时器和调整PWM信号的占空比

10

我使用了STM32Cube初始化代码生成器来生成一个已初始化的定时器函数。为了生成一个固定占空比的PWM信号,我在定时器初始化函数中添加了HAL_TIM_Base_Start(&htim1); //启动TIM基础生成HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1)//启动PWM信号生成,如下所示。

/* 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();
  }

}

当我在sConfigOC.Pulse = pulse_width中硬编码正确的值来替换pulse_width值时,就足以使PWM以注释中指定的固定占空比运行。

在另一个函数中,我有一个算法来更新全局变量pulse_width。该函数名为:adjust_PWM();。该算法计算从ADC测量并存储为全局变量的值。该函数名为:Data_Update();。在main()中,在所有函数初始化完成后,我无限调用这三个函数。

Data_Update();
adjust_PWM();   
MX_TIM1_Init(); 

我尝试过这个方法,但示波器上得到的波形很奇怪,这可能是因为ADC引脚悬空导致浮动测量干扰了算法的占空比。此外,不断调用计时器的初始化会中断PWM信号。有没有更好的方法在运行代码时改变占空比,而不使用全局变量或每次更新占空比时都要初始化计时器?如果有相关链接,请分享。

3
最好的方法是摆脱ST“HAL”庞大的软件,并直接编程寄存器。这样做可以节省一半的代码,实际上付出的努力更少。 - too honest for this site
@Olaf 直接编程寄存器?你能为更加硬件导向的人详细解释一下吗? - Nadim
1
阅读参考手册(无论如何,您都必须这样做),只需包括ST的CMSIS和寄存器定义头文件,并直接编写/读取外设模块的寄存器。作为一个硬件导向的人,这种方式也应该更适合您。这样,您就不必纠结于这个臃肿的软件硬件,而只需关注硬件即可。 - too honest for this site
1
我完全同意@Olaf关于放弃库代码的说法。它最多是一团糟,最坏的情况下是一种负担。从数据手册中工作并自己设置外设,你知道正在发生什么。如果这是一个你将要经常使用的平台,你最终会得到自己的HAL库。 - Colin
@Colin__s:你最终会得到的东西通常被称为“驱动程序” :-) 这是我强烈推荐使用的习惯。当然,在更高的抽象层上没有硬件访问。但是驱动程序应该根据实际用例进行定制。这是STlib所无法做到的。 - too honest for this site
显示剩余2条评论
3个回答

35
不要在想要更改设置时重新初始化计时器,HAL有一个专门用于此目的的宏称为:
/**
  * @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__))

对于定时器1 - 通道1和定时器1 - 通道2,它应该如下所示:

Data_Update();
adjust_PWM();

__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, pulse_width);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, pulse_width);

1
编写自己的函数来更新控制占空比的寄存器。您需要手动更新适当的CCRx寄存器(在您的情况下,x是您正在使用的PWM通道,即CCR1)。
ARR寄存器是您在根据所需的占空比计算CCR寄存器的新值时将引用的寄存器。
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.*/ 
}

虽然M4F和M7支持浮点数,但它们对于嵌入式代码来说仍然是一个负担。不要在这样的代码中使用它们,至少不要这样做。通常只有顶层应用程序代码应该处理浮点数,而且只有在必要的情况下才需要使用。 - too honest for this site
@Olaf 有些情况下,浮点数是危险的,应该避免使用。虽然它们更容易理解,在这段说明性代码中也能很好地发挥作用。如果软浮点是一个问题,那么[0;100] DC可以映射到[0;0xFFFFU]或固定点等。但这会比硬件浮点慢,后者也保留更多的精度。 - Flip
浮点数处理速度并不是唯一的问题,我也没有谈论危险(这是两个问题)。这不是辅导的地方,但有经验的嵌入式程序员会在合理的情况下避免使用浮点数,这往往是问题所在。 - too honest for this site
另外一个问题是 RAM/堆栈空间。对于 CM4F,例如 FPU 寄存器需要额外 64 字节的堆栈空间。这个内存对于高度并行化的小型设备中的 MT 代码可能会成为主要问题(由于成本原因而更受欢迎)。但对于额外的时钟来说,这也可能是一个时间问题。正如我所写的:嵌入式系统中使用浮点数的大部分情况都是由没有经验或懒惰的程序员引起的。恕我直言,但您在这样的平台上有多少经验?STM32F7 明确不是其中之一(尽管在这里 FPU 寄存器需要占用 128 个字节(double)。F4 的边缘情况。 - too honest for this site
顺便提一下:定点数的精度是恒定的,而浮点数的精度是相对的,因此比较有问题。32位定点数在约为“+/-1E0”的范围内比IEEE754 single具有更高的精度。典型的ADC和DAC都使用整数或定点数。 - too honest for this site
@Olaf 很不幸,我必须与大多数经验不足的程序员一起工作,因此使用浮点数往往更少出错,更快(即使有时没有硬件浮点支持),更简单和更可移植。尽管出于你提到的原因,处理器被过度规格化了(F7s)。 - Flip

1
我整理了答案。

`

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))); 

}

`

测试对我来说没问题

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接