对STM32四轮小车PID调速程序的讲解说明(舍去了循迹、图像识别等功能)


前言

        本文是对之前智能车PID调速程序的说明,主要介绍搭建智能车PID调速框架的基础流程,方便交流与改进,也可当作学习PID算法入门级教程。

        注:程序功能仅仅是PID调速,舍去了循迹、图像识别、物联网等功能。

        程序源码下载【提取码9494】https://pan.baidu.com/s/1vA35R8umyZsNi5bFajrG-A

目录

前言

一、简介

       1.应用背景

        2.大致目标

二、方案确定

        1.设备选型以及算法

        (1).主控

        (2).电机                

        (3).电机驱动模块

        (4).PID算法

         2.方案详情

         (1).测速

         (2).调速

         (3).调试

三、代码说明

                 1.电机库

        (1).moter.c

        (2).moter.h

        2.外部中断库

        (1).bsp_exti.c

        (2).bsp_exti.h

        (3).外部中断服务函数

        3.通用定时器库

        (1).bsp_GeneralTim.c

        (2).bsp_GeneralTim.h

        4.基础定时器库

        (1).bsp_TiMbase.c

        (2).bsp_TiMbase.h

        (3).定时器服务函数

                  5.串口库

        (1).bsp_usart.c

        (2).bsp_usart.h

         6.主函数

        (1) .变量申明

        (2). PID计算程序          

        (3).main

        (4).其他函数

四、实验

           1.接线

           2.程序设置

        (1).定时器周期设置

        (2).目标值以及限幅值设置

        (3).Kp,Ki参数设置

总结


一、简介

       1.应用背景

         通常我们对电机进行调速,尤其是智能车中广泛运用的直流电机调速,都采用PWM输出(即改变占空比)的方式。随着不断的实践,我们发现,一辆智能车若想要匀速行驶,仅仅设置占空比值是远远不够的。例如:坡道行驶、电源电压降低等情况。对此,我们加入PID算法进行矫正,使智能车能够匀速行驶。

        2.大致目标

        做出一辆可以匀速行驶的四轮小车,针对坡道匀速行驶、电源电压不稳定等情况,可以自动进行PID调速。


二、方案确定

        1.设备选型以及算法

        (1).主控

                STM32F103VET6

                资源网站

        (2).电机

                霍尔编码电机

                霍尔编码电机的优秀讲解

对STM32四轮小车PID调速程序的讲解说明(舍去了循迹、图像识别等功能)_第1张图片

                

        (3).电机驱动模块

                L298N/TB6612

对STM32四轮小车PID调速程序的讲解说明(舍去了循迹、图像识别等功能)_第2张图片

对STM32四轮小车PID调速程序的讲解说明(舍去了循迹、图像识别等功能)_第3张图片

                           L298N用法

                           TB6612用法

           (4).PID算法

                采用增量式PI算法。

                增量式PID输出: u(k)=Kp * e(k-1)+Ki *e(k)+Kd *(e(k)-2e(k-1)+e(k-2))+u(k-1);

                        u(k):本次实际输出量 

                        u(k-1):上次的输出量

                        △u(k):输出变化量

                        Kp:比例系数

                        Ki:积分系数

                        Kd:微分系数

                        e(k-1): 上一次的目标和实际的误差值

                        e(k) :这次的目标和实际的误差值

                        e(k-2): 上上次目标和实际的误差值

                原理之类的网上实在太多啦:PID算法

         2.方案详情

           (1).测速

                单片机通过中断实时捕捉编码器的脉冲输出,不断累加,通过定时器设置读取周期,打印当前脉冲数,随后清零,即可打印出每个定时周期内的脉冲数(中断数)。注意,脉冲数清零是必须的,否则其便会一直累加,真实速度便无法求出。给出的代码中用的是A相即单相读取,双相原理类似,精度更高。

            (2).调速

                测速时设置了一个定时器,在它的定时器中断服务函数中不仅读取当前数据,还要进行PID整定。采用增量式PI算法,输入当前速度,输出计算所得的PWM值。若定时周期设为50ms,1秒内便可计算20次,设为200ms,一秒内则计算5次。很明显,PID计算的频率最好高一点,STM32的主控还是挺强的,在本代码中采用的是50ms,读者也可尝试更高的频率。

            (3).调试

                当时没找到什么好用的PID调试软件,就用了野火的串口调试助手,实际观测效果也还可以。在每个定时器中断服务函数中都会先打印当前速度,计算完后打印算得的PWM值。n用于计数,代表进入定时器中断服务函数的次数。

                野火串口调试助手下载https://pan.baidu.com/s/12wd1wNrA7fCMXBgrbnoMkQ

                如图所示:方框中为一次定时服务函数所打印的全部内容。对STM32四轮小车PID调速程序的讲解说明(舍去了循迹、图像识别等功能)_第4张图片


三、代码说明

       1.电机库

       智能车最基础的库,想跑至少先会走吧。

        一般与电机相关的东西都会放到这儿,包括:电机引脚的配置、单个轮子的正转反转函数、小车整体的运行函数、初始化函数等等。

        代码如下:

        (1)moter.c

#include "./motor/motor.h"  
void motor_GPIO_init(void)
{
		GPIO_InitTypeDef GPIO_InitStructure;
		RCC_APB2PeriphClockCmd( MOTOR1A_GPIO_CLK | MOTOR1B_GPIO_CLK | MOTOR2A_GPIO_CLK | MOTOR2B_GPIO_CLK | MOTOR3A_GPIO_CLK | MOTOR3B_GPIO_CLK | MOTOR4A_GPIO_CLK | MOTOR4B_GPIO_CLK, ENABLE);
		GPIO_InitStructure.GPIO_Pin = MOTOR1A_GPIO_PIN;	
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 
		GPIO_Init(MOTOR1A_GPIO_PORT, &GPIO_InitStructure);		
		GPIO_InitStructure.GPIO_Pin = MOTOR1B_GPIO_PIN;	
		GPIO_Init(MOTOR1B_GPIO_PORT, &GPIO_InitStructure);	
		GPIO_InitStructure.GPIO_Pin = MOTOR2A_GPIO_PIN;
		GPIO_Init(MOTOR2A_GPIO_PORT, &GPIO_InitStructure);
		GPIO_InitStructure.GPIO_Pin = MOTOR2B_GPIO_PIN;
		GPIO_Init(MOTOR2B_GPIO_PORT, &GPIO_InitStructure);
		GPIO_InitStructure.GPIO_Pin = MOTOR3A_GPIO_PIN;
		GPIO_Init(MOTOR3A_GPIO_PORT, &GPIO_InitStructure);
		GPIO_InitStructure.GPIO_Pin = MOTOR3B_GPIO_PIN;
		GPIO_Init(MOTOR3B_GPIO_PORT, &GPIO_InitStructure);
		GPIO_InitStructure.GPIO_Pin = MOTOR4A_GPIO_PIN;
		GPIO_Init(MOTOR4A_GPIO_PORT, &GPIO_InitStructure);
		GPIO_InitStructure.GPIO_Pin = MOTOR4B_GPIO_PIN;
		GPIO_Init(MOTOR4B_GPIO_PORT, &GPIO_InitStructure);
		
		GPIO_SetBits(MOTOR1A_GPIO_PORT, MOTOR1A_GPIO_PIN);
		GPIO_ResetBits(MOTOR1B_GPIO_PORT, MOTOR1B_GPIO_PIN);
		GPIO_SetBits(MOTOR2A_GPIO_PORT, MOTOR2A_GPIO_PIN);
		GPIO_ResetBits(MOTOR2B_GPIO_PORT, MOTOR2B_GPIO_PIN);
		GPIO_SetBits(MOTOR3A_GPIO_PORT, MOTOR3A_GPIO_PIN);
		GPIO_ResetBits(MOTOR3B_GPIO_PORT, MOTOR3B_GPIO_PIN);
		GPIO_SetBits(MOTOR4A_GPIO_PORT, MOTOR4A_GPIO_PIN);
		GPIO_ResetBits(MOTOR4B_GPIO_PORT, MOTOR4B_GPIO_PIN);
}
void M1_go(void)
{
	GPIO_SetBits(MOTOR1A_GPIO_PORT, MOTOR1A_GPIO_PIN);
	GPIO_ResetBits(MOTOR1B_GPIO_PORT, MOTOR1B_GPIO_PIN);
}
void M2_go(void)
{
	GPIO_SetBits(MOTOR2A_GPIO_PORT, MOTOR2A_GPIO_PIN);
	GPIO_ResetBits(MOTOR2B_GPIO_PORT, MOTOR2B_GPIO_PIN);
}
void M3_go(void)
{
	GPIO_SetBits(MOTOR3A_GPIO_PORT, MOTOR3A_GPIO_PIN);
	GPIO_ResetBits(MOTOR3B_GPIO_PORT, MOTOR3B_GPIO_PIN);
}
void M4_go(void)
{
	GPIO_SetBits(MOTOR4A_GPIO_PORT, MOTOR4A_GPIO_PIN);
	GPIO_ResetBits(MOTOR4B_GPIO_PORT, MOTOR4B_GPIO_PIN);
}


void M1_back(void)
{
	GPIO_ResetBits(MOTOR1A_GPIO_PORT, MOTOR1A_GPIO_PIN);
	GPIO_SetBits(MOTOR1B_GPIO_PORT, MOTOR1B_GPIO_PIN);
}
void M2_back(void)
{
	GPIO_ResetBits(MOTOR2A_GPIO_PORT, MOTOR2A_GPIO_PIN);
	GPIO_SetBits(MOTOR2B_GPIO_PORT, MOTOR2B_GPIO_PIN);
}
void M3_back(void)
{
	GPIO_ResetBits(MOTOR3A_GPIO_PORT, MOTOR3A_GPIO_PIN);
	GPIO_SetBits(MOTOR3B_GPIO_PORT, MOTOR3B_GPIO_PIN);
}
void M4_back(void)
{
	GPIO_ResetBits(MOTOR4A_GPIO_PORT, MOTOR4A_GPIO_PIN);
	GPIO_SetBits(MOTOR4B_GPIO_PORT, MOTOR4B_GPIO_PIN);
}


void Car_go(void)
{
	M1_go();
	M2_go();
	M3_go();
	M4_go();
}
void Car_back(void)
{
	M1_back();
	M2_back();
	M3_back();
	M4_back();
}

        (2)moter.h

#ifndef __MOTOR_H
#define	__MOTOR_H
#include "stm32f10x.h"

#define    MOTOR1A_GPIO_CLK     RCC_APB2Periph_GPIOB
#define    MOTOR1A_GPIO_PORT    GPIOB			   
#define    MOTOR1A_GPIO_PIN		 GPIO_Pin_12

#define    MOTOR1B_GPIO_CLK     RCC_APB2Periph_GPIOB
#define    MOTOR1B_GPIO_PORT    GPIOB			   
#define    MOTOR1B_GPIO_PIN		 GPIO_Pin_13

#define    MOTOR2A_GPIO_CLK     RCC_APB2Periph_GPIOB
#define    MOTOR2A_GPIO_PORT    GPIOB		   
#define    MOTOR2A_GPIO_PIN		 GPIO_Pin_14

#define    MOTOR2B_GPIO_CLK     RCC_APB2Periph_GPIOB
#define    MOTOR2B_GPIO_PORT    GPIOB		   
#define    MOTOR2B_GPIO_PIN		 GPIO_Pin_15

#define    MOTOR3A_GPIO_CLK     RCC_APB2Periph_GPIOC
#define    MOTOR3A_GPIO_PORT    GPIOC		   
#define    MOTOR3A_GPIO_PIN		 GPIO_Pin_8

#define    MOTOR3B_GPIO_CLK     RCC_APB2Periph_GPIOC
#define    MOTOR3B_GPIO_PORT    GPIOC		   
#define    MOTOR3B_GPIO_PIN		 GPIO_Pin_9

#define    MOTOR4A_GPIO_CLK     RCC_APB2Periph_GPIOC
#define    MOTOR4A_GPIO_PORT    GPIOC		   
#define    MOTOR4A_GPIO_PIN		 GPIO_Pin_10

#define    MOTOR4B_GPIO_CLK     RCC_APB2Periph_GPIOC
#define    MOTOR4B_GPIO_PORT    GPIOC	   
#define    MOTOR4B_GPIO_PIN		 GPIO_Pin_11

void motor_GPIO_init(void);
void M1_go(void);
void M2_go(void);
void M3_go(void);
void M4_go(void);
void M1_back(void);
void M2_back(void);
void M3_back(void);
void M4_back(void);
void Car_go(void);
void Car_back(void);

#endif 

        2.外部中断库

        根据编码电机的工作原理,单片机需要实时读取电机的AB相脉冲输出以进行转速测量,故可采用外部中断的方式采集这些脉冲。

        在本工程中,我们只采用A相,若采用AB双相读取精度会更高,二者原理类似,不再赘述,读者可自行设计。

代码如下:

        (1)bsp_exti.c

#include "./Exti/bsp_exti.h" 

static void NVIC_Configuration(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
  NVIC_InitStructure.NVIC_IRQChannel = KEY1_INT_EXTI_IRQ;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
  NVIC_InitStructure.NVIC_IRQChannel = KEY2_INT_EXTI_IRQ;
  NVIC_Init(&NVIC_InitStructure);
  NVIC_InitStructure.NVIC_IRQChannel = KEY3_INT_EXTI_IRQ;
  NVIC_Init(&NVIC_InitStructure);
  NVIC_InitStructure.NVIC_IRQChannel = KEY4_INT_EXTI_IRQ;
  NVIC_Init(&NVIC_InitStructure);
}
void EXTI_Key_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure; 
	EXTI_InitTypeDef EXTI_InitStructure;
	RCC_APB2PeriphClockCmd(KEY1_INT_GPIO_CLK,ENABLE);
	RCC_APB2PeriphClockCmd(KEY2_INT_GPIO_CLK,ENABLE);			
	RCC_APB2PeriphClockCmd(KEY3_INT_GPIO_CLK,ENABLE);
	RCC_APB2PeriphClockCmd(KEY4_INT_GPIO_CLK,ENABLE);		
	NVIC_Configuration();
	
/*--------------------------KEY1-----------------------------*/
  GPIO_InitStructure.GPIO_Pin = KEY1_INT_GPIO_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
  GPIO_Init(KEY1_INT_GPIO_PORT, &GPIO_InitStructure);
  GPIO_EXTILineConfig(KEY1_INT_EXTI_PORTSOURCE, KEY1_INT_EXTI_PINSOURCE); 
  EXTI_InitStructure.EXTI_Line = KEY1_INT_EXTI_LINE;
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  EXTI_Init(&EXTI_InitStructure);
	
  /*--------------------------KEY2----------------------------*/	
  GPIO_InitStructure.GPIO_Pin = KEY2_INT_GPIO_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
  GPIO_Init(KEY2_INT_GPIO_PORT, &GPIO_InitStructure);
  GPIO_EXTILineConfig(KEY2_INT_EXTI_PORTSOURCE, KEY2_INT_EXTI_PINSOURCE); 
  EXTI_InitStructure.EXTI_Line = KEY2_INT_EXTI_LINE;
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  EXTI_Init(&EXTI_InitStructure);
	
	 /*--------------------------KEY3----------------------------*/	
  GPIO_InitStructure.GPIO_Pin = KEY3_INT_GPIO_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
  GPIO_Init(KEY3_INT_GPIO_PORT, &GPIO_InitStructure);
  GPIO_EXTILineConfig(KEY3_INT_EXTI_PORTSOURCE, KEY3_INT_EXTI_PINSOURCE); 
  EXTI_InitStructure.EXTI_Line = KEY3_INT_EXTI_LINE;
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;	
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  EXTI_Init(&EXTI_InitStructure);
	
	  /*--------------------------KEY4-----------------------------*/
  GPIO_InitStructure.GPIO_Pin = KEY4_INT_GPIO_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
  GPIO_Init(KEY4_INT_GPIO_PORT, &GPIO_InitStructure);
  GPIO_EXTILineConfig(KEY4_INT_EXTI_PORTSOURCE, KEY4_INT_EXTI_PINSOURCE); 
  EXTI_InitStructure.EXTI_Line = KEY4_INT_EXTI_LINE;
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  EXTI_Init(&EXTI_InitStructure);
}
/*********************************************END OF FILE**********************/

        (2)bsp_exti.h

#ifndef __EXTI_H
#define	__EXTI_H
#include "stm32f10x.h"

#define KEY1_INT_GPIO_PORT         GPIOE
#define KEY1_INT_GPIO_CLK          (RCC_APB2Periph_GPIOE|RCC_APB2Periph_AFIO)
#define KEY1_INT_GPIO_PIN          GPIO_Pin_0
#define KEY1_INT_EXTI_PORTSOURCE   GPIO_PortSourceGPIOE
#define KEY1_INT_EXTI_PINSOURCE    GPIO_PinSource0
#define KEY1_INT_EXTI_LINE         EXTI_Line0
#define KEY1_INT_EXTI_IRQ          EXTI0_IRQn
#define KEY1_IRQHandler            EXTI0_IRQHandler

#define KEY2_INT_GPIO_PORT         GPIOC
#define KEY2_INT_GPIO_CLK          (RCC_APB2Periph_GPIOC|RCC_APB2Periph_AFIO)
#define KEY2_INT_GPIO_PIN          GPIO_Pin_12
#define KEY2_INT_EXTI_PORTSOURCE   GPIO_PortSourceGPIOC
#define KEY2_INT_EXTI_PINSOURCE    GPIO_PinSource12
#define KEY2_INT_EXTI_LINE         EXTI_Line12
#define KEY2_INT_EXTI_IRQ          EXTI15_10_IRQn
#define KEY2_IRQHandler            EXTI15_10_IRQHandler

#define KEY3_INT_GPIO_PORT         GPIOC
#define KEY3_INT_GPIO_CLK          (RCC_APB2Periph_GPIOC|RCC_APB2Periph_AFIO)
#define KEY3_INT_GPIO_PIN          GPIO_Pin_4
#define KEY3_INT_EXTI_PORTSOURCE   GPIO_PortSourceGPIOC
#define KEY3_INT_EXTI_PINSOURCE    GPIO_PinSource4
#define KEY3_INT_EXTI_LINE         EXTI_Line4
#define KEY3_INT_EXTI_IRQ          EXTI4_IRQn
#define KEY3_IRQHandler            EXTI4_IRQHandler

#define KEY4_INT_GPIO_PORT         GPIOC
#define KEY4_INT_GPIO_CLK          (RCC_APB2Periph_GPIOC|RCC_APB2Periph_AFIO)
#define KEY4_INT_GPIO_PIN          GPIO_Pin_5
#define KEY4_INT_EXTI_PORTSOURCE   GPIO_PortSourceGPIOC
#define KEY4_INT_EXTI_PINSOURCE    GPIO_PinSource5
#define KEY4_INT_EXTI_LINE         EXTI_Line5
#define KEY4_INT_EXTI_IRQ          EXTI9_5_IRQn

#define KEY4_IRQHandler            EXTI9_5_IRQHandler
void EXTI_Key_Config(void);

#endif /* __EXTI_H */

          (3)外部中断服务函数

        注:x1、x2、x3、x4是对四个电机各自脉冲数的累积,电机每输出一次脉冲,单片机中对应中断便触发一次,x的值也便得到累加。

void KEY1_IRQHandler(void)
{ 
	if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET)
	{
		x1++;	  
		EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE);    
	}  
}
void KEY2_IRQHandler(void)
{
	if(EXTI_GetITStatus(KEY2_INT_EXTI_LINE) != RESET) 
	{
		x2++; 
		EXTI_ClearITPendingBit(KEY2_INT_EXTI_LINE);  
	}  
}
void KEY3_IRQHandler(void)
{ 
	if(EXTI_GetITStatus(KEY3_INT_EXTI_LINE) != RESET) 
	{
		x3++;   
		EXTI_ClearITPendingBit(KEY3_INT_EXTI_LINE);    
	}  
}
void KEY4_IRQHandler(void)
{
	if(EXTI_GetITStatus(KEY4_INT_EXTI_LINE) != RESET) 
	{
		x4++;   
		EXTI_ClearITPendingBit(KEY4_INT_EXTI_LINE);   
	}   	  
}

        3.通用定时器库

        PWM输出是通过通用定时器实现的。这里选用定时器3,周期设为7200,详细的配置方法可参考各自的学习板教程。

        注意: 外部导入了四个变量值,即初始化占空比值,是在主函数中设定的,为0。设置该变量是多此一举的,单独调电机时的遗留写法。

	extern uint16_t CCR1_Val;
	extern uint16_t CCR2_Val;
	extern uint16_t CCR3_Val;
	extern uint16_t CCR4_Val;

详细代码如下:

        (1).bsp_GeneralTim.c


#include "./GeneralTim/bsp_GeneralTim.h" 

	extern uint16_t CCR1_Val;
	extern uint16_t CCR2_Val;
	extern uint16_t CCR3_Val;
	extern uint16_t CCR4_Val;
	
static void GENERAL_TIM_GPIO_Config(void) 
{
  GPIO_InitTypeDef GPIO_InitStructure;

  RCC_APB2PeriphClockCmd(GENERAL_TIM_CH1_GPIO_CLK, ENABLE);
  GPIO_InitStructure.GPIO_Pin =  GENERAL_TIM_CH1_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GENERAL_TIM_CH1_PORT, &GPIO_InitStructure);
 	
  RCC_APB2PeriphClockCmd(GENERAL_TIM_CH2_GPIO_CLK, ENABLE);
  GPIO_InitStructure.GPIO_Pin =  GENERAL_TIM_CH2_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GENERAL_TIM_CH2_PORT, &GPIO_InitStructure);
	
  RCC_APB2PeriphClockCmd(GENERAL_TIM_CH3_GPIO_CLK, ENABLE);
  GPIO_InitStructure.GPIO_Pin =  GENERAL_TIM_CH3_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GENERAL_TIM_CH3_PORT, &GPIO_InitStructure);
	
  RCC_APB2PeriphClockCmd(GENERAL_TIM_CH4_GPIO_CLK, ENABLE);
  GPIO_InitStructure.GPIO_Pin =  GENERAL_TIM_CH4_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GENERAL_TIM_CH4_PORT, &GPIO_InitStructure);
}

static void GENERAL_TIM_Mode_Config(void)
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;	
	GENERAL_TIM_APBxClock_FUN(GENERAL_TIM_CLK,ENABLE);
	TIM_TimeBaseStructure.TIM_Period=GENERAL_TIM_Period;	
	TIM_TimeBaseStructure.TIM_Prescaler= GENERAL_TIM_Prescaler;	
	TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;		
	TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;		
	TIM_TimeBaseStructure.TIM_RepetitionCounter=0;	
	TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure);

	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OCInitStructure.TIM_Pulse = CCR1_Val;
	TIM_OC1Init(GENERAL_TIM, &TIM_OCInitStructure);
	TIM_OC1PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
	
	TIM_OCInitStructure.TIM_Pulse = CCR2_Val;
	TIM_OC2Init(GENERAL_TIM, &TIM_OCInitStructure);
	TIM_OC2PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
	
	TIM_OCInitStructure.TIM_Pulse = CCR3_Val;
	TIM_OC3Init(GENERAL_TIM, &TIM_OCInitStructure);
	TIM_OC3PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
	
    TIM_OCInitStructure.TIM_Pulse = CCR4_Val;
	TIM_OC4Init(GENERAL_TIM, &TIM_OCInitStructure);
	TIM_OC4PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
	
	TIM_Cmd(GENERAL_TIM, ENABLE);
}
void GENERAL_TIM_Init(void)
{
	GENERAL_TIM_GPIO_Config();
	GENERAL_TIM_Mode_Config();		
}
/*********************************************END OF FILE**********************/

        (2).bsp_GeneralTim.h

#ifndef __BSP_GENERALTIME_H
#define __BSP_GENERALTIME_H
#include "stm32f10x.h"

#define            GENERAL_TIM                   TIM3
#define            GENERAL_TIM_APBxClock_FUN     RCC_APB1PeriphClockCmd
#define            GENERAL_TIM_CLK               RCC_APB1Periph_TIM3
#define            GENERAL_TIM_Period            7199
#define            GENERAL_TIM_Prescaler         0

#define            GENERAL_TIM_CH1_GPIO_CLK      RCC_APB2Periph_GPIOA
#define            GENERAL_TIM_CH1_PORT          GPIOA
#define            GENERAL_TIM_CH1_PIN           GPIO_Pin_6

#define            GENERAL_TIM_CH2_GPIO_CLK      RCC_APB2Periph_GPIOA
#define            GENERAL_TIM_CH2_PORT          GPIOA
#define            GENERAL_TIM_CH2_PIN           GPIO_Pin_7

#define            GENERAL_TIM_CH3_GPIO_CLK      RCC_APB2Periph_GPIOB
#define            GENERAL_TIM_CH3_PORT          GPIOB
#define            GENERAL_TIM_CH3_PIN           GPIO_Pin_0

#define            GENERAL_TIM_CH4_GPIO_CLK      RCC_APB2Periph_GPIOB
#define            GENERAL_TIM_CH4_PORT          GPIOB
#define            GENERAL_TIM_CH4_PIN           GPIO_Pin_1

void GENERAL_TIM_Init(void);
#endif	/* __BSP_GENERALTIME_H */

        4.基础定时器库

        设置定时器,为PID算法提供计算周期。

        PID功能只用到了定时器6。

        关于定时周期的设置,请参考各自的学习板教程,这里设置499,则定时50ms,即每50ms进行一次PID整定。此外,值得注意的是,测得的速度为:每个周期累计的中断数/周期大小,在程序中由x1、x2、x3、x4存储四个电机各自的速度。比如我们在实验时读取到x1=70,则表示M1电机每50ms会触发70个脉冲,速度由此便可算出。

        (1).bsp_TiMbase.c


#include "bsp_TiMbase.h" 

static void BASIC_TIM_NVIC_Config(void)
{
    NVIC_InitTypeDef NVIC_InitStructure; 
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);		
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;	 
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;	
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

static void BASIC_TIM_Mode_Config(void)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;	
    BASIC_TIM_APBxClock_FUN(BASIC_TIM_CLK, ENABLE);
    TIM_TimeBaseStructure.TIM_Period = BASIC_TIM_Period;	
    TIM_TimeBaseStructure.TIM_Prescaler= BASIC_TIM_Prescaler;
    //TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
    //TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; 
	//TIM_TimeBaseStructure.TIM_RepetitionCounter=0;
    TIM_TimeBaseInit(BASIC_TIM, &TIM_TimeBaseStructure);
    TIM_ClearFlag(BASIC_TIM, TIM_FLAG_Update);
    TIM_ITConfig(BASIC_TIM,TIM_IT_Update,ENABLE);
    TIM_Cmd(BASIC_TIM, ENABLE);	
}
void BASIC_TIM_Init(void)
{
	BASIC_TIM_NVIC_Config();
	BASIC_TIM_Mode_Config();
}
/*********************************************END OF FILE**********************/

        (2).bsp_TiMbase.h

#ifndef __BSP_TIMEBASE_H
#define __BSP_TIMEBASE_H
#include "stm32f10x.h"

#define BASIC_TIM6 
#ifdef  BASIC_TIM6 
#define            BASIC_TIM                   TIM6
#define            BASIC_TIM_APBxClock_FUN     RCC_APB1PeriphClockCmd
#define            BASIC_TIM_CLK               RCC_APB1Periph_TIM6
#define            BASIC_TIM_Period            499
#define            BASIC_TIM_Prescaler         7199
#define            BASIC_TIM_IRQ               TIM6_IRQn
#define            BASIC_TIM_IRQHandler        TIM6_IRQHandler
#else  
#define            BASIC_TIM                   TIM7
#define            BASIC_TIM_APBxClock_FUN     RCC_APB1PeriphClockCmd
#define            BASIC_TIM_CLK               RCC_APB1Periph_TIM7
#define            BASIC_TIM_Period            999
#define            BASIC_TIM_Prescaler         7199
#define            BASIC_TIM_IRQ               TIM7_IRQn
#define            BASIC_TIM_IRQHandler        TIM7_IRQHandler
#endif
void BASIC_TIM_Init(void);
#endif	/* __BSP_TIMEBASE_H */


        (3)定时器服务函数

    主要由三部分组成:

        1.当前状态的打印输出

                x1至x4是脉冲数量;

                n用于累加进入定时器中断的次数,可用于作为算法中的时间度量度。

        2.PID计算随后进行PID整定

                PID计算函数由主函数导入,具体算法参考主函数。计算完成后必须把对应x的值清零。

        3.打印计算所得的输出PWM值

                计算所得的数据保存在pwm数组中,且大小在0~7200的范围内。

void  BASIC_TIM_IRQHandler (void)
{
	if ( TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET ) 
	{	

		n++;
		printf("n£º%d\n",n);
		printf("x1£º%d\n",x1);
		printf("x2£º%d\n",x2);
		printf("x3£º%d\n",x3);
		printf("x4£º%d\n",x4);
		
		PID_Calculate(x1,ExpectedValue[0],0);
		x1=0;
		PID_Calculate(x2,ExpectedValue[1],1);
		x2=0;
		PID_Calculate(x3,ExpectedValue[2],2);
		x3=0;
		PID_Calculate(x4,ExpectedValue[3],3);
		x4=0;
		
		printf("pwm1=£º%f\n",pwm[0]);
		printf("pwm2=£º%f\n",pwm[1]);
		printf("pwm3=£º%f\n",pwm[2]);
		printf("pwm4=£º%f\n",pwm[3]);
		
		TIM_ClearITPendingBit(BASIC_TIM , TIM_FLAG_Update);  
	}		 	
}

        5.串口库

        作为项目开发过程中必不可少的库, 至关重要,配置内容大同小异,详情看附件。

        (1).bsp_usart.c

        (2).bsp_usart.h

        6.主函数

        (1) .变量申明

    float Kp=5, Ki=10, Kd=0;
	float ExpectedValue[4]={50.0,30.0,50.0,70.0};
	float err[4]={0.0,0.0,0.0,0.0};
	float err_last[4]={0.0,0.0,0.0,0.0};
    float pwm[4]={0.0,0.0,0.0,0.0};
	float pwm_max=7100;
    float pwm_min=0;

         其中:

         1.采用增量式PI算法,Kd值设为0,Kp,Ki值由实验测得。

          2.ExpectedValue[4]存储四个电机的目标值。

          3.err[4]存储四个电机每次计算时的误差值。

          4.err_last[4]用于保存上一次计算时的误差值。

          5.pwm[4]存储计算所得的输出PWM值。

          6.pwm_max为限幅最大值。

          7.pwm_min为限幅最小值。


        (2). PID计算程序          

void PID_Calculate(float actualValue,float expectedValue,int a)
	{
	  err[a] = expectedValue - actualValue;		
		pwm[a] += Kp*(err[a] - err_last[a]) + Ki*err[a] ; 
		err_last[a] = err[a];		
	  //限幅
		if(pwm[a]>pwm_max)  
		pwm[a] = pwm_max;
		else if(pwm[a]

        参考增量式PID算法公式。

        (3).main

int main(void)
{
	//初始化
	LED_GPIO_Config();
	BASIC_TIM_Init();
	GENERAL_TIM_Init();
	EXTI_Key_Config(); 
	USART_Config();
	motor_GPIO_init();
	
	Usart_SendString( DEBUG_USARTx,"四轮PID\n");
	printf("AWELA\n\n\n\n");							
	Car_go();
	
	while(1)
	{
		pwm_set(pwm[0],pwm[1],pwm[2],pwm[3]);
		//pwm_set(5000,5000,5000,5000);
	}
}

        进行一系列初始化函数后进入while死循环,不断的将pwm数组中的数据作为实际占空比以控制转速。而该数组中的数据在定时器中断服务函数中会被不断修改。

        (4).其他函数

void Delay(__IO uint32_t nCount) //简单的延时函数
 {	 
 for (; nCount != 0; nCount--); 
 }
void pwm_set(int n1,int n2,int n3,int n4)//PWM赋值函数
{
		GENERAL_TIM->CCR1=n1;
		GENERAL_TIM->CCR2=n2;
		GENERAL_TIM->CCR3=n3;
		GENERAL_TIM->CCR4=n4;
}

四、实验

                程序源码下载【提取码9494】

                野火串口调试助手下载

        为了做比赛已经拆掉了之前的模型,有点遗憾,大概长这样吧。

对STM32四轮小车PID调速程序的讲解说明(舍去了循迹、图像识别等功能)_第5张图片

        针对PID,调节一个电机即可,四个电机如法炮制。

        单个电机的实验如下所示:

 1.接线

     原理图如下:

对STM32四轮小车PID调速程序的讲解说明(舍去了循迹、图像识别等功能)_第6张图片

         实物连接图如下:

对STM32四轮小车PID调速程序的讲解说明(舍去了循迹、图像识别等功能)_第7张图片

   2.程序设置

         (1).定时器周期设置

#define            BASIC_TIM                   TIM6
#define            BASIC_TIM_APBxClock_FUN     RCC_APB1PeriphClockCmd
#define            BASIC_TIM_CLK               RCC_APB1Periph_TIM6
#define            BASIC_TIM_Period            499
#define            BASIC_TIM_Prescaler         7199
#define            BASIC_TIM_IRQ               TIM6_IRQn
#define            BASIC_TIM_IRQHandler        TIM6_IRQHandler

        周期设为499,即50ms中断一次,计算一次PID。

        有空可以试试100ms、200ms的,调节效果都会有所不同。

        (2).目标值以及限幅值设置

float ExpectedValue[4]={45.0,100.0,100.0,100.0};
float err[4]={0.0,0.0,0.0,0.0};
float err_last[4]={0.0,0.0,0.0,0.0};
float pwm[4]={0.0,0.0,0.0,0.0};
float pwm_max=7100;
float pwm_min=0;

        对于目标值,因为只使用第一个电机,故ExpectedValue数组设置第一个元素即可,我们设为45,即期望每50ms有45个脉冲。

        限幅值根据通用定时器来设置,设为0~7100。

        (3).Kp,Ki参数设置

float Kp=5, Ki=10, Kd=0;

        暂不讨论如何调参。

        该组参数实验如图所示,直接用串口助手查看:

对STM32四轮小车PID调速程序的讲解说明(舍去了循迹、图像识别等功能)_第8张图片

         转速从0开始,发现n在25时转速几乎稳定,即大约1秒完成调节。

        截取部分有用数据,如下所示:

四轮自动调速小车程序
AWELA

n:1
x1:16
pwm1=:435.000000

n:2
x1:11
pwm1=:800.000000

n:3
x1:8
pwm1=:1185.000000

n:4
x1:6
pwm1=:1585.000000

n:5
x1:4
pwm1=:2005.000000

n:6
x1:4
pwm1=:2415.000000

n:7
x1:4
pwm1=:2825.000000

n:8
x1:3
pwm1=:3250.000000

n:9
x1:5
pwm1=:3640.000000

n:10
x1:6
pwm1=:4025.000000

n:11
x1:9
pwm1=:4370.000000

n:12
x1:11
pwm1=:4700.000000

n:13
x1:13
pwm1=:5010.000000

n:14
x1:17
pwm1=:5270.000000

n:15
x1:21
pwm1=:5490.000000

n:16
x1:26
pwm1=:5655.000000

n:17
x1:31
pwm1=:5770.000000

n:18
x1:34
pwm1=:5865.000000

n:19
x1:37
pwm1=:5930.000000

n:20
x1:39
pwm1=:5980.000000

n:21
x1:41
pwm1=:6010.000000

n:22
x1:41
pwm1=:6050.000000

n:23
x1:42
pwm1=:6075.000000

n:24
x1:43
pwm1=:6090.000000

n:25
x1:44
pwm1=:6095.000000

n:26
x1:44
pwm1=:6105.000000

n:27
x1:45
pwm1=:6100.000000

n:28
x1:45
pwm1=:6080.000000

n:29
x1:46
pwm1=:6055.000000

n:30
x1:44
pwm1=:6085.000000
 

        调节Kp,Ki的值可以得到不同的结果,当拓展系统功能时,程序结构和参数变化都会很大,本文只讨论智能车PID算法程序的结构搭建,不在调节方法上赘述。


总结

        好像只写PID太单调了,而且方法很朴素,不知道是否合适。

        总之本文的目的仅是对电赛时的PID程序进行说明,初学者可按文中的流程搭建项目,细节上的方法有很多,可自行修改。希望能帮到您。

        侵删,欢迎指正。

你可能感兴趣的:(备战电赛系列,stm32,单片机)