【STM32F103RCT6】电机PWM驱动模块思路与代码

(37) 2024-04-30 20:01:01

目录

【硬件说明】

【软件设计】

驱动端口初始化

理论公式说明

PG反馈信号输入捕获

TIM2初始化

定时器2中断程序

PWM引脚初始化

速度PID模型和位置PID模型

主程序


【硬件说明】

        板子:STM32F103RCT6内核开发板;BLDC驱动器(型号UB510

【STM32F103RCT6】电机PWM驱动模块思路与代码 (https://mushiming.com/)  第1张

图1 驱动器驱动输入输出端口示意图

        EN是低电平使能信号;GND是信号地;FR是方向控制信号,高低电平控制正反转;SV是PWM输入端,该 PWM 输入信号的占空比可为 0 到 100%任意值, PWM 频率应在 1KHz-20KHz 之间,频率偏向低则 PWM 精度高;BK是刹车信号,低电平刹车。

        PG是霍尔信号输出端,PG 信号用于提供电机转速脉冲信号,PG 信号为开漏输出,在连接时,在 5V 与 PG加一个6.7KΩ电阻作上拉电阻。电机一个电周期内将在 PG 信号端输出 3 个脉冲。对于两对极的电机旋转一周将产生 6 个脉冲,四对极则为 12 个脉冲。本次使用的电机是5对极的,电机转动时在 PG 端输出的频率与转速关系如下式:

电机转速(RPM)=20 x PG 信号频率 / 电机极对数    ①

【软件设计】

驱动端口初始化

在程序进入while循环之前,需要对驱动EN、FR、BK端口进行端口初始化配置。

/**************************************************************************
函数功能:驱动端口初始化
入口参数:无 
返回  值:无
**************************************************************************/
void QuDong_Init(void)
{

    GPIO_InitTypeDef GPIO_InitStructure;            //定义结构体GPIO_InitStructure
	
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 使能PB端口时钟  
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); // 使能PC端口时钟 	
	
	//EN2---PB15 EN1---PB13
    GPIO_InitStructure.GPIO_Pin =   GPIO_Pin_15 | GPIO_Pin_13;	  //PB15 PB13
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;  	//推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  	//IO口速度
	GPIO_Init(GPIOB, &GPIO_InitStructure);          	//GBIOB初始化 
	GPIO_ResetBits(GPIOB,GPIO_Pin_15 | GPIO_Pin_13);	//置低使能
		
    //FR2---PC3 FE1---PC2
    GPIO_InitStructure.GPIO_Pin =   GPIO_Pin_3|GPIO_Pin_2;	  // PC3 PC2
	GPIO_Init(GPIOC, &GPIO_InitStructure);  //GBIOC初始化 
	GPIO_SetBits(GPIOC,GPIO_Pin_2);			//置高使能
	GPIO_ResetBits(GPIOC,GPIO_Pin_3);		//置低使能

    //BK2---PC1 BK1---PC0
    GPIO_InitStructure.GPIO_Pin =   GPIO_Pin_1|GPIO_Pin_0;	  // PC1 PC0
	GPIO_Init(GPIOC, &GPIO_InitStructure);          //GBIOC初始化 
	GPIO_SetBits(GPIOC,GPIO_Pin_1|GPIO_Pin_0);		//置高不刹车
}

理论公式说明

由于速度PID是根据编码器返回值与设定的脉冲频率相比较,通过增大输出PWM值来使电机驱动电压增大,转速加快,继而使得编码器返回值趋于设定的脉冲频率值,从而实现速度PID。所以第一个要解决的问题是:如何得到速度对应的脉冲频率值呢?苦想许久,我得出这样一个公式:

mc = v / 20.0 * 5.0 * 60.0 / 2.0 / 3.14 / 0.09 * 50.0        ②

    = value * 20.0 / 5.0 / 60.0 * 2.0 * 3.14 * 0.09 / 50.0        ③

其中50是电机减速比,v是设定的速度,mc是速度对应的脉冲值,value是编码器脉冲值。对于这条公式,要结合PG返回的脉冲值公式:

电机转速(RPM)=20 x PG 信号频率 / 电机极对数        ①

所以,我对于公式③这么解释:value*20.0/5.0/50.0得到的是每分钟电机转了多少转,再除以60得到每秒电机转了多少转。实际测得的轮子的半径为0.09m,所以每秒轮子走过的距离是:

value * 20.0 / 5.0 / 60.0 * 2.0 * 3.14 * 0.09 / 50.0

PG反馈信号输入捕获

在实际过程中,PG信号的捕获至关重要,因为该驱动器应该是内置了一个编码器,通过PG信号的捕获,我们才能得到返回的编码器的值,而这个值根据上文所说,关系到能否计算得到速度PID。

得到PG信号的捕获由两种方式:一种是使用外部时钟计数器,一种是输入捕获。经过大量测试和适配,最终敲定采用输入捕获

在程序进入主函数while循环之前,需要对TIM2进行初始化配置。定时器2输入捕获,开启TIM2_CH3、TIM2_CH4通道。

TIM2初始化

/**************************************************************************
函数功能:TIM2初始化
入口参数:无
返回  值:无
**************************************************************************/

void TIM2_Cap_Init(u16 arr,u16 psc)
{	 
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
  NVIC_InitTypeDef NVIC_InitStructure;
	TIM_ICInitTypeDef  TIM_ICInitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);	//使能TIM2时钟
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);  	//使能GPIOB时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);		// 需要使能AFIO时钟
	GPIO_PinRemapConfig(GPIO_FullRemap_TIM2, ENABLE);		//引脚重映射
	
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_10 | GPIO_Pin_11;  //PB10 PB11 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;    //10M时钟速度
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	TIM_DeInit(TIM2);						//重新将Timer设置为缺省值
	TIM_InternalClockConfig(TIM2);			//采用内部时钟给TIM2提供时钟源
	
	//初始化定时器2 TIM2 
	TIM_TimeBaseStructure.TIM_Period = arr; 	//设定计数器自动重装值 
	TIM_TimeBaseStructure.TIM_Prescaler =psc; 	//预分频器   
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; 	//TIM向上计数模式
	//TIM_TimeBaseStructure.TIM_RepetitionCounter=0; //重复溢出次数,设置为0
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
    TIM_ARRPreloadConfig(TIM2, DISABLE);			//禁止ARR预装载缓冲器
	
	//初始化TIM2输入捕获参数
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_3; 	//选择输入端 IC3映射到TI3上
	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;	    //上升沿捕获
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI3上
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;	    //配置输入分频,不分频 
	TIM_ICInitStructure.TIM_ICFilter = 10;			//配置IC3F输入滤波器 滤波
	TIM_ICInit(TIM2, &TIM_ICInitStructure);
	
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_4; 	//选择输入端 IC4映射到TI4上
	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;		//上升沿捕获
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;	//映射到TI4上
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;	 	//配置输入分频,不分频 
	TIM_ICInitStructure.TIM_ICFilter = 10;	        //配置IC4F输入滤波器 滤波
	TIM_ICInit(TIM2, &TIM_ICInitStructure);

	
	//中断分组初始化
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;  	//TIM2中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  	//先占优先级0级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  	    //从优先级1级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 	//IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器 
	
	TIM_ClearFlag(TIM2, TIM_FLAG_Update|TIM_IT_CC3);	//清除溢出中断标志
	TIM_ClearFlag(TIM2, TIM_FLAG_Update|TIM_IT_CC4);	//清除溢出中断标志	
	TIM_ITConfig(TIM2,TIM_IT_Update|TIM_IT_CC3,ENABLE);	//允许更新中断 ,允许CC3IE捕获中断
	TIM_ITConfig(TIM2,TIM_IT_Update|TIM_IT_CC4,ENABLE);	//允许更新中断 ,允许CC4IE捕获中断
	TIM_Cmd(TIM2,ENABLE ); 	//使能定时器2
}

定时器2中断程序

TIM_ValueTypeDef TIM2_CH3Structure;
TIM_ValueTypeDef TIM2_CH4Structure;
TIM_ValueTypeDef TIM2_CH3Structure={0,0,0,0,0,0,0};
TIM_ValueTypeDef TIM2_CH4Structure={0,0,0,0,0,0,0};
u32 Capture3[6];
u32 Capture4[6];

void  TIM2_IRQHandler(void)
{	
	if(TIM_GetITStatus(TIM2,TIM_IT_Update) != RESET)
	{
		TIM2_CH3Structure.Capture_Period++;
		TIM2_CH4Structure.Capture_Period++;
		TIM_ClearITPendingBit(TIM2,TIM_FLAG_Update);
	}
	
	if(TIM_GetITStatus(TIM2,TIM_IT_CC3) != RESET)
	{
		if(TIM2_CH3Structure.Capture_StartFlag==0)
		{
			TIM2_CH3Structure.Capture_Period=0;
			TIM2_CH3Structure.Capture_StartFlag=1;
			TIM2_CH3Structure.Capture_CcrValue=TIM_GetCapture3(TIM2);
		}
		else if(TIM2_CH3Structure.Capture_StartFlag == 1)
		{
			TIM2_CH3Structure.Capture_CcrValue1=TIM_GetCapture3(TIM2);
			TIM2_CH3Structure.Capture_StartFlag=0;
			TIM2_CH3Structure.value=1000000/(TIM2_CH3Structure.Capture_Period*65536+TIM2_CH3Structure.Capture_CcrValue1-TIM2_CH3Structure.Capture_CcrValue);
			Capture3[(TIM2_CH3Structure.INDEX)++]=TIM2_CH3Structure.value;
			if(TIM2_CH3Structure.INDEX==6)
			{
				TIM2_CH3Structure.INDEX=0;
				TIM2_CH3Structure.Capture_FinishFlag=1;
			}
		}
		TIM_ClearITPendingBit(TIM2,TIM_IT_CC3);
	}

	if(TIM_GetITStatus(TIM2,TIM_IT_CC4) != RESET)
	{
		if(TIM2_CH4Structure.Capture_StartFlag==0)
		{
			TIM2_CH4Structure.Capture_Period=0;
			TIM2_CH4Structure.Capture_StartFlag=1;
			TIM2_CH4Structure.Capture_CcrValue=TIM_GetCapture4(TIM2);
		}
		else if(TIM2_CH4Structure.Capture_StartFlag == 1)
		{
			TIM2_CH4Structure.Capture_CcrValue1=TIM_GetCapture4(TIM2);
			TIM2_CH4Structure.Capture_StartFlag=0;
  TIM2_CH4Structure.value=1000000/(TIM2_CH4Structure.Capture_Period*65536+TIM2_CH4Structure.Capture_CcrValue1-TIM2_CH4Structure.Capture_CcrValue);
			Capture4[(TIM2_CH4Structure.INDEX)++]=TIM2_CH4Structure.value;
			if(TIM2_CH4Structure.INDEX==6)
			{
				TIM2_CH4Structure.INDEX=0;
				TIM2_CH4Structure.Capture_FinishFlag=1;
			}
		}
		TIM_ClearITPendingBit(TIM2,TIM_IT_CC4);
	}	
}

PWM引脚初始化

在程序进入while循环之前,需要对连接SV端口的PWM引脚进行引脚初始化,PWM频率最高20K。

void MiniBalance_PWM_Init(u16 arr,u16 psc)
{
	GPIO_InitTypeDef GPIO_InitStructure;             //定义结构体GPIO_InitStructure
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;  //定义结构TIM_TimeBaseStructure
	TIM_OCInitTypeDef  TIM_OCInitStructure;          //定义结构TIM_OCInitStructure
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);  //使能定时器4时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);//使能PB端口时钟
	GPIO_InitStructure.GPIO_Pin =    GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9;  
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   //IO口速度
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;     //复用推挽模式输出
	GPIO_Init(GPIOB, &GPIO_InitStructure);              //GBIOB初始化  
	
	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	 
	TIM_TimeBaseStructure.TIM_Prescaler = psc; //设置用来作为TIMx时钟频率除数的预分频值  不分频
	TIM_TimeBaseStructure.TIM_ClockDivision = 0;  //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);//根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位

	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;   //PWMTIM脉冲宽度调制模式1
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;       //设置TIM输出比较极性为高
	TIM_OCInitStructure.TIM_OutputState=	TIM_OutputState_Enable;   //比较输出使能

	TIM_OC1Init(TIM4, &TIM_OCInitStructure);  //根据TIM_OCInitStructure中指定的参数初始化外设TIM4
	TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);	    //使能预装载寄存器
	TIM_OC2Init(TIM4, &TIM_OCInitStructure);  //根据TIM_OCInitStructure中指定的参数初始化外设TIM4
	TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable);	    //使能预装载寄存器
	TIM_OC3Init(TIM4, &TIM_OCInitStructure);  //根据TIM_OCInitStructure中指定的参数初始化外设TIM4
	TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);	    //使能预装载寄存器
	TIM_OC4Init(TIM4, &TIM_OCInitStructure);  //根据TIM_OCInitStructure中指定的参数初始化外设TIM4
	TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable);	    //使能预装载寄存器
	TIM_ARRPreloadConfig(TIM4, ENABLE);  //使能自动装载允许位 
	TIM_Cmd(TIM4, ENABLE);   //启动定时器TIM4

}

速度PID模型和位置PID模型

/*********************
入口参数:编码器脉冲频率
返 回 值:该脉冲对应的速度值
**********************/
float get_v(short value)
{
	float v=0;
	v=value*20.0/5.0/60.0*2.0*3.14*0.09/50.0;
	return v;
}

/*********************
入口参数:速度值
返 回 值:该速度对应的脉冲频率
**********************/
float get_mc(float v)
{
	float mc=0;
	mc=v/20.0*5.0*60.0/2.0/3.14/0.09*50.0;
	return mc;
}

/**************************************************************************
函数功能:速度PI
入口参数:编码器测量值,目标速度
返 回 值:电机Pwm
根据增量式离散PID公式   pwm+=Kp[e(k)-e(k-1)]+Ki*e(k)+Kd[e(k)-2e(k-1)+e(k-2)]
e(k)代表本次偏差 
e(k-1)代表上一次的偏差  以此类推 
Pwm代表增量输出
**************************************************************************/

int Velocity_PI(int Encoder, int v_Target)
{
	static int Bias, Pwm, Last_bias, Lsater_bias;
	Bias = Encoder - v_Target;												 //计算偏差
	Pwm += Velocity_KP * (Bias - Last_bias)+ Velocity_KI * Bias;  //增量式PI控制器
//	if (Pwm > 7200)
//		Pwm = 7200;
//	if (Pwm < -7200)
//		Pwm = -7200;
	Lsater_bias = Last_bias; //保存上上一次偏差
	Last_bias = Bias;		 //保存上一次偏差
	return Pwm;				 //增量输出
}

/**************************************************************************
函数功能:位置PID
入口参数:已行驶路程,目标路程
返 回 值:电机Pwm
根据增量式离散PID公式   pwm+=Kp[e(k)-e(k-1)]+Ki*e(k)+Kd[e(k)-2e(k-1)+e(k-2)]
e(k)代表本次偏差 
e(k-1)代表上一次的偏差  以此类推 
Pwm代表增量输出
**************************************************************************/
int Position_PID(float x_now, float x_Target)
{
	static float Bias, Pwm, Integral_bias, Last_bias;
	Bias = x_now - x_Target;												 //计算偏差
	Integral_bias += Bias;
	Pwm = Position_KP * Bias + Position_KI * Integral_bias + Position_KD * (Bias - Last_bias); 
//	if (Pwm > 7200)
//		Pwm = 7200;
//	if (Pwm < -7200)
//		Pwm = -7200;	
	Last_bias = Bias;		 //保存上一次偏差
	return Pwm;				 //增量输出
}

主程序

int main(void)
  {  
		delay_init();	    	        //延时函数初始化	
		MY_NVIC_PriorityGroupConfig(2);	//设置中断分组
		JTAG_Set(SWD_ENABLE);           //打开SWD接口 可以利用主板的SWD接口调试
		QuDong_Init();             		  //初始化驱动器驱动端口	
		uart_init(115200);				  //串口1初始化 
		CAN_Config();					  //初始化can,在中断接收CAN数据包*/
		MiniBalance_PWM_Init(3599,0);	  //PWM频率最高 20K
		TIM2_Cap_Init(0XFFFF, 72-1);	  //定时器2 输入捕获 开启通道 TIM2_CH3 TIM2_CH4 
		TIM6_Int_Init(100 - 1, 7200 - 1); //定时器6初始化 10ms中断	 
		
		printf("开始!\r\n");
		
		v_mc = (int)get_mc(0.15); //输入速度值 转化位对应的脉冲频率
		L=6.0;	
		
		/*** 硬件适配 ***/
		TIM_SetCompare1(TIM4,0);  
		TIM_SetCompare2(TIM4,0);
		delay_ms(250); 
	
        while(1)
		{			
			TIM_SetCompare1(TIM4,sv_L);
			TIM_SetCompare2(TIM4,sv_R*1.013);
		 		
			printf("sv_L = %d\r\n",sv_L);	
			printf("sv_R = %d\r\n",sv_R);	
			
			if(TIM2_CH3Structure.Capture_FinishFlag == 1)
			{
				printf("Frequency_L = %d Hz    ",TIM2_CH3Structure.value);
				printf("V_L = %f M/s    ",get_v(TIM2_CH3Structure.value));
				printf("X_L = %f M \n",X_L);
				TIM2_CH3Structure.Capture_FinishFlag = 0;
			}
			if(TIM2_CH4Structure.Capture_FinishFlag == 1)
			{
				printf("Frequency_R = %d Hz    ",TIM2_CH4Structure.value);
				printf("V_R = %f M/s    ",get_v(TIM2_CH4Structure.value));
				printf("X_R = %f M \n",X_R);
				TIM2_CH4Structure.Capture_FinishFlag = 0;
			}
			else
			{
				printf("Wave is not exist or Cature is incomplete.\n");
			}
					
			delay_ms(1000);
			
			}	
					
		}
	}
	void TIM6_IRQHandler(void) // TIM6 10ms中断 
{

	if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) // 检查指定的TIM6中断发生与否
	{	
		X_L += get_v(TIM2_CH3Structure.value)*0.01;
		X_R += get_v(TIM2_CH4Structure.value)*0.01;
		
		if(TIM2_CH3Structure.value <= v_mc && X_L <= (L-v_mc*0.008))
		{sv_L=(int)(float)(Velocity_PI(TIM2_CH3Structure.value,v_mc)/-20000.0*3600.0);}
		
		if(TIM2_CH4Structure.value <= v_mc && X_R <= (L-v_mc*0.008))		
		{sv_R=(int)(float)(Velocity_PI(TIM2_CH4Structure.value,v_mc)/-20000.0*3600.0);}
		
		if(X_L>=(L-v_mc*0.008))
		{sv_L=(int)(float)(Position_PID(X_L,L)/-20000.0*3600.0);}
		
		if(X_R>=(L-v_mc*0.008))
		{sv_R=(int)(float)(Position_PID(X_R,L)/-20000.0*3600.0);}		
	}
	
	    TIM_ClearITPendingBit(TIM6, TIM_IT_Update); //清除TIM6的中断待处理位
}

其中sv_Rsv_L的计算是对应输出的Pwm占空比的计算。

到此,电机PWM驱动模块的一些基本思路和代码已经罗列出来,其实很多程序雏形都是扒拉大佬的代码,自己理解之后修修改改的,我们都是站在巨人的肩膀上去看世界。

在下菜鸟,大家的鼓励是我继续创作的动力,如果觉得写的不错,欢迎关注,点赞,收藏,转发,谢谢!

【STM32F103RCT6】电机PWM驱动模块思路与代码 (https://mushiming.com/)  第2张

THE END

发表回复