第一个项目
第一步,RCC开启时钟,这个基本上每个代码都是第一步,不用多想,在这里打开时钟后,定时器的基准时钟和整个外设的工作时钟就都会同时打开了
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
第二步,选择时基单元的时钟源,对于定时中断,我们就选择内部时钟源
TIM_InternalClockConfig(TIM2);//很多人不写这个函数,因为定时器上电后默认就是内部时钟
第三步,配置时基单元,包括预分频器、自动重装器、计数模式等等,这些参数用一个struct就可以配置好
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1; //ARR自动重装器的值【0-65535】
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1; //PSC预分频器的值【0-65535】,两者取值都不是唯一的
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器的值(高级定时器c)
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
上面并没有CNT计数器的参数,如果我们需要的话,可以用之前说的SetCounter和GetCounter这两个函数来操作计数器
如果我们想定时1秒(也就是定时频率为1Hz),用下面这个公式来算
计数器溢出频率: C K _ C N T _ O V = C K _ C N T / ( A R R + 1 ) = C K _ P S C / ( P S C + 1 ) / ( A R R + 1 ) CK\_CNT\_OV = CK\_CNT / (ARR + 1) = CK\_PSC / (PSC + 1) / (ARR + 1) CK_CNT_OV=CK_CNT/(ARR+1)=CK_PSC/(PSC+1)/(ARR+1)
TIM_TimeBaseInitStructure
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 */ //TIM_Clock_Division_CKD是用来干啥的呢?滤波器可以滤掉信号的抖动干扰,怎么工作的呢?就是在一个固定时钟频率f下进行采样,如果连续N个采样点都为相同的电平,那就代表输入信号稳定了,就把采样值输出出去,如果这N个采样值不全都相同,那就说明信号有抖动,这时就保持上一次的输出,或者直接输出低电平也行,这样就可以保证输出信号在一定程度上的滤波了,这里的采样频率f(可以内部时钟直接而来,也可以由内部时钟加一个时钟分频而来,分频多少就是由TIM_Clock_Division_CKD决定,这个参数和时基单元关系并不大,我们随便配一个就行)和采样点N都是滤波器的参数,频率越低,采样点数越多,那滤波效果就越好,不过相应的信号延迟就大
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 (N).
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_Clock_Division_CKD
/** @defgroup TIM_Clock_Division_CKD
* @{
*/
#define TIM_CKD_DIV1 ((uint16_t)0x0000) //不分频
#define TIM_CKD_DIV2 ((uint16_t)0x0100) //二分频
#define TIM_CKD_DIV4 ((uint16_t)0x0200) //四分频
#define IS_TIM_CKD_DIV(DIV) (((DIV) == TIM_CKD_DIV1) || \
((DIV) == TIM_CKD_DIV2) || \
((DIV) == TIM_CKD_DIV4))
/**
* @}
*/
TIM_Counter_Mode
/** @defgroup TIM_Counter_Mode
* @{
*/
#define TIM_CounterMode_Up ((uint16_t)0x0000) //向上计数
#define TIM_CounterMode_Down ((uint16_t)0x0010) //向下计数
#define TIM_CounterMode_CenterAligned1 ((uint16_t)0x0020) //三种中央对齐模式
#define TIM_CounterMode_CenterAligned2 ((uint16_t)0x0040)
#define TIM_CounterMode_CenterAligned3 ((uint16_t)0x0060)
#define IS_TIM_COUNTER_MODE(MODE) (((MODE) == TIM_CounterMode_Up) || \
((MODE) == TIM_CounterMode_Down) || \
((MODE) == TIM_CounterMode_CenterAligned1) || \
((MODE) == TIM_CounterMode_CenterAligned2) || \
((MODE) == TIM_CounterMode_CenterAligned3))
/**
* @}
*/
第四步,配置输出中断控制,允许更新中断输出到NVIC
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);//开启了更新中断到NVIC的通路
/**
* @brief Enables or disables the specified TIM interrupts.
* @param TIMx: where x can be 1 to 17 to select the TIMx peripheral.
* @param TIM_IT: specifies the TIM interrupts sources to be enabled or disabled.
* This parameter can be any combination of the following values:
* @arg TIM_IT_Update: TIM update Interrupt source \\ 更新中断
* @arg TIM_IT_CC1: TIM Capture Compare 1 Interrupt source
* @arg TIM_IT_CC2: TIM Capture Compare 2 Interrupt source
* @arg TIM_IT_CC3: TIM Capture Compare 3 Interrupt source
* @arg TIM_IT_CC4: TIM Capture Compare 4 Interrupt source
* @arg TIM_IT_COM: TIM Commutation Interrupt source
* @arg TIM_IT_Trigger: TIM Trigger Interrupt source
* @arg TIM_IT_Break: TIM Break Interrupt source
* @note
* - TIM6 and TIM7 can only generate an update interrupt.
* - TIM9, TIM12 and TIM15 can have only TIM_IT_Update, TIM_IT_CC1,
* TIM_IT_CC2 or TIM_IT_Trigger.
* - TIM10, TIM11, TIM13, TIM14, TIM16 and TIM17 can have TIM_IT_Update or TIM_IT_CC1.
* - TIM_IT_Break is used only with TIM1, TIM8 and TIM15.
* - TIM_IT_COM is used only with TIM1, TIM8, TIM15, TIM16 and TIM17.
* @param NewState: new state of the TIM interrupts.
* This parameter can be: ENABLE or DISABLE.
* @retval None
*/
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState)
{
/* Check the parameters */
assert_param(IS_TIM_ALL_PERIPH(TIMx));
assert_param(IS_TIM_IT(TIM_IT));
assert_param(IS_FUNCTIONAL_STATE(NewState));
if (NewState != DISABLE)
{
/* Enable the Interrupt sources */
TIMx->DIER |= TIM_IT;
}
else
{
/* Disable the Interrupt sources */
TIMx->DIER &= (uint16_t)~TIM_IT;
}
}
第五步,配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级(和中断那一节流程一样)
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
第六步,运行控制
TIM_Cmd(TIM2, ENABLE);
整个模块配置完成后,我们还需要使能一下计数器(要不然计数器不会运行),当定时器使能后,计数器就会开始计数了,当数器更新时,触发中断
最后我们再写一个定时器的中断函数
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
这样这个中断函数每隔一段时间就能自动执行一次了
定时器的库函数
void TIM_DeInit(TIM_TypeDef* TIMx);
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct); //时基单元初始化,它就是用来配置这个图里这里的时基单元的
//第一个TIMx选择某个定时器,第二个是结构体,里面包含了配置时基单元的一些参数
void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);//结构体变量赋一个默认值,
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);//这个是用来使能计数器的,对应的就是图里面的运行控制,第二个NewState新的状态,也就是使能还是失能
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);//这个是用来使能外设的中断输出信号的,对应的就是图里面的中断输出控制,第二个TIM_IT,选择要配置哪个中断输出,第三个,使能与否
下面6个函数对应的就是时基单元的时钟选择部分,可以选择RCC内部时钟、ETR外部时钟、ITRx其他定时器、TIx捕获通道这些
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);//选择内部时钟
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);//选择ITRx其他定时器的时钟,InputTriggerSource:选择要接入哪个其他的定时器
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,
uint16_t TIM_ICPolarity, uint16_t ICFilter);//选择TIx捕获通道的时钟,TIxExternalCLKSource,选择TIx具体的某个引脚,接着还有两个参数ICPolarity和CFilter,输入的极性和滤波器,对于外部引脚的波形,一般都会有极性选择和滤波器,这样更灵活一些
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);//选择ETR通过外部时钟模式1输入的时钟,ExtTRGPrescaler,外部触发预分频器(这里可以对ETR的外部时钟再提前做一个分频),接下来两个和上面那个函数参数一样
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler,
uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);//选择ETR通过外部时钟模式2输入的时钟
//对于ETR输入的外部时钟而言,上面这两个函数是等效的,它们的参数是一样的,如果不需要触发输入的功能,那这两个函数可以互换
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);//这个不是用来选择时钟的,就是单独用来配置ETR引脚的预分频器、极性、滤波器这些参数的
TIM_ITRxExternalClockConfig
因为在初始化结构体里有很多关键的参数,比如自动重装值和预分频值等等,这些参数可能会在初始化之后还需要更改,如果为了改某个参数,再调用一次初始化函数,这样太麻烦了,所以这里有一些单独的函数,可以方便地更改这些关键参数
void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode); //单独用来写预分频值的,Prescaler,要写入的预分频值,TIM_PSCReloadMode,写入的模式(预分频器有一个缓冲器,这个参数决定这个影子寄存器是否生效,或者是在写入后,手动产生一个更新事件,让这个值立刻生效)
void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode); //改变计数器的计数模式的,TIM_CounterMode:选择新的计数器模式
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);//自动重装器预装功能配置,有无预装是可以自己选择的,使能与否就可以使这个是否有无预装
void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);//给计数器写入一个值,如果你想手动给一个计数值,就可以用这个函数
void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);//给自动重装器写入一个值
uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);//获取当前计数器的值
uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);//获取当前预分频器的值
下面四个就是之前中断里面学习的那四个函数,获取标志位和清除标志位的
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
主函数内容
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
uint16_t Num;
int main(void)
{
OLED_Init();
Timer_Init();
OLED_ShowString(1, 1, "Num:");
while (1)
{
OLED_ShowNum(1, 5, Num, 5);
}
}
//为了不让Num跨文件,所以把中断函数放到了这个
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
Num ++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
我们调试运行发现,每次复位Num都是从1开始计数的,按理说Num的初始值是0,应该是从0开始计数的,这说明中断函数在初始化之后就立刻进入了一次,这是怎么回事呢?
打开一下TIM_TimeBaseInit函数的定义
英文意思是:生成一个更新事件,来重新装载预分频器和重复计数器的值,立刻
我们知道这个预分频器是有个缓冲寄存器的,我们写的值只有在更新事件时,才会真正起作用,这里为了让值立刻起作用,所以在这最后,手动生成了一个更新事件,这样预分频器的值就有效了,但同时,它的副作用就是,更新事件和更新中断是同时发生的,更新中断会置更新中断标志位,当我们之后一但初始化完了,更新中断就会立刻进入,这就是我们刚一上电,就立该进中断的原因
解决方案:在TIM_TimeBaseInit后面,开启中断的前面,再手动调用一下TIM_ClearFlag(TIM2, TIM_FLAG_Update);
第二个项目
接线图:
这个PA0引脚就是TIM2的ETR引脚,我们在这个引脚输入一个外部时钟
TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0F);//通过ETR引脚的外部时钟模式2配置
/**
* @brief Configures the External clock Mode2
* @param TIMx: where x can be 1, 2, 3, 4, 5 or 8 to select the TIM peripheral.
* @param TIM_ExtTRGPrescaler: The external Trigger Prescaler. 外部触发预分频器,可以是下面这些值
* This parameter can be one of the following values:
* @arg TIM_ExtTRGPSC_OFF: ETRP Prescaler OFF. //不需要分频就选择第一个
* @arg TIM_ExtTRGPSC_DIV2: ETRP frequency divided by 2.
* @arg TIM_ExtTRGPSC_DIV4: ETRP frequency divided by 4.
* @arg TIM_ExtTRGPSC_DIV8: ETRP frequency divided by 8.
* @param TIM_ExtTRGPolarity: The external Trigger Polarity. //外部触发的极性
* This parameter can be one of the following values:
* @arg TIM_ExtTRGPolarity_Inverted: active low or falling edge active. //反向,就是低电平或下降沿有效
* @arg TIM_ExtTRGPolarity_NonInverted: active high or rising edge active.//不反向,高电平或上升沿有效(目前选这个)
* @param ExtTRGFilter: External Trigger Filter.外部触发滤波器,这个值必须是0x00到0x0F之间的一个值,这个值就是来决定上面讲的滤波器的f和N的
* This parameter must be a value between 0x00 and 0x0F
* @retval None
*/
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler,
uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter)
{
/* Check the parameters */
assert_param(IS_TIM_LIST3_PERIPH(TIMx));
assert_param(IS_TIM_EXT_PRESCALER(TIM_ExtTRGPrescaler));
assert_param(IS_TIM_EXT_POLARITY(TIM_ExtTRGPolarity));
assert_param(IS_TIM_EXT_FILTER(ExtTRGFilter));
/* Configure the ETR Clock source */
TIM_ETRConfig(TIMx, TIM_ExtTRGPrescaler, TIM_ExtTRGPolarity, ExtTRGFilter);
/* Enable the External clock mode2 */
TIMx->SMCR |= TIM_SMCR_ECE;
}
ExtTRGFilter的取值
我们暂时不用滤波器,所以写0x00就行了
因为引脚用到了GPIO所以我们也要初始化GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
但是浮空输入(如果你外部的输入信号功率很小,内部的这个上拉电阻可能会影响到这个输入信号,这时采用浮空输入,防止外部输入的电平)会导致电平跳个没完,所以还是给了上拉输入
TIM_TimeBaseInitStructure.TIM_Period = 10 - 1; //从0计到9就行了
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;//手动模拟的输入没有那么快,所以不需要分频