STM32定时器应用——PWM

STM32的定时器有三种,高级定时器(TIM1和TIM8),通用定时器(TIM2、TIM3、TIM4、TIM5)和基本定时器(TIM6和TIM7)。

这三者的区别是:

  • 基本定时器:基本定时器功能比较简单,主要是计时,也可以为DAC提供时钟,直接触发驱动DAC
  • 通用定时器:通用定时器除了基本的定时功能外,还可以测量输入信号的脉冲长度,也就是输入捕获功能,也可以产生输出波形,即输出比较和PWM。                                                
  • 高级定时器:通用定时器有的功能,高级定时器也有,而且高级定时器还可以输出嵌入死区的互补PWM。 

这几天放假,在家搜到一个NUCLEO-L010RB的开发板,并且在stm社区下载了相关的几份资料,就捣鼓了一下。

这个板子用到的MCU是STM32L010RBT6,这个板子刚开始我还不知道怎么用,不知道烧录程序时是否需要外接ST-Link,最后捣鼓着捣鼓着发现它本身自带ST-Link的功能,USB端口不仅可以给板子供电,还可以用于烧录程序。

第一个程序当然是点亮一个LED灯啦,查阅相关文档发现这个板子的PA5引脚上接了LED2,而且该引脚也是TIM2的CH1通道,那么就可以通过TIM2的CH1输出PWM波控制LED的亮度,实现呼吸灯效果。

一、首先用CubeMX工具生成代码框架:

 这个MCU跟STM32的其他系列有很多的不同,比如定时器方面,这个单片机只有TIM2,TIM21和TIM22。这个程序中用到了TIM2和TIM21这两个定时器,TIM2用于生成PWM调节LED的亮度,TIM21用于定时修改PWM的占空比。

①设置时钟源,原理图上HSE上接的是8MHz的晶振,但是实物上实际并没有外接这个晶振,所以HSE没有配置。LSE上接了32.768KHz的晶振,所以LSE上就选择了Crystal/Ceramic Resonator。如图:

 

 ②定时器设置,如图:

 

  • 定时器的时钟源一般都选择内部时钟源。
  • 这次我们用到TIM2的CH1输出PWM波,所以Channel1应该选择PWM Generation CH1。
  • TIM2挂载在APB1上,而APB1的时钟频率为32MHz,所以TIM2的预分频器设置为32000,分频后得到1KHz,也就是1ms计数一次,计数模式选择向上,ARR设置为20。
  • PWM的模式设置为 PWM mode 1,Pulse设置为0。PWM的模式有两种,模式1:向上计数时,CNT<CCRx时输出有效电平,CNT>=CCRx时输出无效电平。向下计数时,CNT>CCRx时输出无效电平,CNT<=CCRx输出有效电平。模式2:向上计数时,CNT<CCRx时输出无效电平,CNT>=CCRx时输出有效电平。向下计数时,CNT>CCRx时输出有效电平,CNT<=CCRx输出无效电平。二者刚好是相反的。(Pulse就是CCRx的值,这里初始化为0,当然也可以设置为小于ARR大于0的其他数值)
  • CH Polarity选择为High,也就是设置有效电平为高电平。

  TIM21的作用只是定时而已,所以设置比较简单,同样时钟源选择内部时钟,预分频32000,向上计数,ARR为100,因为TIM21挂载在APB2上也是32MHz,所以最终的结果是100ms产生一个中断(TIM21的全局中断需要打开)

 打开TIM21的全局中断:

 ③时钟配置:由图可知,外部高速时钟(HSE)的外部晶振和外部低速时钟(LSE)跟整个时钟树的其他部分是断开的,所以我选择了内部高速时钟(HSI)作为系统时钟源,不过精度肯定没有外部晶振准确,因为HSI采用的是RC振荡器,容易受到温度的影响。

二、代码修改

①首先需要在main()函数里面调用两个函数:

HAL_TIM_Base_Start_IT(&htim21);//开启TIM21的中断
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);//开启PWM的输出

②在TIM21中断的回调函数里增加功能代码:

1 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
 2 {
 3     static uint8_t DutyCycle = 5;
 4     static uint8_t ToggleFlag = 0;
 5     if(htim->Instance == TIM21)
 6     {
 7         if(ToggleFlag == 0)
 8         {
 9             DutyCycle += 1;
10             if(DutyCycle > 19)
11             {
12                 DutyCycle = 19;
13                 ToggleFlag = 1;
14             }
15         }
16         else
17         {
18             DutyCycle -= 1;
19             if(DutyCycle <= 1)
20             {
21                 ToggleFlag = 0;
22             }
23         }
24         
25         TIM2->CCR1 = DutyCycle;
26     }
27 }

TIM21每100ms进入一次中断,就会调用这个回调函数,这个函数里实现的功能就是修改TIM2的CCR1的值,也就是修改PWM的占空比,CCR1每次比上一次的数值增加1(直到19(ARR的值),也就是占空比为1),当CCR1的值达到了19,又开始递减。所以LED的效果就是从暗逐渐变量又从亮逐渐变暗,就好像呼吸一样。关系到呼吸灯的效果有几个参数:

  ①htim2.Init.Prescaler(TIM2的预分频系数),htim2.Init.Period(TIM2 的计数周期,也就是ARR的值),预分频系数越大,得到的频率越小,CNT增加得越慢,可能造成的效果就是看到灯在闪,因为我们看到灯的亮度变化并不是真的可以从本质上改变灯的亮度,而是一个周期内灯亮的时间占了多大的比例,由于一个周期时间很短,短到人眼分辨不出亮灭的变化,就会觉得灯的亮度变化了,所以周期也不应该太长,太长的话,人眼就可以分辨出亮灭变化,就会觉得灯在闪。

  ②CCR1每次的增量太大的话,呼吸灯就没有一个渐变的过程,显得不自然。

  ③htim21.Init.Prescaler 和 htim21.Init.Period这两个参数决定多久更新一次CCR1的数值,也就是决定每一个亮度可以保持多久的时间。

PS:下面是TIM2和TIM21的初始化代码:

 1 void MX_TIM2_Init(void)
 2 {
 3   TIM_ClockConfigTypeDef sClockSourceConfig = {0};
 4   TIM_MasterConfigTypeDef sMasterConfig = {0};
 5   TIM_OC_InitTypeDef sConfigOC = {0};
 6 
 7   htim2.Instance = TIM2;
 8   htim2.Init.Prescaler = 8000-1;
 9   htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
10   htim2.Init.Period = 50-1;
11   htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
12   htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
13   if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
14   {
15     Error_Handler();
16   }
17   sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
18   if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
19   {
20     Error_Handler();
21   }
22   if (HAL_TIM_PWM_Init(&htim2) != HAL_OK)
23   {
24     Error_Handler();
25   }
26   sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
27   sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
28   if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
29   {
30     Error_Handler();
31   }
32   sConfigOC.OCMode = TIM_OCMODE_PWM1;
33   sConfigOC.Pulse = 0;
34   sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
35   sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
36   if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
37   {
38     Error_Handler();
39   }
40   HAL_TIM_MspPostInit(&htim2);
41 
42 }
 1 void MX_TIM21_Init(void)
 2 {
 3   TIM_ClockConfigTypeDef sClockSourceConfig = {0};
 4   TIM_MasterConfigTypeDef sMasterConfig = {0};
 5 
 6   htim21.Instance = TIM21;
 7   htim21.Init.Prescaler = 32000-1;
 8   htim21.Init.CounterMode = TIM_COUNTERMODE_UP;
 9   htim21.Init.Period = 250-1;
10   htim21.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
11   htim21.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
12   if (HAL_TIM_Base_Init(&htim21) != HAL_OK)
13   {
14     Error_Handler();
15   }
16   sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
17   if (HAL_TIM_ConfigClockSource(&htim21, &sClockSourceConfig) != HAL_OK)
18   {
19     Error_Handler();
20   }
21   sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
22   sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
23   if (HAL_TIMEx_MasterConfigSynchronization(&htim21, &sMasterConfig) != HAL_OK)
24   {
25     Error_Handler();
26   }
27 
28 }
原文地址:https://www.cnblogs.com/young-dalong/p/14733260.html