编码器电机转动可以产生脉冲信号,根据脉冲信号,可以得出轮胎的转动速度、轮胎的位移,电机正反转等。
我的编码器电机是130TT减速电机,电机轴转一圈可以产生13个脉冲信号输出,电机减速比为1:120,所以减速电机的输出轴旋转一圈,实际可以产生的脉冲为13*120=1560个。在通过STM32F4编码器接口的4倍频就是6240个脉冲也即是轮子转一圈会有6240个脉冲。(4倍频就是一个脉冲可以被检测到4次,增大脉冲的检测精度)
第一种测速方案是计算一定脉冲下所用时间来得出电机速度
第二种测速方案是是一定时间下所产生的脉冲数量来计算。这里选择第二种。
使用一个定时器TIM4的编码器模式接收电机编码器的脉冲,另一个定时器TIM3负责在一定时间的读取编码器脉冲的值。这样,就可以得出一定时间下的电机的脉冲数了。我这里取100ms取值一次。
那么知道了一定时间下的电机的脉冲数怎么可以测到电机的速度呢?这里我们已经知道了减速电机的输出轴旋转一圈会产生1560个脉冲,四倍频就是6240个,所以可以根据比例来求出轮子的位移(事先用尺子测出轮子一周的长度,比如说100ms有2000个脉冲,那么2000除6240得到比例,再用这个比例乘以轮子的长度求出位移,然后这是在100ms下走的路程,最后用位移除以时间100ms就可以得到电机的速度啦)
TIM4(脉冲计数),TIM3(时间计数)初始化
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "usart.h"
u32 mcs;
float cnt,cnts,speed;
int g;
void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); ///使能TIM3时钟
TIM_TimeBaseInitStructure.TIM_Period = arr; //自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //定时器分频
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);//初始化TIM3
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //允许定时器3更新中断
TIM_Cmd(TIM3,ENABLE); //使能定时器3
NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn; //定时器3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x00; //抢占优先级1
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void Encoder_Init_TIM4(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//开启GPIOB时钟
GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_TIM4);//PB6引脚复用
GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_TIM4);//PB7引脚服用
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; //GPIOB6,GPIOB7
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
//GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//定时器设置-------------------------------------------------------------
TIM_TimeBaseInitStructure.TIM_Period = 60000; //重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler=0x0; //预分频
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; //时钟分割
TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStructure);//初始化TIM3
//编码器模式设置--------------------------------------------------------------
TIM_EncoderInterfaceConfig(TIM4,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//计数模式3
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_ICFilter = 10;//滤波器值
TIM_ICInit(TIM4, &TIM_ICInitStructure);
//溢出中断设置--------------------------------------------------------------
TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE); //允许TIM3溢出中断
NVIC_InitStructure.NVIC_IRQChannel=TIM4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x01;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
//Reset counter-----------------------------------------------
TIM_SetCounter(TIM4,0); //TIM3->CNT=0
TIM_Cmd(TIM4, ENABLE);
}
void TIM4_IRQHandler(void)
{
if(TIM_GetITStatus(TIM4,TIM_IT_Update)==SET)//溢出中断
{
//这里为空,不需要处理
}
TIM_ClearITPendingBit(TIM4,TIM_IT_Update); //清除中断标志位
}
//定时器3中断服务函数
void TIM3_IRQHandler(void)
{
cnt=TIM_GetCounter(TIM4);
TIM_SetCounter(TIM4,0);
TIM_SetCounter(TIM3,0);
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
{
LED1=!LED1;//DS1翻转
g=1;
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
TIM_ClearITPendingBit(TIM4,TIM_IT_Update); //清除中断标志位
}
下面的是主函数(串口检测数据)
int main(void)
{
// u8 t;
// u8 len;
// u16 times=0;
Encoder_Init_TIM4();
TIM3_Int_Init(1000,8400);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
delay_init(168); //延时初始化
uart_init(115200); //串口初始化波特率为115200
LED_Init(); //初始化与LED连接的硬件接口
En_Init();
pwm_Init(7200-1,0);
while(1)
{
LED0=!LED0;
if(g==1)
{
if(cnt<30000)
cnt=cnt/6240;
else if(cnt>30000)
{
cnt=60000-cnt;
cnt=cnt/6420;
}
cnt=0.2*cnt;
speed=cnt/0.1;
printf("%f\n", cnt);
printf(" ");
printf("%f\r\n",speed);
g=0;
}
IN1=0;
IN2=1;
IN3=1;
IN4=0;
TIM_SetCompare3(TIM5,5000);
TIM_SetCompare4(TIM5,5000);
// printf("%d\r\n", mcs);
}
}
这里有个要注意的地方是定时器中断函数中尽可能短,不要太多代码,而且定时器中断函数中不要用printf函数,可能会造成卡死。因为printf本身就用到中断函数。
还有一点是电机正转的话编码器模式是从0开始计数,如果反转的话从设置的重装载开始递减计数,我这里就是从60000开始,如何有2000个脉冲,则计数器CNT的值为58000。为了判断出正反转,我的解决方法是CNT在1至30000为正转,30000~60000为反转(因为我这个项目中100ms内正转不会有30000个脉冲产生)。缺点是都是牺牲数据范围来读取。还有一种办法是读取DIR来判断 但有小概率误判现象。用DIR在极端情况下会出现误判,比如一直处于正反转。
TIM4中的中断函数要写出来,不然可能会卡死。当然,也可以禁止TIM4的中断,这样就不需要写中断函数了。