伺服电机堵转检测

一. 电流数据的分析

电机工作时的电流如下图:
伺服电机堵转检测_第1张图片

电机正常工作时,电机电流具有两个状态:正常旋转和堵转。
正常旋转时,电流在控制算法的作用下,一开始会有很快的上升,过程中电流受到控制算法的作用,没有平稳阶段。
堵转时,电机结束了控制算法,所以堵转时电流上升然后保持一段时间的平稳状态。
用电流曲线的斜率描述这一状态:开始斜率是正的,然后斜率变为0。所以可以计算电流曲线的斜率来识别电机是否处于堵转状态。

二. 检测原理

伺服电机堵转检测_第2张图片
上图中黄色曲线为matlab计算得出的电流曲线斜率,蓝色曲线为单片机计算得出的斜率。
实际上由于所使用的算法在长时间运行下存在过拟合,所以在实际中,在电机开始工作时对算法的参数做重新的初始化,导致单片机一开始的计算结果波动较大。
算法的基本流程:

1.检测斜率是否接近于0,如果接近于0说明电流此时处于平稳的状态。
2.检测此时斜率的均值,均值是此时刻及之前40个时刻的斜率值得均值。若此时刻均值大于0.3说明,在过去的40个时刻电流是上升状态。
3.若此刻斜率均值大于0.3,检测此时刻后300ms内斜率是否会小于-0.2。若存在这种情况说明电流又快速下降,手指并未处在堵转状态。
4.没有小于-0.2的情况,说明电机此时处于堵转状态。

三. 斜率计算实现

考虑到单片机的性能限制,使用了递推最小二乘方法来递推电流曲线的斜率,并使用队列来保存40个时刻的斜率数据用来递推均值,减少运算量。为了能尽快的使计算结果跟踪电流曲线的变化,在开始的40个计算周期内使用带遗忘因子的递推最小二乘,之后使用有限数据窗+遗忘因子的方式计算。数据窗的长度即参与计算的数据个数为40个。
算法原理可参考以下博客
https://blog.csdn.net/qq_33243369/article/details/102713303

四. 实际效果

可以在发生过流后70ms左右检测到过流。
伺服电机堵转检测_第3张图片

图中蓝色曲线是计算得出的电流斜率。黄色曲线为电流斜率的递推均值,绿色曲线代表有无检测出过流。
伺服电机堵转检测_第4张图片
在检测出过流后,将手指电机占空比设置为0.上图第二段曲线是手指正常打下的电流曲线,第一段曲线是手指打下后有堵转,可以看到堵转后70ms内电流回复了正常值。

五.代码实现

least_squares.h

#ifndef _LEAST_SQUARES_H
#define _LEAST_SQUARES_H



#include "queue.h"

typedef struct
{
    float   P[4];
	  float   L[2];
	  float   Q[4];
	  float   lam_p;
	  float   lamp;
      QUEUE   K0_q;
	  QUEUE   K1_q;
	  float   K[2];
      float   lam;
      QUEUE   cur;
	  float   last_mean_cur;
	 // unsigned int  i;
	  uint32_t phi;
	  float mean_k;
	  float k_sum;
	  float start_time;
	  float check_f;
	  uint8_t overcur;
	  float cur_sum;
	  float mean_cur;
} least_squares_paras_t;


void least_squares_init(void);
void least_squares_porcess(float cur);
#endif

least_squares.c

#include "least_squares.h"
#include "math.h"
least_squares_paras_t least_squares_paras;
extern robot_finger_module_t robot_finger_module;


//init paras
void least_squares_init(void)
{
	least_squares_paras.lam = 0.9;
	least_squares_paras.lam_p = 67.6550;
	least_squares_paras.lamp = 0.0148;
    least_squares_paras.phi = 1;
	least_squares_paras.k_sum=0;
	least_squares_paras.mean_k=0;
	least_squares_paras.P[0] = 1000000;
	least_squares_paras.P[1] = 0;
	least_squares_paras.P[2] = 0;
	least_squares_paras.P[3] = 1000000;
	least_squares_paras.K[0] = 0;
	least_squares_paras.K[1] = 0;
	least_squares_paras.check_f = 0;
	least_squares_paras.overcur=0;
	CreateQueue(&least_squares_paras.cur,40);
	CreateQueue(&least_squares_paras.K0_q,40);
	CreateQueue(&least_squares_paras.K1_q,40);
}



void least_squares_porcess(float cur)
{

	//time counter
  least_squares_paras.phi++;
	
	//queue is not full, use forgetting factor
	if(!FullQueue(&least_squares_paras.cur))
	{
		Enqueue(&least_squares_paras.cur,cur);
		least_squares_paras.cur_sum+=cur;
		least_squares_paras.mean_cur = least_squares_paras.cur_sum/least_squares_paras.cur.rear;
		
		//Extract the reused process value and calculate it separately
		float res1 = least_squares_paras.lam + least_squares_paras.phi*least_squares_paras.phi*least_squares_paras.P[0]
		           + least_squares_paras.phi*least_squares_paras.P[1]
	               + least_squares_paras.phi*least_squares_paras.P[2]
	               + least_squares_paras.P[3];
	
	  //calculate L
		least_squares_paras.L[0] = (least_squares_paras.P[0]*least_squares_paras.phi+least_squares_paras.P[1])/res1;
		least_squares_paras.L[1] = (least_squares_paras.P[2]*least_squares_paras.phi+least_squares_paras.P[3])/res1;

    //calculate P
		float P_t[4] = {least_squares_paras.P[0],least_squares_paras.P[1],least_squares_paras.P[2],least_squares_paras.P[3]};
		least_squares_paras.P[0] = (P_t[0]*(1-least_squares_paras.L[0]*least_squares_paras.phi)
															 - least_squares_paras.L[0]*P_t[2])/least_squares_paras.lam;

		least_squares_paras.P[1] = (P_t[1]*(1-least_squares_paras.L[0]*least_squares_paras.phi)
															 - least_squares_paras.L[0]*P_t[3])/least_squares_paras.lam;

		least_squares_paras.P[2] = (P_t[2]*(1-least_squares_paras.L[1])
															 - least_squares_paras.L[1]*P_t[0]*least_squares_paras.phi)/least_squares_paras.lam;

		least_squares_paras.P[3] = (P_t[3]*(1-least_squares_paras.L[1])
															 - least_squares_paras.L[1]*P_t[1]*least_squares_paras.phi)/least_squares_paras.lam;
		
		//calculate slope
		float K_o[2]={least_squares_paras.K[0] ,least_squares_paras.K[1]};
    least_squares_paras.K[0] = least_squares_paras.K[0] 
	                           + least_squares_paras.L[0]*(cur - least_squares_paras.phi*K_o[0]-K_o[1]);
	  least_squares_paras.K[1] = least_squares_paras.K[1] 
                            + least_squares_paras.L[1]*(cur - least_squares_paras.phi*K_o[0]-K_o[1]);
		
		Enqueue(&least_squares_paras.K0_q,least_squares_paras.K[0]);
		Enqueue(&least_squares_paras.K1_q,least_squares_paras.K[1]);
		least_squares_paras.k_sum+=least_squares_paras.K[0];
		least_squares_paras.mean_k = least_squares_paras.k_sum/least_squares_paras.K0_q.rear;
		
		
	}
	else //queue is full,use Limited data window and forgetting factor
	{
		//current before 40 cycles
		float cur_p =0;
		
		//the present time
    float t = least_squares_paras.phi;
	  
		//time before 40 cycles
		float t_p = least_squares_paras.phi-40;
		
		//Extract the reused process value and calculate it separately
		Dequeue(&least_squares_paras.cur,&cur_p);
		float p_t[10];
		uint8_t p_cnt = 0;
		for(int i=0;i<4;i++)
		{
			for(int j=i;j<4;j++)
			{
				p_t[p_cnt]=least_squares_paras.P[i]*least_squares_paras.P[j];
				p_cnt++;
			}
		}
		
		float res = least_squares_paras.lam+least_squares_paras.P[3]
								+t*(least_squares_paras.P[2]+t*least_squares_paras.P[0]+least_squares_paras.P[1]);


    //calculate Q
		least_squares_paras.Q[0] = (least_squares_paras.P[0]-(p_t[5]+t*p_t[2]+t*p_t[1]+t*t*p_t[0])/res)/least_squares_paras.lam;
		least_squares_paras.Q[1] = (least_squares_paras.P[1]-(p_t[6]+t*p_t[3]+t*p_t[4]+t*t*p_t[1])/res)/least_squares_paras.lam;
		least_squares_paras.Q[2] = (least_squares_paras.P[2]-(p_t[8]+t*p_t[7]+t*p_t[3]+t*t*p_t[2])/res)/least_squares_paras.lam;
		least_squares_paras.Q[3] = (least_squares_paras.P[3]-(p_t[9]+t*p_t[8]+t*p_t[6]+t*t*p_t[5])/res)/least_squares_paras.lam;

		
		//Extract the reused process value and calculate it separately
		float q_t[10];
		uint8_t q_cnt = 0;
		for(int i=0;i<4;i++)
		{
			for(int j=i;j<4;j++)
			{
				q_t[q_cnt]=least_squares_paras.Q[i]*least_squares_paras.Q[j];
				q_cnt++;
			}
		}

		
		res = least_squares_paras.Q[3]-least_squares_paras.lam_p+t_p*(least_squares_paras.Q[2]+t_p*least_squares_paras.Q[0]+least_squares_paras.Q[1]);

		//calculate Q
		least_squares_paras.P[0] = least_squares_paras.Q[0]-(q_t[5]+t_p*q_t[2]+t_p*q_t[1]+t_p*t_p*q_t[0])/res;
		least_squares_paras.P[1] = least_squares_paras.Q[1]-(q_t[6]+t_p*q_t[3]+t_p*q_t[4]+t_p*t_p*q_t[1])/res;
		least_squares_paras.P[2] = least_squares_paras.Q[2]-(q_t[8]+t_p*q_t[7]+t_p*q_t[3]+t_p*t_p*q_t[2])/res;
		least_squares_paras.P[3] = least_squares_paras.Q[3]-(q_t[9]+t_p*q_t[8]+t_p*q_t[6]+t_p*t_p*q_t[5])/res;
		

		//calculate slope
		float K_o[2]={least_squares_paras.K[0] ,least_squares_paras.K[1]};
		float res1 = K_o[1]-cur+t*K_o[0];
		float res2 = K_o[1]-cur_p+t_p*K_o[0];
		least_squares_paras.K[0] = K_o[0]-(least_squares_paras.P[1]+t*least_squares_paras.P[0])*res1
		         +(least_squares_paras.lamp*least_squares_paras.P[1]+least_squares_paras.lamp*least_squares_paras.P[0]*t_p)*res2;
		
		least_squares_paras.K[1] = K_o[1]-(least_squares_paras.P[3]+t*least_squares_paras.P[2])*res1
		         +(least_squares_paras.lamp*least_squares_paras.P[3]+least_squares_paras.lamp*least_squares_paras.P[2]*t_p)*res2;
		
		float K_p[2];
		Dequeue(&least_squares_paras.K0_q,&K_p[0]);
		Dequeue(&least_squares_paras.K1_q,&K_p[1]);
		least_squares_paras.k_sum=least_squares_paras.k_sum-K_p[0]+least_squares_paras.K[0];
		least_squares_paras.mean_k = least_squares_paras.k_sum/least_squares_paras.K0_q.maxsize;
		Enqueue(&least_squares_paras.K0_q,least_squares_paras.K[0]);
		Enqueue(&least_squares_paras.K1_q,least_squares_paras.K[1]);
	  Enqueue(&least_squares_paras.cur,cur);
	}
	
	

	
	if(least_squares_paras.check_f)
	{
		uint32_t time = HAL_GetTick();
		
		if(time>least_squares_paras.start_time+150&&time<least_squares_paras.start_time+450)
		{
			if(least_squares_paras.K[0]>0&&least_squares_paras.K[0]<0.2&&least_squares_paras.mean_k>0.35)
			{
				least_squares_paras.overcur=1;
			}
			if(least_squares_paras.K[0]<-0.2)
			{
				least_squares_paras.overcur=0;
			}
		}
		else if(time>least_squares_paras.start_time+450&&time<least_squares_paras.start_time+800)
		{
			//least_squares_paras.check_f=0;
			ClearQuene(&least_squares_paras.cur);
			ClearQuene(&least_squares_paras.K0_q);
			ClearQuene(&least_squares_paras.K1_q);
			least_squares_paras.phi = 1;
			least_squares_paras.k_sum=0;
			least_squares_paras.mean_k=0;
			least_squares_paras.P[0] = 100;
			least_squares_paras.P[1] = 0;
			least_squares_paras.P[2] = 0;
			least_squares_paras.P[3] = 100;
			least_squares_paras.K[0] = 0;
			least_squares_paras.K[1] = 0;
			
			
			least_squares_paras.mean_k = 0;
			
			if(least_squares_paras.overcur)
			{
				//检测出过流,执行相应的保护操作				
			}
			least_squares_paras.overcur=0;
		}
		else if(time>least_squares_paras.start_time+800)
		{
			least_squares_paras.check_f=0;
		}
	}
		
}

queue.h

#ifndef __QUEUE_H_
#define __QUEUE_H_
typedef struct queue 
{
	float *pBase;
	int front;    
	int rear;   
	int maxsize; 
}QUEUE,*PQUEUE;
 
void ClearQuene(PQUEUE Q);
void CreateQueue(PQUEUE Q,int maxsize);
void TraverseQueue(PQUEUE Q);
int FullQueue(PQUEUE Q);
int EmptyQueue(PQUEUE Q);
int Enqueue(PQUEUE Q, float val);
int Dequeue(PQUEUE Q, float *val);
#endif

queue.c

#include
#include"queue.h"
/***********************************************
Function: Create a empty stack;
************************************************/
void CreateQueue(PQUEUE Q,int maxsize)
{
	Q->pBase=(float *)malloc(sizeof(float)*maxsize);
	if(NULL==Q->pBase)
	{
		return;
	}
	Q->front=0;         
	Q->rear=0;
	Q->maxsize=maxsize;
}

void ClearQuene(PQUEUE Q)
{
	Q->front=0;         
	Q->rear=0;
}
/***********************************************
Function: Print the stack element;
************************************************/
void TraverseQueue(PQUEUE Q)
{
	int i=Q->front;

	while(i%Q->maxsize!=Q->rear)
	{
		//printf("%d ",Q->pBase[i]);
		i++;
	}
	//printf("\n");
}
int FullQueue(PQUEUE Q)
{
	if(Q->front==(Q->rear+1)%Q->maxsize)    
		return 1;
	else
		return 0;
}
int EmptyQueue(PQUEUE Q)
{
	if(Q->front==Q->rear)    
		return 1;
	else
		return 0;
}
int Enqueue(PQUEUE Q, float val)
{
	if(FullQueue(Q))
		return 0;
	else
	{
		Q->pBase[Q->rear]=val;
		Q->rear=(Q->rear+1)%Q->maxsize;
		return 1;
	}
}


int Dequeue(PQUEUE Q, float *val)
{
	if(EmptyQueue(Q))
	{
		return 0;
	}
	else
	{
		*val=Q->pBase[Q->front];
		Q->front=(Q->front+1)%Q->maxsize;
		return 1;
	}
}

你可能感兴趣的:(控制算法,最小二乘,单片机,驱动开发)