由于定时器和时钟,听起来总有那么一点相似之处。所以作为本文也简略阐述一下关于STM32定时器的相关内容,有了这部分基础,再去学习定时器或许更清晰。
1、时钟源
STM32有四个时钟源和一个PLL。分别HSI、HSE、LSI、LSE。其英文全称大概是High Speed External之类的,所以顾名思义也能知道它们分别对应,内部高速时钟、外部告诉时钟、内部低速时钟、外部高速时钟。
HSI:STM32内置的时钟,为8Mhz,常给STM32的AHB总线提供时钟源。优点是内置的,不需要外接晶振,缺点则是不稳定且误差大。
HSE:STM32外部时钟,为8Mhz,常给STM32AHB总线提供时钟源。优点是稳定且准确,缺点是需要外接晶振。但我们一般买的最小系统板子都有这个晶振,故也没啥缺点,我们一般默认的也是用这个时钟,而不是HSI。
LSI、LSE:一个内部一个外部,分别是40kHz和32.768kHz。与高速时钟相同,内部的不稳定,外部的稳定。一般给看门狗之类的外设使用。
2、分频和倍频
这里以STM32F193系列的单片机为例,这个系列的单片机最大的时钟频率是72Mhz,但上文提到,我们一般是用8Mhz的HSE提供时钟源,那如何上升到72Mhz呢?
这里PLL就出场了,至于PLL具体原理是什么,这里按下不表,有兴趣的同学自行了解。总之,HSE通过PLL乘以9则变成了72Mhz的主频了,即AHB的频率。
而AHB继续被用于各个总线和外设中,较为经典的是APB1、APB2两条总线。其中APB2完全继承了72Mhz的频率,而APB1最高限制只能是36Mhz的频率,这使得挂载在APB1总线上的设备大都只能有36Mhz的频率。但是,挂载在APB1上的高级定时器,TIM1和TIM8依旧有72Mhz的频率。因为到了定时器,又被默认倍频到72Mhz了。
3、总结
说了一大堆,绕了几个圈,最终还是得出了总所周知的结论。所有定时器,无论是通用定时器还是高级定时器,其时钟频率都是比较准确的72Mhz。
1、什么是pwm
简而言之就是,在一个固定的周期内,对有效电平的占比的调节。如给一个电器接点1s,其中0.6s是高电平(有效电平),剩下0.4s是低电平,则占空比为%60。
而在stm32中,有关PWM的寄存器有TIMx_CCRx,TIMx_ARR。
ARR:计数周期
CCR:计数阈值
以常规的向上计数为例:ARR=1000,CCR=600。则有效电平占空比为%60。具体设置参考下文
2、GPIO设置
PWM模式下的端口需要设置为复用推挽输出,其他和普通IO设置一样即可。
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
注意:GPIO不是普通的任何一个GPIO,是定时器对应通道的指定的GPIO。
3、频率设置
频率设置设计到两个关键的参数,其一则是上文提到的ARR(自动重转载值),另一个就是PSC(预分配系数),这两个参数共同决定了定时器的计数周期。
公式:周期=(PSC+1)*(ARR+1)/72Mhz
在stm32中的设置参考如下:
TIM_BaseInitStructure.TIM_Period = 7199;
TIM_BaseInitStructure.TIM_Prescaler = 0;
或寄存器版本的
TIMX->ARR=7199;
TIMX->PSC=0;
注:上文的PWM周期是100us,X是指代任意一个定时器。读者可根据自身需要灵活设置。
4、其他设置
设置参考如下
TIM_BaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分割,不分割
TIM_BaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数模式,向上计数
5、通道设置
每个定时器都有四路PWM输出通道,以其中一路设置参考如下
TIM_OCInitTypeDef TIM_OCInitStructure; //重命名结构体
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //PWM模式,模式一(从0到CCR)为有效电平
TIM_OCInitStructure.TIM_OCPolarity= TIM_OCPolarity_High; //有效电平是高电平
TIM_OCInitStructure.TIM_OutputState= TIM_OutputState_Enable; //输出使能
TIM_OC1Init(TIM3, &TIM_OCInitStructure); //通道一使能
TIM_OC1PreloadConfig(TIM3,TIM_OCPreload_Enable); //预装载使能
//TIM_CtrlPWMOutputs(TIM1, ENABLE); //TIM1和TIM8时候药打开
TIM_Cmd(TIMx,ENABLE); //最后定时器使能
6、调用
uint16_t PWM; //输入你给定的PWM
TIMx->CCRx=PWM; //和TIM_SetCompareX(TIMx,PWM)一样,其中x是1、2、4之类的数字
1、什么是输入捕获
通俗的讲就是当电平跃变的时候,由定时器检测到,每检测到一个TIMx_CNT加一或减一。跃变通常指上升沿或下降压沿,即电压由低变高或由高变低的过程。
当TIMx_CNT寄存器的值放到TIMx_CCRx中的时候视为捕获完成。(此句了解即可)
2、GPIO设置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //下拉输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
以上三种输入方式任选一种即可。
3、计数设置
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = arr; //设定计数器自动重装值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //触发psc+1次,TIMx_CNT+1。如无特殊要求,设置为0即可
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分割,不分割
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //设置向上计数或者向下计数
TIM_TimeBaseInit(TIMx, &TIM_TimeBaseStructure);
4、通道设置
和PWM模式不同,这里是ICInitStructure,而PWM模式是OCInitStructure,大概对应的是英文Input Channel和Output Channel吧。
TIM_ICInitTypeDef TIM_ICInitStructure; //重命名结构体
TIMx_ICInitStructure.TIM_Channel = TIM_Channel_x; //设置通道
TIMx_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //设置上升沿捕获或下降沿捕获(Falling)
//TIMX_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //如果用的是channel1则直接映射到TI1上
TIMx_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
TIMx_ICInitStructure.TIM_ICFilter = 0x00;// 配置输入滤波器,0x00表示不滤波
TIM_ICInit(TIMx, &TIMx_ICInitStructure);
TIMx->CNT=0; //初始化清空CNT计数器
TIM_Cmd(TIMx, ENABLE); //定时器使能
5、编码器模式设置
编码器比较复杂,这里不展开讲了,总之按照一下设置,将电机的AB相分别接到定时器的一二通道上,再将结果除以4则是电机转动的圈数。
TIM_EncoderInterfaceConfig(TIMx, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising ,TIM_ICPolarity_Rising);
备注:编码器模式下只能是一二通道输入捕获,且如果设置为编码器模式,则通道设置这一部分可以完全省略掉。
6、输入捕获中断
TIM_ITConfig(TIMX,TIM_IT_Update|TIM_IT_CC1,ENABLE); //允许中断和允许更新中断
到这里想要中断成功还是缺乏一个中断嵌套设置(NVIC)和中断服务函数的调用的,放后面讲。
1、什么是中断
摘自百度词条是这样的,但是本人认为在使用中断的过程更多的不是“意外”情况需要主机干预,而是自己”希望“它中断。
中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。
2、定时时间设置
定时器中断不需要设置时间,只需要参考以上的计数设置即可。
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler =psc;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分割,不分割
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //设置向上计数或者向下计数
TIM_TimeBaseInit(TIMx, &TIM_TimeBaseStructure);
其中,psc和arr两个值的组合,构成了定时器中断的时间。例如我arr=7199,psc=49。则中断时间为5ms。
((7199+1)*(49+1))/72M=5ms
值得一提的是,定时器的计数设置之后,使能该定时器则定时器立刻会计数。计数这个功能你便可以用来卡PWM,卡时间中断等等。
3、其他设置
TIM_ITConfig(TIMX,TIM_IT_Update,ENABLE); //使能中断
4、中断设置
中断设置也是比较见简单的了,设置中断组、中断线、子优先级、抢占优先级最后使能。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = TIMX_IRQn; //TIM2中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01; //先占优先级0级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; //从优先级3级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器
5、中断服务函数
定时器的中断服务函数大概长这样,分别是进入中断服务函数之后再次确认是否中断标志位置位了,然后就清空标志位。
void TIMX_IRQHandler()
{
if (TIM_GetITStatus(TIMX,TIM_IT_Update)!=RESET)
{
TIM_ClearITPendingBit(TIMX,TIM_IT_Update);
}
}
再次提醒:上文所有提到的X都是1,2,3,4之类的整数。自行替换才可以使用。