本文是对之前智能车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参数设置
总结
通常我们对电机进行调速,尤其是智能车中广泛运用的直流电机调速,都采用PWM输出(即改变占空比)的方式。随着不断的实践,我们发现,一辆智能车若想要匀速行驶,仅仅设置占空比值是远远不够的。例如:坡道行驶、电源电压降低等情况。对此,我们加入PID算法进行矫正,使智能车能够匀速行驶。
做出一辆可以匀速行驶的四轮小车,针对坡道匀速行驶、电源电压不稳定等情况,可以自动进行PID调速。
STM32F103VET6
资源网站
霍尔编码电机
霍尔编码电机的优秀讲解
L298N/TB6612
L298N用法
TB6612用法
采用增量式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算法
单片机通过中断实时捕捉编码器的脉冲输出,不断累加,通过定时器设置读取周期,打印当前脉冲数,随后清零,即可打印出每个定时周期内的脉冲数(中断数)。注意,脉冲数清零是必须的,否则其便会一直累加,真实速度便无法求出。给出的代码中用的是A相即单相读取,双相原理类似,精度更高。
测速时设置了一个定时器,在它的定时器中断服务函数中不仅读取当前数据,还要进行PID整定。采用增量式PI算法,输入当前速度,输出计算所得的PWM值。若定时周期设为50ms,1秒内便可计算20次,设为200ms,一秒内则计算5次。很明显,PID计算的频率最好高一点,STM32的主控还是挺强的,在本代码中采用的是50ms,读者也可尝试更高的频率。
当时没找到什么好用的PID调试软件,就用了野火的串口调试助手,实际观测效果也还可以。在每个定时器中断服务函数中都会先打印当前速度,计算完后打印算得的PWM值。n用于计数,代表进入定时器中断服务函数的次数。
野火串口调试助手下载https://pan.baidu.com/s/12wd1wNrA7fCMXBgrbnoMkQ
智能车最基础的库,想跑至少先会走吧。
一般与电机相关的东西都会放到这儿,包括:电机引脚的配置、单个轮子的正转反转函数、小车整体的运行函数、初始化函数等等。
代码如下:
#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();
}
#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
根据编码电机的工作原理,单片机需要实时读取电机的AB相脉冲输出以进行转速测量,故可采用外部中断的方式采集这些脉冲。
在本工程中,我们只采用A相,若采用AB双相读取精度会更高,二者原理类似,不再赘述,读者可自行设计。
代码如下:
#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**********************/
#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 */
注: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);
}
}
PWM输出是通过通用定时器实现的。这里选用定时器3,周期设为7200,详细的配置方法可参考各自的学习板教程。
注意: 外部导入了四个变量值,即初始化占空比值,是在主函数中设定的,为0。设置该变量是多此一举的,单独调电机时的遗留写法。
extern uint16_t CCR1_Val;
extern uint16_t CCR2_Val;
extern uint16_t CCR3_Val;
extern uint16_t CCR4_Val;
详细代码如下:
#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**********************/
#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 */
设置定时器,为PID算法提供计算周期。
PID功能只用到了定时器6。
关于定时周期的设置,请参考各自的学习板教程,这里设置499,则定时50ms,即每50ms进行一次PID整定。此外,值得注意的是,测得的速度为:每个周期累计的中断数/周期大小,在程序中由x1、x2、x3、x4存储四个电机各自的速度。比如我们在实验时读取到x1=70,则表示M1电机每50ms会触发70个脉冲,速度由此便可算出。
#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**********************/
#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 */
主要由三部分组成:
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);
}
}
作为项目开发过程中必不可少的库, 至关重要,配置内容大同小异,详情看附件。
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为限幅最小值。
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算法公式。
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数组中的数据作为实际占空比以控制转速。而该数组中的数据在定时器中断服务函数中会被不断修改。
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,调节一个电机即可,四个电机如法炮制。
单个电机的实验如下所示:
原理图如下:
实物连接图如下:
#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的,调节效果都会有所不同。
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。
float Kp=5, Ki=10, Kd=0;
暂不讨论如何调参。
该组参数实验如图所示,直接用串口助手查看:
转速从0开始,发现n在25时转速几乎稳定,即大约1秒完成调节。
截取部分有用数据,如下所示:
四轮自动调速小车程序
AWELAn:1
x1:16
pwm1=:435.000000n:2
x1:11
pwm1=:800.000000n:3
x1:8
pwm1=:1185.000000n:4
x1:6
pwm1=:1585.000000n:5
x1:4
pwm1=:2005.000000n:6
x1:4
pwm1=:2415.000000n:7
x1:4
pwm1=:2825.000000n:8
x1:3
pwm1=:3250.000000n:9
x1:5
pwm1=:3640.000000n:10
x1:6
pwm1=:4025.000000n:11
x1:9
pwm1=:4370.000000n:12
x1:11
pwm1=:4700.000000n:13
x1:13
pwm1=:5010.000000n:14
x1:17
pwm1=:5270.000000n:15
x1:21
pwm1=:5490.000000n:16
x1:26
pwm1=:5655.000000n:17
x1:31
pwm1=:5770.000000n:18
x1:34
pwm1=:5865.000000n:19
x1:37
pwm1=:5930.000000n:20
x1:39
pwm1=:5980.000000n:21
x1:41
pwm1=:6010.000000n:22
x1:41
pwm1=:6050.000000n:23
x1:42
pwm1=:6075.000000n:24
x1:43
pwm1=:6090.000000n:25
x1:44
pwm1=:6095.000000n:26
x1:44
pwm1=:6105.000000n:27
x1:45
pwm1=:6100.000000n:28
x1:45
pwm1=:6080.000000n:29
x1:46
pwm1=:6055.000000n:30
x1:44
pwm1=:6085.000000
调节Kp,Ki的值可以得到不同的结果,当拓展系统功能时,程序结构和参数变化都会很大,本文只讨论智能车PID算法程序的结构搭建,不在调节方法上赘述。
好像只写PID太单调了,而且方法很朴素,不知道是否合适。
总之本文的目的仅是对电赛时的PID程序进行说明,初学者可按文中的流程搭建项目,细节上的方法有很多,可自行修改。希望能帮到您。
侵删,欢迎指正。