伺服电机堵转检测

一. 电流数据的分析

电机工作时的电流如下图:
在这里插入图片描述

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

二. 检测原理

在这里插入图片描述
上图中黄色曲线为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左右检测到过流。
在这里插入图片描述

图中蓝色曲线是计算得出的电流斜率。黄色曲线为电流斜率的递推均值,绿色曲线代表有无检测出过流。
在这里插入图片描述
在检测出过流后,将手指电机占空比设置为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<stdlib.h>
#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;
	}
}
### 回答1: 为了实现对STM32电机的软件检测堵转功能,可以通过以下步骤进行。 首先,需要了解电机堵转的原理。当电机受阻或运行受限时,电机转子的转速会显著降低甚至停止转动,这被称为堵转堵转会导致电机额外的负载和高电流,可能造成设备损坏或安全风险。 为了检测电机堵转,可以通过监测电机的转速变化来判断其是否遇到堵转。我们可以使用STM32的定时器功能来精确地测量电机的转速。在正常运行时,电机转速会稳定在一个范围内,所以当电机堵转时,转速会显著降低。 具体实现时,首先需要初始化STM32的定时器功能,并设置一个合适的计数频率。然后,可以使用计时器的输入捕获功能来测量电机转子的位置,并计算转速。在每个采样周期,可以通过获取两个连续采样点的时间差来计算电机的转速变化。 接下来,需要设定一个合适的堵转阈值。通过实验或根据电机的技术参数,我们可以确定电机堵转时的预期转速范围。当转速低于设定的堵转阈值时,即可确认电机遇到堵转。此时可以触发相应的保护措施,如停止电机运行或警告操作员等。 最后,需要将上述逻辑嵌入到STM32的软件驱动程序中,并在电机运行过程中进行实时检测。可以使用中断或轮询方式来触发检测代码的执行。通过不断地采样和计算转速,可以实时地监测电机的运行状态并及时发现堵转现象。 需要注意的是,软件检测堵转只是一种辅助手段,不能替代机械或其他物理保护装置。在实际应用中,应综合考虑电机的安全性和可靠性需求,结合硬件和软件措施来设计堵转保护方案。 ### 回答2: STM32微控制器可以通过编写软件来检测电机堵转堵转是指电机在工作时由于某种原因无法正常旋转的情况。 首先,我们可以使用与电机相连的编码器或霍尔传感器来监测电机的转速。通过定期读取编码器或传感器输出的脉冲数,可以计算出当前转速。如果电机在一段时间内的转速为零,可能意味着电机已经堵转。 其次,可以使用电机控制器的过电流保护功能来检测堵转。当电机尝试旋转但由于受阻无法转动时,电机会消耗更多的电流。通过监测电机的电流值,当电流超过设定的阈值时,可以判断电机是否堵转。 另外,还可以通过检测电机驱动器输出来判断电机是否堵转。通常电机驱动器会以PWM(脉宽调制)信号的形式控制电机的转速和方向。如果电机的转速与驱动器输出的信号不匹配,或者驱动器输出的信号存在异常,可能意味着电机已经堵转。 在软件实现方面,我们可以使用STM32微控制器的定时器和外部中断功能,来实现对编码器或传感器的读取。通过编写相应的中断服务程序,可以定期获取转速信息,并根据一定的算法来判断电机是否堵转。 此外,还需要编写程序来读取电机的电流值和驱动器输出的信号,并对其进行适当的处理和判断。 需要注意的是,不同的电机和应用场景可能需要不同的检测方法和算法。因此,在实际应用中,需要根据具体情况来选择适合的检测方案,并进行相关的参数调试和优化。 ### 回答3: 在STM32中,可以通过软件检测电机堵转电机堵转是指电机无法正常转动或受阻止的情况。为了检测电机是否堵转,我们可以利用STM32的GPIO输入功能和定时器功能。 首先,我们需要将电机的某个输出信号连接到STM32的GPIO输入引脚。这个引脚可以设置为外部中断模式,当电机停转或受阻时,该引脚的信号将变化。当引脚信号变化时,可以触发STM32的中断服务程序。 接下来,我们可以利用STM32的定时器功能来进行计时。在电机堵转情况下,定时器中断很快地触发,而在电机正常转动时,定时器中断会按照设定的时间间隔触发。通过比较中断触发的时间间隔,我们可以判断电机是否堵转。 具体的步骤如下:首先,配置GPIO引脚为外部中断模式,使其能够检测电机输出信号的变化。然后,配置定时器,设置定时器中断的时间间隔。当电机堵转时,GPIO输入引脚的电平变化将触发外部中断,调用中断服务程序进行处理。中断服务程序中通过读取定时器的当前计数值,可以获取堵转期间的时间间隔。 最后,通过比较堵转期间的时间间隔跟设定的阈值,可以判断电机是否堵转。如果时间间隔超过阈值,则可以判定电机已经堵转,可以触发相应的处理措施,比如报警、保护等。 这样,通过软件检测电机堵转,我们可以及时发现电机的不正常情况,保护电机和系统的正常运行。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值