阅读前须知:本文章没有涵盖所有可能的定时器使用方法,遵循本人的一贯原则,只有见过、理解过并且测试过的才会用自己的话写出来,因此,多余部分不常用的就不到处copy凑字啦!
如果后面工程用到,会不定时更新的。
本文使用STM32F103C8T6作为测试芯片,配合cubemax使用。
研究目的
由于使用cubemax生成的代码修改过后再用一次cubemax就会覆盖掉很多东西,不方便重新生成。
对比使用cubemax生成的base code在不同模式下的区别,以便于手动修改。
理解不同模式的定时器对于程序功能的影响。
给出相关案例供参考。
配置界面简介
当我们使用cubemax配置一个定时器的时候,点击左侧的Timers,选择好定时器之后出现如下界面
Channel1-4只是引脚不同,咱们就看一个,Channel4.
Internal Clock
第一种情况,选择internal clock
接着可以选择是否开启中断
Output Compare
第二种情况,选择某一个通道(这里是Timer2-Channel4)
同样可以选择是否开启中断 。
下面来对比开启中断和不开启中断:
两种情况下的开不开启NVIC的区别是一样的,就拿internal clock的举例。
没有开启NVIC的情况下,代码如下:
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM2)
{
__HAL_RCC_TIM2_CLK_ENABLE();
}
}
void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM2)
{
__HAL_RCC_TIM2_CLK_DISABLE();
}
}
开启之后的代码为:
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM2)
{
__HAL_RCC_TIM2_CLK_ENABLE();
HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM2_IRQn);
}
}
void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM2)
{
__HAL_RCC_TIM2_CLK_DISABLE();
HAL_NVIC_DisableIRQ(TIM2_IRQn);
}
}
可以看到,区别就是设置中断优先级和使能中断(关闭中断)。
那么可以说明,如果你配置cubemax的时候,忘记开中断了,那么就需要加上这三句
当然,需要加在正确的位置!
HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM2_IRQn);
HAL_NVIC_DisableIRQ(TIM2_IRQn);
不过这还没完,你还需要找到stm32f1xx_it.c文件
在其中加入:
extern TIM_HandleTypeDef htim2;
void TIM2_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim2);
}
OK,然后你就拥有了带有中断的定时器!
Internal Clock、Output Compare、PWM Generation、开启NVIC模式的区别
别慌,不是什么大事儿,听我慢慢道来。
从字面上看呢,第一个内部时钟生成模块(Internal Clock)就是最普通的嘛,你单片机运行那么快,总不能叫我工程师自己计数吧,这也太不高级了。
所以就需要一个工具人,但是这个工具人吧脑子简单,只会数数,按照不同速度数数,仅此而已。
至于数到几,速度是多少,数完有什么用,不知道。
我们通常就用这个来进行后台计数,不会抢占当前程序正在执行的函数的优先级,一边运行我一边计数,计数完不一定给谁用,但是你看,哎,我还能计数呢。
然后再看Output Compare和PWM Generation,这俩其实有时候可以混用。
最终的目的就是在刚刚那个工具人的基础上进行升级,你不是只会数数吗,好的,我告诉你数到某一个数(比如100)的时候,你帮我做一件事情。而这件事情在硬件上也就是对电平进行一些操作罢了。
至于更具体是什么,大伙可以去看看这几种模式的内涵:
#define TIM_OCMODE_TIMING
#define TIM_OCMODE_ACTIVE
#define TIM_OCMODE_INACTIVE
#define TIM_OCMODE_TOGGLE
#define TIM_OCMODE_PWM1
#define TIM_OCMODE_PWM2
#define TIM_OCMODE_FORCED_ACTIVE
#define TIM_OCMODE_FORCED_INACTIVE
也就是下面这一句控制的部分:
sConfigOC.OCMode = TIM_OCMODE_TIMING;
我们知道,一个单片机不可能只干一个事情,你比如我就拿一个stm32就计数,那有啥用,肯定要压榨干净剩余价值。
这样的话就牵扯到一个问题了,不同的事件谁先干啊?
这个活就光荣的分配给中断(NVIC)来做了。
中断优先级高的先做,并且如果当前正在做的事情优先级不如我高,那我就可以打断它,你先把我的事情干完再去接着干别的。
中断搭配定时器来用,好处就是刚刚那个工具人好不容易数到100了,那就接着可以去做我刚刚分配给他的任务了,不用等。
如果不开NVIC呢?
就会一直完成不了任务,表现在硬件上就是定时器关联的触发任务没有反应,那可不就失去价值了,工具人直接失业。
补充
最后做一个经验总结:
函数HAL_TIM_OC_Start_IT和HAL_TIM_Base_Start_IT怎么用?
是不是Output Compare就一定要用HAL_TIM_OC_Start_IT?
并不是的,具体用什么函数来打开定时器主要取决于你的目的。比如:
HAL_TIM_OC_Start_IT用于比较输出,就是每次计数到位置就操作引脚,想要输出PWM波形等等的话可以用。
但如果你想用HAL_TIM_PeriodElapsedCallback函数,就得用HAL_TIM_Base_Start_IT了,因为这个函数是到时间就调用CallBack函数的。
应用
用定时器实现按键检测,长按、短按、一次触发、连续按触发。
Stm32主频72MHz,我们设置Period = 999, Prescaler = 71,就得到了周期为1ms的定时器
72M/72/1000=1000Hz=0.001s=1ms
然后我们在HAL_TIM_PeriodElapsedCallback里面写入:
if (htim->Instance == TIM2) {
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5)==KEY_ON)
{
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5)==KEY_ON&&key1_cnt<press_down)key1_cnt++;
else if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5)==KEY_OFF)key1_cnt--;
if(key1_cnt>=press_down)flag=1;//flag=1控制run运行,否则不运行
}
else if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5)==KEY_OFF)
{
key1_cnt=0;
//flag=0;
/*
如果想要实现按住转动,不按不转,就需要解开注释,
并且在run函数里面增加判断flag,flag=0立即退出
否则每次松手都会运行完整整个程序不能立刻停下
*/
}
}
如果想要更改长按短按,只需要改变press_down的值即可。