- 编码器数值的获取及其数值实际意义:电机转速=编码器读数*当前频率/电机减速比/编码器精度/倍频数 (r/s)。
- 闭环控制的意义:有反馈的控制。速度闭环控制的过程:根据当前速度反馈,调整 PWM 值。
速度环 PID 控制原理
速度环 PID 控制的原理比较简单,只需要把 PID 控制流程中的控制对象换成电机速度就可以了。
我们先设置目标转速,系统会计算出偏差 e,然后将偏差输入到 PID 控制的三个环节中,PID 计算后的输出值用于控制 PWM 的占空比,进而控制电机的速度。
电机接口连接
- 其中 2、3、4、5 号接口为电机编码器接口,用于读取电机转速,2、5 号引脚给编码器供电,3、4 号引脚接单片机PB6(TIM4CH1)、PB7(TIM4CH2)引脚。速度闭环控制即有速度反馈的控制,由编码器提供速度反馈。
- 将反馈速度(当前速度_V_Now )与目标速度VT_arget 进行比较,_VTarget 大于VNow 则加大速度,VTarget 与_V_Now 之差越大加速度则越大。即设定VTarget 后,速度闭环控制使电机根据VTarget 与_V_Now 的关系自动加速使电机达到VT_arget ,同时达到_VTarget 后,如果由于外界原因使VNow 变化偏离VTarget ,电机也会自动调整回到VTarget 。
编码器
编码器是将信号(如比特流)或数据进行编制、转换为可用以通讯、传输和存储的信号形式的设备。编码器把角位移或直线位移转换成电信号,前者称为码盘,后者称为码尺。按照读出方式编码器可以分为接触式和非接触式两种;按照工作原理编码器可分为增量式和绝对式两类。
增量式编码器是将位移转换成周期性的电信号,再把这个电信号转变成计数脉冲,用脉冲的个数表示位移的大小。绝对式编码器的每一个位置对应一个确定的数字码,因此它的示值只与测量的起始和终止位置有关,而与测量的中间过程无关。
按照原理一般可分为:
- 光电编码器(光学式)
- 触点电刷式
- 霍尔编码器(磁式)
编码器的工作原理(正交式)
编码器能够将电机的机械几何位移转化为脉冲信号或数字量。本实验采用的编码器为增量式编码器,增量式编码器通常有两个输出信号,分别为A相和B相。电机带动霍尔码盘转动,在码盘的结构位上将电机在转动时会产生A、B两相的脉冲信号,且这两路脉冲信号的相位差为90度(即正交)配置定时器进行捕获计数,测得脉冲频率,再根据脉冲序列的频率确定电机的转速。同时,在此过程中,A,B相位触发的先后顺序可以确定转动的正反方向。
编码器电机的配置
-
M1与M2,高/低电平决定电机转动的方向(测试电机:直接向其接入12V以下电源,另一端接地,反之反转)
-
GND——接地 VCC——接电源3.3V
-
C1——霍尔编码器A相位 C2——霍尔编码器B相位
-
霍尔传感器编码器的测速模块,配有 11 线强磁码盘,A B 双相输出 共同利用下,通过计算可得出车轮转一圈时,脉冲数可达30_11_2=660个,单相也可以达到 330 个.(减速比为30:1)
关于编码器的工作模式:
-
模式1,即计数器仅在TI1的边沿处计数;
-
模式2,即计数器仅在TI2的边沿处计数;
-
模式3,即定时器在TI1双边沿处计数(具体请参考stm32f1系列中文参考手册)
实验电机编译器计算
速度闭环控制需要速度反馈,所以我们速度开环控制的程序基础上添加编码器库函数。同时添加一个定时 10ms 读取编码器数值的函数(使用定时器 TIM2)。
编码器读数与电机转速的关系:
10ms 读取一次编码器(即 100HZ)。我们提供的电机减速比为 20,电机自带的霍尔编码器精度为 13,AB 双相组合得到 4 倍频,则转 1 圈编码器读数为
20 _ 13 _ 4 = 1040。
电机转速=编码器读数 * 100 / 1040 (r/s)。由于电机本身误差,转 1圈编码器读数可能不等于 1040,这时可以添加误差系数。
代码解析
使用外部变量,用于定时读取编码器读数
//外部变量 extern 说明改变量已在其它文件定义
extern int Encoder; //当前速度
extern int TargetVelocity, Encoder,PWM; //目标速度、编码器读数、PWM 控制变量
extern float Velcity_Kp, Velcity_Ki, Velcity_Kd; //相关速度 PID 参数
extern int MortorRun; //允许电机控制标志位
定义相关结构体
GPIO_InitTypeDef GPIO_InitStructure; //定义一个引脚初始化的结构体
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;//定义一个定时器初始化的结构体
TIM_ICInitTypeDef TIM_ICInitStructure; //定义一个定时器编码器模式初始化的结构体
使能相关时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //使能 TIM4 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能 CPIOB 时钟
初始化相关 GPIO
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; //TIM4_CH1、TIM4_CH2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOB, &GPIO_InitStructure); //根据 GPIO_InitStructure 的参数初始化 GPIO
初始化定时器 TIM4
TIM_TimeBaseStructure.TIM_Period = 0xffff; //设定计数器自动重装值
TIM_TimeBaseStructure.TIM_Prescaler = 0; // 预分频器
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //选择时钟分频:不分频
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM 向上计数模式
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据
TIM_TimeBaseInitStruct 的参数初始化定时器 TIM4
初始化编码器
ICPolarity_Rising, TIM_ICPolarity_Rising); //使用编码器模式 3:CH1、CH2 同时计数,为四分频
TIM_ICStructInit(&TIM_ICInitStructure); //把 TIM_ICInitStruct 中的每一个参数按缺省值填入
TIM_ICInitStructure.TIM_ICFilter = 10; //设置滤波器长度
TIM_ICInit(TIM4, &TIM_ICInitStructure); //根 TIM_ICInitStructure 参数初始化定时器 TIM4 编码器模式
清除相关标志位、数值与相关使能
TIM_ClearFlag(TIM4, TIM_FLAG_Update);//清除 TIM 的更新标志位
TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE); //更新中断使能
TIM_SetCounter(TIM4,0); //初始化清空编码器数值
TIM_Cmd(TIM4, ENABLE); //使能定时器 4
实验操作
在第 ⑦节速度开环控制程序的基础上添加一下程序,同时把电机控制程序转移到 TIM2 中断服务函数:
- 编码器初始化函数使能 GPIO、TIM 时钟→初始化 GPIO 引脚→初始化定时器→初始化编码器→清除相关标志位、数值→使能相关功能、定时器。
- 读取编码器计数值函数读取编码器计数并判断方向正反,读取完后将计数值清零。
- 定时读取编码器数值初始化函数使能TIM2时钟→初始化定时器→使能中断→初始化中断分组→使能定时器。
- TIM2 中断服务函数调用读取编码器计数值函数并进行速度闭环控制。
- 主函数(主函数与第 1 节不同)调用相关初始化函数,在 while 循环中进行检测按键,以切换电机使能/失能状态。实验效果为:接通电源打开电源开关后,连接电机和控制器,按下用户按键,电机将进行速度闭环控制,再次按下按键电机停止。同时 OLED 显示屏会显示目标速度值(Target_V)、读取速度值(Current_V)和当前 PWM 值。
PID调参
- 速度环p、i、d各值先赋为0,先调p值(比例系数),整个过程只调p、i的值即可。将编码器获取到的脉冲数encoder_counter(实际值)和目标值Target_Velocity打印到vofa中去,观察encoder_counter值靠近Target_Velocity的趋势,响应太慢则加大p值,比例系数是构建输入与输出的线性关系的。
- 当encoder_counter值靠近但不超过Target_Velocity时,p值就差不多调好了,此时通过调大i值来使encoder_counter值更加靠近Target_Velocity,i值一般以0.001为单位来加。
Code
tb6612.c与tb6612.h 第⑦课一样
encoder.c
#include "motorencoder.h"
//外部变量 extern说明改变量已在其它文件定义
extern int Encoder; //当前速度
extern int TargetVelocity, Encoder,PWM; //目标速度、编码器读数、PWM控制变量
extern float Velcity_Kp, Velcity_Ki, Velcity_Kd; //相关速度PID参数
extern int MortorRun; //允许电机控制标志位
/**************************************************************************
函数功能:编码器初始化函数
入口参数:无
返回 值:无
**************************************************************************/
void MotorEncoder_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //定义一个引脚初始化的结构体
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;//定义一个定时器初始化的结构体
TIM_ICInitTypeDef TIM_ICInitStructure; //定义一个定时器编码器模式初始化的结构体
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //使能TIM4时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能CPIOB时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; //TIM4_CH1、TIM4_CH2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOB, &GPIO_InitStructure); //根据GPIO_InitStructure的参数初始化GPIO
TIM_TimeBaseStructure.TIM_Period = 0xffff; //设定计数器自动重装值
TIM_TimeBaseStructure.TIM_Prescaler = 0; // 预分频器
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //选择时钟分频:不分频
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct的参数初始化定时器TIM4
TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); //使用编码器模式3:CH1、CH2同时计数,为四分频
TIM_ICStructInit(&TIM_ICInitStructure); //把TIM_ICInitStruct 中的每一个参数按缺省值填入
TIM_ICInitStructure.TIM_ICFilter = 10; //设置滤波器长度
TIM_ICInit(TIM4, &TIM_ICInitStructure); //根TIM_ICInitStructure参数初始化定时器TIM4编码器模式
TIM_ClearFlag(TIM4, TIM_FLAG_Update);//清除TIM的更新标志位
TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE); //更新中断使能
TIM_SetCounter(TIM4,0); //初始化清空编码器数值
TIM_Cmd(TIM4, ENABLE); //使能定时器4
}
/**************************************************************************
函数功能:读取TIM4编码器数值
入口参数:无
返回 值:无
**************************************************************************/
int Read_Encoder(void)
{
int Encoder_TIM;
Encoder_TIM=TIM4->CNT; //读取计数
if(Encoder_TIM>0xefff)Encoder_TIM=Encoder_TIM-0xffff; //转化计数值为有方向的值,大于0正转,小于0反转。
//TIM4->CNT范围为0-0xffff,初值为0。
TIM4->CNT=0; //读取完后计数清零
return Encoder_TIM; //返回值
}
/**************************************************************************
函数功能:TIM4中断服务函数
入口参数:无
返回 值:无
**************************************************************************/
void TIM4_IRQHandler(void)
{
if(TIM4->SR&0X0001)//溢出中断
{
}
TIM4->SR&=~(1<<0);//清除中断标志位
}
/**************************************************************************
函数功能:通用定时器2初始化函数,
入口参数:自动重装载值 预分频系数 默认定时时钟为72MHZ时,两者共同决定定时中断时间
返回 值:无
**************************************************************************/
void EncoderRead_TIM2(u16 arr, u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStrue; //定义一个定时中断的结构体
NVIC_InitTypeDef NVIC_InitStrue; //定义一个中断优先级初始化的结构体
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //使能通用定时器2时钟
TIM_TimeBaseInitStrue.TIM_Period=arr; //计数模式为向上计数时,定时器从0开始计数,计数超过到arr时触发定时中断服务函数
TIM_TimeBaseInitStrue.TIM_Prescaler=psc; //预分频系数,决定每一个计数的时长
TIM_TimeBaseInitStrue.TIM_CounterMode=TIM_CounterMode_Up; //计数模式:向上计数
TIM_TimeBaseInitStrue.TIM_ClockDivision=TIM_CKD_DIV1; //一般不使用,默认TIM_CKD_DIV1
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStrue); //根据TIM_TimeBaseInitStrue的参数初始化定时器TIM2
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //使能TIM2中断,中断模式为更新中断:TIM_IT_Update
NVIC_InitStrue.NVIC_IRQChannel=TIM2_IRQn; //属于TIM2中断
NVIC_InitStrue.NVIC_IRQChannelCmd=ENABLE; //中断使能
NVIC_InitStrue.NVIC_IRQChannelPreemptionPriority=1; //抢占优先级为1级,值越小优先级越高,0级优先级最高
NVIC_InitStrue.NVIC_IRQChannelSubPriority=1; //响应优先级为1级,值越小优先级越高,0级优先级最高
NVIC_Init(&NVIC_InitStrue); //根据NVIC_InitStrue的参数初始化VIC寄存器,设置TIM2中断
TIM_Cmd(TIM2, ENABLE); //使能定时器TIM2
}
/**************************************************************************
函数功能:TIM2中断服务函数 定时读取编码器数值并进行速度闭环控制 10ms进入一次
入口参数:无
返回 值:无
**************************************************************************/
void TIM2_IRQHandler()
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update)==1) //当发生中断时状态寄存器(TIMx_SR)的bit0会被硬件置1
{
Encoder=Read_Encoder(); //读取当前编码器读数,即速度
if(MortorRun) //如果按键按下,运行电机控制程序
{
PWM=Velocity_FeedbackControl(TargetVelocity, Encoder); //速度环闭环控制
SetPWM(PWM); //设置PWM
}
else PWM=0,SetPWM(PWM); //如果按键再次按下,电机停止
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //状态寄存器(TIMx_SR)的bit0置0
}
}
return ControlVelocity; //返回速度控制值
}
根据增量式离散PID公式
ControlVelocity+=Kp[e(k)-e(k-1)]+Ki*e(k)+Kd[e(k)-2e(k-1)+e(k-2)]
-
e(k)代表本次偏差
-
e(k-1)代表上一次的偏差 以此类推
-
ControlVelocity代表增量输出
-
在我们的速度控制闭环系统里面,只使用PI控制
ControlVelocity+=Kp[e(k)-e(k-1)]+Ki*e(k)
/**************************************************************************
函数功能:速度闭环PID控制(实际为PI控制)
入口参数:目标速度 当前速度
返回 值:速度控制值
**************************************************************************/
int Velocity_FeedbackControl(int TargetVelocity, int CurrentVelocity)
{
int Bias; //定义相关变量
static int ControlVelocity, Last_bias; //静态变量,函数调用结束后其值依然存在
Bias=TargetVelocity-CurrentVelocity; //求速度偏差
ControlVelocity+=Velcity_Kp*(Bias-Last_bias)+Velcity_Ki*Bias; //增量式PI控制器
//Velcity_Kp*(Bias-Last_bias) 作用为限制加速度
//Velcity_Ki*Bias 速度控制值由Bias不断积分得到 偏差越大加速度越大
Last_bias=Bias;
return ControlVelocity; //返回速度控制值
}
encoder.h
#ifndef __ENCODER_H
#define __ENCODER_H
#include "sys.h"
#include "TB6612.h"
void MotorEncoder_Init(void);
int Read_Encoder(void);
void EncoderRead_TIM2(u16 arr, u16 psc);
int Velocity_FeedbackControl(int TargetVelocity, int CurrentVelocity);
#endif
main.c
#include "delay.h"
#include "usart.h"
#include "TB6612.h"
#include "motorencoder.h"
#include "usart.h"
#include "led.h"
#include "oled.h"
#include "key.h"
int TargetVelocity=50, Encoder, PWM; //目标速度、编码器读数、PWM控制变量
float Velcity_Kp=20, Velcity_Ki=5, Velcity_Kd; //相关速度PID参数
int MortorRun; //允许电机控制标志位
/**************************************************************************
函数功能:OLED显示屏显示内容
入口参数:无
返回 值:无
**************************************************************************/
void Oled_Show(void)
{
OLED_Refresh_Gram(); //刷新显示屏
OLED_ShowString(00,00,"VelocityFeedback"); //速度闭环控制
//显示目标速度,分正负
OLED_ShowString(00,10,"Target_V :");
if(TargetVelocity>=0)
{
OLED_ShowString(80,10,"+");
OLED_ShowNumber(90,10,TargetVelocity,5,12);
}
else
{
OLED_ShowString(80,10,"-");
OLED_ShowNumber(90,10,-TargetVelocity,5,12);
}
//显示当前速度,即编码器读数,分正负
OLED_ShowString(00,20,"Current_V:");
if(Encoder>=0)
{
OLED_ShowString(80,20,"+");
OLED_ShowNumber(90,20,Encoder,5,12);
}
else
{
OLED_ShowString(80,20,"-");
OLED_ShowNumber(90,20,-Encoder,5,12);
}
//显示速度控制值,即PWM,分正负
OLED_ShowString(00,30,"PWM :");
if(PWM>=0)
{
OLED_ShowString(80,30,"+");
OLED_ShowNumber(90,30,PWM,5,12);
}
else
{
OLED_ShowString(80,30,"-");
OLED_ShowNumber(90,30,-PWM,5,12);
}
}
/**************************************************************************
函数功能:主函数
入口参数:无
返回 值:无
**************************************************************************/
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组
delay_init(); //延迟函数初始化
JTAG_Set(JTAG_SWD_DISABLE); //关闭JTAG才能开启OLED显示屏
LED_Init(); //LED灯初始化
OLED_Init(); //OLED显示屏初始化
uart_init(9600); //串口初始化
MotorEncoder_Init(); //编码器初始化 使用定时器4
TB6612_Init(7199, 0); //电机驱动外设初始化 使用定时器3
EncoderRead_TIM2(7199, 99); //10ms读取一次编码器(即100HZ),电机减速比为20,霍尔编码器精度13,AB双相组合得到4倍频,
//则转1圈编码器读数为20*13*4=1040,电机转速=Encoder*100/1040r/s 使用定时器2
delay_ms(2000); //延迟等待初始化完成
while(1)
{
delay_ms(5);
LED=0; //LED灯闪烁
if(KEY_Scan())MortorRun=!MortorRun; //按下按键MortorRun取反
Oled_Show(); //OLED显示屏显示内容
//串口打印目标速度、当前速度、转速
printf("TargetVelocity:%d\r\n",TargetVelocity);
printf("Encoder:%d\r\n",Encoder);
printf("转速:%.3fr/s\r\n", Encoder/1.04);
}
}
评论(1)
您还未登录,请登录后发表或查看评论