PID算法实现直流减速电机控速

由于资源有限,人也比较懒,目前只实现了一个电机的控速,想着需要的时候,薅过去就能直接用,因为设想中是要控制俩编码器电机的,所以代码中会看到有些引脚配置了,但却没有在接线图中看到,直接忽略只看用到了的就好。

目录

大致框架

硬件资源

硬件连线

stm32资源使用情况

PID控速

代码实现


大致框架

                        

硬件资源

STM32f103c8t6最小系统板;直流减速电机;电机驱动模块L298N;12V电源;

硬件连线

元件引脚示意(实物图都网上找的):

连线图:

stm32资源使用情况

TIM1:用于定时,同时配置了中断,每20ms进入一次中断函数

TIM2:利用c1和c2两个引脚捕获电机A、B相脉冲,结合定时器1可实现PID控速

TIM4:c1~c4随便选一个引脚输出PWM直接控制电机

PID控速

PID我也是第一次用,用之前在网上找了很多参考文章,在B站也看了有关视频,得出结论:PID有位置式PID和增量式PID,因为之后要实现的小车并不能获取当前位置和目的地的距离,所以这里用增量式PID,由当前速度经PID算法获得合适的PWM。期间主要参考了这两篇文章:PID理解 、代码实现 

下面这段代码在代码里面也有,这里单独截出来:

            s.curr_val=cnt2*50/4-cnt1*50/4;//当前电机转速(周期/s)
            s.err=s.expect_val-s.curr_val;//本次误差
            p_out=s.P*(s.err-s.err_pre);
            i_out=s.I*s.err;
            d_out=s.D*(s.err-2*s.err_pre+s.err_pre_pre);//参量D这里其实没有用到,一直是0
            pwm+=p_out+i_out+d_out;
            s.err_pre_pre=s.err_pre;//保存上上次误差
            s.err_pre=s.err;//保存上次误差

当前速度(单位:周期/s)计算思路:

图1:

                ​​​​​​​         

图2:

 

电机转动时,结合编码器会在A、B相分别输出脉冲(如上图1),如果时间间隔足够短并且周期不发生突变的话,我们可以认为A、B两相都是周期固定的波形,TIM2的c1和c2两脚获取A、B相的波形,在不用知道电机转动方向的前提下可以只使用一相,假设c1脚每捕获到一个上升沿,CNT就加一(图2可以看到编码器接口的时钟来自TIMx_CH1和TIMx_CH2),那么在固定的时间间隔内(由TIM1计时提供),可以得出该段时间内电机编码器输出了多少个上升沿(周期),即电机转动速度。具体计算在代码里面有。

代码实现

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "usart.h"

void rcc_config(void);
void direction_GPIO_init(void);
void timer_catch_init(void);//TIM2/3
void timer_PWM_init(void);//TIM4
void timer_init_2(void);//TIM1:计时
void exti_init(void);//EXTI2/3(还没有实际用过,忽略就好)
void NVIC_config(void);
void forward(u16 speed);
void right(u16 speed);


typedef struct
{
	float P;
	float I;
	float D;
	float expect_val;//期望值(每秒多少个脉冲)
	float curr_val;//当前值(每秒多少个脉冲)
	float err_pre;//上一次的误差
	float err_pre_pre;//上上次的误差
	float err;//当前值和期望值误差
}PID;

PID s;
float pwm=0;

//PID结构体初始化,P、I、D三个值都要根据实际情况慢慢调
void PID_Init(void)
{
	s.P=1;
	s.I=0.2;//0.2
	s.D=0;
	s.err_pre=0;
	s.err_pre_pre=0;
	s.err=0;
	s.curr_val=0;
	s.expect_val=800;
}

int main() 
{
	PID_Init();
	
//	USART_config();
	
	rcc_config();
	direction_GPIO_init();
	timer_catch_init();
	timer_PWM_init();
	timer_init_2();
//	exti_init();
	NVIC_config();
	while(1)
	{
		if(pwm>=1000)
			pwm=999;
		else if(pwm<=0)
			pwm=0;
		forward((u16)pwm);
	}
}

void rcc_config(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2|RCC_APB1Periph_TIM3,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_TIM1,ENABLE);
}

void direction_GPIO_init(void)
{
	GPIO_InitTypeDef gpio_structure;
	gpio_structure.GPIO_Mode=GPIO_Mode_Out_PP;
	gpio_structure.GPIO_Pin=GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_10|GPIO_Pin_15;
	gpio_structure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&gpio_structure);
	GPIO_ResetBits(GPIOA,GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_10|GPIO_Pin_15);
}

void timer_catch_init(void)
{	
	/*---GPIO口配置---*/
	//TIM2_CH1--PA0 TIM2_CH2--PA1 TIM3_CH1--PA6 TIM3_CH2--PA7
	GPIO_InitTypeDef gpio_structure;
	gpio_structure.GPIO_Mode=GPIO_Mode_IN_FLOATING;//浮空输入
	gpio_structure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_6|GPIO_Pin_7;
	gpio_structure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&gpio_structure);
	
	/*---TIM配置---*/
	//-----时基单元初始化------
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
	TIM_TimeBaseStruct.TIM_Prescaler=1-1;//预分频器
 	TIM_TimeBaseStruct.TIM_Period=65536-1;//自动重装载寄存器
	TIM_TimeBaseStruct.TIM_ClockDivision=0;
	TIM_TimeBaseStruct.TIM_CounterMode=TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStruct);
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStruct);	
		
	//编码器模式(同时也选择了时钟来源)
	TIM_EncoderInterfaceConfig(TIM2,TIM_EncoderMode_TI12,
							TIM_ICPolarity_BothEdge,
							TIM_ICPolarity_BothEdge);
	TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,
							TIM_ICPolarity_BothEdge,
							TIM_ICPolarity_BothEdge);
							
	//-----输入捕获-----
	TIM_ICInitTypeDef TIM_ICInitStruct;
	TIM_ICInitStruct.TIM_Channel = TIM_Channel_1|TIM_Channel_2;
	TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_BothEdge;
	TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;
	TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
	TIM_ICInitStruct.TIM_ICFilter = 0x00;
	//TIM_ICStructInit(&TIM_ICInitStruct);
	TIM_ICInit(TIM2,&TIM_ICInitStruct);
	TIM_ICInit(TIM3,&TIM_ICInitStruct);
	
	TIM_Cmd(TIM2,ENABLE);//使能定时器2
	TIM_Cmd(TIM3,ENABLE);//使能定时器3
}

void timer_PWM_init(void)
{	
	GPIO_InitTypeDef gpio_structure;
	/*---GPIO配置---*/
	gpio_structure.GPIO_Mode=GPIO_Mode_AF_PP;//推挽输出
	gpio_structure.GPIO_Pin=GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9;
	gpio_structure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&gpio_structure);
	//初始为低电平
	GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9);
	
	TIM_InternalClockConfig(TIM4);//选择内部时钟(1、时钟选择)
	
	//-----时基单元初始化------
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
	
	TIM_TimeBaseStruct.TIM_Prescaler=360-1;//预分频器 5ms
 	TIM_TimeBaseStruct.TIM_Period=1000-1;
	TIM_TimeBaseStruct.TIM_ClockDivision=0;
	TIM_TimeBaseStruct.TIM_CounterMode=TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM4,&TIM_TimeBaseStruct);
	
	//-----输出比较-----
	TIM_OCInitTypeDef TIM_OCStruct;

	TIM_OCStruct.TIM_OCMode=TIM_OCMode_PWM1;
	TIM_OCStruct.TIM_OCPolarity=TIM_OCPolarity_High;
	TIM_OCStruct.TIM_OutputState=TIM_OutputState_Enable;
	TIM_OCStruct.TIM_Pulse=0;//TIM_Pulse/(TIM_Period+1)=占空比
	//初始化外设TIM2通道
	TIM_OC1Init(TIM4,&TIM_OCStruct);
	TIM_OC2Init(TIM4,&TIM_OCStruct);
	TIM_OC3Init(TIM4,&TIM_OCStruct);
	TIM_OC4Init(TIM4,&TIM_OCStruct);
	
	TIM_Cmd(TIM4,ENABLE);//使能定时器4
}

void timer_init_2(void)
{
	TIM_InternalClockConfig(TIM1);//选择内部时钟(1、时钟选择)
	
	//-----时基单元初始化------
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
	
	TIM_TimeBaseStruct.TIM_Prescaler=1000-1;//预分频器
 	TIM_TimeBaseStruct.TIM_Period=1440-1;//100ms
	TIM_TimeBaseStruct.TIM_ClockDivision=TIM_CKD_DIV1;
	TIM_TimeBaseStruct.TIM_CounterMode=TIM_CounterMode_Up;
	TIM_TimeBaseStruct.TIM_RepetitionCounter=0;
	TIM_TimeBaseInit(TIM1,&TIM_TimeBaseStruct);
	TIM_ClearFlag(TIM1,TIM_FLAG_Update);
	TIM_ITConfig(TIM1,TIM_IT_Update,ENABLE);
	TIM_Cmd(TIM1,ENABLE);
}

void exti_init()
{
	//2、配置GPIO
	//--PA2、PA3--
	GPIO_InitTypeDef gpio_struct;
	gpio_struct.GPIO_Mode=GPIO_Mode_IN_FLOATING;//查看手册可得
	gpio_struct.GPIO_Pin=GPIO_Pin_3|GPIO_Pin_2;
	gpio_struct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&gpio_struct);
	//3、配置AFIO
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource2);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource3);
	//4、配置EXTI
	EXTI_InitTypeDef exti_struct;
	exti_struct.EXTI_Line=EXTI_Line2;
	exti_struct.EXTI_LineCmd=ENABLE;
	exti_struct.EXTI_Mode=EXTI_Mode_Interrupt;//中断模式
	exti_struct.EXTI_Trigger=EXTI_Trigger_Rising;//上升沿触发
	EXTI_Init(&exti_struct);
	exti_struct.EXTI_Line=EXTI_Line3;
	EXTI_Init(&exti_struct);
}

void NVIC_config(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//分组方式
	NVIC_InitTypeDef nvic_struct;
	nvic_struct.NVIC_IRQChannel=EXTI2_IRQn;
	nvic_struct.NVIC_IRQChannelCmd=ENABLE;
	nvic_struct.NVIC_IRQChannelPreemptionPriority=1;
	nvic_struct.NVIC_IRQChannelSubPriority=1;
	NVIC_Init(&nvic_struct);
	
	nvic_struct.NVIC_IRQChannel=EXTI3_IRQn;
	nvic_struct.NVIC_IRQChannelCmd=ENABLE;
	nvic_struct.NVIC_IRQChannelPreemptionPriority=1;
	nvic_struct.NVIC_IRQChannelSubPriority=1;
	NVIC_Init(&nvic_struct);

	nvic_struct.NVIC_IRQChannel=TIM1_UP_IRQn;
	nvic_struct.NVIC_IRQChannelCmd=ENABLE;
	nvic_struct.NVIC_IRQChannelPreemptionPriority=1;
	nvic_struct.NVIC_IRQChannelSubPriority=1;
	NVIC_Init(&nvic_struct);
}

void forward(u16 speed)
{
	/*****/
	TIM_SetCompare1(TIM4,speed);
	GPIO_SetBits(GPIOA,GPIO_Pin_10);
	GPIO_ResetBits(GPIOA,GPIO_Pin_11);
}

void right(u16 speed)
{
	/*****/
}

/*************中断函数***********************/
//每20ms进一次中断
float cnt1=-1,cnt2=-1;
u8 flag=1;
float p_out=0,i_out=0,d_out=0;
u8 times=0;
void TIM1_UP_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM1,TIM_IT_Update)==SET)
	{
		if(flag==1)
		{
			flag=2;
			cnt1=TIM_GetCounter(TIM2);
		}
		else
		{
			flag=1;
			cnt2=TIM_GetCounter(TIM2);
		}
		if(flag==1 && cnt1!=-1 && cnt2!=-1)
		{
			if(cnt2<cnt1)
				cnt2=65535+cnt2;
			s.curr_val=cnt2*50/4-cnt1*50/4;//当前电机转速(周期/s)
			s.err=s.expect_val-s.curr_val;//本次误差
			p_out=s.P*(s.err-s.err_pre);
			i_out=s.I*s.err;
			d_out=s.D*(s.err-2*s.err_pre+s.err_pre_pre);//参量D这里其实没有用到,一直是0
			pwm+=p_out+i_out+d_out;
			s.err_pre_pre=s.err_pre;//保存上上次误差
			s.err_pre=s.err;//保存上次误差
		}
		TIM_ClearITPendingBit(TIM1,TIM_IT_Update);
	}
}

期间踩过的坑:

1、在测试阶段,发现L298N不能成功驱动电机(在电路连接正确和电源条件满足的情况),后来发现,L298N的5V和GND引脚需要引出来给别的器件供电才能正常驱动电机(到底为啥也不知道)

2、PID调参:看了很多相关文章都说PID三个参数的调试顺序为P、I、D,后来发现这应该只是针对位置式PID,仔细看看,会发现增量式PID的I项和位置式PID的P项要乘的东西是一样的,所以在增量式PID中,可以先尝试调I项。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值