草庐IT

STM32 TIM PWM中阶操作:互补PWM输出

PegasusYu 2023-05-15 原文

STM32 TIM PWM中阶操作详解:互补PWM输出

STM32 TIM可以输出管脚PWM信号适合多种场景使用,功能包括单线/非互补PWM输出,双线/互补PWM输出,以及死区时间和刹车控制等。

实际上,因为早期IP Core的缺陷,早期的芯片包括STM32F1, STM32F2, STM32F3, STM32F4, STM32F7在应用于多路互补PWM时存在缺陷,所以在后期的芯片包括STM32F0, STM32H7,STM32G0, STM32C0,STM32L等系列,增加了TIM16和TIM17可以输出互补PWM信号,原因会在本文里做介绍。

STM32输出3组互补PWM的场景为无刷直流电机的三相驱动,STM32输出2组互补PWM的场景为H桥驱动。本文里以H桥全桥驱动超声波设备为例,说明驱动的方式以及早期芯片存在的问题。

H桥介绍

H桥相当于两组推挽半桥组成的电路,负载跨在中间,在一个半周期左上臂到右下臂(Arm A)导通,在另外一个半周期右上臂和左下臂(Arm B)导通,从而经过负载的是交流信号,可以驱动交流负载如超声波雾化片,超声波清洗器等。在一个上臂和一个下臂导通时,另外的一个上臂和下臂必须处于关闭状态,否则会产生短路冲击。

H桥实现器件有两种,一种用三极管来实现,一种用MOS管来实现.原理是相似的,只是对电流的控制方式不同,一种是流控,一种是压控。

H桥实现方式有里两种,一种上臂是两个PMOS, 下臂是两个NMOS:

另一种通过浮压控制,实现上臂两个MOS管也用NMOS,所以上臂每个NMOS管到桥驱动端会多接一根线:

一般用集成芯片做桥驱动端具有更好的可靠性,而桥驱动端的输入来自外部相位脉冲。这里就是用STM32的PWM功能产生2组互补PWM输出给桥驱动端。

H桥驱动时序

H桥的驱动时序要点为:

  1. 在半臂导通前,另外半臂必须提前关闭,否则会产生强短路冲击,这个提前关闭的时间就是死区时间
  2. 在同半周期导通的半臂里,下半臂必须早于或等于上半臂导通,否则会产生弱短路冲击

这里以STM32F030K6T6为例介绍中阶的互补PWM输出方式,采用STM32CUBEIDE工具。

STM32 PWM系统时钟配置

在应用PWM时,考虑到计算的便捷性和整除性,可以将芯片的时钟配置为10的倍数,如最大84MHz的芯片配置为80MHz,最大72MHz的芯片配置为70MHz,最大48MHz的芯片配置为40MHz。

STM32F030K6T6频率最大48MHz,所以可以配置为40MHz,采用内部和外部时钟倍频都可以。这里配置为采用内部时钟倍频到40MHz。

STM32 互补PWM输出介绍及问题说明

这里配置输出TIM1的PWM通道1, 选择内部时钟:

可以看到,在TIM1里,就可以配置多个互补PWM输出通道。如选择了Channel1为PWM Generation CH1 CH1N, 则是配置了2个输出管脚,并且参数里需要对这2个输出管脚的输出特性进行配置。

这里我们也配置Channel2为PWM Generation CH2 CH2N, 总共就是配置里2组互补PWM输出,即4用4个管脚输出PWM信号。

Active-Break-Input是使能刹车功能,这里不使能,会在《STM32 TIM PWM高阶操作:刹车及状态约束》里做介绍。

通常将PWM输出的管脚驱动能力调高:

这里的输出不涉及DMA和中断使用,直接进行参数部分的配置,如配置为40KHz频率。

这里不介绍刹车部分的功能控制,但需要控制死区时间:

死区时间控制是互补PWM输出的一项重要功能, 首先要注意的要点是:STM32的互补PWM输出设计概念上,以自身管脚最终输出高电平作为外部MOS(也可以是三极管,这里不做区分,以MOS管做描述)导通的逻辑,也就是说,对于上臂的MOS管,STM32管脚输出高电平,则MOS管输出高电平,对于下臂的MOS管,STM32管脚输出高电平,则MOS管输出低电平,所以当上臂一个MOS管和下臂一个MOS管都接收到MCU控制的高电平,则形成一条流过负载的电流导通路径,而当经过半个时钟周期,这两个MOS管接收到MCU控制的低电平,则是都截止,而另外半臂一上一下两个MOS管会导通,想成对负载反向的电流导通。Dead Time的设置,则是将STM32管脚输出的脉冲的高电平的前沿缩回一定时间,从而保证要导通的半臂,MCU输出的两个管脚的高电平前有一段低电平时间,这就是死区时间,在这个时间里保证前面的半臂完全关闭后,MCU再释放出高电平。Dead Time参数的设置,可以查阅文档进行计算,但更好的方式是设置后直接用示波器测试调整,避免计算失误。
如下为同一个管脚无死区时间保持50%占空比的波形和有死区时间占空比变化的波形:

这样,对于不同半臂轮流截止导通时,MCU输出给两个半臂的波形关系如下,可见,实现了死区时间(dt)的效果。

而实现H桥的配置方式有两种,问题将出现。
1. 将同一个通道的互补PWM设置成管脚同相输出。这样实现控制的连接关系将是:

注意这里“LOAD"不是指单纯的负载,而是由H桥驱动芯片,H桥和负载组成。H桥驱动芯片可以用一颗全桥驱动芯片或2颗半桥驱动芯片实现。H桥驱动芯片和H桥的MOS管配合,提供给负载足够的电压和功率。STM32输出的低电压信号控制的是时序。典型的高压半桥驱动芯片有IR2110等。
参数配置为:

Mode选为PWM Mode1, 40KHz的占空比选择为50%,则脉冲高电平时间为125us,因此Pulse里填写124。Output compare preload设置程序运行时实时修改Pulse参数立即生效(disable)还是当前周期完成后生效(enable),这里选enable或disable都可以。Fast Mode用于管脚配置为open drain模式时的快速驱动模式选择,这里用disable。
注意初始输出相位电平CH Polarity和CHN Polarity的配置,在PWM Mode1下,配置CH Polarity为High,则CH对应的管脚输出高电平,配置CH Polarity为Low, 则CH对应的管脚输出低电平,而配置CHN Polarity则是反电平配置,即,配置CHN Polarity为High,则CHN对应的管脚输出低电平,而配置CHN Polarity为Low,则CHN对应的管脚输出高电平。因此将CH1和CH1N配置为High和Low,则是同相电平输出,初始输出从高电平开始,而配置CH2和CH2N为Low和High,也是同相电平输出,初始输出从低电平开始。
CH Idle State和CHN Idle State这里不用,随意设置,会在《STM32 TIM PWM高阶操作:刹车及状态约束》里做介绍。
保存生成初始工程后,在main文件里的while循环之前执行PWM通道使能:

  HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);
  HAL_TIMEx_PWMN_Start(&htim1,TIM_CHANNEL_1);
  HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_2);
  HAL_TIMEx_PWMN_Start(&htim1,TIM_CHANNEL_2);

测得STM32输出给ARM A一个控制信号和给ARM B一个控制信号的信号相位为:

测得的STM32输出给ARM A两个控制的信号(或STM32输出给ARM B两个控制的信号)相位为:

这个时候,输出的信号相位是正常的,但没有死区时间,控制H桥需要加入死区时间,然后问题就会出现:

保存升级工程代码后,进行测试。
测得STM32输出给ARM A一个控制信号和给ARM B一个控制信号的信号相位为:

死区时间出现,相位没有问题。

测得的STM32输出给ARM A两个控制的信号相位为:

输出相位出现不对齐,蓝线波形是输出给上臂MOS管的信号,黄线波形是输出给下臂MOS管的信号,但输出给下臂MOS管的信号高电平宽于输出给上臂MOS管的信号高电平,时序能用。


测得的STM32输出给ARM B两个控制的信号相位为:

输出相位出现了问题
,输出给下臂MOS管的信号高电平应该等于或宽于输出给上臂MOS管的信号高电平,否则会出现弱短路冲击。

@1 : 因此在配置了死区时间后,对于CH CHN配置为High和Low时,输出的高电平时间长度,CHN大于CH,而CH CHN配置为Low和High时,输出的高电平时间长度,CHN小于CH。

@2: 一个需要注意的重要概念是,TIM1的某一个通道使能,则所有配置的通道输出的相位关系就按照配置的初始相位关系生效了,举例而言,当使能TIM1的CH1输出后(HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);),无论延时多少时间使能CH1N输出(HAL_TIMEx_PWMN_Start(&htim1,TIM_CHANNEL_1);),CH1N管脚输出的信号和CH管脚输出的信号的相位关系不变,也就是说,CH1N使能输出的时候,并不是输出一个脉冲周期的高电平部分的最前沿,而可能是一个从一个脉冲周期的任何一个时间点输出。

因为@2这样一个原理概念,所以当在多互补PWM通道的TIM如TIM1要输出两组互补PWM信号控制H桥时,两组互补PWM的输出相位,从一开始就要配置好,所以一组的CH CHN配置为High Low时,另外一组的CH CHN就需要配置为Low High,以实现一组关闭另一组打开的时序配置。而由于@1的bug原因,有一个半桥导通的时候,上管先于下管导通,会造成弱短路冲击。

因此,将同一个通道的互补PWM设置成管脚同相输出,由于芯片IP Core bug的存在,不能实现H桥的良好控制。

2. 将同一个通道的互补PWM设置成管脚反向输出,这样实现的连接关系将是:

对应的配置为:

同样进行测试 ,测得的STM32输出给ARM A两个控制信号的相位为:

蓝线波形是输出给上臂MOS管的信号,黄线波形是输出给下臂MOS管的信号,相位没有问题,但应该有所预感了。
测得的STM32输出给ARM B两个控制信号的相位为:

蓝线波形是输出给上臂MOS管的信号,黄线波形是输出给下臂MOS管的信号,相位同样出现问题。上管先于下管导通,会造成弱短路冲击。

结论:在同一个TIM里,设置2个互补PWM通道输出的时序,不能良好的符合H桥驱动原理。

解决方案

采用互补PWM输出的解决方案是,两路互补PWM输出在不同的TIM里,因此,在STM32后来的STM32F0, STM32L, STM32H, STM32G0, STM32C0各系列里,都有TIM16和TIM17,可以配置互补PWM输出,实现了bug的修正。也就是有3个TIM,每个都可以配置互补PWM输出。但是对于老型号的STM32F1, STM32F2, STM32F3, STM32F4, STM32F7, 则没有具有互补PWM输出的TIM16, TIM17。

前面已经展示,在两个互补PWM配置里,当通道初始态配置不相同时,会出现时序问题,如CH1 CH1N配置为High Low,CH2 CH2N配置为Low High; 或CH1 CH1N配置为High High,CH2 CH2N配置为Low Low,都会出现时序问题。那么,当有多个可产生互补PWM的TIM时,如何实现解决方案?

答案就是把TIMA和TIMB的CH CHN配置成同样的状态,然后,因为互补PWM是在不同的TIM里,所以可以把TIMB延迟1/2周期使能,从而实现正确的时序配合。

有TIM1, TIM16, TIM17可以选择,这里用TIM16作为TIMA, TIM17作为TIMB。

对应如下连接的配置为:

保存升级代码后,首先使能TIM16, 再延迟半个周期使能TIM17, 即:

  HAL_TIM_PWM_Start(&htim16,TIM_CHANNEL_1);
  HAL_TIMEx_PWMN_Start(&htim16,TIM_CHANNEL_1);
  PY_Delay_us_t(16);i>>=1;i>>=1;
  HAL_TIMEx_PWMN_Start(&htim17,TIM_CHANNEL_1);
  HAL_TIM_PWM_Start(&htim17,TIM_CHANNEL_1);

PY_Delay_us_t()是微秒级延时函数,参考 STM32 HAL us delay(微秒延时)的指令延时实现方式及优化 。i>>=1;实现纳秒级延时,参考STM32 纳秒级延时 (ns delay) 的指令延时实现方式及测定。由于不同TIM通道启动时从寄存器配置到管脚输出信号的延时有偏差,要用示波器实际测试调整延时,以实现更准确的半周期延时。

测得的STM32输出给ARM A两个控制信号的相位为:


上图蓝色为下臂控制信号,黄色为上臂控制信号。这里要注意,下臂控制信号上升沿要早于或等于上臂控制信号上升沿。而下降沿对应关断MOS管,所以下降沿的对位偏差是允许的,哪一个臂早一点关断影响微小。所以导通时的上臂和下臂时序符合要求。

测得的STM32输出给ARM A下臂和ARM B下臂的相位为:

一个臂在导通前(上升沿),和另外一个臂有共同的关断时间(低电平),体现出了死区时间,时序也正常。实际上,把死区时间设置大一些可以显示得更明显。

以上解决方案输出了40KHz的H桥控制逻辑,可用于驱动超声波设备。

主函数代码及工程

主函数代码:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2022 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  *Written by Pegasus Yu in 2022
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
TIM_HandleTypeDef htim16;
TIM_HandleTypeDef htim17;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM16_Init(void);
static void MX_TIM17_Init(void);
/* USER CODE BEGIN PFP */
__IO float usDelayBase;
void PY_usDelayTest(void)
{
  __IO uint32_t firstms, secondms;
  __IO uint32_t counter = 0;

  firstms = HAL_GetTick()+1;
  secondms = firstms+1;

  while(uwTick!=firstms) ;

  while(uwTick!=secondms) counter++;

  usDelayBase = ((float)counter)/1000;
}

void PY_Delay_us_t(uint32_t Delay)
{
  __IO uint32_t delayReg;
  __IO uint32_t usNum = (uint32_t)(Delay*usDelayBase);

  delayReg = 0;
  while(delayReg!=usNum) delayReg++;
}

void PY_usDelayOptimize(void)
{
  __IO uint32_t firstms, secondms;
  __IO float coe = 1.0;

  firstms = HAL_GetTick();
  PY_Delay_us_t(1000000) ;
  secondms = HAL_GetTick();

  coe = ((float)1000)/(secondms-firstms);
  usDelayBase = coe*usDelayBase;
}

void PY_Delay_us(uint32_t Delay)
{
  __IO uint32_t delayReg;

  __IO uint32_t msNum = Delay/1000;
  __IO uint32_t usNum = (uint32_t)((Delay%1000)*usDelayBase);

  if(msNum>0) HAL_Delay(msNum);

  delayReg = 0;
  while(delayReg!=usNum) delayReg++;
}

uint8_t i=0;
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/*
 *    PA6(T16C1)          PA7(T17C1)
 *    |                    |
 *    |                    |
 *    ----------|-|-----------
 *    |                    |
 *    |                    |
 *    PB6(T16C1N)         PB7(T17C1N)
 *
 *    H bridge indication
 */


/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM16_Init();
  MX_TIM17_Init();
  /* USER CODE BEGIN 2 */
  PY_usDelayTest();
  PY_usDelayOptimize();

  __disable_irq();

  HAL_TIM_PWM_Start(&htim16,TIM_CHANNEL_1);
  HAL_TIMEx_PWMN_Start(&htim16,TIM_CHANNEL_1);
  PY_Delay_us_t(16); i>>=1;i>>=1;
  HAL_TIMEx_PWMN_Start(&htim17,TIM_CHANNEL_1);
  HAL_TIM_PWM_Start(&htim17,TIM_CHANNEL_1);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL10;
  RCC_OscInitStruct.PLL.PREDIV = RCC_PREDIV_DIV2;
  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_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief TIM16 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM16_Init(void)
{

  /* USER CODE BEGIN TIM16_Init 0 */

  /* USER CODE END TIM16_Init 0 */

  TIM_OC_InitTypeDef sConfigOC = {0};
  TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};

  /* USER CODE BEGIN TIM16_Init 1 */

  /* USER CODE END TIM16_Init 1 */
  htim16.Instance = TIM16;
  htim16.Init.Prescaler = 3;
  htim16.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim16.Init.Period = 249;
  htim16.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim16.Init.RepetitionCounter = 0;
  htim16.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim16) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim16) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 124;
  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(&htim16, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
  sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
  sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
  sBreakDeadTimeConfig.DeadTime = 20;
  sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
  sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
  sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
  if (HAL_TIMEx_ConfigBreakDeadTime(&htim16, &sBreakDeadTimeConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM16_Init 2 */

  /* USER CODE END TIM16_Init 2 */
  HAL_TIM_MspPostInit(&htim16);

}

/**
  * @brief TIM17 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM17_Init(void)
{

  /* USER CODE BEGIN TIM17_Init 0 */

  /* USER CODE END TIM17_Init 0 */

  TIM_OC_InitTypeDef sConfigOC = {0};
  TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};

  /* USER CODE BEGIN TIM17_Init 1 */

  /* USER CODE END TIM17_Init 1 */
  htim17.Instance = TIM17;
  htim17.Init.Prescaler = 3;
  htim17.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim17.Init.Period = 249;
  htim17.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim17.Init.RepetitionCounter = 0;
  htim17.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim17) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim17) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 124;
  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(&htim17, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
  sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
  sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
  sBreakDeadTimeConfig.DeadTime = 20;
  sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
  sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
  sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
  if (HAL_TIMEx_ConfigBreakDeadTime(&htim17, &sBreakDeadTimeConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM17_Init 2 */

  /* USER CODE END TIM17_Init 2 */
  HAL_TIM_MspPostInit(&htim17);

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
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 */

范例下载

基于STM32F030的H桥驱动范例下载(STM32CUBEIDE开发平台HAL库工程)

–End–

有关STM32 TIM PWM中阶操作:互补PWM输出的更多相关文章

  1. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

    为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

  2. ruby - 通过 erb 模板输出 ruby​​ 数组 - 2

    我正在使用puppet为ruby​​程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby​​不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这

  3. ruby - 如何进行排列以有效地定制输出 - 2

    这是一道面试题,我没有答对,但还是很好奇怎么解。你有N个人的大家庭,分别是1,2,3,...,N岁。你想给你的大家庭拍张照片。所有的家庭成员都排成一排。“我是家里的friend,建议家庭成员安排如下:”1岁的家庭成员坐在这一排的最左边。每两个坐在一起的家庭成员的年龄相差不得超过2岁。输入:整数N,1≤N≤55。输出:摄影师可以拍摄的照片数量。示例->输入:4,输出:4符合条件的数组:[1,2,3,4][1,2,4,3][1,3,2,4][1,3,4,2]另一个例子:输入:5输出:6符合条件的数组:[1,2,3,4,5][1,2,3,5,4][1,2,4,3,5][1,2,4,5,3][

  4. ruby - 将 spawn() 的标准输出/标准错误重定向到 Ruby 中的字符串 - 2

    我想使用spawn(针对多个并发子进程)在Ruby中执行一个外部进程,并将标准输出或标准错误收集到一个字符串中,其方式类似于使用Python的子进程Popen.communicate()可以完成的操作。我尝试将:out/:err重定向到一个新的StringIO对象,但这会生成一个ArgumentError,并且临时重新定义$stdxxx会混淆子进程的输出。 最佳答案 如果你不喜欢popen,这是我的方法:r,w=IO.pipepid=Process.spawn(command,:out=>w,:err=>[:child,:out])

  5. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

  6. ruby - Ruby 是否使用 $stdout 来写入 puts 和 return 的输出? - 2

    我想知道Ruby用来在命令行打印这些东西的输出流:irb(main):001:0>a="test"=>"test"irb(main):002:0>putsatest=>nilirb(main):003:0>a=>"test"$stdout是否用于irb(main):002:0>和irb(main):003:0>?而且,在这两次调用之间,$stdout的值是否有任何变化?另外,有人能告诉我打印/写入这些内容的Ruby源代码吗? 最佳答案 是的。而且很容易向自己测试/证明。在命令行试试这个:ruby-e'puts"foo"'>test.

  7. ruby-on-rails - 无法在 Rails 助手中捕获 block 的输出 - 2

    我在使用自定义RailsFormBuilder时遇到了问题,从昨天晚上开始我就发疯了。基本上我想对我的构建器方法之一有一个可选block,以便我可以在我的主要content_tag中显示其他内容。:defform_field(method,&block)content_tag(:div,class:'field')doconcatlabel(method,"Label#{method}")concattext_field(method)capture(&block)ifblock_given?endend当我在我的一个Slim模板中调用该方法时,如下所示:=f.form_field:e

  8. ruby - 如何使用 Selenium Webdriver 根据 div 的内容执行操作? - 2

    我有一个使用SeleniumWebdriver和Nokogiri的Ruby应用程序。我想选择一个类,然后对于那个类对应的每个div,我想根据div的内容执行一个Action。例如,我正在解析以下页面:https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=puppies这是一个搜索结果页面,我正在寻找描述中包含“Adoption”一词的第一个结果。因此机器人应该寻找带有className:"result"的div,对于每个检查它的.descriptiondiv是否包含单词“adoption

  9. ruby-on-rails - 如何处理 Grape 中特定操作的过滤器之前? - 2

    我正在我的Rails项目中安装Grape以构建RESTfulAPI。现在一些端点的操作需要身份验证,而另一些则不需要身份验证。例如,我有users端点,看起来像这样:moduleBackendmoduleV1classUsers现在如您所见,除了password/forget之外的所有操作都需要用户登录/验证。创建一个新的端点也没有意义,比如passwords并且只是删除password/forget从逻辑上讲,这个端点应该与用户资源。问题是Grapebefore过滤器没有像except,only这样的选项,我可以在其中说对某些操作应用过滤器。您通常如何干净利落地处理这种情况?

  10. ruby-on-rails - 在 Ruby on Rails 中发送响应之前如何等待多个异步操作完成? - 2

    在我做的一些网络开发中,我有多个操作开始,比如对外部API的GET请求,我希望它们同时开始,因为一个不依赖另一个的结果。我希望事情能够在后台运行。我找到了concurrent-rubylibrary这似乎运作良好。通过将其混合到您创建的类中,该类的方法具有在后台线程上运行的异步版本。这导致我编写如下代码,其中FirstAsyncWorker和SecondAsyncWorker是我编写的类,我在其中混合了Concurrent::Async模块,并编写了一个名为“work”的方法来发送HTTP请求:defindexop1_result=FirstAsyncWorker.new.async.

随机推荐