我的风格就是先上代码!
main.c:
#include "stm32f10x.h"
#include "usart.h"
#include "delay.h"
#include "Codingmotor.h"
#include "pwm.h"
#include "tim2timing.h"
/
//此程序用到了三个计时器,TIM2用于计数得到时间算速度,TIM3输出pwm波控制电机,TIM4编码器模式记录电机编码器脉冲个数
float target_speed=0.12;//记录目标的速度,单位cm/ms
float actual_speed;//记录实际速度,单位cm/ms
float new_speed_difference;//记录当前速度差值
float before_speed_difference;//记录之前速度差值
float before2_speed_difference;//记录之前之前速度差值
float Cumulative_speed_difference;//记录累计速度
float add_speed;//记录添加速度
float kp=100;
float ki=90;
float kd=100;
int out=0;//占空比
int main(void)
{
delay_init();
uart_init(9600);//串口初始化,可以用来反映电机实时速度
TIM4_Mode_Config();//TIM4编码器模式初始化配置
TIM2_timingInit();//TIM2定时初始化
My_PWMTIM3_Init(1999,71);//TIM3输出pwm波初始化
TIM_SetCompare1(TIM3,0);//设置初始比较值为0
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//分配中断优先级
while(1)
{
actual_speed=(now_num-32767)*0.00967;//计算实际速度,用定时器编码器模式记录小车编码器在5ms内输出脉冲的个数,然后转换为路程,然后算速度
new_speed_difference=target_speed-actual_speed;//当前的速度误差
add_speed=kp*(new_speed_difference-before_speed_difference)+ki*new_speed_difference+kd*(new_speed_difference+before2_speed_difference-2*before_speed_difference);//增量式PID算法,小车更适合增量式
out+=add_speed;//计算实时反馈给pwm波的占空比
if(out>1900)//防止溢出使用
out=1900;
TIM_SetCompare1(TIM3,out);//调节占空比
before2_speed_difference=before_speed_difference;//之前之前的误差等于之前的误差
before_speed_difference=new_speed_difference; //现在的误差变成之前的误差,新一个反馈周期开始
}
}
//
///name:Vincent njw QQ:1504012979 glut
TIM2计数:
#include "tim2timing.h"
int now_num;//当前的编码模式记录的值
int t=0;
void TIM2_timingInit()
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;//时钟计数模式定义结构体
NVIC_InitTypeDef NVIC_InitStructure; //中断优先级定义结构体
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//使能TIM2时钟
TIM_TimeBaseStructure.TIM_Period = 0x1377; //5ms计算一次速度,(71+1)*5000/72000000=5ms
TIM_TimeBaseStructure.TIM_Prescaler = 71; //TIM2时钟预分频值
TIM_TimeBaseStructure.TIM_ClockDivision =TIM_CKD_DIV1 ;//设置时钟分割 T_dts = T_ck_int
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //允许TIM2溢出中断
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x00; //主优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x00; //从优先级
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//使能中断
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2, ENABLE);
}
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
{ now_num=TIM4->CNT;//
TIM4->CNT=0x7FFF;//每产生一次中断令TIM4->CNT的值为0x7FFF,里面记录的是编码器输出脉冲的上升和下降沿个数,我设置的上限为0xFFFF,设置0x7FFF的好处是可以知道是正转还是反转
//但是我的程序设置的是正转,如果要设置反转,把main.c里面的now_num-32767换成32767-now_num即可,原因是定时器编码器模式正转正计数,反转反计数
}
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
/
// name:Vincent njw QQ:1504012979 glut
一,硬件
还是我们常用的stm32f103c8t6(贫困大学生专属)。常见编码电机一个(AB相)当然也可以用编码器,一样的原理。L298N电机驱动,用于控制编码电机。还有一系列电源模块,这里我就不再详细介绍,因为我主要讲解的是PID这个算法和软件方面
二,软件
对于软件方面我在代码段有详细的注释讲解,在这里我给大家粗略的讲解一下,首先TIM4设置成编码器模式用于记录编码器在规定时间内(我设置的5ms)输出的脉冲个数(实际上是上升和下降沿个数),然后根据编码器的参数和电机的转速比和车轮周长算出在这个时间段内小车走过的路程。TIM2设置的是计数器模式,并开启了溢出中断,溢出时间为5ms,意思就是每五毫秒中断一次读取脉冲个数。然后把实际速度和目标速度比较,带入增量式PID算法中,把PID算法计算出来的值反馈给控制电机的pwm波的占空比,由此形成闭环控制。具体的操作我在代码段有详细讲解。
接下来我给大家讲一下我对PID算法我自己的理解和调参技巧,PID按照字面意思来讲就是比例积分微分。对于比例来说,它是实时的目标值和实际值的差,我们来举个例子,一辆车目标速度是5m/s,当前实际速度是0m/s,此时差值是5m/s,应该加速,当加速到2.5m/s,差值是2.5m/s,还是应该加速,不过反馈给输出的比例就比差值为5m/s时小了一半,接下来讲一下P调参技巧。对于P的调参首先要把I和D归零,然后确定数量级,遵循从小到大的原则,当P较小时会出现以下这种情况:
大家就会很好奇,实际为什么永远到不了目标,这是因为阻力的作用,我们知道电机轮子在运转时会受到空气阻力的影响,当P较小时,在一个微妙的时刻反馈给输出的值恰好等于空气阻力抵消的值 ,就会产生一个微妙的平衡,并一直保持这种平衡。这时,我们需要在同一个数量级的条件下适当的调大P的值,调大到实际值前期稍微超调,后期略小于实际值,这是很理想的状态,如下图:
这时我们固定P不动,调I,调I也是遵循从小到大的原则,且通常和P保持着相同的数量级,最后到实际前期略超调于目标,后期在目标处轻微振荡,如图:
这时我们固定PI不动,调D,也是从小到大且保持相同的数量级,到这个状态即可:
最后我对PID这个算法的理解就是,P让实际趋近于目标,I让这个趋近来的更快更剧烈,D则起到一个阻尼效应,让整个过程变得更加的丝滑,振荡情况更加的小。
对于PID算法有两种形式,增量式和位置式,这次给大家讲解的是位置式,增量式就是拿当前位置式的值减去上一次位置式的值然后累加起来反馈到输出,增量式相对而言会更加的复杂,不过大家搞清楚位置式之后,等以后要应用到增量式的时候会理解的很快,网上查查相关资料很快就可以理解。(我的小车用的增量式,大家可以结合起来看看)
Vincent njw,想让学习变得简单!