
【电机控制算法】基于霍尔位置传感器(HALL)估算连续电角度(基于STM32F407+CubeMX+HAL)
目录
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((¤t_loop),speed_loop_achieve(&speed_loop,foc->speed_result.mechRPM));
}
current_loop_achieve(¤t_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°电角度在霍尔真值的哪个区域上,知道了这个也就知道了上文中定义的γ角。
更多推荐









所有评论(0)