原创 stm32库函数学习篇----通用定时器(基本定时功能)

2012-10-30 14:07 10941 15 15 分类: MCU/ 嵌入式 文集: stm32库函数学习

今天熟悉了stm32的通用定时器,调试代码时犯了一个很低级的错误,导致一个严重bug,这样一个上午的时间就过去了,骂自己一声:活该,看你以后还2不2!05.gif最后就完成了一个定时1s翻转led的实验,哎,又尴尬了一次。

代码主要参考开发板例程和网上网友给出的例子。
 
实现功能:使用定时器2控制PD2口每隔一秒取反led灯,然后另个led灯在死循环里随意延时取反,指示程序正在运行。
 
首先认识一下stm32的通用定时器,下面这些东西在网上一搜有大把介绍:

stm32名为TIMx的有八个,其中TIM1和TIM8挂在APB2总线上,而TIM2-TIM7则挂在APB1总线上。其中TIM1&TIM8称为高级控制定时器(advanced control timer).他们所在的APB2总线也比APB1总线要好。APB2可以工作在72MHz下,而APB1最大是36MHz。如图:

20120928133854894.jpg

 

定时器的时钟不是直接来自APB1或APB2,而是来自于输入为APB1或APB2的一个倍频器。

下面以定时器2~7的时钟说明这个倍频器的作用:当APB1的预分频系数为1时,这个倍频器不起作用,定时器的时钟频率等于APB1的频率;当APB1的预分频系数为其它数值(即预分频系数为2、4、8或16)时,这个倍频器起作用,定时器的时钟频率等于APB1的频率两倍。

假定AHB=36MHz,因为APB1允许的最大频率为36MHz,所以APB1的预分频系数可以取任意数值;当预分频系数=1时,APB1=36MHz,TIM2~7的时钟频率=36MHz(倍频器不起作用);当预分频系数=2时,APB1=18MHz,在倍频器的作用下,TIM2~7的时钟频率=36MHz。

有人会问,既然需要TIM2~7的时钟频率=36MHz,为什么不直接取APB1的预分频系数=1?答案是:APB1不但要为TIM2~7提供时钟,而且还要为其它外设提供时钟;设置这个倍频器可以在保证其它外设使用较低时钟频率时,TIM2~7仍能得到较高的时钟频率。

再举个例子:当AHB=72MHz时,APB1的预分频系数必须大于2,因为APB1的最大频率只能为36MHz。如果APB1的预分频系数=2,则因为这个倍频器,TIM2~7仍然能够得到72MHz的时钟频率。能够使用更高的时钟频率,无疑提高了定时器的分辨率,这也正是设计这个倍频器的初衷。

 

TIMER主要是由三部分组成

1、时基单元。

2、输入捕获。

3、输出比较。

还有两种模式控制功能:从模式控制和主模式控制。图上看得清楚:

 
 

 

 

鉴于我现在只会做基本定时器实验,那就只需要懂时基单元了。

 

时基单元有三个部分:CNT、PSC、ARR。CNT的计数方式分三种:向上、向下、中央对齐。通俗的说就是0—ARR、ARR—0、0—(ARR-1)—ARR—1.

 

计数器时钟可以由下列时钟源提供:

·内部时钟(CK_INT)

·外部时钟模式1:外部输入脚(TIx)

·外部时钟模式2:外部触发输入(ETR)

·内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,如可以配置一个定时器Timer1而作为另一个定时器Timer2的预分频器。

由于我只做定时用,当然只需要选择内部时钟就可以了。

 

编程步骤

1.配置系统时钟;

2.配置GPIO

3.配置NVIC

4.配置TIMER

 

//main函数

int main(void)

{     rcc_cfg();

      gpio_cfg();

      nvic_cfg();

      tim2_cfg();

  while (1)

  {

    GPIO_WriteBit(GPIOA, GPIO_Pin_8, Bit_SET);

    delay();

    GPIO_WriteBit(GPIOA, GPIO_Pin_8, Bit_RESET);

    delay();

  }

}

很简单,就是对RCC、IO口、中断控制器和定时器2做初始化。死循环里对PA8口的led灯延时取反,指示程序运行。

 

下面是变量声明,我都用全局声明:

/* Private variables -----------------------------------*/

GPIO_InitTypeDef  GPIO_InitStructure;

TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

NVIC_InitTypeDef  NVIC_InitStructure;

 

然后是RCC配置的实现:

void rcc_cfg()

{

 ;

}

我什么都没干,这里只是意思一下。因为系统会在进入main函数前调用SystemInit函数,上一篇学习记录中有详细描述。

 

然后是IO口的配置实现:

void gpio_cfg()

{

   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOA, ENABLE);

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

  GPIO_Init(GPIOA, &GPIO_InitStructure);

 

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

  GPIO_Init(GPIOD, &GPIO_InitStructure);

}

这也很简单,照本宣科就是了。

 

下面是中断向量控制器的配置实现:

void nvic_cfg()

{

  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

/* Enable the TIM3 global Interrupt */

  NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;  //TIM2中断

  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级0级

  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  //从优先级3级

  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能

  NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器

中断部分我还没有仔细研究过,目前就找本宣科吧。

 

下面是关键的TIM2配置函数实现:

void tim2_cfg()

{

  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

  TIM_DeInit(TIM2);//初始化默认设置

  TIM_InternalClockConfig(TIM2);//设置为内部时钟,而TIM2、3、4的时钟源是 APB1 即 是 PCLK1  ( APB1 对应 PCLK1 ) PCLK1 = APB1 = HCLK/2 = SYSCLK/2 = 36MHZ (36,000,000 HZ)  但是注意: 倍频器会自动倍2, 即是 【72MHZ】!
 

  //预分频系数为36000-1,这样计数器时钟为72MHz/36000 = 2kHz

  TIM_TimeBaseStructure.TIM_Prescaler = 36000 - 1;

  //设置时钟分割

  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;

  //设置计数器模式为向上计数模式

  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;

  //设置计数溢出大小,每计2000个数就产生一个更新事件

  TIM_TimeBaseStructure.TIM_Period = 2000 - 1;

  //将配置应用到TIM2中

  TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure);

  //清除溢出中断标志

  TIM_ClearFlag(TIM2, TIM_FLAG_Update);

  //禁止ARR预装载缓冲器

  TIM_ARRPreloadConfig(TIM2, DISABLE);

  //开启TIM2的中断

  TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);

  TIM_Cmd(TIM2, ENABLE);  //使能TIMx外设

}

其中那个时钟分割结构成员让我纠结了好久,查手册也没查出过啥,最后再网上搜索到了网友的总结,说是用于输入的数字滤波用,与我们的基本定时功能没有多大关系,所以不管他先。但有一个很重要的的地方需要注意,配置完TIM后一定要使能该外设,否则前面功夫都白忙活了。就是这个函数TIM_Cmd(TIM2, ENABLE)。

 

重点是TIM_TimeBaseInitTypeDef这个类的成员要弄明白。看库函数源码如下:

typedef struct

{

uint16_t TIM_Prescaler;         /*!< Specifies the prescaler value used to            divide the TIM clock.This parameter can be a number between 0x0000 and          0xFFFF */

uint16_t TIM_CounterMode;       /*!< Specifies the counter mode.                      This parameter can be a value of @ref TIM_Counter_Mode */

uint16_t TIM_Period;            /*!< Specifies the period value to be loaded           into the active Auto-Reload Register at the next update event.                  This parameter must be a number between 0x0000 and 0xFFFF.  */ 

uint16_t TIM_ClockDivision;     /*!< Specifies the clock division.                       This parameter can be a value of @ref TIM_Clock_Division_CKD */

uint8_t TIM_RepetitionCounter;  /*!< Specifies the repetition counter value.            Each time the RCR downcounter reaches zero, an update event is                  generated and counting restarts from the RCR value.This means in PWM            mode that (N+1) corresponds to:

             - the number of PWM periods in edge-aligned mode

             - the number of half PWM period in center-aligned mode

         This parameter must be a number between 0x00 and 0xFF. 

         @note This parameter is valid only for TIM1 and TIM8. */

} TIM_TimeBaseInitTypeDef;

可以发现TIM_Period其实就是定时器的自动重装值, TIM_RepetitionCounter只用在TIM1和TIM8中,所以我们用TIM2就不管它了。

 

文章评论0条评论)

登录后参与讨论
我要评论
0
15
关闭 站长推荐上一条 /2 下一条