今天熟悉了stm32的通用定时器,调试代码时犯了一个很低级的错误,导致一个严重bug,这样一个上午的时间就过去了,骂自己一声:活该,看你以后还2不2!最后就完成了一个定时1s翻转led的实验,哎,又尴尬了一次。
stm32名为TIMx的有八个,其中TIM1和TIM8挂在APB2总线上,而TIM2-TIM7则挂在APB1总线上。其中TIM1&TIM8称为高级控制定时器(advanced control timer).他们所在的APB2总线也比APB1总线要好。APB2可以工作在72MHz下,而APB1最大是36MHz。如图:
定时器的时钟不是直接来自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条评论)
登录后参与讨论