STM32爬坡小车PID控制(2020年电赛C题)

        由于该题我们没有去参加比赛,只是做着好玩,所以就没有按照竞赛题目的要求用msp430作为主控芯片,而是用了手头上正好有的一块stm32最小系统板,下面是设计思路:

一、硬件电路:

1、电机驱动的选择
我们采用的是L298N电机驱动模块。L298N输出电压最高可达50V,由于我们选用了三节4v的电池供电,正好可以作为L298的12v输入,并且可以输出5v电压给stm32及其他模块供电;此外,L298的四个使能端也可以作为PWM输出口,来控制电机的转速。

2、传感器的选择
由于题目要求我们必须循迹,我们直接用了一个三红外寻迹模块(我自己取的名哈,只要是红外寻迹的就行,就是下面那玩意),上面那三个黄色的旋钮可以调节灵敏度,当他检测到黑线时,对应的GPIO口就会输出高电平,按照题目的意思就是只有在需要转向的时候才会检测到黑线,当它需要转向了,输出的高电平就会反馈到stm32中,再通过调节输出pwm的那几个GPIO口的占空比来控制四个电机的转速,从而实现转向的功能。并且当三个红外全部检测到黑线,即全部输出高电平时,使四个电机反转,注意这个反转的时间很小,占空比也很小,过了这段时间后马上将占空比调节为0。

3、车轮及车胎的选择
我认为,这个题目中,车轮及车胎的选择至关重要,一定要选择摩擦力足够大的车胎,毕竟想要获得高分,你的车就必须要能爬上30度的坡,并且想要爬上30度的坡,除了摩擦力要足够大之外,你的12v的电池肯定是不够的,因为我们的车只能爬10度的坡,哈哈哈~。

二、实物图

丑了吧唧的。。。。

三、PID控制

这个PID控制是我后来学了PID之后才加上去的,所以实物图上面并没有用到编码器电机来测速,这里PID控速还是挺重要的,因为随着角度的提升,小车的速度会越来越慢,以至于后面会爬不上去,但是如果初始占空比给大了,速度又太快,可能导致红外模块刚检测到黑线还没反应过来,小车就已经冲过去了,那还怎么玩是吧。但是有了PID控速的话,只要给了他一个初始速度,他就能自己通过调节占空比来大概的保持这个速度,当编码器测得的实际速度过小时,通过PID算法(增量式用于测速较好),将得到的一个占空比更大的PWM波返回,达到增加速度的目的。PID部分代码如下(最好是将PID部分的代码做成一个结构体,放在另一个.C文件中):

uint16_t pwm_duty;   //增量式PID中PWM的增量
uint16_t pwm_out;     //最后算出需要输出的PWM
uint8_t speed_now = 100;  //实际速度
uint8_t speed_want = 80;     //期望速度(初始速度)
uint8_t P = 50,I = 20,D = 2;
float error = 0.0;            //本次差值
float error_pre = 0.0;        //上次差值
float error_pre_pre = 0.0;    //上上次差值
float error_add = 0;          //差值的累加

//假设当前速度为100r/s(实际速度可由编码器采集获取)

uint16_t PID_PLACE(uint8_t speed_want) //位置式
{
    error = speed_want - speed_now;
    error_add += error;
    pwm_out = (int)(P*error + I*error_add + D*(error-error_pre));
    error_pre = error;
    return pwm_out;
}


uint16_t PID_ADD(uint8_t speed_want) //增量式
{
    error = speed_want - speed_now;
    pwm_duty = (int)(P*(error - error_pre) + I*error + D*(error-2*(error_pre)+error_pre_pre));
    pwm_out += pwm_duty;
    error_pre = error;
    error_pre_pre = error_pre;
    return pwm_out;
}

四、软件部分代码

1、我这里用的是TIM3的四个通道来输出四个PWM波,代码类似,只需要对照引脚定义图看是哪个引脚就行了。这里只列举了TIM3通道1的初始化代码:

void TIM3_CH1_PWM_Init(uint16_t per,uint16_t psc)  //自动重装值和预分频器的值
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用功能输出
    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStructure);
    
   
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStructure.TIM_Period = per;
    TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0x00;
    TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
   
   
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 0; //设置待装入捕获比较寄存器的脉冲值(CCR的值
    TIM_OC1Init(TIM3,&TIM_OCInitStructure);
    
    //开启定时器
    TIM_Cmd(TIM3,ENABLE);
    
    TIM_OC1PreloadConfig(TIM3,TIM_OCPreload_Enable); //使能TIM3的通道2
    
    TIM_ARRPreloadConfig(TIM3,ENABLE);  //使能TIM3在ARR上的预装载寄存器
}

2、然后还需要将L298所用到的八个GPIO口初始化:

void GPIOA_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    
    GPIO_InitTypeDef  GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStructure);
}

void GPIOB_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    
    GPIO_InitTypeDef  GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB,&GPIO_InitStructure);
}

3、将TIM3的初始化封装和设置小车的初始占空比:

void TIM3_Init(void)
{
    TIM3_CH1_PWM_Init(10000-1,144-1); // 72MHZ/10000/144 = 50HZ =  20ms
    TIM3_CH2_PWM_Init(10000-1,144-1);
    TIM3_CH3_PWM_Init(10000-1,144-1);
    TIM3_CH4_PWM_Init(10000-1,144-1);
}

void Car_Forward(int8_t speed)
{
    TIM_SetCompare1(TIM3,speed);
    TIM_SetCompare2(TIM3,speed);
    TIM_SetCompare3(TIM3,speed);
    TIM_SetCompare4(TIM3,speed);
}

除此之外还有编码器测速部分和红外模块的部分代码,红外模块的代码比较简单,只需要控制三个GPIO口输入高低电平即可,编码器测速部分参考了下面这篇博客,原理、代码注释啥的讲的很清楚:STM32——编码器测速原理及STM32编码器模式_卡卡南安的博客-CSDN博客_stm32编码器测速

猜你喜欢

转载自blog.csdn.net/qq_53336580/article/details/123600700
今日推荐