通过STM32单片机计算并控制小车速度,通过控制速度的思想扩展到控制其它变化量

说明:如果有哪里说错了或者说得不好的话还请大家指出来,及时纠正错误,或者哪里有更好的解决方法也可以提出来,我们一起学习交流。

目录

一、编码电机

二、单片机相关定时器的作用以及配置

1、TIM2的配置

2、TIM3的配置

3、TIM4的配置

4、PID函数

5、读定时器的计数值

6、calc_motor_rotate_speed()函数

三、2020电赛C题

四、扩展

五、效果展示


一、编码电机

如果想要控制小车的速度就需要得到小车的速度,想要得到小车的速度就需要用到编码电机,下面先来大概看一下编码电机。

大概就是这个样子了,这里就不介绍它的原理了,可以去查阅相关资料。

二、单片机相关定时器的作用以及配置

本次用到了3个定时器,分别是TIM2、TIM3、TIM4,定时器2用于定时,也就是定时10ms产生中断,然后到中断服务函数中去计算速度,定时器3用于输出PWM波来控制小车的行走,定时器4用来配置成编码器模式,用来读取编码器的数据。

1、TIM2的配置

#include "encoderTim.h"

/*********************
通用定时器6初始化
arr:自动重装值。
psc:时钟预分频数
定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
Ft=定时器工作频率,单位:Mhz
************************/
void TIM2_Int_Init(u16 arr,u16 psc)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);  ///使能TIM时钟
	
    TIM_TimeBaseInitStructure.TIM_Period = arr;   //自动重装载值
	TIM_TimeBaseInitStructure.TIM_Prescaler=psc;  //定时器分频
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; 
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);//初始化TIM7
	
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //允许定时器6更新中断 定时器溢出中断
	TIM_Cmd(TIM2,ENABLE); //使能定时器6  开始工作
	
	NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn; //定时器6中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01; //抢占优先级1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03; //子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;     //中断使能
	NVIC_Init(&NVIC_InitStructure);
}

TIM2中断服务函数

void TIM2_IRQHandler(void)  //
{
	
	if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET){ //检测是否发送中断
	
		calc_motor_rotate_speed();
	}
	TIM_ClearITPendingBit(TIM2, TIM_IT_Update);  //清除中断
}

TIM2用于定时10ms,然后进入中断服务函数,执行calc_motor_rotate_speed()函数,对于这个函数的作用,后面再说明。

2、TIM3的配置

#include "iopwm.h"

void TIM3_PWM_Init(u16 arr,u16 psc)
{
    GPIO_InitTypeDef GPIO_InitStruct;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	TIM_OCInitTypeDef TIM_OCInitStruct;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);	 //使能GPIOA外设
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);  //使能定时器3时钟

	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;    //输出
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_7|GPIO_Pin_6;  // GPIOA.7;GPIOA.6
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;     //TIM3_CH2  TIM3_CH1 
	GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIO
	
	//定时器TIM3初始化
	TIM_TimeBaseInitStruct.TIM_Period=arr;//设置在下一个更新事件装入活动的自动重装载寄存器周期的值
	TIM_TimeBaseInitStruct.TIM_Prescaler=psc;//设置用来作为TIMx时钟频率除数的预分频值 
	TIM_TimeBaseInitStruct.TIM_ClockDivision=0;//设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;//TIM向上计数模式
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);//根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位

	//初始化TIM3 Channel 2 PWM模式	
	TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM2;//选择定时器模式:TIM脉冲宽度调制模式2
	TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable;//比较输出使能
	TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_Low;//输出极性:TIM输出比较极性

	TIM_OC2Init(TIM3,&TIM_OCInitStruct);//根据T指定的参数初始化外设TIM3 OC2
	TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable);使能TIM3在CCR2上的预装载寄存器

	TIM_OC1Init(TIM3,&TIM_OCInitStruct);//根据T指定的参数初始化外设TIM3 OC1
	TIM_OC1PreloadConfig(TIM3,TIM_OCPreload_Enable);使能TIM3在CCR1上的预装载寄存器

	TIM_Cmd(TIM3,ENABLE);//使能定时器
}

TIM3主要就是输出PWM信号来控制电机

3、TIM4的配置

#include "encoder.h"

//TIM4 通道1通道2 正交编码器
void TIM4_ENCODER_Init(void)                      
{
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStruct;
	TIM_ICInitTypeDef TIM_ICInitStruct;   	//输入捕获

	//PB6 ch1  A,PB7 ch2 
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);//使能TIM4时钟	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//使能GPIOA时钟
	
	GPIO_StructInit(&GPIO_InitStructure);//将GPIO_InitStruct中的参数按缺省值输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;         
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//PA6 PA7浮空输入	
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);    

	/*时基初始化*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);   /*使能定时器时钟 APB1*/
	TIM_DeInit(TIM4);                          
	//TIM_TimeBaseStructInit(&TIM_TimeBaseStruct);    
	TIM_TimeBaseStruct.TIM_Prescaler = ENCODER_TIM_PSC;       /*预分频 0*/        
	TIM_TimeBaseStruct.TIM_Period = ENCODER_TIM_PERIOD;       /*周期(重装载值)65535*/
	TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;      
	TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;  /*连续向上计数模式*/  
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStruct); 

	/*编码器模式配置:同时捕获通道1与通道2(即4倍频),极性均为Rising*/
	TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12,TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); 
	TIM_ICStructInit(&TIM_ICInitStruct);        
	TIM_ICInitStruct.TIM_ICFilter = 0;   /*输入通道的滤波参数*/
	TIM_ICInit(TIM4, &TIM_ICInitStruct); /*输入通道初始化*/
	TIM_SetCounter(TIM4, CNT_INIT);      /*CNT设初值  0 */
	TIM_ClearFlag(TIM4,TIM_IT_Update);   /*中断标志清0*/
	TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE); /*中断使能*/
	TIM_Cmd(TIM4,ENABLE);                /*使能CR寄存器*/
}

//定时器4中断服务函数
void TIM4_IRQHandler(void)
{ 		    		  			    
	if(TIM4->SR&0X0001){}//溢出中断			   				     	    	
			   
	TIM4->SR&=~(1<<0);//清除中断标志位 	    
}

TIM4主要就是配置成编码器模式,用来计数编码器脉冲个数

4、PID函数

#include "PIDEncoder.h"

#define  P_DATA      1.5              //P参数
#define  I_DATA      0.1              //I参数
#define  D_DATA      -0.4             //D参数

typedef struct 
{
    double   Proportion;              //比例常数 Proportional Const
    double   Integral;                //积分常数 Integral Const
    double   Derivative;              //微分常数 Derivative Const
    int      LastError;               //Error[-1]
    int      PrevError;               //Error[-2]
}PID;

static PID sPID;
static PID *sptr = &sPID;

void PID_Init(void) 
{
    sptr->LastError=0;            //Error[-1]
    sptr->PrevError=0;            //Error[-2]
    sptr->Proportion=P_DATA;      //比例常数
    sptr->Integral=I_DATA;        //积分常数
    sptr->Derivative=D_DATA;      //微分常数
}
/**************
入口参数:NextPoint:当前输出值 SetPoint: 设定值
返回值:PID调整输出
***************/
int PID_Calc(int NextPoint,int SetPoint) 
{
  int iError,Outpid;                                   //当前误差
  iError=SetPoint-NextPoint;                           //增量计算
  Outpid=(sptr->Proportion * iError)                   //E[k]项
              -(sptr->Integral * sptr->LastError)      //E[k-1]项
              +(sptr->Derivative * sptr->PrevError);   //E[k-2]项
              
  sptr->PrevError=sptr->LastError;                     //存储误差,用于下次计算
  sptr->LastError=iError;
  return Outpid;                                      //返回增量值
}

PID函数就是用来不断调整的,使当前值逐渐接近目标值

5、读定时器的计数值

// 读取定时器计数值
int read_encoder(void)
{
	int encoderNum = 0;
	encoderNum = (int)((int16_t)(TIM4->CNT)); /*CNT为uint32, 转为int16*/
	TIM_SetCounter(TIM4, CNT_INIT);/*CNT设初值*/

	return encoderNum;
}

6、calc_motor_rotate_speed()函数

calc_motor_rotate_speed()函数在TIM2的中断服务函数里面,10ms时间到了就执行这个函数

//计算电机转速(被另一个定时器100ms调用1次)
void calc_motor_rotate_speed()
{
	/*读取编码器的值,正负代表旋转方向*/
	encoderNum = read_encoder();
	/* 转速(1秒钟转多少圈)=单位时间内的计数值/总分辨率*时间系数 */
//	rotateSpeed = (int)encoderNum/TOTAL_RESOLUTION*10;
//	Speed2 = (rotateSpeed * 6.0 * 3.14)/1.0;  //速度   1秒钟转的圈数 X 一圈的距离(轮子周长)/1s  

	Speed = ((encoderNum/(48.4*4)) * 18.84)/0.1;//((总的脉冲数/电机一圈的脉冲*减数比*4倍频)*轮子周长)/10ms 
	para_L = PID_Calc(Speed,SetPoint);	//,计数得到增量式PID的增量数值 
	
	if((para_L < -3) || (para_L > 3)){ // 不做 PID 调整,避免误差较小时频繁调节引起震荡。             
		Moto_Left += para_L;  
	}   
	if(Moto_Left>19000)Moto_Left=19000;//限幅
    if(Moto_Left<-19000)Moto_Left=-19000;//限幅
	
	TIM_SetCompare1(TIM3, Moto_Left);
	TIM_SetCompare2(TIM3, 0);
	
	OLED_ShowString(0,1,"hope");
	OLED_ShowNum(32,1,SetPoint,8,16);
	OLED_ShowString(97,1,"cm/s");
	
	OLED_ShowString(0,4,"now:");
	OLED_ShowNum(32,4,Speed,8,16);
	OLED_ShowString(97,4,"cm/s");
}

        函数中第一步获取编码器的值第二步通过轮子的周长,减数比等一些参数计算出小车当前的实际速度,然后将计算出的实际速度和目标速度作为PID_Calc(Speed,SetPoint)函数的参数进行PID调整,然后得到相应的调整值,再将限幅后的值加载到电机上面,这样就进行了一次调整,然后在OLED屏幕上面进行显示目标速度和实际速度,可以看出调整的过程。

三、2020电赛C题

先来看一下2020年TI被电子设计大赛的C题

         这里要求了小车到达终点的时间,而且木板的角度还要变化,可以发现小车行驶的路程始终是没有改变的,所以这里就可以使用编码电机得到小车实际的速度,然后还知道小车的路程,在规定的时间内到达终点,这样就可以通过速度 = 路程 /速度来设置相应的目标速度了,不管木板的角度如何变化,小车都会自己调整速度,在规定的时间内到达终点,从而实现了小车速度的闭环控制。这里还可以加一个陀螺仪进行精确的调整。

        当然了不用编码电机也可以实现,把每一个角度的对应要输出多少PWM写死就好了,但是这样会很麻烦。

四、扩展

        这个例程是利用编码器输出的脉冲计算得到电机的速度,设置一个目标速度,这样通过PID的调整就可以实现实际速度逐渐接近目标速度,从而实现速度的控制,

        有了这个思想就可以扩展到其他方面了,比如对温度的控制,有些对温度要求很高的场所,温度要一直恒温,不能太高或者太低,如果是人来进行调整的话那就有点麻烦了,这里也可以通过这个方法来实现,只需要将速度改为温度即可,即得到当前的实际温度就可以了。当然还有控制水位高度那些都是类似的了。

这样是不是就实现了一个简单的恒温控制系统,或者水位控制系统

五、效果展示

视频中的PID参数还没有调好,只能说大概是这个现象,作为参考。

STM32单片机实现对小车速度的控制

调得不好,调得不好,调得不好!!!

Guess you like

Origin blog.csdn.net/qq_48458789/article/details/122246652