所需元件
STM32F103开发板、L298N一个、带编码器的直流电机一个(如下图所示,淘宝上有很多)
系统框图
通过系统框图,我们需要做两件事,一是要测速,二是要调节。测速目前流行的就是通过编码器测速,调节器我采用的时PI调节,PI调节器调节的参数少,而且能够消除静差,当然用PID调节器也行。
编码器
编码器的结构简化如下图:
在电机转轴上安装了一个磁环,在磁环的下方有一个霍尔传感器,在磁环转动过程中就在霍尔传感器的附近产生了变化的磁场,于是霍尔传感器就输出了脉冲信号。我所用的这个直流电机是1:48的减速电机,电机转轴每转动1圈,编码器输出13个脉冲信号,也就是说输出转轴转动1圈,编码器输出13x48=624个脉冲,再通过STM32编码器接口 4 倍频就是 624x4=2496 个脉冲信号,通过STM32定时器的计数值除以2496就是输出转轴转动的圈数。
所谓4倍频,如下图:
编码器中有两个线路,即A相和B相。我们以A相或B相为例,1个上升沿或者下降沿代表1个脉冲信号,由图中可知有2个上升沿或2个下降沿,即2个脉冲信号。而所谓的4倍频,就是把A、B相的上升沿和下降沿都加起来,一共8个,与之前的2个脉冲信号就是4倍,而所以要加起来做成4倍频,可以提高测量转速的精度。另外A、B相之间相差90度,从而可以判断电机的转向。如果电机正转,A相比B相先90度,也就是说A相已经上升沿了B还是低电平。
PI调节器
关于PID算法,可参考STM32——PID恒温控制
这里贴两张速度曲线图:
1、
2、
图1中设定目标值为400,从图上可以看出超调量还是比较小的,调节时间也比较短,调节的效果还是可以的。图2中,目标值每隔一段时间增加100,加到400后又设为100,整体的调节效果还是蛮不错的。(我程序中的PID参数套用的时候可能达不到图中的效果,这与电机以及编码器之间的差别有关,可适当在做调节)
主要程序
TIM_Encoder.c
#include "TIM_Encoder.h"
float RPM_1=0; //存储上一次测速结果
void TIM_Encoder_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //使能定时器4的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能PB端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; //端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始化GPIOB
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Prescaler = 0x0; // 预分频器
TIM_TimeBaseStructure.TIM_Period = 65535; //设定计数器自动重装值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //选择时钟分频:不分频
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式3
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_ICFilter = 10;
TIM_ICInit(TIM4, &TIM_ICInitStructure);
TIM_ClearFlag(TIM4, TIM_FLAG_Update); //清除TIM的更新标志位
TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
//Reset counter
TIM_SetCounter(TIM4,0);
TIM_Cmd(TIM4, ENABLE);
}
int GetTIMCounter(void) //获取计数值
{
int count=TIM_GetCounter(TIM4);
return count;
}
float GetRPM(int count) //计算转速
{
// int RPM=count/2496*2000+0.5;//30ms计算一次(pid.T=30),60000ms为1min,也就是1min计算了2000次,2496=13*4*48表示转动一圈的脉冲数,48表示1:48的减速比
float RPM=count*0.8f+0.5f; //等同于上式,2000/2496约等于0.8
if(RPM>1000) //过滤掉不合理的结果,仍然使用上次的速度,在按键设定速度的时候或者在减速为0时会有非常的大的错误测速结果,具体原因还未查清 {
{
return RPM_1;
}
RPM_1=RPM; //更新
return RPM;
}
PID.c
#include "PID.h"
PID pid;
//int time=0;
void PID_Init()
{
pid.Sv=400; //用户设定转速400
pid.Kp=0.3; //比例
pid.Ki=0.015; //积分
pid.Kd=0; //微分
pid.pwmcycle=100; //pwm周期100us
pid.T=30; //PID计算周期30ms
pid.OUT0=0;
pid.C1ms=0;
pid.SEk=0;
pid.Ek=0;
pid.Ek_1=0;
pid.DelEk=0;
pid.Dout=0;
pid.Iout=0;
pid.Pout=0;
}
void PID_Calc(float data) //pid计算
{
float out;
pid.Pv=data;
pid.Ek=pid.Sv-pid.Pv; //得到当前的偏差值
pid.Pout=pid.Kp*pid.Ek; //比例输出
pid.SEk+=pid.Ek; //历史偏差总和
if(pid.SEk<(-50))
{
pid.SEk=(-50);
}
pid.DelEk=pid.Ek-pid.Ek_1; //最近两次偏差之差
pid.Iout=pid.Ki*pid.SEk; //积分输出
if(pid.Iout<(-10))
{
pid.Iout=(-10);
}
pid.Dout=pid.Kd*pid.DelEk; //微分输出
out= pid.Pout+ pid.Iout+ pid.Dout;
if(out>pid.pwmcycle)
{
pid.OUT=pid.pwmcycle;
}
else if(out<=0)
{
pid.OUT=pid.OUT0;
}
else
{
pid.OUT=out+0.5f; //四舍五入
}
pid.Ek_1=pid.Ek; //更新偏差
pid.C1ms=0;
}
工程链接
链接:https://pan.baidu.com/s/1dSXgPf0gzSvTdjlMHyOZ7w
提取码:f8h1
PID调参比较麻烦,这里推荐一个ST官方的软件StmStdio,这个软件网上有很多教程,使用也比较简单。
链接:https://pan.baidu.com/s/1etsrBL80rCe_LouNEE1XEg
提取码:ckve