一:概要
主控芯片为stm32f407,通过编码器计算行驶距离,pid闭环控制,程序源码中包含上一期的pid速度控制。使用本程序所需知道的条件:编码器一周几个脉冲,电机的减速比和车轮的直径。
(注:免费源码链接,请点赞关注私信)
二:效果演示
视频介绍;行驶距离120厘米,换算编码器脉冲共3103个。
stm32行驶指定距离
三:程序代码
1:固定距离行驶pid.c
#include "./BSP/PID/pid.h"
#include "./SYSTEM/sys/sys.h"
#include "./BSP/LCD/lcd.h"
#include "./SYSTEM/usart/usart.h"
extern TIM_HandleTypeDef g_timx_pwm_chy_handle; /* 定时器x句柄 */
extern TIM_HandleTypeDef g_timx_cnt_chy_handle; /* 定时器x句柄 */
extern TIM_HandleTypeDef g_tim3_cnt_chy_handle; /* 定时器x句柄 */
TIM_HandleTypeDef g_timx_handler; /* 定时器参数句柄 */
//uint8_t Point=120; /* 设定目标转速 */
//#define SetPointL Point*528/3000 /* 转速转化为 20ms 编码器脉冲数量 */
//uint16_t Moto;
/*3.1416 * 车轮的直径 /车轮一圈产生的编码脉冲 */
#define Extent ( 3.1416*6.5/528 ) /* 一个编码器脉冲行走的距离 0.038675cm 一圈20.4204cm*/
//uint16_t Destina= 120/Extent; /* 行走距离脉冲数 */
PID zspeed,zpos;
Error value,vpos;
void PID_init(void) /* 初始化 P I D */
{
/* 速度PID */
zspeed.P=20;
zspeed.I=2.5;
zspeed.D=0;
value.Current_Error=0;//当前误差
value.Last_Error=0;//上一次误差
value.Previous_Error=0;//上上次误差
/* 位置PID */
zpos.P=20;
zpos.I=2.5;
zpos.D=0;
vpos.Current_Error=0;//当前误差
vpos.Last_Error=0;//上一次误差
vpos.Previous_Error=0;//上上次误差
}
/*!
* @brief 增量式PID
* @since v1.0
* *sptr :误差参数
* *pid: PID参数
* NowPlace:实际值
* Point: 期望值
*/
// 增量式PID电机控制
uint32_t PID_Increase(Error *sptr, PID *pid, uint32_t NowPlace, uint32_t Point)
{
uint32_t iError, //当前误差
Increase; //最后得出的实际增量
iError = Point - NowPlace; // 计算当前误差
Increase = pid->P * (iError - sptr->Last_Error) //比例P
+ pid->I * iError //积分I
+ pid->D * (iError - 2 * sptr->Last_Error + sptr->Previous_Error); //微分D
sptr->Previous_Error = sptr->Last_Error; // 更新前次误差
sptr->Last_Error = iError; // 更新上次误差
return Increase; // 返回增量
}
/*!
* @brief 位置式PID
* @since v1.0
* *sptr :误差参数
* *pid: PID参数
* NowPlace:当前位置
* Point: 预期位置
*/
// 位置式PID控制
float PID_Realize(Error *sptr,PID *pid, uint32_t NowPlace, float Point)
{
uint32_t iError, // 当前误差
Realize; //实际输出
iError = Point - NowPlace; // 计算当前误差
sptr->Current_Error += pid->I * iError; // 误差积分
sptr->Current_Error = sptr->Current_Error > pid->limit?pid->limit:sptr->Current_Error;//积分限幅
sptr->Current_Error = sptr->Current_Error <-pid->limit?-pid->limit:sptr->Current_Error;
Realize = pid->P * iError //比例P
+ sptr->Current_Error //积分I
+ pid->D * (iError - sptr->Last_Error); //微分D
sptr->Last_Error = iError; // 更新上次误差
return Realize; // 返回实际值
}
//行使固定位置PID
void Destina_PID(float JULI)
{
uint16_t Destina= JULI/Extent;
uint32_t count = 0,Destination=0,Increase=0;
__HAL_TIM_DISABLE(&g_tim3_cnt_chy_handle); /* 关闭编码器定时器 */
__HAL_TIM_SET_COUNTER(&g_tim3_cnt_chy_handle, 0); /* 计数器清零 */
__HAL_TIM_ENABLE(&g_tim3_cnt_chy_handle); /* 开启编码器定时器 */
do
{
//__HAL_TIM_SET_COMPARE(&g_timx_pwm_chy_handle, TIM_CHANNEL_1 ,500); /* 控制小车电机的运行pwm占空比 */
count=__HAL_TIM_GET_COUNTER(&g_tim3_cnt_chy_handle); /* 获取当前编码器脉冲计数值 */
lcd_show_num(50,100,count,6,16,RED);
Destination=(count)*Extent; /* 计算当前行走的距离 */
lcd_show_num(50,80,Destination,3,16,RED);
Increase=PID_Increase( &vpos , &zpos , count , Destina ); /* 计数得到增量式PID的增量数值 */
lcd_show_num(50,60,Increase,3,16,RED);
if(Increase<2)
{
__HAL_TIM_SET_COMPARE(&g_timx_pwm_chy_handle, TIM_CHANNEL_1 ,0);
}
printf("%d\r\n",Destination);
}while(count<=Destina-2); /* 防止惯性的影响 */
__HAL_TIM_SET_COMPARE(&g_timx_pwm_chy_handle, TIM_CHANNEL_1 ,0);/* 停止电机运行 */
//HAL_NVIC_DisableIRQ(BTIM_TIMX_INT_IRQn); /* 关闭中断 */
}
2:pid.h
#ifndef __PID_H
#define __PID_H
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/sys/sys.h"
void btim_timx_int_init(uint16_t arr, uint16_t psc); /* 基本定时器 定时中断初始化函数 */
typedef struct
{
float P,I,D,limit;
}PID;
typedef struct
{
float Current_Error;//当前误差
float Last_Error;//上一次误差
float Previous_Error;//上上次误差
}Error;
void gtim_timx_int_init(uint16_t arr, uint16_t psc);
uint32_t PID_Increase(Error *sptr, PID *pid, uint32_t NowPlace, uint32_t Point);
float PID_Realize(Error *sptr,PID *pid, uint32_t NowPlace, float Point);
void PID_init(void);
//void Speed_PID(uint8_t Point);
void Destina_PID(float JULI);
#endif
3:定时器编码器模式
#include "./BSP/TIMER/gtim.h"
#include "./BSP/LED/led.h"
#include "./SYSTEM/usart/usart.h"
TIM_HandleTypeDef g_timx_cnt_chy_handle; /* 定时器x句柄 */
/*********************************以下是通用定时器PWM输出实验程序*************************************/
TIM_HandleTypeDef g_timx_pwm_chy_handle; /* 定时器x句柄 */
/**
* @brief 通用定时器TIMX 通道Y PWM输出 初始化函数(使用PWM模式1)
* @note
* 通用定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
* 通用定时器的时钟为APB1时钟的2倍, 而APB1为42M, 所以定时器时钟 = 84Mhz
* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
* Ft=定时器工作频率,单位:Mhz
*
* @param arr: 自动重装值。
* @param psc: 预分频系数
* @retval 无
*/
void gtim_timx_pwm_chy_init(uint16_t arr, uint16_t psc)
{
GPIO_InitTypeDef gpio_init_struct;
GTIM_TIMX_PWM_CHY_GPIO_CLK_ENABLE(); /* 开启通道y的CPIO时钟 */
GTIM_TIMX_PWM_CHY_CLK_ENABLE(); /* 使能定时器时钟 */
gpio_init_struct.Pin = GTIM_TIMX_PWM_CHY_GPIO_PIN; /* 通道y的CPIO口 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推完输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
gpio_init_struct.Alternate = GTIM_TIMX_PWM_CHY_GPIO_AF; /* IO口REMAP设置, 是否必要查看头文件配置的说明! */
HAL_GPIO_Init(GTIM_TIMX_PWM_CHY_GPIO_PORT, &gpio_init_struct);
TIM_OC_InitTypeDef timx_oc_pwm_chy = {0}; /* 定时器输出句柄 */
g_timx_pwm_chy_handle.Instance = GTIM_TIMX_PWM; /* 定时器x */
g_timx_pwm_chy_handle.Init.Prescaler = psc; /* 预分频系数 */
g_timx_pwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_timx_pwm_chy_handle.Init.Period = arr; /* 自动重装载值 */
HAL_TIM_PWM_Init(&g_timx_pwm_chy_handle); /* 初始化PWM */
timx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1; /* 模式选择PWM1 */
timx_oc_pwm_chy.Pulse = 500 ; /* 设置比较值,此值用来确定占空比 */
timx_oc_pwm_chy.OCPolarity = TIM_OCPOLARITY_HIGH; /* 输出比较极性为高 */
HAL_TIM_PWM_ConfigChannel(&g_timx_pwm_chy_handle, &timx_oc_pwm_chy, GTIM_TIMX_PWM_CHY); /* 配置TIMx通道y */
HAL_TIM_PWM_Start(&g_timx_pwm_chy_handle, GTIM_TIMX_PWM_CHY); /* 开启对应PWM通道 */
}
/*********************************以下是通用定时器脉冲计数 速度 实验程序*************************************/
uint32_t g_timxchy_cnt_ofcnt = 0 ; /* 计数溢出次数 */
void gtim_timx_cnt_chy_init(uint16_t psc)
{
GPIO_InitTypeDef gpio_init_struct;
TIM_SlaveConfigTypeDef tim_slave_config = {0};
GTIM_TIMX_CNT_CHY_CLK_ENABLE(); /* 使能TIMx时钟 */
GTIM_TIMX_CNT_CHY_GPIO_CLK_ENABLE(); /* 开启GPIOA时钟 */
g_timx_cnt_chy_handle.Instance = GTIM_TIMX_CNT; /* 定时器x */
g_timx_cnt_chy_handle.Init.Prescaler = psc; /* 预分频系数 */
g_timx_cnt_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_timx_cnt_chy_handle.Init.Period = 65535; /* 自动重装载值 */
HAL_TIM_IC_Init(&g_timx_cnt_chy_handle);
gpio_init_struct.Pin = GTIM_TIMX_CNT_CHY_GPIO_PIN; /* 输入捕获的GPIO口 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */
gpio_init_struct.Alternate = GTIM_TIMX_CNT_CHY_GPIO_AF; /* 复用为捕获TIMx的通道 */
HAL_GPIO_Init(GTIM_TIMX_CNT_CHY_GPIO_PORT, &gpio_init_struct);
/* 从模式:外部触发模式1 */
tim_slave_config.SlaveMode = TIM_SLAVEMODE_EXTERNAL1; /* 从模式:外部触发模式1 */
tim_slave_config.InputTrigger = TIM_TS_TI1FP1; /* 输入触发:选择 TI1FP1(TIMX_CH1) 作为输入源 */
tim_slave_config.TriggerPolarity = TIM_TRIGGERPOLARITY_FALLING; /* 触发极性:上升沿 */
tim_slave_config.TriggerPrescaler = TIM_TRIGGERPRESCALER_DIV1; /* 触发预分频:无 */
tim_slave_config.TriggerFilter = 0x0; /* 滤波:本例中不需要任何滤波 */
HAL_TIM_SlaveConfigSynchronization(&g_timx_cnt_chy_handle, &tim_slave_config);
HAL_NVIC_SetPriority(GTIM_TIMX_CNT_IRQn, 1, 3); /* 设置中断优先级,抢占优先级1,子优先级3 */
HAL_NVIC_EnableIRQ(GTIM_TIMX_CNT_IRQn); /* 开启ITMx中断 */
__HAL_TIM_ENABLE_IT(&g_timx_cnt_chy_handle, TIM_IT_UPDATE); /* 使能更新中断 */
HAL_TIM_IC_Start(&g_timx_cnt_chy_handle, GTIM_TIMX_CNT_CHY1); /* 开始捕获TIMx的通道y */
}
/**
* @brief 通用定时器TIMX 通道Y 获取当前计数值
* @param 无
* @retval 当前计数值
*/
uint32_t gtim_timx_cnt_chy_get_count(void)
{
uint32_t count = 0;
//count = g_timxchy_cnt_ofcnt * 65535; /* 计算溢出次数对应的计数值 */
count += __HAL_TIM_GET_COUNTER(&g_timx_cnt_chy_handle); /* 加上当前CNT的值 */
// printf("gtim_timx count %d \r\n", count);
return count;
}
/**
* @brief 通用定时器TIMX 通道Y 重启计数器
* @param 无
* @retval 当前计数值
*/
void gtim_timx_cnt_chy_restart(void)
{
__HAL_TIM_DISABLE(&g_timx_cnt_chy_handle); /* 关闭定时器TIMX */
g_timxchy_cnt_ofcnt = 0; /* 累加器清零 */
__HAL_TIM_SET_COUNTER(&g_timx_cnt_chy_handle, 0); /* 计数器清零 */
__HAL_TIM_ENABLE(&g_timx_cnt_chy_handle); /* 使能定时器TIMX */
}
/**
* @brief 通用定时器TIMX 脉冲计数 更新中断服务函数
* @param 无
* @retval 无
*/
void GTIM_TIMX_CNT_IRQHandler(void)
{
/* 以下代码没有使用定时器HAL库共用处理函数来处理,而是直接通过判断中断标志位的方式 */
if(__HAL_TIM_GET_FLAG(&g_timx_cnt_chy_handle, TIM_FLAG_UPDATE) != RESET)
{
g_timxchy_cnt_ofcnt++; /* 累计溢出次数 */
}
__HAL_TIM_CLEAR_IT(&g_timx_cnt_chy_handle, TIM_IT_UPDATE);
}
/*********************************以下是通用定时器脉冲计数 位置 实验程序*************************************/
TIM_HandleTypeDef g_tim3_cnt_chy_handle; /* 定时器x句柄 */
uint32_t g_tim3chy_cnt_ofcnt = 0 ; /* 计数溢出次数 */
void gtim_tim3_cnt_chy_init(uint16_t psc)
{
GPIO_InitTypeDef gpio_init_struct;
TIM_SlaveConfigTypeDef tim_slave_config = {0};
GTIM_TIM3_CNT_CHY_CLK_ENABLE(); /* 使能TIMx时钟 */
GTIM_TIM3_CNT_CHY_GPIO_CLK_ENABLE(); /* 开启GPIOA时钟 */
g_tim3_cnt_chy_handle.Instance = GTIM_TIM3_CNT; /* 定时器x */
g_tim3_cnt_chy_handle.Init.Prescaler = psc; /* 预分频系数 */
g_tim3_cnt_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_tim3_cnt_chy_handle.Init.Period = 65535; /* 自动重装载值 */
HAL_TIM_IC_Init(&g_tim3_cnt_chy_handle);
gpio_init_struct.Pin = GTIM_TIM3_CNT_CHY_GPIO_PIN; /* 输入捕获的GPIO口 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */
gpio_init_struct.Alternate = GTIM_TIM3_CNT_CHY_GPIO_AF; /* 复用为捕获TIMx的通道 */
HAL_GPIO_Init(GTIM_TIM3_CNT_CHY_GPIO_PORT, &gpio_init_struct);
/* 从模式:外部触发模式1 */
tim_slave_config.SlaveMode = TIM_SLAVEMODE_EXTERNAL1; /* 从模式:外部触发模式1 */
tim_slave_config.InputTrigger = TIM_TS_TI2FP2; /* 输入触发:选择 TI1FP2(TIMX_CH2) 作为输入源 */
tim_slave_config.TriggerPolarity = TIM_TRIGGERPOLARITY_FALLING; /* 触发极性:上升沿 */
tim_slave_config.TriggerPrescaler = TIM_TRIGGERPRESCALER_DIV1; /* 触发预分频:无 */
tim_slave_config.TriggerFilter = 0x0; /* 滤波:本例中不需要任何滤波 */
HAL_TIM_SlaveConfigSynchronization(&g_tim3_cnt_chy_handle, &tim_slave_config);
HAL_NVIC_SetPriority(GTIM_TIM3_CNT_IRQn, 2, 2); /* 设置中断优先级,抢占优先级1,子优先级3 */
HAL_NVIC_EnableIRQ(GTIM_TIM3_CNT_IRQn); /* 开启ITMx中断 */
__HAL_TIM_ENABLE_IT(&g_tim3_cnt_chy_handle, TIM_IT_UPDATE); /* 使能更新中断 */
HAL_TIM_IC_Start(&g_tim3_cnt_chy_handle, GTIM_TIM3_CNT_CHY2); /* 开始捕获TIMx的通道y */
}
/**
* @brief 通用定时器TIMX 脉冲计数 更新中断服务函数
* @param 无
* @retval 无
*/
void GTIM_TIM3_CNT_IRQHandler(void)
{
/* 以下代码没有使用定时器HAL库共用处理函数来处理,而是直接通过判断中断标志位的方式 */
if(__HAL_TIM_GET_FLAG(&g_tim3_cnt_chy_handle, TIM_FLAG_UPDATE) != RESET)
{
g_tim3chy_cnt_ofcnt++; /* 累计溢出次数 */
}
__HAL_TIM_CLEAR_IT(&g_tim3_cnt_chy_handle, TIM_IT_UPDATE);
}
/*(.h)*/
#ifndef __GTIM_H
#define __GTIM_H
#include "./SYSTEM/sys/sys.h"
/******************************************TIMX PWM输出定义 *****************************************************/
#define GTIM_TIMX_PWM_CHY_GPIO_PORT GPIOF
#define GTIM_TIMX_PWM_CHY_GPIO_PIN GPIO_PIN_9
#define GTIM_TIMX_PWM_CHY_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOF_CLK_ENABLE(); }while(0) /* PF口时钟使能 */
#define GTIM_TIMX_PWM_CHY_GPIO_AF GPIO_AF9_TIM14 /* 端口复用到TIM14 */
#define GTIM_TIMX_PWM TIM14 /* TIM14 */
#define GTIM_TIMX_PWM_CHY TIM_CHANNEL_1 /* 通道Y, 1<= Y <=4 */
#define GTIM_TIMX_PWM_CHY_CCRX TIM14->CCR1 /* 通道Y的输出比较寄存器 */
#define GTIM_TIMX_PWM_CHY_CLK_ENABLE() do{ __HAL_RCC_TIM14_CLK_ENABLE(); }while(0) /* TIM14 时钟使能 */
/******************************************TIM2 速度 输入计数定义************************************************/
#define GTIM_TIMX_CNT_CHY_GPIO_PORT GPIOA
#define GTIM_TIMX_CNT_CHY_GPIO_PIN GPIO_PIN_0
#define GTIM_TIMX_CNT_CHY_GPIO_AF GPIO_AF1_TIM2 /* AF功能选择 */
#define GTIM_TIMX_CNT_CHY_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
#define GTIM_TIMX_CNT TIM2
#define GTIM_TIMX_CNT_IRQn TIM2_IRQn
#define GTIM_TIMX_CNT_IRQHandler TIM2_IRQHandler
#define GTIM_TIMX_CNT_CHY1 TIM_CHANNEL_1 /* 通道Y, 1<= Y <=2 */
#define GTIM_TIMX_CNT_CHY_CLK_ENABLE() do{ __HAL_RCC_TIM2_CLK_ENABLE(); }while(0) /* TIM2 时钟使能 */
/*****************************************TIM3 位置 输入计数定义*************************************************/
#define GTIM_TIM3_CNT_CHY_GPIO_PORT GPIOB
#define GTIM_TIM3_CNT_CHY_GPIO_PIN GPIO_PIN_5
#define GTIM_TIM3_CNT_CHY_GPIO_AF GPIO_AF2_TIM3 /* AF功能选择 */
#define GTIM_TIM3_CNT_CHY_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
#define GTIM_TIM3_CNT TIM3
#define GTIM_TIM3_CNT_IRQn TIM3_IRQn
#define GTIM_TIM3_CNT_IRQHandler TIM3_IRQHandler
#define GTIM_TIM3_CNT_CHY2 TIM_CHANNEL_2 /* 通道Y, 1<= Y <=2 */
#define GTIM_TIM3_CNT_CHY_CLK_ENABLE() do{ __HAL_RCC_TIM3_CLK_ENABLE(); }while(0) /* TIM2 时钟使能 */
/****************************************************************************************************************/
void gtim_timx_pwm_chy_init(uint16_t arr, uint16_t psc); /* 通用定时器 PWM初始化函数 */
void gtim_timx_cnt_chy_init(uint16_t psc); /* 通用定时器 脉冲计数初始化函数 */
uint32_t gtim_timx_cnt_chy_get_count(void); /* 通用定时器 获取脉冲计数 */
void gtim_timx_cnt_chy_restart(void); /* 通用定时器 重启计数器 */
void gtim_tim3_cnt_chy_init(uint16_t psc); /* 通用定时器 脉冲计数初始化函数 */
#endif
四:小结
本章内容就到此为了,如果需要源码可以点击这里 stm32控制行驶指定距离觉得本章内容对你有用的话可以给博主点个赞支持一下,本人后续还会分享相关的学习,想要一块学习的小伙伴可以关注一下博主,让我们一块成长。