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 }