(STM32F103RCT6)
定时器时钟 TIMxCLK,即内部时钟 CK_INT,经 APB1 预分频器后分频提供,如果APB1 预分频系数等于 1,则频率不变,否则频率乘2,库函数中 APB1 预分频的系数是 2,即 PCLK1=36M,所以定时器时钟 TIMxCLK=36*2=72M。
定时器时钟经过 PSC 预分频器之后,即 CK_CNT,用来驱动计数器计数。PSC 是一个16 位的预分频器,可以对定时器时钟 TIMxCLK 进行 1~65536 之间的任何一个数进行分频。具体计算方式为:CK_CNT=TIMxCLK/(PSC+1)。
计数器 CNT 是一个 16 位的计数器,只能往上计数,最大计数值为 65535。当计数达到自动重装载寄存器的时候产生更新事件,并清零从头开始计数。
自动重装载寄存器 ARR 是一个 16 位的寄存器,这里面装着计数器能计数的最大数值。当计数到这个值的时候,如果使能了中断的话,定时器就产生溢出中断。
定时器的定时时间等于计数器的中断周期乘以中断的次数。计数器在 CK_CNT 的驱动下,计一个数的时间则是 CK_CLK 的倒数,等于:1/(TIMxCLK/(PSC+1)),产生一次中断的时间则等于:1/(CK_CLK * (ARR+1))。如果在中断服务程序里面设置一个变量 timer,用来记录中断的次数,那么就可以计算出我们需要的定时时间等于 : [1/(CK_CLK * (ARR+1))]*time。
ARR:自动重装载寄存器数值。 PSC:定时器时钟预分频数值。 TIMxCLK:主频。
时基单元包含:● 计数器寄存器(TIMx_CNT) ● 预分频器寄存器 (TIMx_PSC) ● 自动装载寄存器 (TIMx_ARR) ● 重复次数寄存器 (TIMx_RCR)
例如:主频为72M,预分频为72,则计一个数的时间为[(1/(72M/72))秒],即1us;自动重装载寄存器数值设置为1000-1,则利用记一个数的时间乘上该数值,即是产生一次中断的时间:1us * 1000 = 1ms。再用一个timer来记录产生中断的时间,那么可以知道,如果timer等于1000,则表示此时定时的时间为1s。
初始化结构体(3种)
基本定时器 | 输出比较 | 输入捕获 |
---|---|---|
TIM Time Base Init structure | TIM Output Compare Init structure | TIM Input Capture Init structure |
typedef struct
{
uint16_t TIM_Prescaler; /*!< 定时器时钟预分频数值 */
uint16_t TIM_CounterMode; /*!< 计数器计数模式,基本定时器只能向上计数,没有计数模式的设置 */
uint16_t TIM_Period; /*!< 自动重装载寄存器,定时器周期,累计TIM_Period+1个频率后产生一个更新或者中断 */
uint16_t TIM_ClockDivision; /*!< 时钟分频因子,基本定时器没有 */
uint8_t TIM_RepetitionCounter; /*!< 重复计数器的值,基本定时器没有 */
} TIM_TimeBaseInitTypeDef;
typedef struct
{
uint16_t TIM_OCMode; /*!< 比较输出模式 */
uint16_t TIM_OutputState; /*!< 比较输出使能 */
uint16_t TIM_OutputNState; /*!< 比较互补输出使能,只对TIM1和TIM8有效 */
uint16_t TIM_Pulse; /*!< 指定要加载到捕获比较寄存器中的脉冲宽度 */
uint16_t TIM_OCPolarity; /*!< 指定输出极性 */
uint16_t TIM_OCNPolarity; /*!< 指定互补输出的极性,只对TIM1和TIM8有效 */
uint16_t TIM_OCIdleState; /*!< 指定空闲状态下的TIM比较输出引脚状态,只对TIM1和TIM8有效 */
uint16_t TIM_OCNIdleState; /*!< 指定空闲状态下的TIM比较输出引脚状态,只对TIM1和TIM8有效 */
} TIM_OCInitTypeDef;
typedef struct
{
uint16_t TIM_Channel; /*!< 指定输入通道 */
uint16_t TIM_ICPolarity; /*!< 指定输入捕获触发选择 */
uint16_t TIM_ICSelection; /*!< 指定输入捕获选择 */
uint16_t TIM_ICPrescaler; /*!< 指定输入捕获定时器时钟预分频数值 */
uint16_t TIM_ICFilter; /*!< 指定输入捕获滤波器,这个值可以配置为0x0到0xF */
} TIM_ICInitTypeDef;
typedef struct
{
uint16_t TIM_OSSRState; /*!< 运行模式下的关闭状态选择 */
uint16_t TIM_OSSIState; /*!< 空闲模式下的关闭状态选择 */
uint16_t TIM_LOCKLevel; /*!< 锁定配置 */
uint16_t TIM_DeadTime; /*!< 死区时间,配置死区发生器 */
uint16_t TIM_Break; /*!< 断路输入功能选择 */
uint16_t TIM_BreakPolarity; /*!< 断路输入通道BRK极性选择 */
uint16_t TIM_AutomaticOutput; /*!< 自动输出使能 */
} TIM_BDTRInitTypeDef;
定时器分为基本定时器(TIM6和TIM7)、高级定时器(TIM1和TIM8)、通用定时器(TIM2、TIM3、TIM4、TIM5)
注意:TIM_TimeBaseInitTypeDef结构体里面有5个成员,TIM6和TIM7的寄存器里面只有TIM_Prescaler和TIM_Period,所以使用TIM6和TIM7的时候只需初始化这两个成员即可,另外三个成员是通用定时器和高级定时器才有。
然后编写中断服务函数,在stm32f10x_it.c文件中编写
由于基本定时器只能向上计数,所以检测的中断标志位为TIM_IT_Update,清除的中断未决位:TIM_FLAG_Update
(定时器每计时一次,检测一次标志位,如果定时完毕则time++,然后给该标志位写1)
在主函数中编写用户代码
//timer用来接收中断产生次数
int main(void)
{
LED_GPIO_Config();
BASIC_TIM_Init();
while(1)
{
if ( time == 1000 ) /* 1000 * 1 ms = 1s 时间到 */
{
time = 0; /* 记录中断次数值清零 */
......
}
}
}
和基本定时器的配置和使用基本一致,结构体中多了几个要配置的参数,使用也和基本定时器一致,都要在中断中编写中断回调函数。
结构体初始化:对应的GPIO、时基、输出比较模式、死区和刹车配置
PWM输出时的Dead Zone(死区)作用是在电平翻转时插入一个时间间隔,避免关闭前一个设备和打开后一个设备时因为开关速度的问题出现同时开启状态而增加负荷的情况(在没有彻底关闭前打开了后一个设备),尤其是电流过大时容易造成短路等损坏设备,如:互补PWM波输出在逆变器(直流转交流)中的应用。
刹车则是指当BKIN引脚检测到高电平或低电平的时候,输出比较信号被禁止输出,就像刹车一样。
利用PWM做电机调速时,可通过修改占空比来调节。
输出比较通道的互补通道,即电平和输出比较通道反相。
配置输出比较结构体以及初始化
利用虚拟示波器观察结果,也可用来做呼吸灯,控制电机速度等,但要用其它方法提高占空比的分辨率
(外部输入PWM波,stm32接收并进行数据处理)
输入捕获模式配置:时基、输入捕获模式
输入捕获模式和PWM输入模式的区别:
1、输入捕获模式
stm32的通用TIM2、3、4、5 都具有输入捕捉的功能,每个定时器具有四个通道,并且每一个通道都可以单独配置为输入捕捉式,主要用于测量输入信号的高电平时间,也可测量信号的频率(可能不太精确,尤其对于频率很高的信号),如果用于测量信号的高电平时间,配置时需要注意定时器的时基频率。
需要注意的问题:
定时器的时基频率不能太高,如果定时器工作于36M,采集50Hz的信号就会出现偏差(实测),所以程序中将定时器的时基频率配置为1M;如果存在两个以上中断,需要设置中断优先级,否则容易出问题。
2、PWM输入模式
PWM输入模式是输入捕捉模式的高级应用,对于测量频率较高的输入信号的频率特别精确,当然,为了实现这个模式,也得做出一点牺牲。相比于基本输入捕捉功能的实现来说,PWM输入模式中,一路输入信号同时映射到两个引脚,而且只有第一和第二通道可以配置为这种模式,换句话说,每个通用定时器只能测量一路输入信号。
与高级定时器的配置和使用完全相同,先配置定时器初始化结构体,清除中断标志位,开中断,使能计数器;然后编写中断回调函数;最后在主函数中编写用户代码。
与高级定时器中的输出4路占空比不同的PWM的配置一致,可用示波器观察。
先进行输入捕获参数配置,注意输入捕获模式和PWM输入模式的区别
别忘了配置输入捕获的GPIO口和中断优先级的配置
PWM信号 周期和占空比的计算
/* ---------------- PWM信号 周期和占空比的计算--------------- */
// ARR :自动重装载寄存器的值
// CLK_cnt:计数器的时钟,等于 Fck_int / (psc+1) = 72M/(psc+1)
// PWM 信号的周期 T = ARR * (1/CLK_cnt) = ARR*(PSC+1) / 72M
// 占空比P=CCR/(ARR+1)
在中断中记录一个脉冲的时间,先确定上升沿,再确定下降沿,两者之间的时间也就是一个脉冲高电平宽度。乘以2则是一个PWM波的总时间。具体实现过程如下
// 定时器输入捕获用户自定义变量结构体声明
typedef struct
{
uint8_t Capture_FinishFlag; // 捕获结束标志位
uint8_t Capture_StartFlag; // 捕获开始标志位
uint16_t Capture_CcrValue; // 捕获寄存器的值
uint16_t Capture_Period; // 自动重装载寄存器更新标志
}TIM_ICUserValueTypeDef;
void TIM5_IRQHandler(void)
{
// 当要被捕获的信号的周期大于定时器的最长定时时,定时器就会溢出,产生更新中断
// 这个时候我们需要把这个最长的定时周期加到捕获信号的时间里面去
if ( TIM_GetITStatus ( GENERAL_TIM, TIM_IT_Update) != RESET )
{
TIM_ICUserValueStructure.Capture_Period ++;
TIM_ClearITPendingBit ( GENERAL_TIM, TIM_FLAG_Update );
}
if ( TIM_GetITStatus (GENERAL_TIM, GENERAL_TIM_IT_CCx ) != RESET) // 上升沿捕获中断
{
// 第一次捕获
if ( TIM_ICUserValueStructure.Capture_StartFlag == 0 )
{
TIM_SetCounter ( GENERAL_TIM, 0 ); // 计数器清0
TIM_ICUserValueStructure.Capture_Period = 0; // 自动重装载寄存器更新标志清0
TIM_ICUserValueStructure.Capture_CcrValue = 0; // 存捕获比较寄存器的值的变量的值清0
// 当第一次捕获到上升沿之后,就把捕获边沿配置为下降沿
GENERAL_TIM_OCxPolarityConfig_FUN(GENERAL_TIM, TIM_ICPolarity_Falling);
TIM_ICUserValueStructure.Capture_StartFlag = 1; // 开始捕获标志置1
}
// 下降沿捕获中断
else // 第二次捕获
{
// 获取捕获比较寄存器的值,这个值就是捕获到的高电平的时间的值
TIM_ICUserValueStructure.Capture_CcrValue = GENERAL_TIM_GetCapturex_FUN (GENERAL_TIM);
// 当第二次捕获到下降沿之后,就把捕获边沿配置为上升沿,好开启新的一轮捕获
GENERAL_TIM_OCxPolarityConfig_FUN(GENERAL_TIM, TIM_ICPolarity_Rising);
TIM_ICUserValueStructure.Capture_StartFlag = 0; // 开始捕获标志清0
TIM_ICUserValueStructure.Capture_FinishFlag = 1; // 捕获完成标志置1
}
TIM_ClearITPendingBit (GENERAL_TIM,GENERAL_TIM_IT_CCx);
}
}
int main(void)
{
uint32_t time;
uint32_t TIM_PscCLK = 72000000 / (GENERAL_TIM_PSC+1);// TIM 计数器的驱动时钟
USART_Config();/* 串口初始化 */
GENERAL_TIM_Init();/* 定时器初始化 */
while ( 1 )
{
if(TIM_ICUserValueStructure.Capture_FinishFlag == 1)
{
//高电平时间的计数器的值 = 中断次数(自动重装载的次数)*(自动重装载寄存器的值+1)+(最后一次输入捕获1事件(IC1)传输的计数器值)
time = TIM_ICUserValueStructure.Capture_Period
* (GENERAL_TIM_PERIOD+1)
+ (TIM_ICUserValueStructure.Capture_CcrValue+1);
// 打印高电平脉宽时间
printf ( "\r\n测得高电平脉宽时间:%d.%d s\r\n",time/TIM_PscCLK,time%TIM_PscCLK );
TIM_ICUserValueStructure.Capture_FinishFlag = 0;
}
}
}