前言
????????本文是对之前智能车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).电机
? ? ????????? ? 霍尔编码电机
????????????????霍尔编码电机的优秀讲解
????????????????
????????(3).电机驱动模块
? ? ? ????????? L298N/TB6612
? ? ? ? ????????? ? ? ? ? ?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
? ? ? ? ? ? ? ? 如图所示:方框中为一次定时服务函数所打印的全部内容。
三、代码说明
?????? 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£o%d\n",n);
printf("x1£o%d\n",x1);
printf("x2£o%d\n",x2);
printf("x3£o%d\n",x3);
printf("x4£o%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=£o%f\n",pwm[0]);
printf("pwm2=£o%f\n",pwm[1]);
printf("pwm3=£o%f\n",pwm[2]);
printf("pwm4=£o%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]<pwm_min)
pwm[a] = pwm_min;
}
????????参考增量式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】
????????????????野火串口调试助手下载
????????为了做比赛已经拆掉了之前的模型,有点遗憾,大概长这样吧。
????????针对PID,调节一个电机即可,四个电机如法炮制。
????????单个电机的实验如下所示:
?1.接线
? ? ?原理图如下:
?????????实物连接图如下:
?? 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;
????????暂不讨论如何调参。
????????该组参数实验如图所示,直接用串口助手查看:
?????????转速从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程序进行说明,初学者可按文中的流程搭建项目,细节上的方法有很多,可自行修改。希望能帮到您。
? ? ? ? 侵删,欢迎指正。
|