今天我们来学习STM32的定时器。以STM32F103为例,一共有8个定时器,其中,高级定时器2个(TIM1、TIM8)、基本定时器2个(TIM6、TIM7)、通用定时器4个(TIM2、TIM3、TIM4、TIM5)。我们以TIM3为例来详细讲述定时器定时功能的配置。
static void TIM3_Mode_Config(void)// 此处static关键字用于修改函数链接属性,表明此函数只能在此源文件中访问 { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; /* 设置TIM3CLK 时钟为36MHZ */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); /* 基本定时器配置 */ /* APB1 = 36MHz,因为1999 + 1 = 2000分频>1分频,所以由参考手册可知,定时器3频率为36*2 = 72MHz,而72,000,000/2,000 = 36000 ,设计数 值满3600进一次中断,每10次中断进行一次取反,即每1s对LED1取一次反 */ TIM_TimeBaseStructure.TIM_Period = 3600; //当定时器从0计数到255,即为266次,为一个定时周期 TIM_TimeBaseStructure.TIM_Prescaler = 1999; //设置预分频: TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1 ;//设置时钟分频系数 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式 TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); /* TIM3 enable counter */ TIM_Cmd(TIM3, ENABLE); // 使能定时器3 TIM_ITConfig(TIM3,TIM_IT_Update, ENABLE);// 允许update中断 NVIC_Config_PWM();// 必须配置中断优先级,使能相关中断 }
上述代码为定时器配置代码。
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
先定义一个结构体TIM_TimeBaseStructure,其类型为TIM_TimeBaseInitTypeDef,其成员包括预分频值、计数模式、周期、时钟分频因子、重复计数器。
/* 设置TIM3CLK 时钟为36MHZ */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
再是配置时钟,TIM3挂载于APB1总线上,而APB1总线的时钟最大速率为36MHz,此处即配置TIM3时钟速率为36MHz。
TIM_TimeBaseStructure.TIM_Period = 3600; //当定时器从0计数到255,即为266次,为一个定时周期 TIM_TimeBaseStructure.TIM_Prescaler = 1999; //设置预分频: TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1 ;//设置时钟分频系数 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式 TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
然后设置时钟周期为3600,预分频数为1999,根据TIM_CKD_DIV1与TIM_CounterMode_Up定义
#define TIM_CKD_DIV1 ((uint16_t)0x0000) #define TIM_CounterMode_Up ((uint16_t)0x0000)
即时钟分频因子为0,计数模式值为0。
现在来到库函数
TIM_TimeBaseInit();
/** * @brief Initializes the TIMx Time Base Unit peripheral according to * the specified parameters in the TIM_TimeBaseInitStruct. * @param TIMx: where x can be 1 to 17 to select the TIM peripheral. * @param TIM_TimeBaseInitStruct: pointer to a TIM_TimeBaseInitTypeDef * structure that contains the configuration information for the * specified TIM peripheral. * @retval None */ void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct) { uint16_t tmpcr1 = 0; /* Check the parameters */ assert_param(IS_TIM_ALL_PERIPH(TIMx)); assert_param(IS_TIM_COUNTER_MODE(TIM_TimeBaseInitStruct->TIM_CounterMode)); assert_param(IS_TIM_CKD_DIV(TIM_TimeBaseInitStruct->TIM_ClockDivision)); tmpcr1 = TIMx->CR1; if((TIMx == TIM1) || (TIMx == TIM8)|| (TIMx == TIM2) || (TIMx == TIM3)|| (TIMx == TIM4) || (TIMx == TIM5)) { /* Select the Counter Mode */ tmpcr1 &= (uint16_t)(~((uint16_t)(TIM_CR1_DIR | TIM_CR1_CMS))); tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_CounterMode; } if((TIMx != TIM6) && (TIMx != TIM7)) { /* Set the clock division */ tmpcr1 &= (uint16_t)(~((uint16_t)TIM_CR1_CKD)); tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_ClockDivision;// ʱÖÓ·ÖƵÒò×Ó } TIMx->CR1 = tmpcr1; /* Set the Autoreload value */ TIMx->ARR = TIM_TimeBaseInitStruct->TIM_Period ; /* Set the Prescaler value */ TIMx->PSC = TIM_TimeBaseInitStruct->TIM_Prescaler; if ((TIMx == TIM1) || (TIMx == TIM8)|| (TIMx == TIM15)|| (TIMx == TIM16) || (TIMx == TIM17)) { /* Set the Repetition Counter value */ TIMx->RCR = TIM_TimeBaseInitStruct->TIM_RepetitionCounter; } /* Generate an update event to reload the Prescaler and the Repetition counter values immediately */ TIMx->EGR = TIM_PSCReloadMode_Immediate; } tmpcr1 = TIMx->CR1; TIM1->CR1暂未进行配置,所以此时tmpcr1 = 0; if((TIMx == TIM1) || (TIMx == TIM8)|| (TIMx == TIM2) || (TIMx == TIM3)|| (TIMx == TIM4) || (TIMx == TIM5)) { /* Select the Counter Mode */ tmpcr1 &= (uint16_t)(~((uint16_t)(TIM_CR1_DIR | TIM_CR1_CMS))); tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_CounterMode; }
满足判断条件,因为此时tmpcr1为0,所以其与上任何数都为0,所以
tmpcr1 &= (uint16_t)(~((uint16_t)(TIM_CR1_DIR | TIM_CR1_CMS))) = 0;
因为TIM_CounterMode = 0,所以
tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_CounterMode = 0;
if((TIMx != TIM6) && (TIMx != TIM7))
不满足判断条件,跳过后面花括号内代码。
TIMx->CR1 = tmpcr1;
即TIM3->CR1 = 0,根据控制寄存器1描述
可知,TIM3被配置为边沿对齐模式,计数器向上计数。
/* Set the Autoreload value */ TIMx->ARR = TIM_TimeBaseInitStruct->TIM_Period ;
即TIM3->ARR = 3600,根据自动重装载寄存器描述
可知,3600为将要装载入实际的自动重装载寄存器的值。
/* Set the Prescaler value */ TIMx->PSC = TIM_TimeBaseInitStruct->TIM_Prescaler;
即TIM3->PSC = 1999,根据预分频器描述
及STM32时钟描述
因为APB1 = 36MHz,因为分频数1999>1,所以由上图可知,定时器3时钟频率为36*2 = 72MHz,所以计数器时钟频率为72 000 000 / ( 1999 + 1) = 36 000。
if ((TIMx == TIM1) || (TIMx == TIM8)|| (TIMx == TIM15)|| (TIMx == TIM16) || (TIMx == TIM17))
不满足判断条件,跳过其后花括号内代码。
TIMx->EGR = TIM_PSCReloadMode_Immediate;
根据TIM_PSCReloadMode_Immediate定义
#define TIM_PSCReloadMode_Immediate ((uint16_t)0x0001)
得TIM3->EGR = 0x0001,根据事件产生寄存器描述
可知,TIM3被配置为重新初始化计数器,并产生一个更新事件。
/* TIM3 enable counter */ TIM_Cmd(TIM3, ENABLE); TIM_ITConfig(TIM3,TIM_IT_Update, ENABLE);
使能TIM3计数器,并使能TIM3更新中断。
最后配置TIM3中断,调用另一个重要的库函数
NVIC_Config_PWM();
static void NVIC_Config_PWM(void) { NVIC_InitTypeDef NVIC_InitStructure; /* Configure one bit for preemption priority */ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }
同样先定义一个结构体NVIC_InitStructure,其类型为VIC_InitTypeDef,包含成员:指定中断通道、通道抢占优先级、通道子优先级、通道命令。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
配置中断优先级模式,先来看一下NVIC_PriorityGroupConfig()函数。
/** * @brief Configures the priority grouping: pre-emption priority and subpriority. * @param NVIC_PriorityGroup: specifies the priority grouping bits length. * This parameter can be one of the following values: * @arg NVIC_PriorityGroup_0: 0 bits for pre-emption priority * 4 bits for subpriority * @arg NVIC_PriorityGroup_1: 1 bits for pre-emption priority * 3 bits for subpriority * @arg NVIC_PriorityGroup_2: 2 bits for pre-emption priority * 2 bits for subpriority * @arg NVIC_PriorityGroup_3: 3 bits for pre-emption priority * 1 bits for subpriority * @arg NVIC_PriorityGroup_4: 4 bits for pre-emption priority * 0 bits for subpriority * @retval None */ void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup) { /* Check the parameters */ assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup)); /* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */ SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup; }
因为之前NVIC_PriorityGroup_1被定义为
#define NVIC_PriorityGroup_1 ((uint32_t)0x600) /*!< 1 bits for pre-emption priority
而AIRCR_VECTKEY_MASK被定义为
#define AIRCR_VECTKEY_MASK ((uint32_t)0x05FA0000)
所以SCB->AIRCR = 0x05FA0000 | 0x600 = 0x05FA0600;
下面的东西相对而言比较深入,必须结合手册的相关内容来分析。
先看第1点,由上可知,如果要对SCB_AIRCR寄存器进行写操作,则SCB_AIRCR[31:16]必须写0x5FA,否则写操作无效。
再看第2点,优先级。再分析优先级配置之前,我们首先要知道STM32F103芯片使用了几位来表达优先级。
根据参考手册,我们可知该芯片用了4位来表达优先级。
“原则上,CM3支持3个固定的高优先级和多达256级的可编程优先级,并且支持128级抢占优先级。但是,绝大多数CM3芯片都会精简设计,以致实际上支持的优先级数会更少,如8级,16级,32级等。它们在设计时会裁掉表达优先级的几个低端有效位,以减少优先级的级数。可见,不管使用多少位来表达优先级,都是以MSB对齐的。” ——摘录自《CM3权威指南》
MSB为最高加权位,即二进制数的最左边一位,即高位对齐。
由上阐述,对AIRCR写0x05FA0600,即允许写AIRCR寄存器,并配置优先级组模式为抢占优先级为1位(2级),子优先级为3位(8级)。
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure);
NVIC_Init()定义如下
/** * @brief Initializes the NVIC peripheral according to the specified * parameters in the NVIC_InitStruct. * @param NVIC_InitStruct: pointer to a NVIC_InitTypeDef structure that contains * the configuration information for the specified NVIC peripheral. * @retval None */ void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct) { uint32_t tmppriority = 0x00, tmppre = 0x00, tmpsub = 0x0F; /* Check the parameters */ assert_param(IS_FUNCTIONAL_STATE(NVIC_InitStruct->NVIC_IRQChannelCmd)); assert_param(IS_NVIC_PREEMPTION_PRIORITY(NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority)); assert_param(IS_NVIC_SUB_PRIORITY(NVIC_InitStruct->NVIC_IRQChannelSubPriority)); if (NVIC_InitStruct->NVIC_IRQChannelCmd != DISABLE) { /* Compute the Corresponding IRQ Priority --------------------------------*/ tmppriority = (0x700 - ((SCB->AIRCR) & (uint32_t)0x700))>> 0x08; tmppre = (0x4 - tmppriority); tmpsub = tmpsub >> tmppriority; tmppriority = (uint32_t)NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority << tmppre; tmppriority |= NVIC_InitStruct->NVIC_IRQChannelSubPriority & tmpsub; tmppriority = tmppriority << 0x04; NVIC->IP[NVIC_InitStruct->NVIC_IRQChannel] = tmppriority; /* Enable the Selected IRQ Channels --------------------------------------*/ NVIC->ISER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] = (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F); } else { /* Disable the Selected IRQ Channels -------------------------------------*/ NVIC->ICER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] = (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F); } }
因为满足第1个判断语句,所以执行第一个花括号内代码。
tmppriority = (0x700 - ((SCB->AIRCR) & (uint32_t)0x700))>> 0x08; tmppre = (0x4 - tmppriority); tmpsub = tmpsub >> tmppriority;
tmppriority = ( 0x700 - ( 0x05FA & 0x700) ) >> 0x08 = ( 0x700 - 0x600 ) >> 0x08 = 0x01;
tmppre = (0x4 - 0x01) = 0x03;
tmpsub = 0x0F >> 0x01 = 0x07;
tmppriority = (uint32_t)NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority << tmppre; tmppriority |= NVIC_InitStruct->NVIC_IRQChannelSubPriority & tmpsub; tmppriority = tmppriority << 0x04;
tmppriority = 0 << 0x03 = 0;
tmppriority = 0 | ( 0 & 0x07 ) = 0 | 0 = 0;
tmppriority = 0 << 0x04 = 0;
NVIC->IP[NVIC_InitStruct->NVIC_IRQChannel] = tmppriority;
因为TIM3_IRQn被定义为
TIM3_IRQn = 29, /*!< TIM3 global Interrupt */
所以NVIC->IP[29] = 0;
IP数组定义如下
__IO uint8_t IP[240]; /*!< Offset: 0x300 Interrupt Priority Register (8Bit wide) */
即偏移地址相对于NVIC基地址的偏移量是0x300,由《CortexM3 Programming Manual》Table 44
可知,TIM3的优先级为0,即为最高优先级。(注:TIM3中断在向量表中位置为29。具体见下图)
此处请注意:这是我个人的理解,我觉得NVIC->IP[29] = 0表示的就是这个意思,但还是感觉有个地方不能解释,那就是我不能保证IP[29]里面的下标表示的就是其在中断向量表中的位置。
NVIC->ISER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] = (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
即NVIC->ISER[ 29 >> 0x05 ] = 0x01 << ( 29 & 0x1F )
即NVIC->ISER[ 0x1D >> 0x05 ] = 0x01 << ( 0x1D & 0x1F )
即NVIC->ISER[0] = 0x01 << ( 0x1D) = 0x2000 0000
因为ISER数组相对于NVIC基地址偏移量为0,由《CortexM3 Programming Manual》Table 44
第29位被置为1,而由前文可知TIM3在中断向量表的位置为29,所以即表示使能TIM3中断。
好了,到这里TIM3的配置就结束,最终它被配置为:72M时钟、1999 + 1 = 2000分频、时钟分频因子为0、向上计数模式、周期为3600,即3600 / ( 72 000 000 / (1999 + 1)) = 0.1秒。
中断配置为:模式为1位用于表示抢占优先级,3位用于表示子优先级、抢占优先级为最高优先级0、子优先级为最高优先级0、使能TIM中断。
总而言之,最后TIM3被配置为每0.1s进入一次中断,中断优先级为最高级。
原创作品