STM32输入捕获功能,使用TIM2和TIM3同时捕获8路波形。
工作上的一个需求,要采集两组控制步进电机的信号。就是采集8个通道的方波,测量频率和计脉冲个数。看起来简简单单的东西,磕磕碰碰搞了两天,走了一些弯路,这里写出来记录一下。顺便理解一下输入捕获个多种用法。
1. 硬件设计
首先看一下用到那些IO口。在STM32手册里找这样TIMx_CHx的就是输入捕获的口了。
这里用了TIM2的4个口和TIM3的4个口。
定义 | 端口 |
---|---|
TIM2_CH1 | PA0 |
TIM2_CH2 | PA1 |
TIM2_CH3 | PA2 |
TIM2_CH4 | PA3 |
TIM3_CH1 | PA6 |
TIM3_CH2 | PA7 |
TIM3_CH3 | PB0 |
TIM3_CH3 | PB1 |
2. 定时器和输入捕获配置(初始化)
比较长,大概分为,初始化时钟,初始化端口,初始化定时器,配置中断优先级,设置上升沿,使能中断和定时器。
主程序里的调用
TIM2_Cap_Init(0XFFFF,72-1); //1MHz的频率计数
Q:定时器的时间如何计算呢?
A:72000000/72=1000000
TIM_ICInitTypeDef TIM_ICInitStructure;
void TIM2_Cap_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //使能定时器2时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //使能定时器3时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //使能GPIOB时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_6 | GPIO_Pin_7; //
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //设置成输入 并 全部拉低
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_ResetBits(GPIOA,GPIO_Pin_0);
GPIO_ResetBits(GPIOA,GPIO_Pin_1);
GPIO_ResetBits(GPIOA,GPIO_Pin_2);
GPIO_ResetBits(GPIOA,GPIO_Pin_3);
GPIO_ResetBits(GPIOA,GPIO_Pin_6);
GPIO_ResetBits(GPIOA,GPIO_Pin_7);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; //
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //设置成输入 并 全部拉低
GPIO_Init(GPIOB,&GPIO_InitStructure);
GPIO_ResetBits(GPIOB,GPIO_Pin_0);
GPIO_ResetBits(GPIOB,GPIO_Pin_1);
//初始化定时器2
TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装值
TIM_TimeBaseStructure.TIM_Prescaler = psc; //设置与分配器
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //设置向上计数
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure); //配置TIM2
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure); //配置TIM3
//初始化输入捕获参数
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择输入端口1:CH1
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置分配为不分配
TIM_ICInitStructure.TIM_ICFilter = 0x00;//配置输入滤波为不滤波
TIM_ICInit(TIM2,&TIM_ICInitStructure);
TIM_ICInit(TIM3,&TIM_ICInitStructure);
//下面的都一样,就不一一说明了。
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0x00;
TIM_ICInit(TIM2,&TIM_ICInitStructure);
TIM_ICInit(TIM3,&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_3;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0x00;
TIM_ICInit(TIM2,&TIM_ICInitStructure);
TIM_ICInit(TIM3,&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_4;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0x00;
TIM_ICInit(TIM2,&TIM_ICInitStructure);
TIM_ICInit(TIM3,&TIM_ICInitStructure);
//中断分组优先级初始化
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //TIM2中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //先占优先级2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //从优先级2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //配置寄存器
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3; //先占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //配置寄存器
//8个通道都设置成,上升沿捕获
TIM_OC1PolarityConfig(TIM2,TIM_ICPolarity_Rising);
TIM_OC2PolarityConfig(TIM2,TIM_ICPolarity_Rising);
TIM_OC3PolarityConfig(TIM2,TIM_ICPolarity_Rising);
TIM_OC4PolarityConfig(TIM2,TIM_ICPolarity_Rising);
TIM_OC1PolarityConfig(TIM3,TIM_ICPolarity_Rising);
TIM_OC2PolarityConfig(TIM3,TIM_ICPolarity_Rising);
TIM_OC3PolarityConfig(TIM3,TIM_ICPolarity_Rising);
TIM_OC4PolarityConfig(TIM3,TIM_ICPolarity_Rising);
//允许更新中断,允许CCxIE捕获中断
TIM_ITConfig(TIM2,TIM_IT_Update|TIM_IT_CC1|TIM_IT_CC2|TIM_IT_CC3|TIM_IT_CC4,ENABLE);//
TIM_ITConfig(TIM3,TIM_IT_Update|TIM_IT_CC1|TIM_IT_CC2|TIM_IT_CC3|TIM_IT_CC4,ENABLE);//
//使能定时器
TIM_Cmd(TIM2,ENABLE);
TIM_Cmd(TIM3,ENABLE);
}
3. 中断函数
这里举一个例子,TIM2_CH1的,其他的复制粘贴改一下就行。
捕获方波的思路就是,捕获两个上升沿,两个上升沿的时间差就是周期。周期取倒数就是频率呗。
u8 TIMCH_STA[8] = {0}; //标志位数组
u16 TIMCH_VAL[8] = {0}; //计时器数组
void TIM2_IRQHandler(void)
{
if((TIMCH_STA[0]&0X80)==0)//还未捕获到一个完整的周期
{
if(TIM_GetITStatus(TIM2,TIM_IT_CC1) != RESET)//捕获1发生捕获事件
{
if(TIMCH_STA[0]&0X40) //捕获到第二个上升沿
{
TIMCH_STA[0]|=0X80; //标记捕获到一个完整的周期,给主程序处理
TIMCH_VAL[0]=TIM_GetCapture1(TIM2);//获取事件
}
else //捕获到第一个上升沿
{
TIMCH_STA[0]=0; //清空标志位
TIMCH_VAL[0]=0; //清空计时器
TIM_SetCounter(TIM2,0); //清空定时器
TIMCH_STA[0]|=0X40; //标记捕获到第一个上升沿
}
}
}
}
主程序里就很简单了
for(i=0;i<8;i++)//扫描8个通道的标志位
{
if(TIMCH_STA[i]&0X80)//成功捕获到脉冲
{
temp = TIMCH_VAL[i]; //获取周期时间
printf("HIGH[%d]:%d us\r\n",i,temp); //打印输出
TIMCH_STA[i]=0; //复位标志位,开启下一次捕获
}
}
4. 注意事项和扩展说明
1. 采样精度
输入一个1KHz的频率,采到的数是999us。也就是1.001KHz,四舍五入就是1KHz。由于电路也没滤波有误差也情有可原。
2. 采样范围
因为定时器的频率是1M,所以10KHz一下的采的才比较准。测试时候给了一个50KHz,采样的数据是19纳秒,也就是52.6KHz,就不太准了。而且计数器的数组也只是U16型,就是说如果频率小于15.25HZ,就溢出了。
所以这个程序的采样范围就是50Hz~5KHz,如果要大或者要小,就得调整程序。就像示波器不也是要调整频率的吗。
3. 如果要采占空比
第一个上升沿和第二个上升沿的时间是周期,那第一个上升沿和第一个下降沿的时间就是高电平的时间,算一下就是占空比了。
在采到上升沿的时候,把捕获改成捕获下降沿,采到下降沿的时候,改成捕获上升沿。就可以算出占空比了。但是实际测试中精度下检了,大概是因为,在中断里面改配置捕获会占用时间。
TIM_OC1PolarityConfig(TIM3,TIM_ICPolarity_Falling);//捕获下降沿。