野火STM32F407(骄阳)+无刷直流驱动板+永磁同步电机控制学习记录(四)(基于HAL库)

本文详细描述了一种基于TIM1PWM和ADC的FOC闭环控制方法,包括电流环和速度环的配置、PID参数设置、采样频率调整,以及使用DMA优化数据传输。作者分享了自学习过程中关于电机控制参数选择和调试的心得。
摘要由CSDN通过智能技术生成

FOC有感闭环控制(速度环+电流环)

  • 配置TIM1为PWM输出,通道4用于触发电流采样,死区可以不需要,野火有硬件死区;定时器1配置为中心对齐模式,频率计算为 168000000 / (TIM1_PSC +TIM1_ARR)/2 ;
  • 配置ADC采样,用的是注入通道,对于规则组来说相当于中断,可以优先采集,采用定时器1的通道四触发采集。ADC的采样频率最高是36Mhz,挂在APB2上,但是APB2我配置的时钟是84MHz,所以只能采用四分频,ADC采样频率为21Mhz。(如果配置CPU时钟为144Mhz,可以让ADC二分频到36Mhz);采样时间设置为3个周期,所以单次的采样时间为 (3 + 12.5) /  21000000 =0.74us,我定时器1的频率为20kHz,采样周期为50us,所以丝毫不用担心采样间隔的问题。

  • 开启ADC的DMA功能(多通道用DMA好很多)
  • 生成代码的时候注意外设初始化顺序,ADC的初始化要在DMA前面

  • 配置定时器1,开启PWM输出,配置通道4占空比,用于ADC采集
//设置为20k
#define TIM1_ARR	100.0f				//计数100,周期50us,20KHZ
#define TIM1_PSC	168.0f/4.0f			//分频后2M,单个计数500ns
#define Dead_Time	2.0f				//死区时间

HAL_TIMEx_PWMN_Start(&htim1,TIM_CHANNEL_1);//开启互补pwm
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);
	
HAL_TIMEx_PWMN_Start(&htim1,TIM_CHANNEL_2);//开启互补pwm
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_2);
	
HAL_TIMEx_PWMN_Start(&htim1,TIM_CHANNEL_3);//开启互补pwm
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_3);
	
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_4);	//打开通道4PWM,触发ADC采集
	
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_4,TIM1_ARR/2);			//50%
  • 电流采样函数:
HAL_ADCEx_InjectedStart(&hadc1);
ADC_Ia=HAL_ADCEx_InjectedGetValue(&hadc1,ADC_INJECTED_RANK_1);
ADC_Ib=HAL_ADCEx_InjectedGetValue(&hadc1,ADC_INJECTED_RANK_1);

//按照无刷驱动板的原理图上的公式算的,0.02是电阻值,电流转成的电压 除以电阻就是电流
Ia=((ADC_Ia * 3.3f /4095.0f) -1.24f)/8.0f/0.02f;
Ib=((ADC_Ib * 3.3f /4095.0f) -1.24f)/8.0f/0.02f;

FOC开环控制在上一节写了,现在主要是闭环控制;

电流环和速度环的参数设定主要参考袁雷老师的《现代永磁同步电机控制原理及MATLAB仿真》

将电机参数拿MATLAB仿真了一下,总感觉参数怪怪的;电流环带宽按书上这么算,参数带进去直接算出来9000多,这肯定不太行,太抽象了,仿真都有问题。所以先设置电流环带宽为2000。波形不是很理性,但是我不懂为什么

  • 电流环

//电流环
void Current_Loop(void)
{
	static float temp,temp2;
	
		
	//对三相电流进行采样,三相电流相加不为0,是因为硬件上有误差(野火客服说的,可恶!)  电流采样频率和电流频率一样都为20k
	ADC_Transform();
	
	//对电流进行Clark变换,再Park变换
	Motor1.Ialpha = Motor1.Ia;
	Motor1.Ibeta  = (Motor1.Ia + 2*Motor1.Ib)/SQRT_3;
	
	Motor1.Id =  Motor1.Ialpha* qfcosd(Motor1.Theta)+ Motor1.Ibeta* qfsind(Motor1.Theta);
	Motor1.Iq = -Motor1.Ialpha* qfsind(Motor1.Theta)+ Motor1.Ibeta* qfcosd(Motor1.Theta);
	
	//PI环节
	
	//这个电流环的积分项我没整明白,加上积分的话,积分项会直接冲到最大,且一直为最大,然后就电机转动也没有更好,感觉就是反作用,先不整了,把它干掉,后面再研究
	Motor1.Ud = CurrentLoop_Kpd *(Motor1.Id_Set - Motor1.Id) + temp - Motor1.We * MOTOR1_Lq * Motor1.Iq;
//	temp = Value_SetMaxMin( temp+ CurrentLoop_Kid * (Motor1.Id_Set - Motor1.Id) * Motor1.Calculate_Ts, -0.1f, 0.1f);
	
	Motor1.Uq = CurrentLoop_Kpq *(Motor1.Iq_Set - Motor1.Iq) + temp2 + Motor1.We * (MOTOR1_Ld * Motor1.Id + MOTOR1_flux);
//	temp2 = Value_SetMaxMin( temp2+ CurrentLoop_Kiq * (Motor1.Iq_Set - Motor1.Iq) * Motor1.Calculate_Ts, -0.1f, 0.1f);
	
	//合成电压最大值为SQRT_3 * UDC / 3 = 13.85 , Ud 最大值设为3 是为了高频方波注入算法, 这些限值都是试出来的,我也不会算理论值;这样限值转速最大能到2600RPM左右,够了
	Motor1.Ud = Value_SetMaxMin(Motor1.Ud,-3.0f,3.0f);
	Motor1.Uq = Value_SetMaxMin(Motor1.Uq,-12.0f,12.0f);
	
//	Motor1.Ud = 0;
//	Motor1.Uq = 2;
}
  • 速度环,计算速度的时候加了一个卡尔曼滤波,因为采样频率有的快,电机速度慢的时候,编码器的精度不够,导致速度计算出来波动太大了。
//速度环
void Speed_Loop(void)
{
	static float temp;
	static int Encoder_Last;
	float Encoder_W_temp;
	
	if(Encoder_Z_Flag==1)
	{
		Encoder_W_temp = KalmanFilter(Motor1.Encoder_Value - Encoder_Last + Encoder_temp);		
		Encoder_Z_Flag=0;
	}
	else
		Encoder_W_temp = KalmanFilter(Motor1.Encoder_Value - Encoder_Last);
	
	//根据编码器计算角速度  速度范围波动会比较大,因为采样频率太高了,编码器的分辨率又不够高,所以这是很正常的。降低采样频率(即减小TIM2的时钟频率)可以减小波动
	Motor1.We = Encoder_W_temp * We_coefficient;		//单位 rad/s(电角速度)
	Motor1.Wm = Encoder_W_temp * Wm_coefficient;		//单位 RPM(角速度)

	Encoder_Last=Motor1.Encoder_Value;
	
	
	Motor1.Iq_Set = SpeedLoop_Kp * (Motor1.Wm_Set - Motor1.Wm) + temp;
	temp = Value_SetMaxMin(temp +SpeedLoop_Ki * (Motor1.Wm_Set - Motor1.Wm) * Motor1_Time.Speed_Loop_Ts,-10.0f,10.0f);		//给个限值防止他一直往上加,一启动电机速度飞飚,这个值是慢慢试的,大概速度到3000RPM时,这个值为10.多
}


//编码器Z信号外部中断
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if(GPIO_Pin==Encoder_Z_Pin)
	{
//		Motor1.temp = __HAL_TIM_GET_COUNTER(&htim5); 
		//记录编码器到零位跳变前的计数值,并设立标志位
		Encoder_Z_Flag=1;
		Encoder_temp=Get_Encoder_Count();
		
		__HAL_TIM_SET_COUNTER(&htim5,0);
	}
}

//卡尔曼滤波
float KalmanFilter(float inData)
{
        static float prevData = 0;                                 //先前数值
        static float p = 1, q = 0.001, r = 0.1, kGain = 0;      // q为过程噪声  r为测量噪声 
    
        p = p + q;
        kGain = p / ( p + r );                                     //计算卡尔曼增益
        inData = prevData + ( kGain * ( inData - prevData ) );     //计算本次滤波估计值
        p = ( 1 - kGain ) * p;                                     //更新测量方差
        prevData = inData;
        return inData;                                             //返回滤波值
}
  • FOC闭环控制
//整个闭环控制计算可以满足40k的处理频率
void Foc_Control(void)
{	
	static int Number1=0,Number2=0,Number3=0;
	
	Motor1.Encoder_Value=Get_Encoder_Count();
	
	//根据编码器计算角度
	Motor1.Theta=Value_Limit(( Motor1.Encoder_Value + ENCODER_OFFSET ) * Encoder_Theta_coefficient,0.0f,360.0f);
	Motor1.Theta_m = Value_Limit(( Motor1.Encoder_Value + ENCODER_OFFSET ) * Encoder_Theta_m_coefficient,0.0f,360.0f);

	if(++Number1 >= Motor1_Time.Locate_Loop_TimeGain)
	{
		//执行位置环
		Locate_Loop();
		Number1= 0;
	}
	
	if(++Number2 >= Motor1_Time.Speed_Loop_TimeGain)
	{
		//执行速度环
		Speed_Loop();
		Number2= 0;
	}
	
	if(++Number3 >= Motor1_Time.Current_Loop_TimeGain)
	{
		//执行电流环
		Current_Loop();
		Number3= 0;
	}
	
	//对电压进行反Park变换
	Park_Inverse_Transform();
	
	//执行SVPWM并设定电机占空比
	FOC_SVPWM();
}

//定时器2更新中断(20k)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	
	if(htim == &htim2)
	{
		//测试中断所需时间
		HAL_GPIO_WritePin(GPIO_TEST1_GPIO_Port,GPIO_TEST1_Pin,GPIO_PIN_SET);

		Foc_Control();
		
		HAL_GPIO_WritePin(GPIO_TEST1_GPIO_Port,GPIO_TEST1_Pin,GPIO_PIN_RESET);
	}

}
  • 相关参数的初始化
void Foc_Init()
{
	float temp1,temp2;
	
	Motor1.Wm_Set=0;
	Motor1.Id_Set=0;
	
	//设定为20k(电流采样由定时器1的PWM比较触发,频率也为20k)
	Motor1_Time.PWM_Fre =  168000000.0f / (TIM1_ARR * TIM1_PSC) / 2;
	Motor1_Time.PWM_Ts = 1 / Motor1_Time.PWM_Fre;

	//电流环:速度环:位置环的速度比例有多种,有16:4:1, 2:1:1, 1:1:1,都可以  
	
	Motor1_Time.Current_Loop_TimeGain = 2.0f;
	Motor1_Time.Speed_Loop_TimeGain = 4.0f;
	Motor1_Time.Locate_Loop_TimeGain = 4.0f;
	
	//计算频率设定为40k
	Motor1_Time.Calculate_Fre = 84000000.0f / (TIM2_ARR * TIM2_PSC );
	Motor1_Time.Calculate_Ts = 1 / Motor1_Time.Calculate_Fre;
	
	//电流环设定为20k
	Motor1_Time.Current_Loop_Fre = Motor1_Time.Calculate_Fre / Motor1_Time.Current_Loop_TimeGain;
	Motor1_Time.Current_Loop_Ts = 1 / Motor1_Time.Current_Loop_Fre;
	
	//速度环设定为10k
	Motor1_Time.Speed_Loop_Fre = Motor1_Time.Calculate_Fre / Motor1_Time.Speed_Loop_TimeGain;
	Motor1_Time.Speed_Loop_Ts = 1 / Motor1_Time.Speed_Loop_Fre;
	
	//位置设定为10k
	Motor1_Time.Locate_Loop_Fre = Motor1_Time.Calculate_Fre / Motor1_Time.Locate_Loop_TimeGain;
	Motor1_Time.Locate_Loop_Ts = 1 / Motor1_Time.Locate_Loop_Fre;
	
	//编码器计数值转换为角度的系数值
	Encoder_Theta_coefficient = 360 * MOTOR1_Pn /(ENCODER_NUMBER * 4);
	Encoder_Theta_m_coefficient = 360 /(ENCODER_NUMBER * 4);
	
	//编码器计数值转换为角速度的系数值
	We_coefficient = 2* PI /(ENCODER_NUMBER * 4) / Motor1_Time.Speed_Loop_Ts;
	Wm_coefficient = 60.0f /(ENCODER_NUMBER * 4) / Motor1_Time.Speed_Loop_Ts;		
	
	//计算电流环的PI系数
	temp1=MOTOR1_Ld/MOTOR1_R;
	temp2=MOTOR1_Lq/MOTOR1_R;
	
	t=(temp1<temp2)?temp1:temp2;
	Current_BandWidth = 2*PI/t;		//算出来的带宽太大了,有九千多,感觉是电机的参数给的可能有问题?不知道,反正不用这个带宽

	//小了响应速度不够快,大了电流波动大
	Current_BandWidth =1500;
	
	CurrentLoop_Kpd = Current_BandWidth * MOTOR1_Ld;
	CurrentLoop_Kid = Current_BandWidth * MOTOR1_R;
	
	CurrentLoop_Kpq = Current_BandWidth * MOTOR1_Ld;
	CurrentLoop_Kiq = Current_BandWidth * MOTOR1_R;
	
	//计算速度环的带宽
	Speed_BandWidth = 120;
	Ba = (Speed_BandWidth *MOTOR1_J - MOTOR1_B ) / (1.5f * MOTOR1_Pn * MOTOR1_flux);	//这个值在速度环我看别人的仿真不知道为啥给扔了,在速度环的时候不要这个
	SpeedLoop_Kp = ( Speed_BandWidth * MOTOR1_J ) / (1.5f * MOTOR1_Pn * MOTOR1_flux) ; 
	SpeedLoop_Ki = Speed_BandWidth * SpeedLoop_Kp;
}

电流环和速度环的带宽我是试出来的,判断标准就是凭感觉。

电流环带宽的试验:去掉速度环,让Iq_Set=2,Ud=0,此时电机是电流环闭环控制,用手堵转电机然后松开,看电流的波动以及响应情况。电流环带宽一般在1000左右

电流环带宽设为500时

电流环带宽设为1500时

电流环带宽设为2500时

我电流环带宽在2500时,电流波动很大,电机抖动厉害;带宽在500时,电流波动小,但是响应很慢,就是堵转后不太容易恢复转动。所以大概取了个中间值1500,算是效果相对较好。。。。

  • 速度环带宽的试验:给定速度,然后速度加加减减的,看设定速度和实际速度的响应情况;速度环带宽一般在50~200

        速度环带宽为50时:(红色为实际速度,绿色为设定速度)

        速度环带宽为120时

        速度环带宽为150时

速度环带宽设定小了,响应不够快,带宽设定大了波动比较大,电机感觉起来也震一点,而且,当带宽设到200的时候,启动直接就锁死了,应该是PI增益设定太大了。所以选个适中的120。

以上内容都是参考别人资料自学的,很多地方都是凭感觉,因为理论部分不太会算,只要电机能转就行,其他性能指标都没考虑,因为不会,所以这纯属是学习记录帖。肯定有很多漏洞和问题,留到后面学明白了再填补吧。

  • 13
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值