赞
踩
关于定时器,野火《零死角玩转 STM32F103》一书中介绍如下,并用一张框图概括总结了stm32定时器的功能
基本上定时器都会有时基,要不怎么叫定时器呢,定时器的时钟框图如下所示
时钟源就是内部时钟,高级定时器挂载在APB1总线,基本和通用定时器都是挂载在APB2总线下的,不过最后经过一系列倍频什么的都是72Mhz,如下所示
经过时钟源后就进入了PSC预分频器,就是CK_CNT,CK_CNT用来驱动计数器计数。 PSC 是16 位的预分频器,可以对定时器时钟进行 1~65536 之间的任何一个数进行分频,具体计算方式为:CK_CNT=72Mhz/(PSC+1)
再之后就进入计数器CNT,CNT也是16位的计数器,最大计数值为65535,当计数值达到自动重装载寄存器的时候就产生更新事件,并重新进行计数。当然自动重装载寄存器的值也是我们设置的,自动重装载寄存器ARR也是一个16位的寄存器,当计数值达到这个值的时候,就会产生更新事件,比如中断事件,触发其他外设的事件,或者复位计数器的事件。
所以最终定时时间为:
以stm32的基本定时器6为例,打开配置页面就可以看到我们上面讲的一些寄存器了
以定时器6为例,配置分频系数和自动重装载值
并开启中断
在初始化函数中开启定时器中断
编写中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static uint32_t time = 0;
if(htim==(&htim6))
{
time++;
if(time == 1000) //1ms计数1000次就是1s
{
time = 0; //及时复位计数值
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}
}
}
将程序下载到开发板就能看到led一秒闪烁一次了!!!
我们在配置定时器的时候可以看到居然有这样一个选项,翻译下是单脉冲模式的意思
好家伙试了下要是用来做基本定时实验的时候这个功能千万不要勾选,不然的话就会一直亮着,就不会1s闪烁一次了
为啥呢,原来TIMx_CR1的OPM位
位 3 OPM:单脉冲模式 (One-pulse mode)
都不会停止计数了啊,那就只会发生一次中断了,那就肯定就没有1s的led亮灭了啊,但是根据这个特性我们就可以做延时函数了,嗯,确实不错!!!
那要延时的话必然得整个us级别的吧,ms的他都有了还整啥,所以我们配一个us的计数
编写延时函数如下:
void delay_us(uint32_t us)
{
TIM6 -> ARR = us - 1;
TIM6 -> CR1 |= TIM_CR1_CEN;
while(TIM6->CR1&TIM_CR1_CEN);
}
之后在主函数中开启定时器
还是来写比肩方便的io翻转的函数
之后我们再用逻辑分析仪来测试读取
可以看到还是有点效果的hhh!!!
us延时的方案再次加一
一般我们在使用定时器的时候常见的是内部时钟,但其实也可以外部,我们一般都直接忽略过去了,因此这里我做下介绍!
这里我们配置下这个时钟,配置信息如下所示
同时我们可以看到引脚上多了一个外部时钟输入引脚
同时我们开启下外部中断,方我们进行后续的中断处理
那么时钟是啥呢,我们最通俗的理解不就是方波吗,一段时间高一段时间低电平的这个样子,因此我们在软件中来产生一个方波,当然我们同样可以借用外部设备来实现这个功能,效果是一样的!!!
下面正式开启代码部分的编写
同时我们回去看他的配置页面,关于分频系数这里,比如如果我们设置了分频,那么我们计数时间会变的更长,因为频率被分掉了,没有那么快了,这个可以和使用内部时钟的概念来看!
定时器具有输入捕获功能,可用于测量脉宽,频率等
大致示意图如下所示
在cubemx中的配置如下所示:
其实他还是时基,然后根据上升沿下降沿来计数的一种方式,这里我用的最小计数周期为1us,然后最大计数值为65535,选择了上升沿捕获。
同时用另一个定时器来生成PWM,关于PWM配置的详细问题,下面的部分有专门的介绍
之后我们在初始化函数中开启捕获
具体代码如下所示
__HAL_TIM_CLEAR_FLAG(&htim3,TIM_IT_UPDATE);
HAL_TIM_Base_Start_IT(&htim3);
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,200);
之后我们编写捕获完成回调函数,回调函数的位置如下图所示:
定义一个变量来获取捕获值
之后我们进入调试查看,我们这个代码比较简陋,因此我们只坐一次捕获试试效果!
运行程序,可以看到程序暂停在断点处,同时查看变量情况如下
我们在查看下逻辑分析仪结果,两次上升沿之间时间间隔为1000us左右,基本是没问题的!
那下面我们开始完善代码,让他可以连续读取,并处理脉宽什么的了,简单而言就是捕获到上升研的时候设置下降沿捕获,捕获到下降沿的时候设置为上升沿捕获,这样就行了
以测量一个按键的高低电平时间为例:
从原理图上查到按键的位置为PA1
设置为输入模式,最长记录时间65536us
这里我们开启中断用于计数
下面开始编写代码
事先准备好需要的变量
uint32_t ccr_cnt,period_cnt;
uint8_t tri_flag,end_flag;
uint32_t cnt_cl=72000000/72;;
float time;
计数部分代码如下
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { period_cnt++;//中断计数 } void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if(tri_flag==0) { period_cnt=0;//中断次数清零 __HAL_TIM_SET_COUNTER(&htim2,0); ccr_cnt=0; __HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING); tri_flag=1; } else { ccr_cnt=__HAL_TIM_GET_COMPARE(&htim2, TIM_CHANNEL_1); //ccr_cnt=HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);//功能同上 __HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING); tri_flag=0; end_flag=1;//捕获完成标志 } }
下面在主函数里编写函数读取
if(end_flag==1)//结束捕获
{
time=(float)(period_cnt*1000+(ccr_cnt+1))/cnt_cl;//结果强制转换为浮点型
printf( "\r\n测得高电平脉宽时间:%.3fs\r\n",time);//输出结果保留小数点后两位
end_flag=0;
}
下载到开发板,按下按键,可以看到结果如下,测试正常!
下面我们来读取频率和脉宽,我们用一个定时器生成PWM,用另一组定时器的输入捕获通道来读取他的数值
TIM2产生PWM
TIM3输入捕获
首先还是设置相关标志位
#define cnt_clk 72000000/(71+1)
#define arr 65535 //最大计数周期
uint32_t ccr_cnt1,ccr_cnt2;
uint32_t Period_cnt,Period_cnt1,Period_cnt2;
uint32_t ic_flag,end_flag;
float duty_cycle,frequency; //记录频率和脉宽
中断函数处理如下
完整代码如下:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { Period_cnt++; } void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { switch(ic_flag) { case 0://第一个上升沿捕获 { __HAL_TIM_SET_COUNTER(&htim3,0); ccr_cnt1=0; ccr_cnt2=0; Period_cnt=0; Period_cnt1=0; Period_cnt2=0; __HAL_TIM_SET_CAPTUREPOLARITY(&htim3,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_FALLING); ic_flag=1; break; } case 1://第一个下降沿捕获 { ccr_cnt1=__HAL_TIM_GET_COMPARE(&htim3,TIM_CHANNEL_1); Period_cnt1=Period_cnt; __HAL_TIM_SET_CAPTUREPOLARITY(&htim3,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_RISING); ic_flag=2; break; } case 2://第二个上升沿捕获 { ccr_cnt2=__HAL_TIM_GET_COMPARE(&htim3,TIM_CHANNEL_1); Period_cnt2=Period_cnt; ic_flag=0; end_flag=1; break; } default: break; } }
最终的输出代码如下所示
源代码如下,因为PWM的速度比较快,所以建议还是加个延时函数比较好
if(end_flag==1)
{
duty_cycle=(float)(Period_cnt1*(arr+1)+ccr_cnt1+1)*100/(Period_cnt2*(arr+1)+ccr_cnt2+1);
frequency=(float)cnt_clk/(Period_cnt2*(arr+1)+ccr_cnt2+1);
printf("频率%.2f,占空比%.2f\r\n",frequency,duty_cycle);
end_flag=0;
}
HAL_Delay(1000);
下载到开发板,打印结果如下所示:
上面我们用的是软件的方式进行的测量脉宽,但是同样的我们可以用硬件的方式,这个在野火的教程中讲的较为详细
具体介绍如下
PWM 信号由输入通道 TI1 进入,因为是 PWM 输入模式的缘故,信号会被分为两路,一路是 TI1FP1,另外一路是 TI2FP2。其中一路是周期,另一路是占空比,具体哪一路信号对应周期还是占空比,得从程序上设置哪一路信号作为触发输入,作为触发输入的哪一路信号对应的就是周期,另一路就是对应占空比。作为触发输入的那一路信号还需要设置极性,是上升沿还是下降沿捕获,一旦设置好触发输入的极性,另外一路硬件就会自动配置为相反的极性捕获,无需软件配置。一句话概括就是:选定输入通道,确定触发信号,然后设置触发信号的极性即可,因为是 PWM 输入的缘故,另一路信号则由硬件配置,无需软件配置。
同时注意:使用 PWM 输入模式的时候必须将从模式控制器配置为复位模式,这个在我们下面的配置中将体现出来
PWM输入模式时序如下所示:
测量原理为:PWM 信号由输入通道 TI1 进入,配置 TI1FP1 为触发信号,上升沿捕获。当上升沿的时候 IC1 和 IC2 同时捕获,计数器 CNT 清零,到了下降沿的时候, IC2 捕获,此时计数器CNT 的值被锁存到捕获寄存器 CCR2 中,到了下一个上升沿的时候, IC1 捕获,计数器CNT 的值被锁存到捕获寄存器 CCR1 中。其中 CCR2+1 测量的是脉宽, CCR1+1 测量的是周期。 这里要注意的是 CCR2 和 CCR1 的值在计算占空比和频率的时候都必须加 1, 因为计数器是从 0 开始计数的。
在cubemx中配置如下所示:
可以看到两个通道其实是共用一个引脚的
编写输入捕获部分代码如下所示
源代码
uint32_t Cap_val1,Cap_val2;
float Duty,Frequency;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
Cap_val1 = HAL_TIM_ReadCapturedValue (&htim3 ,TIM_CHANNEL_1 );
Cap_val2 = HAL_TIM_ReadCapturedValue (&htim3 ,TIM_CHANNEL_2 );
if(Cap_val1 != 0)
{
Duty = (float )(Cap_val2+ 1)*100 / (Cap_val1 +1) ;
Frequency = 72000000 / 72 / (float )(Cap_val1 +1) ;
}
}
在主函数中编写读取代码
将程序下载到开发板,可以看到效果如下,非常准确
既准确又方便,但是牺牲了一个定时器,看来也不全是好事啊哈哈哈!!!
PWM是我们经常要用到的外设,在电机控制等方面有很广泛的应用
百度百科上的解释如下所示 :PWM是一种对模拟信号电平进行数字编码的方法。通过高分辨率计数器的使用,方波的占空比被调制用来对一个具体模拟信号的电平进行编码。PWM信号仍然是数字的,因为在给定的任何时刻,满幅值的直流供电要么完全有(ON),要么完全无(OFF)。电压或电流源是以一种通(ON)或断(OFF)的重复脉冲序列被加到模拟负载上去的。通的时候即是直流供电被加到负载上的时候,断的时候即是供电被断开的时候。只要带宽足够,任何模拟值都可以使用PWM进行编码。
通俗点一句话就是:PWM是数字量(01两种状态),PWM常用于模拟,任何模拟值都可以用PWM进行编码
一般我们在配置PWM的常用操作如下所示:
选择为PWM输出模式
设置时基等基本参数,之后配置通道
编写代码如下所示
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); //开启pwm通道
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4);
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,200); // 设置tim输出值
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_2,400);
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_3,600);
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_4,800);
使用逻辑分析仪查看值,关于这里的计算,可以参考上面讲到的基本定时器时基配置,基本差不多,区别就在与当计数值达到CCR的时候被截断了!
这样我们就能看到pwm比较完整的状态了,下面我对刚才讲到的一些参数进行说明:
stm32有两种pwm模式:
模式 | CNT计数方式 | 说明 |
---|---|---|
PWM1 | 递增 | CNT<CCR,通道有效 |
PWM1 | 递减 | CNT>CCR,通道有效 |
PWM2 | 递增 | CNT>CCR,通道有效 |
PWM2 | 递减 | CNT<CCR,通道有效 |
简单来说就是如果其他参数相同,PWM1和PWM2模式输出的PWM是互补的! |
上面讲到了通道有效,那么通道有效是高电平还是低电平呢,这个就是极性,就是极性选择高通道有效时候输出的就是高电平,反之输出的是低电平!
这个其实就是初始设置的CCR值了,默认情况下是0,我们如果想上电就输出PWM,那就要事先给他设置好相应的值,当然你也可以就是在初始化中使用软件设置!
软件设置的代码如下:
首先开启定时器PWM输出
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
设置比较值
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,200);
暂时还不清楚
顾名思义,互补的PWM就是互相补充嘛,就是PWM之间相互补充输入,一个输出高的时候另一边就输出低电平
在cubemx中配置互补输出如下所示:
启动代码也要做相应修改,添加互补输出的PWM开启函数:
将程序下载到开发板并接上逻辑分析仪效果如下所示
当然在互补中我们还看到有刹车和死区的部分,其实死区是一直存在的,只是时间比较短,因为这是硬件的伤
野火教程解释如下:在这个半桥驱动电路中, Q1 导通, Q2 截止,此时我想让 Q1 截止 Q2 导通,肯定是要先让Q1 截止一段时间之后,再等一段时间才让 Q2 导通,那么这段等待的时间就称为死区时间,因为 Q1 关闭需要时间(由 MOS 管的工艺决定)。如果 Q1 关闭之后,马上打开 Q2,那么此时一段时间内相当于 Q1 和 Q2 都导通了,这样电路会短路。
关于死区时间的计算我在下面的图中已经注明,为了较明显的看到死区的效果,我这里增大频率,同时设置一个相对较大的死区时间
将程序下载到开发板可以看到效果如下所示:
关于编码器模式我在之前的一篇文章中有讲到过 直流编码电机双闭环(速度+角度)控制
这里仅仅是为了让整个体系更完善一点,加入了这个部分,详情可以看那篇文章的介绍!
cube中的配置如下
这里编码器模式可以选T1,T2,T12三种模式
详情还是看那篇文章吧!
因为系统就是时间的调度吗,所以我们就可以用一个硬件定时器衍生出很多不同的软件定时器(假的),大致思路如下:
首先产生一个1ms周期的中断
之后我们编写回调函数,利用一个变量的判断实现不同阶段的延时,在延时里面可以设置周期任务之类的,同时在最终将这个变量清零,并翻转led,led的闪烁状况就可以作为系统运行的标志!
源码如下所示:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { static uint32_t time = 0; if(htim==(&htim6)) { time++; if(time % 2 == 0) // 2ms的周期任务 { // xxx } if(time % 5 == 0) // 5ms的周期任务 { // xxx } if(time % 10 == 0) // 10ms的周期任务 { // xxx } if(time == 1000) //1ms计数1000次就是1s { time = 0; //及时复位计数值 HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); //用作系统运行指示灯 } } }
定时器可以算是stm32非常重要的外设了,我这篇也仅仅是部分的记录,还有比如无刷电机控制相关的,一些工业应用诸如刹车,死区等都没有讲或者很详细的介绍,希望以后都能一一接触到,并继续分享!!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。