完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980

第34章       STM32H7的定时器应用之TIM1-TIM17的PWM实现

本章教程为大家讲解定时器应用之TIM1 – TIM17所有定时器的PWM实现。实际项目中用到的地方较多,如电机控制、无源蜂鸣器、显示屏背光等场合。

34.1 初学者重要提示

34.2 定时器PWM驱动设计

34.3 定时器板级支持包(bsp_tim_pwm.c)

34.4 定时器驱动移植和使用

34.5 实验例程设计框架

34.6 实验例程说明(MDK)

34.7 实验例程说明(IAR)

34.8 总结

34.1 初学者重要提示

  1. 学习本章节前,务必优先学习第32章,HAL库的几个常用API均作了讲解和举例。
  2. 如果配置的GPIO引脚无法正确输出,注意本章2.1小节,保证是定时器复用支持的引脚。
  3. STM32H7支持TIM1-TIM8,TIM12-TIM17共14个定时器,而中间的TIM9,TIM10,TIM11是不存在的,这点要注意。
  4. STM32H7的PWM输出100MHz也是没问题的。输出效果见本章2.3小节。

34.2 定时器PWM的驱动设计

针对STM32H7的定时器PWM功能,专门设置了一个超级函数,用户可以方便的配置TIM1-TIM17所有定时器的PWM输出。

34.2.1 定时器PWM输出支持的引脚

STM32H7支持的PWM输出引脚如下(未整理互补输出引脚):

    TIM1_CH1,  PA8   PE9   PK1
TIM1_CH2, PA9 PE11
TIM1_CH3, PA10 PE13 PJ9
TIM1_CH4, PA11 PE14 PJ11 TIM2_CH1, PA0 PA5 PA15
TIM2_CH2, PA1 PB3
TIM2_CH3, PA2
TIM2_CH4, PA3 PB11 TIM3_CH1, PA6 PC6 PB4
TIM3_CH2, PA7 PC7 PB5
TIM3_CH3, PB0 PC8
TIM3_CH4, PB1 PC9 TIM4_CH1, PB6 PD12
TIM4_CH2, PB7 PD13
TIM4_CH3, PB8 PD14
TIM4_CH4, PB9 PD15 TIM5_CH1, PA0 PH10
TIM5_CH2, PA1 PH11
TIM5_CH3, PA2 PH12
TIM5_CH4, PA3 PI0 TIM8_CH1, PC6 PI5 PJ8
TIM8_CH2, PC7 PI6 PJ10
TIM8_CH3, PC8 PI7 PK0
TIM8_CH4, PC9 TIM12_CH1, PB14 PH6
TIM12_CH2, PB15 PH9 TIM13_CH1, PF8 TIM14_CH1, PF9 TIM15_CH1, PE5
TIM15_CH2, PE6 TIM16_CH1, PB8 PF6
TIM16_CH2, PF7 TIM17_CH1, PB9

使用时,直接配置定时器PWM模式,并配置相应引脚即可使用。

34.2.2 定时器PWM初始化

下面函数的作用是根据使用的是GPIO,使能相应的GPIO时钟。

.    /*
2. ******************************************************************************************************
3. * 函 数 名: bsp_RCC_GPIO_Enable
4. * 功能说明: 使能GPIO时钟
5. * 形 参: GPIOx GPIOA - GPIOK
6. * 返 回 值: 无
7. ******************************************************************************************************
8. */
. void bsp_RCC_GPIO_Enable(GPIO_TypeDef* GPIOx)
. {
. if (GPIOx == GPIOA) __HAL_RCC_GPIOA_CLK_ENABLE();
. else if (GPIOx == GPIOB) __HAL_RCC_GPIOB_CLK_ENABLE();
. else if (GPIOx == GPIOC) __HAL_RCC_GPIOC_CLK_ENABLE();
. else if (GPIOx == GPIOD) __HAL_RCC_GPIOD_CLK_ENABLE();
. else if (GPIOx == GPIOE) __HAL_RCC_GPIOE_CLK_ENABLE();
. else if (GPIOx == GPIOF) __HAL_RCC_GPIOF_CLK_ENABLE();
. else if (GPIOx == GPIOG) __HAL_RCC_GPIOG_CLK_ENABLE();
. else if (GPIOx == GPIOH) __HAL_RCC_GPIOH_CLK_ENABLE();
. else if (GPIOx == GPIOI) __HAL_RCC_GPIOI_CLK_ENABLE();
. else if (GPIOx == GPIOJ) __HAL_RCC_GPIOJ_CLK_ENABLE();
. else if (GPIOx == GPIOK) __HAL_RCC_GPIOK_CLK_ENABLE();
. }

下面函数的作用是根据使用的定时器,使能和禁止相应的定时器时钟。

.    /*
2. ******************************************************************************************************
3. * 函 数 名: bsp_RCC_TIM_Enable
4. * 功能说明: 使能TIM RCC 时钟
5. * 形 参: TIMx TIM1 - TIM17
6. * 返 回 值: 无
7. ******************************************************************************************************
8. */
. void bsp_RCC_TIM_Enable(TIM_TypeDef* TIMx)
. {
. if (TIMx == TIM1) __HAL_RCC_TIM1_CLK_ENABLE();
. else if (TIMx == TIM2) __HAL_RCC_TIM2_CLK_ENABLE();
. else if (TIMx == TIM3) __HAL_RCC_TIM3_CLK_ENABLE();
. else if (TIMx == TIM4) __HAL_RCC_TIM4_CLK_ENABLE();
. else if (TIMx == TIM5) __HAL_RCC_TIM5_CLK_ENABLE();
. else if (TIMx == TIM6) __HAL_RCC_TIM6_CLK_ENABLE();
. else if (TIMx == TIM7) __HAL_RCC_TIM7_CLK_ENABLE();
. else if (TIMx == TIM8) __HAL_RCC_TIM8_CLK_ENABLE();
. // else if (TIMx == TIM9) __HAL_RCC_TIM9_CLK_ENABLE();
. // else if (TIMx == TIM10) __HAL_RCC_TIM10_CLK_ENABLE();
. // else if (TIMx == TIM11) __HAL_RCC_TIM11_CLK_ENABLE();
. else if (TIMx == TIM12) __HAL_RCC_TIM12_CLK_ENABLE();
. else if (TIMx == TIM13) __HAL_RCC_TIM13_CLK_ENABLE();
. else if (TIMx == TIM14) __HAL_RCC_TIM14_CLK_ENABLE();
. else if (TIMx == TIM15) __HAL_RCC_TIM15_CLK_ENABLE();
. else if (TIMx == TIM16) __HAL_RCC_TIM16_CLK_ENABLE();
. else if (TIMx == TIM17) __HAL_RCC_TIM17_CLK_ENABLE();
. else
. {
. Error_Handler(__FILE__, __LINE__);
. }
. }
.
. /*
35. ******************************************************************************************************
36. * 函 数 名: bsp_RCC_TIM_Disable
37. * 功能说明: 关闭TIM RCC 时钟
38. * 形 参: TIMx TIM1 - TIM17
39. * 返 回 值: TIM外设时钟名
40. ******************************************************************************************************
41. */
. void bsp_RCC_TIM_Disable(TIM_TypeDef* TIMx)
. {
. /*
45. APB1 定时器有 TIM2, TIM3 ,TIM4, TIM5, TIM6, TIM7, TIM12, TIM13, TIM14
46. APB2 定时器有 TIM1, TIM8 , TIM15, TIM16,TIM17
47. */
. if (TIMx == TIM1) __HAL_RCC_TIM3_CLK_DISABLE();
. else if (TIMx == TIM2) __HAL_RCC_TIM2_CLK_DISABLE();
. else if (TIMx == TIM3) __HAL_RCC_TIM3_CLK_DISABLE();
. else if (TIMx == TIM4) __HAL_RCC_TIM4_CLK_DISABLE();
. else if (TIMx == TIM5) __HAL_RCC_TIM5_CLK_DISABLE();
. else if (TIMx == TIM6) __HAL_RCC_TIM6_CLK_DISABLE();
. else if (TIMx == TIM7) __HAL_RCC_TIM7_CLK_DISABLE();
. else if (TIMx == TIM8) __HAL_RCC_TIM8_CLK_DISABLE();
. // else if (TIMx == TIM9) __HAL_RCC_TIM9_CLK_DISABLE();
. // else if (TIMx == TIM10) __HAL_RCC_TIM10_CLK_DISABLE();
. // else if (TIMx == TIM11) __HAL_RCC_TIM11_CLK_DISABLE();
. else if (TIMx == TIM12) __HAL_RCC_TIM12_CLK_DISABLE();
. else if (TIMx == TIM13) __HAL_RCC_TIM13_CLK_DISABLE();
. else if (TIMx == TIM14) __HAL_RCC_TIM14_CLK_DISABLE();
. else if (TIMx == TIM15) __HAL_RCC_TIM15_CLK_DISABLE();
. else if (TIMx == TIM16) __HAL_RCC_TIM16_CLK_DISABLE();
. else if (TIMx == TIM17) __HAL_RCC_TIM17_CLK_DISABLE();
. else
. {
. Error_Handler(__FILE__, __LINE__);
. }
. }

配置定时器的PWM功能时,要是设置引脚的复用模式,下面函数就是起到这个作用。

.    /*
2. ******************************************************************************************************
3. * 函 数 名: bsp_GetAFofTIM
4. * 功能说明: 根据TIM 得到AF寄存器配置
5. * 形 参: TIMx TIM1 - TIM17
6. * 返 回 值: AF寄存器配置
7. ******************************************************************************************************
8. */
. uint8_t bsp_GetAFofTIM(TIM_TypeDef* TIMx)
. {
. uint8_t ret = ;
.
. if (TIMx == TIM1) ret = GPIO_AF1_TIM1;
. else if (TIMx == TIM2) ret = GPIO_AF1_TIM2;
. else if (TIMx == TIM3) ret = GPIO_AF2_TIM3;
. else if (TIMx == TIM4) ret = GPIO_AF2_TIM4;
. else if (TIMx == TIM5) ret = GPIO_AF2_TIM5;
. else if (TIMx == TIM8) ret = GPIO_AF3_TIM8;
. else if (TIMx == TIM12) ret = GPIO_AF2_TIM12;
. else if (TIMx == TIM13) ret = GPIO_AF9_TIM13;
. else if (TIMx == TIM14) ret = GPIO_AF9_TIM14;
. else if (TIMx == TIM15) ret = GPIO_AF4_TIM15;
. else if (TIMx == TIM16) ret = GPIO_AF1_TIM16;
. else if (TIMx == TIM17) ret = GPIO_AF1_TIM17;
. else
. {
. Error_Handler(__FILE__, __LINE__);
. }
.
. return ret;
. }

下面函数的作用是配置用于PWM输出的引脚:

.    /*
2. ******************************************************************************************************
3. * 函 数 名: bsp_ConfigTimGpio
4. * 功能说明: 配置GPIO和TIM时钟, GPIO连接到TIM输出通道
5. * 形 参: GPIOx : GPIOA - GPIOK
6. * GPIO_PinX : GPIO_PIN_0 - GPIO__PIN_15
7. * TIMx : TIM1 - TIM17
8. * 返 回 值: 无
9. ******************************************************************************************************
10. */
. void bsp_ConfigTimGpio(GPIO_TypeDef* GPIOx, uint16_t GPIO_PinX, TIM_TypeDef* TIMx)
. {
. GPIO_InitTypeDef GPIO_InitStruct;
.
. /* 使能GPIO时钟 */
. bsp_RCC_GPIO_Enable(GPIOx);
.
. /* 使能TIM时钟 */
. bsp_RCC_TIM_Enable(TIMx);
.
. GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
. GPIO_InitStruct.Pull = GPIO_PULLUP;
. GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
. GPIO_InitStruct.Alternate = bsp_GetAFofTIM(TIMx);
. GPIO_InitStruct.Pin = GPIO_PinX;
. HAL_GPIO_Init(GPIOx, &GPIO_InitStruct);
. }

当占空比是0%或者100%时,直接设置引脚的高低电平状态。

.    /*
2. ******************************************************************************************************
3. * 函 数 名: bsp_ConfigGpioOut
4. * 功能说明: 配置GPIO为推挽输出。主要用于PWM输出,占空比为0和100的情况。
5. * 形 参: GPIOx : GPIOA - GPIOK
6. * GPIO_PinX : GPIO_PIN_0 - GPIO__PIN_15
7. * 返 回 值: 无
8. ******************************************************************************************************
9. */
. void bsp_ConfigGpioOut(GPIO_TypeDef* GPIOx, uint16_t GPIO_PinX)
. {
. GPIO_InitTypeDef GPIO_InitStruct;
.
. bsp_RCC_GPIO_Enable(GPIOx); /* 使能GPIO时钟 */
.
. GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
. GPIO_InitStruct.Pull = GPIO_NOPULL;
. GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
. GPIO_InitStruct.Pin = GPIO_PinX;
. HAL_GPIO_Init(GPIOx, &GPIO_InitStruct);
. }

下面的函数是实现TIM1 – TIM17进行PWM输出的核心,也是专门供用户调用的。

.    /*
23. ******************************************************************************************************
24. * 函 数 名: bsp_SetTIMOutPWM
25. * 功能说明: 设置引脚输出的PWM信号的频率和占空比. 当频率为0,并且占空为0时,关闭定时器,GPIO输出0;
26. * 当频率为0,占空比为100%时,GPIO输出1.
27. * 形 参: GPIOx : GPIOA - GPIOK
28. * GPIO_Pin : GPIO_PIN_0 - GPIO__PIN_15
29. * TIMx : TIM1 - TIM17
30. * _ucChannel:使用的定时器通道,范围1 - 4
31. * _ulFreq : PWM信号频率,单位Hz (实际测试,可以输出100MHz). 0 表示禁止输出
32. * _ulDutyCycle : PWM信号占空比,单位: 万分之一。如5000,表示50.00%的占空比
33. * 返 回 值: 无
34. ******************************************************************************************************
35. */
. void bsp_SetTIMOutPWM(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, TIM_TypeDef* TIMx, uint8_t _ucChannel,
. uint32_t _ulFreq, uint32_t _ulDutyCycle)
. {
. TIM_HandleTypeDef TimHandle = {};
. TIM_OC_InitTypeDef sConfig = {};
. uint16_t usPeriod;
. uint16_t usPrescaler;
. uint32_t pulse;
. uint32_t uiTIMxCLK;
. const uint16_t TimChannel[+] = {, TIM_CHANNEL_1, TIM_CHANNEL_2, TIM_CHANNEL_3, TIM_CHANNEL_4,
. TIM_CHANNEL_5, TIM_CHANNEL_6};
.
. if (_ucChannel > )
. {
. Error_Handler(__FILE__, __LINE__);
. }
.
. if (_ulDutyCycle == )
. {
. //bsp_RCC_TIM_Disable(TIMx); /* 关闭TIM时钟, 可能影响其他通道 */
. bsp_ConfigGpioOut(GPIOx, GPIO_Pin); /* 配置GPIO为推挽输出 */
. GPIOx->BSRRH = GPIO_Pin; /* PWM = 0 */
. return;
. }
. else if (_ulDutyCycle == )
. {
. //bsp_RCC_TIM_Disable(TIMx); /* 关闭TIM时钟, 可能影响其他通道 */
. bsp_ConfigGpioOut(GPIOx, GPIO_Pin); /* 配置GPIO为推挽输出 */
. GPIOx->BSRRL = GPIO_Pin; /* PWM = 1*/
. return;
. }
.
. /* 下面是PWM输出 */
.
. bsp_ConfigTimGpio(GPIOx, GPIO_Pin, TIMx); /* 使能GPIO和TIM时钟,并连接TIM通道到GPIO */
.
. /*-----------------------------------------------------------------------
73. bsp.c 文件中 void SystemClock_Config(void) 函数对时钟的配置如下:
74.
75. System Clock source = PLL (HSE)
76. SYSCLK(Hz) = 400000000 (CPU Clock)
77. HCLK(Hz) = 200000000 (AXI and AHBs Clock)
78. AHB Prescaler = 2
79. D1 APB3 Prescaler = 2 (APB3 Clock 100MHz)
80. D2 APB1 Prescaler = 2 (APB1 Clock 100MHz)
81. D2 APB2 Prescaler = 2 (APB2 Clock 100MHz)
82. D3 APB4 Prescaler = 2 (APB4 Clock 100MHz)
83.
84. 因为APB1 prescaler != 1, 所以 APB1上的TIMxCLK = APB1 x 2 = 200MHz;
85. 因为APB2 prescaler != 1, 所以 APB2上的TIMxCLK = APB2 x 2 = 200MHz;
86. APB4上面的TIMxCLK没有分频,所以就是100MHz;
87.
88. APB1 定时器有 TIM2, TIM3 ,TIM4, TIM5, TIM6, TIM7, TIM12, TIM13, TIM14,LPTIM1
89. APB2 定时器有 TIM1, TIM8 , TIM15, TIM16,TIM17
90.
91. APB4 定时器有 LPTIM2,LPTIM3,LPTIM4,LPTIM5
92.
93. ----------------------------------------------------------------------- */
. if ((TIMx == TIM1) || (TIMx == TIM8) || (TIMx == TIM15) || (TIMx == TIM16) || (TIMx == TIM17))
. {
. /* APB2 定时器时钟 = 200M */
. uiTIMxCLK = SystemCoreClock / ;
. }
. else
. {
. /* APB1 定时器 = 200M */
. uiTIMxCLK = SystemCoreClock / ;
. }
.
. if (_ulFreq < )
. {
. usPrescaler = - ; /* 分频比 = 10000 */
. usPeriod = (uiTIMxCLK / ) / _ulFreq - ; /* 自动重装的值 */
. }
. else if (_ulFreq < )
. {
. usPrescaler = - ; /* 分频比 = 100 */
. usPeriod = (uiTIMxCLK / ) / _ulFreq - ; /* 自动重装的值 */
. }
. else /* 大于4K的频率,无需分频 */
. {
. usPrescaler = ; /* 分频比 = 1 */
. usPeriod = uiTIMxCLK / _ulFreq - ; /* 自动重装的值 */
. }
. pulse = (_ulDutyCycle * usPeriod) / ;
.
.
. HAL_TIM_PWM_DeInit(&TimHandle);
.
. /* PWM频率 = TIMxCLK / usPrescaler + 1)/usPeriod + 1)*/
. TimHandle.Instance = TIMx;
. TimHandle.Init.Prescaler = usPrescaler;
. TimHandle.Init.Period = usPeriod;
. TimHandle.Init.ClockDivision = ;
. TimHandle.Init.CounterMode = TIM_COUNTERMODE_UP;
. TimHandle.Init.RepetitionCounter = ;
. TimHandle.Init.AutoReloadPreload = ;
. if (HAL_TIM_PWM_Init(&TimHandle) != HAL_OK)
. {
. Error_Handler(__FILE__, __LINE__);
. }
.
. /* 配置定时器PWM输出通道 */
. sConfig.OCMode = TIM_OCMODE_PWM1;
. sConfig.OCPolarity = TIM_OCPOLARITY_HIGH;
. sConfig.OCFastMode = TIM_OCFAST_DISABLE;
. sConfig.OCNPolarity = TIM_OCNPOLARITY_HIGH;
. sConfig.OCNIdleState = TIM_OCNIDLESTATE_RESET;
. sConfig.OCIdleState = TIM_OCIDLESTATE_RESET;
.
. /* 占空比 */
. sConfig.Pulse = pulse;
. if (HAL_TIM_PWM_ConfigChannel(&TimHandle, &sConfig, TimChannel[_ucChannel]) != HAL_OK)
. {
. Error_Handler(__FILE__, __LINE__);
. }
.
. /* 启动PWM输出 */
. if (HAL_TIM_PWM_Start(&TimHandle, TimChannel[_ucChannel]) != HAL_OK)
. {
. Error_Handler(__FILE__, __LINE__);
. }
. }

程序中的注释已经比较详细,这里把几个关键的地方再阐释下:

  • 第39 -40行,HAL库的这两个结构体变量要初始化为0,这个问题在第32章的的4.3和4.4小节有专门说明。
  • 第94 – 120行,计算出要配置的分频和周期。这里要注意一点,因为除了TIM2和TIM5,其它定时器都是16位的,相关寄存器大部分也都是16位的,配置的时候不可以超出0 -65535。这里分频变量usPrescaler和周期变量usPeriod统一按照16位计算,所以有了这几行代码做频率区分,防止超出范围。
  • 第126 – 136行,通过函数HAL_TIM_PWM_Init配置了PWM频率。
  • 第139 – 151行,配置定时器的PWM输出通道,关于结构体成员代表的含义和函数HAL_TIM_PWM_ConfigChannel的用法分别看第32章的3.3和4.4小节。
  • 第154行,启动定时器PWM输出。

34.2.3 定时器PWM输出100MHz的效果

测试PWM输出100MHz方波的效果,因为我的示波器是200MHz带宽,1Gsps采样率的,用来采样100MHz方波的话,仅可以采集到基波(一次谐波,100MHz),而三次谐波(300MHz),五次谐波(500MHz),以此类推都是采集不到的,所以最终的采集应该就是一个标准的100MHz正弦波,实际测试效果完美,就是个100MHz的正弦波。

黄色的是波形,红色的是FFT幅值谱。

实现这个高频率,代码要特别配置,实现如下,注意红字部分:

/*##-1- 配置定时器外设 #######################################*/
htim1.Instance = TIM1;
htim1.Init.Prescaler = ;
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = ;
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = ;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; /*##-2- 使能定时器 ##########################################*/
if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
} /* 配置模式 */
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = ;
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; /* 配置PWM 通道 */
if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
} /* 开启PWM输出 */
if (HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}

34.3 定时器板级支持包(bsp_tim_pwm.c)

定时器驱动文件bsp_tim_pwm.c主要实现了如下两个API供用户调用:

  • bsp_SetTIMOutPWM
  • bsp_SetTIMforInt

这个两个函数都是TIM1-TIM17所有定时器都支持,函数bsp_SetTIMforInt用于定时器周期性中断,下个章节为大家讲解,本小节主要把函数bsp_SetTIMOutPWM做个说明。

34.3.1 函数bsp_SetTIMOutPWM

函数原型:

void bsp_SetTIMOutPWM(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, TIM_TypeDef* TIMx, uint8_t _ucChannel,
uint32_t _ulFreq, uint32_t _ulDutyCycle)

函数描述:

此函数主要用配置定时器的PWM输出。

函数参数:

  • 第1个参数GPIO分组,范围GPIOA – GPIOK。
  • 第2个参数是具体的GPIO引脚,范围GPIO_PIN_0 - GPIO__PIN_15。
  • 第3个参数用于指定使用哪个定时器,参数可以是TIM1 – TIM17所有定时器(不含TIM9,TIM10和TIM11,因为STM32H7不支持这三个定时器)。
  • 第4个参数是使用的定时器通道,范围1-4,分别表示通道1,通道2,通道3和通道4。
  • 第5个参数是要实现的定时器中断频率,单位Hz,如果填0的话,表示关闭。
  • 第6个参数是PWM信号占空比,单位: 万分之一。如5000,表示50.00%的占空比。

注意事项:

  1. PWM频率最好别超过50MHz,因为此函数的源码实现超过50MHz后,计算的已经不准确。10MHz以下基本都是没问题的。

使用举例:

比如配置PB3硬件输出1KHz方波,占空比50%

bsp_SetTIMOutPWM(GPIOB, GPIO_PIN_3,  TIM3,  4, 1000, 5000)

34.4 定时器驱动移植和使用

定时器的移植比较简单:

  • 第1步:复制bsp_tim_pwm.c和bsp_tim_pwm.h到自己的工程目录,并添加到工程里面。
  • 第2步:这几个驱动文件主要用到HAL库的GPIO和TIM驱动文件,简单省事些可以添加所有HAL库.C源文件进来。
  • 第3步,应用方法看本章节配套例子即可。

34.5 实验例程设计框架

通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:

 第1阶段,上电启动阶段:

  • 这部分在第14章进行了详细说明。

  第2阶段,进入main函数:

  • 第1步,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器,LED和串口。
  • 第2步,输出两路PWM以及按键消息处理。

34.6 实验例程说明(MDK)

配套例子:

V7-019_定时器PWM输出(驱动支持TIM1-TIM17)

实验目的:

  1. 学习定时器PWM输出。

实验内容:

  1. 系统上电后驱动了1个软件定时器,每100ms翻转一次LED2,同时PB3和PB15输出1KHz方波,占空比50% 。
  2. TM32H7支持TIM1-TIM8,TIM12-TIM17共14个定时器,而中间的TIM9,TIM10,TIM11是不存在的。

实验操作:

  1. K1键按下,PB1和PB15输出1KHz方波,占空比50%。
  2. K2键按下,PB1和PB15输出10KHz方波,占空比50%。
  3. K3键按下,PB1和PB15输出100KHz方波,占空比50%

PWM输出引脚PB1和PB15的位置:

上电后串口打印的信息:

波特率 115200,数据位 8,奇偶校验位无,停止位 1

程序设计:

系统栈大小分配:

RAM空间用的DTCM:

硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

/*
*********************************************************************************************************
* 函 数 名: bsp_Init
* 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_Init(void)
{
/* 配置MPU */
MPU_Config(); /* 使能L1 Cache */
CPU_CACHE_Enable(); /*
STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
- 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
- 设置NVIV优先级分组为4。
*/
HAL_Init(); /*
配置系统时钟到400MHz
- 切换使用HSE。
- 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
*/
SystemClock_Config(); /*
Event Recorder:
- 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
- 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
*/
#if Enable_EventRecorder == 1
/* 初始化EventRecorder并开启 */
EventRecorderInitialize(EventRecordAll, 1U);
EventRecorderStart();
#endif bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
bsp_InitTimer(); /* 初始化滴答定时器 */
bsp_InitUart(); /* 初始化串口 */
bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */
bsp_InitLed(); /* 初始化LED */
}

MPU配置和Cache配置:

数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。

/*
*********************************************************************************************************
* 函 数 名: MPU_Config
* 功能说明: 配置MPU
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void MPU_Config( void )
{
MPU_Region_InitTypeDef MPU_InitStruct; /* 禁止 MPU */
HAL_MPU_Disable(); /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x24000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x60000000;
MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER1;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /*使能 MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
} /*
*********************************************************************************************************
* 函 数 名: CPU_CACHE_Enable
* 功能说明: 使能L1 Cache
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void CPU_CACHE_Enable(void)
{
/* 使能 I-Cache */
SCB_EnableICache(); /* 使能 D-Cache */
SCB_EnableDCache();
}

主功能:

主程序实现如下操作:

  • K1键按下,PB1和PB15输出1KHz方波,占空比50%。
  • K2键按下,PB1和PB15输出10KHz方波,占空比50%。
  • K3键按下,PB1和PB15输出100KHz方波,占空比50%。
/*
*********************************************************************************************************
* 函 数 名: main
* 功能说明: c程序入口
* 形 参: 无
* 返 回 值: 错误代码(无需处理)
*********************************************************************************************************
*/
int main(void)
{
uint8_t ucKeyCode; /* 按键代码 */ bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程名称和版本等信息 */
PrintfHelp(); /* 打印操作提示 */ bsp_StartAutoTimer(, ); /* 启动1个100ms的自动重装的定时器 */ bsp_SetTIMOutPWM(GPIOB, GPIO_PIN_1, TIM3, , , ); /* PB3硬件输出1KHz方波,占空比50% */
bsp_SetTIMOutPWM(GPIOB, GPIO_PIN_15, TIM12, , , ); /* PB15硬件输出1KHz方波,占空比50% */ /* 进入主程序循环体 */
while ()
{
bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */ /* 判断定时器超时时间 */
if (bsp_CheckTimer())
{
/* 每隔50ms 进来一次 */
bsp_LedToggle();
} /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */
ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
if (ucKeyCode != KEY_NONE)
{
switch (ucKeyCode)
{
case KEY_DOWN_K1: /* K1键按下,PB1和PB15输出1KHz方波,占空比50% */
bsp_SetTIMOutPWM(GPIOB, GPIO_PIN_1, TIM3, , , );
bsp_SetTIMOutPWM(GPIOB, GPIO_PIN_15, TIM12, , , );
break; case KEY_DOWN_K2: /* K2键按下,PB1和PB15输出10KHz方波,占空比50% */
bsp_SetTIMOutPWM(GPIOB, GPIO_PIN_1, TIM3, , , );
bsp_SetTIMOutPWM(GPIOB, GPIO_PIN_15, TIM12, , , );
break; case KEY_DOWN_K3: /* K3键按下,PB1和PB15输出100KHz方波,占空比50% */
bsp_SetTIMOutPWM(GPIOB, GPIO_PIN_1, TIM3, , , );
bsp_SetTIMOutPWM(GPIOB, GPIO_PIN_15, TIM12, , , );
break; default:
/* 其它的键值不处理 */
break;
}
}
}
}

34.7 实验例程说明(IAR)

配套例子:

V7-019_定时器PWM输出(驱动支持TIM1-TIM17)

实验目的:

  1. 学习定时器PWM输出。

实验内容:

  1. 系统上电后驱动了1个软件定时器,每100ms翻转一次LED2,同时PB3和PB15输出1KHz方波,占空比50% 。
  2. TM32H7支持TIM1-TIM8,TIM12-TIM17共14个定时器,而中间的TIM9,TIM10,TIM11是不存在的。

实验操作:

  1. K1键按下,PB1和PB15输出1KHz方波,占空比50%。
  2. K2键按下,PB1和PB15输出10KHz方波,占空比50%。
  3. K3键按下,PB1和PB15输出100KHz方波,占空比50%

PWM输出引脚PB1和PB15的位置:

上电后串口打印的信息:

波特率 115200,数据位 8,奇偶校验位无,停止位 1

程序设计:

系统栈大小分配:

RAM空间用的DTCM:

硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

/*
*********************************************************************************************************
* 函 数 名: bsp_Init
* 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_Init(void)
{
/* 配置MPU */
MPU_Config(); /* 使能L1 Cache */
CPU_CACHE_Enable(); /*
STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
- 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
- 设置NVIV优先级分组为4。
*/
HAL_Init(); /*
配置系统时钟到400MHz
- 切换使用HSE。
- 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
*/
SystemClock_Config(); /*
Event Recorder:
- 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
- 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
*/
#if Enable_EventRecorder == 1
/* 初始化EventRecorder并开启 */
EventRecorderInitialize(EventRecordAll, 1U);
EventRecorderStart();
#endif bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
bsp_InitTimer(); /* 初始化滴答定时器 */
bsp_InitUart(); /* 初始化串口 */
bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */
bsp_InitLed(); /* 初始化LED */
}

MPU配置和Cache配置:

数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。

/*
*********************************************************************************************************
* 函 数 名: MPU_Config
* 功能说明: 配置MPU
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void MPU_Config( void )
{
MPU_Region_InitTypeDef MPU_InitStruct; /* 禁止 MPU */
HAL_MPU_Disable(); /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x24000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x60000000;
MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER1;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /*使能 MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
} /*
*********************************************************************************************************
* 函 数 名: CPU_CACHE_Enable
* 功能说明: 使能L1 Cache
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void CPU_CACHE_Enable(void)
{
/* 使能 I-Cache */
SCB_EnableICache(); /* 使能 D-Cache */
SCB_EnableDCache();
}

主功能:

主程序实现如下操作:

  • K1键按下,PB1和PB15输出1KHz方波,占空比50%。
  • K2键按下,PB1和PB15输出10KHz方波,占空比50%。
  • K3键按下,PB1和PB15输出100KHz方波,占空比50%。
/*
*********************************************************************************************************
* 函 数 名: main
* 功能说明: c程序入口
* 形 参: 无
* 返 回 值: 错误代码(无需处理)
*********************************************************************************************************
*/
int main(void)
{
uint8_t ucKeyCode; /* 按键代码 */ bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程名称和版本等信息 */
PrintfHelp(); /* 打印操作提示 */ bsp_StartAutoTimer(, ); /* 启动1个100ms的自动重装的定时器 */ bsp_SetTIMOutPWM(GPIOB, GPIO_PIN_1, TIM3, , , ); /* PB3硬件输出1KHz方波,占空比50% */
bsp_SetTIMOutPWM(GPIOB, GPIO_PIN_15, TIM12, , , ); /* PB15硬件输出1KHz方波,占空比50% */ /* 进入主程序循环体 */
while ()
{
bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */ /* 判断定时器超时时间 */
if (bsp_CheckTimer())
{
/* 每隔50ms 进来一次 */
bsp_LedToggle();
} /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */
ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
if (ucKeyCode != KEY_NONE)
{
switch (ucKeyCode)
{
case KEY_DOWN_K1: /* K1键按下,PB1和PB15输出1KHz方波,占空比50% */
bsp_SetTIMOutPWM(GPIOB, GPIO_PIN_1, TIM3, , , );
bsp_SetTIMOutPWM(GPIOB, GPIO_PIN_15, TIM12, , , );
break; case KEY_DOWN_K2: /* K2键按下,PB1和PB15输出10KHz方波,占空比50% */
bsp_SetTIMOutPWM(GPIOB, GPIO_PIN_1, TIM3, , , );
bsp_SetTIMOutPWM(GPIOB, GPIO_PIN_15, TIM12, , , );
break; case KEY_DOWN_K3: /* K3键按下,PB1和PB15输出100KHz方波,占空比50% */
bsp_SetTIMOutPWM(GPIOB, GPIO_PIN_1, TIM3, , , );
bsp_SetTIMOutPWM(GPIOB, GPIO_PIN_15, TIM12, , , );
break; default:
/* 其它的键值不处理 */
break;
}
}
}
}

34.8 总结

本章节就为大家讲解这么多,相对比较容易掌握,望初学者熟练运用。

【STM32H7教程】第34章 STM32H7的定时器应用之TIM1-TIM17的PWM实现的更多相关文章

  1. 【STM32H7教程】第33章 STM32H7的定时器应用之TIM1-TIM17的中断实现

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第33章       STM32H7的定时器应用之TIM1-T ...

  2. 【STM32H7教程】第22章 STM32H7的SysTick实现多组软件定时器

    完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980 第22章       STM32H7的SysTick实现 ...

  3. 【STM32H7教程】第32章 STM32H7的TIM定时器基础知识和HAL库API

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第32章       STM32H7的TIM定时器基础知识和H ...

  4. 【STM32H7教程】第60章 STM32H7的DAC应用之定时器触发实现DMA方式双通道波形

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第60章       STM32H7的DAC应用之定时器触发实 ...

  5. 【STM32H7教程】第12章 STM32H7的HAL库框架设计学习

    完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980 第12章       STM32H7的HAL库框架设计学 ...

  6. 【STM32H7教程】第30章 STM32H7的USART应用之八个串口FIFO实现

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第30章       STM32H7的USART应用之八个串口 ...

  7. 【STM32H7教程】第20章 STM32H7的GPIO应用之无源蜂鸣器

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第20章       STM32H7的GPIO应用之无源蜂鸣器 ...

  8. 【STM32H7教程】第54章 STM32H7的LTDC应用之LCD电阻触摸和电容触摸

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第54章       STM32H7的LTDC应用之LCD电阻 ...

  9. 【STM32H7教程】第51章 STM32H7的LTDC应用之LCD汉字显示和2D图形显示

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第51章       STM32H7的LTDC应用之LCD汉字 ...

随机推荐

  1. 痞子衡嵌入式:恩智浦i.MX RTxxx系列MCU启动那些事(4)- OTP及其烧写方法

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是恩智浦i.MX RTxxx系列MCU的OTP. 在i.MXRTxxx启动系列第二篇文章 Boot配置(ISP Pin, OTP) 里痞子 ...

  2. 微信小程序——template详细使用

    WXML提供模板(template),可以在模板中定义代码片段,然后在不同的地方调用减少冗余代码. 1.1定义模板 1.1.1.创建模板文件夹  1.1.2.使用 name 属性,作为模板的名字.然后 ...

  3. Multi-Camera Coordination and Control in Surveillance Systems: A Survey 阅读笔记

    原文: Natarajan, Prabhu, Pradeep K. Atrey, and Mohan Kankanhalli. "Multi-camera coordination and ...

  4. 每个开发人员都应该知道的11个Linux命令

    本文主要挑选出读者有必要首先学习的 11 个 Linux 命令,如果不熟悉的读者可以在虚拟机或云服务器上实操下,对于开发人员来说,能熟练掌握 Linux 做一些基本的操作是必要的! 事不宜迟,这里有 ...

  5. [AHOI2017初中组]guide

    题目描述 农场主John最近在网上买了一辆新车,在购买汽车配件时,John不小心点了两次"提交"按钮.导致汽车上安装了两套GPS系统,更糟糕的是John在使用GPS导航时,两套系统 ...

  6. CoderForces999D-Equalize the Remainders

    D. Equalize the Remainders time limit per test 3 seconds memory limit per test 256 megabytes input s ...

  7. ZOJ 3195 Design the city (LCA 模板题)

    Cerror is the mayor of city HangZhou. As you may know, the traffic system of this city is so terribl ...

  8. numpy sum axis详解

    axis 先看懂numpy.argmax的含义.那么numpy.sum就非常好理解. 看一维的例子. import numpy as np a = np.array([1, 5, 5, 2]) pri ...

  9. Redis KeyExpire的使用

    Set a timeout on key. After the timeout has expired, the key will automatically be deleted. A key wi ...

  10. JS页面跳转和打开新窗口方式

    1.window.location.href=URL : 在本窗体打开一个新的页面,也是最常用的一种方法: 2.window.open(URL)  :  在一个新的窗口打开一个新的页面: 3.loca ...