文章目录
泉水
Stm32F407的介绍
F4的定时器资源概述:F4开发指南,P207
如图F407有12个16位定时器,2个32位定时器
三种定时器区别
STM32通用定时器
- STM32F1 的通用定时器是通过可编程预分频器(PSC)驱动的 16 位自动装载计数器(CNT)构成。
STM32F1有TIM2、TIM3、TIM4 和 TIM5五个通用定时器
STM32 的通用定时器可以被用于:测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和 PWM)等。
使用定时器预分频器和 RCC 时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。
STM32 的每个通用定时器都是完全独立的,没有互相共享的任何资源。
CH即CLOCK Hours
F407的通用定时器工作过程
通用定时器的四个时钟来源:F4开发指南,13.1,P208
TIx时钟源
实际上是来源于输入捕获通道,之后的TI1FP1
ITR1时钟源
来自其它定时器的级联时钟源
CK_INT时钟源
CK_INT的时钟来源及大小:F4开发指南,13.1,P208
也就是说一般CK_INT的时钟是APB1的2倍。
TIMx_ETR时钟源
TIM2_CH1_ETR表示两个功能选一个,分别是TIM2_CH1和TIM2_ETR,TIM2_CH1表示让这个引脚作为TIM2的第一通道对应引脚;TIM2_ETR表示让这个引脚作为TIM2外部时钟提供引脚,这种功能有两种模式,如下图
通用定时器的功能
1)16 位向上、向下、向上/向下自动装载计数器(TIMx_CNT)。
2)16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为 1~65535 之间的任意数值。
3)4 个独立通道(TIMx_CH1~4),这些通道可以用来作为:
A.输入捕获
B.输出比较
C.PWM 生成(边缘或中间对齐模式)
D.单脉冲模式输出
4)可使用外部信号(TIMx_ETR)控制定时器和定时器互连(可以用 1 个定时器控制另外一个定时器)的同步电路。
5)如下事件发生时产生中断/DMA:
A.更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
B.触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
C.输入捕获
D.输出比较
E.支持针对定位的增量(正交)编码器和霍尔传感器电路
F.触发输入作为外部时钟或者按周期的电流管理
更新事件
通用定时器函数
TIM_SetCounter
/**
* @brief Sets the TIMx Counter Register value
*设置定时器的计数值。可以用来复位值。
* @param TIMx: where x can be 1 to 14 to select the TIM peripheral.
* @param Counter: specifies the Counter register new value.
* @retval None
*/
void TIM_SetCounter(TIM_TypeDef* TIMx, uint32_t Counter)
{
/* Check the parameters */
assert_param(IS_TIM_ALL_PERIPH(TIMx));
/* Set the Counter Register value */
TIMx->CNT = Counter;
}
TIM_OC1PolariryConfig
单独设置极性的函数。
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling);
TIM_SetCompare1
设置定时器x的CCR1寄存器(捕获比较寄存器)的值,也就是比较点的值。
每个定时器都有CCR寄存器 4 个,对应 4 个输通道 CH1~4。要想设置的话可以看TIM_SetCompare2等。
/**
* @brief Sets the TIMx Capture Compare1 Register value
* @param TIMx: where x can be 1 to 14 except 6 and 7, to select the TIM peripheral.
* @param Compare1: specifies the Capture Compare1 register new value.
* @retval None
*/
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint32_t Compare1)
{
/* Check the parameters */
assert_param(IS_TIM_LIST1_PERIPH(TIMx));
/* Set the Capture Compare1 Register value */
TIMx->CCR1 = Compare1;
}
延时函数
卡住计数
u8 ucErrTime=0;
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
通用定时器的寄存器
TIMx_CR1控制寄存器 1
中文参考手册15.4.1,P424
第 5,6位是设置计数对齐方式的。
9-8 CLK时钟分频因子
设置定时器的时钟分频因子为 1,2,4倍的定时器频率,在后面输入捕获时使用的比较多。
7 ARPE自动重载预装载使能
- 二者的区别如下,ARPE=1,ARR立即生效。APRE=0,ARR下个比较周期生效。
- PWM模式下必须使能预装载,自动重载预装载和输出比较预装载。才能及时改变占空比。
4 DIR计数方向
1 UDIS更新事件禁止位
0 CEN计数器使能位
计数器使能位,该位必须置 1,才能让定时器开始计数。
TIMx_DIER DMA/中断使能寄存器
DMA interrupt enable register
一个 16 位的寄存器
14 TED触发DMA请求使能
8 更新DMA请求使能
6 TIE触发信号中断使能
1 CC1IE捕获/比较1中断使能
0 UIE更新中断使能
TIMx_PSC预分频寄存器
Prescaler
预分频器值寄存器TIMx_PSC也存在影子寄存器,起作用的也正是影子寄存器(官方翻译为缓冲功能),所以在定时器启动后更改TIMx_PSC的值并不会立即影响当前定时器的时钟频率。要等到下一个更新事件(UEV)发生时才会生效。
一个16位正整数值,该寄存器用设置将定时器时钟源进行分频输出,然后提供给计数器,作为计数器的时钟。
预分频器的工作的工作原理是,定时器时钟源每tick一次,预分频器计数器值+1,直到达到预分频器的设定值,然后再tick一次后计数器归零,同时,CNT计数器值+1。
由此可以看出,因为达到最大值后还要再tick一次才归零,所以定时器时钟频率应该为Fosc/(PSC+ 1)。其中Fosc是定时器的时钟源。比如想对时钟源进行72分频,那么预分频器的值就应该设置为71。
TIMx_CNT计数寄存器
Count
该寄存器是定时器的计数器,该寄存器存储了当前定时器的计数值。
常用命令
TIM7->CNT = 0; 定时器计数清零。
TIMx_ARR 自动重装载寄存器
Automatic reload register
该寄存器在物理上实际对应着 2 个寄存器。
- 一个是程序员可以直接操作的,另外一个是程序员看不到的影子寄存器。事实上真正起作用的是影子寄存器。
- 根据 TIMx_CR1 寄存器中 APRE 位的设置:APRE=0 时,预装载寄存器的内容可以随时传送到影子寄存器,此时 2者是连通的;而 APRE=1 时,在每一次更新事件(UEV)时,才把预装在寄存器的内容传送到影子寄存器。
TIMx_SR状态寄存器
State Register
中文参考手册14.4.5,P372
该寄存器用来标记当前与定时器相关的各种事件/中断是否发生。
### 12 捕获比较4 重复捕获标志
6 TIF触发中断标记
2 CC2iF 捕获比较2 中断标记
0 UIF更新中断标记
清除标记位
在最后清除标志位的时候可以通过
TIM_ClearITPendingBit(TIM6,TIM_IT_Update); //清除中断标志位
TIM6->SR = 0xFE;
TIM7->SR = 0xFE 等价于 TIM_ClearITPendingBit(TIM7,TIM_IT_Update)
定时器中断清除函数(1111 1110)也就是说清除了更新中断。
TIMx_CCMR1/2捕获/比较模式寄存器
Capture compare mode register
该寄存器总共有 2 个,TIMx _CCMR1和 TIMx _CCMR2。TIMx_CCMR1 控制 CH1 和 2,而 TIMx_CCMR2 控制 CH3 和 4。
中文参考手册15.4.7,P432
上图把寄存器分了 2层,上面一层对应输出,而下面的则对应输入捕获模式。
7-4 IC1F(输入)捕获1滤波器
6-4 OC1M(输出)模式设置位
总共可以配置成 7 种模式,我们使用的是 PWM 模式,所以这 3 位必须设置为 110/111。这两种PWM 模式的区别就是输出电平的极性相反。
- 所谓有效电平要靠CCER->CC1P来设置
3 OC1PE(输出)输出比较的预装载使能
- PWM模式下必须使能预装载,自动重载预装载和输出比较预装载。才能及时改变占空比。
2 OC1FE(输出) 输出比较使能
3-2 ICPSC(输入) 预分频器
1-0 CC1S通道方向设置
CCxS 用于设置通道的方向(输入/输出)默认设置为 0,就是设置通道作为输出使用。
TIMx_CCER捕获/比较使能寄存器
Capture compare enable register
该寄存器控制着各个输入输出通道的开关。
1 CC1P
0 CC1E
该位是输入/捕获 1 输出使能位,要想PWM 从 IO 口输出,这个位必须设置为 1。
TIMx_CCR1~4捕获/比较寄存器
Capture compare register
该寄存器总共有 4 个,对应 4 个输通道 CH1~4。因为这 4 个寄存器都差不多,TIMx_CCR1 为例介绍
所有位,比较点装载
-
在输出模式下,该寄存器的值与 CNT 的值比较,根据比较结果产生相应动作。通过修改这个寄存器的值,就可以控制 PWM 的输出脉宽了。
如果使用的是 TIM3的通道 2,需要修改 TIM3_CCR2 以实现脉宽控制 DS0 的亮度。 -
在输入模式下,触发捕获的意义就在于将定时器的计数值加载到输入捕获比较寄存器。
实现定时器中断
介绍
定时器相关的库函数主要集中在固件库文件 stm32f10x_tim.h 和 stm32f10x_tim.c 文件中
TIM3 时钟使能
TIM3 是挂载在 APB1 之下,所以我们通过 APB1 总线下的使能使能函数来使能 TIM3。调用函数:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能
TIM_TimeBaseInit
在库函数中,定时器的初始化参数是通过初始化函数 TIM_TimeBaseInit 实现的:
在正式使用时,可以自己定义Time3_Init()这样的函数。关键的变量留出来,一般就是psc和arr就可以了。
void TIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
第一个参数是确定是哪个定时器。
第二个参数是定时器初始化参数结构体指针,结构体类型为 TIM_TimeBaseInitTypeDef
结构体的定义:
typedef struct
{
uint16_t TIM_Prescaler;
uint16_t TIM_CounterMode;
uint16_t TIM_Period;
uint16_t TIM_ClockDivision;
uint8_t TIM_RepetitionCounter;
} TIM_TimeBaseInitTypeDef;
这个结构体一共有 5 个成员变量,对于通用定时器只有前面四个参数有用,
最后一个参数 TIM_RepetitionCounter 是高级定时器才有用的,这里不多解释。
TIM_Prescaler
设置分频系数的。prescaler 是用来分频来自APBx的时钟频率,然后提供给定时器,作为定时器的心跳。一个16位正整数值
- 预分频寄存器(TIMx_PSC)
TIM_CounterMode计数器模式
设置计数方式,可以设置为向上计数,向下计数方式还有中央对齐计数方式。
- F407取值范围
#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))
向下计数模式时序图:
-
CK_CNT是时钟
-
CNT_EN使能
-
计数到0溢出
-
触发中断
-
向上计数模式:计数器从0计数到自动加载值(TIMx_ARR),然后重新从0开始计数并且产生一个计数器溢出事件。
-
中央对齐模式(向上/向下计数):计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件;然后再从0开始重新计数。
TIM_Period
第三个参数是设置自动重载计数周期值。
TIM_ClockDivision
设置时钟分频因子。ClockDivision是对于输入的分频,在输入捕获的时候要用到,决定数字滤波器采样频率的参数。
-
控制寄存器 1(TIMx_CR1)
之后在使用输入捕获滤波器时这些参数会被用到,可以根据硬件情况配置滤波。
-
F103取值范围:
#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))
TIM3 初始化范例代码格式:
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = 5000;
TIM_TimeBaseStructure.TIM_Prescaler =7199;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
中断时间计算
根据TIM3 的时钟和我们设计的 arr 和 psc 的值,就可以计算中断时间了。
计算公式如下:
Tout= ((arr+1)*(psc+1))/Tclk;
其中:
Tclk:TIM3 的时钟频率(单位为 Mhz)。
Tout:TIM3 溢出时间(单位为 us)。
Tclk/(psc+1)是频率,频率分之一是时钟,也就是秒/次,多少次?arr+1次,最终得到时间
TIM_ITConfig
设置 TIM3_DIER 寄存器的相应位使能更新中断。
在库函数里面定时器中断使能是通过 TIM_ITConfig 函数来实现的:
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
例如我们要使能 TIM3 的更新中断,格式为:
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE );
TIMx
选择定时器号
- F103取值为 TIM1~TIM17
- F407的取值如下
#define IS_TIM_ALL_PERIPH(PERIPH) (((PERIPH) == TIM1) || \
((PERIPH) == TIM2) || \
((PERIPH) == TIM3) || \
((PERIPH) == TIM4) || \
((PERIPH) == TIM5) || \
((PERIPH) == TIM6) || \
((PERIPH) == TIM7) || \
((PERIPH) == TIM8) || \
((PERIPH) == TIM9) || \
((PERIPH) == TIM10) || \
((PERIPH) == TIM11) || \
((PERIPH) == TIM12) || \
(((PERIPH) == TIM13) || \
((PERIPH) == TIM14)))
TIM_IT
使能的定时器中断的类型,定时器中断的类型有很多种,包括更新中断 TIM_IT_Update,触发中断 TIM_IT_Trigger,以及输入捕获中断等等。
- F103
#define TIM_IT_Update ((uint16_t)0x0001)
#define TIM_IT_CC1 ((uint16_t)0x0002)
#define TIM_IT_CC2 ((uint16_t)0x0004)
#define TIM_IT_CC3 ((uint16_t)0x0008)
#define TIM_IT_CC4 ((uint16_t)0x0010)
#define TIM_IT_COM ((uint16_t)0x0020)
#define TIM_IT_Trigger ((uint16_t)0x0040)
#define TIM_IT_Break ((uint16_t)0x0080)
- F407的取值如下
#define TIM_FLAG_Update ((uint16_t)0x0001)
#define TIM_FLAG_CC1 ((uint16_t)0x0002)
#define TIM_FLAG_CC2 ((uint16_t)0x0004)
#define TIM_FLAG_CC3 ((uint16_t)0x0008)
#define TIM_FLAG_CC4 ((uint16_t)0x0010)
#define TIM_FLAG_COM ((uint16_t)0x0020)
#define TIM_FLAG_Trigger ((uint16_t)0x0040)
#define TIM_FLAG_Break ((uint16_t)0x0080)
#define TIM_FLAG_CC1OF ((uint16_t)0x0200)
#define TIM_FLAG_CC2OF ((uint16_t)0x0400)
#define TIM_FLAG_CC3OF ((uint16_t)0x0800)
#define TIM_FLAG_CC4OF ((uint16_t)0x1000)
#define IS_TIM_GET_FLAG(FLAG) (((FLAG) == TIM_FLAG_Update) || \
((FLAG) == TIM_FLAG_CC1) || \
((FLAG) == TIM_FLAG_CC2) || \
((FLAG) == TIM_FLAG_CC3) || \
((FLAG) == TIM_FLAG_CC4) || \
((FLAG) == TIM_FLAG_COM) || \
((FLAG) == TIM_FLAG_Trigger) || \
((FLAG) == TIM_FLAG_Break) || \
((FLAG) == TIM_FLAG_CC1OF) || \
((FLAG) == TIM_FLAG_CC2OF) || \
((FLAG) == TIM_FLAG_CC3OF) || \
((FLAG) == TIM_FLAG_CC4OF))
NewState
第三个参数就很简单了,就是失能还是使能。
TIM3 中断优先级设置
在定时器中断使能之后,因为要产生中断,必不可少的要设置 NVIC 相关寄存器,设置中断优先级。
使能 TIM3
开启定时器通过 TIM3_CR1 的 CEN 位来设置。
在固件库里面使能定时器的函数是通过 TIM_Cmd 函数来实现的:
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)
这个函数非常简单,比如我们要使能定时器 3,方法为:
TIM_Cmd(TIM3, ENABLE); //使能 TIMx 外设
等价于 TIMx->CR1 |= TIM_CR1_CEN;
等价于 TIMx->CR1 |= 0x0001; 相加
失能即 TIMx->CR1 &= (uint16_t)~TIM_CR1_CEN;
等价于 TIMx->CR1 &= (uint16_t)FFFE; 相乘
编写中断服务函数
- 中断产生后,通过状态寄存器的值来判断此次产生的中断属于什么类型。
- 这里使用的是更新(溢出)中断,所以在状态寄存器 SR 的最低位。
- 在处理完中断之后应该向 TIM3_SR 的最低位写 0,来清除该中断标志。
在固件库函数里面,用来读取中断状态寄存器的值判断中断类型的函数是:
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);
比如,我们要判断定时器 3 是否发生更新(溢出)中断,方法为:
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET){}
等价于 TIM3->SR & TIM_FLAG_Update != RESET
固件库中清除中断标志位的函数是,比如我们在TIM3 的溢出中断发生后,我们要清除中断标志位:
TIM_ClearITPendingBit(TIM3, TIM_IT_Update );
- 固件库还提供了两个函数用来判断定时器状态以及清除定时器状态标志位的函数
- TIM_GetFlagStatus 和 TIM_ClearFlag,他们的作用和前面两个函数的作用类似。
在 TIM_GetITStatus 函数中会先判断这种中断是否使能,使能了才去判断中断标志位,而TIM_GetFlagStatus 直接用来判断状态标志位。
代码
主函数
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
delay_init(168); //初始化延时函数
LED_Init(); //初始化LED端口
TIM3_Int_Init(5000-1,8400-1); //定时器时钟84M,分频系数8400,所以84M/8400=10Khz的计数频率,计数5000次为500ms
while(1)
{
LED0=!LED0;//DS0翻转
delay_ms(200);//延时200ms
};
}
中断处理函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
{
LED1=!LED1;//DS1翻转
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}
LED初始化函数
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);//使能GPIOF时钟
//GPIOF9,F10初始化设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化
GPIO_SetBits(GPIOF,GPIO_Pin_9 | GPIO_Pin_10);//GPIOF9,F10设置高,灯灭
}