【嵌入式电机控制#24】BLDC:霍尔测速(高难度,重理解)
前面我们实现了霍尔传感器检测转子位置、改变导通策略的换相方法,但是无刷电机自身存在着难以直接消除的非线性。也就是说,我们无法保证给定占空比后的速度是准确的,这个时候我们就必须依靠测速来实现速度控制。
此部分开始,将会进入电控算法中代码整体可读性最差、逻辑最复杂的部分,这不是代码本身造成的,而是无刷控制逻辑本身需求就很高。
后面做FOC使用了SDK库,工作量将会减少很多。
一、霍尔计数原理
与有刷电机的Encoder类似,无刷的HALL接口也是通过不断采样数字信号来获得计数值的。
这里的采样频率,就是计数器CCR工作的频率。区别在于,我们做无刷时,习惯性把传感器异或信号频率直接进行估算,这会导致一系列问题。
霍尔信号频率与电机转速理论表达式如下所示。
问题在于,虽然以上方法在数学角度讲能够测出转速,但是经过实际测试,我们发现由于无刷采用了六步换相的驱动方式,每相间的时间差不同,所以用以上方式测出的转速存在较大误差。
在直流无刷电机工程中,我们采用求和平均的测速方法。
具体做法是,利用预设定时器中断,调用多次输入捕获中断中的sum和CNT值,触发fHALL和spd的计算,进而算出实时转速。
需要注意的是,控制理论中严格规定,控制系统的采样必须以时间为基准。无论是在有刷中的霍尔编码器、电压放大器采样,还是有刷中的霍尔传感器,在反馈过程中,都采用了时间窗口作为最终反馈数据的测量标准。
时间窗口,也就是我们常说的动态窗口的一种。在嵌入式系统中,固定时间长内采样次数会因为各种非线性而飘忽不定,比如霍尔传感器的输入信号,每次采样在时间域上不一定是等长的。只有计算测量变量一个隐式的时间加权平均和,才能更精确的测量数据并减少其对控制性能影响。
二、定时器参数的确定(重要)
(1)霍尔定时器计数周期(fTIM)确定
a. 从奈奎斯特采样定律角度考虑
如果霍尔输出信号存在最小脉宽,则采样周期不得大于最小脉宽度的1/2
b. 从定时器频率角度考虑
在不损失性能的情况下,定时器频率尽量要远大于输入脉冲频率(>10倍)
我们的霍尔定时器周期大概在18 - 20ms,远大于霍尔脉宽频率
(2)SYSTiCK中断周期
SYSTICK中断周期尽量为霍尔计数周期的数倍,但不得损失采样速率
我们正是通过定时中断限制一个时间窗口,让SUM和CNT进行加权平均运算
(3)中断优先级设定
霍尔中断 > 定时器中断
三、源码解析
1. 输入捕获函数(霍尔样本采集)
const uint8_t HallDirCcw [7] = {0, 3, 6, 2, 5, 1, 4};
//霍尔序列表,保存着上一次的霍尔相位值//这里使用霍尔序列表和霍尔方向的原因是,在无刷电机中,霍尔方向和电机控制方向不一定是一个值
//当我们在计算速度方向(正负)时,不能参考电机控制方向,而是霍尔的电角度方向void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{int32_t RT_hallPhase = 0; //此次霍尔相位寄存器RT_hallPhase = HALL_GetPhase(); // 获取相位//进行换向BLDCMotor_PhaseCtrl(RT_hallPhase);
// HAL_TIM_GenerateEvent(&htim1, TIM_EVENTSOURCE_COM); // Èí¼þÉú³ÉCOMʼþ
// __HAL_TIM_CLEAR_FLAG(&htim1, TIM_FLAG_COM);//进行CCR累加RT_hallcomp += __HAL_TIM_GET_COMPARE(&htim2, TIM_CHANNEL_1 );RT_hallcnt++;//统计CNTif(HallDirCcw[RT_hallPhase] == LS_hallPhase) // ÐòÁÐÓë±íÖеÄÒ»ÖÂ{//如果上次状态转移符合霍尔相位表,则标记为逆时针RT_hallDir = MOTOR_DIR_CCW; }else/若不符合,则是顺时针RT_hallDir = MOTOR_DIR_CW;//进行旧相位值迭代LS_hallPhase = RT_hallPhase; // ¼Ç¼ÕâÒ»¸öµÄ»ô¶ûÖµ
}
2. 定时器中断处理(加权均值计算)
float Speed_hz = 0;
void HAL_SYSTICK_Callback()
{if(timeTick != 0)timeTick--;//提前定义了ticktime = 0,开始计时else {uint32_t tmpCC = 0;//初始化加权结果if(RT_hallcnt == 0) //为了避免分母是0而出错,我们应该做判断。{//原则上讲,分母为0,则计算结果必然是0,因为你什么采集信号都没有Speed_hz = 0;}else {//进行加权结果计算tmpCC = RT_hallcomp / RT_hallcnt; // tmpCC:Á½´Î²¶»ñÖ®¼äµÄ²¶»ñÖµ,Speed_hz = (float)HALL_F/(float)(tmpCC); //估算霍尔频率}RT_hallcomp = 0;RT_hallcnt = 0;//时间窗口参量清零if(RT_hallDir == MOTOR_DIR_CW)//依据霍尔方向进行速度符号处理Speed_hz = Speed_hz > 0 ? Speed_hz : (-Speed_hz);elseSpeed_hz = Speed_hz < 0 ? Speed_hz : (-Speed_hz);// printf("%.3f Hz, %.2f RPS, %.2fRPM\n", Speed_hz, Speed_hz/PPR, (Speed_hz/PPR)*60);//建议用vofa输出最终速度,printf则最好用DMA发出去Vofa_data((Speed_hz/PPR)*60,VOFA_CHANNEL0);//计时器重新初始化isTimeUp = 0;timeTick = TIMECNT;}
}
3. 执行函数
void BLDCMotor_PhaseCtrl(int32_t Phase){if(MOTOR_DIR_CW == Motor_Dir) Phase = 0x07 ^ Phase;// ½«µÍÈýλÒì»ò 111b ^ 010b -> 101belse {Phase = 0x07 ^ Phase;// ½«µÍÈýλÒì»ò 111b ^ 010b -> 101b}//根据控制方向进行输出控制,其他与之前相同switch(Phase){case 5: //B+ A-{/* Channe3 configuration */ HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_3);HAL_TIMEx_PWMN_Stop(&htim1, TIM_CHANNEL_3);/* Channe2 configuration */__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2,4200 * PWM_set);HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);HAL_TIMEx_PWMN_Stop(&htim1, TIM_CHANNEL_2); /* Channe1 configuration */__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 4201);HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_1);HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1);}break;case 4:// C+ A-{/* Channe2 configuration */ HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_2);HAL_TIMEx_PWMN_Stop(&htim1, TIM_CHANNEL_2);/* Channe3 configuration */__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3,4200 * PWM_set);HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);HAL_TIMEx_PWMN_Stop(&htim1, TIM_CHANNEL_3); /* Channe1 configuration */__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1,4201);HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_1);HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1);}break;case 6://C+ B-{/* Channe1 configuration */ HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_1);HAL_TIMEx_PWMN_Stop(&htim1, TIM_CHANNEL_1);/* Channe3 configuration */__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3,4200 * PWM_set);HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);HAL_TIMEx_PWMN_Stop(&htim1, TIM_CHANNEL_3); /* Channe2 configuration */__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2,4201);HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_2);HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_2);}break;case 2: // A+ B-{/* Channe3 configuration */HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_3);HAL_TIMEx_PWMN_Stop(&htim1, TIM_CHANNEL_3);/* Channe1 configuration */__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1,4200 * PWM_set);HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);HAL_TIMEx_PWMN_Stop(&htim1, TIM_CHANNEL_1); /* Channe2 configuration */__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2,4201);HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_2);HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_2);}break;case 3:// A+ C-{/* Channe2 configuration */HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_2);HAL_TIMEx_PWMN_Stop(&htim1, TIM_CHANNEL_2);/* Channe1 configuration */__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1,4200 * PWM_set);HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);HAL_TIMEx_PWMN_Stop(&htim1, TIM_CHANNEL_1); /* Channe3 configuration */__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3,4201); HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_3);HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_3);}break;case 1: // B+ C-{/* Channe1 configuration */ HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_1);HAL_TIMEx_PWMN_Stop(&htim1, TIM_CHANNEL_1);/* Channe2 configuration */__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, 4200 * PWM_set);HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);HAL_TIMEx_PWMN_Stop(&htim1, TIM_CHANNEL_2); /* Channe3 configuration */__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, 4201);HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_3);HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_3);}}
// HAL_TIM_GenerateEvent(&htim1, TIM_EVENTSOURCE_COM);}