1.永磁同步电机PMSM/无刷直流电机BLDC中的霍尔位置传感器

永磁同步电机的转子电气位置是贯穿整个电机控制工程的核心要点,无角度无控制!
其实该内容已在1年半前就已经在工程上实现并做了简单的定量分析,由于谋生,生活节凑紧凑,只能丢在csdn草稿箱,拖了一年才发布。本科毕业后要做更多生活中谋生的工作,不怎么接触电机了,应该会有很长时间不再更新;出于个人水平实在有限,且有些问题时间久了忘了可能回答不上来,还希望见谅。感谢以前支持我的粉丝,希望我大学做的玩具能够帮到电机控制上有需求的朋友。
由于时间精力问题, 文中描述的两种方法涉及到的相位滞后和相关补偿问题,就不展开论述。本文默认读者的基础知识较好哈(MCU小白慎入)!代码并不完整,有一些语句靠英文单词完全能理解意思,但电角度这方面知识库已在文章中毫无保留地写了出来,对于不愿意自己动脑思考和实践的,张口就是要现成代码的,一律谢绝。 为了节省下笔的时间,直接截图笔者自己论文草稿的页面(节省码字时间)!
插值法与锁相环法相配合,低速进行增磁,4对极电机空载达到80RPM完全没有问题。
所谓增磁,是为了确保信噪比,目标速度过低时产生d轴电流使得定子d轴磁场与转子d轴磁场方向相同,两矢量叠加,故称增磁。
主要以120°电角度放置方式为例,两种放置方式除了霍尔真值不同,其它的,理想情况下完全一样。
在这里插入图片描述
将文中的逻辑换成代码的形式:

/** 
 * @brief 120°或60°两种类型霍尔传感器的真值获取逻辑
 * @return 0XUVW
 */
uint8_t get_hallValue(void)
{
    uint8_t state=0;
    /* 读取霍尔位置传感器 U 的状态 */
    if(HAL_GPIO_ReadPin(TIM_HALL_CHANNEL1_PORT, TIM_HALL_CHANNEL1_Pin) != GPIO_PIN_RESET)
    {
        state |= 0x01U << 2;
    }

    /* 读取霍尔位置传感器 V 的状态 */
    if(HAL_GPIO_ReadPin(TIM_HALL_CHANNEL2_PORT, TIM_HALL_CHANNEL2_Pin) != GPIO_PIN_RESET)
    {
        state |= 0x01U << 1;
    }

    /* 读取霍尔位置传感器 W 的状态 */
    if(HAL_GPIO_ReadPin(TIM_HALL_CHANNEL3_PORT, TIM_HALL_CHANNEL3_Pin) != GPIO_PIN_RESET)
    {
        state |= 0x01U << 0;
    }

    return state;
}

120°和60°的真值可以相互转化,存在这样的算法,这里笔者不赘述,默认读者掌握。
在这里插入图片描述

在这里插入图片描述

2.FOC必须掌握的电角度调试方法

图4-2:
在这里插入图片描述

图4-3:
在这里插入图片描述

2.1 V/F爬坡启动

上图为4-2与4-3
在这里插入图片描述

2.2 I/F爬坡启动

I/F是在V/F基础上做了电流控制。
在这里插入图片描述

n0是为了确保达到2s以上爬坡时间,在foc高频任务中注册的标志位变量。
在这里插入图片描述

在这里插入图片描述

3.插值拟合法

一个霍尔真值对应一个60°的区域,正转或反转到达同一真值时,其对应的转子电气角度完全不一样。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Δθ可以在这里理解:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Δθ的处理在中频任务和高频任务中都有负责的部分。主旨是根据执行频率的差异,均等补偿进去!下文将详细讲述,如果读者有疑问,还请静下心来。 这种补偿是有限度的,不一定要限制在30°,需要根据实际调试情况自行设限。

在这里插入图片描述

在这里插入图片描述
重点(指γ角的试凑法步骤):
为了便于作图展示过程,图中f_FOC=4f_Speed其实是不合理的,但根据FOC本身特性,实际程序必须满足f_FOC>=8f_Speed。
在这里插入图片描述
在这里插入图片描述

低频任务和中频任务见第6部分。

/**
 * @brief   更新电机的电角速度和机械角速度,将整型速度值/每foc执行周期转化为rad/s
 * @param   dpp 整型,指的是整型速度值/每foc执行周期
 * @param   hall 霍尔传感器对象
 * @return
 */
float32_t  hall_get_Speed(HALL_Struct* hall)
{
    /*dpp为整型速度值/每foc执行周期 ,需要后期转化为rad/s*/
    /*所以高频任务的执行周期和65535和2PI需要结合实际弄出个乘积因子完成单位转化过程*/
    static float32_t K=(float)(N2_PI/65536/(FOC_PERIOD/1000000.0f));
	
    hall->omega_inter=((float)(K*hall1.dpp));
	
    return hall->omega_inter;
}
/**
 * @brief   插值法电角度估算函数(用于FOC高频中断任务中)
 * @param   hall 霍尔传感器指针
 * @funtion 实现了插值法捕获速度值;该方式使用0~65536表示0到2pi
 */
float hall_positionEst(HALL_Struct *hall)
{
      static float32_t theta_k=N2_PI/65536;

      hall->refer_theta+=hall->dpp;//<refer_theta就是文中的θ_ref(在代码中仅有整型形式)
      /***********************电角度从0到2PI往复 **************************/
      if(hall->refer_theta>65536L)
      {
          hall->refer_theta-=65536L;
      }
      else if(hall->refer_theta<0)
      {
          hall->refer_theta+=65536L;
      }
      hall->theta+=(hall->dpp+hall->comp_dpp)//<就是文中的θ_use的插值法整型形式
      /***********************电角度从0到2PI往复 **************************/
      if(hall->theta>65536L)
     {
         hall->theta-=65536L;
     }
     else if(hall->theta<0)
     {
         hall->theta+=65536L;
     }
		 
	 hall->theta_inter=(float32_t)(hall->theta*theta_k);//<theta_inter插值法估算的电角度,就是文中的θ_use浮点形式
     return hall->theta_inter;
}

4.锁相环跟踪法

自动控制原理的知识不管是用于考研,还是工作就业,都非常有用,有志向的读者一定要好好学自动控制原理与现代控制理论。
本文描述的只是非常粗糙的锁相环方法,是不完美的,还有更多的优化点,当然这需要更深厚的理论知识!
锁相环既是控制器也是跟踪器,学术上使用的锁相环法还考虑高次谐波的去除以及误差的补偿,但笔者没有那么做,还没到那个水平。即使它很粗糙,其效果远大于插值法,若不是伺服用途,在工程上绰绰有余。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
将当前的电角速度ωe作为ωc带入公式,就能整定锁相环PLL的参数。
PLL的参数自整定中不能够使用自己的估算电角速度,电角速度一定要使用插值法的估计速度。
FOC环路中可以使用锁相环的估算电角速度。

/**
 * @brief            锁相环法PLL核心程序(Kp与Ki根据电角速度自适应)
 * @param    hall    霍尔位置传感器结构体对象
 * @function         浮点运算直接求弧度,实现了锁相环法;
					 
 * @warnings         !!!注意:速度用插值法速度,PLL估算出来的速度不能引入环路
 */
float hall_pll_filter(HALL_Struct *hall)
{
	static float32_t theta_k=N2_PI/65536;//<浮点数2PI用整型65536表示
	static float32_t error=0;
	
	error=hall->hall_theta*theta_k-hall->pll_theta;
	
	/***********************电角度从0到2PI往复,误差相应要处理 **************************/
	/***********************至于为什么这样处理,就交给读者思考啦! **********************/
	if(error>=PI)
	{
		error-=N2_PI;
	}
	if(error<=-PI)
	{
		error+=N2_PI;
	}
	/***********************电角度从0到2PI往复,误差响应要处理 **************************/
	
	hall->pll_omega+=(hall->pll_ki*error);
	/*************************锁相环法电角速度的限幅 ****************************/
	/*pll_omega结构体成员是弧度制(笔者整型和弧度制混用了,这方面代码没做到统一,是无法否认的缺点)*/
	/*笔者的实验电机4对极,额定转速3000RPM*/
	/*2320的选取背景是:把3000RPM(额定转速)转换为rad/s单位,并乘以极对数*/
	/*比如极对数4乘以3000RPM约等于1230rad/s*/
	/*有时候用到弱磁可以突破额定转速到5000RPM,所以给定一个较大的值,我这里直接1230乘以2倍左右*/
	if(hall->pll_omega>2320)
	{
		hall->pll_omega=2320;
	}
	else if(hall->pll_omega<-2320)
	{
		hall->pll_omega=-2320;
	}
	/*************************锁相环法电角速度的限幅 ****************************/
	hall->pll_theta+=(hall->pll_omega+hall->pll_kp*error)*hall->pll_Te;
	
	/*************************电角度从0到2PI往复 ****************************/
	if(hall->pll_theta>N2_PI)
	{
		hall->pll_theta-=N2_PI;
	}
	if(hall->pll_theta<0)
	{
		hall->pll_theta+=N2_PI;
	}
	/*************************电角度从0到2PI往复 ****************************/
	
	/*不能自己参照自己*/
	/*改变Kp与Ki时,要使用插值法的速度*/
	/*插值法的速度估计非常重要,重要程度最高*/
	/*插值法的速度处理直接影响性能,包括启动,快速正反转,堵转恢复*/
	/*omega_inter:插值法电角速度*/
	///<绝对不能用自己估算出来的速度,否则就是自己观测自己,控制系统不稳定
	hall->pll_ki=0.236f*hall->omega_inter*hall->omega_inter;//<0.236f约等于根号5-2
	arm_sqrt_f32(hall->pll_ki,&hall->pll_kp);
	hall->pll_kp*=1.414f;      //<阻尼比选0.707       2*0.707f=1.414f
	hall->pll_ki*=hall->pll_Te;//<pll_Te是PLL法的执行时间,单位为s。放在FOC中断中其值就是1/f_FOC
	
	return hall->pll_theta;
}

在这里插入图片描述

5.MCU配置与任务流程

主控是STM32F407VET6,时钟使用外部时钟8M,在时钟树中配置为标准的主频168M,这里默认读者有一定的MCU基础,不赘述。

一些宏定义

/****************************请根据需要修改****************************/
#define SHUNT_RESISTOR           (0.02f)        ///<采样电阻阻值(单位:Ω)
#define U_DC                     (24.0f)        ///<电机的直流动力供电电压
#define P                        (4UL)          ///<电机极对数
#define Rs                       (0.68f)        ///<定子电阻(单位:ohms@25℃)
#define Ls                       (0.000426f)    ///<定子电感(单位:H@1khz)
#define NORMAL_CURRENT           (4f)           ///<正常运行电流(单位:A)
#define NORMAL_SPEED             (3000UL)       ///<额定转速(单位:rpm)
#define FLUX_VALUE               (0.0083817f) 
#define B_EMF                    (0.0580703f)   ///<反电动势常数(单位:V.s/rad)
#define J                        (0.00000173f)  ///<转子惯量(单位:kg*m2)1kg*m^2=1N*m^2/(m/s^2)=1N*m*s^2
#define MOS_DELAY                (80UL)         ///<开关管最大开关时延(单位:ns)
/****************************TIM外设参数******************************/

#define TS					     (16799.0f)     	///<PWM周期浮点型F
#define TS_UL				     (16799UL)      	///<PWM周期定点型Q
#define PERIOD                   (8399.0f)      ///<输出定时器的重装载值F
#define PERIOD_UL                (8399UL)       ///<输出定时器的重装载值Q
//#define Svpwm_Km_Backw   	     (0.0721688f) 
#define PWM_HalfPerMax   		 (4199)         ///<输出定时器的重装载值中值Q
/**
 * @brief 注意!!!!  PWM的输出模式,这将直接影响SVPWM的输出方式
 * @param PWM_MODE1     PWM模式一(不推荐)
 * @param PWM_MODE2     PWM模式二
 */
#define PWM_MODE2                              ///<此处采用PWM模式2
#define DEAD_TIME                (200UL)       ///<互补PWM的死区时间
#define FOC_PERIOD               (100UL)        ///<FOC核心部分的执行周期 (单位:us)
#define SPEED_PERIOD             (1000UL)      ///<速度更新周期 (单位:us)
/****************************SVPWM相关定义*********************************/
#define N0_PI                    (0.00000000000000f)  ///<0°弧度制
#define PI_3                     (1.04719755119660f)  ///<60°弧度制
#define N2_PI_3                  (2.09439510239320f)  ///<120°弧度制
#define N4_PI_3                  (4.18879020478640f)  ///<240°弧度制
#define N5_PI_3                  (5.23598775598299f)  ///<300°弧度制
#define N2_PI                    (6.28318530717959f)  ///<360°弧度制  

#define SQRT_3                   (1.73205080756888f) ///<根号3
#define N1_SQRT_3                (0.57735026918963f) ///<1除以根号3
#define N2_SQRT_3                (1.15470053837925f) ///<2除以根号3
#define SQRT_3_2                 (0.86602540f)       ///<根号3除以2

                                                       
#define ROUND_TO_UINT8(x)        ((uint8_t)(x+0.5f))   ///<将浮点数x(<127)四舍五入为uint8_t
#define ROUND_TO_UINT16(x)       ((uint16_t)(x+0.5f))  ///<将浮点数x(<65535)四舍五入为uint16_t
#define ROUND_TO_UINT32(x)       ((uint32_t)(x+0.5f))  ///<将浮点数x四舍五入为uint32_t       


#define ROUND_TO_INT8(x)         ((int8_t)(x+0.5f))    ///<将浮点数x(<127)四舍五入为int8_t
#define ROUND_TO_INT16(x)        ((int16_t)(x+0.5f))   ///<将浮点数x(<65535)四舍五入为int16_t
#define ROUND_TO_INT32(x)        ((int32_t)(x+0.5f))   ///<将浮点数x四舍五入为int32_t

5.0 霍尔位置传感器结构体对象的定义(参考)

结构体成员的具体使用,需结合第4部分已挂代码与第6部分的例程,配合理论公式综合分析,这并非一时之功,还请读者花时间,自己动脑,仔细思考。

    /****************************霍尔传感器相关结构体******************* **************/
    #define WBuffer_MAX_SIZE			(6UL)
	typedef struct {
		int8_t          last_direction;        //<上一次的电机转向
		int8_t          direction;             //<当前的电机转向
    	uint8_t         buffer_index;          //<记录当前buffer缓存的下标
    	uint8_t         last_state;            //<上一次的霍尔真值
    	uint8_t         state;                 //<当前的霍尔真值
    
	  	uint8_t         startup;                   //电机启动标志位
	  	uint8_t         directionChange;           //方向改变标志
	  	uint8_t         locked;                    //霍尔堵转标志位
      	uint8_t         RatioDec;                  //预分频变小标志位
      	uint8_t         RatioInc;                  //预分频变大标志位
	
	  	uint16_t        overcount;                 //<TIM霍尔捕获模式下因电机转速过慢CNT计数溢出的次数
	
      	int32_t         buffer[WBuffer_MAX_SIZE];  //<WBuffer_MAX_SIZE=6
      	int32_t         dpp_sum;                   //<用于把上面缓存6个成员累加,过程是滑动均值,请结合第6部分代码分析
		
      	int32_t         prescaler;              //<当前TIM的实际预分频
      	int32_t         cap;					//<当前TIM的CCRx捕获值
      	int32_t         f_hall;            		//<霍尔中断的执行频率(实时动态计算)
      	int32_t         f_speed;           		//<中频中断任务执行频率,也是速度值更新的频率
      	int32_t         f_foc;             		//<高频中断任务的频率
 		
 		/**程序中用定点数65535表示2PI,这里是相关的操作变量,可结合实际代码理解(dpp是定点数角度的意思)**/
 		/****************它们的单位:整型速度值/每foc执行周期 ,需要后期转化为rad/s****************/
      	int32_t         dpp_2;
      	int32_t         dpp_1;
      	int32_t         dpp;
      	int32_t         avg_dpp;
      	int32_t         comp_dpp;               //<就是Δθ/n的值
      	int32_t         a_dpp;
		
		/**********************************结束********************************************/

		/****************它们仍是整型定点数65535表示2PI****************/
	  	int32_t         hall_theta;             //<强制校准时保存角度的变量(插值法)
      	int32_t         offset_theta;           //<就是文中介绍的γ角
      	int32_t         refer_theta;            //<作为基准参考被追踪的指标角度变量(插值法)
      	int32_t         delta_theta;            //<就是文中的Δθ(插值法)
      	int32_t         theta;                  //<去追踪基准指标角度的角度变量,也就是公式的θ_use(插值法整型版)
		
		
		/*以下全为浮点型*/
		float           theta_inter;             //<插值法估算的角度,也就是公式的θ_use(插值法浮点型版)
		float		    omega_inter;             //<插值法估算的速度
		
		float			pll_kp;
		float			pll_ki;
		float           pll_theta;               //<锁相环法估算的角度,也就是公式的θ_use(PLL法浮点型版)
		float			pll_omega;               //<锁相环法估算的速度
		float			pll_Te;                  //<锁相环执行周期
}HALL_Struct;

5.1 定时器的霍尔捕获模式(低频任务硬件配置)

在这里插入图片描述
在这里插入图片描述
图中中断优先级有误,应该是(1,0)

5.2 1K更新中断频率的定时器(中频任务硬件配置)

在这里插入图片描述
在这里插入图片描述
图中中断优先级有误,应该是(1,1)

5.3 高级TIM通道4触发ADC注入通道中断采样(高频任务硬件配置)

在这里插入图片描述
而这个信号来源于高级定时器的配置:
在这里插入图片描述
在这里插入图片描述
图中中断优先级无误,就是(0,0),高频任务优先级最高

5.4 中断任务优先级

高频任务>低频任务和中频任务,低频任务和中频任务主优先级相同,但次优先级低频任务>中频任务,两者不能互相打断,因为低频任务里有霍尔真值校准,重要性比中频任务高。

6.例程

6.1 低频任务(霍尔跳变捕获中断)

/**
 * @brief            霍尔捕获(中断)低频任务(频率不定,所以在本函数还对霍尔中断频率做了计算)
 * @param    htim    霍尔中断捕获所使用的定时器指针
 * @function         实现了插值法捕获速度值;该方式使用-65536~65536表示-2pi到2pi
 *	                 方案采用扇区强制校准+插值补偿	
 *                   本部分函数实现了扇区角度强制校准+中间速度缓存变量计算+平均速度计算
                    
                     此处以120°电角度放置的霍尔位置传感器为例
					 LF:low frequency低频
 * @warnings         !!!注意:本函数配合中频任务共同运行才能完成一次完整的速度估计
                     所以,完整电角度估算与速度估算流程是共同混杂在foc高频中断+速度更新中频任务+霍尔中断低频任务中的
 */
 	#define TIM_HALL_CK_INT				(84000000UL)          //用户自定义,指霍尔捕获所用的定时器外设主频,F4芯片一般除TIM1/TIM8,其余为84M
    #define TIM_HALL_REFER_CAP          (30000UL)


    #define S16_60_PHASE_SHIFT       	(10923UL)
    #define S16_120_PHASE_SHIFT      	(21845UL)
    #define S16_180_PHASE_SHIFT      	(32768UL)
    #define S16_240_PHASE_SHIFT      	(43691UL)
    #define S16_300_PHASE_SHIFT      	(54613UL)
    
    #define HALL_FW                  	(+1L)                   //霍尔周期正转
    #define HALL_RV                  	(-1L)                   //霍尔周期反转
    ///<120°电角度放置时的霍尔真值
    #define HALL_5                  	(5UL)
    #define HALL_1                  	(1UL)
    #define HALL_3                  	(3UL)
    #define HALL_2                  	(2UL)
    #define HALL_6                  	(6UL)
    #define HALL_4                  	(4UL)
    

void hall_processLF_IT(TIM_HandleTypeDef *htim)
{
	static uint16_t  ccr1=0;
    static int32_t   dpp=0;
    
    //<产生霍尔跳变,必定同时产生更新标记(先检测)和捕获标记(后检测)
    //<堵转只产生更新标记,不会产生捕获标记,因为霍尔真值没有变化
	if(__HAL_TIM_GET_FLAG(htim,TIM_FLAG_UPDATE))
	{
		///<没有霍尔跳变却发生了更新中断,说明速度太慢或堵转,计数器溢出更新
       if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC1)!= SET)
       {
			hall1.overcount++;
			if(hall1.overcount>500U)//<用户自定义堵转时间间隔,这里选500.但不一定非要500,具体情况具体分析,读者根据自己的需求来选堵转阈值。
			{
				hall1.locked=1;
				hall1.buffer[0]=0;
				hall1.buffer[1]=0;
				hall1.buffer[2]=0;
				hall1.buffer[3]=0;
				hall1.buffer[4]=0;
				hall1.buffer[5]=0;
				
				hall1.dpp_sum=0;
				hall1.avg_dpp=0;
				motor1.state=MOTOR_STOP;//<电机已经堵转,初始化所有值并标记当前为电机停运状态
			}
       }	
		__HAL_TIM_CLEAR_FLAG(htim,TIM_FLAG_UPDATE);
	}
	///<发生了霍尔跳变,捕获当前霍尔真值,并进行相应操作
	if(__HAL_TIM_GET_FLAG(htim,TIM_FLAG_CC1))
	{
		hall1.last_state=hall1.state;//<保存上次的霍尔真值
    	hall1.state=get_hallValue();
		
    	/**********************计算溢出期间的计数值 ************************/
   		/*************************CCR1捕获到值 ****************************/
		ccr1=htim->Instance->CCR1;
        hall1.cap=(hall1.overcount*0x10000L)+ccr1;
   		/********因为霍尔捕获存在硬件误差,根据STM32的定时器更新特性做速度适应处理********/
   		/*当然不做预分频更新处理,也不影响算法运行,若读者技术水平较弱,入门还请略过此步骤否则会适得其反*/
   		/*****笔者的目的是为了在不同速度下,其预分频下捕捉到的CCRx值都在30000左右(满值为65535嘛)*****/
   		/*这个时候呢,需要根据不同速度,动态调整预分频值,使得CCRx稳定在一个较大值,使硬件误差最小化*/
   		/*****************************上文图片中默认配置了预分频为128-1*****************************/
   		/*因为STM32的预分频值更新使用了影子功能,预分频更新值与计算f_HALL实际用到的分频值,还请参考笔者代码*/
    	if(hall1.overcount>0)     ///<速度过慢,但预分频值过低
    	{
			hall1.prescaler=htim->Instance->PSC+1;
			if(hall1.RatioInc!=0) ///<RatioInc预分频值增加标志位
			{
				hall1.overcount=0;
				hall1.RatioInc=0;	
			}
			else
		  	{
				if(htim->Instance->PSC<65535)	
				{
					__HAL_TIM_SET_PRESCALER(htim,htim->Instance->PSC+1);
					hall1.RatioInc=1;	
				}
			}	
   		 }
    	else                       ///<速度过快,但预分频值过高
    	{
			if(hall1.RatioDec!=0) ///<RatioDec预分频值减少标志位
			{
				hall1.prescaler=htim->Instance->PSC+2;
				hall1.RatioDec=0;	
			}
			else
		  	{
				hall1.prescaler=htim->Instance->PSC+1;
				if(ccr1<TIM_HALL_REFER_CAP)
				{
					if(htim->Instance->PSC>0)
					{
						__HAL_TIM_SET_PRESCALER(htim,htim->Instance->PSC-1);
						hall1.RatioDec=1;	
					}
			    }		
		    }    
   		}
		
    /*******************************!!!校准********************************/
    ///正反转相同位置,同一霍尔真值,但电角度实际是相差60°的
    ///offset_theta就是上文中提到的γ角
	switch (hall1.state)
    {
      case HALL_5:
      {
        if(hall1.last_state==HALL_1)
        {
          	hall1.direction=HALL_FW;//<refer_theta就是上文的θref
          	hall1.refer_theta=hall1.offset_theta+S16_240_PHASE_SHIFT;
			hall1.hall_theta=hall1.offset_theta+S16_240_PHASE_SHIFT;
        }
        else if(hall1.last_state==HALL_4)
        {
          	hall1.direction=HALL_RV;
          	hall1.refer_theta = hall1.offset_theta+S16_300_PHASE_SHIFT;
			hall1.hall_theta= hall1.offset_theta+S16_300_PHASE_SHIFT;
        }
		else
			return;
        break;
	  }
      case HALL_1:
      {
        if(hall1.last_state==HALL_3)
        {
          	hall1.direction=HALL_FW;
          	hall1.refer_theta = hall1.offset_theta+S16_180_PHASE_SHIFT;
			hall1.hall_theta= hall1.offset_theta+S16_180_PHASE_SHIFT;
        }
        else if(hall1.last_state==HALL_5)
        {
          	hall1.direction=HALL_RV;
          	hall1.refer_theta = hall1.offset_theta+S16_240_PHASE_SHIFT;
			hall1.hall_theta=hall1.offset_theta+S16_240_PHASE_SHIFT;
        }
		else
			return;
        break;
      }
       case HALL_3:
       {
        if(hall1.last_state==HALL_2)
        {
          	hall1.direction=HALL_FW;
          	hall1.refer_theta = hall1.offset_theta+S16_120_PHASE_SHIFT;
			hall1.hall_theta= hall1.offset_theta+S16_120_PHASE_SHIFT;
        }
        else if(hall1.last_state==HALL_1)
        {
          	hall1.direction=HALL_RV;
          	hall1.refer_theta = hall1.offset_theta+S16_180_PHASE_SHIFT;
			hall1.hall_theta= hall1.offset_theta+S16_180_PHASE_SHIFT;
        }
		else
			return;
        break;
       }
       case HALL_2:
       {
         if(hall1.last_state==HALL_6)
         {
           	hall1.direction=HALL_FW;
           	hall1.refer_theta = hall1.offset_theta+S16_60_PHASE_SHIFT;
			hall1.hall_theta=hall1.offset_theta+S16_60_PHASE_SHIFT;
         }
         else if(hall1.last_state==HALL_3)
         {
           	hall1.direction=HALL_RV;
           	hall1.refer_theta = hall1.offset_theta+S16_120_PHASE_SHIFT;
			hall1.hall_theta= hall1.offset_theta+S16_120_PHASE_SHIFT;
         }
		 else
			return;
         break;
	   }
       case HALL_6:
       {
         if(hall1.last_state==HALL_4)
         {
           	hall1.direction=HALL_FW;
           	hall1.refer_theta = hall1.offset_theta;
			hall1.hall_theta= hall1.offset_theta;
         }
         else if(hall1.last_state==HALL_2)
         {
           	hall1.direction=HALL_RV;
           	hall1.refer_theta = hall1.offset_theta+S16_60_PHASE_SHIFT;
			hall1.hall_theta= hall1.offset_theta+S16_60_PHASE_SHIFT;
         }
		 else
			return;
         break;
       }
        case HALL_4:
        {
           if(hall1.last_state==HALL_5)
          {
             	hall1.direction=HALL_FW;
             	hall1.refer_theta = hall1.offset_theta+S16_300_PHASE_SHIFT;
				hall1.hall_theta=hall1.offset_theta+S16_300_PHASE_SHIFT;
          }
          else if(hall1.last_state==HALL_6)
          {
             	hall1.direction=HALL_RV;
             	hall1.refer_theta = hall1.offset_theta;
				hall1.hall_theta= hall1.offset_theta;
          }
		 else
				return;
         break;
        }
     }
    	/*****************************校准 End********************************/
		
		/*************************电角度估算参数迭代 ****************************/
		if(hall1.startup!=0)
		{
			//电机启动期间速度计算(启动时候六个扇区都没有填满值,启动前6次有多少个数据就除多少个)
			hall1.dpp_sum-=hall1.buffer[hall1.buffer_index];
			hall1.f_hall=(TIM_HALL_CK_INT/(hall1.prescaler*hall1.cap));
			dpp=(10923UL*hall1.f_hall/hall1.f_foc)*hall1.direction;///<10923UL是60°弧度的整型定标
			hall1.buffer[hall1.buffer_index]=dpp;
			hall1.dpp_sum+=hall1.buffer[hall1.buffer_index];
			hall1.buffer_index++;
			hall1.avg_dpp=(hall1.dpp_sum/hall1.buffer_index);	
			if(hall1.buffer_index>=WBuffer_MAX_SIZE)//<WBuffer_MAX_SIZE此处为6
			{
				hall1.startup=0;//<清除电机启动标志位
				hall1.buffer_index=0;
			}
		}
		else
		{
			///滑动均值滤波(完成启动,缓存区都有6个数据,往后都除以6求平均)
			hall1.dpp_sum-=hall1.buffer[hall1.buffer_index];
			hall1.f_hall=(TIM_HALL_CK_INT/(hall1.prescaler*hall1.cap));
			dpp=(10923UL*hall1.f_hall/hall1.f_foc)*hall1.direction;
			hall1.buffer[hall1.buffer_index]=dpp;
			hall1.dpp_sum+=hall1.buffer[hall1.buffer_index];
			hall1.buffer_index++;
			if(hall1.buffer_index>=WBuffer_MAX_SIZE)
			{
				hall1.buffer_index=0;
			}
			hall1.avg_dpp=(hall1.dpp_sum/WBuffer_MAX_SIZE);		
		}
        /*************************电角度估算参数迭代 End************************/
		__HAL_TIM_CLEAR_FLAG(htim,TIM_FLAG_CC1);
	}
}

6.2 中频任务(定时器更新中断)

/**
 * @brief            速度更新中频任务(频率1k,确保速度更新大于速度环2倍以上,速度更新频率一般1kHz)
 * @param    htim    速度更新所使用的定时器指针
 * @function         这里配合foc高频中断+霍尔中断捕获实现估算插值补偿               
 *                   MF:Moderate frequency中频
 

 * @warnings         !!!注意:本函数配合中频任务共同运行才能完成一次完整的速度估计
                     所以,完整电角度估算与速度估算流程是共同混杂在foc任务+速度更新中频任务+霍尔中断低频任务中的
 */
void speed_updateMF_IT(TIM_HandleTypeDef *htim)
{
	static int32_t  n=(int32_t)(SPEED_PERIOD/FOC_PERIOD);///<就是公式中的n
	if(__HAL_TIM_GET_FLAG(htim,TIM_FLAG_UPDATE))
	{
		hall1.dpp_2=hall1.dpp_1;
    	hall1.dpp_1=hall1.avg_dpp;
    	hall1.a_dpp=hall1.dpp_1-hall1.dpp_2;
    	hall1.dpp = hall1.dpp_1+hall1.a_dpp;
    	hall1.delta_theta = hall1.refer_theta - hall1.theta;
		
		//10922为60°的整型值,65536为2PI
		//当电机实际的角度追踪值与FOC所用的角度值差某个度数(取60°一般合理,因为超过60°就已经跨SVPWM扇区了,是不可接受的
		//故当偏差大于60°时强制让它瞬变为目标追踪值,不再靠补偿平缓逼近目标追踪值;
		if(hall1.delta_theta>10922)
		{
			hall1.theta=hall1.refer_theta;//<refer_theta就是上文的θref
			hall1.comp_dpp =0;
		}
		else if (hall1.delta_theta<-10922)
		{
			hall1.theta=hall1.refer_theta;
			hall1.comp_dpp =0;
		}
		else
		{
			hall1.comp_dpp =(hall1.delta_theta / n);
		}
		__HAL_TIM_CLEAR_FLAG(htim,TIM_FLAG_UPDATE);
	}
}

6.3 高频任务(FOC执行中断)

/**
 * @brief            FOC高频中断运行流程(至少8K,这里选取10K执行频率)
 * @param            电机FOC流程对象结构体
 * @description      电机的状态机用户可自行定义,这里从简
 *                   HF:High frequency高频
 */
void foc_processHF_IT(foc_object *foc)
{
		static float32_t Rv_U=0,Rv_V=0,Rv_W=0;                //<采样电阻电压
		static float32_t current_U=0,current_V=0,current_W=0; //<三电阻法,相电流存储变量
		static float32_t speedP=(float)(1.0f/P);              //<乘积因子,电角速度=极对数*机械角速度
		
		if(__HAL_ADC_GET_FLAG(foc->adc_motor_output.hadc,ADC_FLAG_JEOC))
		{
			/*********电流采样(入门秘法:不分扇区情况,按两电阻法直接暴力采)*******/
			switch (foc->state)
			{
				case MOTOR_ADC_ALIGN:
				{
					//<第一步:电机ADC偏置矫正程序
					if(adc_count1--<0)//<间隔一段时间(adc_count1的值来决定)让硬件稳定
					{
						//<硬件稳定后,外设采集1024(adc_count2的值来决定)叠加
						foc->adc_motor_output.currentA_biasQ+=HAL_ADCEx_InjectedGetValue(foc->adc_motor_output.hadc,ADC_INJECTED_RANK_1);//<A/U相电压ADC校准
						foc->adc_motor_output.currentB_biasQ+=HAL_ADCEx_InjectedGetValue(foc->adc_motor_output.hadc,ADC_INJECTED_RANK_2);//<B/V相电压ADC校准
						foc->adc_motor_output.currentC_biasQ+=HAL_ADCEx_InjectedGetValue(foc->adc_motor_output.hadc,ADC_INJECTED_RANK_3);//<C/W相电压ADC校准
						foc->adc_motor_output.vbus_biasQ+=HAL_ADCEx_InjectedGetValue(foc->adc_motor_output.hadc,ADC_INJECTED_RANK_4);//<母线电压ADC校准
						if(adc_count2--<0)//<采完1024次,整型右移10位是除以1024求平均值
						{	
							foc->adc_motor_output.currentA_biasQ>>=10;
							foc->adc_motor_output.currentB_biasQ>>=10;
							foc->adc_motor_output.currentC_biasQ>>=10;
							foc->adc_motor_output.vbus_biasQ>>=10;
							foc->adc_motor_output.currentA_biasF=3.3f*foc->adc_motor_output.currentA_biasQ/4096.0f;
							foc->adc_motor_output.currentB_biasF=3.3f*foc->adc_motor_output.currentB_biasQ/4096.0f;
							foc->adc_motor_output.currentC_biasF=3.3f*foc->adc_motor_output.currentC_biasQ/4096.0f;
							foc->adc_motor_output.vbus_biasF=3.3f*foc->adc_motor_output.vbus_biasQ/4096.0f;
							motor_enable(&motor1);//<电机使能,不用在意,这个读者自己根据项目需求自行定义就可
							foc->state=MOTOR_RUN;
						}
					}
					break;
				}
				case MOTOR_RUN:
				{
					//<第二步:电机双闭环运行
					//<若做了母线电压采集,请在此处引入母线电压的ADC采集接口
					//foc->voltage_result.v_dc_real=HAL_ADCEx_InjectedGetValue(foc->adc_motor_output.hadc,ADC_INJECTED_RANK_4);

					//<精密电阻R=0.01Ω电位采集
					Rv_U=HAL_ADCEx_InjectedGetValue(foc->adc_motor_output.hadc,ADC_INJECTED_RANK_1);//IU
					Rv_V=HAL_ADCEx_InjectedGetValue(foc->adc_motor_output.hadc,ADC_INJECTED_RANK_2);//IV
					Rv_W=HAL_ADCEx_InjectedGetValue(foc->adc_motor_output.hadc,ADC_INJECTED_RANK_3);//IW
					/** 计算:数字量转化为模拟量*/
					Rv_U=3.3f*Rv_U/4096.0f;
					Rv_V=3.3f*Rv_V/4096.0f;
					Rv_W=3.3f*Rv_W/4096.0f;
					/** v_ADC=1.65-10v*/
					/** 用户可自行修改*/
					current_U=(foc->adc_motor_output.currentA_biasF-Rv_U)*10.0f;
					current_V=(foc->adc_motor_output.currentB_biasF-Rv_V)*10.0f;
					current_W=(foc->adc_motor_output.currentC_biasF-Rv_W)*10.0f;
					foc->current_result.i_a=current_U;
					foc->current_result.i_b=current_V;
					foc->current_result.i_c=current_W;
					clark_transform(&foc->current_result, &foc->clark_result);
					
					/*********二选一,根据需求注释掉就可,推荐PLL法********/
					//****************插值法****************//
					//hall_get_Speed(&hall1);//<插值法估算电角速度rad/s
					//hall_positionEst(&hall1);//<插值法估算角度
					//***********锁相环PLL法***************//
					hall_get_Speed(&hall1);//<插值法获取电角速度rad/s
					foc->angle_result.electrical_angle=hall_pll_filter(&hall1);


					//****机械角速度计算*****//
					foc->speed_result.eleSpeed=hall1.omega_inter;//<插值法或锁相环法中的估算电角速度都可以带入(推荐插值法速度),单位rad/s
					MOTOR_GET_MECH((&motor1),speedP);//<电角速度rad/s转化为机械角速度rad/s
					MOTOR_GET_MECHRPM((&motor1));//<机械角速度rad/s转化为机械角速度RPM
					park_transform(&foc->clark_result, &foc->park_result, &foc->angle_result.electrical_angle);
					if(speed_loop_count--<0)
					{
						speed_loop_count=20;//<速度更新频率为1k,但速度环执行频率必须小于次,这里取500Hz
						iq_set((&current_loop),speed_loop_achieve(&speed_loop,foc->speed_result.mechRPM));
					}
					current_loop_achieve(&current_loop,foc->park_result.i_d,foc->park_result.i_q);
					//<Rs:同步旋转轴电阻
					//<Ls=Ld=Lq:同步旋转轴电感(SPMSM/BLDC)
					//<数据一定要精确才能加入耦合项,若数据不精确请大胆去掉耦合项
					//<这里加上耦合项是符合理论的,符合我的另一篇博文PI控制器的参数整定
				 	foc->ipark_result.v_d=current_loop.id_loop_output-foc->speed_result.eleSpeed*Ls*foc->park_result.i_q;
				 	//<FLUX_VALUE:磁链系数,单位是V·s/rad
					foc->ipark_result.v_q=current_loop.iq_loop_output+foc->speed_result.eleSpeed*(Ls*foc->park_result.i_d+FLUX_VALUE);
					
					circle_calculate(foc);//<电压圆限制
					
					ipark_transform(&foc->ipark_result, foc->ipark_result.v_d, foc->ipark_result.v_q,&foc->angle_result.electrical_angle);
					/**ִSVPWM生成,详情请参考我的另一篇博文**/
					svpwm_myAchieve(foc);
					break;
				}
				case MOTOR_STOP:
				{
					//<状况三:电机停止功能程序接口,用户自定义
					motor_disable(foc);
					break;
				}
				case MOTOR_REPAIR:
				{
					//<状况四:电机故障修复程序接口,用户自定义
					break;
				}
			}
		__HAL_ADC_CLEAR_FLAG(foc->adc_motor_output.hadc,ADC_FLAG_JEOC);
	}
}

7.峰岹科技对于霍尔γ角的确定方法(仅供参考)

γ角是自己定义的,核心是为了找出0°电角度在霍尔真值的哪个区域上,知道了这个也就知道了上文中定义的γ角。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

推荐内容
阅读全文
AI总结
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐