最近在做直流电机测速相关的内容,发现大多数都是STM32F4系列单片机进行测速,本文重点介绍STM32F1系列单片机的测速原理和代码展示。
最开始是使用的传统的M/T进行测速,但是在实验过程中发现存在较大误差。在查阅相关资料后发现STM32是自带定时器的编码器模式的,使用编码器模式,即可以方便的计算转速,方向,频率在测试误差后发现误差远比使用定时器中断捕获脉冲小。话不多说,接下来给大家介绍原理和代码实现。
首先,编码器是怎么使用的呢?我们的测速原理是什么?
当电机转动时,光电码盘会随之转动,同时产生一定数量的A,B相脉冲。我们通过单片机测得一定时间内的脉冲数就可以计算电机转速和频率了。具体来说,电机转动一圈会产生一定数量的脉冲(我们把这个脉冲命名为分辨率),分辨率越高,测速效果也就越准确。我们计算出来的脉冲除以这个分辨率再除以时间,也就得到了这个电机的转速。同时由于AB相存在90°的相位差,我们只需要比较A相在前还是B相在前就可以测出电机旋转的方向。
原理理解清楚后,问题就在于怎么测得这个脉冲数。
一般来说有两种方法,一是通过M/T法.在规定的采样周期 Tc 内,同时计算编码器脉冲个数 M1 与高频时钟脉冲个数 M2 ,两个计数保持严格同步,检测时间与编码器输出脉冲保持一致,最大限度的减小误差。
计算公式为:
M1为单位时间内定时器记得的编码器脉冲数,M2为单位时间内定时器记得的高频时钟脉冲数,fo为高频脉冲频率
下面是我用过的部分代码:
************************************************************
*函数名称:void TIM2_IRQHandler(void)
*功 能:TIM2中断入口函数
*说 明:以1M的频率计数,1ms的时间定时(mt法测速)
*输入参数:无
*输出参数:无
*************************************************************/
void TIM2_IRQHandler(void)
{
static u8 start=0; //开始计时标志
static u8 Z=2; //转动一圈脉冲数
static u8 flag=0; //计数1S标志
static u16 M1=0; //单位时间内脉冲数
static u16 a=0; //暂存时间
static u16 count=0; //中断计数
static u16 pulse_counter = 0; //脉冲计数器
static double M2=0; //单位时间内高频脉冲数
static double f0=1000000; //频率1Mhz
if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) //更新中断1ms
{
if(start==1)//开始计时
{
if(count++==1000) //计数1000次,即1s
{
flag=1; //计数1S标志
count=0; //中断计数清零
}
if(flag==1) //计时1s到
{
M1=pulse_counter; //得到脉冲个数
a=pulse_counter; //得到脉冲个数
flag = 2; //置标志不然一直进来
}
if(flag==2)//有标志
{
if(a!=pulse_counter)//下一个脉冲开始
{
start=0; //开始标志清零
M2=1000000+1000*count; //得到总的时间1s+1ms*count
n=(f0*M1)*60/(Z*M2); //得到转速
count=0; //中断计数清零
pulse_counter=0; //脉冲计数器清零
}
}
}
}
if(TIM_GetITStatus(TIM2, TIM_IT_CC3) != RESET) //输入引脚捕获3发生捕获事件
{
pulse_counter++; //脉冲计数器+1
if(pulse_counter==1)//捕获到第一个脉冲
{
flag=0; //计数1S标志清零
start=1; //开始标志置1
}
}
-
TIM_ClearITPendingBit(TIM2, TIM_IT_CC3|TIM_IT_Update); //清除中断标志位
}
这个是我用IO引脚产生1KHz的频率,另外一个引脚捕获输入脉冲,进行M/T法计算的M/T法主要算法,得出的转速除以60就是频率 。虽然能够测出转速,但是误差较大。当然如果有大佬有更好精度更高的算法欢迎交流。
二也是我主要介绍的通过STM32F1单片机自带的定时器编码模式进行脉冲测量。
#include "encode.h"
#include "usart.h"
int Encoder_Timer_Overflow; //编码器溢出次数
u16 Previous_Count; //上一次CNT的值
void TIM4_Encoder_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; //GPIOB6为A相GPIOB7为B相
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //ËÙ¶È50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //³õʼ»¯PB6ºÍPB7
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period=1996;
TIM_TimeBaseStructure.TIM_Prescaler=0;
TIM_TimeBaseInit(TIM4,&TIM_TimeBaseStructure);
TIM_ICInitStructure.TIM_Channel=TIM_Channel_1;
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 =6;
TIM_ICInit(TIM4,&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=6; //滤波
TIM_ICInit(TIM4,&TIM_ICInitStructure);
TIM_EncoderInterfaceConfig(TIM4,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising );//编码器模式配置
NVIC_InitStructure.NVIC_IRQChannel=TIM4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01;
NVIC_InitStructure.NVIC_IRQChannelSubPriority =0x02;
NVIC_Init(&NVIC_InitStructure);
TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);
TIM_Cmd(TIM4,ENABLE);
}
void TIM4_IRQHandler(void)
{
if(TIM_GetITStatus(TIM4,TIM_IT_Update)==SET)
{
Encoder_Timer_Overflow++;
}
TIM_ClearITPendingBit(TIM4,TIM_IT_Update);
}
u32 Read_Encoder(void)
{
u32 Count; //一段时间的脉冲数
u16 Current_Count; //当前脉冲数
u16 Enc_Timer_Overflow_one;
Enc_Timer_Overflow_one=Encoder_Timer_Overflow;
Current_Count = TIM_GetCounter(TIM4); //得到脉冲数
Encoder_Timer_Overflow=0; //清0方便下次计算
Count = (u32)(Current_Count - Previous_Count + (Enc_Timer_Overflow_one) * (4*ENCODER_PPR)); //进行测速
Previous_Count = Current_Count;
return(Count);
}
同时定义了一个1S的定时器TIM5在他的溢出中断中计算转速
void TIM3_Int_Init(u16 arr,u16 psc)//¶¨Ê±Æ÷3½øÐж¨Ê±ÖжÏ
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler =psc;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE );
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM3, ENABLE);
}
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM5,TIM_IT_Update)==SET)
{
encode=Read_Encoder();
printf("编码器脉冲为%d\r\n",encode);
speed=encode/500.0f/4.0f;
printf("编码器速度为%f\r\n",speed);
}
TIM_ClearITPendingBit(TIM5,TIM_IT_Update);
}
最终通过串口把脉冲和速度打印到上位机上。