前置技能 : 通用定时器原理
我们将通用定时器分为四个部分:
1,选择时钟
2,时基电路
3,输入捕获
4,输出比较
本节定时器PWM输出主要涉及到定时器框图左下方部分,即输入捕获部分
一个通道的框图:
作用:捕获TIMx_CH1通道信号的跳变,并触发捕获中断
fDTS是根据TIMx_CR1-CKD[1:0]的设置确定的
如果TIMx_CR1-CKD[1:0]设置为00,则fTDS=fCK_INT = 72Mhz
如果TIMx_CR1-CKD[1:0]设置为10,则fTDS=fCK_INT / 4 = 18Mhz
TIMx_CCMR1-ICF[3:0]输入捕获1滤波器,设置输入采样频率和数字滤波长度
如果TIMx_CCMR1-IC1F[3:0]=0011,采样频率fSAMPLING=fCK_INT , 数字滤波长度为8
举例:
如果IC1映射到通道1,且设置为上升沿捕获
当出现上升沿后,以fCK_INT频率连续采集8次通道1的电平,如果都是高电平,说明是一次有效触发
如果此时开启了输入捕获中断,会进入输入捕获中断
这样会过滤掉小于8个采样周期的脉冲信号,达到滤波目的
经过滤波器的信号(步骤1),再进入边沿检测器,通过TIMx_CCER-CC1P设置上升沿检测还是下降沿检测
TIMx_CCER-CC1P[0]设置边沿检测器上升沿检测还是下降沿检测
0:上升沿捕获
1:下降沿捕获
如图,已将干扰部分隐去
通过设置TIMx_CCMR1-CC1S[1:0],设置通道方向(输入/输出),及输入脚的选择
当CC1S=00时,为输出比较
当CC1S=01时,为输入捕获,IC1映射在TI1上
当CC1S=10时,为输入捕获,IC1映射在TI2上
回到定时器框图,本节输入捕获部分
如图:
以IC1为例,是可以映射到TI1或TI2的
TI1捕获的信号可以连到IC1也可以连接到IC2
也就是TI1和TI2的IC1和IC2是可以彼此交叉的
通过设置TIMx_CCMR1-ICPS[1:0]位,设置捕获分频系数,达到分频目的
例如:
当设置为上升沿触发,TIMx_CCMR1-ICPS[1:0]=01时,每两个上升沿捕获一次,相当于2分频
例如:
配置TIMx_DIER-CC1IE[1]=1,开启捕获比较通道1中断
若设置为上升沿捕获,当捕获到上升沿时,会触发捕获/比较中断
// 1,输入捕获通道初始化函数
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);
// TIM_ICInitTypeDef结构体
typedef struct {
uint16_t TIM_Channel; //捕获通道1-4
uint16_t TIM_ICPolarity; //捕获极性
uint16_t TIM_ICSelection; //映射关系
uint16_t TIM_ICPrescaler; //分频系数
uint16_t TIM_ICFilter; //滤波器
} TIM_ICInitTypeDef;
// 2,通道极性设置函数
void TIM_OCxPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
// 3,获取通道捕获值
// 每触发一次捕获,都会将计数器值CNT加载到捕获比较寄存器中,可以读取
uint32_t TIM_GetCapture1(TIM_TypeDef* TIMx);
// 1,初始化定时器和通道对应IO的时钟。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 2,初始化IO口,模式为输入:GPIO_Init();
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0 输入
// 3,初始化定时器,设置自动装在职ARR,预分频系数PSC,确定每一个时钟周期时间
TIM_TimeBaseInit();
// 4,初始化输入捕获通道,设置哪个沿捕获,是否分频等
TIM_ICInit();
// 5,设置开启捕获中断
TIM_ITConfig();
NVIC_Init();(需在主函数配置中断优先级分钟组)
// 6,使能定时器:
TIM_Cmd();
// 7,编写中断服务函数:
TIMx_IRQHandler();
PA0引脚有一个Weak_UP按键,按下为高电平,抬起为低电平,请参考 : 按键输入-GPIO输入
使用TIM5_CH1捕获PA0高电平持续时间
方法:
先设置为上升沿捕获,捕获后将定时器值设置为0
然后设置为下降沿捕获,捕获到下降沿时,将计数器值记录下来
上升沿持续时间 = ( A-0 ) * 1 / f
timer.h
#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"
void TIM5_Cap_Init(u16 arr,u16 psc);
#endif
timer.c
#include "timer.h"
#include "led.h"
#include "usart.h"
// 定时器5通道1输入捕获配置
TIM_ICInitTypeDef TIM5_ICInitStructure;
// arr 重装载值
// psc 预分频系数
void TIM5_Cap_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); // 使能定时器5时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // PA0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;// PA0 输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_ResetBits(GPIOA,GPIO_Pin_0); // PA0 下拉(默认低电平,按下为高电平)
//初始化 TIM5
TIM_TimeBaseStructure.TIM_Period = arr; // 重装载值
TIM_TimeBaseStructure.TIM_Prescaler =psc; // 预分频器
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;// 向上计数模式
TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure);
//初始化ITM5输入捕获参数
TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1; // CC1S=01 选择输入端 IC1映射到TI1上
TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; // 上升沿捕获
TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;// 映射到TI1
TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; // 配置输入分频-不分频
TIM5_ICInitStructure.TIM_ICFilter = 0x00;// IC1F=0000 配置输入滤波器-不滤波
TIM_ICInit(TIM5, &TIM5_ICInitStructure);
// 中断分组初始化
NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn; // TIM5中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 抢占优先级2级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 子优先级0级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // IRQ通道使能
NVIC_Init(&NVIC_InitStructure);
TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE); // 使能更新中断,使能CC1IE捕获中断
TIM_Cmd(TIM5,ENABLE ); // 使能定时器5
}
u8 TIM5CH1_CAPTURE_STA=0; // 输入捕获状态
u16 TIM5CH1_CAPTURE_VAL; // 记录输入比较值
// 定时器5中断服务函数
void TIM5_IRQHandler(void)
{
if((TIM5CH1_CAPTURE_STA&0X80)==0) // 还未捕获成功
{
if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET) // 更新中断-捕获过程中,溢出了
{
if(TIM5CH1_CAPTURE_STA&0X40) // 已经捕获到了高电平-说明在捕获过程中,溢出了
{
if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F) // 捕获的时间超长了(2的6次方减1)
{
TIM5CH1_CAPTURE_STA|=0X80; // 标记成功捕获
TIM5CH1_CAPTURE_VAL=0XFFFF; // 赋一个最大值
}else TIM5CH1_CAPTURE_STA++; // 溢出次数+1
}
}
if (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET) // 捕获中断
{
if(TIM5CH1_CAPTURE_STA&0X40) // STA位6是否为1,若为1说明前一次为上升沿,本次为下降沿
{
TIM5CH1_CAPTURE_STA|=0X80; //本次捕获完成,标记捕获完成
TIM5CH1_CAPTURE_VAL=TIM_GetCapture1(TIM5); //将捕获比较寄存器的值记录到Val
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising); //CC1P=0 设置为上升沿捕获,为下一次捕获做准备
}else // STA位6不为1,说明上一次未捕获上升沿,本次为上升沿,开始一次计数
{
TIM5CH1_CAPTURE_STA=0; // 清空
TIM5CH1_CAPTURE_VAL=0; // 清空-准备计数
TIM_SetCounter(TIM5,0); // 计数器值设置为0
TIM5CH1_CAPTURE_STA|=0X40; // 标记本次捕获到了上升沿
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling);// CC1P=1 捕获极性设置为下降沿,为下一次捕获做准备
}
}
}
TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); // 清除中断标志位
}
main.c
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"
#include "timer.h"
extern u8 TIM5CH1_CAPTURE_STA;
extern u16 TIM5CH1_CAPTURE_VAL;
int main(void)
{
u32 temp=0;
delay_init(); // 延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组
LED_Init(); // LED初始化
uart_init(115200); // 配置串口 115200
TIM3_PWM_Init(899,0); // PWM频率=72000/(899+1)=80Khz
TIM5_Cap_Init(0XFFFF,72-1); // TIM5为16为定时器,ARR最大0xFFFF,预分频系数72-1,通用定时器时钟72M,一个计数器周期为1ms
while(1)
{
delay_ms(10);
TIM_SetCompare2(TIM3,TIM_GetCapture2(TIM3)+1);
if(TIM_GetCapture2(TIM3)==300)TIM_SetCompare2(TIM3,0);
if(TIM5CH1_CAPTURE_STA&0X80)// 检测最高位是否为1,如果是1说明已经捕获到了上升沿
{
temp=TIM5CH1_CAPTURE_STA&0X3F;// 取低六位,看计数器溢出了多少次
temp*=65536;// 到0xFFFF才溢出,所以事件总和(微秒) = 溢出次数 * 2的16次方(65536)
temp+=TIM5CH1_CAPTURE_VAL;// 再加上没有溢出的这段计时,得到总时间(微秒)
printf("HIGH:%d us\r\n",temp);// 打印总时间
TIM5CH1_CAPTURE_STA=0;// 开启下一次捕获:捕获中断函数才会进入判断
}
}
}
使用串口调试助手XCOM,输出按键按下时间,以微秒为单位